优化小程序部分

This commit is contained in:
hero920103 committed 2026-10-31 19:17:37 +08:00
1 parent 727319a6eb
commit d4e89788c5
46 files changed
+4906 -63

No files matched your search

@@ -0,0 +1,135 @@
# 微信小程序「消息推送」配置指引
对应小程序后台:**开发管理 → 开发设置 → 消息推送**(就是你截图里的那个表单)。
---
## 0. 先搞清楚这个配置是干什么的
微信后台的「消息推送」= **你的服务器接收微信主动推送的入口**。
| 场景 | 是否走这个配置 |
| --- | --- |
| 用户给小程序「客服」发消息、进入客服会话 | ✅ 走(微信 POST 到你的 URL) |
| 扫普通链接二维码打开小程序等事件 | ✅ 走 |
| 订阅消息(`wx.requestSubscribeMessage`) | ❌ 不走,用服务端 API 主动发 |
| 模板消息 | ❌ 已下线,改用订阅消息 |
---
## 1. 前置条件(不满足会一直「配置失败」)
1. **域名已 ICP 备案**(微信后台会校验)。
2. **只用 80 / 443 端口**:`https://xxx.com`(443)或 `http://xxx.com`(80),其它端口微信不接受。
3. **微信服务器能直连**:公网可达,不能是 `127.0.0.1`、内网 IP 或本机端口。
4. **HTTPS 证书链完整**:只部署 `.crt` 不带中间证书,微信侧会握手失败(自签证书不行)。
5. **5 秒内返回**:接口要在 5 秒内响应,否则微信断开并重试。
> 本项目的回调地址在 `go/conf/app.conf` 里配置:
> `wechat_mini_callback_base = https://api.yunzer.cn`(留空则退化为相对路径 `/api/wechat/mini/callback`,需由网关补域名)。
---
## 2. 第一步:在本平台生成参数
平台端:**系统设置 → 小程序配置 → 编辑某个小程序 → 消息推送**
| 字段 | 操作 | 说明 |
| --- | --- | --- |
| 回调地址 | 自动生成,点「复制」 | 即 `https://你的域名/api/wechat/mini/callback` |
| Token | 点「随机生成」 | 3-32 位字母数字 |
| EncodingAESKey | 点「随机生成」 | 43 位字母数字,后台也有「随机生成」按钮,两边值必须一致 |
| 加解密方式 | 明文 / 兼容 / 安全 | 与微信后台选择保持一致 |
| 数据格式 | XML / JSON | 与微信后台选择保持一致 |
| 自动回复文案 | 选填 | 留空 = 只接收不回复 |
填完点 **「保存消息推送配置」**(注意:这一步只存平台端,不会自动同步到微信后台)。
---
## 3. 第二步:在微信后台把值抄过去
小程序后台 → 开发管理 → 开发设置 → 消息推送,逐项对应:
| 后台字段 | 填什么 |
| --- | --- |
| URL(服务器地址) | 平台端「回调地址」整条复制,如 `https://api.yunzer.cn/api/wechat/mini/callback` |
| Token(令牌) | 平台端「Token」 |
| EncodingAESKey(消息加密密钥) | 平台端「EncodingAESKey」(43 位) |
| 消息加密方式 | 与平台端一致(明文 / 兼容 / 安全) |
| 数据格式 | 与平台端一致(XML / JSON) |
填完点「提交」,微信会立刻发一个 **GET** 请求到你的 URL 做校验,通过就提示配置成功。
⚠️ 配置**提交后立即生效**,切换加密方式或数据格式前,务必先把平台端改成同样的值,否则消息会被验签/解密失败直接丢弃。
---
## 4. 提交时微信做了什么(校验逻辑)
微信 GET 你的 URL,参数:`signature` `timestamp` `nonce` `echostr`。
- **明文 / 兼容模式**:服务端算 `sha1(sort(token, timestamp, nonce) 拼接)` 与 `signature` 比对,通过后**原样返回 `echostr`**。
- **安全模式**:`echostr` 是密文,额外带 `msg_signature = sha1(sort(token, timestamp, nonce, echostr))`;服务端验签后解密,再返回解密结果。
对应实现:
- `go/services/wechatmini/push.go` —— 签名校验、AES 加解密、XML/JSON 解析、被动回复构造
- `go/controllers/api_wechat_mini.go` —— `GET /api/wechat/mini/callback` 校验 + `POST` 收消息
**多小程序**:每个小程序在自己的后台单独配置,本平台按 AppID 分别维护 Token / AESKey;
回调接口不携带 appid,服务端会逐个应用匹配签名,命中哪个就按哪个的密钥解密。
---
## 5. 收到消息后的行为
- 微信 **5 秒**内没收到响应会断开并重试,**总共重试 3 次**(间隔 15s / 5s / 5s)。
- 返回**空串**或 `success` = 已处理,微信不再重试(我们的实现返回空串)。
- 平台端配了「自动回复文案」时,5 秒内以被动回复形式把文案回给用户。
- 复杂业务(例如按关键字回复、转发人工)可接管钩子:
`services/wechatmini/push.go` 中的 `PushHandler`。
---
## 6. 常见报错排查
| 现象 | 常见原因 | 处理 |
| --- | --- | --- |
| 后台一直「配置失败」 | 域名未备案 / 端口不是 80、443 / 证书链不完整 / 返回内容不是 echostr | 逐条对照第 1 节 |
| 后台提示超时 | 接口处理超过 5 秒 | 先只回 echostr,业务逻辑异步做 |
| 后台提示 Token 校验失败 | 平台端 Token 与后台填的不一致 | 两边都复制一遍核对(区分大小写) |
| 配置成功但收不到消息 | 加解密方式或数据格式两边不一致 | 平台端与后台改成一致 |
| 小程序会话提示「客服暂时无法提供服务」 | 5 秒内没回复 | 配「自动回复文案」,或让接口先返回空串 |
| 明文模式正常,切安全模式收不到 | EncodingAESKey 两边不一致 | 重新复制 43 位密钥 |
### 自测命令(明文模式)
```bash
# signature = sha1(sort(token, timestamp, nonce) 拼接),用与平台端相同的 token
curl "https://api.yunzer.cn/api/wechat/mini/callback?signature=<签名>&timestamp=1700000000&nonce=abc123&echostr=hello"
# 期望输出:hello(等于传入的 echostr)
```
服务端日志会打印 `[wechat-mini] 收到推送: ...`,看不到这行说明微信请求没到服务端(先查域名 / 端口 / 证书)。
---
## 7. 相关接口
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET/POST | `/api/wechat/mini/callback` | 微信回调(无需鉴权,内部验签) |
| GET | `/platform/wechatMini/app/push?app_id=xxx` | 读取推送配置(含回调地址) |
| POST | `/platform/wechatMini/app/push/save` | 保存推送配置 |
| POST | `/platform/wechatMini/app/push/generate` | 生成随机 Token / EncodingAESKey |
## 8. 数据库
新增字段(`token` / `aes_key` / `encrypt_mode` / `data_format` / `push_reply`):
```bash
mysql -h <host> -P <port> -u <user> -p <db> < go/docs/sql/alter_wechat_mini_app_push.sql
```
`aes_key` 以 AES-256-GCM 加密入库(复用公众号的 `wechat_mp_secret_key`),接口只回显掩码。