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,144 @@
/**
* 微信服务号配置检查脚本
*
* 功能:
* 1. 检查必需的环境变量是否配置
* 2. 验证Token和EncodingAESKey长度
* 3. 测试签名生成功能
* 4. 输出配置信息(用于微信公众平台)
*/
import dotenv from 'dotenv';
import crypto from 'crypto';
import path from 'path';
import { fileURLToPath } from 'url';
// 获取当前文件目录
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 加载.env文件
dotenv.config({ path: path.resolve(__dirname, '../../../.env') });
console.log('🔧 微信服务号配置检查');
console.log('=' .repeat(60));
console.log('');
// ==================== 1. 检查必需的环境变量 ====================
console.log('📋 检查必需的环境变量...\n');
const requiredEnvs = [
{ key: 'WECHAT_MP_APP_ID', description: 'AppID' },
{ key: 'WECHAT_MP_APP_SECRET', description: 'AppSecret' },
{ key: 'WECHAT_MP_TOKEN', description: 'Token用于签名验证' },
{ key: 'WECHAT_MP_ENCODING_AES_KEY', description: 'EncodingAESKey用于消息加解密' },
];
let hasError = false;
requiredEnvs.forEach(({ key, description }) => {
const value = process.env[key];
if (!value) {
console.error(`❌ 缺少环境变量: ${key} (${description})`);
hasError = true;
} else {
const displayValue =
value.length > 20 ? `${value.substring(0, 10)}...${value.substring(value.length - 5)}` : value;
console.log(`${key}:`);
console.log(` 值: ${displayValue}`);
console.log(` 长度: ${value.length}`);
console.log(` 说明: ${description}\n`);
}
});
if (hasError) {
console.error('\n❌ 配置不完整,请检查 backend/.env 文件');
console.error('\n请参考 backend/WECHAT_ENV_CONFIG.md 进行配置');
process.exit(1);
}
// ==================== 2. 验证Token长度 ====================
console.log('\n📏 验证Token长度...\n');
const token = process.env.WECHAT_MP_TOKEN!;
if (token.length < 3 || token.length > 32) {
console.error(`❌ Token长度不正确: ${token.length}应为3-32位`);
console.error(` 当前Token: ${token}`);
hasError = true;
} else {
console.log(`✅ Token长度正确: ${token.length}`);
}
// ==================== 3. 验证EncodingAESKey长度 ====================
console.log('\n🔐 验证EncodingAESKey长度...\n');
const aesKey = process.env.WECHAT_MP_ENCODING_AES_KEY!;
if (aesKey.length !== 43) {
console.error(`❌ EncodingAESKey长度不正确: ${aesKey.length}必须43位`);
console.error(` 当前AESKey: ${aesKey}`);
console.error(` 提示: 可以使用以下命令生成43位字符串`);
console.error(` openssl rand -base64 43 | tr -d '/+=' | head -c 43`);
hasError = true;
} else {
console.log(`✅ EncodingAESKey长度正确: 43位`);
}
// ==================== 4. 测试签名生成 ====================
console.log('\n🔐 测试签名生成...\n');
try {
const timestamp = Date.now().toString();
const nonce = Math.random().toString(36).substring(2, 12);
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(` token: ${token.substring(0, 10)}...`);
console.log(`\n排序后拼接: ${str.substring(0, 50)}...`);
console.log(`\n生成的签名: ${signature}`);
console.log(`\n✅ 签名生成功能正常`);
} catch (error: any) {
console.error(`❌ 签名生成失败: ${error.message}`);
hasError = true;
}
// ==================== 5. 总结 ====================
console.log('\n' + '='.repeat(60));
if (hasError) {
console.error('\n❌ 配置检查失败,请修复以上错误后重试\n');
process.exit(1);
} else {
console.log('\n✅ 配置检查通过!可以开始配置微信公众平台\n');
console.log('=' .repeat(60));
console.log('\n📋 配置信息(复制以下内容到微信公众平台):\n');
console.log('登录地址https://mp.weixin.qq.com/');
console.log('配置路径:设置与开发 → 基本配置 → 服务器配置\n');
console.log('配置参数:');
console.log(` URL: https://iit.xunzhengyixue.com/api/v1/iit/patient-wechat/callback`);
console.log(` Token: ${token}`);
console.log(` EncodingAESKey: ${aesKey}`);
console.log(` 消息加解密方式: 安全模式(推荐)`);
console.log(` 数据格式: XML`);
console.log('\n本地开发环境URL使用natapp');
console.log(` URL: https://devlocal.xunzhengyixue.com/api/v1/iit/patient-wechat/callback`);
console.log('\n' + '='.repeat(60));
console.log('\n📝 后续步骤:\n');
console.log(' 1. 启动后端服务npm run dev');
console.log(' 2. 本地开发需要启动natapp内网穿透');
console.log(' 3. 登录微信公众平台,配置服务器地址');
console.log(' 4. 点击"提交"进行URL验证');
console.log(' 5. 验证成功后点击"启用"');
console.log(' 6. 运行测试脚本验证功能:');
console.log(' npx tsx src/modules/iit-manager/test-patient-wechat-url-verify.ts');
console.log('\n');
}