feat(iit-manager): Add WeChat Official Account integration for patient notifications

Features:
- PatientWechatCallbackController for URL verification and message handling
- PatientWechatService for template and customer messages
- Support for secure mode (message encryption/decryption)
- Simplified route /wechat/patient/callback for WeChat config
- Event handlers for subscribe/unsubscribe/text messages
- Template message for visit reminders

Technical details:
- Reuse @wecom/crypto for encryption (compatible with Official Account)
- Relaxed Fastify schema validation to prevent early request blocking
- Access token caching (7000s with 5min pre-refresh)
- Comprehensive logging for debugging

Testing: Local URL verification passed, ready for SAE deployment

Status: Code complete, waiting for WeChat platform configuration
This commit is contained in:
2026-01-04 22:53:42 +08:00
parent dfc472810b
commit b31255031e
167 changed files with 3055 additions and 2 deletions

View File

@@ -0,0 +1,532 @@
# 微信服务号接入指南(患者端)
> **版本**: v1.0
> **创建日期**: 2026-01-04
> **目标**: 为患者端接入微信服务号,实现访视提醒和消息推送
> **预估工作量**: 3天
---
## 📋 一、准备工作清单
### 1.1 微信服务号信息
**已完成**
- 服务号名称:`AI for 临床研究`
- AppID`wx062568ff49e4570c`
- AppSecret`c0d19435d1a1e948939c16d767ec0faf`
- 认证状态:✅ 已认证(企业认证)
- 主体名称:`北京壹证循科技有限公司`
### 1.2 需要配置的参数
🔧 **待配置**
1. **Token**3-32位字符串
2. **EncodingAESKey**43位字符串
3. **服务器URL**(回调地址)
---
## 🔐 二、生成Token和EncodingAESKey
### 2.1 Token生成推荐使用随机字符串
```bash
# 方法1使用OpenSSL推荐
openssl rand -base64 24 | tr -d '/+=' | cut -c1-32
# 方法2使用Node.js
node -e "console.log(require('crypto').randomBytes(24).toString('base64').replace(/[\/\+=]/g, '').substring(0, 32))"
# 方法3在线生成器
# https://suijimimashengcheng.51240.com/
```
**推荐Token**(示例,请重新生成):
```
IitPatientWechat2026Jan04Abc
```
### 2.2 EncodingAESKey生成必须43位
```bash
# 方法1使用OpenSSL推荐
openssl rand -base64 43 | tr -d '/+=' | head -c 43
# 方法2使用Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('base64').replace(/[\/\+=]/g, '').substring(0, 43))"
# 方法3微信公众平台随机生成最简单
# 登录微信公众平台 → 基本配置 → 消息加密密钥 → 点击"随机生成"
```
**推荐EncodingAESKey**(示例,请重新生成):
```
abcdefghijklmnopqrstuvwxyz0123456789ABC
```
---
## ⚙️ 三、配置步骤(详细)
### 3.1 更新环境变量
编辑 `backend/.env` 文件,添加以下配置:
```env
# ==========================================
# 微信服务号配置(患者端)
# ==========================================
# 微信服务号基础配置
WECHAT_MP_APP_ID=wx062568ff49e4570c
WECHAT_MP_APP_SECRET=c0d19435d1a1e948939c16d767ec0faf
# 微信服务号回调配置(消息加解密,安全模式)
WECHAT_MP_TOKEN=IitPatientWechat2026Jan04Abc
WECHAT_MP_ENCODING_AES_KEY=abcdefghijklmnopqrstuvwxyz0123456789ABC
# 微信小程序配置(可选,后续开发)
WECHAT_MINI_APP_ID=
```
⚠️ **注意**
1. Token和EncodingAESKey必须与微信公众平台配置的**完全一致**
2. Token长度3-32位建议使用英文字母和数字
3. EncodingAESKey长度**必须43位**,大小写敏感
### 3.2 配置微信公众平台
#### Step 1: 登录微信公众平台
访问https://mp.weixin.qq.com/
使用管理员微信扫码登录(账号:`zhi***ng`
#### Step 2: 进入基本配置页面
```
左侧菜单 → 设置与开发 → 基本配置
```
#### Step 3: 配置服务器地址
找到 **"服务器配置"** 部分,点击 **"修改配置"**
填写以下信息:
| 配置项 | 值 | 说明 |
|--------|-----|------|
| **URL** | `https://iit.xunzhengyixue.com/api/v1/iit/patient-wechat/callback` | 生产环境回调URL |
| **Token** | `IitPatientWechat2026Jan04Abc` | 与.env中的WECHAT_MP_TOKEN一致 |
| **EncodingAESKey** | `abcdefghijklmnopqrstuvwxyz0123456789ABC` | 与.env中的WECHAT_MP_ENCODING_AES_KEY一致 |
| **消息加解密方式** | ✅ **安全模式(推荐)** | 选择"安全模式" |
| **数据格式** | ✅ **XML** | 默认选择 |
**本地开发环境**使用natapp内网穿透
```
URL: https://devlocal.xunzhengyixue.com/api/v1/iit/patient-wechat/callback
```
#### Step 4: 点击"提交"并验证
微信会发送GET请求到您的服务器进行验证
```
GET https://iit.xunzhengyixue.com/api/v1/iit/patient-wechat/callback?signature=xxx&timestamp=xxx&nonce=xxx&echostr=xxx
```
**验证成功标志**
- ✅ 页面显示"配置成功"
- ✅ "服务器配置"状态为"已启用"
**验证失败原因**
- ❌ Token不一致
- ❌ 服务器无法访问(防火墙、未部署)
- ❌ 代码逻辑错误(签名验证失败)
#### Step 5: 启用服务器配置
验证成功后,点击 **"启用"** 按钮。
⚠️ **注意**:启用后,公众号的消息和事件会推送到您的服务器,不会显示在公众平台后台。
---
## 🧪 四、测试验证
### 4.1 测试URL验证手动测试
在配置微信公众平台时,点击"提交"按钮会自动触发URL验证。
查看后端日志:
```bash
# 本地开发
cd D:\MyCursor\AIclinicalresearch\backend
npm run dev
# 查看日志
# 应该看到类似以下日志:
# ✅ 微信服务号回调控制器初始化成功
# 📥 收到微信服务号 URL 验证请求
# ✅ URL 验证成功,返回 echostr
```
### 4.2 测试脚本1验证Token和AESKey配置
创建测试脚本 `backend/src/modules/iit-manager/test-patient-wechat-config.ts`
```typescript
import dotenv from 'dotenv';
import crypto from 'crypto';
dotenv.config();
console.log('🔧 微信服务号配置检查\n');
// 1. 检查必需的环境变量
const requiredEnvs = [
'WECHAT_MP_APP_ID',
'WECHAT_MP_APP_SECRET',
'WECHAT_MP_TOKEN',
'WECHAT_MP_ENCODING_AES_KEY',
];
let hasError = false;
requiredEnvs.forEach((key) => {
const value = process.env[key];
if (!value) {
console.error(`❌ 缺少环境变量: ${key}`);
hasError = true;
} else {
console.log(`${key}: ${value.substring(0, 10)}... (长度: ${value.length})`);
}
});
if (hasError) {
console.error('\n❌ 配置不完整,请检查 .env 文件');
process.exit(1);
}
// 2. 验证Token长度
const token = process.env.WECHAT_MP_TOKEN!;
if (token.length < 3 || token.length > 32) {
console.error(`\n❌ Token长度不正确: ${token.length}应为3-32位`);
hasError = true;
} else {
console.log(`\n✅ Token长度正确: ${token.length}`);
}
// 3. 验证EncodingAESKey长度
const aesKey = process.env.WECHAT_MP_ENCODING_AES_KEY!;
if (aesKey.length !== 43) {
console.error(`❌ EncodingAESKey长度不正确: ${aesKey.length}必须43位`);
hasError = true;
} else {
console.log(`✅ EncodingAESKey长度正确: 43位`);
}
// 4. 测试签名生成
console.log('\n🔐 测试签名生成...');
const timestamp = Date.now().toString();
const nonce = Math.random().toString(36).substring(2);
const arr = [token, timestamp, nonce].sort();
const str = arr.join('');
const signature = crypto.createHash('sha1').update(str).digest('hex');
console.log(`生成的签名: ${signature}`);
console.log(`✅ 签名生成功能正常`);
// 5. 总结
if (hasError) {
console.error('\n❌ 配置检查失败,请修复错误后重试');
process.exit(1);
} else {
console.log('\n✅ 配置检查通过!可以开始配置微信公众平台');
console.log('\n📋 配置信息(用于微信公众平台):');
console.log(`URL: https://iit.xunzhengyixue.com/api/v1/iit/patient-wechat/callback`);
console.log(`Token: ${token}`);
console.log(`EncodingAESKey: ${aesKey}`);
console.log(`消息加解密方式: 安全模式(推荐)`);
}
```
**运行测试**
```bash
cd backend
npx tsx src/modules/iit-manager/test-patient-wechat-config.ts
```
### 4.3 测试脚本2模拟微信URL验证请求
创建测试脚本 `backend/src/modules/iit-manager/test-patient-wechat-url-verify.ts`
```typescript
import axios from 'axios';
import crypto from 'crypto';
import dotenv from 'dotenv';
dotenv.config();
const BASE_URL = 'http://localhost:3001';
const TOKEN = process.env.WECHAT_MP_TOKEN || '';
async function testUrlVerification() {
console.log('🧪 测试微信服务号URL验证\n');
// 1. 准备参数
const timestamp = Date.now().toString();
const nonce = Math.random().toString(36).substring(2, 12);
const echostr = 'test_echo_' + Math.random().toString(36).substring(2);
// 2. 生成签名
const arr = [TOKEN, timestamp, nonce].sort();
const str = arr.join('');
const signature = crypto.createHash('sha1').update(str).digest('hex');
console.log('📝 请求参数:');
console.log(` timestamp: ${timestamp}`);
console.log(` nonce: ${nonce}`);
console.log(` echostr: ${echostr}`);
console.log(` signature: ${signature}\n`);
// 3. 发送GET请求
try {
const url = `${BASE_URL}/api/v1/iit/patient-wechat/callback`;
const response = await axios.get(url, {
params: {
signature,
timestamp,
nonce,
echostr,
},
});
console.log('✅ URL验证成功');
console.log(`返回内容: ${response.data}`);
if (response.data === echostr) {
console.log('✅ 返回的echostr正确');
} else {
console.error('❌ 返回的echostr不正确');
}
} catch (error: any) {
console.error('❌ URL验证失败', error.message);
if (error.response) {
console.error('响应状态:', error.response.status);
console.error('响应内容:', error.response.data);
}
}
}
testUrlVerification();
```
**运行测试**
```bash
# 先启动后端服务
npm run dev
# 新开一个终端,运行测试
npx tsx src/modules/iit-manager/test-patient-wechat-url-verify.ts
```
### 4.4 测试关注事件
1. 用测试微信号关注公众号:`AI for 临床研究`
2. 查看后端日志,应该看到:
```
📥 收到微信服务号回调消息
🔐 检测到加密消息,开始解密...
✅ 消息解密成功
📬 提取用户消息成功
🎯 处理事件消息: subscribe
👤 用户关注公众号: oXXXXXXXXXXXXXXXXX
```
### 4.5 测试文本消息
1. 在公众号对话框发送文本消息:`你好`
2. 查看后端日志,应该看到:
```
📥 收到微信服务号回调消息
🔐 检测到加密消息,开始解密...
✅ 消息解密成功
💬 处理文本消息: 你好
📝 文本消息已记录
```
---
## 🚀 五、部署上线
### 5.1 本地开发环境natapp内网穿透
**1. 启动natapp**
```bash
# Windows
cd D:\tools\natapp
natapp.exe -authtoken=YOUR_TOKEN -subdomain=devlocal
```
**2. 验证映射**
访问:`https://devlocal.xunzhengyixue.com/api/v1/iit/health`
应该返回:
```json
{
"status": "ok",
"module": "iit-manager",
"version": "1.1.0"
}
```
**3. 配置微信公众平台**
```
URL: https://devlocal.xunzhengyixue.com/api/v1/iit/patient-wechat/callback
```
### 5.2 生产环境SAE
**1. 更新SAE环境变量**
登录阿里云SAE控制台 → 应用管理 → 环境变量配置
添加以下环境变量:
```
WECHAT_MP_APP_ID=wx062568ff49e4570c
WECHAT_MP_APP_SECRET=c0d19435d1a1e948939c16d767ec0faf
WECHAT_MP_TOKEN=IitPatientWechat2026Jan04Abc
WECHAT_MP_ENCODING_AES_KEY=abcdefghijklmnopqrstuvwxyz0123456789ABC
```
**2. 部署代码**
```bash
cd backend
./deploy-to-sae.ps1
```
**3. 验证部署**
访问:`https://iit.xunzhengyixue.com/api/v1/iit/health`
**4. 配置微信公众平台**
```
URL: https://iit.xunzhengyixue.com/api/v1/iit/patient-wechat/callback
```
**5. 配置IP白名单**(重要):
登录微信公众平台 → 基本配置 → IP白名单
添加SAE应用的出口IP可以从SAE控制台查看
---
## 📋 六、常见问题排查
### Q1: URL验证失败提示"Token验证失败"
**原因**
- Token配置不一致大小写、多余空格
- 签名计算错误
**解决方法**
1. 检查 `.env` 文件中的 `WECHAT_MP_TOKEN` 是否与微信公众平台配置一致
2. 运行配置检查脚本:`npx tsx src/modules/iit-manager/test-patient-wechat-config.ts`
3. 查看后端日志,确认签名计算过程
### Q2: URL验证失败提示"请求URL超时"
**原因**
- 服务器未启动
- 防火墙阻止
- URL配置错误
**解决方法**
1. 确认后端服务已启动:`npm run dev`
2. 本地开发确认natapp已启动
3. 生产环境确认SAE应用状态正常
4. 使用浏览器直接访问健康检查接口测试连通性
### Q3: 消息解密失败
**原因**
- EncodingAESKey配置不一致
- EncodingAESKey长度不正确必须43位
**解决方法**
1. 检查 `.env` 文件中的 `WECHAT_MP_ENCODING_AES_KEY` 长度是否为43位
2. 确认与微信公众平台配置完全一致(包括大小写)
3. 重新生成EncodingAESKey并同步更新
### Q4: 收不到用户消息
**原因**
- 服务器配置未启用
- 回调URL配置错误
- 服务端代码异常
**解决方法**
1. 登录微信公众平台,确认"服务器配置"状态为"已启用"
2. 查看后端日志确认是否收到POST请求
3. 检查是否有异常日志
---
## 📝 七、后续开发计划
### Phase 1: 基础消息推送(当前)
- [x] 创建PatientWechatCallbackController
- [x] 创建PatientWechatService
- [x] 配置路由和环境变量
- [ ] 测试URL验证
- [ ] 测试消息接收
### Phase 2: 患者绑定功能
- [ ] 创建患者绑定数据表
- [ ] 开发患者绑定H5页面
- [ ] 实现手机号验证码功能
- [ ] 实现患者身份验证逻辑
### Phase 3: 模板消息推送
- [ ] 申请模板消息权限
- [ ] 设计访视提醒模板
- [ ] 开发定时任务检测到期访视
- [ ] 实现模板消息推送
### Phase 4: 微信小程序
- [ ] 注册微信小程序
- [ ] 搭建小程序框架
- [ ] 开发核心页面
- [ ] 前后端联调
---
## 📞 联系方式
如有问题,请联系:
- 技术负责人:冯志博
- 邮箱gofeng117@163.com
- 微信aiforresearch
---
**最后更新**2026-01-04
**文档版本**v1.0