All checks were successful
continuous-integration/drone/push Build is passing
- Add token and encodingAESKey fields to NotificationTemplatesConfig interface - Add UI form inputs for WeChat token and EncodingAESKey in system config page - Add configuration constants SaTokenKey and SaEncodingAesKeyKey to SystemConfigService - Update WeChatEventController to fetch token from database instead of hardcoded value - Make CheckSignature method async and retrieve token from ISystemConfigService - Update GetNotificationTemplates to include token and encodingAESKey from config - Update SetNotificationTemplates to persist token and encodingAESKey to database - Update mini app manifest with new appid - Enables dynamic WeChat server configuration without code changes
193 lines
6.3 KiB
C#
193 lines
6.3 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using System.Security.Cryptography;
|
||
using System.Text;
|
||
using System.Xml.Linq;
|
||
using XiangYi.Application.Interfaces;
|
||
using XiangYi.Core.Entities.Biz;
|
||
using XiangYi.Core.Interfaces;
|
||
|
||
namespace XiangYi.AppApi.Controllers;
|
||
|
||
/// <summary>
|
||
/// 微信服务号事件推送控制器
|
||
/// </summary>
|
||
[ApiController]
|
||
[Route("api/wechat/event")]
|
||
[AllowAnonymous]
|
||
public class WeChatEventController : ControllerBase
|
||
{
|
||
private readonly IRepository<User> _userRepository;
|
||
private readonly ILogger<WeChatEventController> _logger;
|
||
private readonly IConfiguration _configuration;
|
||
private readonly ISystemConfigService _configService;
|
||
|
||
public WeChatEventController(
|
||
IRepository<User> userRepository,
|
||
ILogger<WeChatEventController> logger,
|
||
IConfiguration configuration,
|
||
ISystemConfigService configService)
|
||
{
|
||
_userRepository = userRepository;
|
||
_logger = logger;
|
||
_configuration = configuration;
|
||
_configService = configService;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 微信服务器验证(GET请求)
|
||
/// </summary>
|
||
[HttpGet]
|
||
public async Task<IActionResult> Verify(string signature, string timestamp, string nonce, string echostr)
|
||
{
|
||
_logger.LogInformation("收到微信服务器验证请求: signature={Signature}, timestamp={Timestamp}, nonce={Nonce}",
|
||
signature, timestamp, nonce);
|
||
|
||
if (await CheckSignatureAsync(signature, timestamp, nonce))
|
||
{
|
||
_logger.LogInformation("微信服务器验证成功");
|
||
return Content(echostr);
|
||
}
|
||
|
||
_logger.LogWarning("微信服务器验证失败");
|
||
return BadRequest("验证失败");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 接收微信事件推送(POST请求)
|
||
/// </summary>
|
||
[HttpPost]
|
||
public async Task<IActionResult> ReceiveEvent()
|
||
{
|
||
try
|
||
{
|
||
using var reader = new StreamReader(Request.Body, Encoding.UTF8);
|
||
var body = await reader.ReadToEndAsync();
|
||
|
||
_logger.LogInformation("收到微信事件推送: {Body}", body);
|
||
|
||
var xml = XDocument.Parse(body);
|
||
var root = xml.Root;
|
||
|
||
var msgType = root?.Element("MsgType")?.Value;
|
||
var eventType = root?.Element("Event")?.Value;
|
||
var openId = root?.Element("FromUserName")?.Value;
|
||
|
||
if (msgType == "event")
|
||
{
|
||
switch (eventType?.ToLower())
|
||
{
|
||
case "subscribe":
|
||
await HandleSubscribeEvent(openId, root);
|
||
break;
|
||
case "unsubscribe":
|
||
await HandleUnsubscribeEvent(openId);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 返回success表示已处理
|
||
return Content("success");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "处理微信事件推送异常");
|
||
return Content("success");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理关注事件
|
||
/// </summary>
|
||
private async Task HandleSubscribeEvent(string? serviceAccountOpenId, XElement? root)
|
||
{
|
||
if (string.IsNullOrEmpty(serviceAccountOpenId))
|
||
{
|
||
_logger.LogWarning("关注事件缺少OpenId");
|
||
return;
|
||
}
|
||
|
||
_logger.LogInformation("用户关注服务号: ServiceAccountOpenId={OpenId}", serviceAccountOpenId);
|
||
|
||
// 尝试通过UnionId关联用户
|
||
// 注意:需要服务号和小程序绑定到同一个开放平台才能获取UnionId
|
||
var unionId = root?.Element("UnionId")?.Value;
|
||
|
||
if (!string.IsNullOrEmpty(unionId))
|
||
{
|
||
// 通过UnionId查找用户
|
||
var users = await _userRepository.GetListAsync(u => u.UnionId == unionId);
|
||
var user = users.FirstOrDefault();
|
||
|
||
if (user != null)
|
||
{
|
||
user.ServiceAccountOpenId = serviceAccountOpenId;
|
||
user.IsFollowServiceAccount = true;
|
||
user.UpdateTime = DateTime.Now;
|
||
await _userRepository.UpdateAsync(user);
|
||
|
||
_logger.LogInformation("用户关注服务号并关联成功: UserId={UserId}, UnionId={UnionId}, ServiceAccountOpenId={OpenId}",
|
||
user.Id, unionId, serviceAccountOpenId);
|
||
}
|
||
else
|
||
{
|
||
_logger.LogInformation("用户关注服务号但未找到关联用户: UnionId={UnionId}", unionId);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_logger.LogInformation("用户关注服务号但无UnionId: ServiceAccountOpenId={OpenId}", serviceAccountOpenId);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理取消关注事件
|
||
/// </summary>
|
||
private async Task HandleUnsubscribeEvent(string? serviceAccountOpenId)
|
||
{
|
||
if (string.IsNullOrEmpty(serviceAccountOpenId))
|
||
{
|
||
_logger.LogWarning("取消关注事件缺少OpenId");
|
||
return;
|
||
}
|
||
|
||
_logger.LogInformation("用户取消关注服务号: ServiceAccountOpenId={OpenId}", serviceAccountOpenId);
|
||
|
||
// 查找并更新用户状态
|
||
var users = await _userRepository.GetListAsync(u => u.ServiceAccountOpenId == serviceAccountOpenId);
|
||
var user = users.FirstOrDefault();
|
||
|
||
if (user != null)
|
||
{
|
||
user.IsFollowServiceAccount = false;
|
||
user.UpdateTime = DateTime.Now;
|
||
await _userRepository.UpdateAsync(user);
|
||
|
||
_logger.LogInformation("用户取消关注服务号: UserId={UserId}", user.Id);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证微信签名
|
||
/// </summary>
|
||
private async Task<bool> CheckSignatureAsync(string signature, string timestamp, string nonce)
|
||
{
|
||
var token = await _configService.GetConfigValueAsync("sa_token");
|
||
if (string.IsNullOrEmpty(token))
|
||
{
|
||
_logger.LogWarning("服务号Token未配置");
|
||
return false;
|
||
}
|
||
|
||
var arr = new[] { token, timestamp, nonce };
|
||
Array.Sort(arr);
|
||
var str = string.Join("", arr);
|
||
|
||
using var sha1 = SHA1.Create();
|
||
var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(str));
|
||
var hashStr = BitConverter.ToString(hash).Replace("-", "").ToLower();
|
||
|
||
return hashStr == signature?.ToLower();
|
||
}
|
||
}
|