shop/CoreCms.Net.Web.WebApi/Controllers/WeChatOAuth/WxOpenController.cs
2025-10-11 11:19:23 +08:00

275 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/***********************************************************************
* Project: CoreCms
* ProjectName: 核心内容管理系统
* Web: https://www.corecms.net
* Author: 大灰灰
* Email: jianweie@163.com
* CreateTime: 2021/1/31 21:45:10
* 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
{
/// <summary>
/// 微信小程序Controller
/// </summary>
public class WxOpenController : ControllerBase
{
private readonly WeChat.Service.HttpClients.IWeChatApiHttpClientFactory _weChatApiHttpClientFactory;
private readonly WeChatOptions _weChatOptions;
private readonly IMediator _mediator;
/// <summary>
/// 原始的加密请求如果不加密则为null
/// </summary>
public XDocument? EcryptRequestDocument { get; set; } = null;
/// <summary>
/// 是否使用加密
/// </summary>
public bool UsingEncryptMessage = false;
/// <summary>
/// 是否取消执行
/// </summary>
public bool CancelExecute = false;
/// <summary>
/// 是否使用兼容模式
/// </summary>
public bool UsingCompatibilityModelEncryptMessage = false;
/// <summary>
/// 微信小程序服务器交互
/// </summary>
/// <param name="weChatApiHttpClientFactory"></param>
/// <param name="weChatOptions"></param>
/// <param name="mediator"></param>
public WxOpenController(IWeChatApiHttpClientFactory weChatApiHttpClientFactory, IOptions<WeChatOptions> weChatOptions, IMediator mediator)
{
_weChatApiHttpClientFactory = weChatApiHttpClientFactory;
_mediator = mediator;
_weChatOptions = weChatOptions.Value;
}
#region GET请求用于处理微信小程序后台的URL验证
/// <summary>
/// GET请求用于处理微信小程序后台的URL验证
/// </summary>
/// <returns></returns>
[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
/// <summary>
/// 接收服务器推送 文档https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html
/// </summary>
/// <returns></returns>
[HttpPost]
[ActionName("Index")]
public async Task<IActionResult> 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;//根据自己后台的设置保持一致(必须提供)
var body = Request.Body;
//获取流数据转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内容
/// <summary>
/// 对解密后的xml数据进行筛选并分发处理结果
/// </summary>
public async Task<WeChatApiCallBack> 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<SKIT.FlurlHttpClient.Wechat.Api.Events.TextMessageEvent>(msgXml);
callBack = await _mediator.Send(new TextMessageEventCommand() { EventObj = textMessageEvent });
break;
case RequestMsgType.Location:
break;
case RequestMsgType.Image:
var imageMessageEvent = client.DeserializeEventFromXml<SKIT.FlurlHttpClient.Wechat.Api.Events.ImageMessageEvent>(msgXml);
callBack = await _mediator.Send(new ImageMessageEventCommand() { EventObj = imageMessageEvent });
break;
case RequestMsgType.Voice:
var voiceMessageEvent = client.DeserializeEventFromXml<SKIT.FlurlHttpClient.Wechat.Api.Events.VoiceMessageEvent>(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文本数据
/// <summary>
/// 初始化获取xml文本数据
/// </summary>
/// <param name="postDataDocument"></param>
/// <param name="postModel"></param>
/// <param name="msgXml"></param>
/// <returns></returns>
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
}
}