xiangyixiangqin/server/src/XiangYi.AppApi/Controllers/WeChatEventController.cs
zpc 4af5ae8065
All checks were successful
continuous-integration/drone/push Build is passing
feat(wechat): Add WeChat official account token and encryption key configuration
- 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
2026-03-29 20:03:12 +08:00

193 lines
6.3 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.

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();
}
}