Pārlūkot izejas kodu

自动回复调整

yuqian 8 gadi atpakaļ
vecāks
revīzija
5e037e10e0

+ 2 - 0
.gitignore

@@ -15,3 +15,5 @@ ServiceWechat/ServiceWechat.Utility/obj/ServiceWechat.Utility.csproj.nuget.cache
15 15
 ServiceWechat/ServiceWechat.Utility/obj/ServiceWechat.Utility.csproj.nuget.g.props
16 16
 ServiceWechat/ServiceWechat.Utility/obj/ServiceWechat.Utility.csproj.nuget.g.targets
17 17
 /ServiceWechat/.vs/ServiceWechat/v15
18
+/ServiceWechat/ServiceWechat.Utility/obj/Release/netcoreapp2.0
19
+/ServiceWechat/ServiceWechat.Utility/bin/Release/netcoreapp2.0

+ 38 - 10
ServiceWechat/ServiceWechat/Controllers/WechatApis/HomeController.cs

@@ -9,6 +9,10 @@ using Senparc.Weixin.MP.MvcExtension;
9 9
 using Senparc.Weixin.MP.Entities.Request;
10 10
 using ServiceWechat.Api.MessageHandlers.CustomMessageHandler;
11 11
 using ServiceWechat.Api.Controllers.Base;
12
+using Microsoft.Extensions.Logging;
13
+using NLog;
14
+using System.Text;
15
+using System.IO;
12 16
 
13 17
 namespace ServiceWechat.Api.Controllers.WechatApis
14 18
 {
@@ -18,6 +22,7 @@ namespace ServiceWechat.Api.Controllers.WechatApis
18 22
         private readonly string Token;
19 23
         private readonly string AppId;
20 24
         private readonly string EncodingAESKey;
25
+        static Logger Logger = LogManager.GetCurrentClassLogger();
21 26
         public HomeController(IConfiguration configuration)
22 27
         {
23 28
             Token = configuration["WechatStatic:Token"];
@@ -36,8 +41,12 @@ namespace ServiceWechat.Api.Controllers.WechatApis
36 41
         [HttpGet]
37 42
         public string Verify(string signature, string timestamp, string nonce, string echostr)
38 43
         {
44
+
39 45
             if (CheckSignature.Check(signature, timestamp, nonce, Token))
46
+            {
47
+                Logger.Info($"接收微信开发验证");
40 48
                 return echostr; //返回随机字符串则表示验证通过
49
+            }
41 50
             return $"failed:{signature},{CheckSignature.GetSignature(timestamp, nonce, Token) }。如果您在浏览器中看到这条信息,表明此Url可以填入微信后台。";
42 51
         }
43 52
 
@@ -45,22 +54,41 @@ namespace ServiceWechat.Api.Controllers.WechatApis
45 54
         /// 用户发送消息后,微信平台自动Post一个请求到这里,并等待响应XML。
46 55
         /// </summary>
47 56
         [HttpPost]
48
-        public WeixinResult RecieveMsg(PostModel postModel)
57
+        public WeixinResult Verify(PostModel postModel)
49 58
         {
50
-            if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, Token))
59
+
60
+            CustomMessageHandler messageHandler = null;
61
+            try
51 62
             {
52
-                return new WeixinResult("参数错误");
53
-            }
63
+                if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, Token))
64
+                {
65
+                    return new WeixinResult("参数错误");
66
+                }
67
+
68
+                postModel.Token = Token;//根据自己后台的设置保持一致
69
+                postModel.EncodingAESKey = EncodingAESKey;//根据自己后台的设置保持一致
70
+                postModel.AppId = AppId;//根据自己后台的设置保持一致
54 71
 
55
-            postModel.Token = Token;//根据自己后台的设置保持一致
56
-            postModel.EncodingAESKey = EncodingAESKey;//根据自己后台的设置保持一致
57
-            postModel.AppId = AppId;//根据自己后台的设置保持一致
72
+                //StreamReader reader = new StreamReader(Request.Body, Encoding.);
73
+                //string text = reader.ReadToEnd();
58 74
 
59
-            //自定义MessageHandler,对微信请求的详细判断操作都在这里面。
60
-            var messageHandler = new CustomMessageHandler(Request.Body, postModel);//接收消息
61
-            messageHandler.Execute();//执行微信处理过程
75
+
76
+                //Logger.Info($"接收用户消息调用RecieveMsg 【{  text }】");
77
+                //自定义MessageHandler,对微信请求的详细判断操作都在这里面。
78
+                messageHandler = new CustomMessageHandler(Request.Body, postModel);//接收消息
79
+                messageHandler.Execute();//执行微信处理过程
80
+                return new WeixinResult(messageHandler);//返回结果
81
+            }
82
+            catch (Exception ex)
83
+            {
84
+                Logger.Error(ex);
85
+            }
62 86
             return new WeixinResult(messageHandler);//返回结果
63 87
         }
64 88
 
89
+        public string Test()
90
+        {
91
+            return "test";
92
+        }
65 93
     }
66 94
 }

+ 80 - 33
ServiceWechat/ServiceWechat/Controllers/WechatApis/MenuController.cs

@@ -1,14 +1,20 @@
1 1
 using System;
2 2
 using System.Collections.Generic;
3
+using System.IO;
3 4
 using System.Linq;
5
+using System.Text;
4 6
 using System.Threading.Tasks;
5 7
 using Microsoft.AspNetCore.Mvc;
6 8
 using Microsoft.Extensions.Configuration;
