using System;
using CoreCms.Net.WeChat.Service.HttpClients;
using Microsoft.AspNetCore.Mvc;
using SKIT.FlurlHttpClient.Wechat.Api;
using SKIT.FlurlHttpClient.Wechat.Api.Events;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using CoreCms.Net.Caching.AccressToken;
using CoreCms.Net.Configuration;
using CoreCms.Net.IServices;
using CoreCms.Net.Loging;
using CoreCms.Net.Model.Entities;
using CoreCms.Net.Utility.Helper;
using CoreCms.Net.WeChat.Service.Configuration;
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.Extensions.Options;
using Newtonsoft.Json;
using SKIT.FlurlHttpClient.Wechat.Api.Models;
using LogLevel = NLog.LogLevel;
using CoreCms.Net.Services;
using SqlSugar;
namespace CoreCms.Net.Web.Controllers.WeChat
{
///
/// 微信公众号消息推送对接
///
public class WeChatOffiaccountNotifyController : ControllerBase
{
private readonly IWeChatApiHttpClientFactory _weChatApiHttpClientFactory;
private readonly WeChatOptions _weChatOptions;
private readonly IMediator _mediator;
private readonly ICoreCmsUserWeChatInfoServices _weChatUserInfoServices;
///
/// 原始的加密请求(如果不加密则为null)
///
public XDocument? EcryptRequestDocument { get; set; } = null;
///
/// 是否使用加密
///
public bool UsingEncryptMessage = false;
///
/// 是否取消执行
///
public bool CancelExecute = false;
///
/// 是否使用兼容模式
///
public bool UsingCompatibilityModelEncryptMessage = false;
///
/// 构造函数
///
public WeChatOffiaccountNotifyController(IWeChatApiHttpClientFactory weChatApiHttpClientFactory, IOptions weChatOptions, IMediator mediator, ICoreCmsUserWeChatInfoServices weChatUserInfoServices)
{
_weChatApiHttpClientFactory = weChatApiHttpClientFactory;
_mediator = mediator;
_weChatUserInfoServices = weChatUserInfoServices;
_weChatOptions = weChatOptions.Value;
}
///
/// GET请求用于处理微信公众号后台的URL验证
///
///
[HttpGet]
[ActionName("Index")]
public ActionResult Get([FromQuery(Name = "timestamp")] string timestamp,
[FromQuery(Name = "nonce")] string nonce,
[FromQuery(Name = "signature")] string signature,
[FromQuery(Name = "echostr")] string echoString)
{
// 验证服务器推送
// 文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
var getModel = new
{
timestamp,
nonce,
signature,
echoString
};
//NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "GET接收到微信推送的数据", JsonConvert.SerializeObject(getModel));
//var client = _weChatApiHttpClientFactory.CreateWeXinClient();
//var valid = client.VerifyEventSignatureForEcho(callbackTimestamp: timestamp, callbackNonce: nonce, callbackSignature: signature);
//return Content(!valid ? "fail" : echoString);
if (!CheckSignature.Check(signature, timestamp, nonce, _weChatOptions.WeiXinToken))
{
NLogUtil.WriteFileLog(LogLevel.Error, LogType.WeChat, "GET接收到微信推送的数据(签名错误)", JsonConvert.SerializeObject(getModel));
return Content("fail");
}
else
{
NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "GET接收到微信推送的数据(签名成功)", JsonConvert.SerializeObject(getModel));
return Content(echoString);
}
}
///
/// 接收服务器推送
///
///
[HttpPost]
[ActionName("Index")]
public async Task Post(PostModel postModel)
{
// 接收服务器推送
// 文档:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, _weChatOptions.WeiXinToken))
{
NLogUtil.WriteFileLog(LogLevel.Error, LogType.WeChat, "Post接收服务器推送(签名错误)", JsonConvert.SerializeObject(postModel));
return Content("fail");
}
else
{
NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "Post接收服务器推送(签名成功)", JsonConvert.SerializeObject(postModel));
}
postModel.Token = _weChatOptions.WeiXinToken;//根据自己后台的设置保持一致
postModel.EncodingAESKey = _weChatOptions.WeiXinEncodingAesKey;//根据自己后台的设置保持一致
postModel.AppId = _weChatOptions.WeiXinAppId;//根据自己后台的设置保持一致(必须提供)
//获取流数据转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.WeChat, "接收服务器推送(XML格式的通知内容)", JsonConvert.SerializeObject(callbackXml));
var callBack = await ExecuteProcess(callbackXml, msgXml);
NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "接收服务器推送(XML通知微信服务器)", callBack.Data);
return Content(callBack.Data);
}
else
{
NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "接收服务器推送(解密失败)", JsonConvert.SerializeObject(callbackXml));
return Content("fail");
}
}
#region 处理xml内容
///
/// 对解密后的xml数据进行筛选并分发处理结果
///
public async Task ExecuteProcess(XDocument sourceXml, string msgXml)
{
//被动回复消息
//文件:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html
var requestType = sourceXml.Root?.Element("MsgType")?.Value;
WeChatApiCallBack callBack = new WeChatApiCallBack();
if (!string.IsNullOrEmpty(requestType))
{
var client = _weChatApiHttpClientFactory.CreateWeXinClient();
switch (requestType)
{
case RequestMsgType.Text:
{
var eventModel = client.DeserializeEventFromXml(msgXml);
var replyModel = new TextMessageReply()
{
ToUserName = eventModel.FromUserName,
FromUserName = eventModel.ToUserName,
CreateTimestamp = CommonHelper.GetTimeStampByTotalSeconds(),
Content = "您发送的消息是" + eventModel.Content
};
var replyXml = client.SerializeEventToXml(replyModel, false);
callBack.Data = replyXml;
}
break;
case RequestMsgType.Location:
{
var eventModel = client.DeserializeEventFromXml(msgXml);
}
break;
case RequestMsgType.Image:
{
var eventModel = client.DeserializeEventFromXml(msgXml);
var replyModel = new ImageMessageReply()
{
ToUserName = eventModel.FromUserName,
FromUserName = eventModel.ToUserName,
CreateTimestamp = CommonHelper.GetTimeStampByTotalSeconds(),
};
var replyXml = client.SerializeEventToXml(replyModel, false);
callBack.Data = replyXml;
}
break;
case RequestMsgType.Voice:
{
var eventModel = client.DeserializeEventFromXml(msgXml);
var replyModel = new VoiceMessageReply()
{
ToUserName = eventModel.FromUserName,
FromUserName = eventModel.ToUserName,
CreateTimestamp = CommonHelper.GetTimeStampByTotalSeconds(),
};
var replyXml = client.SerializeEventToXml(replyModel, false);
callBack.Data = replyXml;
}
break;
case RequestMsgType.Video:
{
var eventModel = client.DeserializeEventFromXml(msgXml);
var replyModel = new VideoMessageReply()
{
ToUserName = eventModel.FromUserName,
FromUserName = eventModel.ToUserName,
CreateTimestamp = CommonHelper.GetTimeStampByTotalSeconds(),
};
var replyXml = client.SerializeEventToXml(replyModel, false);
callBack.Data = replyXml;
}
break;
case RequestMsgType.ShortVideo:
{
var eventModel = client.DeserializeEventFromXml(msgXml);
}
break;
case RequestMsgType.Link:
{
var eventModel = client.DeserializeEventFromXml(msgXml);
}
break;
case RequestMsgType.MessageEvent:
{
var eventType = sourceXml.Root?.Element("Event")?.Value;
if (!string.IsNullOrEmpty(eventType))
{
switch (eventType)
{
//订阅(关注)事件
case EventType.Subscribe:
{
var eventModel = client.DeserializeEventFromXml(msgXml);
var accessToken = WeChatCacheAccessTokenHelper.GetWeChatAccessToken();
if (eventModel.FromUserName != null)
{
var request = new CgibinUserInfoRequest() { AccessToken = accessToken, OpenId = eventModel.FromUserName };
var response = await client.ExecuteCgibinUserInfoAsync(request, cancellationToken: HttpContext.RequestAborted);
if (!response.IsSuccessful())
{
NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "获取用户基本信息失败", @"获取用户基本信息失败(状态码:{response.RawStatus},错误代码:{response.ErrorCode},错误描述:{ response.ErrorMessage})。");
}
else
{
var userInfo = await _weChatUserInfoServices.QueryByClauseAsync(p => p.openid == eventModel.FromUserName);
if (userInfo == null)
{
userInfo = new Model.Entities.CoreCmsUserWeChatInfo()
{
isSubscribe = response.IsSubscribed,
openid = response.OpenId,
language = response.Language,
//createTime = response.SubscribeTimestamp,
type = (int)GlobalEnumVars.UserAccountTypes.微信公众号,
//sessionKey = response.s,
gender = 1,
createTime = DateTime.Now,
unionId = response.UnionId,
};
var id = await _weChatUserInfoServices.InsertAsync(userInfo);
if (id > 0)
{
await _weChatUserInfoServices.UpdateAsync(
p => new Model.Entities.CoreCmsUserWeChatInfo()
{
userId = id
}, p => p.id == id);
}
}
else
{
userInfo.isSubscribe = response.IsSubscribed;
userInfo.unionId = response.UnionId;
userInfo.updateTime = DateTime.Now;
await _weChatUserInfoServices.UpdateAsync(userInfo);
}
}
}
}
break;
// 退订
// 实际上用户无法收到非订阅账号的消息,所以这里可以随便写。
// unsubscribe事件的意义在于及时删除网站应用中已经记录的OpenID绑定,消除冗余数据。并且关注用户流失的情况。
case EventType.Unsubscribe:
{
}
break;
case EventType.Localtion:
{
}
break;
case EventType.Click:
{
var eventModel = client.DeserializeEventFromXml(msgXml);
var replyModel = new TextMessageReply()
{
ToUserName = eventModel.FromUserName,
FromUserName = eventModel.ToUserName,
CreateTimestamp = CommonHelper.GetTimeStampByTotalSeconds(),
Content = "您刚才发送了ENTER事件请求"
};
var replyXml = client.SerializeEventToXml(replyModel, false);
callBack.Data = replyXml;
}
break;
default:
NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "接收服务器推送(处理xml内容/Event无匹配)", JsonConvert.SerializeObject(sourceXml));
break;
}
}
}
break;
default:
NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "接收服务器推送(处理xml内容/MsgType无匹配)", JsonConvert.SerializeObject(sourceXml));
break;
}
}
else
{
NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "接收服务器推送(处理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
}
}