From c87d61d03b48a6f55c0a0819b9be522f77e3c9a0 Mon Sep 17 00:00:00 2001
From: sunpengfei <i@angelzzz.com>
Date: 星期一, 01 十二月 2025 14:48:40 +0800
Subject: [PATCH] feat:开发

---
 ApiTools.Core/Utils/WxmpUtils/Crypto/WXBizMsgCrypt.cs                          |  221 +++++++++++++++++
 ApiTools.Core/Models/WxmpUtils/Models/WxmpSubscribMessageNotifyRequestQuery.cs |   23 +
 ApiTools.Core/Utils/WxmpUtils/Crypto/Cryptography.cs                           |  232 +++++++++++++++++
 ApiTools.Core/ApiTools.Core.csproj                                             |    4 
 ApiTools.Web.Entry/appsettings.json                                            |    9 
 ApiTools.Core/Utils/WxmpUtils/Crypto/Sample.cs                                 |   82 ++++++
 ApiTools.Web.Entry/Controllers/WxmpController.cs                               |   43 ++
 ApiTools.Core/ApiTools.Core.xml                                                |   53 +++
 ApiTools.Core/Utils/WxmpUtils/Crypto/Readme.txt                                |    4 
 ApiTools.Application/WxUtils/Commands/WxmpSubscribMessageCommandHandler.cs     |   27 +
 ApiTools.Core/Models/WxmpUtils/Commands/WxmpSubscribMessageNotifyCommand.cs    |   29 +
 11 files changed, 696 insertions(+), 31 deletions(-)

diff --git a/ApiTools.Application/WxUtils/Commands/WxmpSubscribMessageCommandHandler.cs b/ApiTools.Application/WxUtils/Commands/WxmpSubscribMessageCommandHandler.cs
index 3e600b2..bfdf10c 100644
--- a/ApiTools.Application/WxUtils/Commands/WxmpSubscribMessageCommandHandler.cs
+++ b/ApiTools.Application/WxUtils/Commands/WxmpSubscribMessageCommandHandler.cs
@@ -1,5 +1,7 @@
 锘縰sing Aop.Api.Domain;
 using ApiTools.Core;
+using Furion;
+using Furion.HttpRemote;
 using log4net.Core;
 using MediatR;
 using Microsoft.AspNetCore.Http;
@@ -15,14 +17,14 @@
     public class WxmpSubscribMessageCommandHandler(
             ILogger<WxmpSubscribMessageCommandHandler> logger,
             WxmpUtils utils,
-            IHttpContextAccessor httpContextAccessor
+            IHttpRemoteService httpRemoteService
         ) :
         IRequestHandler<SendWxmpSubscribMessageCommand, Guid>,
         IRequestHandler<WxmpSubscribMessageNotifyCommand, bool>
     {
         private readonly ILogger<WxmpSubscribMessageCommandHandler> logger = logger;
         private readonly WxmpUtils utils = utils;
-        private readonly IHttpContextAccessor httpContextAccessor = httpContextAccessor;
+        private readonly IHttpRemoteService httpRemoteService = httpRemoteService;
 
         /// <summary>
         /// 寰俊灏忕▼搴忓彂閫佽闃呮秷鎭�
@@ -51,15 +53,20 @@
         /// <returns></returns>
         public async Task<bool> Handle(WxmpSubscribMessageNotifyCommand request, CancellationToken cancellationToken)
         {
-            var req = httpContextAccessor.HttpContext.Request;
-            logger.LogInformation($"寰俊灏忕▼搴忚闃呮秷鎭�氱煡query锛歿req.QueryString.Value}");
+            var env = App.GetConfig<string>("Environment");
+            if (env == "Product")
+            {
+                try
+                {
+                    var json = request.ToJson();
+                    await httpRemoteService.PostAsStringAsync("http://118.178.252.28:8780/api/common/wxmp/wxmpSubscribMessageNotify",
+                        builder => builder.SetJsonContent(json));
+                }
+                catch
+                {
 
-            req.EnableBuffering();
-            req.Body.Position = 0;
-            using var reader = new StreamReader(req.Body, Encoding.UTF8, leaveOpen: true);
-            var body = await reader.ReadToEndAsync();
-            logger.LogInformation($"寰俊灏忕▼搴忚闃呮秷鎭�氱煡body锛歿body}");
-            req.Body.Position = 0;
+                }
+            }
             return true;
         }
     }
diff --git a/ApiTools.Core/ApiTools.Core.csproj b/ApiTools.Core/ApiTools.Core.csproj
index bea4bf4..15bd617 100644
--- a/ApiTools.Core/ApiTools.Core.csproj
+++ b/ApiTools.Core/ApiTools.Core.csproj
@@ -39,4 +39,8 @@
 	  </None>
 	</ItemGroup>
 
+	<ItemGroup>
+	  <Folder Include="Utils\WxmpUtils\Crypto\" />
+	</ItemGroup>
+
 </Project>
diff --git a/ApiTools.Core/ApiTools.Core.xml b/ApiTools.Core/ApiTools.Core.xml
index ffe0338..5d582b5 100644
--- a/ApiTools.Core/ApiTools.Core.xml
+++ b/ApiTools.Core/ApiTools.Core.xml
@@ -3323,22 +3323,52 @@
             寰俊灏忕▼搴忚闃呮秷鎭�氱煡
             </summary>
         </member>