9
+using Microsoft.Extensions.Logging;
10
+using Newtonsoft.Json;
11
+using NLog;
7 12
 using Senparc.Weixin.MP;
8 13
 using Senparc.Weixin.MP.CommonAPIs;
9 14
 using Senparc.Weixin.MP.Containers;
10 15
 using Senparc.Weixin.MP.Entities.Menu;
11 16
 using ServiceWechat.Api.Controllers.Base;
17
+using ServiceWechat.Api.Models;
12 18
 
13 19
 // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
14 20
 
@@ -21,6 +27,7 @@ namespace ServiceWechat.Api.Controllers.WechatApis
21 27
         private readonly string AppId;
22 28
         private readonly string AppSecret;
23 29
         private readonly string EncodingAESKey;
30
+        static Logger Logger = LogManager.GetCurrentClassLogger();
24 31
         public MenuController(IConfiguration configuration)
25 32
         {
26 33
             Token = configuration["WechatStatic:Token"];
@@ -48,43 +55,83 @@ namespace ServiceWechat.Api.Controllers.WechatApis
48 55
         [HttpPost]
49 56
         public JsonResult CreateMenus()
50 57
         {
51
-            ButtonGroup btnGroup = new ButtonGroup();
58
+            StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8);
59
+            string text = reader.ReadToEnd();
60
+            var MenuButtons = JsonConvert.DeserializeObject<MenuButtons>(text);
52 61
 
53
-            //单击
54
-            btnGroup.button.Add(new SingleClickButton()
62
+            ButtonGroup btnGroup = new ButtonGroup();
63
+            foreach (var list in MenuButtons.Menus)
55 64
             {
56
-                name = "单击测试",
57
-                key = "OneClick",
58
-                type = ButtonType.click.ToString(),//默认已经设为此类型,这里只作为演示
65
+                if (list.Count > 0)
66
+                {
67
+                    SubButton subButton = null;
68
+                    foreach (var item in list)
69
+                    {
70
+                        if (item == null) continue;
59 71
 
60
-            });
72
+                        SingleButton button = null;
73
+                        if (list.Count <= 1)
74
+                        {
75
+
76
+                            if (item.Type == "click")
77
+                            {
78
+                                button = new SingleClickButton()
79
+                                {
80
+                                    name = item.Name,
81
+                                    key = item.Key,
82
+                                    type = item.Type,
83
+                                };
84
+                            }
85
+                            else if (item.Type == "view")
86
+                            {
87
+                                button = new SingleViewButton()
88
+                                {
89
+                                    url = item.Url,
90
+                                    name = item.Name
91
+                                };
92
+                            }
93
+                            btnGroup.button.Add(button);
94
+                        }
95
+                        else
96
+                        {
97
+                            if (subButton == null)
98
+                            {
99
+                                subButton = new SubButton()
100
+                                {
101
+                                    name = item.Name
102
+                                };
103
+                            }
104
+                            else
105
+                            {
106
+                                if (item.Type == "click")
107
+                                {
108
+                                    button = new SingleClickButton()
109
+                                    {
110
+                                        name = item.Name,
111
+                                        key = item.Key,
112
+                                        type = item.Type,
113
+                                    };
114
+                                }
115
+                                else if (item.Type == "view")
116
+                                {
117
+                                    button = new SingleViewButton()
118
+                                    {
119
+                                        url = item.Url,
120
+                                        name = item.Name
121
+                                    };
122
+                                }
123
+                                subButton.sub_button.Add(button);
124
+                            }
125
+                        }
126
+                    }
127
+
128
+                    if (subButton != null && subButton.sub_button.Count > 0)
129
+                    {
130
+                        btnGroup.button.Add(subButton);
131
+                    }
132
+                }
133
+            }
61 134
 
62
-            //二级菜单
63
-            var subButton = new SubButton()
64
-            {
65
-                name = "二级菜单"
66
-            };
67
-            subButton.sub_button.Add(new SingleClickButton()
68
-            {
69
-                key = "SubClickRoot_Text",
70
-                name = "返回文本"
71
-            });
72
-            subButton.sub_button.Add(new SingleClickButton()
73
-            {
74
-                key = "SubClickRoot_News",
75
-                name = "返回图文"
76
-            });
77
-            subButton.sub_button.Add(new SingleClickButton()
78
-            {
79
-                key = "SubClickRoot_Music",
80
-                name = "返回音乐"
81
-            });
82
-            subButton.sub_button.Add(new SingleViewButton()
83
-            {
84
-                url = "http://weixin.senparc.com",
85
-                name = "Url跳转"
86
-            });
87
-            btnGroup.button.Add(subButton);
88 135
 
89 136
             var result = CommonApi.CreateMenu(AppId, btnGroup);
90 137
             return new JsonResult(result);

+ 232 - 0
ServiceWechat/ServiceWechat/CryptCode/Cryptography.cs

