using Fake.Application; using Microsoft.AspNetCore.Mvc; using SimpleAdmin.Api.WeChat.Options; using SimpleAdmin.Api.WeChat.Services; namespace SimpleAdmin.Api.WeChat; internal static class WeChatMpCallbackSupport { /// /// 微信 GET 接入验签:从 Query 读取参数,验签通过后原样输出 echostr。 /// public static bool TryValidateGetRequest( WeChatMpOptions options, HttpRequest request, ILogger? logger, out string echostr) { echostr = string.Empty; if (string.IsNullOrWhiteSpace(options.Token)) { logger?.LogWarning("微信 GET 验签失败:未配置 Token"); return false; } var signature = request.Query["signature"].ToString(); var timestamp = request.Query["timestamp"].ToString(); var nonce = request.Query["nonce"].ToString(); echostr = request.Query["echostr"].ToString(); if (string.IsNullOrWhiteSpace(signature) || string.IsNullOrWhiteSpace(timestamp) || string.IsNullOrWhiteSpace(nonce) || string.IsNullOrEmpty(echostr)) { logger?.LogWarning( "微信 GET 验签失败:缺少必要参数 signature={HasSig} timestamp={HasTs} nonce={HasNonce} echostr={HasEcho}", !string.IsNullOrWhiteSpace(signature), !string.IsNullOrWhiteSpace(timestamp), !string.IsNullOrWhiteSpace(nonce), !string.IsNullOrEmpty(echostr)); return false; } if (!WeChatSignatureHelper.Validate(options.Token, timestamp, nonce, signature)) { logger?.LogWarning( "微信 GET 验签失败:signature 不匹配 timestamp={Timestamp} nonce={Nonce}", timestamp, nonce); return false; } return true; } public static ContentResult Validate( WeChatMpOptions options, HttpRequest request, string? signature, string? timestamp, string? nonce) { if (!TryValidateGetRequest(options, request, null, out var echostr)) { return PlainError(); } return EchoStr(echostr); } /// 微信或探活可能发 HEAD,须返回 200(WMS/Spring 默认支持)。 public static ContentResult HeadOk() => new() { Content = string.Empty, ContentType = "text/plain; charset=utf-8", StatusCode = StatusCodes.Status200OK }; public static ContentResult PlainError() => PlainText("error"); public static async Task HandleMessageAsync( WeChatMpOptions options, IWeChatEventHandler eventHandler, HttpRequest request, ILogger logger, string signature, string timestamp, string nonce, string? encryptType, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(options.Token)) { logger.LogWarning("微信消息回调未配置 Token"); return PlainText("error"); } // token、timestamp、nonce 字典序排序后拼接,SHA1 与 signature 对比 if (!WeChatSignatureHelper.Validate(options.Token, timestamp, nonce, signature)) { logger.LogWarning("微信消息验签失败"); return PlainText("error"); } using var reader = new StreamReader(request.Body); var requestBody = await reader.ReadToEndAsync(cancellationToken); if (string.IsNullOrWhiteSpace(requestBody)) { return AckSuccess(); } // 明文模式:无 encrypt_type 或 encrypt_type=raw;安全模式 AES 暂不支持 if (!string.IsNullOrWhiteSpace(encryptType) && !string.Equals(encryptType, "raw", StringComparison.OrdinalIgnoreCase)) { logger.LogWarning( "收到加密微信消息({EncryptType}),当前仅支持明文模式,请在公众平台切换为明文或后续启用 AES。", encryptType); return AckSuccess(); } var message = WeChatIncomingMessageParser.Parse(requestBody, request.ContentType); if (message == null) { logger.LogWarning("无法解析微信消息体(支持明文 XML / JSON)"); return AckSuccess(); } var reply = await eventHandler.HandleAsync(message, cancellationToken); return BuildMessageReply(reply); } public static void EnsureAppId(WeChatMpOptions options, string appId) { var configured = options.AppId; if (string.IsNullOrWhiteSpace(configured)) { throw new BusinessException("未配置微信公众号 AppId"); } if (!string.Equals(configured, appId, StringComparison.OrdinalIgnoreCase)) { throw new BusinessException("公众号 AppId 不匹配"); } } private static ContentResult EchoStr(string echostr) => new() { Content = echostr, ContentType = "text/plain", StatusCode = StatusCodes.Status200OK }; private static ContentResult AckSuccess() => PlainText("success"); private static ContentResult BuildMessageReply(string? reply) { if (string.IsNullOrEmpty(reply)) { return AckSuccess(); } return reply.TrimStart().StartsWith('<') ? Xml(reply) : PlainText(reply); } private static ContentResult PlainText(string content) => new() { Content = content, ContentType = "text/plain; charset=utf-8", StatusCode = StatusCodes.Status200OK }; private static ContentResult Xml(string content) => new() { Content = content, ContentType = "application/xml; charset=utf-8", StatusCode = StatusCodes.Status200OK }; }