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:
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* 微信服务号URL验证测试脚本
|
||||
*
|
||||
* 功能:
|
||||
* 1. 模拟微信服务器发送GET请求验证URL
|
||||
* 2. 测试签名生成和验证逻辑
|
||||
* 3. 验证服务端是否正确返回echostr
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import crypto from 'crypto';
|
||||
import dotenv from 'dotenv';
|
||||
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') });
|
||||
|
||||
const BASE_URL = process.env.API_BASE_URL || 'http://localhost:3001';
|
||||
const TOKEN = process.env.WECHAT_MP_TOKEN || '';
|
||||
|
||||
console.log('🧪 微信服务号URL验证测试');
|
||||
console.log('='.repeat(60));
|
||||
console.log('');
|
||||
|
||||
// ==================== 1. 检查配置 ====================
|
||||
|
||||
if (!TOKEN) {
|
||||
console.error('❌ 未配置 WECHAT_MP_TOKEN,请检查 .env 文件');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('📋 测试环境:');
|
||||
console.log(` BASE_URL: ${BASE_URL}`);
|
||||
console.log(` TOKEN: ${TOKEN.substring(0, 10)}... (长度: ${TOKEN.length})`);
|
||||
console.log('');
|
||||
|
||||
// ==================== 2. 生成测试参数 ====================
|
||||
|
||||
console.log('🔧 生成测试参数...\n');
|
||||
|
||||
const timestamp = Date.now().toString();
|
||||
const nonce = Math.random().toString(36).substring(2, 12);
|
||||
const echostr = 'test_echo_' + Math.random().toString(36).substring(2);
|
||||
|
||||
console.log(' timestamp: ' + timestamp);
|
||||
console.log(` nonce: ${nonce}`);
|
||||
console.log(` echostr: ${echostr}\n`);
|
||||
|
||||
// ==================== 3. 生成签名 ====================
|
||||
|
||||
console.log('🔐 生成签名...\n');
|
||||
|
||||
const arr = [TOKEN, timestamp, nonce].sort();
|
||||
const str = arr.join('');
|
||||
const signature = crypto.createHash('sha1').update(str).digest('hex');
|
||||
|
||||
console.log(` 排序后拼接: ${str.substring(0, 50)}...`);
|
||||
console.log(` SHA1哈希: ${signature}\n`);
|
||||
|
||||
// ==================== 4. 发送GET请求 ====================
|
||||
|
||||
async function testUrlVerification() {
|
||||
console.log('📤 发送URL验证请求...\n');
|
||||
|
||||
const url = `${BASE_URL}/api/v1/iit/patient-wechat/callback`;
|
||||
|
||||
console.log(` 请求URL: ${url}`);
|
||||
console.log(` 请求方法: GET`);
|
||||
console.log(` 查询参数:`);
|
||||
console.log(` signature: ${signature}`);
|
||||
console.log(` timestamp: ${timestamp}`);
|
||||
console.log(` nonce: ${nonce}`);
|
||||
console.log(` echostr: ${echostr}\n`);
|
||||
|
||||
try {
|
||||
const response = await axios.get(url, {
|
||||
params: {
|
||||
signature,
|
||||
timestamp,
|
||||
nonce,
|
||||
echostr,
|
||||
},
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
console.log('='.repeat(60));
|
||||
console.log('\n✅ URL验证成功!\n');
|
||||
console.log(`HTTP状态码: ${response.status}`);
|
||||
console.log(`返回内容类型: ${response.headers['content-type']}`);
|
||||
console.log(`返回内容: ${response.data}\n`);
|
||||
|
||||
// 验证返回内容
|
||||
if (response.data === echostr) {
|
||||
console.log('✅ 返回的echostr正确,验证通过!\n');
|
||||
console.log('='.repeat(60));
|
||||
console.log('\n🎉 测试成功!您的服务端配置正确\n');
|
||||
console.log('📝 后续步骤:');
|
||||
console.log(' 1. 登录微信公众平台:https://mp.weixin.qq.com/');
|
||||
console.log(' 2. 进入:设置与开发 → 基本配置 → 服务器配置');
|
||||
console.log(' 3. 填写以下信息:');
|
||||
console.log(` URL: ${BASE_URL}/api/v1/iit/patient-wechat/callback`);
|
||||
console.log(` Token: ${TOKEN}`);
|
||||
console.log(` EncodingAESKey: ${process.env.WECHAT_MP_ENCODING_AES_KEY?.substring(0, 10)}...`);
|
||||
console.log(' 4. 选择"安全模式"');
|
||||
console.log(' 5. 点击"提交"进行验证');
|
||||
console.log(' 6. 验证成功后点击"启用"');
|
||||
console.log('');
|
||||
} else {
|
||||
console.error('❌ 返回的echostr不正确!');
|
||||
console.error(` 期望: ${echostr}`);
|
||||
console.error(` 实际: ${response.data}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log('='.repeat(60));
|
||||
console.error('\n❌ URL验证失败\n');
|
||||
|
||||
if (error.code === 'ECONNREFUSED') {
|
||||
console.error('连接被拒绝,可能的原因:');
|
||||
console.error(' 1. 后端服务未启动');
|
||||
console.error(' 2. 端口号不正确');
|
||||
console.error('\n解决方法:');
|
||||
console.error(' 1. 运行 npm run dev 启动后端服务');
|
||||
console.error(' 2. 确认服务运行在正确的端口(默认3001)');
|
||||
} else if (error.response) {
|
||||
console.error(`HTTP状态码: ${error.response.status}`);
|
||||
console.error(`错误信息: ${error.response.statusText}`);
|
||||
console.error(`响应内容: ${JSON.stringify(error.response.data, null, 2)}`);
|
||||
|
||||
if (error.response.status === 403) {
|
||||
console.error('\n可能的原因:');
|
||||
console.error(' 1. Token配置不一致');
|
||||
console.error(' 2. 签名验证失败');
|
||||
console.error('\n解决方法:');
|
||||
console.error(' 1. 检查 .env 文件中的 WECHAT_MP_TOKEN');
|
||||
console.error(' 2. 运行配置检查脚本:');
|
||||
console.error(' npx tsx src/modules/iit-manager/test-patient-wechat-config.ts');
|
||||
} else if (error.response.status === 404) {
|
||||
console.error('\n可能的原因:');
|
||||
console.error(' 1. 路由未注册');
|
||||
console.error(' 2. URL路径不正确');
|
||||
console.error('\n解决方法:');
|
||||
console.error(' 1. 确认路由已在 routes/index.ts 中注册');
|
||||
console.error(' 2. 检查URL路径是否正确');
|
||||
}
|
||||
} else {
|
||||
console.error(`错误信息: ${error.message}`);
|
||||
}
|
||||
|
||||
console.error('\n');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 5. 执行测试 ====================
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await testUrlVerification();
|
||||
} catch (error: any) {
|
||||
console.error('测试执行异常:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user