# 微信 (18_Wechat)

最后更新: 2024-09-20


# 📚 概述

DarkM 框架的微信模块提供了完整的微信公众号/小程序开发能力。包括消息处理、菜单管理、用户管理、微信支付、JS-SDK 等功能,支持多账号管理。

源代码位置: DarkM/src/Framework/Wechat


# 🏗️ 模块架构

# 项目结构

Wechat/
├── Wechat.Abstractions/               # 抽象层(配置、模型、异常)
│   ├── WechatConfig.cs                # 微信配置类
│   ├── WechatAccount.cs               # 微信账号配置
│   ├── CacheKeys.cs                   # 缓存键定义
│   ├── FuncFlag.cs                    # 功能标识
│   ├── Exception/                     # 异常类
│   ├── Module/                        # 微信消息模型
│   │   ├── TextMessage.cs             # 文本消息
│   │   ├── ImageMessage.cs            # 图片消息
│   │   ├── VoiceMessage.cs            # 语音消息
│   │   ├── VideoMessage.cs            # 视频消息
│   │   ├── LocationMessage.cs         # 位置消息
│   │   ├── LinkMessage.cs             # 链接消息
│   │   └── EventMessage.cs            # 事件消息
│   └── Support/                       # 支持类
│       ├── JsSdkConfig.cs             # JS-SDK 配置
│       ├── AccessToken.cs             # 访问令牌
│       └── JsApiTicket.cs             # JSAPI 票据
│
├── Wechat.Core/                       # 核心实现
│   ├── WechatProcesser.cs             # 微信处理器
│   ├── WechatHandleMiddleware.cs      # 微信处理中间件
│   ├── WechatHelper.cs                # 微信帮助类
│   ├── Commands/                      # 命令处理
│   │   └── EventHandle/               # 事件处理器
│   │       ├── BaseEventHandle.cs     # 基础事件处理
│   │       ├── TextEventHandle.cs     # 文本事件处理
│   │       └── NotBindedHandle.cs     # 未绑定处理
│   └── Support/                       # 支持类
│       ├── WechatProvider.cs          # 微信提供器
│       └── WechatHandler.cs           # 微信处理器
│
├── Wechat.Integration/                # 集成层(DI 注册)
│   └── ServiceCollectionExtensions.cs  # DI 扩展
│
└── WxPayAPI/                          # 微信支付 SDK(官方)
    ├── lib/                           # 基础库
    │   ├── Config.cs                  # 支付配置
    │   ├── WxPayApi.cs                # 支付 API
    │   ├── Data.cs                    # 数据结构
    │   └── Notify.cs                  # 通知处理
    └── business/                      # 业务类
        ├── JsApiPay.cs                # JSAPI 支付
        ├── NativePay.cs               # 扫码支付
        ├── OrderQuery.cs              # 订单查询
        ├── Refund.cs                  # 退款
        └── RefundQuery.cs             # 退款查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 🔧 核心配置类

# WechatConfig(微信配置)

public class WechatConfig
{
    /// <summary>
    /// 默认的 AppId
    /// </summary>
    public string Default { get; set; }

    /// <summary>
    /// 网站地址 (域名)
    /// </summary>
    public string WebSite { get; set; }

    /// <summary>
    /// 关注后跳转首页(绑定页面)
    /// </summary>
    public string HomePage { get; set; }

    /// <summary>
    /// 是否测试阶段
    /// </summary>
    public bool Test { get; set; }

    /// <summary>
    /// 是否维护状态
    /// </summary>
    public bool Maintenance { get; set; }

    /// <summary>
    /// 维护页面地址
    /// </summary>
    public string MaintenancePage { get; set; }

    /// <summary>
    /// 消息类型配置
    /// 0:全部使用小 I 机器人
    /// 1:白名单方式使用小 I
    /// 10:禁止使用小 I
    /// 20:小 I 发生错误时使用默认
    /// 99:不回复任何消息
    /// </summary>
    public int MessageType { get; set; }

    /// <summary>
    /// 微信账号列表
    /// </summary>
    public List<WechatAccount> Servers { get; set; }

    /// <summary>
    /// 获取指定账号
    /// </summary>
    public WechatAccount GetAccount(string appId)
    {
        return Servers?.FirstOrDefault(s => s.AppID.Equals(appId));
    }