@@ -0,0 +1,232 @@
1
+using System;
2
+using System.Collections.Generic;
3
+using System.Linq;
4
+using System.Text;
5
+using System.Security.Cryptography;
6
+using System.IO;
7
+using System.Net;
8
+namespace ServiceWechat.Api.CryptCode
9
+{
10
+    public class Cryptography
11
+    {
12
+        public static UInt32 HostToNetworkOrder(UInt32 inval)
13
+        {
14
+            UInt32 outval = 0;
15
+            for (int i = 0; i < 4; i++)
16
+                outval = (outval << 8) + ((inval >> (i * 8)) & 255);
17
+            return outval;
18
+        }
19
+
20
+        public static Int32 HostToNetworkOrder(Int32 inval)
21
+        {
22
+            Int32 outval = 0;
23
+            for (int i = 0; i < 4; i++)
24
+                outval = (outval << 8) + ((inval >> (i * 8)) & 255);
25
+            return outval;
26
+        }
27
+        /// <summary>
28
+        /// 解密方法
29
+        /// </summary>
30
+        /// <param name="Input">密文</param>
31
+        /// <param name="EncodingAESKey"></param>
32
+        /// <returns></returns>
33
+        /// 
34
+        public static string AES_decrypt(String Input, string EncodingAESKey, ref string appid)
35
+        {
36
+            byte[] Key;
37
+            Key = Convert.FromBase64String(EncodingAESKey + "=");
38
+            byte[] Iv = new byte[16];
39
+            Array.Copy(Key, Iv, 16);
40
+            byte[] btmpMsg = AES_decrypt(Input, Iv, Key);
41
+
42
+            int len = BitConverter.ToInt32(btmpMsg, 16);
43
+            len = IPAddress.NetworkToHostOrder(len);
44
+
45
+
46
+            byte[] bMsg = new byte[len];
47
+            byte[] bAppid = new byte[btmpMsg.Length - 20 - len];
48
+            Array.Copy(btmpMsg, 20, bMsg, 0, len);
49
+            Array.Copy(btmpMsg, 20 + len, bAppid, 0, btmpMsg.Length - 20 - len);
50
+            string oriMsg = Encoding.UTF8.GetString(bMsg);
51
+            appid = Encoding.UTF8.GetString(bAppid);
52
+
53
+
54
+            return oriMsg;
55
+        }
56
+
57
+        public static String AES_encrypt(String Input, string EncodingAESKey, string appid)
58
+        {
59
+            byte[] Key;
60
+            Key = Convert.FromBase64String(EncodingAESKey + "=");
61
+            byte[] Iv = new byte[16];
62
+            Array.Copy(Key, Iv, 16);
63
+            string Randcode = CreateRandCode(16);
64
+            byte[] bRand = Encoding.UTF8.GetBytes(Randcode);
65
+            byte[] bAppid = Encoding.UTF8.GetBytes(appid);
66
+            byte[] btmpMsg = Encoding.UTF8.GetBytes(Input);
67
+            byte[] bMsgLen = BitConverter.GetBytes(HostToNetworkOrder(btmpMsg.Length));
68
+            byte[] bMsg = new byte[bRand.Length + bMsgLen.Length + bAppid.Length + btmpMsg.Length];
69
+
70
+            Array.Copy(bRand, bMsg, bRand.Length);
71
+            Array.Copy(bMsgLen, 0, bMsg, bRand.Length, bMsgLen.Length);
72
+            Array.Copy(btmpMsg, 0, bMsg, bRand.Length + bMsgLen.Length, btmpMsg.Length);
73
+            Array.Copy(bAppid, 0, bMsg, bRand.Length + bMsgLen.Length + btmpMsg.Length, bAppid.Length);
74
+
75
+            return AES_encrypt(bMsg, Iv, Key);
76
+
77
+        }
78
+        private static string CreateRandCode(int codeLen)
79
+        {
80
+            string codeSerial = "2,3,4,5,6,7,a,c,d,e,f,h,i,j,k,m,n,p,r,s,t,A,C,D,E,F,G,H,J,K,M,N,P,Q,R,S,U,V,W,X,Y,Z";
81
+            if (codeLen == 0)
82
+            {
83
+                codeLen = 16;
84
+            }
85
+            string[] arr = codeSerial.Split(',');
86
+            string code = "";
87
+            int randValue = -1;
88
+            Random rand = new Random(unchecked((int)DateTime.Now.Ticks));
89
+            for (int i = 0; i < codeLen; i++)
90
+            {
91
+                randValue = rand.Next(0, arr.Length - 1);
92
+                code += arr[randValue];
93
+            }
94
+            return code;
95
+        }
96
+
97
+        private static String AES_encrypt(String Input, byte[] Iv, byte[] Key)
98
+        {
99
+            var aes = new RijndaelManaged();
100
+            //秘钥的大小,以位为单位
101
+            aes.KeySize = 256;
102
+            //支持的块大小
103
+            aes.BlockSize = 128;
104
+            //填充模式
105
+            aes.Padding = PaddingMode.PKCS7;
106
+            aes.Mode = CipherMode.CBC;
107
+            aes.Key = Key;
108
+            aes.IV = Iv;
109
+            var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);
110
+            byte[] xBuff = null;
111
+
112
+            using (var ms = new MemoryStream())
113
+            {
114
+                using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write))
115
+                {
116
+                    byte[] xXml = Encoding.UTF8.GetBytes(Input);
117
+                    cs.Write(xXml, 0, xXml.Length);
118
+                }
119
+                xBuff = ms.ToArray();
120
+            }
121
+            String Output = Convert.ToBase64String(xBuff);
122
+            return Output;
123
+        }
124
+
125
+        private static String AES_encrypt(byte[] Input, byte[] Iv, byte[] Key)
126
+        {
127
+            var aes = new RijndaelManaged();
128
+            //秘钥的大小,以位为单位
129
+            aes.KeySize = 256;
130
+            //支持的块大小
131
+            aes.BlockSize = 128;
132
+            //填充模式
133
+            //aes.Padding = PaddingMode.PKCS7;
134
+            aes.Padding = PaddingMode.None;
135
+            aes.Mode = CipherMode.CBC;
136
+            aes.Key = Key;
137
+            aes.IV = Iv;
138
+            var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);
139
+            byte[] xBuff = null;
140
+
141
+            #region 自己进行PKCS7补位,用系统自己带的不行
142
+            byte[] msg = new byte[Input.Length + 32 - Input.Length % 32];
143
+            Array.Copy(Input, msg, Input.Length);
144
+            byte[] pad = KCS7Encoder(Input.Length);
145
+            Array.Copy(pad, 0, msg, Input.Length, pad.Length);
146
+            #endregion
147
+
148
+            #region 注释的也是一种方法,效果一样
149
+            //ICryptoTransform transform = aes.CreateEncryptor();
150
+            //byte[] xBuff = transform.TransformFinalBlock(msg, 0, msg.Length);
151
+            #endregion
152
+
153
+            using (var ms = new MemoryStream())
154
+            {
155
+                using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write))
156
+                {
157
+                    cs.Write(msg, 0, msg.Length);
158
+                }
159
+                xBuff = ms.ToArray();
160
+            }
161
+
162
+            String Output = Convert.ToBase64String(xBuff);
163
+            return Output;
164
+        }
165
+
166
+        private static byte[] KCS7Encoder(int text_length)
167
+        {
168
+            int block_size = 32;
169
+            // 计算需要填充的位数
170
+            int amount_to_pad = block_size - (text_length % block_size);
171
+            if (amount_to_pad == 0)
172
+            {
173
+                amount_to_pad = block_size;
174
+            }
175
+            // 获得补位所用的字符
176
+            char pad_chr = chr(amount_to_pad);
177
+            string tmp = "";
178
+            for (int index = 0; index < amount_to_pad; index++)
179
+            {
180
+                tmp += pad_chr;
181
+            }
182
+            return Encoding.UTF8.GetBytes(tmp);
183
+        }
184
+        /**
185
+         * 将数字转化成ASCII码对应的字符,用于对明文进行补码
186
+         * 
187
+         * @param a 需要转化的数字
188
+         * @return 转化得到的字符
189
+         */
190
+        static char chr(int a)
191
+        {
192
+
193
+            byte target = (byte)(a & 0xFF);
194
+            return (char)target;
195
+        }
196
+        private static byte[] AES_decrypt(String Input, byte[] Iv, byte[] Key)
197
+        {
198
+            RijndaelManaged aes = new RijndaelManaged();
199
+            aes.KeySize = 256;
200
+            aes.BlockSize = 128;
201
+            aes.Mode = CipherMode.CBC;
202
+            aes.Padding = PaddingMode.None;
203
+            aes.Key = Key;
204
+            aes.IV = Iv;
205
+            var decrypt = aes.CreateDecryptor(aes.Key, aes.IV);
206
+            byte[] xBuff = null;
207
+            using (var ms = new MemoryStream())
208
+            {
209
+                using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
210
+                {
211
+                    byte[] xXml = Convert.FromBase64String(Input);
212
+                    byte[] msg = new byte[xXml.Length + 32 - xXml.Length % 32];
213
+                    Array.Copy(xXml, msg, xXml.Length);
214
+                    cs.Write(xXml, 0, xXml.Length);
215
+                }
216
+                xBuff = decode2(ms.ToArray());
217
+            }
218
+            return xBuff;
219
+        }
220
+        private static byte[] decode2(byte[] decrypted)
221
+        {
222
+            int pad = (int)decrypted[decrypted.Length - 1];
223
+            if (pad < 1 || pad > 32)
224
+            {
225
+                pad = 0;
226
+            }
227
+            byte[] res = new byte[decrypted.Length - pad];
228
+            Array.Copy(decrypted, 0, res, 0, decrypted.Length - pad);
229
+            return res;
230
+        }
231
+    }
232
+}

