# IIT Manager Agent - 最小MVP闭环开发计? > **版本**: v1.0 > **创建日期**: 2026-01-02 > **目标**: 打?REDCap ?Node.js ?企业微信 的完整闭? > **预估工作?*: 2天(Day 3-4? > **核心理念**: 先打通最小闭环,验证技术可行性,再逐步丰富功能 --- ## 🎯 一、MVP定义与目? ### 1.1 什么是最小闭环? **核心闭环**?``` REDCap录入数据 ?Node.js实时捕获 ?企业微信智能通知 ?PI对话查询 ``` **验证价?*?1. ?**实时感知**:PI无需登录REDCap,随时掌握项目进?2. ?**主动通知**:数据录入后立即推送,不会遗漏 3. ?**智能交互**:在企业微信中即可查询数据、获取报?4. ?**易扩?*:闭环打通后,可以快速添加质控、提醒、统计等功能 ### 1.2 暂不实现的功能(后续扩展? - ⏸️ **数据质控规则生成**(Phase 1.5):上传研究方案 ?AI生成规则??基于规则质控 - ⏸️ **PC Workbench前端**(Phase 2):复核AI建议的Web界面 - ⏸️ **REDCap数据回写**(Phase 2):AI建议审批后回写到REDCap - ⏸️ **微信小程?*(Phase 3):移动端原生体?- ⏸️ **复杂对话能力**(Phase 3):多轮对话、上下文理解 --- ## 📊 二、当前状态总结 ### 2.1 已完成的准备工作(Day 1-2? #### ?Day 1:环境初始化?026-01-01? **数据?*?- ?创建 `iit_schema`?个表?- ?Prisma Schema完整(IitProject, IitPendingAction, IitTaskRun, IitUserMapping, IitAuditLog?- ?CRUD测试通过?1/11? **企业微信**?- ?企业微信开发者账号注?- ?自建应用创建(IIT Manager Agent?- ?凭证获取? - **CorpID**: `ww01cb7b72ea2db83c` - **AgentID**: `1000002` - **Secret**: `F3XqlAqKdcOKHi9pLGv5a2dSUowWbevdcDRrBk2pXLM` - ?**网页授权及JS-SDK授权已获?* - ?**可信域名配置成功**:`iit.xunzhengyixue.com` - ?**域名验证文件部署**:`WW_verify_YnhsQBwI0ARnNoG0.txt` - ?**Access Token获取测试通过** **模块骨架**?- ?目录结构创建 - ?类型定义完整?23行) - ?路由前缀配置(`/api/v1/iit`? --- #### ?Day 2:REDCap实时集成?026-01-02? **核心交付?*(~2,200行代码)?1. ?**RedcapAdapter.ts**?71行) - `exportRecords()` - 导出记录(支持全?增量/指定记录? - `exportMetadata()` - 导出字段定义 - `importRecords()` - 导入记录(预留) - `testConnection()` - 连接测试 2. ?**WebhookController.ts**?27行) - `handleWebhook()` - 接收REDCap DET触发?10ms响应? - 幂等性检查(防止重复处理? - 审计日志记录 - 推送到质控队列(`iit_quality_check`? 3. ?**SyncManager.ts**?98行) - `initScheduledJob()` - 初始化定时任务(?分钟? - `handlePoll()` - 轮询所有active项目 - `manualSync()` - 手动同步 - `fullSync()` - 全量同步 4. ?**Worker注册**?1行) - `iit_quality_check` - 质控任务(已注册,待补充逻辑? - `iit_redcap_poll` - 定时轮询任务 5. ?**路由配置**?03行) - `/api/v1/iit/health` - 健康检? - `/api/v1/iit/webhooks/redcap` - DET回调接收(支持form-urlencoded? - `/api/v1/iit/webhooks/health` - Webhook健康检? - `/api/v1/iit/projects/:id/sync` - 手动同步 - `/api/v1/iit/projects/:id/full-sync` - 全量同步 **测试脚本**?12行)?- ?`test-redcap-api.ts`?89行)- API适配器测?- ?`test-redcap-webhook.ts`?74行)- Webhook接收器测?- ?`test-redcap-integration.ts`?49行)- 端到端集成测? **测试结果**?- ?**集成测试通过?*?2/12?00%?- ?**Webhook响应时间**?10ms(目?100ms?- ?**DET触发延迟**?秒(实时?- ?**数据同步准确?*?00% **REDCap环境**?- ?REDCap 15.8.0本地Docker部署 - ?测试项目创建(test0102, PID 16?- ?6条测试数据录?- ?DET配置成功:`http://host.docker.internal:3001/api/v1/iit/webhooks/redcap` - ?API Token生成:`FCB30F9CBD12EE9E8E9B3E3A0106701B` **关键技术突?*?1. ?REDCap DET实时触发?秒延迟) 2. ?form-urlencoded格式支持(REDCap原生格式?3. ?双保险机制(Webhook + 定时轮询?4. ?符合团队开发规范(队列命名、Worker注册? --- ### 2.2 当前架构状? ``` ┌─────────────────────────────────────────────────?? 已完成的基础设施 ?├─────────────────────────────────────────────────?? ?? ┌─────────────? ?? ? REDCap ? ?数据录入 ?? ? 15.8.0 ? ?DET配置 ? ?? └──────┬──────? ?? ?DET触发?秒延迟)? ?? ? ?? ┌─────────────────────? ?? ? Node.js Backend ? ?? ? (Fastify + pg-boss) ? ?? ├─────────────────────? ?? ??WebhookController ??接收DET (<10ms) ?? ??RedcapAdapter ??拉取数据 ?? ??SyncManager ??轮询补充 ?? ??Worker队列 ??异步处理 ?? ??WechatService ??待开?🔥 ?? └──────┬──────────────? ?? ?待打?🔥 ?? ? ?? ┌─────────────────────? ?? ? 企业微信 ? ?? ? (已配?? ? ?? ├─────────────────────? ?? ??应用创建 ? ?? ??凭证配置 ? ?? ??域名授权 ? ?? ??AccessToken获取 ? ?? ??消息推? ??待开?🔥 ?? ??消息接收 ??待开?🔥 ?? └─────────────────────? ?? ?└─────────────────────────────────────────────────?``` --- ## ⚠️ 三、关键技术风险与规避方案 在正式开发前?*必须了解**以下3个企业微信对接的致命陷阱? ### 风险A?秒超时魔?🔥 Critical **问题**:企业微信要求被动回复消息必须在**5秒内**完成。如果Node.js等AI计算完再return,企微早已提?服务暂时不可?并断开连接? **错误做法**?```typescript ?async handleCallback(request, reply) { const answer = await callLLM(message); // 可能耗时10? return reply.send(answer); // 超时了! } ``` **正确做法**:采?*异步回复模式** ```typescript ?async handleCallback(request, reply) { // 1. 立即返回?1秒) reply.send('success'); // 2. 异步处理(不阻塞? setImmediate(async () => { const answer = await callLLM(message); // 可以慢慢? await wechatService.sendTextMessage(userId, answer); // 主动推? }); } ``` **规避措施**:在Day 3开发中**必须**使用异步回复模式,禁止在HTTP Response中直接返回AI结果? --- ### 风险B:XML加解?🔥 P0 **问题**:企业微信的消息体是**加密的XML**(Encrypt字段),不是明文JSON。解密需要AES算法和复杂的Padding规则? **错误做法**:自己写加解密算法(极易出错? **正确做法**:使用官方库 ```bash npm install @wecom/crypto ``` ```typescript import { WXBizMsgCrypt } from '@wecom/crypto'; const crypt = new WXBizMsgCrypt( WECHAT_TOKEN, // 自定义Token WECHAT_ENCODING_AES_KEY, // 企微后台生成?3位) WECHAT_CORP_ID ); // 解密消息 const decrypted = crypt.decrypt(encryptedXml); ``` **规避措施**:强制使用官方库,禁止自己实现加解密? --- ### 风险C:IP白名单限?🔥 P0 **问题**:获取AccessToken时,企业微信会校验请求来源IP? **现状**:SAE通过NAT网关访问外网 - **NAT网关IP**:`182.92.176.14` - **企业微信配置**:未添加?企业可信IP"列表 **后果**:如不配置,getAccessToken()会报**60020错误** **规避措施**?1. 登录企业微信管理后台 2. 进入"应用管理" ?"IIT Manager Agent" 3. 找到"企业可信IP"配置 4. 添加:`182.92.176.14` --- ### 风险D:关键词匹配 vs AI Agent ⚠️ P2 **问题**:简单的关键词匹配(`if (msg.includes('汇?))`)不是AI Agent,只是if-else机器人? **正确做法**:使用LLM做意图分?```typescript // ?真正的AI Agent const intent = await classifyIntent(userMessage); // 调用LLM switch(intent.type) { case 'query_weekly_summary': ... case 'query_patient_info': ... } ``` **规避措施**:在Day 3下午实现基于LLM的意图识别? --- ## 🚀 四、Day 3-4 开发计? ### Day 3:企业微信集成(2026-01-03?小时)? #### 准备工作:企业微信配置检查(1小时)⚠?必须先做 **任务0:企业微信配置完整性检?* **配置清单**?1. [ ] **企业可信IP配置** 🔥 Critical - 登录企业微信管理后台 - 应用管理 ?IIT Manager Agent ?企业可信IP - 添加:`182.92.176.14`(SAE NAT网关IP? - 测试:调用getAccessToken,验证不?0020错误 2. [ ] **获取EncodingAESKey** 🔥 Critical - 企业微信后台 ?应用管理 ?IIT Manager Agent - 开发者接??接收消息 - 点击"随机生成"按钮,获?3位AESKey - 复制保存(用于消息解密) 3. [ ] **设置Token** 🔥 Critical - 自定义一个Token(用于签名验证) - 建议?2位随机字符串 - 保存到环境变? 4. [ ] **配置回调URL** - URL:`https://iit.xunzhengyixue.com/api/v1/iit/wechat/callback` - 验证Token、EncodingAESKey配置正确 **环境变量新增**?```env # 企业微信配置(已有) WECHAT_CORP_ID=ww6ab493470ab4f377 WECHAT_AGENT_ID=1000002 WECHAT_CORP_SECRET=AZIVxMtoLb0rEszXS81e4dBRl-I9kgTjygIS0cFfENU # 企业微信加解密配置(新增)?WECHAT_TOKEN=your_32_char_random_token_here WECHAT_ENCODING_AES_KEY=your_43_char_aes_key_from_wechat_console ``` **验收标准**?- ?IP白名单已配置 - ?EncodingAESKey已获取并保存 - ?Token已设置并保存 - ?AccessToken可以正常获取(无60020错误? --- #### 上午:企业微信推送服务(4小时? **目标**:完成从Node.js到企业微信的推送通道 **任务1:创?WechatService.ts?小时?* **文件位置**:`backend/src/modules/iit-manager/services/WechatService.ts` **核心功能**?```typescript class WechatService { /** * 获取Access Token(带缓存? */ async getAccessToken(): Promise /** * 发送数据录入通知 */ async sendDataEntryNotification(params: { userId: string; projectName: string; recordId: string; action: 'created' | 'updated'; fieldsSummary: string; }): Promise /** * 发送文本消息(通用? */ async sendTextMessage(userId: string, content: string): Promise /** * 发送卡片消息(通用? */ async sendTextCard(params: { userId: string; title: string; description: string; url?: string; btntxt?: string; }): Promise } ``` **实现要点**?- ?AccessToken缓存到Postgres?000秒过期时间) - ?重试机制?次重试,指数退避) - ?错误处理和日志记?- ?支持文本消息和卡片消? **验收标准**?- ?AccessToken可以正确获取和缓?- ?可以发送文本消息到指定用户 - ?可以发送卡片消?- ?缓存机制正常工作 --- **任务2:完?iit_quality_check Worker?小时?* **文件位置**:`backend/src/modules/iit-manager/index.ts` **当前状?*:Worker已注册,但逻辑为空(返回pending_implementation? **更新内容**?```typescript jobQueue.process('iit_quality_check', async (job) => { const { projectId, recordId, recordData, instrument } = job.data; try { // 1. 获取项目配置 const project = await prisma.projects.findUnique({ where: { id: projectId } }); // 2. 构造数据摘? const fieldsList = Object.keys(recordData); const summary = `录入${fieldsList.length}个字段`; // 3. 发送企业微信通知 const wechatService = new WechatService(); await wechatService.sendDataEntryNotification({ userId: 'GaoFeng', // TODO: 从项目配置中获取 projectName: project.name, recordId, action: instrument === 'demographics' ? 'created' : 'updated', fieldsSummary: summary }); // 4. 记录审计日志 await prisma.auditLogs.create({ data: { projectId, actionType: 'WECHAT_NOTIFICATION_SENT', entityId: recordId, details: { recordId, instrument, fieldCount: fieldsList.length, notified: true } } }); logger.info('Wechat notification sent', { recordId, instrument, fieldCount: fieldsList.length }); return { success: true, notified: true }; } catch (error: any) { logger.error('Wechat notification failed', { recordId, error: error.message }); throw error; } }); ``` **验收标准**?- ?Worker可以正常执行 - ?企业微信通知发送成?- ?审计日志正确记录 - ?错误处理完善 --- **任务3:端到端测试?小时?* **测试场景1:数据录入通知** ``` 操作:在REDCap中新增记录ID 8 预期?1. DET实时触发?秒延迟) 2. WebhookController接收?10ms响应?3. Worker执行?秒内?4. 企业微信收到通知?秒内?``` **测试场景2:数据更新通知** ``` 操作:在REDCap中修改记录ID 2 预期?1. DET实时触发 2. 企业微信收到更新通知 3. 通知内容包含修改的字段信?``` **验收标准**?- ?完整闭环(REDCap ?Node.js ?企微)打?- ?响应时间<5?- ?通知内容准确 - ?审计日志完整 --- #### 下午:企业微信对话接收(4小时? **目标**:实现PI在企业微信中与AI Agent对话 **任务4:安装并配置消息解密库(15分钟?* ```bash cd backend npm install @wecom/crypto npm install xml2js # XML解析 ``` **验收标准**?- ?依赖安装成功 - ?可以import成功 --- **任务5:创?WechatCallbackController.ts?.5小时)?关键** **文件位置**:`backend/src/modules/iit-manager/controllers/WechatCallbackController.ts` **核心功能**?*异步回复模式**): ```typescript import { WXBizMsgCrypt } from '@wecom/crypto'; import { parseString } from 'xml2js'; class WechatCallbackController { private crypt: WXBizMsgCrypt; constructor() { this.crypt = new WXBizMsgCrypt( process.env.WECHAT_TOKEN!, process.env.WECHAT_ENCODING_AES_KEY!, process.env.WECHAT_CORP_ID! ); } /** * 验证企业微信签名(GET请求? * 企业微信首次配置回调URL时会发送验证请? */ async verifyCallback(request: any, reply: any): Promise { const { msg_signature, timestamp, nonce, echostr } = request.query; try { // 验证签名并解密echostr const decrypted = this.crypt.verifyURL( msg_signature, timestamp, nonce, echostr ); logger.info('Wechat callback URL verified'); return reply.send(decrypted); } catch (error: any) { logger.error('Wechat callback verification failed', { error: error.message }); return reply.code(403).send('Verification failed'); } } /** * 接收企业微信用户消息(POST请求? * 🔥 关键:必须在5秒内返回,使用异步处? */ async handleCallback(request: any, reply: any): Promise { const { msg_signature, timestamp, nonce } = request.query; const encryptedXml = request.body; try { // 1. 解密消息? const decryptedXml = this.crypt.decrypt( msg_signature, timestamp, nonce, encryptedXml ); // 2. 解析XML const message = await this.parseXML(decryptedXml); const { FromUserName, Content, MsgId, MsgType } = message; logger.info('Wechat message received', { userId: FromUserName, msgType: MsgType, msgId: MsgId }); // 3. 🔥 立即返回'success'?1秒) reply.send('success'); // 4. 🔥 异步处理(不阻塞,可以慢慢算? if (MsgType === 'text') { setImmediate(async () => { try { // 可能耗时5-10? const answer = await this.processUserMessage(FromUserName, Content); // 使用主动推送接口返回结? const wechatService = new WechatService(); await wechatService.sendTextMessage(FromUserName, answer); // 记录审计日志 await this.logInteraction(MsgId, FromUserName, Content, answer); } catch (error: any) { logger.error('Async message processing failed', { msgId: MsgId, error: error.message }); // 发送错误提? await wechatService.sendTextMessage( FromUserName, '抱歉,处理您的消息时出错了,请稍后重试? ); } }); } return; // 已经返回?success'? } catch (error: any) { logger.error('Wechat callback failed', { error: error.message }); return reply.code(500).send('Internal error'); } } /** * 处理用户消息(?使用LLM做意图识别,而非关键词匹配) */ private async processUserMessage( userId: string, message: string ): Promise { // 1. 调用LLM做意图识? const intent = await this.classifyIntent(message); logger.info('Intent classified', { userId, intent }); // 2. 根据意图执行对应操作 try { switch(intent.type) { case 'query_weekly_summary': return await this.getWeeklySummary(intent.params); case 'query_patient_info': return await this.getPatientInfo(intent.params.recordId); case 'query_project_stats': return await this.getProjectStats(intent.params.projectId); case 'unknown': default: return this.getHelpMessage(); } } catch (error: any) { logger.error('Query execution failed', { intent, error: error.message }); throw error; } } /** * 🔥 使用LLM做意图分类(真正的AI Agent? */ private async classifyIntent(message: string): Promise<{ type: string; params: any; }> { const prompt = ` 你是一个意图分类器。用户说?${message}" 请判断用户的意图并提取参数,返回JSON格式? 意图类型?- query_weekly_summary: 查询最近一周数据汇?- query_patient_info: 查询患者信?- query_project_stats: 查询项目统计 - unknown: 无法识别 示例?用户?看看这周有没有新病人? 返回:{"type": "query_weekly_summary", "params": {"timeRange": "this_week"}} 用户?患?的情况怎么样?" 返回:{"type": "query_patient_info", "params": {"recordId": "8"}} 用户?项目进度如何? 返回:{"type": "query_project_stats", "params": {}} 请返回JSON? `.trim(); try { // 调用DeepSeek API(或其他LLM? const response = await this.callLLM(prompt); const intent = JSON.parse(response); return intent; } catch (error: any) { logger.error('Intent classification failed', { message, error: error.message }); // 降级:返回unknown return { type: 'unknown', params: {} }; } } /** * 调用LLM API */ private async callLLM(prompt: string): Promise { // TODO: 实现DeepSeek API调用 // 暂时返回mock数据用于测试 // 简单的关键词匹配作为fallback if (prompt.includes('一?) || prompt.includes('汇?)) { return JSON.stringify({ type: 'query_weekly_summary', params: { timeRange: 'this_week' } }); } const patientMatch = prompt.match(/患?\d+)/); if (patientMatch) { return JSON.stringify({ type: 'query_patient_info', params: { recordId: patientMatch[1] } }); } if (prompt.includes('进度') || prompt.includes('统计')) { return JSON.stringify({ type: 'query_project_stats', params: {} }); } return JSON.stringify({ type: 'unknown', params: {} }); } /** * 获取最近一周数据汇? */ private async getWeeklySummary(params: any): Promise /** * 获取患者信? */ private async getPatientInfo(recordId: string): Promise /** * 获取项目统计 */ private async getProjectStats(projectId: string): Promise /** * 帮助消息 */ private getHelpMessage(): string { return `您好!我是IIT Manager AI助手 🤖 您可以这样问我: 📊 "最近一周数据汇? 👤 "查询患?的情? 📈 "项目进度如何" 我会尽力帮助您!`; } /** * 解析XML消息 */ private async parseXML(xml: string): Promise { return new Promise((resolve, reject) => { parseString(xml, { explicitArray: false }, (err, result) => { if (err) reject(err); else resolve(result.xml); }); }); } /** * 记录交互日志 */ private async logInteraction( msgId: string, userId: string, userMessage: string, botResponse: string ): Promise { await prisma.auditLogs.create({ data: { actionType: 'WECHAT_USER_MESSAGE', entityId: userId, details: { msgId, userMessage, botResponse, timestamp: new Date() } } }); } } ``` **实现要点(关键修正)**?- ?使用 `@wecom/crypto` 解密消息(不自己实现?- ?立即返回'success'?1秒,避免5秒超时) - ?使用 `setImmediate` 异步处理 - ?使用主动推送接口返回结?- ?使用LLM做意图分类(而非关键词匹配) - ?完善的错误处理和日志记录 **验收标准**?- ?企业微信回调URL验证通过 - ?可以接收并解密用户消?- ?响应时间<1秒(异步处理?- ?LLM意图识别正确?80% - ?可以正确返回查询结果 --- **任务6:注册企业微信回调路由(30分钟?* **文件位置**:`backend/src/modules/iit-manager/routes/index.ts` **新增路由**?```typescript const wechatCallbackController = new WechatCallbackController(); // 企业微信回调验证(GET?fastify.get( '/api/v1/iit/wechat/callback', wechatCallbackController.verifyCallback.bind(wechatCallbackController) ); // 企业微信消息接收(POST?fastify.post( '/api/v1/iit/wechat/callback', { schema: { body: { type: 'object' } } }, wechatCallbackController.handleCallback.bind(wechatCallbackController) ); logger.info('Registered route: GET/POST /api/v1/iit/wechat/callback'); ``` **验收标准**?- ?路由注册成功 - ?GET请求可以验证签名 - ?POST请求可以接收消息 --- **任务7:对话功能测试(1小时?* **测试场景1:最近一周数据汇?* ``` 操作:在企业微信中发送:"最近一周数据汇? 预期?AI Agent回复?"📊 最近一周数据汇总: 新增受试者:2?数据更新??总计?条操作记? ``` **测试场景2:患者信息查?* ``` 操作:在企业微信中发送:"查询患?的情? 预期?AI Agent回复?"📋 患?4 基本信息?姓名:test 4 test 4 年龄?8?性别:男 BMI?8.3 录入状态:已完? ``` **测试场景3:项目进度查?* ``` 操作:在企业微信中发送:"项目进度" 预期?AI Agent回复?"📊 test0102 项目统计?总受试者数??本周新增??数据完整率:87.5%" ``` **验收标准**?- ?3个测试场景全部通过 - ?回复内容准确 - ?响应时间<3?- ?错误处理友好 --- ### 📊 Day 3 完成总结?026-01-03)✅ **实际完成时间**?026-01-03 **任务完成?*?00% #### 核心成果 | 交付?| 代码?| 状?| |-------|--------|------| | WechatService(企业微信推送) | 314?| ?完成 | | WechatCallbackController(回调处理) | 501?| ?完成 | | 质控Worker完善 | 336?| ?完成 | | Worker注册修复(`initIitManager`?| - | ?完成 | | 数据库字段修复(`action_type`?| - | ?完成 | | 端到端测?| - | ?通过 | | WECHAT_ENV_CONFIG.md | 401?| ?完成 | | **总计** | **1,755?* | **?完成** | #### 关键里程? 🎯 **MVP闭环完全打?*?``` REDCap录入数据 ?Node.js实时捕获?10ms? ?Worker处理(~50ms? ?企业微信推送通知?2秒) ?手机端接收✅ ``` #### 性能指标 | 指标 | 目标 | 实际 | 状?| |------|------|------|------| | Webhook响应时间 | <10ms | 5.8ms | ?超出预期 | | Worker执行时间 | <100ms | ~50ms | ?超出预期 | | 端到端延?| <5?| <2?| ?超出预期 | | 消息发送成功率 | >99% | 100% | ?超出预期 | #### 测试验证 **端到端测?*(已通过): - ?REDCap创建记录 ID 9 - ?DET实时触发?秒延迟) - ?Webhook接收?.8ms响应?- ?任务推送到pg-boss队列 - ?Worker执行质控检?- ?发送企业微信通知 - ?手机端成功接收通知 - ?审计日志记录成功 - ?无循环发送问? **企业微信推送测?*(已通过): - ?文本消息推送成?- ?Textcard卡片消息推送成?- ?Markdown消息推送成?- ?手机端全部接收正? #### 技术亮? 1. **异步Worker架构**:符合Postgres-Only最佳范?2. **企业微信消息加解?*:完整实现签名验证和加解?3. **异步回复模式**:`setImmediate` 确保5秒内响应 4. **完整的错误处?*:审计日志失败不影响主流?5. **pg-boss重试机制**:自动重?次,确保可靠? #### 临时措施与技术债务 | 序号 | 临时措施 | 改进计划 | |------|---------|---------| | 1 | UserID硬编码(环境变量?| Phase 2: 从项目配置表读取 | | 2 | 定时轮询禁用 | Phase 2: 使用node-cron或扩展PgBossQueue | | 3 | 质控逻辑简化(无AI?| Phase 1.5: 集成Dify RAG | | 4 | `notification_config`字段未创?| Phase 2: 添加JSONB字段 | | 5 | Access Token内存缓存 | Phase 2: 使用Redis或数据库 | #### 问题与解? 1. **Worker未注?*:`initIitManager()` 未调???`src/index.ts` 中添加调?2. **字段名错?*:`action` ?`action_type`?处修复) 3. **循环发?*:审计日志错误导致Worker失败重试 ?添加try-catch 4. **`notification_config`不存?*:移除字段查询,直接使用环境变量 #### 参考文? - `06-开发记?Day3-企业微信集成与端到端测试完成记录.md` - `backend/WECHAT_ENV_CONFIG.md` --- ### 📊 Phase 1.5:AI对话能力?026-01-03 & 2026-01-04)✅ **任务目标**:在企业微信中实现AI对话查询能力 **实际完成时间**?- Day 3 下午?026-01-03):基础对话 + REDCap集成 - Day 4 上午?026-01-04):Dify知识库集? **任务完成?*?00% #### 核心成果(Phase 1.5? | 交付?| 代码?| 状?| |-------|--------|------| | ChatService.ts(对话服务) | 485?| ?完成 | | SessionMemory.ts(会话记忆) | 120?| ?完成 | | WechatCallbackController(对话集成) | 更新 | ?完成 | | Dify知识库关?| 脚本 | ?完成 | | 意图识别优化 | 扩展 | ?完成 | | Bug修复(字段路径) | 关键修复 | ?完成 | | 调试脚本?个) | 280?| ?完成 | | 开发记录文?| 600+?| ?完成 | | **总计** | **~1,485?* | **?完成** | #### 关键里程? 🎯 **混合检索架构实?*?``` 用户提问(企业微信) ?意图识别(ChatService.detectIntent? ?┌───────────────┬───────────────┬──────────────??query_protocol?query_record ?count_records?? (文档查询) ? (记录查询) ? (统计查询) ?└───────┬───────┴───────┬───────┴──────┬───────? ? ? ? Dify API REDCap API REDCap API (知识? (患者数? (患者数? ? ? ? 文档片段 JSON数据 JSON数据 ? ? ? └───────────────┴──────────────? ? 构建LLM Prompt (System + Context + Data) ? DeepSeek-V3 ? AI回答 ``` #### 功能验证 **测试项目**: test0102 - REDCap PID: 16, 11条记?- Dify Dataset ID: `b49595b2-bf71-4e47-9988-4aa2816d3c6f` - 文档: 研究方案、CRF表格?个文件,已处理) **测试场景**? | 场景 | 用户问题 | 数据?| 结果 | 状?| |------|---------|--------|------|------| | 文档查询 | "这个研究的排除标准是什么?" | Dify | 基于文档准确回答 | ?通过 | | CRF查询 | "CRF表格中有哪些观察指标? | Dify | 基于文档准确回答 | ?通过 | | 患者查?| "ID 7的患者情? | REDCap | 完全匹配真实数据 | ?通过 | | 统计查询 | "目前入组了多少人? | REDCap | 准确统计11?| ?通过 | | 混合查询 | "这个研究的主要研究目的是什么?" | Dify | 基于文档回答 | ?通过 | | 上下文记?| 多轮对话 | SessionMemory | 记得上一轮内?| ?通过 | #### 技术亮? 1. **混合检?*:同时支持结构化数据(REDCap)和非结构化文档(Dify?2. **智能路由**:根据意图自动选择数据?3. **防止幻觉**:所有回答基于真实数?文档,绝不编?4. **上下文记?*:SessionMemory保存最?轮对?5. **复用LLMFactory**:零配置使用DeepSeek-V3 6. **即时反馈**?正在查询"消息,避免用户焦? #### 问题排查与修? **关键Bug修复**? | 问题 | 根因 | 解决方案 | 状?| |------|------|---------|------| | AI编造答?| 意图识别关键词不全(?入?等) | 扩充关键词列?| ?已修?| | Dify内容为undefined | 错误的API响应字段路径 | 修正为`record.segment.document.name`和`record.segment.content` | ?已修?| **调试工具**?- `debug-dify-injection.ts`:追踪Dify结果注入流程 - `inspect-dify-response.ts`:查看Dify API实际返回结构 #### 核心能力 1. ?**实时数据查询**:通过REDCap API查询患者CRF数据 2. ?**研究方案查询**:通过Dify知识库检索研究方案、CRF、伦理文?3. ?**智能理解**:自然语言提问,无需记忆命令 4. ?**上下文理?*:多轮对话,支持代词解析 5. ?**数据真实?*:所有回答基于真实数据,绝不编?6. ?**混合检?*:同时查询多个数据源,统一呈现 #### 参考文? - [Phase 1.5开发计划](./Phase1.5-AI对话能力开发计?md) - [Phase 1.5开发完成记?(REDCap)](../06-开发记?Phase1.5-AI对话集成REDCap完成记录.md) - [Dify知识库集成开发记录](../06-开发记?2026-01-04-Dify知识库集成开发记?md) - [IIT Manager Agent 技术路径与架构设计](../02-技术设?IIT%20Manager%20Agent%20技术路径与架构设计.md) --- ### Day 4:完善与文档?026-01-04?小时)⏸? #### 上午:优化与测试?小时? **任务8:完善审计日志(1小时?* **新增日志类型**?```typescript // 企业微信交互日志 await prisma.auditLogs.create({ data: { actionType: 'WECHAT_USER_MESSAGE', entityId: userId, details: { userMessage: message, botResponse: response, intent: 'query_patient_info', timestamp: new Date() } } }); ``` **验收标准**?- ?所有企业微信交互都有日?- ?日志包含完整的上下文信息 - ?可以追溯每次对话 --- **任务9:错误处理优化(1小时?* **优化内容**?1. **企业微信API调用失败**? - 自动重试3? - 记录错误日志 - 降级处理(通知失败不影响主流程? 2. **REDCap API超时**? - 30秒超? - 友好错误提示 - 返回缓存数据(如果有? 3. **用户消息解析失败**? - 返回友好提示 - 记录错误日志 - 不影响其他功? **验收标准**?- ?所有错误场景有友好提示 - ?系统能自动重?- ?错误不影响其他任务执? --- **任务10:完整闭环压力测试(1小时?* **测试内容**?1. **连续录入测试**? - 在REDCap中连续录?0条数? - 验证所有通知都能正确发? - 验证没有遗漏和重? 2. **并发对话测试**? - 同时发?个查询请? - 验证所有请求都能正确响? - 验证响应时间<5? 3. **长时间运行测?*? - 系统运行1小时 - 期间随机录入数据和查? - 验证系统稳定? **验收标准**?- ?连续录入测试通过?0/10?- ?并发对话测试通过?/5?- ?长时间运行无异常 - ?内存和性能正常 --- #### 下午:文档编写(3小时? **任务11:创建开发完成文档(2小时?* **文档位置**:`docs/03-业务模块/IIT Manager Agent/06-开发记?Day3-4-最小MVP闭环完成记录.md` **文档内容**?1. 开发概?2. 完成的功能模块(详细说明?3. 测试验证结果 4. 性能指标 5. 遇到的问题与解决方案 6. 闭环效果演示 7. 下一步计? --- **任务12:更新模块状态文档(30分钟?* **文档位置**:`docs/03-业务模块/IIT Manager Agent/00-模块当前状态与开发指?md` **更新内容**?- 更新整体完成度(35% ?50%?- 更新已完成功能列?- 更新Day 3-4完成情况 - 更新下一步工作计? --- **任务13:创建使用手册(30分钟?* **文档位置**:`docs/03-业务模块/IIT Manager Agent/05-使用手册/企业微信对话指南.md` **文档内容**?1. 企业微信应用配置 2. 用户如何加入应用 3. 支持的对话指令列?4. 常见问题FAQ 5. 故障排查指南 --- ## ?四、Day 4结束时的闭环效果 ### 4.1 完整闭环验证 ``` 🎯 最小MVP闭环完全打通: ┌─────────────────────────────────────────??Step 1: REDCap数据录入 ??────────────────────────────────────────??用户在REDCap中: ??- 新增患者ID 8 ??- 或修改患者ID 2 ?└──────────┬──────────────────────────────? ?0秒延? ?┌─────────────────────────────────────────??Step 2: Node.js实时捕获 ??────────────────────────────────────────???DET触发 (0? ???Webhook接收 (<10ms) ???Worker处理 (~2? ?└──────────┬──────────────────────────────? ? ?┌─────────────────────────────────────────??Step 3: 企业微信推送通知 ??────────────────────────────────────────???PI收到卡片消息 (<5? ???通知内容? ?? "📊 test0102 - 数据录入 ?? 受试者:8 ?? 操作:新? ?? 录入8个字? ?└──────────┬──────────────────────────────? ? ?┌─────────────────────────────────────────??Step 4: PI在企业微信中对话 ??────────────────────────────────────────???发送:"最近一周数据汇? ???AI回复? ?? "📊 最近一周数据汇总: ?? 新增受试者:2? ?? 数据更新?? ?? ???发送:"查询患?的情? ???AI回复:患者基本信? ?? ???发送:"项目进度" ???AI回复:项目统计数? ?└─────────────────────────────────────────?``` ### 4.2 验收标准(最终) **功能完整?*?- ?REDCap数据实时监听(DET + 轮询?- ?Node.js数据捕获与处?- ?企业微信通知推?- ?企业微信对话查询 - ?审计日志完整记录 **性能指标**?| 指标 | 目标?| 实际?| 状?| |------|--------|--------|------| | DET触发延迟 | <1?| 0?| ?| | Webhook响应时间 | <100ms | <10ms | ?| | 通知推送延?| <10?| <5?| ?| | 对话响应时间 | <5?| ~3?| ?| | 数据同步准确?| 100% | 100% | ?| **技术指?*?- ?代码符合团队规范 - ?错误处理完善 - ?日志记录完整 - ?测试覆盖?90% **可演示?*?- ?可以现场演示完整闭环 - ?可以演示对话查询功能 - ?效果明显、价值清? --- ## 📚 五、技术实现细? ### 5.1 企业微信异步回复模式(?Critical? #### 问题描述 企业微信要求被动回复消息必须?*5秒内**完成。如果超时,用户会看?服务暂时不可?的提示? #### 错误做法 ```typescript ?async handleCallback(request, reply) { const message = parseMessage(request.body); // 调用LLM(可能耗时10秒) const answer = await classifyIntent(message); const result = await queryData(answer); // 超时了!企微已断开连接 return reply.send(result); } ``` #### 正确做法:异步回复模?```typescript ?async handleCallback(request, reply) { const message = parseMessage(request.body); // 1. 立即返回'success'?1秒) reply.send('success'); // 2. 异步处理(不阻塞? setImmediate(async () => { // 可以慢慢算(10秒?0秒都可以? const answer = await classifyIntent(message); const result = await queryData(answer); // 3. 使用主动推送接口返回结? await wechatService.sendTextMessage(userId, result); }); } ``` #### 关键?1. ?**立即返回**:收到消息后<1秒返?success' 2. ?**异步处理**:使用`setImmediate`或消息队?3. ?**主动推?*:调用发送消息接口,而非HTTP Response 4. ?**错误处理**:异步任务失败时,推送友好错误提? --- ### 5.2 企业微信XML加解密(🔥 Critical? #### 问题描述 企业微信的消息体?*加密的XML**,不是明文。格式如下: ```xml ``` #### 解密流程 ``` 加密XML ?Base64解码 ?AES解密 ?去除Padding ?验证CorpID ?明文XML ``` #### 错误做法 ```typescript ?// 自己实现AES解密(极易出错) function decrypt(encryptedData) { // 手写AES、Padding、Base64... // 99%的人都会写错 } ``` #### 正确做法:使用官方库 ```bash npm install @wecom/crypto ``` ```typescript ?import { WXBizMsgCrypt } from '@wecom/crypto'; const crypt = new WXBizMsgCrypt( process.env.WECHAT_TOKEN!, // 自定义Token process.env.WECHAT_ENCODING_AES_KEY!, // 企微后台生成?3位) process.env.WECHAT_CORP_ID! ); // 一行代码完成解?const decryptedXml = crypt.decrypt( msg_signature, timestamp, nonce, encryptedXml ); ``` #### 需要的配置 | 配置?| 来源 | 示例 | |--------|------|------| | `WECHAT_TOKEN` | 自定?| 32位随机字符串 | | `WECHAT_ENCODING_AES_KEY` | 企微后台生成 | 43位字符(如:abcdefghijklmnopqrstuvwxyz0123456789ABC?| | `WECHAT_CORP_ID` | 企微后台 | ww01cb7b72ea2db83c | --- ### 5.3 企业微信IP白名单(🔥 P0? #### 问题描述 获取AccessToken时,企业微信会校验请求来源IP。如果IP不在白名单中,会返回**60020错误**? #### 配置步骤 1. 确认SAE NAT网关IP:`182.92.176.14` 2. 登录企业微信管理后台 3. 应用管理 ?IIT Manager Agent 4. 找到"企业可信IP"配置 5. 添加:`182.92.176.14` 6. 保存并测? #### 验证方法 ```typescript // 测试AccessToken获取 const response = await fetch( `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${CORP_ID}&corpsecret=${SECRET}` ); const data = await response.json(); if (data.errcode === 60020) { console.error('?IP白名单未配置?); } else if (data.errcode === 0) { console.log('?IP白名单配置正?); } ``` --- ### 5.4 企业微信消息格式 #### 文本消息格式 ```json { "touser": "GaoFeng", "msgtype": "text", "agentid": 1000002, "text": { "content": "这是一条文本消? } } ``` #### 卡片消息格式 ```json { "touser": "GaoFeng", "msgtype": "textcard", "agentid": 1000002, "textcard": { "title": "📊 test0102 - 数据录入", "description": "
受试者:8
操作:新?/div>
录入8个字?/div>", "url": "https://iit.xunzhengyixue.com/chat", "btntxt": "查看详情" } } ``` ### 5.5 企业微信回调消息格式 #### 用户文本消息(XML格式?```xml 1641024000 1234567890 1000002 ``` #### 回复消息格式(XML格式?```xml 1641024001 ``` ### 5.6 企业微信签名验证 ```typescript function verifySignature(signature: string, timestamp: string, nonce: string, echostr: string): boolean { const token = WECHAT_TOKEN; // 企业微信配置的Token const tmpArr = [token, timestamp, nonce, echostr].sort(); const tmpStr = tmpArr.join(''); const sha1 = crypto.createHash('sha1'); sha1.update(tmpStr); const calculatedSignature = sha1.digest('hex'); return calculatedSignature === signature; } ``` --- ## 🎯 六、下一步扩展方向(Phase 1.5+? ### Phase 1.5:数据质控规则生成(1-2天) **目标**:实现基于规则的数据质控 **核心功能**?1. 上传研究方案PDF 2. AI自动提取质控规则 3. 规则库管理(可编辑、可审核?4. 基于规则进行数据质控 5. 质控结果推送到企业微信 **技术方?*?- 使用大模型提取规则(非实时调用) - 规则保存到数据库(`quality_rules`表) - 录入时基于规则判断(快速、可靠) - 证据链追溯(规则来源于方案第X页第Y条) --- ### Phase 2:PC Workbench前端?-3天) **目标**:提供Web端复核界? **核心功能**?1. 质控建议列表 2. 当前数据 vs AI建议对比 3. 证据链展示(PDF高亮?4. 审批操作(确?拒绝?5. REDCap数据回写 --- ### Phase 3:智能化演进(长期) **功能扩展**?1. 多轮对话能力 2. 上下文理?3. 任务驱动引擎 4. 患者随访Agent 5. 智能汇报Agent --- ## 📋 七、检查清? ### Day 3开始前 - [ ] 企业微信UserID确认(用于测试) - [ ] 企业微信回调URL配置(在企业微信管理后台?- [ ] REDCap测试数据准备(至?条) - [ ] 后端服务运行正常 ### Day 3结束?- [ ] WechatService开发完?- [ ] iit_quality_check Worker完善 - [ ] 企业微信推送测试通过 - [ ] 端到端闭环打通(REDCap ?企微? ### Day 4结束?- [ ] WechatCallbackController开发完?- [ ] 企业微信对话功能测试通过 - [ ] 完整闭环压力测试通过 - [ ] 文档全部更新完成 - [ ] 代码提交到Git --- ## 🎊 八、成功标? **MVP成功的标?*?1. ?PI可以在企业微信中实时收到数据录入通知 2. ?PI可以在企业微信中查询项目数据 3. ?完整闭环(REDCap ?Node.js ?企微)稳定运?4. ?响应时间满足要求?5秒) 5. ?可以现场演示给团队看 **价值验?*?- ?PI不用每天登录REDCap查看进度 - ?数据录入后立即知晓,不会遗漏 - ?随时随地可以查询数据(手机端?- ?为后续功能扩展打下基础 --- **文档维护?*:开发团? **最后更?*?026-01-02 **状?*:?开发计划(待执行)