    /// <summary>
    /// 获取默认账号
    /// </summary>
    public WechatAccount DefaultServer => GetAccount(Default);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# WechatAccount(微信账号配置)

public class WechatAccount
{
    //=======【基本信息设置】=====================================
    
    /// <summary>
    /// APPID 对应的公众号/小程序名称
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// APPID 对应的公众号/小程序描述说明
    /// </summary>
    public string Label { get; set; }

    /// <summary>
    /// APPID:绑定支付的 APPID(必须配置)
    /// </summary>
    public string AppID { get; set; }

    /// <summary>
    /// APPSECRET:公众帐号 secert(仅 JSAPI 支付时需要)
    /// </summary>
    public string AppSecret { get; set; }

    /// <summary>
    /// MCHID:商户号(必须配置)
    /// </summary>
    public string MchID { get; set; }

    /// <summary>
    /// 商户支付密钥(必须配置)
    /// </summary>
    public string PayKey { get; set; }

    /// <summary>
    /// Token(用于验证微信服务器)
    /// </summary>
    public string UserToken { get; set; }

    /// <summary>
    /// 是否小程序账号
    /// </summary>
    public bool MiniApp { get; set; }

    /// <summary>
    /// 证书路径(仅退款、撤销订单时需要)
    /// </summary>
    public string SSlCertPath { get; set; }

    /// <summary>
    /// 证书密码
    /// </summary>
    public string SSlCertPassword { get; set; }

    /// <summary>
    /// 支付结果通知回调 URL
    /// </summary>
    public string NotifyUrl { get; set; }

    /// <summary>
    /// 退款通知地址
    /// </summary>
    public string RefundNotifyUrl { get; set; }

    //=======【高级配置】=====================================
    
    /// <summary>
    /// APIv3 密钥(v3 接口必填)
    /// </summary>
    public string V3Key { get; set; }

    /// <summary>
    /// RSA 公钥(企业付款到银行卡时使用)
    /// </summary>
    public string RsaPublicKey { get; set; }

    /// <summary>
    /// 子商户应用号(服务商模式)
    /// </summary>
    public string SubAppId { get; set; }

    /// <summary>
    /// 子商户号(服务商模式)
    /// </summary>
    public string SubMchId { get; set; }

    /// <summary>
    /// 服务器 IP(可自动获取)
    /// </summary>
    public string ServerIp { get; set; }

    /// <summary>
    /// 代理 URL(如有需要才设置)
    /// </summary>
    public string ProxyUrl { get; set; }