-        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyRequestQuery.Signature">
+        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyCommand.Code">
+            <summary>
+            灏忕▼搴忎唬鐮�
+            </summary>
+        </member>
+        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyCommand.OpenId">
+            <summary>
+            鐢ㄦ埛寮�鏀綢d
+            </summary>
+        </member>
+        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyCommand.CreateTime">
+            <summary>
+            鍒涘缓鏃堕棿
+            </summary>
+        </member>
+        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyCommand.MsgType">
+            <summary>
+            娑堟伅绫诲瀷
+            </summary>
+        </member>
+        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyCommand.Event">
+            <summary>
+            浜嬩欢
+            </summary>
+        </member>
+        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyCommand.Content">
+            <summary>
+            鍐呭
+            </summary>
+        </member>
+        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyRequestQuery.signature">
             <summary>
             绛惧悕
             </summary>
         </member>
-        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyRequestQuery.Timestamp">
+        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyRequestQuery.timestamp">
             <summary>
             鏃堕棿鎴�
             </summary>
         </member>
-        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyRequestQuery.Nonce">
+        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyRequestQuery.nonce">
             <summary>
             闅忔満鏁�
             </summary>
         </member>
-        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyRequestQuery.Echostr">
+        <member name="P:ApiTools.Core.WxmpSubscribMessageNotifyRequestQuery.echostr">
             <summary>
             闅忔満瀛楃涓�
             </summary>
@@ -7343,5 +7373,20 @@
             <param name="xmlDoc"></param>
             <returns></returns>
         </member>
+        <member name="M:Tencent.Cryptography.AES_decrypt(System.String,System.String,System.String@)">
+            <summary>
+            瑙e瘑鏂规硶
+            </summary>
+            <param name="Input">瀵嗘枃</param>
+            <param name="EncodingAESKey"></param>
+            <returns></returns>
+            
+        </member>
+        <member name="M:Tencent.Cryptography.chr(System.Int32)">
+            灏嗘暟瀛楄浆鍖栨垚ASCII鐮佸搴旂殑瀛楃锛岀敤浜庡鏄庢枃杩涜琛ョ爜
+            
+            @param a 闇�瑕佽浆鍖栫殑鏁板瓧
+            @return 杞寲寰楀埌鐨勫瓧绗�
+        </member>
     </members>
 </doc>
diff --git a/ApiTools.Core/Models/WxmpUtils/Commands/WxmpSubscribMessageNotifyCommand.cs b/ApiTools.Core/Models/WxmpUtils/Commands/WxmpSubscribMessageNotifyCommand.cs
index 55575cc..afad571 100644
--- a/ApiTools.Core/Models/WxmpUtils/Commands/WxmpSubscribMessageNotifyCommand.cs
+++ b/ApiTools.Core/Models/WxmpUtils/Commands/WxmpSubscribMessageNotifyCommand.cs
@@ -1,4 +1,5 @@
 锘縰sing MediatR;
+using Newtonsoft.Json;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -10,15 +11,35 @@
     /// <summary>
     /// 寰俊灏忕▼搴忚闃呮秷鎭�氱煡
     /// </summary>
-    [Resource([EnumResourceController.CommonServerWxmpUtils], Method = EnumResourceMethod.Get)]
+    [Resource([EnumResourceController.CommonServerWxmpUtils], AllowAnonymous = true)]
     public class WxmpSubscribMessageNotifyCommand : IRequest<bool>
     {
-
-
+        /// <summary>
+        /// 灏忕▼搴忎唬鐮�
+        /// </summary>
+        public string Code { get; set; }
+        /// <summary>
+        /// 鐢ㄦ埛寮�鏀綢d
+        /// </summary>
+        public string OpenId { get; set; }
         public string ToUserName { get; set; }
         public string FromUserName { get; set; }
-        public DateTime CreateTime { get; set; }
+        /// <summary>
+        /// 鍒涘缓鏃堕棿
+        /// </summary>
+        public int CreateTime { get; set; }
+        /// <summary>
+        /// 娑堟伅绫诲瀷
+        /// </summary>
         public string MsgType { get; set; }
+        /// <summary>
+        /// 浜嬩欢
+        /// </summary>
         public string Event { get; set; }
+        /// <summary>
+        /// 鍐呭
+        /// </summary>
+        [JsonProperty("debug_str")]
+        public string Content { get; set; }
     }
 }
diff --git a/ApiTools.Core/Models/WxmpUtils/Models/WxmpSubscribMessageNotifyRequestQuery.cs b/ApiTools.Core/Models/WxmpUtils/Models/WxmpSubscribMessageNotifyRequestQuery.cs
index 0441bf0..b2f8b67 100644
--- a/ApiTools.Core/Models/WxmpUtils/Models/WxmpSubscribMessageNotifyRequestQuery.cs
+++ b/ApiTools.Core/Models/WxmpUtils/Models/WxmpSubscribMessageNotifyRequestQuery.cs
@@ -13,18 +13,33 @@
         /// <summary>
         /// 绛惧悕
         /// </summary>
