/*********************************************************************** * Project: baifenBinfa * ProjectName: 百分兵法管理系统 * Web: http://chuanyin.com * Author: * Email: * CreateTime: 202403/02 * Description: 暂无 ***********************************************************************/ #nullable enable using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using CoreCms.Net.Loging; using CoreCms.Net.Model.ViewModels.UI; using CoreCms.Net.WeChat.Service.Configuration; using CoreCms.Net.WeChat.Service.Enums; using CoreCms.Net.WeChat.Service.HttpClients; using CoreCms.Net.WeChat.Service.Mediator; using CoreCms.Net.WeChat.Service.Models; using CoreCms.Net.WeChat.Service.Options; using CoreCms.Net.WeChat.Service.Utilities; using MediatR; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using NetTaste; using Newtonsoft.Json; using NLog; using SKIT.FlurlHttpClient.Wechat.Api; namespace CoreCms.Net.Web.WebApi.Controllers.WeChatOAuth { /// /// 微信小程序Controller /// public class WxOpenController : ControllerBase { private readonly WeChat.Service.HttpClients.IWeChatApiHttpClientFactory _weChatApiHttpClientFactory; private readonly WeChatOptions _weChatOptions; private readonly IMediator _mediator; /// /// 原始的加密请求(如果不加密则为null) /// public XDocument? EcryptRequestDocument { get; set; } = null; /// /// 是否使用加密 /// public bool UsingEncryptMessage = false; /// /// 是否取消执行 /// public bool CancelExecute = false; /// /// 是否使用兼容模式 /// public bool UsingCompatibilityModelEncryptMessage = false; /// /// 微信小程序服务器交互 /// /// /// /// public WxOpenController(IWeChatApiHttpClientFactory weChatApiHttpClientFactory, IOptions weChatOptions, IMediator mediator) { _weChatApiHttpClientFactory = weChatApiHttpClientFactory; _mediator = mediator; _weChatOptions = weChatOptions.Value; } #region GET请求用于处理微信小程序后台的URL验证 /// /// GET请求用于处理微信小程序后台的URL验证 /// /// [HttpGet] [ActionName("Index")] public ActionResult Get(PostModel postModel, string echostr) { if (CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, _weChatOptions.WxOpenToken)) { return Content(echostr); //返回随机字符串则表示验证通过 } else { return Content("failed:" + postModel.Signature + "," + CheckSignature.GetSignature(postModel.Timestamp, postModel.Nonce, _weChatOptions.WxOpenToken) + "。" + "如果你在浏览器中看到这句话,说明此地址可以被作为微信小程序后台的Url,请注意保持Token一致。"); } } #endregion #region 接收服务器推送 /// /// 接收服务器推送 文档:https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html /// /// [HttpPost] [ActionName("Index")] public async Task Post(PostModel postModel) { if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, _weChatOptions.WxOpenToken)) { NLogUtil.WriteFileLog(LogLevel.Error, LogType.WxPost, "接收服务器推送(签名错误)", JsonConvert.SerializeObject(postModel)); return Content("fail"); } else { NLogUtil.WriteFileLog(LogLevel.Info, LogType.WxPost, "接收服务器推送(签名成功)", JsonConvert.SerializeObject(postModel)); } postModel.Token = _weChatOptions.WxOpenToken;//根据自己后台的设置保持一致 postModel.EncodingAESKey = _weChatOptions.WxOpenEncodingAESKey;//根据自己后台的设置保持一致 postModel.AppId = _weChatOptions.WxOpenAppId;//根据自己后台的设置保持一致(必须提供) //获取流数据转xml流 XDocument postDataDocument = XmlUtility.Convert(Request.GetRequestStream()); var msgXml = string.Empty; var callbackXml = Init(postDataDocument, postModel, ref msgXml); //怕出现误判,所以将最优结果判断 if (callbackXml != null && CancelExecute == false && !string.IsNullOrEmpty(msgXml)) { /* 如果是 XML 格式的通知内容 */ NLogUtil.WriteFileLog(LogLevel.Info, LogType.WxPost, "接收服务器推送(XML格式的通知内容)", JsonConvert.SerializeObject(callbackXml)); var callBack = await ExecuteProcess(callbackXml, msgXml); NLogUtil.WriteFileLog(LogLevel.Info, LogType.WxPost, "接收服务器推送(XML通知微信服务器)", callBack.Data); return Content(callBack.Data); } else { NLogUtil.WriteFileLog(LogLevel.Info, LogType.WxPost, "接收服务器推送(解密失败)", JsonConvert.SerializeObject(callbackXml)); return Content("fail"); } } #endregion #region 处理xml内容 /// /// 对解密后的xml数据进行筛选并分发处理结果 /// public async Task ExecuteProcess(XDocument sourceXml, string msgXml) { var requestType = sourceXml.Root?.Element("MsgType")?.Value; WeChatApiCallBack callBack = new WeChatApiCallBack(); if (!string.IsNullOrEmpty(requestType)) { var client = _weChatApiHttpClientFactory.CreateWxOpenClient(); switch (requestType) { case RequestMsgType.Text: var textMessageEvent = client.DeserializeEventFromXml(msgXml); callBack = await _mediator.Send(new TextMessageEventCommand() { EventObj = textMessageEvent }); break; case RequestMsgType.Location: break; case RequestMsgType.Image: var imageMessageEvent = client.DeserializeEventFromXml(msgXml); callBack = await _mediator.Send(new ImageMessageEventCommand() { EventObj = imageMessageEvent }); break; case RequestMsgType.Voice: var voiceMessageEvent = client.DeserializeEventFromXml(msgXml); callBack = await _mediator.Send(new VoiceMessageEventCommand() { EventObj = voiceMessageEvent }); break; case RequestMsgType.Video: break; case RequestMsgType.ShortVideo: break; case RequestMsgType.Link: break; case RequestMsgType.MessageEvent: var eventType = sourceXml.Root?.Element("Event")?.Value; if (!string.IsNullOrEmpty(eventType)) { switch (eventType) { case EventType.Subscribe: break; case EventType.Unsubscribe: break; case EventType.Localtion: break; default: NLogUtil.WriteFileLog(LogLevel.Info, LogType.WxPost, "接收服务器推送(处理xml内容/Event无匹配)", JsonConvert.SerializeObject(sourceXml)); break; } } break; default: NLogUtil.WriteFileLog(LogLevel.Info, LogType.WxPost, "接收服务器推送(处理xml内容/MsgType无匹配)", JsonConvert.SerializeObject(sourceXml)); break; } } else { NLogUtil.WriteFileLog(LogLevel.Info, LogType.WxPost, "接收服务器推送(处理xml内容/获取MsgType失败)", JsonConvert.SerializeObject(sourceXml)); } return callBack; } #endregion #region 初始化获取xml文本数据 /// /// 初始化获取xml文本数据 /// /// /// /// /// private XDocument? Init(XDocument postDataDocument, PostModel postModel, ref string msgXml) { //进行加密判断并处理 var postDataStr = postDataDocument.ToString(); XDocument decryptDoc = postDataDocument; if (postDataDocument.Root?.Element("Encrypt") != null && !string.IsNullOrEmpty(postDataDocument.Root.Element("Encrypt")?.Value)) { //使用了加密 UsingEncryptMessage = true; EcryptRequestDocument = postDataDocument; WXBizMsgCrypt msgCrype = new WXBizMsgCrypt(postModel.Token, postModel.EncodingAESKey, postModel.AppId); var result = msgCrype.DecryptMsg(postModel.Msg_Signature, postModel.Timestamp, postModel.Nonce, postDataStr, ref msgXml); //判断result类型 if (result != 0) { //验证没有通过,取消执行 CancelExecute = true; return null; } if (postDataDocument.Root.Element("FromUserName") != null && !string.IsNullOrEmpty(postDataDocument.Root.Element("FromUserName")?.Value)) { //TODO:使用了兼容模式,进行验证即可 UsingCompatibilityModelEncryptMessage = true; } decryptDoc = XDocument.Parse(msgXml);//完成解密 } return decryptDoc; } #endregion } }