    /// <summary>
    /// 日志等级
    /// 0.不输出日志
    /// 1.只输出错误信息
    /// 2.输出错误和正常信息
    /// 3.输出错误信息、正常信息和调试信息
    /// </summary>
    public int LogLevel { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

# JsSdkConfig(JS-SDK 配置)

public class JsSdkConfig
{
    /// <summary>
    /// 公众号的唯一标识
    /// </summary>
    public string AppId { get; set; }

    /// <summary>
    /// 时间戳
    /// </summary>
    public string Timestamp { get; set; }

    /// <summary>
    /// 随机字符串
    /// </summary>
    public string NonceStr { get; set; }

    /// <summary>
    /// 签名
    /// </summary>
    public string Signature { get; set; }

    /// <summary>
    /// JS 接口列表
    /// </summary>
    public string[] JsApiList => JsApi.Split(',');

    /// <summary>
    /// 默认 JS 接口列表
    /// </summary>
    public static String JsApi = "JSAPI,onMenuShareTimeline,onMenuShareAppMessage," +
        "onMenuShareQQ,onMenuShareWeibo,startRecord,stopRecord,onVoiceRecordEnd," +
        "playVoice,pauseVoice,stopVoice,onVoicePlayEnd,uploadVoice,downloadVoice," +
        "chooseImage,previewImage,uploadImage,downloadImage,translateVoice," +
        "getNetworkType,openLocation,getLocation,hideOptionMenu,showOptionMenu," +
        "hideMenuItems,showMenuItems,hideAllNonBaseMenuItem,showAllNonBaseMenuItem," +
        "closeWindow,scanQRCode,chooseWXPay,openProductSpecificView,addCard," +
        "chooseCard,openCard";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 🏗️ 依赖注入

# 自动注册机制

// Wechat.Integration/ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// 添加微信相关功能
    /// </summary>
    public static IServiceCollection AddWechat(
        this IServiceCollection services, 
        IConfiguration cfg)
    {
        var config = new WechatConfig();
        var section = cfg.GetSection("Wechat");

        if (section != null)
        {
            section.Bind(config);
            services.AddSingleton(config);

            // 加载 Wechat.Core 程序集
            var assembly = AssemblyHelper.LoadByNameEndString(
                $".Lib.Wechat.Core");
            
            if (assembly == null)
                return services;

            // 注册微信处理器
            var providerType = assembly.GetTypes()
                .FirstOrDefault(m => m.Name.EndsWith("WechatProcesser"));
            
            if (providerType != null)
            {
                services.AddSingleton(providerType);
            }
        }

        return services;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 💡 使用示例

# 1. 配置微信模块

// appsettings.json
{
  "Wechat": {
    "Default": "wx1234567890abcdef",
    "WebSite": "https://www.example.com",
    "HomePage": "/wechat/bind",
    "Test": false,
    "Maintenance": false,
    "MessageType": 0,
    "Servers": [
      {
        "AppID": "wx1234567890abcdef",
        "Name": "我的公众号",
        "Label": "公众号描述",
        "AppSecret": "your-app-secret",
        "MchID": "1234567890",
        "PayKey": "your-pay-key-32-chars",
        "UserToken": "your-token",
        "MiniApp": false,
        "SSlCertPath": "/path/to/apiclient_cert.p12",
        "SSlCertPassword": "1234567890",
        "NotifyUrl": "https://api.example.com/wechatpay/notify",
        "RefundNotifyUrl": "https://api.example.com/wechatpay/refund-notify",
        "LogLevel": 2
      }
    ]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 2. 注册服务

// Program.cs 或 Startup.cs
var builder = WebApplication.CreateBuilder(args);

// 添加微信服务
builder.Services.AddWechat(builder.Configuration);

// 添加微信处理中间件
builder.Services.AddWechatHandleMiddleware();

var app = builder.Build();

// 使用微信处理中间件
app.UseWechatHandleMiddleware();

app.Run();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 3. 微信消息处理

public class WechatMessageController : ControllerBase
{
    private readonly WechatProcesser _wechatProcesser;
    private readonly WechatConfig _config;
    private readonly ILogger<WechatMessageController> _logger;

    public WechatMessageController(
        WechatProcesser wechatProcesser,
        WechatConfig config,
        ILogger<WechatMessageController> logger)
    {
        _wechatProcesser = wechatProcesser;
        _config = config;
        _logger = logger;
    }

    /// <summary>
    /// 微信服务器验证(GET)
    /// </summary>
    [HttpGet("wechat/message")]
    public string ValidateWechat(
        [FromQuery] string signature,
        [FromQuery] string timestamp,
        [FromQuery] string nonce,
        [FromQuery] string echostr)
    {
        var account = _config.DefaultServer;
        
        if (WechatUtility.VerifySignature(
            signature, timestamp, nonce, account.UserToken))
        {
            return echostr;
        }

        return "error";
    }

    /// <summary>
    /// 接收微信消息(POST)
    /// </summary>
    [HttpPost("wechat/message")]
    public async Task<string> ReceiveMessage()
    {
        using var reader = new StreamReader(Request.Body);
        var xml = await reader.ReadToEndAsync();

        _logger.LogInformation("收到微信消息:{Xml}", xml);

        // 处理消息
        var response = await _wechatProcesser.ProcessMessageAsync(xml);

        _logger.LogInformation("返回微信响应:{Response}", response);

        return response ?? "success";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 4. 获取 Access Token

public class WechatTokenService
{
    private readonly WechatConfig _config;
    private readonly IMemoryCache _cache;
    private readonly ILogger<WechatTokenService> _logger;

    public WechatTokenService(
        WechatConfig config,
        IMemoryCache cache,
        ILogger<WechatTokenService> logger)
    {
        _config = config;
        _cache = cache;
        _logger = logger;
    }

    /// <summary>
    /// 获取 Access Token(带缓存)
    /// </summary>
    public async Task<string> GetAccessTokenAsync(string appId = null)
    {
        appId ??= _config.Default;
        var account = _config.GetAccount(appId);

        if (account == null)
            throw new WechatException($"未找到公众号配置:{appId}");

        var cacheKey = $"wechat_access_token_{appId}";

        return await _cache.GetOrCreateAsync(
            cacheKey, 
            async entry =>
            {
                entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(7000);

                var url = $"https://api.weixin.qq.com/cgi-bin/token?" +
                    $"grant_type=client_credential&" +
                    $"appid={account.AppID}&" +
                    $"secret={account.AppSecret}";

                using var httpClient = new HttpClient();
                var response = await httpClient.GetStringAsync(url);
                var json = JsonDocument.Parse(response);

                if (json.RootElement.TryGetProperty("access_token", out var tokenElem))
                {
                    return tokenElem.GetString();
                }

                throw new WechatException(
                    $"获取 Access Token 失败:{response}");
            });
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

# 5. JS-SDK 签名配置

public class WechatJsSdkService
{
    private readonly WechatConfig _config;
    private readonly WechatTokenService _tokenService;

    public WechatJsSdkService(
        WechatConfig config,
        WechatTokenService tokenService)
    {
        _config = config;
        _tokenService = tokenService;
    }

    /// <summary>
    /// 获取 JS-SDK 配置
    /// </summary>
    public async Task<JsSdkConfig> GetJsSdkConfigAsync(
        string url, 
        string appId = null)
    {
        appId ??= _config.Default;
        var account = _config.GetAccount(appId);

        // 获取 JsApiTicket
        var ticket = await GetJsApiTicketAsync(appId);

        // 生成签名参数
        var timestamp = WechatUtility.GetTimestamp();
        var noncestr = Guid.NewGuid().ToString("N");

        // 生成签名
        var signature = WechatUtility.CreateJsSdkSignature(
            ticket, noncestr, timestamp, url);

        return new JsSdkConfig
        {
            AppId = account.AppID,
            Timestamp = timestamp,
            NonceStr = noncestr,
            Signature = signature
        };
    }

    /// <summary>
    /// 获取 JsApiTicket(带缓存)
    /// </summary>
    private async Task<string> GetJsApiTicketAsync(string appId)
    {
        var cacheKey = $"wechat_jsapi_ticket_{appId}";

        return await _cache.GetOrCreateAsync(
            cacheKey,
            async entry =>
            {
                entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(7000);

                var accessToken = await _tokenService.GetAccessTokenAsync(appId);

                var url = $"https://api.weixin.qq.com/cgi-bin/ticket/getticket?" +
                    $"access_token={accessToken}&type=jsapi";

                using var httpClient = new HttpClient();
                var response = await httpClient.GetStringAsync(url);
                var json = JsonDocument.Parse(response);

                if (json.RootElement.TryGetProperty("ticket", out var ticketElem))
                {
                    return ticketElem.GetString();
                }

                throw new WechatException(
                    $"获取 JsApiTicket 失败:{response}");
            });
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

# 6. 微信支付(JSAPI)

public class WechatPayService
{
    private readonly WechatConfig _config;
    private readonly ILogger<WechatPayService> _logger;

    public WechatPayService(
        WechatConfig config,
        ILogger<WechatPayService> logger)
    {
        _config = config;
        _logger = logger;
    }

    /// <summary>
    /// 统一下单
    /// </summary>
    public WxPayData CreateUnifiedOrder(
        string openId,
        string outTradeNo,
        decimal amount,
        string body)
    {
        var account = _config.DefaultServer;

        var req = new WxPayData();
        req.SetValue("appid", account.AppID);
        req.SetValue("mch_id", account.MchID);
        req.SetValue("nonce_str", WxPayApi.GenerateNonceStr());
        req.SetValue("body", body);
        req.SetValue("out_trade_no", outTradeNo);
        req.SetValue("total_fee", (int)(amount * 100)); // 单位:分
        req.SetValue("spbill_create_ip", GetClientIp());
        req.SetValue("notify_url", account.NotifyUrl);
        req.SetValue("trade_type", "JSAPI");
        req.SetValue("openid", openId);

        // 生成签名
        req.SetValue("sign", WxPayApi.GenerateSign(req));

        // 调用统一下单 API
        var response = WxPayApi.UnifiedOrder(req);

        if (response.GetValue("return_code").ToString() != "SUCCESS" ||
            response.GetValue("result_code").ToString() != "SUCCESS")
        {
            throw new WechatPayException(
                $"微信支付下单失败:{response.GetValue("return_msg")}");
        }

        return response;
    }

    /// <summary>
    /// 获取小程序支付参数
    /// </summary>
    public WxPayData GetJsApiPayData(string prepayId)
    {
        var account = _config.DefaultServer;

        var jsApiPayData = new WxPayData();
        jsApiPayData.SetValue("appId", account.AppID);
        jsApiPayData.SetValue("timeStamp", WxPayApi.GetTimestamp());
        jsApiPayData.SetValue("nonceStr", WxPayApi.GenerateNonceStr());
        jsApiPayData.SetValue("package", $"prepay_id={prepayId}");
        jsApiPayData.SetValue("signType", "MD5");

        // 生成支付签名
        jsApiPayData.SetValue("paySign", 
            WxPayApi.GenerateSign(jsApiPayData));

        return jsApiPayData;
    }

    /// <summary>
    /// 查询订单
    /// </summary>
    public WxPayData QueryOrder(string outTradeNo)
    {
        var req = new WxPayData();
        req.SetValue("out_trade_no", outTradeNo);

        return WxPayApi.OrderQuery(req);
    }

    /// <summary>
    /// 申请退款
    /// </summary>
    public WxPayData Refund(
        string outTradeNo,
        string outRefundNo,
        decimal refundAmount,
        decimal totalAmount)
    {
        var account = _config.DefaultServer;

        var req = new WxPayData();
        req.SetValue("out_trade_no", outTradeNo);
        req.SetValue("out_refund_no", outRefundNo);
        req.SetValue("total_fee", (int)(totalAmount * 100));
        req.SetValue("refund_fee", (int)(refundAmount * 100));
        req.SetValue("op_user_id", account.MchID);

        return WxPayApi.Refund(req);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

# 🔧 核心功能

# 消息类型

类型 说明 示例
文本消息 用户发送的文本 TextMessage
图片消息 用户发送的图片 ImageMessage
语音消息 用户发送的语音 VoiceMessage
视频消息 用户发送的视频 VideoMessage
位置消息 用户发送的位置 LocationMessage
链接消息 用户发送的链接 LinkMessage
事件消息 关注/取消关注等 EventMessage

# 事件类型

事件 说明 处理类
subscribe 用户关注 SubscribeEventHandle
unsubscribe 用户取消关注 UnsubscribeEventHandle
SCAN 用户扫码 ScanEventHandle
LOCATION 上报地理位置 LocationEventHandle
CLICK 点击菜单 ClickEventHandle
VIEW 点击菜单链接 ViewEventHandle

# 支付功能

功能 说明 业务类
JSAPI 支付 公众号/小程序支付 JsApiPay
扫码支付 Native 扫码支付 NativePay
订单查询 查询订单状态 OrderQuery
申请退款 退款操作 Refund
退款查询 查询退款状态 RefundQuery
下载对账单 下载交易对账单 DownloadBill

# 💡 最佳实践

# 1. 多账号管理

public class MultiWechatAccountService
{
    private readonly WechatConfig _config;
    private readonly IMemoryCache _cache;

    public MultiWechatAccountService(
        WechatConfig config,
        IMemoryCache cache)
    {
        _config = config;
        _cache = cache;
    }

    /// <summary>
    /// 根据 AppID 获取账号
    /// </summary>
    public WechatAccount GetAccount(string appId)
    {
        return _config.GetAccount(appId);
    }

    /// <summary>
    /// 获取默认账号
    /// </summary>
    public WechatAccount GetDefaultAccount()
    {
        return _config.DefaultServer;
    }

    /// <summary>
    /// 获取所有账号
    /// </summary>
    public List<WechatAccount> GetAllAccounts()
    {
        return _config.Servers ?? new List<WechatAccount>();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 2. Access Token 缓存

public class WechatTokenCacheService
{
    private readonly IMemoryCache _cache;

    public WechatTokenCacheService(IMemoryCache cache)
    {
        _cache = cache;
    }

    /// <summary>
    /// 获取 Token(自动缓存)
    /// </summary>
    public async Task<string> GetTokenAsync(
        string appId, 
        string appSecret,
        TokenType type = TokenType.AccessToken)
    {
        var cacheKey = $"wechat_{type.ToString().ToLower()}_{appId}";

        return await _cache.GetOrCreateAsync(
            cacheKey,
            async entry =>
            {
                // AccessToken 有效期 7200 秒,提前 5 分钟刷新
                entry.AbsoluteExpirationRelativeToNow = 
                    type == TokenType.AccessToken 
                        ? TimeSpan.FromSeconds(7000)
                        : TimeSpan.FromSeconds(7000);

                return await FetchTokenAsync(appId, appSecret, type);
            });
    }

    /// <summary>
    /// 清除缓存
    /// </summary>
    public void ClearCache(string appId)
    {
        var keys = new[] {
            $"wechat_access_token_{appId}",
            $"wechat_jsapi_ticket_{appId}"
        };

        foreach (var key in keys)
        {
            _cache.Remove(key);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 3. 消息回复模板

public class WechatReplyTemplate
{
    /// <summary>
    /// 文本消息回复
    /// </summary>
    public static string CreateTextReply(
        string fromUser, 
        string toUser, 
        string content)
    {
        return $@"<xml>
<ToUserName><![CDATA[{fromUser}]]></ToUserName>
<FromUserName><![CDATA[{toUser}]]></FromUserName>
<CreateTime>{DateTimeOffset.Now.ToUnixTimeSeconds()}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[{content}]]></Content>
</xml>";
    }

    /// <summary>
    /// 图文消息回复
    /// </summary>
    public static string CreateNewsReply(
        string fromUser,
        string toUser,
        List<NewsArticle> articles)
    {
        var articlesXml = string.Join("", articles.Select(a => $@"
<item>
<Title><![CDATA[{a.Title}]]></Title>
<Description><![CDATA[{a.Description}]]></Description>
<PicUrl><![CDATA[{a.PicUrl}]]></PicUrl>
<Url><![CDATA[{a.Url}]]></Url>
</item>"));

        return $@"<xml>
<ToUserName><![CDATA[{fromUser}]]></ToUserName>
<FromUserName><![CDATA[{toUser}]]></FromUserName>
<CreateTime>{DateTimeOffset.Now.ToUnixTimeSeconds()}</CreateTime>
<MsgType><![CDATA[news]]></MsgType>
<ArticleCount>{articles.Count}</ArticleCount>
<Articles>{articlesXml}</Articles>
</xml>";
    }
}

public class NewsArticle
{
    public string Title { get; set; }
    public string Description { get; set; }
    public string PicUrl { get; set; }
    public string Url { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 🔍 常见问题

# Q1: 微信服务器验证失败?

检查清单:

  1. ✅ Token 配置是否与微信后台一致
  2. ✅ 验证算法是否正确(SHA1 排序)
  3. ✅ 返回 echostr 参数是否正确
  4. ✅ 服务器是否可被微信访问(80/443 端口)

验证代码:

public static bool VerifySignature(
    string signature,
    string timestamp,
    string nonce,
    string token)
{
    var arr = new[] { token, timestamp, nonce }.OrderBy(x => x).ToArray();
    var hash = string.Join("", arr).ToSHA1();
    return hash.Equals(signature, StringComparison.OrdinalIgnoreCase);
}
1
2
3
4
5
6
7
8
9
10

# Q2: Access Token 频繁失效?

解决方案:

  1. ✅ 使用缓存(有效期 7200 秒,提前刷新)
  2. ✅ 避免每次请求都获取 Token
  3. ✅ 多进程共享缓存(使用 Redis)
  4. ✅ Token 失效时自动重试获取
// 使用 Redis 共享 Token
public class RedisWechatTokenService
{
    private readonly IDistributedCache _cache;

    public async Task<string> GetTokenAsync(string appId, string appSecret)
    {
        var key = $"wechat:token:{appId}";
        
        var token = await _cache.GetStringAsync(key);
        if (token != null)
            return token;

        // 获取新 Token
        token = await FetchTokenAsync(appId, appSecret);
        
        // 缓存 7000 秒
        await _cache.SetStringAsync(
            key, 
            token, 
            new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(7000)
            });

        return token;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# Q3: 微信支付证书加载失败?

解决方案:

// 正确加载证书
public X509Certificate2 LoadCertificate(string certPath, string password)
{
    if (!File.Exists(certPath))
        throw new FileNotFoundException($"证书文件不存在:{certPath}");

    return new X509Certificate2(
        certPath,
        password,
        X509KeyStorageFlags.MachineKeySet |
        X509KeyStorageFlags.PersistKeySet |
        X509KeyStorageFlags.Exportable);
}

// 配置示例
{
  "Wechat": {
    "Servers": [
      {
        "SSlCertPath": "/path/to/apiclient_cert.p12",
        "SSlCertPassword": "1234567890"
      }
    ]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 📚 相关文档


# 🔗 参考链接


最后更新: 2024-09-20