-        public string Signature { get; set; }
+        public string signature { get; set; }
+
         /// <summary>
         /// 鏃堕棿鎴�
         /// </summary>
-        public string Timestamp { get; set; }
+        public string timestamp { get; set; }
+
         /// <summary>
         /// 闅忔満鏁�
         /// </summary>
-        public string Nonce { get; set; }
+        public string nonce { get; set; }
+
         /// <summary>
         /// 闅忔満瀛楃涓�
         /// </summary>
-        public string Echostr { get; set; }
+        public string echostr { get; set; }
+
+        public string openid { get; set; }
+
+        public string encrypt_type { get; set; }
+
+        public string msg_signature { get; set; }
+    }
+
+    public class WxmpSubscribMessageNotifyRequestBody
+    {
+        public string ToUserName { get; set; }
+        public string Encrypt { get; set; }
     }
 }
diff --git a/ApiTools.Core/Utils/WxmpUtils/Crypto/Cryptography.cs b/ApiTools.Core/Utils/WxmpUtils/Crypto/Cryptography.cs
new file mode 100644
index 0000000..05b5f77
--- /dev/null
+++ b/ApiTools.Core/Utils/WxmpUtils/Crypto/Cryptography.cs
@@ -0,0 +1,232 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Security.Cryptography;
+using System.IO;
+using System.Net;
+namespace Tencent
+{
+    class Cryptography
+    {
+        public static UInt32 HostToNetworkOrder(UInt32 inval)
+        {
+            UInt32 outval = 0;
+            for (int i = 0; i < 4; i++)
+                outval = (outval << 8) + ((inval >> (i * 8)) & 255);
+            return outval;
+        }
+
+        public static Int32 HostToNetworkOrder(Int32 inval)
+        {
+            Int32 outval = 0;
+            for (int i = 0; i < 4; i++)
+                outval = (outval << 8) + ((inval >> (i * 8)) & 255);
+            return outval;
+        }
+        /// <summary>
+        /// 瑙e瘑鏂规硶
+        /// </summary>
+        /// <param name="Input">瀵嗘枃</param>
+        /// <param name="EncodingAESKey"></param>
+        /// <returns></returns>
+        /// 
+        public static string AES_decrypt(String Input, string EncodingAESKey, ref string appid)
+        {
+            byte[] Key;
+            Key = Convert.FromBase64String(EncodingAESKey + "=");
+            byte[] Iv = new byte[16];
+            Array.Copy(Key, Iv, 16);
+            byte[] btmpMsg = AES_decrypt(Input, Iv, Key);
+
+            int len = BitConverter.ToInt32(btmpMsg, 16);
+            len = IPAddress.NetworkToHostOrder(len);
+
+
+            byte[] bMsg = new byte[len];
+            byte[] bAppid = new byte[btmpMsg.Length - 20 - len];
+            Array.Copy(btmpMsg, 20, bMsg, 0, len);
+            Array.Copy(btmpMsg, 20+len , bAppid, 0, btmpMsg.Length - 20 - len);
+            string oriMsg = Encoding.UTF8.GetString(bMsg);
+            appid = Encoding.UTF8.GetString(bAppid);
+
+            
+            return oriMsg;
+        }
+
+        public static String AES_encrypt(String Input, string EncodingAESKey, string appid)
+        {
+            byte[] Key;
+            Key = Convert.FromBase64String(EncodingAESKey + "=");
+            byte[] Iv = new byte[16];
+            Array.Copy(Key, Iv, 16);
+            string Randcode = CreateRandCode(16);
+            byte[] bRand = Encoding.UTF8.GetBytes(Randcode);
+            byte[] bAppid = Encoding.UTF8.GetBytes(appid);
+            byte[] btmpMsg = Encoding.UTF8.GetBytes(Input);
+            byte[] bMsgLen = BitConverter.GetBytes(HostToNetworkOrder(btmpMsg.Length));
+            byte[] bMsg = new byte[bRand.Length + bMsgLen.Length + bAppid.Length + btmpMsg.Length];
+
+            Array.Copy(bRand, bMsg, bRand.Length);
+            Array.Copy(bMsgLen, 0, bMsg, bRand.Length, bMsgLen.Length);
+            Array.Copy(btmpMsg, 0, bMsg, bRand.Length + bMsgLen.Length, btmpMsg.Length);
+            Array.Copy(bAppid, 0, bMsg, bRand.Length + bMsgLen.Length + btmpMsg.Length, bAppid.Length);
+   
+            return AES_encrypt(bMsg, Iv, Key);
+
+        }
+        private static string CreateRandCode(int codeLen)
+        {
+            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";
+            if (codeLen == 0)
+            {
+                codeLen = 16;
+            }
+            string[] arr = codeSerial.Split(',');
+            string code = "";
+            int randValue = -1;
+            Random rand = new Random(unchecked((int)DateTime.Now.Ticks));
+            for (int i = 0; i < codeLen; i++)
+            {
+                randValue = rand.Next(0, arr.Length - 1);
+                code += arr[randValue];
+            }
+            return code;
+        }
+
+        private static String AES_encrypt(String Input, byte[] Iv, byte[] Key)
+        {
+            var aes = new RijndaelManaged();
+            //绉橀挜鐨勫ぇ灏忥紝浠ヤ綅涓哄崟浣�
+            aes.KeySize = 256;
+            //鏀寔鐨勫潡澶у皬
+            aes.BlockSize = 128;
+            //濉厖妯″紡
+            aes.Padding = PaddingMode.PKCS7;
+            aes.Mode = CipherMode.CBC;
+            aes.Key = Key;
+            aes.IV = Iv;
+            var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);
+            byte[] xBuff = null;
+
+            using (var ms = new MemoryStream())
+            {
+                using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write))
+                {
+                    byte[] xXml = Encoding.UTF8.GetBytes(Input);
+                    cs.Write(xXml, 0, xXml.Length);
+                }
+                xBuff = ms.ToArray();
+            }
+            String Output = Convert.ToBase64String(xBuff);
+            return Output;
+        }
+
+        private static String AES_encrypt(byte[] Input, byte[] Iv, byte[] Key)
+        {
+            var aes = new RijndaelManaged();
+            //绉橀挜鐨勫ぇ灏忥紝浠ヤ綅涓哄崟浣�
+            aes.KeySize = 256;
+            //鏀寔鐨勫潡澶у皬
+            aes.BlockSize = 128;
+            //濉厖妯″紡
+            //aes.Padding = PaddingMode.PKCS7;
+            aes.Padding = PaddingMode.None;
+            aes.Mode = CipherMode.CBC;
+            aes.Key = Key;
+            aes.IV = Iv;
+            var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);
+            byte[] xBuff = null;
+
+            #region 鑷繁杩涜PKCS7琛ヤ綅锛岀敤绯荤粺鑷繁甯︾殑涓嶈
+            byte[] msg = new byte[Input.Length + 32 - Input.Length % 32];
+            Array.Copy(Input, msg, Input.Length);
+            byte[] pad = KCS7Encoder(Input.Length);
+            Array.Copy(pad, 0, msg, Input.Length, pad.Length);
+            #endregion
+
+            #region 娉ㄩ噴鐨勪篃鏄竴绉嶆柟娉曪紝鏁堟灉涓�鏍�
+            //ICryptoTransform transform = aes.CreateEncryptor();
+            //byte[] xBuff = transform.TransformFinalBlock(msg, 0, msg.Length);
+            #endregion
+
+            using (var ms = new MemoryStream())
+            {
+                using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write))
+                {
+                    cs.Write(msg, 0, msg.Length);
+                }
+                xBuff = ms.ToArray();
+            }
+
+            String Output = Convert.ToBase64String(xBuff);
+            return Output;
+        }
+
+        private static byte[] KCS7Encoder(int text_length)
+        {
+            int block_size = 32;
+            // 璁$畻闇�瑕佸~鍏呯殑浣嶆暟
+            int amount_to_pad = block_size - (text_length % block_size);
+            if (amount_to_pad == 0)
+            {
+                amount_to_pad = block_size;
+            }
+            // 鑾峰緱琛ヤ綅鎵�鐢ㄧ殑瀛楃
+            char pad_chr = chr(amount_to_pad);
+            string tmp = "";
+            for (int index = 0; index < amount_to_pad; index++)
+            {
+                tmp += pad_chr;
+            }
+            return Encoding.UTF8.GetBytes(tmp);
+        }
+        /**
+         * 灏嗘暟瀛楄浆鍖栨垚ASCII鐮佸搴旂殑瀛楃锛岀敤浜庡鏄庢枃杩涜琛ョ爜
+         * 
+         * @param a 闇�瑕佽浆鍖栫殑鏁板瓧
+         * @return 杞寲寰楀埌鐨勫瓧绗�
+         */
+        static char chr(int a)
+        {
+
+            byte target = (byte)(a & 0xFF);
+            return (char)target;
+        }
+        private static byte[] AES_decrypt(String Input, byte[] Iv, byte[] Key)
+        {
+            RijndaelManaged aes = new RijndaelManaged();
+            aes.KeySize = 256;
+            aes.BlockSize = 128;
+            aes.Mode = CipherMode.CBC;
+            aes.Padding = PaddingMode.None;
+            aes.Key = Key;
+            aes.IV = Iv;
+            var decrypt = aes.CreateDecryptor(aes.Key, aes.IV);
+            byte[] xBuff = null;
+            using (var ms = new MemoryStream())
+            {
+                using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
+                {
+                    byte[] xXml = Convert.FromBase64String(Input);
+                    byte[] msg = new byte[xXml.Length + 32 - xXml.Length % 32];
+                    Array.Copy(xXml, msg, xXml.Length);
+                    cs.Write(xXml, 0, xXml.Length);
+                }
+                xBuff = decode2(ms.ToArray());
+            }
+            return xBuff;
+        }
+        private static byte[] decode2(byte[] decrypted)
+        {
+            int pad = (int)decrypted[decrypted.Length - 1];
+            if (pad < 1 || pad > 32)
+            {
+                pad = 0;
+            }
+            byte[] res = new byte[decrypted.Length - pad];
+            Array.Copy(decrypted, 0, res, 0, decrypted.Length - pad);
+            return res;
+        }
+    }
+}
diff --git a/ApiTools.Core/Utils/WxmpUtils/Crypto/Readme.txt b/ApiTools.Core/Utils/WxmpUtils/Crypto/Readme.txt
new file mode 100644
index 0000000..a88ea6d
--- /dev/null
+++ b/ApiTools.Core/Utils/WxmpUtils/Crypto/Readme.txt
@@ -0,0 +1,4 @@
+注意事项
+1.Cryptography.cs文件封装了AES加解密过程,用户无须关心具体实现。WXBizMsgCrypt.cs文件提供了用户接入企业微信的两个接口,Sample.cs文件提供了如何使用这两个接口的示例。
+2.WXBizMsgCrypt.cs封装了DecryptMsg, EncryptMsg两个接口,分别用于收到用户回复消息的解密以及开发者回复消息的加密过程。使用方法可以参考Sample.cs文件。
+3.加解密协议请参考微信公众平台官方文档。
\ No newline at end of file
diff --git a/ApiTools.Core/Utils/WxmpUtils/Crypto/Sample.cs b/ApiTools.Core/Utils/WxmpUtils/Crypto/Sample.cs
new file mode 100644
index 0000000..45862f9
--- /dev/null
+++ b/ApiTools.Core/Utils/WxmpUtils/Crypto/Sample.cs
@@ -0,0 +1,82 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Xml;
+namespace MsgCryptTest
+{
+    class Sample
+    {
+
+        static void Main(string[] args)
+        {
+            //鍏紬骞冲彴涓婂紑鍙戣�呰缃殑token, appID, EncodingAESKey
+            string sToken = "QDG6eK";
+            string sAppID = "wx5823bf96d3bd56c7";
+            string sEncodingAESKey = "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C";
+
+            Tencent.WXBizMsgCrypt wxcpt = new Tencent.WXBizMsgCrypt(sToken, sEncodingAESKey, sAppID);
+            
+             /* 1. 瀵圭敤鎴峰洖澶嶇殑鏁版嵁杩涜瑙e瘑銆�
+             * 鐢ㄦ埛鍥炲娑堟伅鎴栬�呯偣鍑讳簨浠跺搷搴旀椂锛屼紒涓氫細鏀跺埌鍥炶皟娑堟伅锛屽亣璁句紒涓氭敹鍒扮殑鎺ㄩ�佹秷鎭細
+             * 	POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6&timestamp=1409659813&nonce=1372623149 HTTP/1.1
+	            Host: qy.weixin.qq.com
+                Content-Length: 613
+             *
+             * 	<xml>
+	                <ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName>
+	                <Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt>
+                </xml>
+             */
+            string sReqMsgSig = "477715d11cdb4164915debcba66cb864d751f3e6";
+            string sReqTimeStamp = "1409659813";
+            string sReqNonce = "1372623149";
+            string sReqData = "<xml><ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt></xml>";
+            string sMsg = "";  //瑙f瀽涔嬪悗鐨勬槑鏂�
+			int ret = 0;
+            ret = wxcpt.DecryptMsg(sReqMsgSig, sReqTimeStamp, sReqNonce, sReqData, ref sMsg);
+            if (ret != 0)
+            {
+                System.Console.WriteLine("ERR: Decrypt fail, ret: " + ret);
+                return;
+            }
+            System.Console.WriteLine(sMsg);
+
+
+            /*
+             * 2. 浼佷笟鍥炲鐢ㄦ埛娑堟伅涔熼渶瑕佸姞瀵嗗拰鎷兼帴xml瀛楃涓层��
+             * 鍋囪浼佷笟闇�瑕佸洖澶嶇敤鎴风殑娑堟伅涓猴細
+             * 		<xml>
+             * 		<ToUserName><![CDATA[mycreate]]></ToUserName>
+             * 		<FromUserName><![CDATA[wx5823bf96d3bd56c7]]></FromUserName>
+             * 		<CreateTime>1348831860</CreateTime>
+                    <MsgType><![CDATA[text]]></MsgType>
+             *      <Content><![CDATA[this is a test]]></Content>
+             *      <MsgId>1234567890123456</MsgId>
+             *      </xml>
+             * 鐢熸垚xml鏍煎紡鐨勫姞瀵嗘秷鎭繃绋嬩负锛�
+             */
+            string sRespData = "<xml><ToUserName><![CDATA[mycreate]]></ToUserName><FromUserName><![CDATA[wx582娴嬭瘯涓�涓嬩腑鏂囩殑鎯呭喌锛屾秷鎭暱搴︽槸鎸夊瓧鑺傛潵绠楃殑396d3bd56c7]]></FromUserName><CreateTime>1348831860</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[this is a test]]></Content><MsgId>1234567890123456</MsgId></xml>";
+            string sEncryptMsg = ""; //xml鏍煎紡鐨勫瘑鏂�
+            ret = wxcpt.EncryptMsg(sRespData, sReqTimeStamp, sReqNonce, ref sEncryptMsg);
+            System.Console.WriteLine("sEncryptMsg");
+            System.Console.WriteLine(sEncryptMsg);
+
+            /*娴嬭瘯锛�
+             * 灏唖EncryptMsg瑙e瘑鐪嬬湅鏄惁鏄師鏂�
+             * */
+            XmlDocument doc = new XmlDocument();
+            doc.LoadXml(sEncryptMsg);
+            XmlNode root = doc.FirstChild;
+            string sig = root["MsgSignature"].InnerText;
+            string enc = root["Encrypt"].InnerText;
+            string timestamp = root["TimeStamp"].InnerText;
+            string nonce = root["Nonce"].InnerText;
+            string stmp = "";
+            ret = wxcpt.DecryptMsg(sig, timestamp, nonce, sEncryptMsg, ref stmp);
+            System.Console.WriteLine("stemp");
+            System.Console.WriteLine(stmp + ret);
+            return;
+        }
+    }
+}
diff --git a/ApiTools.Core/Utils/WxmpUtils/Crypto/WXBizMsgCrypt.cs b/ApiTools.Core/Utils/WxmpUtils/Crypto/WXBizMsgCrypt.cs
new file mode 100644
index 0000000..da3e234
--- /dev/null
+++ b/ApiTools.Core/Utils/WxmpUtils/Crypto/WXBizMsgCrypt.cs
@@ -0,0 +1,221 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Xml;
+using System.Collections;
+//using System.Web;
+using System.Security.Cryptography;
+//-40001 锛� 绛惧悕楠岃瘉閿欒
+//-40002 :  xml瑙f瀽澶辫触
+//-40003 :  sha鍔犲瘑鐢熸垚绛惧悕澶辫触
+//-40004 :  AESKey 闈炴硶
+//-40005 :  appid 鏍¢獙閿欒
+//-40006 :  AES 鍔犲瘑澶辫触
+//-40007 锛� AES 瑙e瘑澶辫触
+//-40008 锛� 瑙e瘑鍚庡緱鍒扮殑buffer闈炴硶
+//-40009 :  base64鍔犲瘑寮傚父
+//-40010 :  base64瑙e瘑寮傚父
+namespace Tencent
+{
+    public class WXBizMsgCrypt
+    {
+        string m_sToken;
+        string m_sEncodingAESKey;
+        string m_sAppID;
+        enum WXBizMsgCryptErrorCode
+        {
+            WXBizMsgCrypt_OK = 0,
+            WXBizMsgCrypt_ValidateSignature_Error = -40001,
+            WXBizMsgCrypt_ParseXml_Error = -40002,
+            WXBizMsgCrypt_ComputeSignature_Error = -40003,
+            WXBizMsgCrypt_IllegalAesKey = -40004,
+            WXBizMsgCrypt_ValidateAppid_Error = -40005,
+            WXBizMsgCrypt_EncryptAES_Error = -40006,
+            WXBizMsgCrypt_DecryptAES_Error = -40007,
+            WXBizMsgCrypt_IllegalBuffer = -40008,
+            WXBizMsgCrypt_EncodeBase64_Error = -40009,
+            WXBizMsgCrypt_DecodeBase64_Error = -40010
+        };
+
+        //鏋勯�犲嚱鏁�
+	    // @param sToken: 鍏紬骞冲彴涓婏紝寮�鍙戣�呰缃殑Token
+	    // @param sEncodingAESKey: 鍏紬骞冲彴涓婏紝寮�鍙戣�呰缃殑EncodingAESKey
+	    // @param sAppID: 鍏紬甯愬彿鐨刟ppid
+        public WXBizMsgCrypt(string sToken, string sEncodingAESKey, string sAppID)
+        {
+            m_sToken = sToken;
+            m_sAppID = sAppID;
+            m_sEncodingAESKey = sEncodingAESKey;
+        }
+
+
+        // 妫�楠屾秷鎭殑鐪熷疄鎬э紝骞朵笖鑾峰彇瑙e瘑鍚庣殑鏄庢枃
+        // @param sMsgSignature: 绛惧悕涓诧紝瀵瑰簲URL鍙傛暟鐨刴sg_signature
+        // @param sTimeStamp: 鏃堕棿鎴筹紝瀵瑰簲URL鍙傛暟鐨則imestamp
+        // @param sNonce: 闅忔満涓诧紝瀵瑰簲URL鍙傛暟鐨刵once
+        // @param sPostData: 瀵嗘枃锛屽搴擯OST璇锋眰鐨勬暟鎹�
+        // @param sMsg: 瑙e瘑鍚庣殑鍘熸枃锛屽綋return杩斿洖0鏃舵湁鏁�
+        // @return: 鎴愬姛0锛屽け璐ヨ繑鍥炲搴旂殑閿欒鐮�
+        public int DecryptMsg(string sMsgSignature, string sTimeStamp, string sNonce, string sPostData, ref string sMsg)
+        {
+			if (m_sEncodingAESKey.Length!=43)
+			{
+				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
+			}
+            XmlDocument doc = new XmlDocument();
+            XmlNode root;
+            string sEncryptMsg;
+            try
+            {
+                doc.LoadXml(sPostData);
+                root = doc.FirstChild;
+                sEncryptMsg = root["Encrypt"].InnerText;
+            }
+            catch (Exception)
+            {
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error;
+            }
+            //verify signature
+            int ret = 0;
+            ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature);
+            if (ret != 0)
+                return ret;
+            //decrypt
+            string cpid = "";
+            try
+            {
+                sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid);
+            }
+            catch (FormatException)
+            {
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecodeBase64_Error;
+            }
+            catch (Exception)
+            {
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
+            }
+            if (cpid != m_sAppID)
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateAppid_Error;
+            return 0;
+        }
+
+        //灏嗕紒涓氬彿鍥炲鐢ㄦ埛鐨勬秷鎭姞瀵嗘墦鍖�
+        // @param sReplyMsg: 浼佷笟鍙峰緟鍥炲鐢ㄦ埛鐨勬秷鎭紝xml鏍煎紡鐨勫瓧绗︿覆
+        // @param sTimeStamp: 鏃堕棿鎴筹紝鍙互鑷繁鐢熸垚锛屼篃鍙互鐢║RL鍙傛暟鐨則imestamp
+        // @param sNonce: 闅忔満涓诧紝鍙互鑷繁鐢熸垚锛屼篃鍙互鐢║RL鍙傛暟鐨刵once
+        // @param sEncryptMsg: 鍔犲瘑鍚庣殑鍙互鐩存帴鍥炲鐢ㄦ埛鐨勫瘑鏂囷紝鍖呮嫭msg_signature, timestamp, nonce, encrypt鐨剎ml鏍煎紡鐨勫瓧绗︿覆,
+        //						褰搑eturn杩斿洖0鏃舵湁鏁�
+        // return锛氭垚鍔�0锛屽け璐ヨ繑鍥炲搴旂殑閿欒鐮�
+        public int EncryptMsg(string sReplyMsg, string sTimeStamp, string sNonce, ref string sEncryptMsg)
+        {
+			if (m_sEncodingAESKey.Length!=43)
+			{
+				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
+			}
+            string raw = "";
+            try
+            {
+                raw = Cryptography.AES_encrypt(sReplyMsg, m_sEncodingAESKey, m_sAppID);
+            }
+            catch (Exception)
+            {
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_EncryptAES_Error;
+            }
+            string MsgSigature = "";
+            int ret = 0;
+            ret = GenarateSinature(m_sToken, sTimeStamp, sNonce, raw, ref MsgSigature);
+            if (0 != ret)
+                return ret;
+            sEncryptMsg = "";
+
+            string EncryptLabelHead = "<Encrypt><![CDATA[";
+            string EncryptLabelTail = "]]></Encrypt>";
+            string MsgSigLabelHead = "<MsgSignature><![CDATA[";
+            string MsgSigLabelTail = "]]></MsgSignature>";
+            string TimeStampLabelHead = "<TimeStamp><![CDATA[";
+            string TimeStampLabelTail = "]]></TimeStamp>";
+            string NonceLabelHead = "<Nonce><![CDATA[";
+            string NonceLabelTail = "]]></Nonce>";
+            sEncryptMsg = sEncryptMsg + "<xml>" + EncryptLabelHead + raw + EncryptLabelTail;
+            sEncryptMsg = sEncryptMsg + MsgSigLabelHead + MsgSigature + MsgSigLabelTail;
+            sEncryptMsg = sEncryptMsg + TimeStampLabelHead + sTimeStamp + TimeStampLabelTail;
+            sEncryptMsg = sEncryptMsg + NonceLabelHead + sNonce + NonceLabelTail;
+            sEncryptMsg += "</xml>";
+            return 0;
+        }
+
+        public class DictionarySort : System.Collections.IComparer
+        {
+            public int Compare(object oLeft, object oRight)
+            {
+                string sLeft = oLeft as string;
+                string sRight = oRight as string;
+                int iLeftLength = sLeft.Length;
+                int iRightLength = sRight.Length;
+                int index = 0;
+                while (index < iLeftLength && index < iRightLength)
+                {
+                    if (sLeft[index] < sRight[index])
+                        return -1;
+                    else if (sLeft[index] > sRight[index])
+                        return 1;
+                    else
+                        index++;
+                }
+                return iLeftLength - iRightLength;
+
+            }
+        }
+        //Verify Signature
+        private static int VerifySignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, string sSigture)
+        {
+            string hash = "";
+            int ret = 0;
+            ret = GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash);
+            if (ret != 0)
+                return ret;
+            //System.Console.WriteLine(hash);
+            if (hash == sSigture)
+                return 0;
+            else
+            {
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateSignature_Error;
+            }
+        }
+
+        public static int GenarateSinature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt ,ref string sMsgSignature)
+        {
+            ArrayList AL = new ArrayList();
+            AL.Add(sToken);
+            AL.Add(sTimeStamp);
+            AL.Add(sNonce);
+            AL.Add(sMsgEncrypt);
+            AL.Sort(new DictionarySort());
+            string raw = "";
+            for (int i = 0; i < AL.Count; ++i)
+            {
+                raw += AL[i];
+            }
+
+            SHA1 sha;
+            ASCIIEncoding enc;
+            string hash = "";
+            try
+            {
+                sha = new SHA1CryptoServiceProvider();
+                enc = new ASCIIEncoding();
+                byte[] dataToHash = enc.GetBytes(raw);
+                byte[] dataHashed = sha.ComputeHash(dataToHash);
+                hash = BitConverter.ToString(dataHashed).Replace("-", "");
+                hash = hash.ToLower();
+            }
+            catch (Exception)
+            {
+                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ComputeSignature_Error;
+            }
+            sMsgSignature = hash;
+            return 0;
+        }
+    }
+}
diff --git a/ApiTools.Web.Entry/Controllers/WxmpController.cs b/ApiTools.Web.Entry/Controllers/WxmpController.cs
index a6bc4a8..4ec97c2 100644
--- a/ApiTools.Web.Entry/Controllers/WxmpController.cs
+++ b/ApiTools.Web.Entry/Controllers/WxmpController.cs
@@ -1,43 +1,72 @@
-锘縰sing ApiTools.Core;
+锘縰sing Aop.Api.Domain;
+using ApiTools.Core;
 using Furion.DataEncryption;
 using Furion.DynamicApiController;