+ 221 - 0
ServiceWechat/ServiceWechat/CryptCode/WXBizMsgCrypt.cs

@@ -0,0 +1,221 @@
1
+using System;
2
+using System.Collections.Generic;
3
+using System.Linq;
4
+using System.Text;
5
+using System.Xml;
6
+using System.Collections;
7
+//using System.Web;
8
+using System.Security.Cryptography;
9
+//-40001 : 签名验证错误
10
+//-40002 :  xml解析失败
11
+//-40003 :  sha加密生成签名失败
12
+//-40004 :  AESKey 非法
13
+//-40005 :  appid 校验错误
14
+//-40006 :  AES 加密失败
15
+//-40007 : AES 解密失败
16
+//-40008 : 解密后得到的buffer非法
17
+//-40009 :  base64加密异常
18
+//-40010 :  base64解密异常
19
+namespace ServiceWechat.Api.CryptCode
20
+{
21
+    public class WXBizMsgCrypt
22
+    {
23
+        string m_sToken;
24
+        string m_sEncodingAESKey;
25
+        string m_sAppID;
26
+        enum WXBizMsgCryptErrorCode
27
+        {
28
+            WXBizMsgCrypt_OK = 0,
29
+            WXBizMsgCrypt_ValidateSignature_Error = -40001,
30
+            WXBizMsgCrypt_ParseXml_Error = -40002,
31
+            WXBizMsgCrypt_ComputeSignature_Error = -40003,
32
+            WXBizMsgCrypt_IllegalAesKey = -40004,
33
+            WXBizMsgCrypt_ValidateAppid_Error = -40005,
34
+            WXBizMsgCrypt_EncryptAES_Error = -40006,
35
+            WXBizMsgCrypt_DecryptAES_Error = -40007,
36
+            WXBizMsgCrypt_IllegalBuffer = -40008,
37
+            WXBizMsgCrypt_EncodeBase64_Error = -40009,
38
+            WXBizMsgCrypt_DecodeBase64_Error = -40010
39
+        };
40
+
41
+        //构造函数
42
+        // @param sToken: 公众平台上,开发者设置的Token
43
+        // @param sEncodingAESKey: 公众平台上,开发者设置的EncodingAESKey
44
+        // @param sAppID: 公众帐号的appid
45
+        public WXBizMsgCrypt(string sToken, string sEncodingAESKey, string sAppID)
46
+        {
47
+            m_sToken = sToken;
48
+            m_sAppID = sAppID;
49
+            m_sEncodingAESKey = sEncodingAESKey;
50
+        }
51
+
52
+
53
+        // 检验消息的真实性,并且获取解密后的明文
54
+        // @param sMsgSignature: 签名串,对应URL参数的msg_signature
55
+        // @param sTimeStamp: 时间戳,对应URL参数的timestamp
56
+        // @param sNonce: 随机串,对应URL参数的nonce
57
+        // @param sPostData: 密文,对应POST请求的数据
58
+        // @param sMsg: 解密后的原文,当return返回0时有效
59
+        // @return: 成功0,失败返回对应的错误码
60
+        public int DecryptMsg(string sMsgSignature, string sTimeStamp, string sNonce, string sPostData, ref string sMsg)
61
+        {
62
+            if (m_sEncodingAESKey.Length != 43)
63
+            {
64
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
65
+            }
66
+            XmlDocument doc = new XmlDocument();
67
+            XmlNode root;
68
+            string sEncryptMsg;
69
+            try
70
+            {
71
+                doc.LoadXml(sPostData);
72
+                root = doc.FirstChild;
73
+                sEncryptMsg = root["Encrypt"].InnerText;
74
+            }
75
+            catch (Exception)
76
+            {
77
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error;
78
+            }
79
+            //verify signature
80
+            int ret = 0;
81
+            ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature);
82
+            if (ret != 0)
83
+                return ret;
84
+            //decrypt
85
+            string cpid = "";
86
+            try
87
+            {
88
+                sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid);
89
+            }
90
+            catch (FormatException)
91
+            {
92
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecodeBase64_Error;
93
+            }
94
+            catch (Exception)
95
+            {
96
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
97
+            }
98
+            if (cpid != m_sAppID)
99
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateAppid_Error;
100
+            return 0;
101
+        }
102
+
103
+        //将企业号回复用户的消息加密打包
104
+        // @param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串
105
+        // @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp
106
+        // @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce
107
+        // @param sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
108
+        //						当return返回0时有效
109
+        // return:成功0,失败返回对应的错误码
110
+        public int EncryptMsg(string sReplyMsg, string sTimeStamp, string sNonce, ref string sEncryptMsg)
111
+        {
112
+            if (m_sEncodingAESKey.Length != 43)
113
+            {
114
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
115
+            }
116
+            string raw = "";
117
+            try
118
+            {
119
+                raw = Cryptography.AES_encrypt(sReplyMsg, m_sEncodingAESKey, m_sAppID);
120
+            }
121
+            catch (Exception)
122
+            {
123
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_EncryptAES_Error;
124
+            }
125
+            string MsgSigature = "";
126
+            int ret = 0;
127
+            ret = GenarateSinature(m_sToken, sTimeStamp, sNonce, raw, ref MsgSigature);
128
+            if (0 != ret)
129
+                return ret;
130
+            sEncryptMsg = "";
131
+
132
+            string EncryptLabelHead = "<Encrypt><![CDATA[";
133
+            string EncryptLabelTail = "]]></Encrypt>";
134
+            string MsgSigLabelHead = "<MsgSignature><![CDATA[";
135
+            string MsgSigLabelTail = "]]></MsgSignature>";
136
+            string TimeStampLabelHead = "<TimeStamp><![CDATA[";
137
+            string TimeStampLabelTail = "]]></TimeStamp>";
138
+            string NonceLabelHead = "<Nonce><![CDATA[";
139
+            string NonceLabelTail = "]]></Nonce>";
140
+            sEncryptMsg = sEncryptMsg + "<xml>" + EncryptLabelHead + raw + EncryptLabelTail;
141
+            sEncryptMsg = sEncryptMsg + MsgSigLabelHead + MsgSigature + MsgSigLabelTail;
142
+            sEncryptMsg = sEncryptMsg + TimeStampLabelHead + sTimeStamp + TimeStampLabelTail;
143
+            sEncryptMsg = sEncryptMsg + NonceLabelHead + sNonce + NonceLabelTail;
144
+            sEncryptMsg += "</xml>";
145
+            return 0;
146
+        }
147
+
148
+        public class DictionarySort : System.Collections.IComparer
149
+        {
150
+            public int Compare(object oLeft, object oRight)
151
+            {
152
+                string sLeft = oLeft as string;
153
+                string sRight = oRight as string;
154
+                int iLeftLength = sLeft.Length;
155
+                int iRightLength = sRight.Length;
156
+                int index = 0;
157
+                while (index < iLeftLength && index < iRightLength)
158
+                {
159
+                    if (sLeft[index] < sRight[index])
160
+                        return -1;
161
+                    else if (sLeft[index] > sRight[index])
162
+                        return 1;
163
+                    else
164
+                        index++;
165
+                }
166
+                return iLeftLength - iRightLength;
167
+
168
+            }
169
+        }
170
+        //Verify Signature
171
+        private static int VerifySignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, string sSigture)
172
+        {
173
+            string hash = "";
174
+            int ret = 0;
175
+            ret = GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash);
176
+            if (ret != 0)
177
+                return ret;
178
+            //System.Console.WriteLine(hash);
179
+            if (hash == sSigture)
180
+                return 0;
181
+            else
182
+            {
183
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateSignature_Error;
184
+            }
185
+        }
186
+
187
+        public static int GenarateSinature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, ref string sMsgSignature)
188
+        {
189
+            ArrayList AL = new ArrayList();
190
+            AL.Add(sToken);
191
+            AL.Add(sTimeStamp);
192
+            AL.Add(sNonce);
193
+            AL.Add(sMsgEncrypt);
194
+            AL.Sort(new DictionarySort());
195
+            string raw = "";
196
+            for (int i = 0; i < AL.Count; ++i)
197
+            {
198
+                raw += AL[i];
199
+            }
200
+
201
+            SHA1 sha;
202
+            ASCIIEncoding enc;
203
+            string hash = "";
204
+            try
205
+            {
206
+                sha = new SHA1CryptoServiceProvider();
207
+                enc = new ASCIIEncoding();
208
+                byte[] dataToHash = enc.GetBytes(raw);
209
+                byte[] dataHashed = sha.ComputeHash(dataToHash);
210
+                hash = BitConverter.ToString(dataHashed).Replace("-", "");
211
+                hash = hash.ToLower();
212
+            }
213
+            catch (Exception)
214
+            {
215
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ComputeSignature_Error;
216
+            }
217
+            sMsgSignature = hash;
218
+            return 0;
219
+        }
220
+    }
221
+}

