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:
144
backend/src/modules/iit-manager/test-patient-wechat-config.ts
Normal file
144
backend/src/modules/iit-manager/test-patient-wechat-config.ts
Normal 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');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user