+using Furion.FriendlyException;
+using MediatR;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
+using Org.BouncyCastle.Ocsp;
+using System.Buffers.Binary;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
 
 namespace ApiTools.Web.Entry.Controllers
 {
     [Route("api/common/wxmp")]
     public class WxmpController(
             WxmpUtils utils,
-            IOptions<WxmpOptions> options
+            IOptions<WxmpOptions> options,
+            IMediator mediator
         ) : ControllerBase
     {
         private readonly WxmpUtils utils = utils;
         private readonly IOptions<WxmpOptions> options = options;
+        private readonly IMediator mediator = mediator;
 
         [HttpGet("subscribMessageNotify")]
         [AllowAnonymous]
         [NonUnify]
-        public IActionResult SubscribMessageNotify([FromQuery] WxmpSubscribMessageNotifyRequestQuery request)
+        public IActionResult SubscribMessageNotify([FromQuery] WxmpSubscribMessageNotifyRequestQuery query)
         {
             var @params = new[]
             {
                 options.Value.SubscribMessage.Token,
-                request.Timestamp,
-                request.Nonce
+                query.timestamp,
+                query.nonce
             }
             .OrderBy(p => p)
             .ToArray();
             var text = string.Concat(@params);
-            if (SHA1Encryption.Compare(text, request.Signature, true))
+            if (SHA1Encryption.Compare(text, query.signature, true))
             {
-                return Content(request.Echostr);
+                return Content(query.echostr);
             }
             else
             {
                 return Unauthorized("楠岀澶辫触");
             }
         }
+
+        [HttpPost("subscribMessageNotify/{code}")]
+        [AllowAnonymous]
+        [NonUnify]
+        public async Task<IActionResult> SubscribMessageNotify([FromRoute] string code, [FromQuery] WxmpSubscribMessageNotifyRequestQuery query, [FromBody] WxmpSubscribMessageNotifyRequestBody body)
+        {
+            var appId = options.Value.Items.FirstOrDefault(it => it.Code == code).AppId;
+            Tencent.WXBizMsgCrypt wxcpt = new Tencent.WXBizMsgCrypt(options.Value.SubscribMessage.Token, options.Value.SubscribMessage.EncodingAESKey, appId);
+            var data = $"<xml><ToUserName><![CDATA[{body.ToUserName}]]></ToUserName><Encrypt><![CDATA[{body.Encrypt}]]></Encrypt></xml>";
+            var content = "";
+            var error = wxcpt.DecryptMsg(query.msg_signature, query.timestamp, query.nonce, data, ref content);
+            if (error != 0) return Unauthorized("楠岀澶辫触");
+            var command = content.JsonTo<WxmpSubscribMessageNotifyCommand>();
+            command.Code = code;
+            command.OpenId = query.openid;
+            await mediator.Send(command);
+            return Content(query.echostr);
+        }
     }
 }