+ 20 - 13
ServiceWechat/ServiceWechat/MessageHandlers/CustomMessageHandler/CustomMessageContext.cs

@@ -8,22 +8,29 @@ using Senparc.Weixin;
8 8
 
9 9
 namespace ServiceWechat.Api.MessageHandlers.CustomMessageHandler
10 10
 {
11
-    public class CustomMessageContext : IMessageContext<IRequestMessageBase, IResponseMessageBase>
11
+    public class CustomMessageContext : MessageContext<IRequestMessageBase, IResponseMessageBase>
12 12
     {
13
-        public string UserName { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
14
-        public DateTime LastActiveTime { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
15
-        public MessageContainer<IRequestMessageBase> RequestMessages { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
16
-        public MessageContainer<IResponseMessageBase> ResponseMessages { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
17
-        public int MaxRecordCount { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
18
-        public object StorageData { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
19
-        public double? ExpireMinutes { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
20
-        public AppStoreState AppStoreState { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
21
-
22
-        public event EventHandler<WeixinContextRemovedEventArgs<IRequestMessageBase, IResponseMessageBase>> MessageContextRemoved;
13
+        public CustomMessageContext()
14
+        {
15
+            base.MessageContextRemoved += CustomMessageContext_MessageContextRemoved;
16
+        }
23 17
 
24
-        public void OnRemoved()
18
+        void CustomMessageContext_MessageContextRemoved(object sender, WeixinContextRemovedEventArgs<IRequestMessageBase, IResponseMessageBase> e)
25 19
         {
26
-            throw new NotImplementedException();
20
+            /* 注意,这个事件不是实时触发的(当然你也可以专门写一个线程监控)
21
+             * 为了提高效率,根据WeixinContext中的算法,这里的过期消息会在过期后下一条请求执行之前被清除
22
+             */
23
+
24
+            var messageContext = e.MessageContext as CustomMessageContext;
25
+            if (messageContext == null)
26
+            {
27
+                return;//如果是正常的调用,messageContext不会为null
28
+            }
29
+
30
+            //TODO:这里根据需要执行消息过期时候的逻辑,下面的代码仅供参考
31
+
32
+            //Log.InfoFormat("{0}的消息上下文已过期",e.OpenId);
33
+            //api.SendMessage(e.OpenId, "由于长时间未搭理客服,您的客服状态已退出!");
27 34
         }
28 35
     }
29 36
 }

+ 40 - 11
ServiceWechat/ServiceWechat/MessageHandlers/CustomMessageHandler/CustomMessageHandler.cs

@@ -1,4 +1,7 @@
1
-using Senparc.Weixin.MP.Entities;
1
+using Microsoft.Extensions.Logging;
2
+using NLog;
3
+using Senparc.Weixin.Context;
4
+using Senparc.Weixin.MP.Entities;
2 5
 using Senparc.Weixin.MP.Entities.Request;
3 6
 using Senparc.Weixin.MP.MessageHandlers;
4 7
 using System;
@@ -9,12 +12,14 @@ using System.Threading.Tasks;
9 12
 
10 13
 namespace ServiceWechat.Api.MessageHandlers.CustomMessageHandler
11 14
 {
12
-    public class CustomMessageHandler : MessageHandler<CustomMessageContext>
15
+    public partial class CustomMessageHandler : MessageHandler<MessageContext<IRequestMessageBase, IResponseMessageBase>>
13 16
     {
17
+        static Logger Logger = LogManager.GetCurrentClassLogger();
18
+
14 19
         public CustomMessageHandler(Stream inputStream, PostModel postModel)
15 20
             : base(inputStream, postModel)
16 21
         {
17
-
22
+            Logger.Info($"实例化CustomMessageHandler");
18 23
         }
19 24
 
20 25
         /// <summary>
@@ -24,7 +29,8 @@ namespace ServiceWechat.Api.MessageHandlers.CustomMessageHandler
24 29
         /// <returns></returns>
25 30
         public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
26 31
         {
27
-            var responseMessage = base.CreateResponseMessage<ResponseMessageText>(); //ResponseMessageText也可以是News等其他类型
32
+            var responseMessage = this.CreateResponseMessage<ResponseMessageText>(); //ResponseMessageText也可以是News等其他类型
33
+            Logger.Info($"我是默认的自动回复 么么哒~~~。");
28 34
             responseMessage.Content = "我是默认的自动回复 么么哒~~~。";
29 35
             return responseMessage;
30 36
         }
@@ -38,19 +44,42 @@ namespace ServiceWechat.Api.MessageHandlers.CustomMessageHandler
38 44
         /// <returns></returns>
39 45
         public override IResponseMessageBase OnTextRequest(RequestMessageText requestMessage)
40 46
         {
41
-            //requestMessage.FromUserName也可以直接写成base.WeixinOpenId
42
-            var openId = base.WeixinOpenId;
43
-            //客户发来的文本信息
44
-            var content = requestMessage.Content;
45 47
 
46
-            //【TODO:查询已配置的文本回复信息】
48
+            ////requestMessage.FromUserName也可以直接写成base.WeixinOpenId
49
+            //var openId = base.WeixinOpenId;
50
+            ////客户发来的文本信息
51
+            //var content = requestMessage.Content;
52
+
53
+            //Logger.Info($"您的OpenID是{openId}。\r\n您发送了文字信息:{content}, requestMessage.MsgId={requestMessage.MsgId},requestMessage.MsgId={requestMessage.CreateTime}");
54
+            ////【TODO:查询已配置的文本回复信息】
55
+
56
+            //var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
57
+            //responseMessage.Content = $"您的OpenID是{openId}。\r\n您发送了文字信息:{content}, requestMessage.MsgId={requestMessage.MsgId},requestMessage.MsgId={requestMessage.CreateTime}";
58
+            //Logger.Info($"responseMessage.FromUserName = {responseMessage.FromUserName}   responseMessage.MsgType={responseMessage.MsgType},responseMessage.ToUserName={responseMessage.ToUserName}");
59
+            //return responseMessage;
60
+
47 61
 
48 62
 
49
-            var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
50
-            responseMessage.Content = $"您的OpenID是{openId}。\r\n您发送了文字信息:{content}";
63
+            var responseMessage = ResponseMessageBase.CreateFromRequestMessage<ResponseMessageText>(requestMessage);
64
+            responseMessage.Content = $"您的OpenID是{requestMessage.FromUserName}。\r\n您发送了文字信息:{requestMessage.Content}, requestMessage.MsgId={requestMessage.MsgId},requestMessage.MsgId={requestMessage.CreateTime}";
51 65
             return responseMessage;
52 66
         }
53 67
 
54 68
 
69
+        public override void OnExecuting()
70
+        {
71
+            //测试MessageContext.StorageData
72
+            if (CurrentMessageContext.StorageData == null)
73
+            {
74
+                CurrentMessageContext.StorageData = 0;
75
+            }
76
+            base.OnExecuting();
77
+        }
78
+
79
+        public override void OnExecuted()
80
+        {
81
+            base.OnExecuted();
82
+            CurrentMessageContext.StorageData = ((int)CurrentMessageContext.StorageData) + 1;
83
+        }
55 84
     }
56 85
 }

+ 59 - 0
ServiceWechat/ServiceWechat/MessageHandlers/CustomMessageHandler/CustomMessageHandler_Events.cs

@@ -0,0 +1,59 @@
1
+using System;
2
+using System.Diagnostics;
3
+using System.Linq;
4
+using System.Web;
5
+using Senparc.Weixin.MP.Agent;
6
+using Senparc.Weixin.Context;
7
+using Senparc.Weixin.MP.Entities;
8
+using Senparc.Weixin.MP.Helpers;
9
+using Senparc.Weixin.MP.MessageHandlers;
10
+
11
+
12
+namespace ServiceWechat.Api.MessageHandlers.CustomMessageHandler
13
+{
14
+    /// <summary>
15
+    /// 自定义MessageHandler
16
+    /// </summary>
17
+    public partial class CustomMessageHandler
18
+    {
19
+
20
+        /// <summary>
21
+        /// 菜单点击事件
22
+        /// </summary>
23
+        /// <param name="requestMessage"></param>
24
+        /// <returns></returns>
25
+        public override IResponseMessageBase OnEvent_ClickRequest(RequestMessageEvent_Click requestMessage)
26
+        {
27
+            var responseMessage = ResponseMessageBase.CreateFromRequestMessage<ResponseMessageText>(requestMessage);
28
+            responseMessage.Content = "您刚才发送了ENTER事件请求。";
29
+            return responseMessage;
30
+        }
31
+        /// <summary>
32
+        /// 订阅(关注)事件
33
+        /// </summary>
34
+        /// <returns></returns>
35
+        public override IResponseMessageBase OnEvent_SubscribeRequest(RequestMessageEvent_Subscribe requestMessage)
36
+        {
37
+            return EventProcess(6, requestMessage);
38
+        }
39
+
40
+        /// <summary>
41
+        /// 退订
42
+        /// 实际上用户无法收到非订阅账号的消息,所以这里可以随便写。
43
+        /// unsubscribe事件的意义在于及时删除网站应用中已经记录的OpenID绑定,消除冗余数据。并且关注用户流失的情况。
44
+        /// </summary>
45
+        /// <returns></returns>
46
+        public override IResponseMessageBase OnEvent_UnsubscribeRequest(RequestMessageEvent_Unsubscribe requestMessage)
47
+        {
48
+            return EventProcess(7, requestMessage);
49
+        }
50
+
51
+        private IResponseMessageBase EventProcess(int type, RequestMessageEventBase requestMessage)
52
+        {
53
+            var responseMessage = ResponseMessageBase.CreateFromRequestMessage<ResponseMessageText>(requestMessage);
54
+            responseMessage.Content = "关注或者退订。";
55
+            return responseMessage;
56
+        }
57
+
58
+    }
59
+}

+ 23 - 0
ServiceWechat/ServiceWechat/Models/MenuInput.cs

@@ -0,0 +1,23 @@
1
+using Senparc.Weixin.MP;
2
+using Senparc.Weixin.MP.Entities.Menu;
3
+using System;
4
+using System.Collections.Generic;
5
+using System.Linq;
6
+using System.Threading.Tasks;
7
+
8
+namespace ServiceWechat.Api.Models
9
+{
10
+    public class MenuButtons
11
+    {
12
+        public List<List<MenuItem>> Menus { get; set; } = new List<List<MenuItem>>();
13
+    }
14
+
15
+    public class MenuItem
16
+    {
17
+        public int Level { get; set; }
18
+        public string Name { get; set; } = "";
19
+        public string Key { get; set; } = "OneClick";
20
+        public string Type { get; set; } = ButtonType.click.ToString();
21
+        public string Url { get; set; } = "";
22
+    }
23
+}

+ 10 - 3
ServiceWechat/ServiceWechat/ServiceWechat.Api.csproj

@@ -11,10 +11,11 @@
11 11
   </ItemGroup>
12 12
 
13 13
   <ItemGroup>
14
-    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
14
+    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
15 15
     <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.2" />
16
-    <PackageReference Include="Senparc.Weixin" Version="4.18.9" />
17
-    <PackageReference Include="Senparc.Weixin.MP" Version="14.8.11" />
16
+    <PackageReference Include="NLog.Web.AspNetCore" Version="4.4.1" />
17
+    <PackageReference Include="Senparc.Weixin" Version="4.19.0" />
18
+    <PackageReference Include="Senparc.Weixin.MP" Version="14.9.0" />
18 19
     <PackageReference Include="Senparc.Weixin.MP.MVC" Version="5.0.0" />
19 20
   </ItemGroup>
20 21
 
@@ -26,4 +27,10 @@
26 27
     <ProjectReference Include="..\ServiceWechat.Utility\ServiceWechat.Utility.csproj" />
27 28
   </ItemGroup>
28 29
 
30
+  <ItemGroup>
31
+    <Content Update="StaticConfig\nlog.config">
32
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
33
+    </Content>
34
+  </ItemGroup>
35
+
29 36
 </Project>

+ 7 - 1
ServiceWechat/ServiceWechat/Startup.cs

@@ -8,6 +8,8 @@ using Microsoft.Extensions.Configuration;
8 8
 using Microsoft.Extensions.DependencyInjection;
9 9
 using Microsoft.Extensions.Logging;
10 10
 using Microsoft.Extensions.Options;
11
+using NLog.Extensions.Logging;
12
+using NLog.Web;
11 13
 using Senparc.Weixin.MP.Containers;
12 14
 
13 15
 namespace ServiceWechat.Api
@@ -32,7 +34,7 @@ namespace ServiceWechat.Api
32 34
         }
33 35
 
34 36
         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
35
-        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
37
+        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
36 38
         {
37 39
             if (env.IsDevelopment())
38 40
             {
@@ -41,6 +43,10 @@ namespace ServiceWechat.Api
41 43
 
42 44
             app.UseMvc();
43 45
 
46
+            #region NLog
47
+            loggerFactory.AddNLog();//添加NLog
48
+            env.ConfigureNLog("StaticConfig/nlog.config");//读取Nlog配置文件
49
+            #endregion
44 50
 
45 51
             AccessTokenContainer.Register(AppId, AppSecret);
46 52
         }

+ 30 - 0
ServiceWechat/ServiceWechat/StaticConfig/nlog.config

@@ -0,0 +1,30 @@
1
+<?xml version="1.0" encoding="utf-8" ?>
2
+<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
3
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+      autoReload="true"
5
+      internalLogLevel="Warn"
6
+      internalLogFile="internal-nlog.txt">
7
+
8
+  <!-- define various log targets -->
9
+  <targets>
10
+    <!-- write logs to file -->
11
+
12
+    <target xsi:type="File" name="allfile" fileName="nlog-all-${shortdate}.log"
13
+                 layout="${longdate}|${logger}|${uppercase:${level}}|${message} ${exception}" />
14
+
15
+
16
+    <target xsi:type="File" name="ownFile-web" fileName="${basedir}/logs/${shortdate}/${uppercase:${level}}.log"
17
+             layout="${longdate}|${logger}|${uppercase:${level}}|${message}|${exception}| ${stacktrace} ${event-context:item=stacktrace}" />
18
+
19
+    <target xsi:type="Null" name="blackhole" />
20
+  </targets>
21
+
22
+  <rules>
23
+    <!--All logs, including from Microsoft-->
24
+    <logger name="*" minlevel="Trace" writeTo="allfile" />
25
+
26
+    <!--Skip Microsoft logs and so log only own logs-->
27
+    <logger name="Microsoft.*" minlevel="Trace" writeTo="blackhole" final="true" />
28
+    <logger name="*" minlevel="Trace" writeTo="ownFile-web" />
29
+  </rules>
30
+</nlog>