diff --git a/ApiTools.Web.Entry/appsettings.json b/ApiTools.Web.Entry/appsettings.json
index 206c6de..d837641 100644
--- a/ApiTools.Web.Entry/appsettings.json
+++ b/ApiTools.Web.Entry/appsettings.json
@@ -53,6 +53,12 @@
         "EnvVersion": "trial"
       },
       {
+        "Code": "Supplier",
+        "AppId": "wxc47d6f255e7d0566",
+        "AppSecret": "9e02d66cf005fa2f4aefb2e4dc1fced5",
+        "EnvVersion": "trial"
+      },
+      {
         "Code": "Public",
         "AppId": "wxf940ff1d35a98493",
         "AppSecret": "9a132eda735bc925200b0e215cffe20a",
@@ -60,10 +66,9 @@
       }
     ],
     "SubscribMessage": {
-      "Url": "http://118.178.252.28:8780/api/common/wxmp/subscribMessageNotify",
       "Token": "8Uu6CZ9KM2CAr3Q3O0YdWUYPfcXFhgMK",
       "EncodingAESKey": "tbBkUB7nCgZlfton3aKMlfzHSm7QdWgnpKFibl6sjn7"
-    },
+    }
   },
   "Task": {
     "SettlementTime": "T0"

--
Gitblit v1.9.1