From e52020409c25c7e345610ff899c72215e7183f9b Mon Sep 17 00:00:00 2001 From: HaHafeng Date: Sun, 16 Nov 2025 15:43:55 +0800 Subject: [PATCH] docs: complete documentation system (250+ files) - System architecture and design documentation - Business module docs (ASL/AIA/PKB/RVW/DC/SSA/ST) - ASL module complete design (quality assurance, tech selection) - Platform layer and common capabilities docs - Development standards and API specifications - Deployment and operations guides - Project management and milestone tracking - Architecture implementation reports - Documentation templates and guides --- docs/00-系统总体设计/00-今日架构设计总结.md | 523 ++++++ docs/00-系统总体设计/00-核心问题解答.md | 698 +++++++ docs/00-系统总体设计/00-阅读指南.md | 174 ++ docs/00-系统总体设计/01-系统架构分层设计.md | 939 ++++++++++ docs/00-系统总体设计/02-文档体系重构方案.md | 1480 +++++++++++++++ docs/00-系统总体设计/03-数据库架构说明.md | 447 +++++ docs/00-系统总体设计/04-运营管理端架构设计.md | 872 +++++++++ .../05-Schema隔离方案与成本分析.md | 1055 +++++++++++ .../06-模块独立部署与单机版方案.md | 1554 ++++++++++++++++ docs/00-系统总体设计/07-Monorepo架构评估.md | 568 ++++++ docs/00-系统总体设计/08-架构设计全景图.md | 684 +++++++ docs/00-系统总体设计/09-总体需求文档(PRD).md | 100 + .../10-核心业务规则总览.md} | 12 + docs/00-系统总体设计/99-下一步行动决策建议.md | 630 +++++++ docs/00-系统总体设计/README.md | 278 +++ docs/00-系统总体设计/[AI对接] 快速上下文.md | 167 ++ .../[重要] 2025-11-06 架构设计完成报告.md | 554 ++++++ .../前后端模块化架构设计-V2.md | 1342 ++++++++++++++ .../壹证循科技 AI科研产品需求文档.md | 86 + .../壹证循科技AI科研产品 - 技术架构白皮书.md | 234 +++ docs/00-项目概述/文档梳理与差异分析.md | 498 +++++ .../00-项目概述/最新需求与技术方案深度评估.md | 1348 ++++++++++++++ docs/00-项目概述/现有系统技术摸底报告.md | 1604 +++++++++++++++++ docs/00-项目概述/系统总体架构设计.md | 50 + .../01-用户与权限中心(UAM)/README.md | 85 + docs/01-平台基础层/02-存储服务/README.md | 65 + docs/01-平台基础层/03-通知服务/README.md | 51 + docs/01-平台基础层/04-监控与日志/README.md | 51 + docs/01-平台基础层/05-系统配置/README.md | 47 + .../06-前端架构/01-前端总体架构设计.md | 577 ++++++ .../06-前端架构/02-导航结构设计.md | 390 ++++ .../06-前端架构/03-架构原型图.html | 306 ++++ docs/01-平台基础层/06-前端架构/README.md | 55 + docs/01-平台基础层/README.md | 78 + .../[AI对接] 平台层快速上下文.md | 135 ++ docs/01-设计文档/API设计规范.md | 1131 ------------ docs/01-设计文档/数据库设计文档.md | 893 --------- .../01-LLM大模型网关/03-CloseAI集成指南.md | 524 ++++++ docs/02-通用能力层/01-LLM大模型网关/README.md | 149 ++ .../01-LLM大模型网关/[AI对接] LLM网关快速上下文.md | 535 ++++++ docs/02-通用能力层/02-文档处理引擎/README.md | 107 ++ docs/02-通用能力层/03-RAG引擎/README.md | 102 ++ docs/02-通用能力层/04-数据ETL引擎/README.md | 88 + docs/02-通用能力层/05-医学NLP引擎/README.md | 82 + docs/02-通用能力层/README.md | 95 + .../[AI对接] 通用能力快速上下文.md | 180 ++ docs/03-业务模块/ADMIN-运营管理端/README.md | 101 ++ .../ADMIN-运营管理端/[AI对接] ADMIN快速上下文.md | 504 ++++++ .../AIA-AI智能问答/02-技术设计/01-数据库设计.md | 527 ++++++ docs/03-业务模块/AIA-AI智能问答/README.md | 70 + .../AIA-AI智能问答}/用户端原型图.html | 0 .../ASL-AI智能文献/00-系统设计/01-系统架构设计.md | 99 + .../00-系统设计/02-模块关联关系设计.md | 24 + .../ASL-AI智能文献/00-系统设计/03-数据流设计.md | 24 + .../ASL-AI智能文献/00-系统设计/04-技术选型说明.md | 24 + .../ASL-AI智能文献/01-需求分析/01-需求总览.md | 61 + .../01-需求分析/02-标题摘要初筛需求详述.md | 199 ++ .../01-需求分析/03-全文复筛需求详述.md | 26 + .../01-需求分析/04-用户故事和验收标准.md | 24 + .../ASL-AI智能文献/01-需求分析/05-非功能性需求.md | 24 + .../01-需求分析/AI智能文献PRD(1)-产品概览.md | 89 + .../01-需求分析/AI智能文献PRD(2)-初筛与复筛.md | 59 + .../01-需求分析/AI智能文献PRD(3)-提取与分析模块.md | 51 + .../ASL-AI智能文献/02-技术设计/01-数据库设计.md | 202 +++ .../ASL-AI智能文献/02-技术设计/02-API设计规范.md | 238 +++ .../ASL-AI智能文献/02-技术设计/03-前端组件设计.md | 155 ++ .../02-技术设计/04-AI模型集成设计.md | 24 + .../ASL-AI智能文献/02-技术设计/05-文件处理设计.md | 24 + .../02-技术设计/06-质量保障与可追溯策略.md | 949 ++++++++++ .../02-技术设计/07-文献处理技术选型.md | 1024 +++++++++++ .../03-UI设计/01-标题摘要初筛UI设计.md | 87 + .../ASL-AI智能文献/03-UI设计/02-全文复筛UI设计.md | 24 + .../ASL-AI智能文献/03-UI设计/03-设计规范.md | 24 + .../03-UI设计/AI智能文献-全文复筛.html | 524 ++++++ .../03-UI设计/AI智能文献-标题摘要初筛原型.html | 450 +++++ .../ASL-AI智能文献/04-开发计划/01-开发里程碑.md | 72 + .../04-开发计划/02-标题摘要初筛开发计划.md | 99 + .../ASL-AI智能文献/04-开发计划/03-任务分解.md | 24 + .../ASL-AI智能文献/05-测试文档/01-测试计划.md | 24 + .../05-测试文档/02-标题摘要初筛测试用例.md | 24 + .../05-测试文档/03-测试数据/README.md | 272 +++ .../05-测试文档/03-测试数据/pdf-extraction/.gitkeep | 0 .../05-测试文档/03-测试数据/screening/.gitkeep | 0 .../05-测试文档/03-测试数据/screening/Test Cases.xlsx | Bin 0 -> 201771 bytes .../screening/测试案例的PICOS、纳入标准、排除标准.txt | 77 + docs/03-业务模块/ASL-AI智能文献/README.md | 82 + .../ASL-AI智能文献/[AI对接] ASL快速上下文.md | 321 ++++ docs/03-业务模块/DC-数据清洗整理/README.md | 98 + .../PKB-个人知识库/02-技术设计/01-数据库设计.md | 596 ++++++ docs/03-业务模块/PKB-个人知识库/README.md | 62 + docs/03-业务模块/README.md | 119 ++ docs/03-业务模块/RVW-稿件审查系统/README.md | 95 + .../RVW-稿件审查系统/稿件方法学评估标准.txt | 31 + .../RVW-稿件审查系统/稿约规范性评估标准.txt | 26 + docs/03-业务模块/SSA-智能统计分析/README.md | 84 + docs/03-业务模块/ST-统计分析工具/README.md | 82 + .../[AI对接] 业务模块快速上下文.md | 173 ++ docs/04-开发规范/01-数据库设计规范.md | 497 +++++ docs/04-开发规范/02-API设计规范.md | 527 ++++++ docs/04-开发规范/03-数据库全局视图.md | 349 ++++ docs/04-开发规范/04-API路由总览.md | 393 ++++ .../代码规范.md => 04-开发规范/05-代码规范.md} | 10 + docs/04-开发规范/README.md | 228 +++ docs/04-开发计划/开发里程碑.md | 1346 -------------- docs/05-每日进度/Day04-05-完成总结.md | 447 ----- docs/05-每日进度/Day04-环境搭建完成.md | 159 -- docs/05-每日进度/Day05-后端基础架构完成.md | 376 ---- docs/05-每日进度/Day06-前端基础架构完成.md | 439 ----- docs/05-每日进度/Day07-前端完整布局完成.md | 321 ---- docs/05-每日进度/Day08-09-项目管理API完成.md | 465 ----- .../Day10-11-智能体配置系统完成.md | 580 ------ .../Day12-13-LLM适配器与对话系统完成.md | 746 -------- docs/05-每日进度/Day14-17-前端对话界面完成.md | 609 ------- docs/05-每日进度/Day18-Dify部署完成.md | 333 ---- docs/05-每日进度/Day19-Dify客户端封装完成.md | 495 ----- docs/05-每日进度/Day20-知识库管理后端完成.md | 423 ----- .../Day21-22-知识库前端开发与问题修复.md | 231 --- docs/05-每日进度/Day21-知识库管理前端完成.md | 709 -------- .../Day23-24-知识库检索与@引用功能完成.md | 419 ----- docs/05-每日进度/Day25-智能问答功能完成.md | 597 ------ docs/05-每日进度/Dify部署状态-Day4-Day5.md | 275 --- docs/05-每日进度/README导航优化总结.md | 249 --- docs/05-每日进度/里程碑1-MVP完成总结.md | 638 ------- docs/05-部署文档/01-部署架构设计.md | 40 + docs/05-部署文档/README.md | 62 + docs/06-测试文档/README.md | 65 + docs/07-运维文档/01-环境配置指南.md | 478 +++++ docs/07-运维文档/02-环境变量配置模板.md | 208 +++ docs/07-运维文档/README.md | 50 + docs/08-项目管理/01-整体开发计划/README.md | 69 + docs/08-项目管理/02-里程碑规划/README.md | 43 + .../03-每周计划/2025-11-12-工作总结.md | 394 ++++ .../03-每周计划/2025-11-13-任务19完成总结.md | 136 ++ .../03-每周计划/2025-11-13-工作总结.md | 363 ++++ .../03-每周计划/2025-11-14-任务19完成总结.md | 231 +++ docs/08-项目管理/03-每周计划/README.md | 40 + .../08-项目管理/03-每周计划/Week2-总结报告.md | 297 +++ docs/08-项目管理/README.md | 83 + docs/08-项目管理/V2.2版本变化说明.md | 309 ++++ .../下一阶段行动计划-V2.0-模块化架构优先.md | 829 +++++++++ .../下一阶段行动计划-V2.1-务实版.md | 939 ++++++++++ .../下一阶段行动计划-V2.2-前端架构优先版.md | 598 ++++++ .../下一阶段行动计划-V2.2-完整版.md | 1557 ++++++++++++++++ .../01-Schema隔离架构设计(10个).md | 888 +++++++++ docs/09-架构实施/02-数据库连接配置.md | 534 ++++++ docs/09-架构实施/Frontend-v2创建完成报告.md | 371 ++++ docs/09-架构实施/Prisma配置完成报告.md | 204 +++ docs/09-架构实施/README.md | 90 + docs/09-架构实施/Schema迁移完成报告.md | 302 ++++ .../001-create-all-10-schemas.sql | 128 ++ .../migration-scripts/002-migrate-platform.sql | 146 ++ .../migration-scripts/003-migrate-aia.sql | 339 ++++ .../migration-scripts/004-migrate-pkb.sql | 412 +++++ .../migration-scripts/005-validate-all.sql | 544 ++++++ docs/09-架构实施/migration-scripts/README.md | 325 ++++ .../migration-scripts/execute-migration.ps1 | 268 +++ docs/09-架构实施/前端模块注册机制实施报告.md | 557 ++++++ docs/09-架构实施/后端代码分层-迁移计划.md | 460 +++++ docs/09-架构实施/后端代码分层实施报告.md | 411 +++++ docs/09-架构实施/后端架构增量演进方案.md | 450 +++++ docs/09-架构实施/快速功能测试报告.md | 244 +++ docs/09-架构实施/数据库验证通过.md | 87 + docs/09-架构实施/模块配置更新报告.md | 235 +++ docs/09-架构实施/编码规范-UTF8最佳实践.md | 232 +++ docs/README.md | 331 +++- docs/[AI对接] 项目状态与下一步指南.md | 677 +++++++ docs/[完成] 文档重构总结报告.md | 366 ++++ docs/_templates/API设计-模板.md | 475 +++++ docs/_templates/README.md | 79 + docs/_templates/[AI对接] 快速上下文-模板.md | 180 ++ docs/_templates/数据库设计-模板.md | 220 +++ docs/_templates/模块README-模板.md | 87 + docs/文档整理清单.md | 282 +++ 173 files changed, 46227 insertions(+), 11964 deletions(-) create mode 100644 docs/00-系统总体设计/00-今日架构设计总结.md create mode 100644 docs/00-系统总体设计/00-核心问题解答.md create mode 100644 docs/00-系统总体设计/00-阅读指南.md create mode 100644 docs/00-系统总体设计/01-系统架构分层设计.md create mode 100644 docs/00-系统总体设计/02-文档体系重构方案.md create mode 100644 docs/00-系统总体设计/03-数据库架构说明.md create mode 100644 docs/00-系统总体设计/04-运营管理端架构设计.md create mode 100644 docs/00-系统总体设计/05-Schema隔离方案与成本分析.md create mode 100644 docs/00-系统总体设计/06-模块独立部署与单机版方案.md create mode 100644 docs/00-系统总体设计/07-Monorepo架构评估.md create mode 100644 docs/00-系统总体设计/08-架构设计全景图.md create mode 100644 docs/00-系统总体设计/09-总体需求文档(PRD).md rename docs/{03-业务规则/核心业务规则总览.md => 00-系统总体设计/10-核心业务规则总览.md} (99%) create mode 100644 docs/00-系统总体设计/99-下一步行动决策建议.md create mode 100644 docs/00-系统总体设计/README.md create mode 100644 docs/00-系统总体设计/[AI对接] 快速上下文.md create mode 100644 docs/00-系统总体设计/[重要] 2025-11-06 架构设计完成报告.md create mode 100644 docs/00-系统总体设计/前后端模块化架构设计-V2.md create mode 100644 docs/00-项目概述/壹证循科技 AI科研产品需求文档.md create mode 100644 docs/00-项目概述/壹证循科技AI科研产品 - 技术架构白皮书.md create mode 100644 docs/00-项目概述/文档梳理与差异分析.md create mode 100644 docs/00-项目概述/最新需求与技术方案深度评估.md create mode 100644 docs/00-项目概述/现有系统技术摸底报告.md create mode 100644 docs/00-项目概述/系统总体架构设计.md create mode 100644 docs/01-平台基础层/01-用户与权限中心(UAM)/README.md create mode 100644 docs/01-平台基础层/02-存储服务/README.md create mode 100644 docs/01-平台基础层/03-通知服务/README.md create mode 100644 docs/01-平台基础层/04-监控与日志/README.md create mode 100644 docs/01-平台基础层/05-系统配置/README.md create mode 100644 docs/01-平台基础层/06-前端架构/01-前端总体架构设计.md create mode 100644 docs/01-平台基础层/06-前端架构/02-导航结构设计.md create mode 100644 docs/01-平台基础层/06-前端架构/03-架构原型图.html create mode 100644 docs/01-平台基础层/06-前端架构/README.md create mode 100644 docs/01-平台基础层/README.md create mode 100644 docs/01-平台基础层/[AI对接] 平台层快速上下文.md delete mode 100644 docs/01-设计文档/API设计规范.md delete mode 100644 docs/01-设计文档/数据库设计文档.md create mode 100644 docs/02-通用能力层/01-LLM大模型网关/03-CloseAI集成指南.md create mode 100644 docs/02-通用能力层/01-LLM大模型网关/README.md create mode 100644 docs/02-通用能力层/01-LLM大模型网关/[AI对接] LLM网关快速上下文.md create mode 100644 docs/02-通用能力层/02-文档处理引擎/README.md create mode 100644 docs/02-通用能力层/03-RAG引擎/README.md create mode 100644 docs/02-通用能力层/04-数据ETL引擎/README.md create mode 100644 docs/02-通用能力层/05-医学NLP引擎/README.md create mode 100644 docs/02-通用能力层/README.md create mode 100644 docs/02-通用能力层/[AI对接] 通用能力快速上下文.md create mode 100644 docs/03-业务模块/ADMIN-运营管理端/README.md create mode 100644 docs/03-业务模块/ADMIN-运营管理端/[AI对接] ADMIN快速上下文.md create mode 100644 docs/03-业务模块/AIA-AI智能问答/02-技术设计/01-数据库设计.md create mode 100644 docs/03-业务模块/AIA-AI智能问答/README.md rename docs/{01-设计文档 => 03-业务模块/AIA-AI智能问答}/用户端原型图.html (100%) create mode 100644 docs/03-业务模块/ASL-AI智能文献/00-系统设计/01-系统架构设计.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/00-系统设计/02-模块关联关系设计.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/00-系统设计/03-数据流设计.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/00-系统设计/04-技术选型说明.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/01-需求分析/01-需求总览.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/01-需求分析/02-标题摘要初筛需求详述.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/01-需求分析/03-全文复筛需求详述.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/01-需求分析/04-用户故事和验收标准.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/01-需求分析/05-非功能性需求.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/01-需求分析/AI智能文献PRD(1)-产品概览.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/01-需求分析/AI智能文献PRD(2)-初筛与复筛.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/01-需求分析/AI智能文献PRD(3)-提取与分析模块.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/02-技术设计/01-数据库设计.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/02-技术设计/02-API设计规范.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/02-技术设计/03-前端组件设计.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/02-技术设计/04-AI模型集成设计.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/02-技术设计/05-文件处理设计.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/02-技术设计/06-质量保障与可追溯策略.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/02-技术设计/07-文献处理技术选型.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/03-UI设计/01-标题摘要初筛UI设计.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/03-UI设计/02-全文复筛UI设计.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/03-UI设计/03-设计规范.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/03-UI设计/AI智能文献-全文复筛.html create mode 100644 docs/03-业务模块/ASL-AI智能文献/03-UI设计/AI智能文献-标题摘要初筛原型.html create mode 100644 docs/03-业务模块/ASL-AI智能文献/04-开发计划/01-开发里程碑.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/04-开发计划/02-标题摘要初筛开发计划.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/04-开发计划/03-任务分解.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/05-测试文档/01-测试计划.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/05-测试文档/02-标题摘要初筛测试用例.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/README.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/pdf-extraction/.gitkeep create mode 100644 docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/screening/.gitkeep create mode 100644 docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/screening/Test Cases.xlsx create mode 100644 docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/screening/测试案例的PICOS、纳入标准、排除标准.txt create mode 100644 docs/03-业务模块/ASL-AI智能文献/README.md create mode 100644 docs/03-业务模块/ASL-AI智能文献/[AI对接] ASL快速上下文.md create mode 100644 docs/03-业务模块/DC-数据清洗整理/README.md create mode 100644 docs/03-业务模块/PKB-个人知识库/02-技术设计/01-数据库设计.md create mode 100644 docs/03-业务模块/PKB-个人知识库/README.md create mode 100644 docs/03-业务模块/README.md create mode 100644 docs/03-业务模块/RVW-稿件审查系统/README.md create mode 100644 docs/03-业务模块/RVW-稿件审查系统/稿件方法学评估标准.txt create mode 100644 docs/03-业务模块/RVW-稿件审查系统/稿约规范性评估标准.txt create mode 100644 docs/03-业务模块/SSA-智能统计分析/README.md create mode 100644 docs/03-业务模块/ST-统计分析工具/README.md create mode 100644 docs/03-业务模块/[AI对接] 业务模块快速上下文.md create mode 100644 docs/04-开发规范/01-数据库设计规范.md create mode 100644 docs/04-开发规范/02-API设计规范.md create mode 100644 docs/04-开发规范/03-数据库全局视图.md create mode 100644 docs/04-开发规范/04-API路由总览.md rename docs/{02-开发规范/代码规范.md => 04-开发规范/05-代码规范.md} (99%) create mode 100644 docs/04-开发规范/README.md delete mode 100644 docs/04-开发计划/开发里程碑.md delete mode 100644 docs/05-每日进度/Day04-05-完成总结.md delete mode 100644 docs/05-每日进度/Day04-环境搭建完成.md delete mode 100644 docs/05-每日进度/Day05-后端基础架构完成.md delete mode 100644 docs/05-每日进度/Day06-前端基础架构完成.md delete mode 100644 docs/05-每日进度/Day07-前端完整布局完成.md delete mode 100644 docs/05-每日进度/Day08-09-项目管理API完成.md delete mode 100644 docs/05-每日进度/Day10-11-智能体配置系统完成.md delete mode 100644 docs/05-每日进度/Day12-13-LLM适配器与对话系统完成.md delete mode 100644 docs/05-每日进度/Day14-17-前端对话界面完成.md delete mode 100644 docs/05-每日进度/Day18-Dify部署完成.md delete mode 100644 docs/05-每日进度/Day19-Dify客户端封装完成.md delete mode 100644 docs/05-每日进度/Day20-知识库管理后端完成.md delete mode 100644 docs/05-每日进度/Day21-22-知识库前端开发与问题修复.md delete mode 100644 docs/05-每日进度/Day21-知识库管理前端完成.md delete mode 100644 docs/05-每日进度/Day23-24-知识库检索与@引用功能完成.md delete mode 100644 docs/05-每日进度/Day25-智能问答功能完成.md delete mode 100644 docs/05-每日进度/Dify部署状态-Day4-Day5.md delete mode 100644 docs/05-每日进度/README导航优化总结.md delete mode 100644 docs/05-每日进度/里程碑1-MVP完成总结.md create mode 100644 docs/05-部署文档/01-部署架构设计.md create mode 100644 docs/05-部署文档/README.md create mode 100644 docs/06-测试文档/README.md create mode 100644 docs/07-运维文档/01-环境配置指南.md create mode 100644 docs/07-运维文档/02-环境变量配置模板.md create mode 100644 docs/07-运维文档/README.md create mode 100644 docs/08-项目管理/01-整体开发计划/README.md create mode 100644 docs/08-项目管理/02-里程碑规划/README.md create mode 100644 docs/08-项目管理/03-每周计划/2025-11-12-工作总结.md create mode 100644 docs/08-项目管理/03-每周计划/2025-11-13-任务19完成总结.md create mode 100644 docs/08-项目管理/03-每周计划/2025-11-13-工作总结.md create mode 100644 docs/08-项目管理/03-每周计划/2025-11-14-任务19完成总结.md create mode 100644 docs/08-项目管理/03-每周计划/README.md create mode 100644 docs/08-项目管理/03-每周计划/Week2-总结报告.md create mode 100644 docs/08-项目管理/README.md create mode 100644 docs/08-项目管理/V2.2版本变化说明.md create mode 100644 docs/08-项目管理/下一阶段行动计划-V2.0-模块化架构优先.md create mode 100644 docs/08-项目管理/下一阶段行动计划-V2.1-务实版.md create mode 100644 docs/08-项目管理/下一阶段行动计划-V2.2-前端架构优先版.md create mode 100644 docs/08-项目管理/下一阶段行动计划-V2.2-完整版.md create mode 100644 docs/09-架构实施/01-Schema隔离架构设计(10个).md create mode 100644 docs/09-架构实施/02-数据库连接配置.md create mode 100644 docs/09-架构实施/Frontend-v2创建完成报告.md create mode 100644 docs/09-架构实施/Prisma配置完成报告.md create mode 100644 docs/09-架构实施/README.md create mode 100644 docs/09-架构实施/Schema迁移完成报告.md create mode 100644 docs/09-架构实施/migration-scripts/001-create-all-10-schemas.sql create mode 100644 docs/09-架构实施/migration-scripts/002-migrate-platform.sql create mode 100644 docs/09-架构实施/migration-scripts/003-migrate-aia.sql create mode 100644 docs/09-架构实施/migration-scripts/004-migrate-pkb.sql create mode 100644 docs/09-架构实施/migration-scripts/005-validate-all.sql create mode 100644 docs/09-架构实施/migration-scripts/README.md create mode 100644 docs/09-架构实施/migration-scripts/execute-migration.ps1 create mode 100644 docs/09-架构实施/前端模块注册机制实施报告.md create mode 100644 docs/09-架构实施/后端代码分层-迁移计划.md create mode 100644 docs/09-架构实施/后端代码分层实施报告.md create mode 100644 docs/09-架构实施/后端架构增量演进方案.md create mode 100644 docs/09-架构实施/快速功能测试报告.md create mode 100644 docs/09-架构实施/数据库验证通过.md create mode 100644 docs/09-架构实施/模块配置更新报告.md create mode 100644 docs/09-架构实施/编码规范-UTF8最佳实践.md create mode 100644 docs/[AI对接] 项目状态与下一步指南.md create mode 100644 docs/[完成] 文档重构总结报告.md create mode 100644 docs/_templates/API设计-模板.md create mode 100644 docs/_templates/README.md create mode 100644 docs/_templates/[AI对接] 快速上下文-模板.md create mode 100644 docs/_templates/数据库设计-模板.md create mode 100644 docs/_templates/模块README-模板.md create mode 100644 docs/文档整理清单.md diff --git a/docs/00-系统总体设计/00-今日架构设计总结.md b/docs/00-系统总体设计/00-今日架构设计总结.md new file mode 100644 index 00000000..168eb6fd --- /dev/null +++ b/docs/00-系统总体设计/00-今日架构设计总结.md @@ -0,0 +1,523 @@ +# 2025-11-06 架构设计总结 + +> **日期:** 2025-11-06 +> **工作类型:** 系统架构深度设计 +> **成果:** 7个核心架构文档,完整的技术方案 + +--- + +## 🎉 今日成果 + +### 完成的核心文档(7个) + +| # | 文档 | 核心内容 | 页数 | +|---|------|---------|------| +| 1 | [00-核心问题解答](./00-核心问题解答.md) | 部署模式、审稿系统独立性、数据库架构澄清 | 详尽 | +| 2 | [01-系统架构分层设计](./01-系统架构分层设计.md) | 三层架构、8个业务模块、5个通用能力 | 900+ | +| 3 | [02-文档体系重构方案v2.0](./02-文档体系重构方案.md) | 新文档结构、8个模块、4种部署方案 | 790+ | +| 4 | [03-数据库架构说明](./03-数据库架构说明.md) | PostgreSQL Docker部署、两个独立数据库 | 434+ | +| 5 | [04-运营管理端架构设计](./04-运营管理端架构设计.md) | 15个功能模块、3阶段实施 | 859+ | +| 6 | [05-Schema隔离方案与成本分析](./05-Schema隔离方案与成本分析.md) | 逻辑vs物理隔离、改造成本对比 | 1042+ | +| 7 | [06-模块独立部署与单机版方案](./06-模块独立部署与单机版方案.md) | 完整打包、共享服务、Electron架构 | 1541+ | +| 8 | [07-Monorepo架构评估](./07-Monorepo架构评估.md) | 当前阶段是否需要、成本收益分析 | 555+ | + +**总计:** 6000+ 行详细设计文档 + +--- + +## 📊 核心架构决策 + +### 1. 系统架构分层 ⭐⭐⭐⭐⭐ + +**三层架构:** +``` +┌───────────────────────────────────────┐ +│ 业务模块层(8个模块) │ +│ AIA | ASL | PKB | DC | SSA | ST | RVW | ADMIN +└───────────────────────────────────────┘ + ↓ 依赖 +┌───────────────────────────────────────┐ +│ 通用能力层(5个能力) │ +│ LLM网关 | 文档处理 | RAG | ETL | NLP │ +└───────────────────────────────────────┘ + ↓ 依赖 +┌───────────────────────────────────────┐ +│ 平台基础层 │ +│ 用户权限 | 存储 | 通知 | 监控 | 配置 │ +└───────────────────────────────────────┘ +``` + +**关键洞察:** +- ✅ LLM网关:71%复用率(5/7模块依赖) +- ✅ 文档处理:86%复用率(6/7模块依赖) +- ✅ 模块独立性:RVW、ASL、DC可独立销售 + +--- + +### 2. 业务模块规划(8个模块) + +| 模块 | 名称 | 商业价值 | 独立性 | 状态 | +|------|------|---------|-------|------| +| AIA | AI智能问答 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ✅ 已完成 | +| ASL | AI智能文献 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⏳ 下一步重点 | +| PKB | 个人知识库 | ⭐⭐⭐ | ⭐⭐⭐ | ✅ 已完成 | +| DC | 数据清洗整理 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⏳ 规划中 | +| SSA | 智能统计分析 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⏳ 规划中 | +| ST | 统计分析工具 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⏳ 规划中 | +| RVW | 稿件审查系统 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⚡ 独立系统 | +| **ADMIN** | 运营管理端 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐ 新增 | + +**核心亮点:** +- ✅ 新增运营管理端(15个功能模块) +- ✅ 3个模块极具独立销售价值(RVW、ASL、DC) + +--- + +### 3. 部署方案(4种模式) + +| 部署模式 | 目标用户 | 技术方案 | 代码复用 | 优先级 | +|---------|---------|---------|---------|-------| +| **云端SaaS** | 个人、小机构 | Node.js + PostgreSQL | 100% | P0(当前) | +| **独立产品包** | 特定客户 | Docker完整打包 | 80% | P1(阶段二) | +| **Electron单机版** | 个人医生 | 前端90%+后端80% | 85% | P2(阶段二) | +| **私有化部署** | 医院、机构 | K8s/Docker | 100% | P1(阶段二) | + +**关键洞察:** +- ✅ Electron单机版代码复用率极高(85%+) +- ✅ 独立产品包支持模块化销售 +- ✅ 4种部署覆盖全部市场需求 + +--- + +### 4. 数据库架构 + +**当前状态:** +- ✅ 有自己独立的PostgreSQL数据库(Docker部署) +- ✅ 16张表,全部在public schema +- ❌ 需要Schema隔离(未来微服务拆分的基础) + +**Schema隔离决策:** + +| 方案 | 成本 | 时机 | 建议 | +|------|------|------|------| +| 现在做物理隔离 | 1周 | 数据量小 | ⭐⭐⭐⭐⭐ 强烈推荐 | +| 继续逻辑隔离 | 0 | 当前 | ⭐⭐⭐ 可接受 | +| 未来做物理隔离 | 3-5周 | 数据量大 | ⚠️ 成本5倍 | + +**关键洞察:** +- ✅ 现在做成本最低(数据量小) +- ✅ 为7个模块打下坚实基础 +- ✅ 支持模块独立部署 + +--- + +### 5. 运营管理端(第8个模块) + +**15个核心功能:** + +**P0(必须):** +1. 用户管理 +2. Feature Flag管理 +3. LLM模型管理 +4. 系统配置管理 + +**P1(重要):** +5. 智能体提示词管理 +6. 监控与日志 +7. 数据统计与报表 +8. 成本分析与计费 + +**P2(有用):** +9-15. 租户管理、公告、文档、工单、健康检查、备份、运营分析 + +**实施计划:** +- 阶段一(1-2个月):P0功能 +- 阶段二(1-2个月):P1功能 +- 阶段三(1-2个月):P2功能 + +**关键洞察:** +- ✅ 商业模式的技术保障(Feature Flag、成本控制) +- ✅ 独立的前端应用(`https://admin.yizhengxun.com`) + +--- + +### 6. 模块独立部署方案 + +**方案一:完整打包(独立产品)** +``` +审稿系统独立产品 = + RVW模块 + 必需的平台层 + 必需的能力层 + 独立数据库 +``` + +**方案二:共享服务(平台内模块)** +``` +API网关 + ├─ AIA模块服务(独立部署) + ├─ ASL模块服务(独立部署) + └─ 共享服务(平台层+能力层,一次部署) +``` + +**关键技术:** +- ✅ Monorepo架构(包管理、代码复用) +- ✅ 选择性导出(精简版平台层) +- ✅ Docker打包(一键部署) + +--- + +### 7. Monorepo架构评估 + +**成本对比:** + +| 方案 | 立即成本 | 未来成本 | 投入产出比 | +|------|---------|---------|-----------| +| **现在转换** | 2-3天 | 0 | ⭐⭐⭐⭐⭐ 极高 | +| **延后转换** | 0 | 7-11天 | ⭐⭐ 低 | + +**建议:** 现在转换 ⭐⭐⭐⭐⭐ + +**理由:** +1. ✅ 投入小(2-3天),收益大(节省5-8天) +2. ✅ 正处于最佳时机(即将开发多模块) +3. ✅ 符合未来规划(运营管理端、独立产品) +4. ✅ 学习成本可控(有AI全程指导) + +--- + +## 🎯 关键技术决策总结 + +### 决策1:Schema隔离 + +**建议:现在做物理隔离** ⭐⭐⭐⭐⭐ + +- 成本:1周 +- 收益:避免未来3-5周的改造成本 +- 理由:数据量小,改造风险低 + +--- + +### 决策2:Monorepo架构 + +**建议:现在转换** ⭐⭐⭐⭐⭐ + +- 成本:2-3天 +- 收益:节省未来5-8天 +- 理由:即将开发多个应用(运营管理端、ASL等) + +--- + +### 决策3:部署方案 + +**阶段一:专注云端SaaS** +- 不做混合部署(技术难度极高,需求不明确) +- 暂缓Electron单机版(阶段二再做) + +--- + +### 决策4:下一步开发重点 + +**优先级:** +1. P0:ASL模块(AI智能文献) +2. P0:LLM网关(商业模式基础) +3. P1:Schema隔离(可选,但强烈推荐) +4. P1:Monorepo转换(可选,但强烈推荐) + +--- + +## 📚 文档体系v2.0 + +### 新文档结构 + +``` +docs/ + ├── 00-系统总体设计/ ✅ 7个核心文档 + ├── 01-平台基础层/ + ├── 02-通用能力层/ + ├── 03-业务模块/ ✅ 新增ADMIN模块(共8个) + ├── 04-开发规范/ + ├── 05-部署文档/ ✅ 扩展为4种部署方案 + ├── 06-测试文档/ + ├── 07-运维文档/ + ├── 08-项目管理/ + └── 09-架构实施/ ✅ 新增(Monorepo、打包、微服务) +``` + +--- + +## 💡 核心洞察 + +### 1. LLM网关是商业模式的核心 + +**为什么?** +``` +商业模式: +- 基础版:只能用DeepSeek-V3(¥1/百万tokens) +- 高级版:可用DeepSeek + Qwen3 +- 旗舰版:可用所有模型 + +成本控制: +- 统一监控、限流、计费 +- 超出配额自动降级 +- 按版本动态切换模型 + +5个模块依赖(71%复用率) +``` + +--- + +### 2. 审稿系统极具独立价值 + +**为什么适合独立?** +``` +1. 用户群完全不同(期刊编辑部 vs 临床医生) +2. 业务逻辑完全独立 +3. 部署场景独立 +4. 商业模式独立(按期刊订阅) + +独立销售价值:⭐⭐⭐⭐⭐ 极高 +``` + +--- + +### 3. 现在是最佳改造时机 + +**为什么现在做?** +``` +Schema隔离: +- 现在:1周(数据量小) +- 未来:3-5周(数据量大,成本5倍) + +Monorepo转换: +- 现在:2-3天(代码量适中) +- 未来:7-11天(多应用、代码量大) + +总结:越早做,成本越低 +``` + +--- + +### 4. Electron单机版代码复用率极高 + +**为什么可行?** +``` +前端复用:90%+ +- 所有React组件 +- UI库、状态管理、路由 +- 只需修改API调用层(1个文件) + +后端复用:80%+ +- 所有Service层(业务逻辑) +- Prisma ORM +- 只需适配:HTTP路由 → IPC Handler + +总复用率:85%+ +技术可行性:⭐⭐⭐⭐⭐ +``` + +--- + +## 🚀 下一步行动建议 + +### 方案A:快速推进业务(推荐给时间紧迫的情况) + +**本周:** +- ✅ 立即开始ASL模块开发 +- ⚠️ 暂缓Schema隔离 +- ⚠️ 暂缓Monorepo转换 + +**触发条件:** +- 开发运营管理端时,必须转换Monorepo +- 数据量超过50万行时,必须做Schema隔离 + +**优点:** 立即推进业务 +**缺点:** 累积技术债,未来成本高 + +--- + +### 方案B:夯实基础,稳步推进(强烈推荐) + +**第1周:Schema隔离 + Monorepo转换(5-6天)** +- Day 1-3:Schema隔离(逻辑隔离 → 物理隔离) +- Day 4-6:Monorepo转换 + +**第2周:开始ASL模块开发** +- 享受Schema隔离带来的清晰架构 +- 享受Monorepo带来的代码复用便利 + +**优点:** +- ✅ 一次性还清技术债 +- ✅ 为7个模块打下坚实基础 +- ✅ 避免未来大规模重构 + +**缺点:** +- ⚠️ ASL模块延迟1周 + +**投入产出比:** ⭐⭐⭐⭐⭐ 极高 + +--- + +### 方案C:折中方案(推荐) + +**本周:** +- Day 1-3:Monorepo转换(必须,近期开发运营管理端) +- Day 4-7:开始ASL模块开发 + +**下周:** +- 继续ASL模块开发 + +**未来(1-2个月后):** +- Schema隔离(数据量增长前) + +**优点:** +- ✅ 解决最紧迫的问题(Monorepo) +- ✅ 快速推进业务(ASL) +- ✅ 延后但不放弃Schema隔离 + +**缺点:** +- ⚠️ Schema隔离成本会增加 + +--- + +## 📊 投入产出比分析 + +| 投入 | 成本 | 收益 | ROI | +|------|------|------|-----| +| **Schema隔离** | 1周 | 避免未来3-5周改造 | 300-500% | +| **Monorepo转换** | 2-3天 | 避免未来7-11天改造 | 300-400% | +| **架构设计** | 1天 | 清晰的技术路线图 | 无价 | + +**总投入产出比:** ⭐⭐⭐⭐⭐ 极高 + +--- + +## 🎯 我的最终建议 + +### 推荐:方案B(夯实基础)⭐⭐⭐⭐⭐ + +**核心理由:** + +**1. 投入1周,节省未来1个月** +``` +现在投入: +- Schema隔离:3天 +- Monorepo转换:3天 +- 总计:6天(1周) + +未来节省: +- Schema隔离:15-25天(3-5周) +- Monorepo转换:7-11天 +- 总计:22-36天(1个月+) + +投入产出比:300-500% +``` + +**2. 正处于最佳时机** +``` +当前: +- 数据量小(< 1万行) +- 代码量适中 +- 即将开发多个模块 +- 团队小,沟通成本低 + +未来: +- 数据量大(100万行+) +- 代码量大 +- 多个应用同时运行 +- 重构影响范围大 +``` + +**3. 为7个模块打下坚实基础** +``` +ASL、DC、SSA、ST、RVW、ADMIN等模块: +- 直接享受Schema隔离的好处 +- 直接享受Monorepo的代码复用 +- 避免重复造轮子 +``` + +**4. 避免技术债累积** +``` +技术债的特点: +- 时间越久,利息越高 +- 改造成本成倍增长 +- 影响业务创新速度 + +现在改造 = 一次性还清 +``` + +--- + +## ✅ 今日完成清单 + +### 架构设计(100%完成) + +- [x] 系统架构分层设计 +- [x] 文档体系重构方案v2.0 +- [x] 数据库架构说明 +- [x] 运营管理端架构设计 +- [x] Schema隔离方案与成本分析 +- [x] 模块独立部署与单机版方案 +- [x] Monorepo架构评估 + +### 关键决策(100%完成) + +- [x] 三层架构设计 +- [x] 8个业务模块规划 +- [x] 4种部署方案设计 +- [x] Schema隔离时机建议 +- [x] Monorepo转换时机建议 + +### 文档产出(100%完成) + +- [x] 7个核心架构文档 +- [x] 6000+行详细设计 +- [x] 完整的技术方案 + +--- + +## 🎉 恭喜! + +**今天完成了非常扎实和深入的架构设计工作!** + +我们从零开始,构建了一个: +- ✅ 清晰的三层架构 +- ✅ 完整的8个业务模块规划 +- ✅ 4种部署方案 +- ✅ 详细的实施路径 + +**这些架构设计将指导未来6-12个月的开发工作!** + +--- + +## 📌 明天开始可以做的事 + +### 选择1:立即开始开发 +- ASL模块开发 +- 使用现有架构 + +### 选择2:夯实基础(强烈推荐) +- Week 1:Schema隔离 + Monorepo转换 +- Week 2:ASL模块开发 + +### 选择3:折中方案 +- Day 1-3:Monorepo转换 +- Day 4+:ASL模块开发 + +--- + +**您的选择?** 😊 + +我已经准备好全程协助您实施任何一个方案! + + + + + + + + + + + + + + + diff --git a/docs/00-系统总体设计/00-核心问题解答.md b/docs/00-系统总体设计/00-核心问题解答.md new file mode 100644 index 00000000..a5a9eaf6 --- /dev/null +++ b/docs/00-系统总体设计/00-核心问题解答.md @@ -0,0 +1,698 @@ +# 核心问题解答 + +> **创建日期:** 2025-11-06 +> **文档目的:** 回答用户提出的关键架构问题 + +--- + +## 📋 您提出的问题 + +您提出了非常核心的架构问题,这些问题直接影响了整个系统的设计。让我逐一深入解答: + +--- + +## 1️⃣ 文档系统重构 + +### 您的建议 + +> (1)系统总体架构、总体需求PRD、系统总体设计、系统总体部署等,应该是一个独立的文件夹 +> (2)7个模块应该是独立的7个文件夹,每个模块有:项目概述、设计文档、业务规则、开发计划、每日进度、部署文档 + +### 我的回复 + +✅ **完全认同!这是非常正确的架构思路!** + +我已经创建了详细的文档重构方案: +- 📄 [文档体系重构方案](./02-文档体系重构方案.md) + +**新文档结构:** +``` +docs/ + ├── 00-系统总体设计/ # ✅ 总体独立 + │ ├── 01-系统架构分层设计.md + │ ├── 02-文档体系重构方案.md + │ ├── 03-总体需求文档(PRD).md + │ ├── 04-技术架构白皮书.md + │ └── 05-部署架构设计.md + │ + ├── 01-平台基础层/ # 平台层(用户权限、存储、通知等) + ├── 02-通用能力层/ # 通用能力(LLM网关、文档处理、RAG等) + │ + └── 03-业务模块/ # ✅ 7个模块独立 + ├── AIA-AI智能问答/ + ├── ASL-AI智能文献/ + ├── PKB-个人知识库/ + ├── DC-数据清洗整理/ + ├── SSA-智能统计分析/ + ├── ST-统计分析工具/ + └── RVW-稿件审查系统/ +``` + +**每个模块的标准结构:** +``` +XXX-模块名称/ + ├── 00-项目概述/ # PRD、功能清单、用户故事 + ├── 01-设计文档/ # 技术架构、数据库、API、前端组件、UI原型 + ├── 02-业务规则/ # 业务规则、验证规则 + ├── 03-开发计划/ # 里程碑、任务分解 + ├── 04-测试文档/ # 测试计划、测试用例 + ├── 05-部署文档/ # 部署指南、配置说明 + ├── 06-开发进度/ # 每日/每周进度(可选) + └── README.md # 模块导航 +``` + +**核心优势:** +- ✅ 总体与模块分离 +- ✅ 层次清晰(平台层、通用能力层、业务模块层) +- ✅ 支持模块独立销售(每个模块文档可独立打包) +- ✅ 易于扩展(新增模块复制模板即可) + +--- + +## 2️⃣ 不考虑混合部署 + +### 您的决策 + +> 只考虑云端、本地化(私有化)、单机版,不考虑混合部署 + +### 我的回复 + +✅ **完全赞同!混合部署技术难度极高,需求不明确,应该暂缓。** + +**调整后的部署模式:** +| 部署模式 | 目标用户 | 技术难度 | 优先级 | +|---------|---------|---------|-------| +| **云端SaaS版** | 个人用户、小机构 | ⭐⭐⭐ 中等 | P0(当前) | +| **私有化部署** | 医院、大机构 | ⭐⭐⭐⭐ 高 | P1(阶段二) | +| **单机版(Electron)** | 个人医生 | ⭐⭐⭐⭐⭐ 很高 | P2(阶段二) | +| ~~混合部署~~ | ~~高级客户~~ | ~~⭐⭐⭐⭐⭐ 极高~~ | ❌ 不考虑 | + +**简化后的好处:** +- ✅ 降低技术复杂度 +- ✅ 聚焦核心场景 +- ✅ 加快开发速度 +- ✅ 降低维护成本 + +**阶段规划:** +``` +阶段一(当前-6个月):云端SaaS版 +- 专注云端部署 +- 验证市场需求 +- 快速迭代 + +阶段二(6-18个月):私有化部署 +- 触发条件:有客户明确要求 +- 引入K8s +- Docker一键部署 + +阶段三(18个月+):单机版(可选) +- 触发条件:有大量单机版需求 +- 开发Electron应用 +- 本地化运行 +``` + +--- + +## 3️⃣ 审稿系统的独立性 + +### 您的计划 + +> 未来还会增加一个审稿系统的开发,目前已经有核心审稿功能了,可能需要独立成一个系统 + +### 我的回复 + +✅ **审稿系统非常适合独立!这是一个极具商业价值的决策!** + +**为什么审稿系统适合独立?** + +**1. 用户群完全不同** +``` +主平台用户:临床医生、研究者 +审稿系统用户:期刊编辑部、出版社、学会 + +→ 两类用户没有交集,完全独立 +``` + +**2. 业务逻辑完全独立** +``` +主平台:科研全流程(统计分析、文献筛选、数据清洗) +审稿系统:稿件审查流程(投稿、审稿、修改、决策) + +→ 业务逻辑无关联 +``` + +**3. 部署场景独立** +``` +主平台:云端SaaS为主,部分私有化 +审稿系统:期刊编辑部独立部署 + +→ 部署需求不同 +``` + +**4. 商业模式独立** +``` +主平台:按版本订阅(基础版、高级版、旗舰版) +审稿系统:按期刊订阅,或按稿件数量计费 + +→ 商业模式完全不同 +``` + +**当前状态:** +- ✅ 核心功能已实现(文档提取、规范性评估、方法学评估) +- ✅ 数据库表已独立(review_tasks) +- ⚠️ 需要扩展(审稿人管理、审稿流程、多轮审稿) + +**建议:** +``` +短期(当前): +- 审稿系统作为主平台的一个模块 +- 但在架构设计上保持独立(独立Schema、独立API) + +中期(6-12个月): +- 开发完整审稿系统(审稿人、流程、多轮审稿) +- 验证市场需求 + +长期(12个月+): +- 完全独立为单独产品"AI辅助审稿系统" +- 独立部署、独立销售 +- 目标客户:期刊编辑部、出版社 +``` + +**独立销售价值:⭐⭐⭐⭐⭐ 极高!** +- 市场空白:国内缺乏AI审稿工具 +- 刚需:期刊编辑部审稿压力大 +- 付费能力强:期刊有预算 + +--- + +## 4️⃣ 总体 vs 通用 vs 模块独立 + +### 您的核心问题 + +> 哪些是总体的?哪些是通用的技术能力?哪些是各模块独立的? +> 哪些能力是复用的?哪些技术架构可以复用? + +### 我的回复 + +这是最核心的架构问题!我已经创建了详细的架构分层设计: +- 📄 [系统架构分层设计](./01-系统架构分层设计.md) + +**三层架构总览:** + +### 第一层:平台基础层(Platform Layer) + +**定义:** 所有业务模块的地基,提供通用的基础设施能力 + +**包含模块:** +1. ✅ **用户与权限中心(UAM)** - 用户认证、权限管理、Feature Flag +2. ✅ **存储服务** - 文件上传下载、OSS/本地文件系统 +3. ✅ **通知服务** - 站内消息、邮件、WebSocket推送 +4. ✅ **监控与日志** - 操作日志、错误日志、审计日志 +5. ✅ **系统配置** - 系统级配置管理 + +**特征:** +- ✅ 全局唯一(整个平台只有一套) +- ✅ 业务无关(不涉及具体业务逻辑) +- ✅ 强依赖性(所有业务模块都必须依赖) +- ✅ 稳定性高(很少变动) + +--- + +### 第二层:通用能力层(Capability Layer) + +**定义:** 跨业务模块共享的核心技术能力 + +**包含能力:** + +#### 1. LLM大模型网关 ⭐⭐⭐⭐⭐ **最核心** + +**职责:** +- 统一管理所有LLM调用 +- 根据用户版本动态切换模型 +- 成本控制与限流 +- Token计数与计费 + +**使用模块:** +- ✅ AIA(AI智能问答) +- ✅ ASL(AI智能文献) +- ✅ PKB(个人知识库) +- ✅ DC(数据清洗) +- ✅ RVW(稿件审查) + +**复用率:** 5/7 = 71% + +**为什么是核心?** +``` +这是商业模式的技术保障: +- 基础版:只能用DeepSeek-V3(¥1/百万tokens) +- 高级版:可用DeepSeek + Qwen3 +- 旗舰版:可用DeepSeek + Qwen3 + Qwen-Long + Claude + +成本控制: +- 统一监控所有LLM API调用 +- 超出配额自动限流 +- 按版本计费 +``` + +**当前状态:** ❌ 未实现(P0优先级) + +--- + +#### 2. 文档处理引擎 ⭐⭐⭐⭐⭐ **最核心** + +**职责:** +- 多格式文档提取(PDF/Docx/Txt/Excel) +- 文本清洗与预处理 +- OCR处理 +- 表格提取 + +**使用模块:** +- ✅ ASL(文献PDF提取) +- ✅ PKB(知识库文档) +- ✅ DC(Excel/Docx导入) +- ✅ SSA(数据导入) +- ✅ ST(数据导入) +- ✅ RVW(稿件提取) + +**复用率:** 6/7 = 86% + +**当前状态:** ✅ 已实现(Python微服务) + +--- + +#### 3. RAG引擎 ⭐⭐⭐⭐ **核心** + +**职责:** +- 向量化存储(Embedding) +- 语义检索(Semantic Search) +- 检索增强生成(RAG) + +**使用模块:** +- ✅ PKB(个人知识库问答) +- ✅ ASL(文献内容检索) +- ✅ AIA(@知识库问答) + +**复用率:** 3/7 = 43% + +**当前状态:** ✅ 已实现(基于Dify) + +--- + +#### 4. 数据ETL引擎 ⭐⭐⭐ **中等** + +**职责:** +- Excel多表JOIN +- 数据清洗 +- 数据转换 + +**使用模块:** +- ✅ DC(数据清洗) +- ✅ SSA(统计分析数据预处理) + +**复用率:** 2/7 = 29% + +**当前状态:** ❌ 未实现(P2优先级) + +--- + +#### 5. 医学NLP引擎 ⭐⭐⭐ **中等** + +**职责:** +- 医学实体识别(NER) +- 医学术语标准化 + +**使用模块:** +- ✅ DC(病例数据NER提取) + +**复用率:** 1/7 = 14% + +**当前状态:** ❌ 未实现(P2优先级) + +--- + +### 第三层:业务模块层(Product Layer) + +**定义:** 面向用户的产品功能,每个模块都是独立的产品单元 + +**7个业务模块:** + +| 模块 | 名称 | 商业价值 | 独立性 | 状态 | +|------|------|---------|-------|------| +| **AIA** | AI智能问答 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ✅ 已完成 | +| **ASL** | AI智能文献 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⏳ 下一步重点 | +| **PKB** | 个人知识库 | ⭐⭐⭐ | ⭐⭐⭐ | ✅ 已完成 | +| **DC** | 数据清洗整理 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⏳ 规划中 | +| **SSA** | 智能统计分析 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⏳ 规划中 | +| **ST** | 统计分析工具 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⏳ 规划中 | +| **RVW** | 稿件审查系统 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⚡ 独立系统 | + +**特征:** +- ✅ 独立部署:可以单独打包部署 +- ✅ 独立销售:可以单独售卖 +- ✅ 低耦合:模块间不直接依赖 +- ✅ 高内聚:模块内功能完整 + +--- + +### 可复用能力总结 + +| 能力 | 使用频率 | 复用模块数 | 优先级 | 状态 | +|------|---------|-----------|--------|------| +| **LLM网关** | 71% | 5/7 | P0 | ❌ 待实现 | +| **文档处理引擎** | 86% | 6/7 | P0 | ✅ 已实现 | +| **RAG引擎** | 43% | 3/7 | P1 | ✅ 已实现 | +| **ETL引擎** | 29% | 2/7 | P2 | ❌ 待实现 | +| **医学NLP** | 14% | 1/7 | P2 | ❌ 待实现 | + +--- + +## 5️⃣ 数据库架构 + +### 您的问题 + +> 设计多Schema架构是非常必要的。 +> 目前当前系统的数据库似乎是DIFY的? +> 目前我们有自己独立的数据库吗? + +### 我的回复 + +✅ **我们有自己独立的PostgreSQL数据库,不是Dify的!** + +**当前数据库情况:** + +```typescript +// 数据源:backend/prisma/schema.prisma +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} +``` + +**当前表结构:** +``` +当前数据库(PostgreSQL,独立于Dify): +├── users # 用户表 +├── projects # 项目表 +├── conversations # 对话表(AI问答) +├── messages # 消息表 +├── knowledge_bases # 知识库表 +├── documents # 文档表 +├── admin_logs # 管理员日志 +├── general_conversations # 通用对话 +├── general_messages # 通用消息 +├── batch_tasks # 批处理任务(Phase 3) +├── batch_results # 批处理结果 +├── task_templates # 任务模板 +└── review_tasks # 稿件审查任务 + +Dify数据库(完全独立): +├── dify自己的表(不在我们的数据库) +└── 通过Dify API调用,不直接访问 +``` + +**关键澄清:** +- ✅ 我们有自己的PostgreSQL数据库 +- ✅ Dify有自己的数据库(我们不直接访问) +- ✅ 我们通过Dify API调用(HTTP REST API) +- ✅ 两个数据库完全独立 + +**但是存在的问题:** +- ❌ **所有表在同一Schema(public)** +- ❌ 未来微服务拆分困难 +- ❌ 不支持模块独立部署 + +--- + +### Schema隔离方案 + +**目标架构:** +```sql +-- 平台层Schema +CREATE SCHEMA platform_schema; + ├── users + ├── roles + ├── permissions + ├── feature_flags + ├── notifications + └── admin_logs + +-- 通用能力Schema +CREATE SCHEMA capability_schema; + ├── llm_usage_logs + ├── llm_quotas + └── document_processing_logs + +-- 业务模块Schema +CREATE SCHEMA aia_schema; -- AI智能问答 + ├── aia_projects + ├── aia_conversations + └── aia_messages + +CREATE SCHEMA asl_schema; -- AI智能文献 + ├── asl_projects + ├── asl_literature_items + ├── asl_screening_results + └── ... + +CREATE SCHEMA pkb_schema; -- 个人知识库 + ├── pkb_knowledge_bases + └── pkb_documents + +CREATE SCHEMA dc_schema; -- 数据清洗 + ├── dc_projects + ├── dc_raw_files + └── dc_cleaned_data + +CREATE SCHEMA ssa_schema; -- 智能统计分析 + ├── ssa_projects + └── ssa_analysis_tasks + +CREATE SCHEMA st_schema; -- 统计分析工具 + └── st_tool_usage + +CREATE SCHEMA review_schema; -- 稿件审查 + ├── review_tasks + ├── review_journals + ├── review_reviewers + └── review_workflows +``` + +--- + +### 迁移策略 + +**阶段一:逻辑隔离(当前阶段,立即实施)** +``` +目标:在代码层面按Schema组织,但数据库还是public +方式:Prisma中使用@@map指定表名前缀 + +例如: +model AiaProject { + id String @id @default(uuid()) + // ... + @@map("aia_projects") // 表名前缀标识模块 +} + +model AslProject { + id String @id @default(uuid()) + // ... + @@map("asl_projects") +} + +model DcProject { + id String @id @default(uuid()) + // ... + @@map("dc_projects") +} +``` + +**好处:** +- ✅ 立即可实施,无需数据迁移 +- ✅ 为未来物理隔离打基础 +- ✅ 代码层面已经按模块组织 + +--- + +**阶段二:物理隔离(微服务拆分时,阶段二)** +``` +目标:真正创建独立Schema +时机:当需要拆分某个模块为独立微服务时 + +步骤: +1. CREATE SCHEMA xxx_schema; +2. CREATE TABLE xxx_schema.table_name ... +3. INSERT INTO xxx_schema.table SELECT * FROM public.old_table; +4. 更新Prisma Schema(指定schema) +5. 测试验证 +6. 切换生产 +``` + +--- + +## 6️⃣ 下一步开发重点 + +### 您的决策 + +> 下一步着重开发的功能肯定是AI智能文献模块。 +> 当然在总体架构讨论清楚前,我们先不开发。 + +### 我的回复 + +✅ **完全赞同!AI智能文献是核心竞争力,应该是下一步重点。** + +**开发顺序建议:** + +**第一步:架构设计(本周,1-2天)** +``` +P0文档(必须完成): +1. ✅ 系统架构分层设计(已完成) +2. ✅ 文档体系重构方案(已完成) +3. ⚠️ LLM大模型网关设计(关键) +4. ⚠️ 数据库Schema隔离方案(关键) +``` + +**第二步:文档整理(本周,1-2天)** +``` +1. 调整ASL模块文档结构(按新模板) +2. 补充缺失的设计文档 +3. 明确开发里程碑 +``` + +**第三步:关键技术验证(下周,1-2天)** +``` +1. ⚠️ R语言技术验证(SSA模块需要,可延后) +2. ⚠️ LLM Gateway原型验证 +3. ⚠️ Schema隔离迁移测试 +``` + +**第四步:开始ASL模块开发(下周开始)** +``` +优先级: +- P0:标题摘要初筛(核心功能,已有原型) +- P1:全文复筛(核心功能,已有原型) +- P2:全文解析与数据提取 +- P3:数据分析与报告生成 +``` + +--- + +## 🎯 总体策略建议 + +### 核心原则 + +**1. 架构先行,文档先行** +``` +✅ 先把总体架构讨论清楚 +✅ 先把文档结构调整好 +✅ 然后再开始开发 +``` + +**2. 聚焦核心,逐步扩展** +``` +阶段一(当前-6个月): +- 云端SaaS版 +- 核心模块:ASL、DC、AIA优化 +- 关键能力:LLM网关、Schema隔离 + +阶段二(6-18个月): +- 私有化部署 +- 扩展模块:SSA、ST +- 独立系统:RVW(审稿系统) + +阶段三(18个月+): +- 单机版(可选) +- 全面微服务 +``` + +**3. 模块独立,能力复用** +``` +✅ 业务模块独立设计(低耦合) +✅ 通用能力统一提供(高复用) +✅ 支持模块独立销售 +``` + +--- + +## ✅ 立即行动清单 + +### 本周行动(P0) + +**1. 架构设计(1-2天)** +- [x] 系统架构分层设计 ✅ +- [x] 文档体系重构方案 ✅ +- [ ] LLM大模型网关设计 +- [ ] 数据库Schema隔离方案 + +**2. 文档迁移(1-2天)** +- [ ] 创建新文件夹结构 +- [ ] 迁移ASL模块文档 +- [ ] 调整文档结构(按新模板) + +--- + +### 下周行动(P1) + +**3. 关键文档补充(2-3天)** +- [ ] ASL模块缺失文档 +- [ ] LLM网关详细设计 +- [ ] RVW独立系统规划 + +**4. 开始ASL模块开发(启动)** +- [ ] 数据库表设计(asl_schema) +- [ ] API设计 +- [ ] 前端组件设计 + +--- + +## 📊 总结 + +### 您的想法非常正确! + +1. ✅ **文档系统重构**:总体独立,模块独立 +2. ✅ **不考虑混合部署**:简化技术复杂度 +3. ✅ **审稿系统独立**:极具商业价值 +4. ✅ **架构分层清晰**:平台层、通用能力层、业务模块层 +5. ✅ **Schema隔离必要**:支持模块独立和微服务拆分 +6. ✅ **ASL是下一步重点**:核心竞争力 + +### 当前最关键的工作 + +**P0(本周):** +1. 完成架构设计文档(LLM网关、Schema隔离) +2. 调整文档结构(迁移ASL模块) + +**P1(下周):** +3. 补充关键文档 +4. 开始ASL模块开发 + +### 我们不着急,先把总体思路沟通清楚 + +✅ **完全认同您的想法!** + +架构设计是地基,地基不牢,后面开发会很痛苦。 + +我们先把架构和文档梳理清楚,再开始开发。 + +--- + +**接下来您想讨论什么?** +1. LLM大模型网关的详细设计? +2. 数据库Schema隔离的具体实施? +3. ASL模块的开发计划? +4. 审稿系统的独立规划? +5. 其他架构问题? + + + + + + + + + + + + + + + diff --git a/docs/00-系统总体设计/00-阅读指南.md b/docs/00-系统总体设计/00-阅读指南.md new file mode 100644 index 00000000..847e1e89 --- /dev/null +++ b/docs/00-系统总体设计/00-阅读指南.md @@ -0,0 +1,174 @@ +# 系统总体设计 - 阅读指南 + +> **文档日期:** 2025-11-06 +> **文档目的:** 快速导航,找到您需要的文档 + +--- + +## 🎯 如果您想... + +### 快速了解今天的成果 +👉 **必读:** [00-今日架构设计总结](./00-今日架构设计总结.md) +- 7个核心文档总览 +- 关键决策总结 +- 下一步建议 + +--- + +### 了解整个系统架构 +👉 **必读:** [08-架构设计全景图](./08-架构设计全景图.md) +- 一图看懂完整架构 +- 四种部署模式 +- 代码组织结构 + +👉 **详细了解:** [01-系统架构分层设计](./01-系统架构分层设计.md) +- 三层架构详细设计 +- 8个业务模块 +- 5个通用能力 +- 依赖关系分析 + +--- + +### 做出下一步行动决策 +👉 **必读:** [99-下一步行动决策建议](./99-下一步行动决策建议.md) +- 3个方案对比(立即开发 vs 先改造 vs 折中) +- 成本收益分析 +- 明确的建议和实施计划 + +--- + +### 了解数据库架构 +👉 **先看:** [03-数据库架构说明](./03-数据库架构说明.md) +- PostgreSQL是Docker部署的 +- 您有自己独立的数据库 +- 与Dify数据库的关系 + +👉 **深入了解:** [05-Schema隔离方案与成本分析](./05-Schema隔离方案与成本分析.md) +- 逻辑隔离 vs 物理隔离 +- 改造成本对比(现在1周 vs 未来3-5周) +- 实施方案 + +--- + +### 了解如何模块独立部署 +👉 **必读:** [06-模块独立部署与单机版方案](./06-模块独立部署与单机版方案.md) +- 完整打包方案(独立产品) +- 共享服务方案(平台内模块) +- Electron单机版架构 +- 代码复用率分析(85%+) + +--- + +### 了解运营管理端 +👉 **必读:** [04-运营管理端架构设计](./04-运营管理端架构设计.md) +- 15个功能模块 +- 3阶段实施计划 +- 对系统的6大影响 + +--- + +### 了解是否需要Monorepo +👉 **必读:** [07-Monorepo架构评估](./07-Monorepo架构评估.md) +- 当前阶段需要吗? +- 成本对比(现在2-3天 vs 未来7-11天) +- 实施方案 + +--- + +### 了解文档体系如何重构 +👉 **必读:** [02-文档体系重构方案v2.0](./02-文档体系重构方案.md) +- 新文档结构设计 +- 8个模块的标准文档模板 +- 迁移计划 + +--- + +### 了解所有架构问题的答案 +👉 **必读:** [00-核心问题解答](./00-核心问题解答.md) +- 部署模式调整(3种,不考虑混合部署) +- 审稿系统独立性分析 +- 数据库架构澄清 + +--- + +## 📊 文档阅读顺序建议 + +### 路径1:快速了解(20分钟) + +1. [00-今日架构设计总结](./00-今日架构设计总结.md) - 5分钟 +2. [08-架构设计全景图](./08-架构设计全景图.md) - 10分钟 +3. [99-下一步行动决策建议](./99-下一步行动决策建议.md) - 5分钟 + +--- + +### 路径2:全面理解(1-2小时) + +1. [00-今日架构设计总结](./00-今日架构设计总结.md) - 10分钟 +2. [01-系统架构分层设计](./01-系统架构分层设计.md) - 30分钟 +3. [05-Schema隔离方案与成本分析](./05-Schema隔离方案与成本分析.md) - 20分钟 +4. [07-Monorepo架构评估](./07-Monorepo架构评估.md) - 15分钟 +5. [99-下一步行动决策建议](./99-下一步行动决策建议.md) - 10分钟 + +--- + +### 路径3:深入研究(3-4小时) + +按顺序阅读所有10个文档 + +--- + +## 🎯 关键结论速查 + +### Schema隔离 +- **建议:** 现在做 ⭐⭐⭐⭐⭐ +- **成本:** 1周 vs 未来3-5周 +- **ROI:** 300-500% + +### Monorepo +- **建议:** 现在转换 ⭐⭐⭐⭐⭐ +- **成本:** 2-3天 vs 未来7-11天 +- **ROI:** 300-400% +- **触发条件:** 近期开发运营管理端,必须转换 + +### 下一步行动 +- **方案A:** 立即开发ASL(累积技术债) +- **方案B:** 先改造架构,再开发ASL(强烈推荐)⭐⭐⭐⭐⭐ +- **方案C:** 只做Monorepo,延后Schema隔离 + +--- + +## 📝 今日成果 + +**完成的文档:** +1. ✅ 00-核心问题解答.md +2. ✅ 01-系统架构分层设计.md(900+行) +3. ✅ 02-文档体系重构方案v2.0.md(790+行) +4. ✅ 03-数据库架构说明.md(434+行) +5. ✅ 04-运营管理端架构设计.md(859+行) +6. ✅ 05-Schema隔离方案与成本分析.md(1042+行) +7. ✅ 06-模块独立部署与单机版方案.md(1541+行) +8. ✅ 07-Monorepo架构评估.md(555+行) +9. ✅ 08-架构设计全景图.md +10. ✅ 99-下一步行动决策建议.md + +**总计:** 6000+行详细设计 + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + + diff --git a/docs/00-系统总体设计/01-系统架构分层设计.md b/docs/00-系统总体设计/01-系统架构分层设计.md new file mode 100644 index 00000000..9793f6c4 --- /dev/null +++ b/docs/00-系统总体设计/01-系统架构分层设计.md @@ -0,0 +1,939 @@ +# 系统架构分层设计 + +> **文档版本:** v1.0 +> **创建日期:** 2025-11-06 +> **最后更新:** 2025-11-06 +> **文档状态:** 架构设计中 +> **作者:** 技术架构师 + +--- + +## 📋 文档说明 + +本文档是壹证循AI科研平台的**核心架构设计文档**,定义了: +1. **总体架构**:平台级的基础设施和通用能力 +2. **业务模块**:各个独立的产品模块 +3. **可复用能力**:跨模块共享的技术能力 +4. **模块独立性**:如何支持模块独立部署和销售 + +**核心设计原则:** +- ✅ **总体与模块分离**:平台基础设施与业务模块清晰分层 +- ✅ **能力可复用**:通用能力在平台层统一提供 +- ✅ **模块可独立**:任何模块都可以独立部署和销售 +- ✅ **架构可演进**:从单体到微服务平滑演进 + +--- + +## 🏗️ 架构分层总览 + +### 三层架构 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 业务模块层(Product Layer) │ +│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │ AIA │ │ ASL │ │ PKB │ │ DC │ │ SSA │ … │ +│ │智能问答│ │智能文献│ │知识库 │ │数据清洗│ │智能统计│ │ +│ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ↓ 依赖 +┌─────────────────────────────────────────────────────────────┐ +│ 通用能力层(Capability Layer) │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │LLM网关 │ │文档处理 │ │RAG引擎 │ │数据ETL │ … │ +│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ↓ 依赖 +┌─────────────────────────────────────────────────────────────┐ +│ 平台基础层(Platform Layer) │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │用户权限 │ │存储服务 │ │通知服务 │ │监控日志 │ … │ +│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 📐 第一层:平台基础层(Platform Layer) + +### 定义 + +**平台基础层是所有业务模块的地基,提供通用的基础设施能力。** + +### 核心特征 + +- ✅ **全局唯一**:整个平台只有一套 +- ✅ **业务无关**:不涉及具体业务逻辑 +- ✅ **强依赖性**:所有业务模块都必须依赖 +- ✅ **稳定性高**:很少变动 + +### 包含的模块 + +#### 1. 用户与权限中心(UAM) + +**职责:** +- 用户注册、登录、认证(JWT) +- 用户信息管理 +- 角色与权限管理(RBAC) +- 多租户管理(SaaS版) +- Feature Flag控制(版本权限) + +**数据表:** +```typescript +// 平台层Schema: platform_schema +- users // 用户基础信息 +- roles // 角色定义 +- permissions // 权限定义 +- user_roles // 用户-角色关联 +- role_permissions // 角色-权限关联 +- tenants // 租户(SaaS多租户) +- feature_flags // Feature Flag配置 +``` + +**API示例:** +``` +POST /api/auth/register // 注册 +POST /api/auth/login // 登录 +GET /api/users/me // 获取当前用户 +PUT /api/users/me // 更新用户信息 +GET /api/users/permissions // 获取用户权限 +GET /api/users/feature-flags // 获取用户Feature Flag +``` + +--- + +#### 2. 存储服务(Storage Service) + +**职责:** +- 文件上传、下载、删除 +- 对象存储(OSS/S3) +- 本地文件系统(单机版) +- 文件权限控制 + +**技术方案:** +```typescript +// 云端版:MinIO/阿里云OSS +// 单机版:本地文件系统 + +interface StorageService { + upload(file: File, path: string): Promise; // 上传,返回URL + download(url: string): Promise; // 下载 + delete(url: string): Promise; // 删除 + getSignedUrl(url: string, expiresIn: number): string; // 获取临时访问URL +} +``` + +--- + +#### 3. 通知服务(Notification Service) + +**职责:** +- 站内消息 +- 邮件通知 +- WebSocket实时推送 + +**数据表:** +```typescript +// 平台层Schema: platform_schema +- notifications // 通知记录 +- notification_templates // 通知模板 +``` + +**API示例:** +``` +GET /api/notifications // 获取通知列表 +POST /api/notifications/:id/read // 标记已读 +``` + +--- + +#### 4. 监控与日志(Monitoring & Logging) + +**职责:** +- 操作日志 +- 错误日志 +- 性能监控 +- 审计日志(合规要求) + +**数据表:** +```typescript +// 平台层Schema: platform_schema +- admin_logs // 管理员操作日志 +- error_logs // 错误日志 +- audit_logs // 审计日志(合规) +``` + +--- + +#### 5. 系统配置(System Config) + +**职责:** +- 系统级配置管理 +- 多环境配置(开发、测试、生产) +- 动态配置更新 + +**数据表:** +```typescript +// 平台层Schema: platform_schema +- system_configs // 系统配置 +``` + +--- + +## 🔧 第二层:通用能力层(Capability Layer) + +### 定义 + +**通用能力层是跨业务模块共享的核心技术能力,是业务逻辑的基础。** + +### 核心特征 + +- ✅ **可复用**:多个业务模块共享 +- ✅ **业务相关**:包含领域知识 +- ✅ **独立部署**:可以独立为微服务 +- ✅ **高内聚**:每个能力职责单一 + +### 包含的能力 + +#### 1. LLM大模型网关(LLM Gateway)⭐⭐⭐⭐⭐ **核心** + +**职责:** +- 统一管理所有LLM调用 +- 根据用户版本动态切换模型 +- 成本控制与限流 +- Token计数与计费 + +**核心价值:** +``` +这是商业模式的技术保障: +- 基础版:只能用DeepSeek-V3(便宜) +- 高级版:可用DeepSeek + Qwen3 +- 旗舰版:可用DeepSeek + Qwen3 + Qwen-Long + Claude + +成本控制: +- 统一监控所有LLM API调用 +- 超出配额自动限流 +- 按版本计费 +``` + +**技术架构:** +```typescript +// LLM Gateway统一入口 +interface LLMGateway { + // 调用LLM + chat(params: { + userId: string; + modelType?: 'deepseek-v3' | 'qwen3' | 'qwen-long' | 'claude'; + messages: Message[]; + stream?: boolean; + }): Promise; + + // 根据用户版本选择模型 + selectModel(userId: string, preferredModel?: string): string; + + // 检查配额 + checkQuota(userId: string): Promise<{ allowed: boolean; remaining: number }>; + + // Token计数 + countTokens(text: string): number; +} +``` + +**数据表:** +```typescript +// 通用能力Schema: capability_schema +- llm_usage_logs // LLM使用日志 +- llm_quotas // 用户配额 +``` + +**为什么是通用能力?** +- ✅ AIA模块需要(AI智能问答) +- ✅ ASL模块需要(AI智能文献筛选) +- ✅ PKB模块需要(知识库RAG问答) +- ✅ DC模块需要(数据清洗NER提取) +- ✅ 审稿模块需要(稿件审查) + +**所有模块都依赖LLM Gateway!** + +--- + +#### 2. 文档处理引擎(Document Processing Engine)⭐⭐⭐⭐⭐ **核心** + +**职责:** +- 多格式文档提取(PDF/Docx/Txt/Excel) +- 文本清洗与预处理 +- OCR处理 +- 表格提取 + +**技术架构:** +```typescript +// Python微服务(已实现) +interface DocumentProcessor { + // 提取文本 + extract(file: File, method: 'pymupdf' | 'nougat' | 'mammoth'): Promise<{ + text: string; + metadata: { + charCount: number; + language: string; + quality: number; + }; + }>; + + // 提取表格 + extractTables(file: File): Promise; + + // OCR处理 + ocr(image: Buffer): Promise; +} +``` + +**为什么是通用能力?** +- ✅ ASL模块需要(文献PDF提取) +- ✅ PKB模块需要(知识库文档上传) +- ✅ DC模块需要(Excel/Docx数据导入) +- ✅ 审稿模块需要(稿件文档提取) + +**至少4个模块依赖文档处理引擎!** + +--- + +#### 3. RAG引擎(RAG Engine)⭐⭐⭐⭐ **核心** + +**职责:** +- 向量化存储(Embedding) +- 语义检索(Semantic Search) +- 检索增强生成(RAG) +- Rerank重排序 + +**技术架构:** +```typescript +// 基于Dify或自建向量数据库 +interface RAGEngine { + // 创建知识库 + createDataset(name: string, description?: string): Promise; + + // 上传文档 + uploadDocument(datasetId: string, file: File): Promise; + + // 语义检索 + search(datasetId: string, query: string, topK?: number): Promise; + + // RAG问答 + chatWithRAG(datasetId: string, query: string, history?: Message[]): Promise; +} +``` + +**为什么是通用能力?** +- ✅ PKB模块需要(个人知识库问答) +- ✅ ASL模块需要(文献内容检索) +- ✅ AIA模块需要(@知识库问答) + +**至少3个模块依赖RAG引擎!** + +--- + +#### 4. 数据ETL引擎(Data ETL Engine)⭐⭐⭐ **中等** + +**职责:** +- Excel多表JOIN +- 数据清洗 +- 数据转换 +- 数据验证 + +**技术架构:** +```python +# Python微服务(基于Polars) +class ETLEngine: + # 读取多个Excel + def read_excel(self, files: List[File]) -> List[DataFrame]: + pass + + # JOIN操作 + def join(self, dfs: List[DataFrame], keys: List[str]) -> DataFrame: + pass + + # 数据清洗 + def clean(self, df: DataFrame, rules: Dict) -> DataFrame: + pass + + # 导出 + def export(self, df: DataFrame, format: str) -> bytes: + pass +``` + +**为什么是通用能力?** +- ✅ DC模块需要(数据清洗整理) +- ✅ SSA模块需要(统计分析数据预处理) + +**至少2个模块依赖ETL引擎!** + +--- + +#### 5. 医学NLP引擎(Medical NLP Engine)⭐⭐⭐ **中等** + +**职责:** +- 医学实体识别(NER) +- 医学术语标准化 +- 疾病/药物识别 + +**技术架构:** +```python +# Python微服务(基于spaCy + 医学模型) +class MedicalNLP: + # 实体识别 + def extract_entities(self, text: str) -> List[Entity]: + pass + + # 术语标准化 + def normalize(self, term: str) -> str: + pass + + # 疾病识别 + def extract_diseases(self, text: str) -> List[str]: + pass +``` + +**为什么是通用能力?** +- ✅ DC模块需要(病例数据NER提取) +- ✅ ASL模块可能需要(文献关键词提取) + +--- + +## 🎨 第三层:业务模块层(Product Layer) + +### 定义 + +**业务模块层是面向用户的产品功能,每个模块都是独立的产品单元。** + +### 核心特征 + +- ✅ **独立部署**:可以单独打包部署 +- ✅ **独立销售**:可以单独售卖 +- ✅ **低耦合**:模块间不直接依赖 +- ✅ **高内聚**:模块内功能完整 + +### 模块清单 + +| 模块代号 | 模块名称 | 核心功能 | 商业价值 | 技术栈 | 依赖的通用能力 | +|---------|---------|---------|---------|-------|--------------| +| **AIA** | AI智能问答 | 10+智能体 | ⭐⭐⭐⭐ 差异化 | Node.js + Dify | LLM网关、RAG引擎 | +| **ASL** | AI智能文献 | 文献筛选、提取 | ⭐⭐⭐⭐⭐ 核心 | Node.js + Python | LLM网关、文档处理、RAG引擎 | +| **PKB** | 个人知识库 | RAG问答 | ⭐⭐⭐ 辅助 | Node.js + Dify | LLM网关、文档处理、RAG引擎 | +| **DC** | 数据清洗整理 | 自动ETL + NER | ⭐⭐⭐⭐⭐ 核心 | Node.js + Python | LLM网关、文档处理、ETL引擎、医学NLP | +| **SSA** | 智能统计分析 | 3条分析路径 | ⭐⭐⭐⭐⭐ 刚需 | Node.js + R | 文档处理、ETL引擎 | +| **ST** | 统计分析工具 | 100+小工具 | ⭐⭐⭐⭐ 高频 | Node.js + R | 文档处理 | +| **RVW** | 稿件审查系统 | 方法学评估 | ⭐⭐⭐⭐ 独立 | Node.js + Python | LLM网关、文档处理 | + +--- + +### 业务模块详述 + +#### 1. AIA - AI智能问答模块 ✅ **已完成** + +**当前状态:** +- ✅ 10个智能体已实现 +- ✅ 多轮对话 +- ✅ 流式输出 +- ✅ 模型切换 +- ✅ @知识库问答 + +**数据Schema:** +```typescript +// 业务模块Schema: aia_schema +- projects // 项目 +- conversations // 对话 +- messages // 消息 +- agents_config // 智能体配置 +``` + +**独立部署能力:** +- ✅ 前端:可独立打包 +- ✅ 后端:可独立运行(依赖LLM网关、RAG引擎) +- ✅ 数据库:独立Schema +- ⚠️ 需要:平台层(用户认证) + +--- + +#### 2. ASL - AI智能文献模块 ⏳ **规划中** + +**核心功能:** +1. 标题摘要初筛(双模型AI判断) +2. 全文复筛(PDF全文分析) +3. 全文解析与数据提取 +4. 数据分析与报告生成 +5. 系统评价与Meta分析 +6. 文献管理 + +**数据Schema:** +```typescript +// 业务模块Schema: asl_schema +- literature_projects // 文献项目 +- literature_items // 文献条目 +- pico_configs // PICO(S)配置 +- screening_results // 筛选结果 +- screening_history // 筛选历史 +- extraction_tasks // 提取任务 +- extraction_results // 提取结果 +- meta_analysis_tasks // Meta分析任务 +``` + +**独立部署能力:** +- ✅ 前端:独立React应用 +- ✅ 后端:独立Node.js服务 +- ✅ 数据库:独立Schema +- ✅ 依赖:LLM网关、文档处理、RAG引擎 + +**独立销售价值:⭐⭐⭐⭐⭐ 非常高!** +- 可以作为独立产品"AI文献筛选系统"售卖 +- 目标客户:系统评价研究者、循证医学中心 + +--- + +#### 3. PKB - 个人知识库模块 ✅ **已完成** + +**当前状态:** +- ✅ 知识库CRUD +- ✅ 文档上传 +- ✅ RAG问答 +- ✅ @知识库引用 + +**数据Schema:** +```typescript +// 业务模块Schema: pkb_schema +- knowledge_bases // 知识库 +- documents // 文档 +``` + +**独立部署能力:** +- ✅ 前端:可独立打包 +- ✅ 后端:可独立运行 +- ✅ 数据库:独立Schema +- ✅ 依赖:LLM网关、文档处理、RAG引擎 + +--- + +#### 4. DC - 数据清洗整理模块 ⏳ **规划中** + +**核心功能:** +1. 多Excel文件导入 +2. 自动JOIN和清洗 +3. 医学NER实体提取 +4. 数据质量报告 +5. 导出标准化数据 + +**数据Schema:** +```typescript +// 业务模块Schema: dc_schema +- dc_projects // 数据清洗项目 +- dc_raw_files // 原始文件 +- dc_cleaned_data // 清洗后数据 +- dc_ner_results // NER提取结果 +- dc_quality_reports // 质量报告 +``` + +**独立部署能力:** +- ✅ 前端:独立React应用 +- ✅ 后端:Node.js + Python微服务 +- ✅ 数据库:独立Schema(SQLite for 单机版) +- ✅ 依赖:LLM网关、文档处理、ETL引擎、医学NLP + +**独立销售价值:⭐⭐⭐⭐⭐ 非常高!** +- 可以作为独立产品"医学数据清洗工具"售卖 +- 目标客户:临床科室、数据管理员 + +--- + +#### 5. SSA - 智能统计分析模块 ⏳ **规划中** + +**核心功能:** +1. 队列研究分析 +2. 预测模型构建 +3. RCT研究分析 +4. 数据质量核查 +5. 统计分析报告 + +**数据Schema:** +```typescript +// 业务模块Schema: ssa_schema +- ssa_projects // 统计分析项目 +- ssa_datasets // 数据集 +- ssa_analysis_tasks // 分析任务 +- ssa_results // 分析结果 +- ssa_reports // 分析报告 +``` + +**独立部署能力:** +- ✅ 前端:独立React应用 +- ✅ 后端:Node.js + R微服务 +- ✅ 数据库:独立Schema +- ✅ 依赖:文档处理、ETL引擎 + +**独立销售价值:⭐⭐⭐⭐⭐ 非常高!** +- 可以作为独立产品"临床统计分析平台"售卖 + +--- + +#### 6. ST - 统计分析工具模块 ⏳ **规划中** + +**核心功能:** +- 100+小工具(t检验、卡方检验、ROC曲线等) + +**数据Schema:** +```typescript +// 业务模块Schema: st_schema +- st_tool_usage // 工具使用记录 +``` + +**独立部署能力:** +- ✅ 前端:独立React应用 +- ✅ 后端:Node.js + R微服务 +- ✅ 数据库:独立Schema(可选) +- ✅ 依赖:文档处理 + +--- + +#### 7. RVW - 稿件审查系统 ⏳ **规划中(已有核心功能)** + +**当前状态:** +- ✅ 文档上传 +- ✅ 文本提取 +- ✅ 稿约规范性评估(editorial_review) +- ✅ 方法学评估(methodology_review) +- ✅ 综合评分 + +**未来扩展:** +- ⚠️ 审稿人管理 +- ⚠️ 审稿流程管理 +- ⚠️ 多轮审稿 +- ⚠️ 审稿意见模板 + +**数据Schema:** +```typescript +// 业务模块Schema: review_schema +- review_tasks // 审查任务(已有) +// 未来扩展: +- review_journals // 期刊库 +- review_reviewers // 审稿人 +- review_workflows // 审稿流程 +- review_comments // 审稿意见 +``` + +**独立部署能力:** +- ✅ 前端:独立React应用 +- ✅ 后端:独立Node.js服务 +- ✅ 数据库:独立Schema +- ✅ 依赖:LLM网关、文档处理 + +**独立销售价值:⭐⭐⭐⭐⭐ 极高!** +- 可以作为完全独立的产品"AI辅助审稿系统"售卖 +- 目标客户:期刊编辑部、出版社、学会 +- 商业模式:按期刊订阅,或按稿件数量计费 + +**为什么审稿系统适合独立?** +1. ✅ **用户群独立**:期刊编辑部 vs 临床医生(完全不同) +2. ✅ **业务逻辑独立**:与其他模块无关联 +3. ✅ **部署场景独立**:期刊编辑部有自己的部署需求 +4. ✅ **商业模式独立**:可以按期刊订阅 + +--- + +## 🔗 模块间关系 + +### 依赖关系图 + +``` +业务模块层 + ├── AIA (AI智能问答) + │ └── 依赖:LLM网关、RAG引擎 + │ + ├── ASL (AI智能文献) + │ └── 依赖:LLM网关、文档处理、RAG引擎 + │ + ├── PKB (个人知识库) + │ └── 依赖:LLM网关、文档处理、RAG引擎 + │ + ├── DC (数据清洗) + │ └── 依赖:LLM网关、文档处理、ETL引擎、医学NLP + │ + ├── SSA (智能统计分析) + │ └── 依赖:文档处理、ETL引擎 + │ + ├── ST (统计分析工具) + │ └── 依赖:文档处理 + │ + └── RVW (稿件审查) + └── 依赖:LLM网关、文档处理 + +通用能力层 + ├── LLM网关 ⭐ (所有AI模块依赖) + ├── 文档处理引擎 ⭐ (所有文档模块依赖) + ├── RAG引擎 (AIA、ASL、PKB依赖) + ├── ETL引擎 (DC、SSA依赖) + └── 医学NLP (DC依赖) + +平台基础层 + ├── 用户与权限中心 ⭐ (所有模块依赖) + ├── 存储服务 ⭐ (所有模块依赖) + ├── 通知服务 + ├── 监控与日志 + └── 系统配置 +``` + +### 关键洞察 + +**1. LLM网关是核心中枢** +``` +依赖LLM网关的模块: +- AIA(AI智能问答) +- ASL(AI智能文献) +- PKB(个人知识库) +- DC(数据清洗) +- RVW(稿件审查) + +共计:5个模块 / 7个模块 = 71% + +结论:LLM网关必须优先设计和实现! +``` + +**2. 文档处理引擎使用广泛** +``` +依赖文档处理的模块: +- ASL(文献PDF提取) +- PKB(知识库文档) +- DC(Excel/Docx导入) +- SSA(数据导入) +- ST(数据导入) +- RVW(稿件提取) + +共计:6个模块 / 7个模块 = 86% + +结论:文档处理引擎已实现,需要继续增强! +``` + +**3. 模块独立性分析** + +| 模块 | 独立性 | 原因 | +|------|-------|------| +| **RVW(审稿)** | ⭐⭐⭐⭐⭐ 极高 | 用户群、业务逻辑、部署场景都完全独立 | +| **ASL(文献)** | ⭐⭐⭐⭐⭐ 极高 | 可作为独立产品售卖给系统评价研究者 | +| **DC(数据清洗)** | ⭐⭐⭐⭐⭐ 极高 | 可作为独立产品售卖给数据管理员 | +| **SSA(统计分析)** | ⭐⭐⭐⭐ 高 | 可独立,但与ST模块有协同效应 | +| **ST(分析工具)** | ⭐⭐⭐⭐ 高 | 可独立,但与SSA模块有协同效应 | +| **AIA(AI问答)** | ⭐⭐⭐ 中 | 可独立,但与PKB有较强关联(@知识库) | +| **PKB(知识库)** | ⭐⭐⭐ 中 | 可独立,但与AIA有较强关联(@知识库) | + +--- + +## 💾 数据库架构设计 + +### Schema隔离策略 + +**当前问题:** +- ❌ 所有表在同一Schema(public) +- ❌ 未来微服务拆分困难 +- ❌ 不支持模块独立部署 + +**目标架构:** +```sql +-- 平台层Schema +CREATE SCHEMA platform_schema; + - users + - roles + - permissions + - user_roles + - role_permissions + - tenants + - feature_flags + - notifications + - admin_logs + - system_configs + +-- 通用能力Schema +CREATE SCHEMA capability_schema; + - llm_usage_logs + - llm_quotas + - document_processing_logs + +-- 业务模块Schema +CREATE SCHEMA aia_schema; -- AI智能问答 +CREATE SCHEMA asl_schema; -- AI智能文献 +CREATE SCHEMA pkb_schema; -- 个人知识库 +CREATE SCHEMA dc_schema; -- 数据清洗 +CREATE SCHEMA ssa_schema; -- 智能统计分析 +CREATE SCHEMA st_schema; -- 统计分析工具 +CREATE SCHEMA review_schema; -- 稿件审查 +``` + +### 迁移策略 + +**阶段一:逻辑隔离(当前阶段)** +``` +目标:在代码层面按Schema组织,但数据库还是public +方式:Prisma中使用@@map指定表名前缀 + +例如: +@@map("aia_projects") // AI问答的项目表 +@@map("asl_projects") // AI文献的项目表 +@@map("dc_projects") // 数据清洗的项目表 +``` + +**阶段二:物理隔离(微服务拆分时)** +``` +目标:真正创建独立Schema +方式: +1. 创建新Schema +2. 迁移数据(INSERT INTO new_schema.table SELECT * FROM public.old_table) +3. 更新Prisma Schema +4. 测试验证 +``` + +--- + +## 🎯 可复用能力总结 + +### 什么是可复用能力? + +**定义:** +- ✅ 被多个模块使用的技术能力 +- ✅ 可以独立为服务 +- ✅ 有明确的接口定义 +- ✅ 职责单一 + +### 核心可复用能力清单 + +| 能力 | 使用频率 | 复用模块 | 优先级 | 当前状态 | +|------|---------|---------|--------|---------| +| **LLM网关** | 71% (5/7) | AIA、ASL、PKB、DC、RVW | P0 | ❌ 未实现 | +| **文档处理引擎** | 86% (6/7) | 所有模块 | P0 | ✅ 已实现 | +| **RAG引擎** | 43% (3/7) | AIA、ASL、PKB | P1 | ✅ 已实现(基于Dify) | +| **ETL引擎** | 29% (2/7) | DC、SSA | P2 | ❌ 未实现 | +| **医学NLP** | 14% (1/7) | DC | P2 | ❌ 未实现 | + +### 立即行动 + +**P0能力(必须立即设计):** +1. ✅ **LLM网关** - 商业模式的基础,5个模块依赖 +2. ✅ **文档处理引擎** - 已实现,需要增强(表格提取) + +**P1能力(近期设计):** +3. ✅ **RAG引擎** - 已实现,需要优化 +4. ⚠️ **ETL引擎** - DC模块开发时再设计 + +**P2能力(延后):** +5. ⚠️ **医学NLP** - DC模块开发时再设计 + +--- + +## 🚀 架构演进路径 + +### 阶段一:模块化单体(当前 - 6个月) + +**架构特点:** +``` +单一代码库,但严格按模块划分: +backend/ + ├── platform/ # 平台层 + │ ├── auth/ + │ ├── storage/ + │ └── notification/ + ├── capabilities/ # 通用能力层 + │ ├── llm-gateway/ + │ ├── document/ + │ └── rag/ + ├── modules/ # 业务模块层 + │ ├── aia/ + │ ├── asl/ + │ ├── pkb/ + │ ├── dc/ + │ ├── ssa/ + │ ├── st/ + │ └── review/ + └── shared/ # 共享工具 + +单一数据库,但逻辑隔离(表名前缀) +``` + +**关键纪律:** +- ✅ 模块间不直接import,只能通过接口调用 +- ✅ 每个模块有独立的路由、服务、数据访问层 +- ✅ 使用表名前缀(逻辑Schema隔离) + +--- + +### 阶段二:首次拆分(6-18个月) + +**触发条件:** +- 有客户要求私有化部署 +- 有客户要求单机版 +- 需要独立销售某个模块 + +**拆分策略:** +``` +1. 优先拆分RVW(审稿系统)- 最独立 +2. 其次拆分DC或ASL - 商业价值高 +3. 引入API网关 +4. 引入K8s(可选,私有化部署需要) +``` + +**架构:** +``` +API网关 (Kong/Traefik) + ├── 平台服务 (UAM、Storage) + ├── LLM网关服务 + ├── 文档处理服务 (Python微服务) + ├── 审稿模块服务 (独立部署) + └── 其他模块服务 (暂时仍为单体) +``` + +--- + +### 阶段三:全面微服务(18个月+) + +**架构:** +``` +所有模块独立部署,支持灵活组合和模块化售卖 +``` + +--- + +## 📊 总结 + +### 架构分层回答了您的关键问题 + +**1. 哪些是总体的?** +- ✅ 平台基础层:用户权限、存储、通知、监控、配置 + +**2. 哪些是通用的技术能力?** +- ✅ 通用能力层:LLM网关、文档处理、RAG引擎、ETL引擎、医学NLP + +**3. 哪些是各子模块独立的功能?** +- ✅ 业务模块层:AIA、ASL、PKB、DC、SSA、ST、RVW + +**4. 哪些能力是复用的?** +- ✅ LLM网关(5个模块) +- ✅ 文档处理(6个模块) +- ✅ RAG引擎(3个模块) + +**5. 哪些可以随时独立成系统?** +- ⭐⭐⭐⭐⭐ **RVW(审稿系统)** - 最适合独立 +- ⭐⭐⭐⭐⭐ **ASL(智能文献)** - 商业价值高 +- ⭐⭐⭐⭐⭐ **DC(数据清洗)** - 商业价值高 + +--- + +**下一步:基于这个架构分层,重新组织文档结构。** + + + + + + + + + + + + + + + diff --git a/docs/00-系统总体设计/02-文档体系重构方案.md b/docs/00-系统总体设计/02-文档体系重构方案.md new file mode 100644 index 00000000..ef8fe2c6 --- /dev/null +++ b/docs/00-系统总体设计/02-文档体系重构方案.md @@ -0,0 +1,1480 @@ +# 文档体系重构方案 + +> **文档版本:** v3.0 +> **创建日期:** 2025-11-06 +> **最后更新:** 2025-11-06 +> **文档状态:** 待实施 +> **作者:** 技术架构师 +> +> **更新说明:** +> - v3.0: 新增文档分层原则、快速上下文体系、详细执行计划 +> - v2.0: 新增运营管理端、多种部署方案、Monorepo架构相关文档 + +--- + +## 📋 文档重构背景 + +### 当前问题 + +**现有文档结构:** +``` +AIclinicalresearch/docs/ + ├── 00-项目概述/ + ├── 01-设计文档/ + ├── 02-开发规范/ + ├── 03-业务规则/ + ├── 04-开发计划/ + ├── 05-每日进度/ + └── AI智能文献/ +``` + +**存在的问题:** +1. ❌ **总体与模块混杂**:系统总体设计与具体模块文档混在一起 +2. ❌ **层次不清晰**:无法区分平台层、通用能力层、业务模块层 +3. ❌ **模块文档分散**:AI智能文献有独立文件夹,其他模块没有 +4. ❌ **难以扩展**:新增模块时,文档结构混乱 +5. ❌ **不支持独立销售**:无法快速打包某个模块的完整文档 +6. ❌ **规范与设计混杂**:数据库设计规范和具体设计文档放在一起 +7. ❌ **缺少快速上下文**:新AI对接时需要阅读大量文档,Token消耗高 +8. ❌ **文档查找困难**:没有清晰的导航和分层 + +--- + +## 🎯 重构目标 + +### 核心原则 + +1. ✅ **总体与模块分离**:系统总体设计独立于业务模块 +2. ✅ **层次清晰**:平台层、通用能力层、业务模块层分开 +3. ✅ **模块完整**:每个模块有完整的文档结构(需求、设计、开发、测试、部署) +4. ✅ **易于扩展**:新增模块只需复制文档模板 +5. ✅ **支持独立销售**:每个模块的文档可以独立打包 +6. ✅ **规范与设计分离**:设计规范统一管理,具体设计按模块组织 +7. ✅ **快速上下文优先**:每个层级都有快速上下文文档,降低AI对接成本 +8. ✅ **金字塔式文档**:从简到详,按需深入 + +--- + +## 🏗️ 新文档结构设计 + +### 总体结构 + +``` +AIclinicalresearch/ + ├── docs/ + │ │ + │ ├── 📁 00-系统总体设计/ # 【新增】总体层面 + │ │ ├── 00-核心问题解答.md # ✅ 已创建 + │ │ ├── 01-系统架构分层设计.md # ✅ 已创建 + │ │ ├── 02-文档体系重构方案.md # ✅ 当前文档 + │ │ ├── 03-数据库架构说明.md # ✅ 已创建 + │ │ ├── 04-运营管理端架构设计.md # ✅ 已创建(v2.0新增) + │ │ ├── 05-Schema隔离方案与成本分析.md # ✅ 已创建(v2.0新增) + │ │ ├── 06-模块独立部署与单机版方案.md # ✅ 已创建(v2.0新增) + │ │ ├── 07-总体需求文档(PRD).md # ⏳ 待迁移 + │ │ ├── 08-技术架构白皮书.md # ⏳ 待迁移 + │ │ ├── 09-商业模式设计.md # ⏳ 待创建 + │ │ ├── 10-版本规划.md # ⏳ 待创建 + │ │ └── README.md # ✅ 已创建 + │ │ + │ ├── 📁 01-平台基础层/ # 【新增】平台层 + │ │ ├── 01-用户与权限中心(UAM)/ + │ │ ├── 02-存储服务/ + │ │ ├── 03-通知服务/ + │ │ ├── 04-监控与日志/ + │ │ ├── 05-系统配置/ + │ │ └── README.md + │ │ + │ ├── 📁 02-通用能力层/ # 【新增】通用能力层 + │ │ ├── 01-LLM大模型网关/ # P0优先级 + │ │ ├── 02-文档处理引擎/ + │ │ ├── 03-RAG引擎/ + │ │ ├── 04-数据ETL引擎/ + │ │ ├── 05-医学NLP引擎/ + │ │ └── README.md + │ │ + │ ├── 📁 03-业务模块/ # 【新增】业务模块层 + │ │ │ + │ │ ├── 📁 AIA-AI智能问答/ # ✅ 已完成 + │ │ ├── 📁 ASL-AI智能文献/ # ⏳ 下一步重点 + │ │ ├── 📁 PKB-个人知识库/ # ✅ 已完成 + │ │ ├── 📁 DC-数据清洗整理/ # ⏳ 规划中 + │ │ ├── 📁 SSA-智能统计分析/ # ⏳ 规划中 + │ │ ├── 📁 ST-统计分析工具/ # ⏳ 规划中 + │ │ ├── 📁 RVW-稿件审查系统/ # ⚡ 独立系统 + │ │ ├── 📁 ADMIN-运营管理端/ # ⭐ v2.0新增(独立系统) + │ │ │ + │ │ └── README.md # 业务模块导航 + │ │ + │ ├── 📁 04-开发规范/ # 保留 + │ │ ├── 代码规范.md + │ │ ├── Git规范.md + │ │ └── README.md + │ │ + │ ├── 📁 05-部署文档/ # 【扩展】部署相关(v2.0扩展) + │ │ ├── 01-云端SaaS部署/ # ⭐ v2.0扩展 + │ │ │ ├── 01-完整平台部署.md + │ │ │ ├── 02-微服务架构部署.md + │ │ │ ├── 03-K8s部署指南.md + │ │ │ └── README.md + │ │ │ + │ │ ├── 02-独立产品包部署/ # ⭐ v2.0新增 + │ │ │ ├── 01-审稿系统独立部署.md + │ │ │ ├── 02-AI文献系统独立部署.md + │ │ │ ├── 03-Docker打包指南.md + │ │ │ └── README.md + │ │ │ + │ │ ├── 03-Electron单机版/ # ⭐ v2.0新增 + │ │ │ ├── 01-单机版架构设计.md + │ │ │ ├── 02-打包配置指南.md + │ │ │ ├── 03-跨平台适配.md + │ │ │ ├── 04-自动更新方案.md + │ │ │ └── README.md + │ │ │ + │ │ ├── 04-私有化部署/ + │ │ │ ├── 01-Docker部署指南.md + │ │ │ ├── 02-K3s轻量级部署.md + │ │ │ ├── 03-一键部署脚本.md + │ │ │ └── README.md + │ │ │ + │ │ └── README.md # 部署文档总导航 + │ │ + │ ├── 📁 06-测试文档/ # 【新增】 + │ │ ├── 01-测试策略.md + │ │ ├── 02-自动化测试.md + │ │ ├── 03-性能测试.md + │ │ └── README.md + │ │ + │ ├── 📁 07-运维文档/ # 【新增】 + │ │ ├── 01-监控告警.md + │ │ ├── 02-故障排查.md + │ │ ├── 03-备份恢复.md + │ │ └── README.md + │ │ + │ ├── 📁 08-项目管理/ # 【调整】项目管理 + │ │ ├── 01-整体开发计划.md + │ │ ├── 02-里程碑规划.md + │ │ ├── 03-每日进度/ # 保留原有每日进度 + │ │ └── README.md + │ │ + │ ├── 📁 09-架构实施/ # ⭐ v2.0新增 + │ │ ├── 01-Monorepo架构设计/ + │ │ │ ├── 01-Monorepo总体设计.md + │ │ │ ├── 02-包管理策略.md + │ │ │ ├── 03-代码共享与复用.md + │ │ │ └── README.md + │ │ │ + │ │ ├── 02-产品打包方案/ + │ │ │ ├── 01-独立产品打包流程.md + │ │ │ ├── 02-依赖管理.md + │ │ │ ├── 03-构建脚本.md + │ │ │ └── README.md + │ │ │ + │ │ ├── 03-微服务拆分/ + │ │ │ ├── 01-拆分策略.md + │ │ │ ├── 02-服务间通信.md + │ │ │ ├── 03-API网关配置.md + │ │ │ └── README.md + │ │ │ + │ │ └── README.md + │ │ + │ └── README.md # 文档总导航 + │ + └── backend/ + └── frontend/ + └── ... +``` + +--- + +## 📐 文档分层原则 ⭐ **v3.0新增** + +### 核心问题:规范文档 vs 具体设计文档 + +在文档重构过程中,我们发现一个关键问题:**数据库设计文档、API设计规范等文档应该放在哪里?** + +### 解决方案:明确区分规范和设计 + +| 文档类型 | 位置 | 说明 | 示例 | +|---------|------|------|------| +| **全局规范** | `04-开发规范/` | 告诉大家"怎么做" | 数据库命名规范、API设计规范、代码规范 | +| **总体架构** | `00-系统总体设计/` | 整体视角 | Schema结构总览、部署架构、技术选型 | +| **平台层设计** | `01-平台基础层/` | 平台级服务的具体设计 | UAM的数据库设计、存储服务API | +| **通用能力设计** | `02-通用能力层/` | 可复用能力的具体设计 | LLM网关设计、文档处理设计 | +| **业务模块设计** | `03-业务模块/` | 各模块的具体设计 | ASL的数据库设计、AIA的API设计 | + +### 示例:数据库设计文档处理 + +**当前问题:** `01-设计文档/数据库设计文档.md`(1319行)包含所有模块的表设计 + +**重构方案:** 拆分为4部分 + +``` +┌─────────────────────────────────────────────────┐ +│ 1. 规范文档(告诉大家怎么设计) │ +│ 04-开发规范/03-数据库设计规范.md │ +│ - 表命名规范(模块前缀_表名) │ +│ - 字段类型规范(统一使用UUID) │ +│ - 索引设计原则 │ +│ - Schema隔离规范 │ +└─────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────┐ +│ 2. 总体架构文档(整体视角) │ +│ 00-系统总体设计/03-数据库架构说明.md (已有) │ +│ - Schema结构总览 │ +│ - 表关系图 │ +│ - 数据流向 │ +└─────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────┐ +│ 3. 平台层数据库设计(平台级服务) │ +│ 01-平台基础层/01-用户与权限中心(UAM)/ │ +│ └─ 01-设计文档/02-数据库设计.md │ +│ - platform_schema的表设计 │ +│ - users, roles, permissions等 │ +└─────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────┐ +│ 4. 各业务模块数据库设计(模块具体设计) │ +│ 03-业务模块/AIA-AI智能问答/01-设计文档/ │ +│ └─ 02-数据库设计.md │ +│ - aia_schema的表设计 │ +│ │ +│ 03-业务模块/ASL-AI智能文献/01-设计文档/ │ +│ └─ 02-数据库设计.md │ +│ - asl_schema的表设计 │ +│ ... │ +└─────────────────────────────────────────────────┘ +``` + +### 示例:API设计规范处理 + +**重构方案:** + +``` +┌─────────────────────────────────────────────────┐ +│ 规范文档(全局标准) │ +│ 04-开发规范/02-API设计规范.md │ +│ - RESTful规范(GET/POST/PUT/DELETE) │ +│ - 错误码规范(200/400/500) │ +│ - 认证规范(JWT Token) │ +│ - 版本控制(/api/v1/) │ +│ - 分页规范(page/pageSize) │ +└─────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────┐ +│ 各模块具体API设计(具体实现) │ +│ 03-业务模块/AIA-AI智能问答/01-设计文档/ │ +│ └─ 03-API设计.md │ +│ - POST /api/v1/projects │ +│ - GET /api/v1/conversations/:id │ +│ ... │ +│ │ +│ 03-业务模块/ASL-AI智能文献/01-设计文档/ │ +│ └─ 03-API设计.md │ +│ - POST /api/v1/literature/projects │ +│ - POST /api/v1/literature/screening │ +│ ... │ +└─────────────────────────────────────────────────┘ +``` + +### 规范文档清单(`04-开发规范/`) + +**需要创建的规范文档:** + +1. ✅ **01-代码规范.md**(已有) + - TypeScript代码规范 + - 命名规范 + - 注释规范 + +2. ⏳ **02-API设计规范.md**(待创建) + - RESTful规范 + - 错误码规范 + - 认证规范 + - 版本控制 + - 分页规范 + +3. ⏳ **03-数据库设计规范.md**(待创建) + - 表命名规范 + - 字段类型规范 + - 索引设计原则 + - Schema隔离规范 + - 外键规范 + +4. ⏳ **04-前端组件设计规范.md**(待创建) + - 组件命名规范 + - 目录结构规范 + - Props设计规范 + - 状态管理规范 + +5. ⏳ **05-Git规范.md**(待创建) + - 分支管理规范 + - Commit Message规范 + - PR规范 + +### 好处 + +✅ **规范统一**:所有模块遵循同一套规范 +✅ **模块独立**:每个模块的设计文档完整、可独立 +✅ **易于维护**:修改某个模块不影响其他模块 +✅ **支持独立部署**:模块打包时带上完整文档 +✅ **新人友好**:先学规范,再看具体设计 + +--- + +## 🚀 快速上下文体系 ⭐ **v3.0新增** + +### 核心问题:如何让新AI快速了解项目? + +**痛点:** +- ❌ 每次新对话都需要阅读大量文档 +- ❌ Token消耗高(完整文档可能5K-10K tokens) +- ❌ 无法快速定位到具体模块 +- ❌ 缺少分层的上下文导航 + +### 解决方案:金字塔式快速上下文 + +``` + ┌─────────────────────┐ + │ 30秒上下文 (L0) │ ← 1页,800 tokens + │ 总体快速上下文 │ 所有对话必读 + └─────────────────────┘ + ↓ 需要更多细节 + ┌─────────────────────┐ + │ 3分钟上下文 (L1) │ ← 3页,1500 tokens + │ 层级快速上下文 │ 平台层/能力层/模块层 + └─────────────────────┘ + ↓ 需要开发细节 + ┌─────────────────────┐ + │ 5分钟上下文 (L2) │ ← 5页,2000 tokens + │ 模块快速上下文 │ 具体模块开发 + └─────────────────────┘ + ↓ 需要完整信息 + ┌─────────────────────┐ + │ 完整文档体系 │ ← 完整PRD和设计文档 + └─────────────────────┘ +``` + +### 快速上下文文档布局 + +``` +docs/ + │ + ├── 00-系统总体设计/ + │ └── [AI对接] 快速上下文.md ⭐ L0:30秒-2分钟(800 tokens) + │ - 一句话描述项目 + │ - 8个业务模块状态 + │ - 关键架构决策 + │ - 快速跳转指引 + │ + ├── 01-平台基础层/ + │ ├── [AI对接] 平台层快速上下文.md ⭐ L1:2-3分钟(1500 tokens) + │ └── 01-用户与权限中心(UAM)/ + │ └── [AI对接] UAM快速上下文.md ⭐ L2:1-2分钟(800 tokens) + │ + ├── 02-通用能力层/ + │ ├── [AI对接] 通用能力快速上下文.md ⭐ L1:2-3分钟(1500 tokens) + │ └── 01-LLM大模型网关/ + │ └── [AI对接] LLM网关快速上下文.md ⭐ L2:1-2分钟(800 tokens) + │ + └── 03-业务模块/ + ├── [AI对接] 业务模块快速上下文.md ⭐ L1:2-3分钟(1500 tokens) + └── ASL-AI智能文献/ + └── [AI对接] ASL快速上下文.md ⭐ L2:3-5分钟(2000 tokens) +``` + +### 快速上下文内容标准 + +#### L0 - 总体快速上下文(必读) + +**位置:** `00-系统总体设计/[AI对接] 快速上下文.md` + +**内容结构:** +```markdown +# [AI对接] 快速上下文 + +> **阅读时间:** 2分钟 | **Token消耗:** ~800 tokens + +## 一句话描述 +[项目核心定位] + +## 核心信息卡片 +- 项目状态 +- 8个业务模块(优先级) +- 关键架构决策 +- 代码位置 +- 核心依赖 + +## 快速跳转(根据任务选择) +- 开发ASL模块 → [链接] +- 了解架构设计 → [链接] +- 了解数据库 → [链接] + +## 常见任务快速指引 +[任务类型 → 需要阅读的文档 → Token消耗] +``` + +#### L1 - 层级快速上下文 + +**位置:** 各大层级根目录 + +**内容:** 该层级的所有模块概览、依赖关系、快速导航 + +#### L2 - 模块快速上下文 + +**位置:** 具体模块根目录 + +**内容:** 模块定位、核心功能、技术架构、业务流程、开发计划 + +### 使用策略 + +**场景1:首次对话,完全不了解项目** +``` +阅读顺序: +1. [必读] 00-系统总体设计/[AI对接] 快速上下文.md (~800 tokens,2分钟) +→ 现在可以开始对话了 +``` + +**场景2:开发ASL模块** +``` +阅读顺序: +1. [必读] 00-系统总体设计/[AI对接] 快速上下文.md (~800 tokens) +2. [必读] 03-业务模块/ASL-AI智能文献/[AI对接] ASL快速上下文.md (~2000 tokens) +3. [可选] 如果需要LLM网关细节 → LLM网关快速上下文 (~800 tokens) +总计:~3600 tokens,5-8分钟 +``` + +**场景3:架构讨论** +``` +阅读顺序: +1. [必读] 00-系统总体设计/[AI对接] 快速上下文.md (~800 tokens) +2. [必读] 00-系统总体设计/08-架构设计全景图.md (~3000 tokens) +3. [可选] 根据讨论方向选择具体文档 +总计:~4000-8000 tokens,10-15分钟 +``` + +### Token消耗对比 + +| 方式 | Token消耗 | 阅读时间 | 覆盖范围 | +|-----|----------|---------|---------| +| **传统方式**(读完整PRD) | 5K-10K | 20-30分钟 | 单个模块 | +| **快速上下文L0** | 800 | 2分钟 | 项目全貌 | +| **快速上下文L0+L2** | 2.8K | 5-8分钟 | 项目+具体模块 | +| **节省** | **50-70%** | **70-80%** | - | + +### 实施要求 + +**在文档重构时,同步创建快速上下文:** + +1. ✅ **L0 - 总体快速上下文**(已存在,需更新) + - `00-系统总体设计/[AI对接] 快速上下文.md` + +2. ⏳ **L1 - 层级快速上下文**(新创建) + - `01-平台基础层/[AI对接] 平台层快速上下文.md` + - `02-通用能力层/[AI对接] 通用能力快速上下文.md` + - `03-业务模块/[AI对接] 业务模块快速上下文.md` + +3. ⏳ **L2 - 模块快速上下文**(随模块创建) + - ASL模块创建时,同步创建快速上下文 + - LLM网关设计时,同步创建快速上下文 + +4. ⏳ **标准模板**(创建模板文件) + - `_templates/[AI对接] 模块快速上下文-模板.md` + +--- + +## 📂 业务模块文档结构(标准模板) + +### 每个业务模块的标准结构 + +``` +03-业务模块/ + └── XXX-模块名称/ + │ + ├── 📁 00-项目概述/ + │ ├── 00-模块导航.md # 模块内文档导航 + │ ├── 01-产品需求文档(PRD).md # 产品需求 + │ ├── 02-核心功能清单.md # 功能列表 + │ ├── 03-用户故事.md # 用户故事 + │ ├── 04-非功能需求.md # 性能、安全等 + │ └── README.md + │ + ├── 📁 01-设计文档/ + │ ├── 01-技术架构设计.md # 模块技术架构 + │ ├── 02-数据库设计.md # Schema、表结构 + │ ├── 03-API设计.md # RESTful API + │ ├── 04-前端组件设计.md # UI组件 + │ ├── 05-AI模型集成设计.md # AI相关(如需要) + │ ├── 06-UI原型/ # 原型图 + │ │ ├── xxx原型.html + │ │ └── ... + │ └── README.md + │ + ├── 📁 02-业务规则/ + │ ├── 01-核心业务规则.md + │ ├── 02-数据验证规则.md + │ ├── 03-权限规则.md + │ └── README.md + │ + ├── 📁 03-开发计划/ + │ ├── 01-开发里程碑.md + │ ├── 02-任务分解.md + │ ├── 03-技术难点与风险.md + │ └── README.md + │ + ├── 📁 04-测试文档/ + │ ├── 01-测试计划.md + │ ├── 02-测试用例.md + │ ├── 03-测试报告.md + │ └── README.md + │ + ├── 📁 05-部署文档/ + │ ├── 01-部署指南.md + │ ├── 02-配置说明.md + │ ├── 03-常见问题.md + │ └── README.md + │ + ├── 📁 06-开发进度/ # 可选 + │ ├── Week-01.md + │ ├── Week-02.md + │ └── README.md + │ + └── README.md # 模块总导航 +``` + +--- + +## 🎯 具体模块文档规划 + +### 1. AIA - AI智能问答模块 ✅ **已完成** + +**当前状态:** 功能已完成,需要补充文档 + +**文档清单:** +``` +03-业务模块/AIA-AI智能问答/ + ├── 00-项目概述/ + │ ├── 01-产品需求文档(PRD).md # ⚠️ 需要补充 + │ ├── 02-核心功能清单.md # ✅ 可基于现有文档整理 + │ └── README.md + ├── 01-设计文档/ + │ ├── 01-技术架构设计.md # ✅ 可基于现有文档整理 + │ ├── 02-数据库设计.md # ✅ 已有(基于Prisma Schema) + │ ├── 03-API设计.md # ✅ 已有(基于API规范) + │ └── 04-前端组件设计.md # ⚠️ 需要补充 + ├── 02-业务规则/ + │ └── 01-核心业务规则.md # ⚠️ 需要补充 + ├── 03-开发计划/ + │ ├── 01-开发里程碑.md # ✅ 已完成(里程碑1、1.5) + │ └── README.md + └── README.md +``` + +**优先级:** P2(已完成,文档补充不急) + +--- + +### 2. ASL - AI智能文献模块 ⏳ **下一步重点** + +**当前状态:** 已有PRD和原型,即将开发 + +**文档清单:** +``` +03-业务模块/ASL-AI智能文献/ + ├── 00-项目概述/ + │ ├── 01-产品需求文档(PRD).md # ✅ 已有(3个PRD文档) + │ ├── 02-核心功能清单.md # ✅ 已有(6大模块) + │ ├── 03-用户故事.md # ⚠️ 需要补充 + │ └── README.md + ├── 01-设计文档/ + │ ├── 01-技术架构设计.md # ✅ 已有(系统架构设计) + │ ├── 02-数据库设计.md # ✅ 已有(数据库设计) + │ ├── 03-API设计.md # ✅ 已有(API设计) + │ ├── 04-前端组件设计.md # ✅ 已有(前端组件设计) + │ ├── 05-AI模型集成设计.md # ✅ 已有(AI模型集成设计) + │ └── 06-UI原型/ + │ ├── 标题摘要初筛原型.html # ✅ 已有 + │ └── 全文复筛原型.html # ✅ 已有 + ├── 02-业务规则/ + │ └── 01-核心业务规则.md # ⚠️ 需要补充 + ├── 03-开发计划/ + │ ├── 01-开发里程碑.md # ✅ 已有 + │ ├── 02-任务分解.md # ✅ 已有 + │ └── README.md + ├── 04-测试文档/ + │ ├── 01-测试计划.md # ✅ 已有 + │ ├── 02-测试用例.md # ✅ 已有(标题摘要初筛) + │ └── README.md + └── README.md +``` + +**优先级:** P0(下一步开发重点) + +**特点:** +- ✅ 文档最完整(已有AI智能文献/文件夹) +- ✅ 可以直接作为模板 +- ⚠️ 需要调整结构(按新模板重新组织) + +--- + +### 3. PKB - 个人知识库模块 ✅ **已完成** + +**当前状态:** 功能已完成,需要补充文档 + +**文档清单:** +``` +03-业务模块/PKB-个人知识库/ + ├── 00-项目概述/ + │ ├── 01-产品需求文档(PRD).md # ⚠️ 需要补充 + │ └── README.md + ├── 01-设计文档/ + │ ├── 01-技术架构设计.md # ⚠️ 需要补充 + │ ├── 02-数据库设计.md # ✅ 已有(基于Prisma Schema) + │ ├── 03-API设计.md # ✅ 已有(基于API规范) + │ └── README.md + └── README.md +``` + +**优先级:** P2(已完成,文档补充不急) + +--- + +### 4. DC - 数据清洗整理模块 ⏳ **规划中** + +**当前状态:** 未开发,需要完整设计 + +**文档清单:** +``` +03-业务模块/DC-数据清洗整理/ + ├── 00-项目概述/ + │ ├── 01-产品需求文档(PRD).md # ❌ 待创建 + │ ├── 02-核心功能清单.md # ❌ 待创建 + │ ├── 03-用户故事.md # ❌ 待创建 + │ └── README.md + ├── 01-设计文档/ + │ ├── 01-技术架构设计.md # ❌ 待创建 + │ ├── 02-数据库设计.md # ❌ 待创建 + │ ├── 03-API设计.md # ❌ 待创建 + │ ├── 04-前端组件设计.md # ❌ 待创建 + │ ├── 05-ETL引擎设计.md # ❌ 待创建(关键) + │ ├── 06-医学NLP设计.md # ❌ 待创建(关键) + │ └── README.md + ├── 02-业务规则/ + │ └── 01-核心业务规则.md # ❌ 待创建 + ├── 03-开发计划/ + │ ├── 01-开发里程碑.md # ❌ 待创建 + │ └── README.md + └── README.md +``` + +**优先级:** P1(核心竞争力,ASL之后开发) + +--- + +### 5. SSA - 智能统计分析模块 ⏳ **规划中** + +**当前状态:** 未开发,需要完整设计 + +**文档清单:** +``` +03-业务模块/SSA-智能统计分析/ + ├── 00-项目概述/ + │ ├── 01-产品需求文档(PRD).md # ❌ 待创建 + │ └── README.md + ├── 01-设计文档/ + │ ├── 01-技术架构设计.md # ❌ 待创建 + │ ├── 02-R语言集成设计.md # ❌ 待创建(关键) + │ ├── 03-数据库设计.md # ❌ 待创建 + │ └── README.md + └── README.md +``` + +**优先级:** P2(需要R语言,有一定难度) + +--- + +### 6. ST - 统计分析工具模块 ⏳ **规划中** + +**当前状态:** 未开发,需要完整设计 + +**文档清单:** +``` +03-业务模块/ST-统计分析工具/ + ├── 00-项目概述/ + │ ├── 01-产品需求文档(PRD).md # ❌ 待创建 + │ └── README.md + ├── 01-设计文档/ + │ ├── 01-技术架构设计.md # ❌ 待创建 + │ ├── 02-工具清单.md # ❌ 待创建(100+工具) + │ └── README.md + └── README.md +``` + +**优先级:** P3(相对简单,可延后) + +--- + +### 7. RVW - 稿件审查系统 ⚡ **独立系统** + +**当前状态:** 核心功能已完成,需要独立规划 + +**文档清单:** +``` +03-业务模块/RVW-稿件审查系统/ + ├── 00-项目概述/ + │ ├── 01-产品需求文档(PRD).md # ❌ 待创建 + │ ├── 02-独立系统规划.md # ❌ 待创建(关键) + │ ├── 03-商业模式设计.md # ❌ 待创建(独立销售) + │ └── README.md + ├── 01-设计文档/ + │ ├── 01-技术架构设计.md # ⚠️ 需要补充 + │ ├── 02-数据库设计.md # ✅ 已有(基于Prisma Schema) + │ ├── 03-审稿流程设计.md # ❌ 待创建 + │ ├── 04-审稿人管理设计.md # ❌ 待创建 + │ └── README.md + ├── 02-业务规则/ + │ ├── 01-稿约规范性评估标准.md # ✅ 已有 + │ ├── 02-方法学评估标准.md # ✅ 已有 + │ └── README.md + └── README.md +``` + +**优先级:** P1(独立系统,高商业价值) + +**特点:** +- ⚡ 可以完全独立部署和销售 +- ⚡ 目标客户:期刊编辑部、出版社 +- ⚡ 商业模式:按期刊订阅 + +--- + +### 8. ADMIN - 运营管理端 ⭐ **v2.0新增(独立系统)** + +**定位:** 横跨所有层次的管理系统 + +**核心功能模块(15个):** +``` +P0(必须,阶段一): +1. 用户管理 +2. Feature Flag管理(版本功能控制) +3. LLM模型管理 +4. 系统配置管理 + +P1(重要,阶段二): +5. 智能体提示词管理 +6. 监控与日志 +7. 数据统计与报表 +8. 成本分析与计费 + +P2(有用,阶段三): +9. 租户管理(私有化部署) +10. 公告与通知管理 +11. 帮助文档管理 +12. 反馈与工单管理 +13. 系统健康检查 +14. 数据库备份与恢复 +15. 运营数据分析 +``` + +**文档清单:** +``` +03-业务模块/ADMIN-运营管理端/ + ├── 00-项目概述/ + │ ├── 01-产品需求文档(PRD).md # ❌ 待创建 + │ ├── 02-功能清单(15个模块).md # ❌ 待创建 + │ ├── 03-权限体系设计.md # ❌ 待创建 + │ └── README.md + ├── 01-设计文档/ + │ ├── 01-技术架构设计.md # ❌ 待创建 + │ ├── 02-数据库设计.md # ❌ 待创建 + │ ├── 03-API设计.md # ❌ 待创建 + │ ├── 04-前端组件设计.md # ❌ 待创建 + │ ├── 05-权限体系实现.md # ❌ 待创建 + │ └── README.md + ├── 02-业务规则/ + │ ├── 01-权限规则.md # ❌ 待创建 + │ ├── 02-审计规则.md # ❌ 待创建 + │ └── README.md + ├── 03-开发计划/ + │ ├── 01-开发里程碑.md # ❌ 待创建 + │ ├── 02-任务分解(按阶段).md # ❌ 待创建 + │ └── README.md + └── README.md +``` + +**优先级:** P1(商业模式基础) + +**技术选型:** +- 前端:React + Ant Design Pro +- 后端:Node.js + Fastify +- 数据库:PostgreSQL(admin_schema) + +**部署方式:** +- 独立域名:`https://admin.yizhengxun.com` +- 独立前端应用 +- 独立后端API + +**实施阶段:** +- 阶段一(1-2个月):P0功能 +- 阶段二(1-2个月):P1功能 +- 阶段三(1-2个月):P2功能 + +--- + +## 🔄 文档迁移计划 + +### 第一阶段:创建新结构(1-2天) + +**Step 1:创建总体文件夹** +```bash +mkdir -p "00-系统总体设计" +mkdir -p "01-平台基础层" +mkdir -p "02-通用能力层" +mkdir -p "03-业务模块" +``` + +**Step 2:移动现有文档(保留原文件,先复制)** +``` +# 总体文档 +00-项目概述/ → 00-系统总体设计/ + - 壹证循科技 AI科研产品需求文档.md → 03-总体需求文档(PRD).md + - 壹证循科技AI科研产品 - 技术架构白皮书.md → 04-技术架构白皮书.md + - 技术架构总览.md → 05-技术架构总览.md(作为补充) + +# AI智能文献模块 +AI智能文献/ → 03-业务模块/ASL-AI智能文献/ + (调整内部结构,按新模板组织) + +# 稿件审查评估标准 +docs/稿件方法学评估标准.txt → 03-业务模块/RVW-稿件审查系统/02-业务规则/ +docs/稿约规范性评估标准.txt → 03-业务模块/RVW-稿件审查系统/02-业务规则/ +``` + +--- + +### 第二阶段:补充关键文档(按优先级) + +**P0文档(本周内):** +1. ✅ 00-系统总体设计/01-系统架构分层设计.md(已完成) +2. ✅ 00-系统总体设计/02-文档体系重构方案.md(当前文档) +3. ⚠️ 02-通用能力层/01-LLM大模型网关/01-设计文档.md +4. ⚠️ 03-业务模块/ASL-AI智能文献/(调整结构) + +**P1文档(本月内):** +5. ⚠️ 03-业务模块/RVW-稿件审查系统/00-项目概述/02-独立系统规划.md +6. ⚠️ 03-业务模块/DC-数据清洗整理/00-项目概述/01-产品需求文档(PRD).md +7. ⚠️ 01-平台基础层/01-用户与权限中心(UAM)/01-Feature-Flag设计.md + +**P2文档(下月):** +8. 其他模块的PRD和设计文档 + +--- + +### 第三阶段:清理旧文档(迁移完成后) + +**策略:** +- ✅ 保留原文档一段时间(1个月) +- ✅ 在原位置添加README,指向新位置 +- ✅ 确认新文档无问题后,删除旧文档 + +**示例README(原位置):** +```markdown +# 📣 文档已迁移 + +本文档已迁移到新位置,请访问: +- [00-系统总体设计](../00-系统总体设计/README.md) + +旧文档将在2025年12月后删除。 +``` + +--- + +## 📊 文档迁移矩阵 + +| 原位置 | 新位置 | 状态 | 优先级 | +|-------|-------|------|--------| +| `AI智能文献/` | `03-业务模块/ASL-AI智能文献/` | ⏳ 待迁移 | P0 | +| `壹证循科技 AI科研产品需求文档.md` | `00-系统总体设计/03-总体需求文档(PRD).md` | ⏳ 待迁移 | P0 | +| `壹证循科技AI科研产品 - 技术架构白皮书.md` | `00-系统总体设计/04-技术架构白皮书.md` | ⏳ 待迁移 | P0 | +| `稿件方法学评估标准.txt` | `03-业务模块/RVW-稿件审查系统/02-业务规则/` | ⏳ 待迁移 | P1 | +| `稿约规范性评估标准.txt` | `03-业务模块/RVW-稿件审查系统/02-业务规则/` | ⏳ 待迁移 | P1 | +| `04-开发计划/开发里程碑.md` | `08-项目管理/02-里程碑规划.md` | ⏳ 待迁移 | P2 | +| `05-每日进度/` | `08-项目管理/03-每日进度/` | ⏳ 待迁移 | P2 | + +--- + +## ✅ 迁移验收标准 + +### 结构验收 +- ✅ 所有总体文档都在`00-系统总体设计/` +- ✅ 所有业务模块都在`03-业务模块/`下,且结构一致 +- ✅ 每个模块都有完整的README导航 +- ✅ 文档层次清晰,无混杂 + +### 内容验收 +- ✅ 所有文档内的内部链接已更新 +- ✅ 所有文档的引用路径已更新 +- ✅ 所有图片、原型图路径已更新 + +### 可用性验收 +- ✅ 可以快速定位任何一个文档 +- ✅ 新增模块时,可以复制模板快速创建 +- ✅ 某个模块的文档可以独立打包(如审稿系统) + +--- + +## 🎯 总结 + +### 重构后的核心优势 + +**1. 总体与模块分离** +``` +总体设计(00-系统总体设计/): +- 面向整体产品战略 +- 面向技术架构师 +- 更新频率低,稳定性高 + +业务模块(03-业务模块/): +- 面向具体功能开发 +- 面向开发团队 +- 更新频率高,独立演进 +``` + +**2. 层次清晰** +``` +平台层(01-平台基础层/):所有模块的地基 +通用能力层(02-通用能力层/):跨模块共享能力 +业务模块层(03-业务模块/):独立产品单元 +``` + +**3. 支持独立销售** +``` +独立打包某个模块的完整文档: +- ASL(AI智能文献)→ 独立产品文档包 +- RVW(稿件审查)→ 独立产品文档包 +- DC(数据清洗)→ 独立产品文档包 +``` + +**4. 易于扩展** +``` +新增模块: +1. 复制模块模板 +2. 填充PRD和设计文档 +3. 开始开发 +``` + +--- + +**下一步行动:** +1. ✅ 创建新文件夹结构 +2. ⚠️ 迁移ASL模块文档(P0) +3. ⚠️ 补充LLM网关设计文档(P0) +4. ⚠️ 补充RVW独立系统规划(P1) + +--- + +## 📊 v2.0 更新总结 + +### 新增核心内容 + +**1. 运营管理端(ADMIN)** ⭐ **重要新增** +- 新增第8个业务模块:运营管理端 +- 15个核心功能模块(用户管理、Feature Flag、LLM模型管理等) +- 独立的文档结构和实施计划 +- 详见:`04-运营管理端架构设计.md` + +**2. 多种部署方案** ⭐⭐⭐ **重要扩展** +- **云端SaaS部署**(扩展):完整平台部署、微服务架构 +- **独立产品包部署**(新增):审稿系统、AI文献系统独立打包 +- **Electron单机版**(新增):完全离线、本地SQLite +- **私有化部署**(保留):Docker、K3s轻量级部署 + +**3. 架构实施文档** ⭐⭐ **新增** +- **Monorepo架构设计**:包管理、代码共享与复用 +- **产品打包方案**:独立产品打包流程、依赖管理 +- **微服务拆分**:拆分策略、服务间通信、API网关 + +**4. 系统总体设计文档** ⭐⭐⭐ **核心扩展** +- `03-数据库架构说明.md`:Docker部署、两个独立数据库对比 +- `05-Schema隔离方案与成本分析.md`:逻辑隔离 vs 物理隔离、改造成本 +- `06-模块独立部署与单机版方案.md`:完整打包、共享服务、Electron架构 + +### 文档结构变化 + +**00-系统总体设计/ - 新增文档:** +``` +✅ 03-数据库架构说明.md +✅ 04-运营管理端架构设计.md +✅ 05-Schema隔离方案与成本分析.md +✅ 06-模块独立部署与单机版方案.md +``` + +**03-业务模块/ - 新增模块:** +``` +⭐ ADMIN-运营管理端/(第8个模块) +``` + +**05-部署文档/ - 扩展结构:** +``` +⭐ 02-独立产品包部署/(新增) +⭐ 03-Electron单机版/(新增) +``` + +**09-架构实施/ - 新增文件夹:** +``` +⭐ 01-Monorepo架构设计/ +⭐ 02-产品打包方案/ +⭐ 03-微服务拆分/ +``` + +### 关键技术决策 + +**1. 模块独立部署方案:** +- ✅ 方案一:完整打包(独立产品) +- ✅ 方案二:共享服务(平台内模块) + +**2. Electron单机版:** +- ✅ 前端代码复用:90%+ +- ✅ 后端代码复用:80%+ +- ✅ 只需修改:API调用层、数据库(SQLite) + +**3. Schema隔离:** +- ✅ 现在做:1周,成本最低 +- ⚠️ 未来做:3-5周,成本5倍 + +### 实施优先级 + +**P0(已完成):** +- ✅ 系统架构分层设计 +- ✅ 数据库架构说明 +- ✅ 运营管理端架构设计 +- ✅ Schema隔离方案 +- ✅ 模块独立部署方案 +- ✅ 文档体系重构方案v2.0 + +**P1(本周):** +- ⏳ 创建新文件夹结构 +- ⏳ 迁移ASL模块文档 +- ⏳ LLM网关详细设计 +- ⏳ Schema隔离实施(可选) + +**P2(本月):** +- ⏳ 补充运营管理端详细文档 +- ⏳ 补充部署文档 +- ⏳ 补充Monorepo架构文档 + +**P3(下月):** +- ⏳ 迁移其他模块文档 +- ⏳ 补充测试和运维文档 + +--- + +## 📅 文档重构计划 v3.0 ⭐ **详细执行计划** + +### 执行时间估算 + +| 阶段 | 任务 | 预计时间 | 累计时间 | +|-----|------|---------|---------| +| **阶段一** | 创建目录结构 + 快速上下文模板 | 1小时 | 1小时 | +| **阶段二** | 迁移和整理现有文档 | 3小时 | 4小时 | +| **阶段三** | 创建业务模块基础文档 + 快速上下文 | 2小时 | 6小时 | +| **阶段四** | 创建总导航文档 + 规范文档 | 1.5小时 | 7.5小时 | +| **阶段五** | 清理和验证 + 更新链接 | 1小时 | 8.5小时 | +| **总计** | - | **8.5小时** | - | + +--- + +### 🎯 阶段一:创建目录结构(1小时) + +#### 1.1 创建所有目录框架 + +```bash +# 平台基础层 +01-平台基础层/ + ├── 01-用户与权限中心(UAM)/01-设计文档/ + ├── 02-存储服务/01-设计文档/ + ├── 03-通知服务/01-设计文档/ + ├── 04-监控与日志/01-设计文档/ + └── 05-系统配置/01-设计文档/ + +# 通用能力层 +02-通用能力层/ + ├── 01-LLM大模型网关/01-设计文档/ + ├── 02-文档处理引擎/01-设计文档/ + ├── 03-RAG引擎/01-设计文档/ + ├── 04-数据ETL引擎/01-设计文档/ + └── 05-医学NLP引擎/01-设计文档/ + +# 业务模块(8个模块) +03-业务模块/ + ├── AIA-AI智能问答/ + ├── ASL-AI智能文献/ + ├── PKB-个人知识库/ + ├── DC-数据清洗整理/ + ├── SSA-智能统计分析/ + ├── ST-统计分析工具/ + ├── RVW-稿件审查系统/ + └── ADMIN-运营管理端/ + +# 其他目录 +04-开发规范/ +05-部署文档/ + ├── 01-云端SaaS部署/ + ├── 02-独立产品包部署/ + ├── 03-Electron单机版/ + └── 04-私有化部署/ + +06-测试文档/ +07-运维文档/ +08-项目管理/ + ├── 01-整体开发计划/ + ├── 02-里程碑规划/ + └── 03-每日进度/ + +09-架构实施/ + ├── 01-Monorepo架构设计/ + ├── 02-产品打包方案/ + └── 03-微服务拆分/ + +_templates/(模板文件夹) +``` + +#### 1.2 创建快速上下文模板 + +**创建文件:** `_templates/[AI对接] 快速上下文-模板.md` + +**内容:** 标准的快速上下文模板,包含必填字段说明 + +--- + +### 🎯 阶段二:迁移和整理现有文档(3小时) + +#### 2.1 处理 `00-项目概述/` 文档(30分钟) + +| 文件名 | 处理方式 | 目标位置 | +|-------|---------|---------| +| 壹证循科技 AI科研产品需求文档.md | ✅ 迁移 | `00-系统总体设计/09-总体需求文档(PRD).md` | +| 壹证循科技AI科研产品 - 技术架构白皮书.md | ✅ 迁移 | `00-系统总体设计/10-技术架构白皮书.md` | +| 现有系统技术摸底报告.md | ✅ 保留 | 原位置(重要参考) | +| AI智能文献PRD(1-3).md | ✅ 迁移 | `03-业务模块/ASL-AI智能文献/00-项目概述/` | +| 产品需求文档(PRD).md | ⚠️ 审查合并 | 可能与总体PRD重复 | +| 技术架构总览.md | ⚠️ 审查 | 可能与白皮书重复 | +| 系统总体架构设计.md | ⚠️ 审查 | 可能与架构分层重复 | + +**操作:** +1. 先审查重复文档,确定保留版本 +2. 迁移文件(复制,不删除) +3. 在原位置创建迁移说明 + +#### 2.2 处理 `01-设计文档/` 文档(1小时) + +**核心任务:拆分数据库设计文档(1319行)** + +| 原文件 | 拆分方式 | 目标位置 | +|-------|---------|---------| +| 数据库设计文档.md | 提取规范部分 | `04-开发规范/03-数据库设计规范.md` | +| | 提取总体架构部分 | 补充到 `00-系统总体设计/03-数据库架构说明.md` | +| | 提取平台层表设计 | `01-平台基础层/01-用户与权限中心(UAM)/01-设计文档/02-数据库设计.md` | +| | 提取AIA模块表设计 | `03-业务模块/AIA-AI智能问答/01-设计文档/02-数据库设计.md` | +| | 提取PKB模块表设计 | `03-业务模块/PKB-个人知识库/01-设计文档/02-数据库设计.md` | +| | 提取RVW模块表设计 | `03-业务模块/RVW-稿件审查系统/01-设计文档/02-数据库设计.md` | + +| 原文件 | 处理方式 | 目标位置 | +|-------|---------|---------| +| API设计规范.md | 拆分 | `04-开发规范/02-API设计规范.md`(规范) + 各模块API设计 | +| 平台前端架构设计/ | 迁移 | `01-平台基础层/06-前端架构/` | +| AI智能文献原型.html(2个) | 迁移 | `03-业务模块/ASL-AI智能文献/01-设计文档/07-UI设计/` | + +#### 2.3 处理 `AI智能文献/` 目录(1小时) + +**操作:整体迁移并调整结构** + +``` +AI智能文献/ → 03-业务模块/ASL-AI智能文献/ + +调整后的结构: +ASL-AI智能文献/ + ├── [AI对接] ASL快速上下文.md # ⭐ 新建 + ├── 00-项目概述/ + │ ├── 01-产品需求文档(PRD).md # 合并3个PRD + │ ├── 02-核心功能清单.md + │ ├── 03-用户故事和验收标准.md + │ └── 04-非功能性需求.md + ├── 01-设计文档/ + │ ├── 01-系统架构设计.md + │ ├── 02-数据库设计.md + │ ├── 03-API设计.md + │ ├── 04-前端组件设计.md + │ ├── 05-AI模型集成设计.md + │ ├── 06-文件处理设计.md + │ └── 07-UI设计/ + │ ├── 标题摘要初筛UI设计.md + │ ├── 全文复筛UI设计.md + │ ├── 标题摘要初筛原型.html + │ └── 全文复筛原型.html + ├── 02-业务规则/ + │ └── 01-核心业务规则.md + ├── 03-开发计划/ + │ ├── 01-开发里程碑.md + │ └── 02-任务分解.md + ├── 04-测试文档/ + │ ├── 01-测试计划.md + │ └── 02-标题摘要初筛测试用例.md + └── README.md +``` + +#### 2.4 处理 `04-开发计划/` 和 `05-每日进度/`(30分钟) + +``` +04-开发计划/ → 08-项目管理/01-整体开发计划/ +05-每日进度/ → 08-项目管理/03-每日进度/ + +同时提取里程碑文档到: +08-项目管理/02-里程碑规划/ + ├── 里程碑1-MVP完成总结.md + ├── 里程碑1.5-RAG优化完成.md + └── README.md +``` + +#### 2.5 处理稿件审查相关(10分钟) + +``` +稿件方法学评估标准.txt → 03-业务模块/RVW-稿件审查系统/02-业务规则/01-方法学评估标准.md +稿约规范性评估标准.txt → 03-业务模块/RVW-稿件审查系统/02-业务规则/02-稿约规范性评估标准.md +``` + +--- + +### 🎯 阶段三:创建业务模块基础文档 + 快速上下文(2小时) + +#### 3.1 创建AIA模块文档(30分钟) + +**任务:** +1. ✅ 创建模块目录结构 +2. ✅ 创建 `[AI对接] AIA快速上下文.md`(根据模板) +3. ✅ 从现有代码和Prisma Schema提取数据库设计 +4. ✅ 整理核心功能清单 +5. ✅ 创建README导航 + +**目录结构:** +``` +AIA-AI智能问答/ + ├── [AI对接] AIA快速上下文.md # ⭐ 新建 + ├── 00-项目概述/ + │ ├── 01-功能清单.md # 基于现有功能整理 + │ └── README.md + ├── 01-设计文档/ + │ ├── 01-智能体配置系统.md # 基于现有代码整理 + │ ├── 02-数据库设计.md # 提取Prisma Schema + │ ├── 03-API设计.md # 提取现有API + │ └── README.md + └── README.md +``` + +#### 3.2 创建PKB模块文档(30分钟) + +**类似AIA模块,提取现有功能和设计** + +#### 3.3 创建RVW模块文档(30分钟) + +**包含稿件审查评估标准** + +#### 3.4 创建DC、SSA、ST、ADMIN空模板(30分钟) + +**每个模块创建:** +``` +模块名/ + ├── [AI对接] 快速上下文.md(标记为待完善) + ├── 00-项目概述/README.md + ├── 01-设计文档/README.md + └── README.md +``` + +--- + +### 🎯 阶段四:创建总导航文档 + 规范文档(1.5小时) + +#### 4.1 创建规范文档(1小时) + +**需要创建:** + +1. **02-API设计规范.md** + - 从现有代码中提取RESTful规范 + - 错误码规范 + - 认证规范(JWT) + - 分页规范 + +2. **03-数据库设计规范.md** + - 从现有Prisma Schema中提取命名规范 + - 字段类型规范 + - 索引规范 + - Schema隔离规范 + +3. **04-前端组件设计规范.md** + - 从现有前端代码中提取规范 + +4. **05-Git规范.md** + - Commit Message规范 + - 分支管理规范 + +#### 4.2 创建README导航(30分钟) + +**需要创建/更新的README:** + +1. ✅ `docs/README.md` - 总导航(更新) +2. ⏳ `01-平台基础层/README.md` +3. ⏳ `01-平台基础层/[AI对接] 平台层快速上下文.md` +4. ⏳ `02-通用能力层/README.md` +5. ⏳ `02-通用能力层/[AI对接] 通用能力快速上下文.md` +6. ⏳ `03-业务模块/README.md` +7. ⏳ `03-业务模块/[AI对接] 业务模块快速上下文.md` +8. ⏳ `04-开发规范/README.md` +9. ⏳ `05-部署文档/README.md` +10. ⏳ `08-项目管理/README.md` + +--- + +### 🎯 阶段五:清理和验证(1小时) + +#### 5.1 创建迁移说明(20分钟) + +**在所有迁移文件的原位置创建:** `_MIGRATED.md` + +```markdown +# 📣 文档已迁移 + +本文档已迁移到新位置: +- [新位置链接](../新路径/文档名.md) + +**迁移日期:** 2025-11-06 +**旧文档将在:** 2025-12-06 删除 + +--- + +**新文档结构说明:** +[简要说明为什么迁移,新结构的好处] +``` + +#### 5.2 验证清单(20分钟) + +- [ ] 所有文档链接已更新 +- [ ] 图片路径已更新(相对路径) +- [ ] README导航完整且链接有效 +- [ ] 无死链接 +- [ ] 每个模块都有快速上下文 +- [ ] 文档层次清晰,符合三层架构 + +#### 5.3 生成文档地图(20分钟) + +**创建:** `docs/[文档地图] 完整导航.md` + +**内容:** 树形结构展示所有文档及其用途,方便查找 + +--- + +## ✅ 关键决策确认 + +### 需要您确认的问题: + +#### 1. 重复文档处理 + +**问题:** 以下文档可能重复,如何处理? + +| 文档A | 文档B | 建议 | 您的决定 | +|------|------|------|---------| +| 00-项目概述/产品需求文档(PRD).md | 壹证循科技 AI科研产品需求文档.md | 审查后保留更完整的 | _________ | +| 00-项目概述/系统总体架构设计.md | 00-系统总体设计/01-系统架构分层设计.md | 保留新版本 | _________ | +| 00-项目概述/技术架构总览.md | 壹证循科技AI科研产品 - 技术架构白皮书.md | 审查后决定 | _________ | + +**您的决定:** +- [ ] 选项A:自动保留更新的版本 +- [ ] 选项B:我来手动审查每个重复文档 +- [ ] 选项C:都保留,标记为不同版本 + +#### 2. 旧文档删除策略 + +**您的决定:** +- [ ] 选项A:迁移后立即删除原文件(激进) +- [ ] 选项B:保留1个月,创建迁移说明(稳妥) ⭐ 推荐 +- [ ] 选项C:移动到 `docs/_archived/` 存档(保守) + +#### 3. AI智能文献PRD合并 + +**当前:** 3个独立PRD文档(产品概览、初筛与复筛、提取与分析) + +**您的决定:** +- [ ] 选项A:合并为1个完整PRD ⭐ 推荐 +- [ ] 选项B:保持独立,按章节组织 + +#### 4. 数据库设计文档拆分 + +**当前:** `01-设计文档/数据库设计文档.md`(1319行) + +**您的决定:** +- [ ] 选项A:自动拆分(AI辅助) ⭐ 推荐 +- [ ] 选项B:手动拆分(精确但耗时) +- [ ] 选项C:暂不拆分,标记为待处理 + +#### 5. 执行方式 + +**您的决定:** +- [ ] 选项A:分批执行(5批,每批1-2小时)⭐ 推荐 +- [ ] 选项B:一次性全部执行(8.5小时连续) + +--- + +## 📊 v3.0 完成标志 + +### 文档完整性 +- ✅ 8个业务模块(含运营管理端) +- ✅ 4种部署方案(云端、独立包、单机版、私有化) +- ✅ 3层架构(平台层、能力层、业务层) +- ⭐ **规范与设计分离**(规范在04-开发规范/) +- ⭐ **快速上下文体系**(L0/L1/L2三级) +- ⭐ **金字塔式文档**(从简到详) + +### 可操作性 +- ✅ 每个模块可独立打包和部署 +- ✅ 每个模块有完整文档(需求、设计、开发、测试、部署) +- ✅ 新增模块可复制模板快速创建 +- ⭐ **快速上下文导航**(新AI 2分钟了解项目) +- ⭐ **分层查找文档**(规范/总体/模块) + +### 商业价值 +- ✅ 支持模块独立销售(审稿系统、AI文献等) +- ✅ 支持多种部署模式(满足不同客户需求) +- ✅ 支持灵活的商业模式(订阅制、一次性License、模块化售卖) +- ⭐ **降低AI对接成本**(Token消耗减少50-70%) +- ⭐ **提升开发效率**(规范统一,查找快速) + +--- + +## 🚀 立即开始 + +**请您回复确认:** + +1. **是否认可整体计划?** + - [ ] 是,开始执行 + - [ ] 否,需要调整:___________ + +2. **关键决策确认:** + - 重复文档处理:选项 _____ + - 旧文档删除策略:选项 _____ + - PRD合并:选项 _____ + - 数据库文档拆分:选项 _____ + - 执行方式:选项 _____ + +3. **是否立即开始第1批操作?** + - [ ] 是,立即开始(创建目录结构 + 模板) + - [ ] 否,等待进一步确认 + +--- + +**最后更新:** 2025-11-06 v3.0 +**维护人:** 技术架构师 + +--- + +**下一步行动:** 等待您的确认后,立即开始执行文档重构!🚀 + diff --git a/docs/00-系统总体设计/03-数据库架构说明.md b/docs/00-系统总体设计/03-数据库架构说明.md new file mode 100644 index 00000000..a24aa91a --- /dev/null +++ b/docs/00-系统总体设计/03-数据库架构说明.md @@ -0,0 +1,447 @@ +# 数据库架构说明 + +> **创建日期:** 2025-11-06 +> **文档目的:** 澄清数据库部署方式和架构 + +--- + +## 📋 核心澄清 + +### ✅ 您有自己独立的PostgreSQL数据库! + +**关键事实:** +1. ✅ PostgreSQL是通过Docker部署的(`docker-compose.yml`) +2. ✅ 这是您项目的独立数据库,不是Dify的 +3. ✅ Dify有自己完全独立的数据库(在`dify/docker/`目录下) +4. ✅ 您不需要手动安装PostgreSQL,Docker会自动创建 + +--- + +## 🐳 Docker部署详情 + +### docker-compose.yml配置 + +```yaml +# 位置:AIclinicalresearch/docker-compose.yml + +services: + # PostgreSQL 数据库 + postgres: + image: postgres:15-alpine # 使用官方PostgreSQL镜像 + container_name: ai-clinical-postgres + environment: + POSTGRES_DB: ai_clinical_research # 数据库名 + POSTGRES_USER: postgres # 用户名 + POSTGRES_PASSWORD: postgres123 # 密码 + ports: + - "5432:5432" # 端口映射 + volumes: + - postgres_data:/var/lib/postgresql/data # 数据持久化 + networks: + - ai-clinical-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + + # Redis 缓存 + redis: + image: redis:7-alpine + container_name: ai-clinical-redis + ports: + - "6379:6379" + volumes: + - redis_data:/data + networks: + - ai-clinical-network + +volumes: + postgres_data: # PostgreSQL数据卷(数据持久化存储) + redis_data: # Redis数据卷 +``` + +--- + +## 🚀 启动流程 + +### 一键启动脚本(一键启动.bat) + +```batch +[步骤2/7] 启动PostgreSQL和Redis容器 +docker-compose up -d + +这个命令会: +1. 自动下载PostgreSQL 15镜像(如果本地没有) +2. 创建PostgreSQL容器 +3. 创建Redis容器 +4. 创建数据卷(postgres_data)用于持久化存储 +5. 启动容器并在后台运行 +``` + +**您不需要手动安装PostgreSQL!** +- ✅ Docker会自动创建和管理 +- ✅ 数据存储在Docker数据卷中,不会丢失 +- ✅ 可以通过`localhost:5432`连接 + +--- + +## 🗄️ 数据库连接信息 + +### 连接配置 + +**数据库连接字符串(DATABASE_URL):** +``` +postgresql://postgres:postgres123@localhost:5432/ai_clinical_research +``` + +**拆解:** +- **协议:** postgresql:// +- **用户名:** postgres +- **密码:** postgres123 +- **主机:** localhost(Docker映射到本地) +- **端口:** 5432 +- **数据库名:** ai_clinical_research + +### 后端配置(backend/.env) + +```bash +# 数据库连接 +DATABASE_URL=postgresql://postgres:postgres123@localhost:5432/ai_clinical_research + +# 这个连接的是您自己的PostgreSQL,不是Dify的! +``` + +--- + +## 🔍 两个独立的数据库系统 + +### 系统对比 + +| 项目 | 您的数据库 | Dify的数据库 | +|------|----------|-------------| +| **位置** | `AIclinicalresearch/docker-compose.yml` | `dify/docker/docker-compose.yml` | +| **容器名** | `ai-clinical-postgres` | `dify-db`(猜测) | +| **端口** | `5432` | 可能是`5433`或不暴露 | +| **数据库名** | `ai_clinical_research` | `dify` | +| **用途** | 存储您项目的业务数据 | 存储Dify的数据 | +| **访问方式** | 直接Prisma访问 | 通过Dify API访问 | + +### 架构图 + +``` +┌─────────────────────────────────────────────────────────┐ +│ AIclinicalresearch项目 │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ Backend │──────│ PostgreSQL │ │ +│ │ (Node.js) │ SQL │ (Docker) │ │ +│ │ │ │ │ │ +│ │ Prisma │ │ Port: 5432 │ │ +│ └──────────────┘ └──────────────┘ │ +│ │ │ +│ │ HTTP API │ +│ ↓ │ +│ ┌──────────────┐ │ +│ │ Dify │──────> Dify自己的PostgreSQL │ +│ │ (Docker) │ (完全独立) │ +│ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## 📊 当前数据库表结构 + +### 您的PostgreSQL中的表 + +```sql +-- 来源:backend/prisma/schema.prisma + +-- 用户模块 +users -- 用户表 + +-- 项目模块 +projects -- 项目表 + +-- AI问答模块 +conversations -- 对话表 +messages -- 消息表 +general_conversations -- 通用对话表 +general_messages -- 通用消息表 + +-- 知识库模块 +knowledge_bases -- 知识库表 +documents -- 文档表 + +-- 批处理模块(Phase 3) +batch_tasks -- 批处理任务表 +batch_results -- 批处理结果表 +task_templates -- 任务模板表 + +-- 稿件审查模块 +review_tasks -- 稿件审查任务表 + +-- 运营管理模块 +admin_logs -- 管理员日志表 +``` + +**总计:16张表,全部在您自己的PostgreSQL中。** + +--- + +## 🔧 常用操作 + +### 查看Docker容器状态 + +```bash +# 查看所有容器 +docker ps + +# 应该能看到: +# ai-clinical-postgres (PostgreSQL) +# ai-clinical-redis (Redis) +``` + +### 连接到PostgreSQL + +**方法1:使用Docker命令** +```bash +# 进入PostgreSQL容器 +docker exec -it ai-clinical-postgres psql -U postgres -d ai_clinical_research + +# 然后可以执行SQL: +\dt # 查看所有表 +\d users # 查看users表结构 +SELECT * FROM users LIMIT 10; +``` + +**方法2:使用数据库客户端** +- **DBeaver** / **pgAdmin** / **DataGrip** / **Navicat** +- 连接信息: + - Host: `localhost` + - Port: `5432` + - Database: `ai_clinical_research` + - User: `postgres` + - Password: `postgres123` + +### 停止和启动数据库 + +```bash +# 停止(但不删除数据) +docker-compose down + +# 启动 +docker-compose up -d + +# 重启 +docker-compose restart postgres +``` + +### 查看数据库日志 + +```bash +# 查看PostgreSQL日志 +docker logs ai-clinical-postgres + +# 实时查看日志 +docker logs -f ai-clinical-postgres +``` + +--- + +## 💾 数据持久化 + +### 数据存储位置 + +**数据卷(Volume):** +``` +postgres_data:/var/lib/postgresql/data +``` + +**实际存储位置:** +- Windows: `C:\ProgramData\Docker\volumes\aiclinicalresearch_postgres_data\_data` +- Mac/Linux: `/var/lib/docker/volumes/aiclinicalresearch_postgres_data/_data` + +**重要:** +- ✅ 即使删除容器(`docker-compose down`),数据不会丢失 +- ✅ 数据存储在Docker数据卷中,持久化保存 +- ⚠️ 只有执行`docker-compose down -v`(删除数据卷)才会清空数据 + +### 备份数据库 + +```bash +# 备份(导出SQL) +docker exec ai-clinical-postgres pg_dump -U postgres ai_clinical_research > backup.sql + +# 恢复(导入SQL) +docker exec -i ai-clinical-postgres psql -U postgres ai_clinical_research < backup.sql +``` + +--- + +## 🎯 未来:Schema隔离计划 + +### 当前状态 + +```sql +-- 所有表都在public schema +public.users +public.projects +public.conversations +public.knowledge_bases +public.documents +public.review_tasks +... +``` + +### 目标架构(Schema隔离) + +```sql +-- 平台层Schema +CREATE SCHEMA platform_schema; +platform_schema.users +platform_schema.roles +platform_schema.permissions + +-- 业务模块Schema +CREATE SCHEMA aia_schema; -- AI智能问答 +aia_schema.projects +aia_schema.conversations +aia_schema.messages + +CREATE SCHEMA pkb_schema; -- 个人知识库 +pkb_schema.knowledge_bases +pkb_schema.documents + +CREATE SCHEMA asl_schema; -- AI智能文献 +asl_schema.projects +asl_schema.literature_items +asl_schema.screening_results + +CREATE SCHEMA review_schema; -- 稿件审查 +review_schema.review_tasks +review_schema.review_journals +``` + +**实施计划:** +- **阶段一(立即):** 逻辑隔离,使用表名前缀(`aia_projects`, `asl_projects`) +- **阶段二(微服务拆分时):** 物理隔离,创建真正的Schema + +--- + +## 🔍 Dify数据库(独立系统) + +### Dify的部署 + +``` +dify/docker/docker-compose.yml + ├── dify-db (PostgreSQL) + ├── dify-redis + ├── dify-web + ├── dify-api + └── ... +``` + +**关键点:** +- ✅ Dify有自己完全独立的docker-compose.yml +- ✅ Dify的PostgreSQL是独立的容器 +- ✅ 您的项目不直接访问Dify的数据库 +- ✅ 通过Dify API(HTTP REST)调用Dify功能 + +### 您的项目如何使用Dify + +```typescript +// 不是直接访问Dify数据库,而是通过API + +// 创建知识库 +const response = await fetch('http://localhost/v1/datasets', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${DIFY_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + name: '我的知识库' + }) +}); + +// 上传文档 +const formData = new FormData(); +formData.append('file', file); +await fetch(`http://localhost/v1/datasets/${datasetId}/document/create-by-file`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${DIFY_API_KEY}` + }, + body: formData +}); +``` + +**您的数据库中存储:** +```sql +-- knowledge_bases表 +{ + id: 'uuid', + name: '我的知识库', + dify_dataset_id: 'xxx' -- 关联Dify的dataset_id +} +``` + +--- + +## 🎯 总结 + +### 核心要点 + +1. ✅ **您有自己的PostgreSQL数据库** + - 通过Docker部署(`docker-compose.yml`) + - 容器名:`ai-clinical-postgres` + - 数据库名:`ai_clinical_research` + +2. ✅ **您不需要手动安装PostgreSQL** + - `docker-compose up -d`会自动创建 + - 数据持久化存储在Docker数据卷中 + +3. ✅ **Dify是完全独立的系统** + - 有自己的PostgreSQL数据库 + - 您通过Dify API访问,不直接访问数据库 + +4. ✅ **当前16张表全部在您的PostgreSQL中** + - 用户、项目、对话、知识库、文档、批处理、稿件审查等 + - 全部在`public` schema(未来需要隔离) + +### 为什么您不记得安装PostgreSQL? + +**因为您根本没有手动安装!** 😊 + +- ✅ Docker自动下载镜像 +- ✅ Docker自动创建容器 +- ✅ 一键启动脚本自动启动 +- ✅ 您只需要运行`一键启动.bat` + +**这就是Docker的魔力!** + +--- + +**需要进一步了解的内容:** +1. 如何备份和恢复数据库? +2. 如何迁移到Schema隔离架构? +3. 如何连接数据库进行手动查询? +4. Prisma如何管理数据库迁移? + + + + + + + + + + + + + + + diff --git a/docs/00-系统总体设计/04-运营管理端架构设计.md b/docs/00-系统总体设计/04-运营管理端架构设计.md new file mode 100644 index 00000000..7bdbaaf4 --- /dev/null +++ b/docs/00-系统总体设计/04-运营管理端架构设计.md @@ -0,0 +1,872 @@ +# 运营管理端架构设计 + +> **文档版本:** v1.0 +> **创建日期:** 2025-11-06 +> **最后更新:** 2025-11-06 +> **文档状态:** 架构设计中 +> **作者:** 技术架构师 + +--- + +## 📋 文档说明 + +本文档详细设计运营管理端(Admin/Operations Platform)的完整架构,包括: +1. 运营管理端的层次定位 +2. 完整的功能清单 +3. 对系统总体设计的影响 +4. 技术实现方案 + +--- + +## 🎯 核心问题解答 + +### 1. 运营管理端是应用层吗? + +**答案:运营管理端是"横跨多层"的特殊系统** + +``` +运营管理端的定位: + +┌─────────────────────────────────────────────────────────┐ +│ 运营管理端(Admin Platform) │ +│ 独立的前端应用 + 独立的后端API │ +│ │ +│ 管理范围: │ +│ ├─ 平台基础层配置(用户、权限、租户) │ +│ ├─ 通用能力层配置(LLM模型、Feature Flag) │ +│ ├─ 业务模块配置(智能体提示词、模块开关) │ +│ └─ 运营数据分析(统计、监控、成本分析) │ +└─────────────────────────────────────────────────────────┘ + ↓ 管理 +┌─────────────────────────────────────────────────────────┐ +│ 业务模块层 │ +│ AIA | ASL | PKB | DC | SSA | ST | RVW │ +└─────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────┐ +│ 通用能力层 │ +│ LLM网关 | 文档处理 | RAG | ETL | NLP │ +└─────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────┐ +│ 平台基础层 │ +│ 用户权限 | 存储 | 通知 | 监控 | 配置 │ +└─────────────────────────────────────────────────────────┘ +``` + +**准确定位:** +- ✅ 运营管理端是一个**独立的应用系统** +- ✅ 它管理和配置所有三层(平台层、能力层、业务层) +- ✅ 它有自己的前端应用、后端API、权限体系 +- ✅ 它是"平台的管理者",而不是"应用层" + +**类比:** +- 业务前端(用户端):给医生、研究者使用 +- 运营管理端:给公司内部运营人员、管理员使用 + +--- + +### 2. 运营管理端的完整功能清单 + +基于7个业务模块,我为您补充了完整的功能清单: + +--- + +## 📊 功能模块设计 + +### 模块1:用户管理 ✅ 已提到 + +**核心功能:** +1. **用户列表与搜索** + - 列表展示(邮箱、姓名、注册时间、状态、版本) + - 搜索和筛选(按邮箱、姓名、状态、版本、注册时间) + - 批量操作(批量禁用、批量删除) + +2. **用户详情与编辑** + - 基本信息(邮箱、姓名、头像、手机号) + - 账户状态(激活、禁用、锁定) + - 用户版本(基础版、高级版、旗舰版) + - 配额管理(知识库配额、文档配额、月度AI配额) + - 试用期管理(是否试用、试用到期时间) + +3. **用户权限管理** + - 模块权限(可访问哪些模块:AIA、ASL、PKB等) + - 功能权限(批处理、全文模式、模型切换等) + - 角色分配(普通用户、高级用户、VIP用户、管理员) + +4. **用户操作日志** + - 登录日志(登录时间、IP地址、设备) + - 操作日志(上传文档、创建对话、批处理任务等) + - API调用日志(LLM调用次数、Token使用量) + +5. **用户数据统计** + - 知识库数量、文档数量 + - 对话数量、消息数量 + - AI调用次数、Token使用量 + - 存储空间使用量 + +--- + +### 模块2:智能体提示词管理 ✅ 已提到 + +**核心功能:** +1. **智能体列表** + - 10个智能体(选题评价、PICO梳理、样本量计算等) + - 每个智能体的基本信息(名称、描述、图标) + - 启用/禁用状态 + +2. **提示词编辑** + - System Prompt(系统提示词) + - User Prompt Template(用户提示词模板) + - 支持变量(如`{研究背景}`、`{研究目标}`) + - 实时预览 + +3. **提示词版本管理** + - 版本历史记录 + - 版本对比 + - 版本回滚 + - 版本发布(灰度发布、全量发布) + +4. **测试与验证** + - 提示词测试工具 + - 输入测试数据,查看AI输出 + - A/B测试(对比不同提示词效果) + +--- + +### 模块3:版本功能设置(Feature Flag) ✅ 已提到 + +**核心功能:** +1. **版本定义** + - 基础版(Basic) + - 高级版(Advanced) + - 旗舰版(Flagship) + - 企业版(Enterprise) + +2. **模块权限配置** + - 7个业务模块的访问权限矩阵 + ``` + 版本 | AIA | ASL | PKB | DC | SSA | ST | RVW + -----|-----|-----|-----|----|----|----|----| + 基础 | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | + 高级 | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✗ | + 旗舰 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | + ``` + +3. **功能权限配置** + - AI模型选择权限(DeepSeek、Qwen3、Claude等) + - 批处理功能 + - 全文阅读模式 + - 知识库配额(3个、10个、无限) + - 文档配额(100个、1000个、无限) + - 月度AI配额(100次、500次、2000次、无限) + +4. **动态配置** + - 实时生效(无需重启) + - 配置预览(修改前预览效果) + - 配置回滚 + +--- + +### 模块4:用户权限和版本设置 ✅ 已提到 + +**核心功能:** +1. **批量版本调整** + - 选择多个用户批量升级/降级版本 + - 批量延长试用期 + - 批量调整配额 + +2. **自动化规则** + - 新用户默认版本(基础版) + - 新用户默认试用期(14天) + - 试用期到期自动降级 + - 配额超出自动限流 + +3. **特殊权限** + - 内部员工账号(所有权限) + - 测试账号(无限配额) + - 合作伙伴账号(特殊权限) + +--- + +### 模块5:租户管理 🆕 **新增** + +**适用场景:** 私有化部署、企业版 + +**核心功能:** +1. **租户列表** + - 租户名称、Logo、域名 + - 租户状态(试用、正式、过期、禁用) + - 租户类型(标准版、企业版、定制版) + +2. **租户配置** + - 品牌定制(Logo、主题色、名称) + - 独立域名(如`hospital-a.yizhengxun.com`) + - 数据隔离(独立Schema) + - 模块开关(可选模块) + +3. **租户统计** + - 用户数量、活跃用户 + - 存储使用量 + - API调用量 + - 成本分析 + +--- + +### 模块6:LLM模型管理 🆕 **新增(关键)** + +**核心功能:** +1. **模型配置** + - 支持的模型列表(DeepSeek-V3、Qwen3、Qwen-Long、Claude等) + - 每个模型的API Key + - 模型基础URL + - 模型参数(Temperature、Max Tokens等) + +2. **模型与版本绑定** + ``` + 基础版:只能用DeepSeek-V3 + 高级版:可用DeepSeek-V3、Qwen3 + 旗舰版:可用DeepSeek-V3、Qwen3、Qwen-Long、Claude + ``` + +3. **模型成本配置** + - 每个模型的成本(¥/百万tokens) + - DeepSeek-V3:¥1/百万tokens + - Qwen3:¥5/百万tokens + - Claude 3:¥50/百万tokens(估算) + +4. **模型切换策略** + - 默认模型(如DeepSeek-V3) + - 降级策略(模型不可用时自动降级) + - 限流策略(超出配额时禁用高成本模型) + +--- + +### 模块7:系统配置管理 🆕 **新增** + +**核心功能:** +1. **全局配置** + - 系统名称、Logo、Favicon + - 默认语言(中文、英文) + - 时区设置 + - 文件上传限制(单文件大小、总大小) + +2. **安全配置** + - JWT Token过期时间 + - 密码强度要求 + - 登录失败锁定策略 + - CORS配置 + +3. **邮件配置** + - SMTP服务器 + - 邮件模板(注册、密码重置、通知等) + +4. **存储配置** + - 对象存储类型(本地、MinIO、阿里云OSS、S3) + - 存储配额限制 + +--- + +### 模块8:监控与日志 🆕 **新增(关键)** + +**核心功能:** +1. **系统监控** + - 实时在线用户数 + - 实时API调用量(TPS/QPS) + - 系统资源使用(CPU、内存、磁盘) + - 数据库连接数 + +2. **错误监控** + - 错误日志列表(时间、用户、错误类型、错误信息) + - 错误统计(按类型、按模块、按时间) + - 错误告警(邮件、短信) + +3. **性能监控** + - API响应时间统计 + - 慢查询日志 + - LLM调用延迟 + +4. **审计日志** + - 管理员操作日志(登录、修改配置、修改用户等) + - 敏感操作记录(删除用户、修改权限、修改提示词等) + +--- + +### 模块9:数据统计与报表 🆕 **新增(关键)** + +**核心功能:** +1. **用户统计** + - 注册用户趋势(日/周/月) + - 活跃用户趋势(DAU/WAU/MAU) + - 用户留存率 + - 用户版本分布 + +2. **业务统计** + - 各模块使用情况(AIA、ASL、PKB等) + - 智能体使用排行 + - 知识库数量趋势 + - 文档上传量趋势 + +3. **AI调用统计** + - LLM调用次数趋势 + - Token使用量趋势 + - 各模型使用占比 + - 成本分析(按模型、按模块、按用户) + +4. **存储统计** + - 总存储使用量 + - 各模块存储占比 + - 用户存储排行 + +5. **导出报表** + - Excel导出 + - PDF导出 + - 定期自动生成报表(周报、月报) + +--- + +### 模块10:成本分析与计费 🆕 **新增(商业关键)** + +**核心功能:** +1. **成本统计** + - LLM API总成本(按日/周/月) + - 各模块成本占比 + - 各用户成本排行 + - 各模型成本占比 + +2. **计费管理** + - 用户账单生成 + - 欠费用户列表 + - 自动扣费 + - 发票管理 + +3. **成本预警** + - 成本超出预算告警 + - 单用户成本异常告警 + - 免费用户滥用检测 + +4. **利润分析** + - 收入 vs 成本 + - ROI分析 + - 各版本利润率 + +--- + +### 模块11:公告与通知管理 🆕 **新增** + +**核心功能:** +1. **系统公告** + - 创建公告(标题、内容、优先级) + - 公告展示位置(首页横幅、弹窗、消息中心) + - 公告定时发布 + - 公告过期时间 + +2. **站内消息** + - 给所有用户发消息 + - 给特定版本用户发消息 + - 给特定用户发消息 + - 消息模板 + +3. **邮件群发** + - 邮件内容编辑(富文本) + - 邮件预览 + - 定时发送 + - 发送记录 + +--- + +### 模块12:帮助文档管理 🆕 **新增** + +**核心功能:** +1. **文档库** + - 文档分类(快速入门、功能介绍、常见问题等) + - 文档列表(标题、状态、更新时间) + - 文档编辑(Markdown富文本) + - 文档预览 + +2. **视频教程** + - 视频上传 + - 视频分类 + - 视频播放统计 + +3. **常见问题(FAQ)** + - 问题分类 + - 问题编辑 + - 搜索优化 + +--- + +### 模块13:反馈与工单管理 🆕 **新增** + +**核心功能:** +1. **用户反馈** + - 反馈列表(用户、内容、类型、状态) + - 反馈分类(功能建议、Bug报告、咨询) + - 反馈回复 + - 反馈状态(待处理、处理中、已完成、已忽略) + +2. **工单系统** + - 工单创建 + - 工单分配(分配给客服、技术团队) + - 工单跟进 + - 工单关闭 + +3. **满意度调查** + - 问题解决后发送满意度调查 + - 满意度统计 + +--- + +### 模块14:系统健康检查 🆕 **新增** + +**核心功能:** +1. **健康检查** + - 数据库连接检查 + - Redis连接检查 + - Dify API连接检查 + - Python微服务连接检查 + - LLM API连接检查 + +2. **自动化测试** + - API可用性测试 + - 端到端测试(注册、登录、上传文档、AI对话) + +3. **告警通知** + - 服务异常告警 + - 邮件/短信通知管理员 + +--- + +### 模块15:数据库备份与恢复 🆕 **新增** + +**核心功能:** +1. **备份管理** + - 手动备份 + - 定时自动备份(每日、每周) + - 备份列表(时间、大小、状态) + - 备份下载 + +2. **恢复管理** + - 选择备份恢复 + - 恢复预览(确认恢复内容) + - 恢复进度 + +--- + +## 📊 功能优先级分类 + +### P0(必须,阶段一) + +| 模块 | 功能 | 原因 | +|------|------|------| +| **用户管理** | 用户列表、编辑、禁用 | 基础功能 | +| **版本功能设置** | Feature Flag配置 | 商业模式基础 | +| **LLM模型管理** | 模型配置、成本配置 | 成本控制 | +| **系统配置** | 全局配置、安全配置 | 系统运行基础 | + +### P1(重要,阶段一或阶段二) + +| 模块 | 功能 | 原因 | +|------|------|------| +| **智能体提示词管理** | 提示词编辑、版本管理 | 产品优化 | +| **监控与日志** | 错误监控、审计日志 | 稳定性 | +| **数据统计与报表** | 用户统计、AI调用统计 | 运营决策 | +| **成本分析与计费** | 成本统计、计费管理 | 商业运营 | + +### P2(有用,阶段二或阶段三) + +| 模块 | 功能 | 原因 | +|------|------|------| +| **租户管理** | 租户配置、数据隔离 | 私有化部署需要 | +| **公告与通知** | 系统公告、站内消息 | 用户运营 | +| **帮助文档** | 文档管理、视频教程 | 用户支持 | +| **反馈与工单** | 用户反馈、工单系统 | 客户服务 | +| **系统健康检查** | 健康检查、自动化测试 | 运维自动化 | +| **数据库备份** | 备份管理、恢复管理 | 数据安全 | + +--- + +## 🏗️ 对系统总体设计的影响 + +### 影响1:独立的前端应用 + +**当前架构:** +``` +frontend/ # 用户端前端(React) + ├── src/ + │ ├── pages/ + │ │ ├── login/ + │ │ ├── dashboard/ + │ │ └── ... + └── ... +``` + +**新架构(需要增加运营管理端):** +``` +frontend/ # 用户端前端(React) + └── ... + +admin-frontend/ # 运营管理端前端(React)⭐ 新增 + ├── src/ + │ ├── pages/ + │ │ ├── login/ # 管理员登录 + │ │ ├── dashboard/ # 运营仪表盘 + │ │ ├── users/ # 用户管理 + │ │ ├── agents/ # 智能体管理 + │ │ ├── feature-flags/ # Feature Flag管理 + │ │ ├── llm-models/ # LLM模型管理 + │ │ ├── monitoring/ # 监控与日志 + │ │ ├── statistics/ # 数据统计 + │ │ ├── cost-analysis/ # 成本分析 + │ │ └── ... + │ └── ... + └── ... +``` + +**部署方式:** +- 用户端:`https://app.yizhengxun.com` +- 运营管理端:`https://admin.yizhengxun.com` + +--- + +### 影响2:独立的后端API + +**当前后端API:** +``` +/api/auth/* # 用户认证 +/api/users/* # 用户相关 +/api/projects/* # 项目管理 +/api/conversations/* # AI对话 +/api/kb/* # 知识库 +... +``` + +**新增管理端API:** +``` +/api/admin/auth/* # 管理员认证 ⭐ +/api/admin/users/* # 用户管理 ⭐ +/api/admin/agents/* # 智能体管理 ⭐ +/api/admin/feature-flags/* # Feature Flag管理 ⭐ +/api/admin/llm-models/* # LLM模型管理 ⭐ +/api/admin/tenants/* # 租户管理 ⭐ +/api/admin/monitoring/* # 监控与日志 ⭐ +/api/admin/statistics/* # 数据统计 ⭐ +/api/admin/cost-analysis/* # 成本分析 ⭐ +/api/admin/announcements/* # 公告管理 ⭐ +/api/admin/feedback/* # 反馈管理 ⭐ +... +``` + +--- + +### 影响3:权限体系扩展 + +**当前权限:** +``` +用户角色: +- user(普通用户) +- admin(管理员,但功能有限) +``` + +**新权限体系:** +``` +用户角色: +- user(普通用户) +- vip(VIP用户,高级功能) + +管理员角色:⭐ 新增 +- super_admin(超级管理员,所有权限) +- admin(管理员,部分权限) +- operator(运营人员,用户管理、数据统计) +- customer_service(客服,反馈管理、工单管理) +- auditor(审计员,只读权限,查看日志和报表) + +管理员权限:⭐ 新增 +- user_management # 用户管理 +- agent_management # 智能体管理 +- feature_flag_management # Feature Flag管理 +- llm_model_management # LLM模型管理 +- tenant_management # 租户管理 +- monitoring_access # 监控与日志访问 +- statistics_access # 数据统计访问 +- cost_analysis_access # 成本分析访问 +- system_config_management # 系统配置管理 +- announcement_management # 公告管理 +- feedback_management # 反馈管理 +``` + +--- + +### 影响4:数据库Schema扩展 + +**当前Schema(逻辑隔离):** +```sql +-- 平台层 +platform_schema.users +platform_schema.admin_logs + +-- 业务模块 +aia_schema.projects +pkb_schema.knowledge_bases +... +``` + +**新增管理端Schema:** +```sql +-- 管理端Schema ⭐ 新增 +CREATE SCHEMA admin_schema; + +-- 管理员相关 +admin_schema.admin_users # 管理员账号 +admin_schema.admin_roles # 管理员角色 +admin_schema.admin_permissions # 管理员权限 + +-- Feature Flag +admin_schema.feature_flags # Feature Flag配置 +admin_schema.feature_flag_history # Feature Flag变更历史 + +-- 智能体配置 +admin_schema.agent_configs # 智能体配置 +admin_schema.agent_prompt_history # 提示词版本历史 + +-- LLM模型配置 +admin_schema.llm_models # LLM模型配置 +admin_schema.llm_cost_config # 模型成本配置 + +-- 租户管理 +admin_schema.tenants # 租户表 +admin_schema.tenant_configs # 租户配置 + +-- 公告与通知 +admin_schema.announcements # 系统公告 +admin_schema.notifications # 站内通知 + +-- 反馈与工单 +admin_schema.feedback # 用户反馈 +admin_schema.tickets # 工单 + +-- 系统配置 +admin_schema.system_configs # 系统配置 + +-- 审计日志 +admin_schema.audit_logs # 审计日志 +admin_schema.operation_logs # 操作日志 +``` + +--- + +### 影响5:新增通用能力 + +**LLM Gateway需要增强:** +```typescript +// 原有功能 +interface LLMGateway { + chat(...): Promise; +} + +// 新增功能(支持运营管理端)⭐ +interface LLMGateway { + chat(...): Promise; + + // 管理端功能 + getUsageStats(startDate, endDate): Promise; // 使用量统计 + getCostStats(startDate, endDate): Promise; // 成本统计 + getUserUsage(userId): Promise; // 单用户使用量 + getModelConfig(): Promise; // 模型配置 + updateModelConfig(config): Promise; // 更新模型配置 + checkQuota(userId): Promise; // 配额检查 + resetQuota(userId): Promise; // 重置配额 +} +``` + +--- + +### 影响6:监控与日志增强 + +**新增监控能力:** +```typescript +// 监控服务 ⭐ 新增 +interface MonitoringService { + // 实时监控 + getSystemStatus(): Promise; // 系统状态 + getOnlineUsers(): Promise; // 在线用户数 + getApiMetrics(): Promise; // API指标(TPS/QPS) + + // 错误监控 + getErrorLogs(filter): Promise; // 错误日志 + getErrorStats(): Promise; // 错误统计 + + // 性能监控 + getSlowQueries(): Promise; // 慢查询 + getApiLatency(): Promise; // API延迟 + + // 审计日志 + getAuditLogs(filter): Promise; // 审计日志 + logAdminOperation(operation): Promise; // 记录管理员操作 +} +``` + +--- + +## 🎯 实施方案 + +### 阶段一:核心功能(MVP) + +**时间:1-2个月** + +**实现内容:** +1. ✅ 管理端前端(React + Ant Design Pro) +2. ✅ 管理端后端API(Node.js + Fastify) +3. ✅ 管理员认证与权限 +4. ✅ 用户管理(P0) +5. ✅ Feature Flag管理(P0) +6. ✅ LLM模型管理(P0) +7. ✅ 基础监控(错误日志、审计日志) + +--- + +### 阶段二:运营功能 + +**时间:1-2个月** + +**实现内容:** +1. ✅ 智能体提示词管理 +2. ✅ 数据统计与报表 +3. ✅ 成本分析与计费 +4. ✅ 公告与通知管理 + +--- + +### 阶段三:高级功能 + +**时间:1-2个月** + +**实现内容:** +1. ✅ 租户管理(私有化部署需要) +2. ✅ 反馈与工单系统 +3. ✅ 帮助文档管理 +4. ✅ 系统健康检查 +5. ✅ 数据库备份与恢复 + +--- + +## 📊 技术选型 + +### 前端技术栈 + +**推荐:Ant Design Pro** +``` +admin-frontend/ + ├── React 18 + ├── TypeScript + ├── Ant Design Pro (开箱即用的中台前端解决方案) + ├── Ant Design Charts (数据可视化) + ├── ProComponents (高级组件) + └── UmiJS (路由和构建) +``` + +**优势:** +- ✅ 专为中台/管理系统设计 +- ✅ 开箱即用的布局、菜单、权限、表格、表单 +- ✅ 丰富的数据可视化组件 +- ✅ 与Ant Design生态完美集成 + +--- + +### 后端技术栈 + +**继续使用现有技术栈:** +``` +backend/ + ├── Node.js + TypeScript + ├── Fastify + ├── Prisma + └── PostgreSQL +``` + +**新增模块:** +``` +backend/src/admin/ + ├── controllers/ # 管理端控制器 + ├── services/ # 管理端服务 + ├── middleware/ # 管理员认证中间件 + └── routes/ # 管理端路由 +``` + +--- + +## 🎯 总结 + +### 运营管理端的定位 + +**运营管理端是:** +- ✅ 一个**独立的应用系统**(独立前端 + 独立API) +- ✅ **横跨所有层次**的管理者(管理平台层、能力层、业务层) +- ✅ **商业运营的核心**(Feature Flag、成本控制、计费管理) +- ✅ **系统健康的保障**(监控、日志、健康检查) + +### 完整功能清单 + +**您提到的4个功能:** +1. ✅ 用户管理 +2. ✅ 智能体提示词设置 +3. ✅ 版本功能设置(Feature Flag) +4. ✅ 用户权限和版本设置 + +**我补充的11个功能:** +5. 🆕 租户管理(私有化部署) +6. 🆕 LLM模型管理(关键) +7. 🆕 系统配置管理 +8. 🆕 监控与日志(关键) +9. 🆕 数据统计与报表(关键) +10. 🆕 成本分析与计费(商业关键) +11. 🆕 公告与通知管理 +12. 🆕 帮助文档管理 +13. 🆕 反馈与工单管理 +14. 🆕 系统健康检查 +15. 🆕 数据库备份与恢复 + +**总计:15个功能模块** + +### 对系统总体设计的影响 + +1. ✅ **新增独立前端应用**(admin-frontend/) +2. ✅ **新增独立后端API**(/api/admin/*) +3. ✅ **扩展权限体系**(管理员角色、管理员权限) +4. ✅ **扩展数据库Schema**(admin_schema) +5. ✅ **增强LLM Gateway**(使用量统计、成本分析) +6. ✅ **新增监控服务**(实时监控、错误监控、性能监控) + +### 实施建议 + +**阶段一(P0):** 1-2个月 +- 用户管理、Feature Flag、LLM模型管理、基础监控 + +**阶段二(P1):** 1-2个月 +- 智能体管理、数据统计、成本分析、公告管理 + +**阶段三(P2):** 1-2个月 +- 租户管理、反馈工单、帮助文档、健康检查、备份恢复 + +--- + +**下一步建议:** +1. 确认运营管理端的功能优先级 +2. 设计管理端的UI原型 +3. 设计管理端的数据库表结构 +4. 设计管理端的API接口 + + + + + + + + + + + + + + + diff --git a/docs/00-系统总体设计/05-Schema隔离方案与成本分析.md b/docs/00-系统总体设计/05-Schema隔离方案与成本分析.md new file mode 100644 index 00000000..b634ac1e --- /dev/null +++ b/docs/00-系统总体设计/05-Schema隔离方案与成本分析.md @@ -0,0 +1,1055 @@ +# Schema隔离方案与成本分析 + +> **文档版本:** v1.0 +> **创建日期:** 2025-11-06 +> **最后更新:** 2025-11-06 +> **文档状态:** 架构分析 +> **作者:** 技术架构师 + +--- + +## 📋 核心问题 + +1. 什么是真正的Schema隔离? +2. 从逻辑隔离到物理隔离的改造成本有多高? +3. 现在做Schema隔离的成本高吗? +4. 当前业务体量下,需要现在做吗? +5. 最佳实施时机是什么时候? + +--- + +## 🎯 两种隔离方式对比 + +### 逻辑隔离(当前方案) + +**定义:** +- 所有表都在同一个Schema(`public`)中 +- 通过**表名前缀**来区分不同模块 +- 代码层面按模块组织 + +**示例:** +```sql +-- 所有表都在public schema +public.users -- 用户表 +public.aia_projects -- AI问答模块的项目表 +public.aia_conversations -- AI问答模块的对话表 +public.asl_projects -- AI文献模块的项目表 +public.asl_literature_items -- AI文献模块的文献表 +public.pkb_knowledge_bases -- 知识库模块的知识库表 +public.dc_projects -- 数据清洗模块的项目表 +public.review_tasks -- 稿件审查模块的任务表 +``` + +**Prisma Schema示例:** +```prisma +// 逻辑隔离:使用@@map指定表名 +model AiaProject { + id String @id @default(uuid()) + userId String @map("user_id") + name String + // ... + + @@map("aia_projects") // 表名前缀标识模块 +} + +model AslProject { + id String @id @default(uuid()) + userId String @map("user_id") + name String + // ... + + @@map("asl_projects") // 表名前缀标识模块 +} +``` + +**查询方式:** +```typescript +// 业务代码无感知 +const project = await prisma.aiaProject.findUnique({ + where: { id: projectId } +}); +``` + +--- + +### 物理隔离(真正的Schema隔离) + +**定义:** +- 为每个模块创建**独立的PostgreSQL Schema** +- 表分散在不同的Schema中 +- 数据库层面真正隔离 + +**示例:** +```sql +-- 创建独立Schema +CREATE SCHEMA platform_schema; -- 平台层 +CREATE SCHEMA aia_schema; -- AI问答模块 +CREATE SCHEMA asl_schema; -- AI文献模块 +CREATE SCHEMA pkb_schema; -- 知识库模块 +CREATE SCHEMA dc_schema; -- 数据清洗模块 +CREATE SCHEMA review_schema; -- 稿件审查模块 +CREATE SCHEMA admin_schema; -- 运营管理端 + +-- 表分布在不同Schema +platform_schema.users -- 用户表 +aia_schema.projects -- AI问答的项目表 +aia_schema.conversations -- AI问答的对话表 +asl_schema.projects -- AI文献的项目表 +asl_schema.literature_items -- AI文献的文献表 +pkb_schema.knowledge_bases -- 知识库表 +dc_schema.projects -- 数据清洗的项目表 +review_schema.tasks -- 稿件审查的任务表 +``` + +**Prisma Schema示例:** +```prisma +// 物理隔离:指定schema +model AiaProject { + id String @id @default(uuid()) + userId String @map("user_id") + name String + // ... + + @@map("projects") // 表名不需要前缀 + @@schema("aia_schema") // 指定Schema ⭐ +} + +model AslProject { + id String @id @default(uuid()) + userId String @map("user_id") + name String + // ... + + @@map("projects") // 表名不需要前缀 + @@schema("asl_schema") // 指定Schema ⭐ +} +``` + +**查询方式:** +```typescript +// 业务代码无感知(Prisma会自动处理) +const project = await prisma.aiaProject.findUnique({ + where: { id: projectId } +}); + +// 实际执行的SQL: +// SELECT * FROM aia_schema.projects WHERE id = $1 +``` + +--- + +## 📊 两种方案对比 + +| 维度 | 逻辑隔离(当前) | 物理隔离(目标) | +|------|----------------|----------------| +| **复杂度** | ⭐ 简单 | ⭐⭐⭐ 中等 | +| **实施难度** | ⭐ 低 | ⭐⭐⭐ 中等 | +| **维护成本** | ⭐⭐ 低 | ⭐⭐⭐ 中等 | +| **隔离性** | ⭐⭐ 中等(代码层面) | ⭐⭐⭐⭐⭐ 高(数据库层面) | +| **权限控制** | ⭐⭐ 中等 | ⭐⭐⭐⭐⭐ 高(数据库级别) | +| **微服务拆分** | ⭐⭐⭐ 需要改造 | ⭐⭐⭐⭐⭐ 易于拆分 | +| **模块独立部署** | ⭐⭐ 困难 | ⭐⭐⭐⭐⭐ 容易 | +| **跨模块查询** | ⭐⭐⭐⭐⭐ 容易(同一Schema) | ⭐⭐⭐ 需要跨Schema查询 | +| **备份恢复** | ⭐⭐⭐ 整体备份 | ⭐⭐⭐⭐⭐ 可按Schema备份 | +| **当前适用性** | ✅ 适合当前阶段 | ⚠️ 未来阶段 | + +--- + +## 💰 改造成本分析 + +### 从逻辑隔离到物理隔离需要改什么? + +#### 1. 数据库层面改造 + +**步骤1:创建Schema** +```sql +-- 创建新Schema(非常简单) +CREATE SCHEMA platform_schema; +CREATE SCHEMA aia_schema; +CREATE SCHEMA asl_schema; +CREATE SCHEMA pkb_schema; +CREATE SCHEMA dc_schema; +CREATE SCHEMA review_schema; +CREATE SCHEMA admin_schema; +``` + +**工作量:** 10分钟 + +--- + +**步骤2:创建新表结构** +```sql +-- 在新Schema中创建表 +CREATE TABLE aia_schema.projects ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + -- ... 其他字段 + + -- 外键可能需要跨Schema + CONSTRAINT fk_user FOREIGN KEY (user_id) + REFERENCES platform_schema.users(id) +); + +-- 类似的,为所有表创建新的表结构 +``` + +**工作量:** +- 自动生成(Prisma Migrate):1-2小时 +- 手动创建:1天 + +--- + +**步骤3:数据迁移** +```sql +-- 迁移数据(从public到新Schema) +INSERT INTO aia_schema.projects +SELECT * FROM public.aia_projects; + +INSERT INTO aia_schema.conversations +SELECT * FROM public.aia_conversations; + +-- ... 为所有表迁移数据 +``` + +**工作量:** +- 数据量小(<100万行):1-2小时 +- 数据量大(>100万行):半天到1天 + +**风险:** +- ⚠️ 需要停机(或做在线迁移) +- ⚠️ 需要验证数据完整性 +- ⚠️ 外键约束可能有问题 + +--- + +**步骤4:清理旧表** +```sql +-- 删除旧表(确认无误后) +DROP TABLE public.aia_projects; +DROP TABLE public.aia_conversations; +-- ... +``` + +**工作量:** 1小时 + +**总计数据库改造工作量:** +- 自动化:半天到1天 +- 手动:2-3天 + +--- + +#### 2. 代码层面改造 + +**步骤1:修改Prisma Schema** + +**逻辑隔离(当前):** +```prisma +model AiaProject { + id String @id @default(uuid()) + userId String @map("user_id") + name String + + @@map("aia_projects") // 需要前缀 +} +``` + +**物理隔离(修改后):** +```prisma +model AiaProject { + id String @id @default(uuid()) + userId String @map("user_id") + name String + + @@map("projects") // 不需要前缀 + @@schema("aia_schema") // 新增:指定Schema ⭐ +} +``` + +**工作量:** +- 修改所有Model(约50-100个Model) +- 时间:2-4小时 + +--- + +**步骤2:处理外键和关联** + +**问题:跨Schema的外键** +```prisma +// User在platform_schema +model User { + id String @id @default(uuid()) + email String + + @@map("users") + @@schema("platform_schema") +} + +// Project在aia_schema +model AiaProject { + id String @id @default(uuid()) + userId String @map("user_id") + + // 跨Schema关联 ⚠️ + user User @relation(fields: [userId], references: [id]) + + @@map("projects") + @@schema("aia_schema") +} +``` + +**Prisma处理:** +- ✅ Prisma会自动处理跨Schema的关联 +- ✅ 生成的SQL会包含正确的Schema前缀 +- ⚠️ 但需要测试验证 + +**工作量:** +- 测试所有跨Schema关联:半天到1天 + +--- + +**步骤3:业务代码改造** + +**好消息:业务代码几乎不需要改!** ✅ + +```typescript +// 业务代码完全不变 +const project = await prisma.aiaProject.findUnique({ + where: { id: projectId }, + include: { + user: true // 跨Schema关联,Prisma自动处理 + } +}); +``` + +**原因:** +- ✅ Prisma ORM抽象了底层Schema +- ✅ 业务代码只依赖Prisma Model,不直接写SQL + +**例外情况:** +- ⚠️ 如果有原始SQL查询(`prisma.$queryRaw`),需要修改 +- ⚠️ 如果有数据库视图(View),需要修改 +- ⚠️ 如果有数据库函数(Function),需要修改 + +**工作量:** +- 业务代码:0改造(如果没有原始SQL) +- 原始SQL:需要逐个修改(估计10-20处) +- 时间:半天到1天 + +--- + +**步骤4:运行Prisma Migrate** + +```bash +# 生成新的迁移文件 +npx prisma migrate dev --name schema-isolation + +# 或手动迁移(生产环境) +npx prisma migrate deploy +``` + +**工作量:** +- 开发环境:10分钟 +- 生产环境:需要详细计划(半天准备) + +--- + +#### 3. 测试验证 + +**需要测试的内容:** +1. ✅ 所有API接口是否正常 +2. ✅ 跨Schema关联是否正常(用户-项目、项目-对话等) +3. ✅ 外键约束是否正常 +4. ✅ 数据完整性检查 +5. ✅ 性能测试(跨Schema查询性能) +6. ✅ 备份恢复测试 + +**工作量:** +- 单元测试:自动化,1-2小时 +- 集成测试:半天 +- 端到端测试:1天 +- 总计:1-2天 + +--- + +### 总改造成本 + +**开发环境:** +| 任务 | 工作量 | +|------|-------| +| 数据库Schema创建 | 10分钟 | +| Prisma Schema修改 | 2-4小时 | +| 数据迁移脚本 | 1-2小时 | +| 原始SQL修改 | 半天 | +| 测试验证 | 1-2天 | +| **总计** | **2-3天** | + +**生产环境:** +| 任务 | 工作量 | +|------|-------| +| 迁移方案设计 | 半天 | +| 数据备份 | 1小时 | +| 数据迁移 | 半天到1天 | +| 验证测试 | 1天 | +| 应急回滚方案 | 半天 | +| **总计** | **3-4天** | + +**风险评估:** +- ⚠️ **风险等级:中等** +- ⚠️ **主要风险:数据迁移失败、外键约束问题、停机时间** +- ✅ **缓解措施:详细测试、回滚方案、灰度发布** + +--- + +## 🤔 现在做 vs 以后做 + +### 现在做的优势 ✅ + +1. **数据量小,迁移快** + ``` + 当前数据量(估算): + - 用户:< 100 + - 项目:< 500 + - 对话:< 5,000 + - 文档:< 1,000 + + 迁移时间:< 1小时 + 风险:低 + ``` + +2. **业务复杂度低,测试简单** + ``` + 当前模块: + - ✅ AIA(AI问答)- 已完成 + - ✅ PKB(知识库)- 已完成 + - ⏳ ASL(AI文献)- 未开发 + - ⏳ DC、SSA、ST、RVW - 未开发 + + 需要测试的模块少 + ``` + +3. **为未来打下坚实基础** + ``` + 优势: + - ✅ 新模块(ASL、DC等)直接用物理隔离 + - ✅ 避免未来大规模改造 + - ✅ 支持模块独立部署 + - ✅ 支持微服务拆分 + ``` + +4. **团队学习成本低** + ``` + 当前团队小: + - 开发人员少,沟通成本低 + - 代码改动影响范围小 + - 易于推广新规范 + ``` + +--- + +### 现在做的劣势 ❌ + +1. **需要投入时间** + ``` + 开发时间:2-3天(开发环境) + 生产时间:3-4天(生产环境) + 总计:1周 + + 延迟:ASL模块开发延迟1周 + ``` + +2. **有一定风险** + ``` + 风险: + - 数据迁移失败 + - 外键约束问题 + - 需要停机 + + 缓解:详细测试、回滚方案 + ``` + +3. **当前没有紧迫需求** + ``` + 事实: + - 没有微服务拆分需求 + - 没有模块独立部署需求 + - 没有私有化部署需求 + + 结论:暂时不需要物理隔离 + ``` + +--- + +### 以后做的优势 ✅ + +1. **延迟投入** + ``` + 当前:专注业务开发(ASL、DC等) + 未来:有需求时再改造 + + 优势:快速推进业务 + ``` + +2. **需求明确时再做** + ``` + 触发条件: + - 需要微服务拆分 + - 需要模块独立部署 + - 需要私有化部署 + - 数据量大,需要性能优化 + + 此时改造目标明确 + ``` + +--- + +### 以后做的劣势 ❌ + +1. **数据量大,迁移慢** + ``` + 未来数据量(估算): + - 用户:10,000+ + - 项目:100,000+ + - 对话:1,000,000+ + - 文档:500,000+ + + 迁移时间:数小时到数天 + 风险:高 + ``` + +2. **业务复杂,测试困难** + ``` + 未来模块: + - ✅ 7个模块全部上线 + - ✅ 复杂的跨模块关联 + - ✅ 大量用户在使用 + + 测试难度:高 + 回滚成本:高 + ``` + +3. **需要停机或在线迁移** + ``` + 停机迁移: + - 影响用户体验 + - 可能丢失订单 + + 在线迁移: + - 技术复杂度高 + - 需要双写、数据同步 + ``` + +4. **改造成本成倍增长** + ``` + 未来改造成本(估算): + - 开发时间:1-2周 + - 测试时间:1-2周 + - 生产迁移:1周 + - 总计:3-5周 + + 现在的3-5倍! + ``` + +--- + +## 🎯 建议与决策 + +### 方案A:现在做物理隔离 ⭐⭐⭐⭐⭐ **强烈推荐** + +**适用场景:** +- ✅ 有1周时间投入 +- ✅ 重视长期架构健康 +- ✅ 未来有模块独立部署需求 +- ✅ 未来有私有化部署需求 + +**理由:** +1. **成本最低**:数据量小,迁移快(< 1小时) +2. **风险最低**:业务简单,测试容易 +3. **收益最大**:为未来7个模块打下坚实基础 +4. **避免技术债**:避免未来大规模改造 + +**实施计划:** +``` +Week 1:Schema隔离改造(2-3天) + Day 1-2:开发环境改造 + - 创建Schema + - 修改Prisma Schema + - 数据迁移脚本 + - 测试验证 + + Day 3:生产环境迁移 + - 备份数据 + - 执行迁移 + - 验证测试 + - 监控 + +Week 2:继续ASL模块开发 +``` + +**投入产出比:** ⭐⭐⭐⭐⭐ 极高 + +--- + +### 方案B:暂不做物理隔离,继续逻辑隔离 ⭐⭐⭐ **可接受** + +**适用场景:** +- ✅ 时间紧迫,必须尽快上线ASL模块 +- ✅ 近期(6个月内)没有微服务拆分需求 +- ✅ 近期没有私有化部署需求 + +**理由:** +1. **快速推进业务**:专注ASL模块开发 +2. **逻辑隔离足够用**:当前阶段可以满足需求 +3. **延迟改造**:未来有需求时再做 + +**但需要遵守纪律:** ⚠️ 非常重要 +``` +严格使用表名前缀: +- aia_* +- asl_* +- pkb_* +- dc_* +- review_* +- admin_* + +为未来改造打基础 +``` + +**触发改造的条件:** +1. 需要微服务拆分 +2. 需要模块独立部署 +3. 需要私有化部署 +4. 数据量超过100万行 + +**投入产出比:** ⭐⭐⭐ 中等 + +--- + +### 方案C:混合方案(折中) ⭐⭐⭐⭐ **推荐** + +**方案描述:** +1. **新模块使用物理隔离** + - ASL、DC、SSA、ST、RVW等新模块直接用物理隔离 + - 从一开始就创建独立Schema + +2. **老模块暂时保持逻辑隔离** + - AIA、PKB等已完成模块暂时不动 + - 等未来有需求时再迁移 + +**实施计划:** +``` +立即: +1. 创建新Schema + CREATE SCHEMA asl_schema; + CREATE SCHEMA dc_schema; + CREATE SCHEMA admin_schema; + ... + +2. ASL模块使用物理隔离 + model AslProject { + @@schema("asl_schema") + } + +未来(6-12个月后): +3. 迁移老模块 + - 迁移AIA到aia_schema + - 迁移PKB到pkb_schema +``` + +**优势:** +- ✅ 立即投入小(只需创建Schema,10分钟) +- ✅ 新模块享受物理隔离的好处 +- ✅ 老模块延迟改造,风险低 + +**投入产出比:** ⭐⭐⭐⭐ 高 + +--- + +## 📊 决策矩阵 + +| 方案 | 立即投入 | 未来成本 | 风险 | 灵活性 | 推荐度 | +|------|---------|---------|------|-------|-------| +| **方案A:现在做物理隔离** | ⭐⭐⭐ 中(1周) | ⭐⭐⭐⭐⭐ 低 | ⭐⭐⭐⭐ 低 | ⭐⭐⭐⭐⭐ 高 | ⭐⭐⭐⭐⭐ | +| **方案B:继续逻辑隔离** | ⭐⭐⭐⭐⭐ 零 | ⭐⭐ 高 | ⭐⭐⭐ 中 | ⭐⭐ 低 | ⭐⭐⭐ | +| **方案C:混合方案** | ⭐⭐⭐⭐⭐ 低(10分钟) | ⭐⭐⭐⭐ 中 | ⭐⭐⭐⭐ 低 | ⭐⭐⭐⭐ 高 | ⭐⭐⭐⭐ | + +--- + +## 🎯 最终建议 + +### 我的推荐:方案A(现在做物理隔离)⭐⭐⭐⭐⭐ + +**理由:** + +**1. 成本最低的时间窗口** +``` +当前: +- 数据量小(< 1万行) +- 迁移时间:< 1小时 +- 测试时间:1-2天 +- 总成本:1周 + +6个月后: +- 数据量大(100万行+) +- 迁移时间:数小时到数天 +- 测试时间:1-2周 +- 总成本:3-5周 + +成本差异:3-5倍! +``` + +**2. 最佳学习机会** +``` +当前: +- 团队小,沟通成本低 +- 模块少,改造范围小 +- 易于建立规范 + +未来: +- 团队大,沟通成本高 +- 模块多,改造范围大 +- 难以统一规范 +``` + +**3. 避免技术债累积** +``` +技术债的特点: +- 时间越久,利息越高 +- 改造成本成倍增长 +- 影响业务创新 + +现在改造: +- 一次性还清 +- 轻装上阵 +``` + +**4. 为未来打下坚实基础** +``` +未来需求(6-12个月): +- 审稿系统独立部署 +- AI文献模块独立销售 +- 私有化部署 +- 微服务拆分 + +物理隔离是基础设施 +``` + +--- + +### 如果必须选择方案B或C + +**推荐:方案C(混合方案)** + +**最低要求:** +1. ✅ 立即创建所有Schema(10分钟) +2. ✅ 新模块(ASL、DC等)使用物理隔离 +3. ✅ 老模块(AIA、PKB)延迟迁移 + +**触发老模块迁移的条件:** +- 数据量超过50万行 +- 需要模块独立部署 +- 需要私有化部署 + +--- + +## 📋 实施清单(方案A) + +### 准备阶段(1天) + +- [ ] 详细阅读Prisma Schema文档 +- [ ] 设计Schema结构(platform_schema, aia_schema, asl_schema等) +- [ ] 编写数据迁移脚本 +- [ ] 准备测试用例 +- [ ] 准备回滚方案 + +### 开发环境改造(1-2天) + +- [ ] 创建所有Schema +- [ ] 修改Prisma Schema(所有Model) +- [ ] 运行Prisma Migrate +- [ ] 迁移开发环境数据 +- [ ] 运行单元测试 +- [ ] 运行集成测试 +- [ ] 手动测试所有功能 + +### 生产环境迁移(1天) + +- [ ] 备份数据库 +- [ ] 创建Schema +- [ ] 执行数据迁移 +- [ ] 验证数据完整性 +- [ ] 启动应用 +- [ ] 监控错误日志 +- [ ] 性能测试 + +### 验收标准 + +- [ ] 所有API接口正常 +- [ ] 跨Schema关联正常 +- [ ] 外键约束正常 +- [ ] 数据完整性正常 +- [ ] 性能无明显下降 +- [ ] 无错误日志 + +--- + +## 💡 技术细节:如何实现物理隔离 + +### Step 1: 创建Schema + +```sql +-- 创建所有Schema +CREATE SCHEMA platform_schema; +CREATE SCHEMA aia_schema; +CREATE SCHEMA asl_schema; +CREATE SCHEMA pkb_schema; +CREATE SCHEMA dc_schema; +CREATE SCHEMA ssa_schema; +CREATE SCHEMA st_schema; +CREATE SCHEMA review_schema; +CREATE SCHEMA admin_schema; +``` + +--- + +### Step 2: 修改Prisma Schema + +**修改database配置:** +```prisma +generator client { + provider = "prisma-client-js" + previewFeatures = ["multiSchema"] // 启用多Schema支持 ⭐ +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + schemas = [ + "platform_schema", + "aia_schema", + "asl_schema", + "pkb_schema", + "dc_schema", + "ssa_schema", + "st_schema", + "review_schema", + "admin_schema" + ] // 声明所有Schema ⭐ +} +``` + +**修改Model:** +```prisma +// 平台层 +model User { + id String @id @default(uuid()) + email String @unique + password String + // ... + + @@map("users") + @@schema("platform_schema") // 指定Schema ⭐ +} + +// AI问答模块 +model AiaProject { + id String @id @default(uuid()) + userId String @map("user_id") + name String + + user User @relation(fields: [userId], references: [id]) + + @@map("projects") // 不需要前缀 + @@schema("aia_schema") // 指定Schema ⭐ +} + +// AI文献模块 +model AslProject { + id String @id @default(uuid()) + userId String @map("user_id") + name String + + user User @relation(fields: [userId], references: [id]) + + @@map("projects") + @@schema("asl_schema") // 指定Schema ⭐ +} +``` + +--- + +### Step 3: 生成迁移 + +```bash +# 生成迁移文件 +npx prisma migrate dev --name schema-isolation --create-only + +# 查看生成的SQL +# migrations/20251106_schema-isolation/migration.sql +``` + +**生成的SQL示例:** +```sql +-- CreateSchema +CREATE SCHEMA IF NOT EXISTS "platform_schema"; +CREATE SCHEMA IF NOT EXISTS "aia_schema"; +-- ... + +-- CreateTable +CREATE TABLE "platform_schema"."users" ( + "id" UUID NOT NULL DEFAULT gen_random_uuid(), + "email" VARCHAR(255) NOT NULL, + -- ... + PRIMARY KEY ("id") +); + +CREATE TABLE "aia_schema"."projects" ( + "id" UUID NOT NULL DEFAULT gen_random_uuid(), + "user_id" UUID NOT NULL, + "name" VARCHAR(255) NOT NULL, + -- ... + + CONSTRAINT "fk_user" FOREIGN KEY ("user_id") + REFERENCES "platform_schema"."users"("id") ON DELETE CASCADE, + + PRIMARY KEY ("id") +); +``` + +--- + +### Step 4: 数据迁移 + +**编写迁移脚本:** +```sql +-- migrate-data.sql + +-- 迁移用户表 +INSERT INTO platform_schema.users +SELECT * FROM public.users; + +-- 迁移AIA模块 +INSERT INTO aia_schema.projects +SELECT * FROM public.aia_projects; + +INSERT INTO aia_schema.conversations +SELECT * FROM public.aia_conversations; + +INSERT INTO aia_schema.messages +SELECT * FROM public.aia_messages; + +-- 迁移PKB模块 +INSERT INTO pkb_schema.knowledge_bases +SELECT * FROM public.knowledge_bases; + +INSERT INTO pkb_schema.documents +SELECT * FROM public.documents; + +-- ... 其他表 +``` + +**执行迁移:** +```bash +# 方式1:使用psql +psql -U postgres -d ai_clinical_research -f migrate-data.sql + +# 方式2:使用Prisma +npx prisma db execute --file migrate-data.sql +``` + +--- + +### Step 5: 验证 + +```typescript +// test-schema-isolation.ts + +async function testSchemaIsolation() { + // 测试跨Schema关联 + const project = await prisma.aiaProject.findUnique({ + where: { id: 'test-id' }, + include: { + user: true // 跨Schema关联(platform_schema.users) + } + }); + + console.log('跨Schema关联正常:', project); + + // 测试所有模块 + const stats = { + users: await prisma.user.count(), + aiaProjects: await prisma.aiaProject.count(), + aslProjects: await prisma.aslProject.count(), + knowledgeBases: await prisma.knowledgeBase.count(), + }; + + console.log('数据统计:', stats); +} +``` + +--- + +## 📊 成本收益总结 + +### 投入成本 + +| 项目 | 时间 | 风险 | +|------|------|------| +| 学习和准备 | 1天 | 低 | +| 开发环境改造 | 1-2天 | 低 | +| 测试验证 | 1-2天 | 中 | +| 生产环境迁移 | 1天 | 中 | +| **总计** | **5-6天** | **中等** | + +### 预期收益 + +| 收益 | 价值 | +|------|------| +| 模块独立部署 | ⭐⭐⭐⭐⭐ | +| 微服务拆分 | ⭐⭐⭐⭐⭐ | +| 数据库级别权限控制 | ⭐⭐⭐⭐ | +| 按Schema备份恢复 | ⭐⭐⭐⭐ | +| 避免未来大规模改造 | ⭐⭐⭐⭐⭐ | +| **总价值** | **极高** | + +--- + +## 🎯 最终建议总结 + +### 我的建议:立即做物理隔离(方案A) + +**核心理由:** +1. ✅ **现在是成本最低的时间窗口**(数据量小,改造快) +2. ✅ **避免技术债累积**(未来改造成本成倍增长) +3. ✅ **为7个模块打下坚实基础**(模块独立部署、微服务拆分) +4. ✅ **支持未来商业模式**(模块化销售、私有化部署) + +**投入:** 1周 +**收益:** 避免未来3-5倍的改造成本 +**投入产出比:** 极高 + +--- + +**如果您决定采纳方案A,我可以立即帮您:** +1. 设计详细的Schema结构 +2. 编写Prisma Schema修改方案 +3. 编写数据迁移脚本 +4. 制定详细的实施计划 +5. 准备测试用例和验收标准 + +**您觉得呢?** 😊 + + + + + + + + + + + + + + + diff --git a/docs/00-系统总体设计/06-模块独立部署与单机版方案.md b/docs/00-系统总体设计/06-模块独立部署与单机版方案.md new file mode 100644 index 00000000..dd3fc1a1 --- /dev/null +++ b/docs/00-系统总体设计/06-模块独立部署与单机版方案.md @@ -0,0 +1,1554 @@ +# 模块独立部署与单机版方案 + +> **文档版本:** v1.0 +> **创建日期:** 2025-11-06 +> **最后更新:** 2025-11-06 +> **文档状态:** 架构设计 +> **作者:** 技术架构师 + +--- + +## 📋 核心问题 + +1. **如何实现每个模块独立部署?** + - 模块依赖平台层和通用能力层,如何独立? + - 如何变成独立产品销售? + - 如何实现本地化部署? + +2. **如何开发单机版?** + - 如何利用Electron框架? + - 如何复用现有代码? + - 架构如何设计? + +--- + +## 🎯 Part 1:模块独立部署方案 + +### 核心理念:独立部署 ≠ 完全孤立 + +**关键洞察:** +``` +独立部署的真正含义: +✅ 可以单独打包 +✅ 可以单独运行 +✅ 可以单独销售 +✅ 可以单独升级 + +但不是: +❌ 完全不依赖其他代码 +❌ 完全不共享基础设施 +``` + +**类比:** +``` +就像一个独立的手机App: +- 它依赖操作系统(Android/iOS) +- 它依赖系统API(摄像头、定位等) +- 但它可以独立下载、安装、运行、卸载 + +模块独立部署也是一样: +- 它依赖平台层(用户认证、存储等) +- 它依赖通用能力层(LLM网关、文档处理等) +- 但它可以独立打包、部署、销售 +``` + +--- + +## 📦 方案一:完整打包(推荐用于独立产品) + +### 适用场景 + +- ✅ 模块作为独立产品销售(如审稿系统) +- ✅ 客户要求完全本地化部署 +- ✅ 不依赖其他模块或平台 + +### 核心思路 + +**将依赖的平台层和通用能力层一起打包** + +``` +审稿系统独立产品包: +┌─────────────────────────────────────────┐ +│ RVW审稿系统独立产品 │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ 业务模块层 │ │ +│ │ RVW(稿件审查) │ │ +│ └────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ 通用能力层(必需部分) │ │ +│ │ - LLM网关 │ │ +│ │ - 文档处理引擎 │ │ +│ └────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ 平台基础层(必需部分) │ │ +│ │ - 用户认证(简化版) │ │ +│ │ - 存储服务 │ │ +│ │ - 监控日志 │ │ +│ └────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ 独立数据库 │ │ +│ │ PostgreSQL(Docker) │ │ +│ └────────────────────────────────────┘ │ +└─────────────────────────────────────────┘ +``` + +--- + +### 技术实现 + +#### Step 1: 代码组织(Monorepo) + +``` +AIclinicalresearch/ + ├── packages/ # Monorepo包管理 + │ │ + │ ├── platform-core/ # 平台核心(可复用) + │ │ ├── auth/ # 用户认证 + │ │ ├── storage/ # 存储服务 + │ │ ├── monitoring/ # 监控日志 + │ │ └── index.ts + │ │ + │ ├── capabilities-core/ # 通用能力核心(可复用) + │ │ ├── llm-gateway/ # LLM网关 + │ │ ├── document-processor/ # 文档处理 + │ │ ├── rag-engine/ # RAG引擎 + │ │ └── index.ts + │ │ + │ ├── module-aia/ # AI问答模块 + │ │ ├── backend/ + │ │ ├── frontend/ + │ │ └── package.json + │ │ + │ ├── module-asl/ # AI文献模块 + │ │ ├── backend/ + │ │ ├── frontend/ + │ │ └── package.json + │ │ + │ ├── module-review/ # 审稿系统模块 + │ │ ├── backend/ + │ │ ├── frontend/ + │ │ └── package.json + │ │ + │ └── ... + │ + ├── products/ # 独立产品打包 + │ │ + │ ├── review-system/ # 审稿系统独立产品 ⭐ + │ │ ├── docker-compose.yml + │ │ ├── deploy.sh + │ │ ├── README.md + │ │ └── package.json # 依赖关系 + │ │ + │ ├── literature-system/ # AI文献独立产品 ⭐ + │ │ └── ... + │ │ + │ └── full-platform/ # 完整平台 + │ └── ... + │ + └── ... +``` + +--- + +#### Step 2: 依赖管理(package.json) + +**审稿系统独立产品的依赖:** +```json +// products/review-system/package.json +{ + "name": "@yizhengxun/review-system", + "version": "1.0.0", + "private": true, + "dependencies": { + // 平台核心(必需) + "@yizhengxun/platform-core": "workspace:*", + + // 通用能力(必需) + "@yizhengxun/capabilities-core": "workspace:*", + + // 业务模块 + "@yizhengxun/module-review": "workspace:*" + }, + "scripts": { + "build": "node build.js", + "deploy": "sh deploy.sh" + } +} +``` + +**平台核心的选择性导出:** +```typescript +// packages/platform-core/index.ts + +// 完整导出(用于完整平台) +export * from './auth'; +export * from './storage'; +export * from './monitoring'; +export * from './notification'; + +// 精简导出(用于独立产品) +export { + // 只导出必需的认证功能 + authMiddleware, + jwtService +} from './auth'; + +export { + // 只导出必需的存储功能 + uploadFile, + downloadFile +} from './storage'; + +export { + // 只导出必需的监控功能 + logError, + logAudit +} from './monitoring'; +``` + +--- + +#### Step 3: 打包脚本 + +**审稿系统独立打包:** +```javascript +// products/review-system/build.js + +const { build } = require('esbuild'); +const { dependencies } = require('./package.json'); + +async function buildReviewSystem() { + console.log('构建审稿系统独立产品...'); + + // 1. 构建后端 + await build({ + entryPoints: ['../../packages/module-review/backend/src/index.ts'], + bundle: true, + platform: 'node', + target: 'node18', + outfile: 'dist/backend/index.js', + external: ['pg', 'fastify', 'prisma'], // 不打包这些大库 + + // 自动包含依赖 + plugins: [ + // 自动解析workspace依赖 + resolveWorkspaceDependencies() + ] + }); + + // 2. 构建前端 + await build({ + entryPoints: ['../../packages/module-review/frontend/src/main.tsx'], + bundle: true, + platform: 'browser', + outfile: 'dist/frontend/index.js', + loader: { '.tsx': 'tsx' } + }); + + // 3. 复制必要文件 + copyFiles([ + 'docker-compose.yml', + 'README.md', + 'deploy.sh' + ]); + + console.log('构建完成!'); +} + +buildReviewSystem(); +``` + +--- + +#### Step 4: Docker部署配置 + +**审稿系统独立部署:** +```yaml +# products/review-system/docker-compose.yml + +version: '3.8' + +services: + # PostgreSQL数据库(独立) + postgres: + image: postgres:15-alpine + container_name: review-system-postgres + environment: + POSTGRES_DB: review_system + POSTGRES_USER: review + POSTGRES_PASSWORD: ${DB_PASSWORD} + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - review-network + + # 后端服务(包含平台层和通用能力层) + backend: + build: + context: ./dist/backend + container_name: review-system-backend + environment: + DATABASE_URL: postgresql://review:${DB_PASSWORD}@postgres:5432/review_system + LLM_API_KEY: ${LLM_API_KEY} + depends_on: + - postgres + networks: + - review-network + + # 前端服务 + frontend: + build: + context: ./dist/frontend + container_name: review-system-frontend + ports: + - "80:80" + depends_on: + - backend + networks: + - review-network + +volumes: + postgres_data: + +networks: + review-network: + driver: bridge +``` + +--- + +#### Step 5: 一键部署脚本 + +```bash +#!/bin/bash +# products/review-system/deploy.sh + +echo "======================================" +echo " 审稿系统独立部署脚本" +echo "======================================" + +# 1. 检查Docker +if ! command -v docker &> /dev/null; then + echo "错误:未安装Docker" + exit 1 +fi + +# 2. 设置环境变量 +read -p "请输入数据库密码: " DB_PASSWORD +read -p "请输入LLM API密钥: " LLM_API_KEY + +export DB_PASSWORD +export LLM_API_KEY + +# 3. 启动服务 +docker-compose up -d + +# 4. 等待服务启动 +echo "等待服务启动..." +sleep 10 + +# 5. 初始化数据库 +docker exec review-system-backend npx prisma migrate deploy + +# 6. 创建默认管理员 +docker exec review-system-backend node scripts/create-admin.js + +echo "======================================" +echo " 部署完成!" +echo " 访问地址:http://localhost" +echo " 管理员账号:admin@review.com" +echo " 密码:admin123(请及时修改)" +echo "======================================" +``` + +--- + +### 关键技术点 + +#### 1. 平台层的精简和适配 + +**完整平台的用户认证(复杂):** +```typescript +// 完整平台 +- 多租户支持 +- RBAC权限控制 +- SSO单点登录 +- 第三方登录(微信、企业微信等) +- 复杂的权限树 +``` + +**审稿系统的用户认证(简化):** +```typescript +// 审稿系统独立产品 +- 简化的用户认证(邮箱+密码) +- 简单的角色(管理员、编辑、审稿人) +- 基于JWT的Token认证 +- 无SSO、无多租户 +``` + +**实现方式:** +```typescript +// packages/platform-core/auth/simple-auth.ts +// 为独立产品提供简化版认证 + +export class SimpleAuthService { + // 只保留核心功能 + async login(email: string, password: string) { } + async register(email: string, password: string) { } + async verifyToken(token: string) { } + async logout(userId: string) { } + + // 移除复杂功能 + // ❌ async loginWithWeChat() + // ❌ async setupSSO() + // ❌ async multiTenantCheck() +} +``` + +--- + +#### 2. 通用能力层的精简和适配 + +**完整平台的LLM网关(复杂):** +```typescript +// 完整平台 +- 支持10+种模型 +- Feature Flag控制 +- 配额管理 +- 成本分析 +- A/B测试 +``` + +**审稿系统的LLM网关(简化):** +```typescript +// 审稿系统独立产品 +- 只支持1-2种模型(DeepSeek + Claude) +- 简单的配额检查 +- 无Feature Flag +- 无成本分析 +``` + +**实现方式:** +```typescript +// packages/capabilities-core/llm-gateway/simple-llm.ts + +export class SimpleLLMGateway { + // 只保留核心功能 + async chat(params: ChatParams) { } + async checkQuota(userId: string) { } + + // 移除复杂功能 + // ❌ async getCostStats() + // ❌ async selectModelByVersion() + // ❌ async runABTest() +} +``` + +--- + +#### 3. 数据库的隔离 + +**完整平台的数据库(复杂):** +```sql +-- 平台层Schema +platform_schema.users +platform_schema.tenants +platform_schema.feature_flags +-- ... + +-- 所有模块Schema +aia_schema.* +asl_schema.* +review_schema.* +-- ... +``` + +**审稿系统独立产品的数据库(简化):** +```sql +-- 只包含必需的表 +CREATE DATABASE review_system; + +-- 简化的用户表 +CREATE TABLE users ( + id UUID PRIMARY KEY, + email VARCHAR(255) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + role VARCHAR(50) NOT NULL, -- admin/editor/reviewer + created_at TIMESTAMP DEFAULT NOW() +); + +-- 审稿系统的业务表 +CREATE TABLE review_tasks (...); +CREATE TABLE review_journals (...); +CREATE TABLE review_reviewers (...); +-- ... +``` + +--- + +### 独立产品的优势 + +| 优势 | 说明 | +|------|------| +| **独立销售** | 可以单独定价、单独销售 | +| **独立部署** | 客户可以本地化部署,数据完全隔离 | +| **独立升级** | 不影响其他模块 | +| **轻量化** | 只包含必需功能,包体积小 | +| **定制化** | 可以针对特定客户定制 | + +--- + +## 📦 方案二:共享服务(推荐用于平台内模块) + +### 适用场景 + +- ✅ 同一客户购买多个模块 +- ✅ 云端SaaS部署 +- ✅ 模块之间需要共享用户和数据 + +### 核心思路 + +**平台层和通用能力层作为共享服务** + +``` +完整平台架构(微服务版): + +┌─────────────────────────────────────────────────────────┐ +│ API网关(Kong/Traefik) │ +│ 路由:/aia/* /asl/* /review/* │ +└─────────────────────────────────────────────────────────┘ + │ │ │ + ↓ ↓ ↓ +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ AIA模块服务 │ │ ASL模块服务 │ │ RVW模块服务 │ +│ (独立部署) │ │ (独立部署) │ │ (独立部署) │ +└─────────────┘ └─────────────┘ └─────────────┘ + │ │ │ + └──────────────┴──────────────┘ + ↓ + ┌───────────────────────────┐ + │ 共享服务(平台层) │ + │ - 用户认证服务 │ + │ - 存储服务 │ + │ - 监控服务 │ + └───────────────────────────┘ + ↓ + ┌───────────────────────────┐ + │ 共享服务(通用能力层) │ + │ - LLM网关服务 │ + │ - 文档处理服务 │ + │ - RAG引擎服务 │ + └───────────────────────────┘ + ↓ + ┌───────────────────────────┐ + │ 共享数据库 │ + │ - PostgreSQL (多Schema) │ + └───────────────────────────┘ +``` + +--- + +### 技术实现 + +#### Step 1: 服务拆分 + +**平台基础服务(独立部署):** +```yaml +# services/platform/docker-compose.yml + +services: + # 用户认证服务 + auth-service: + image: yizhengxun/auth-service:latest + ports: + - "3001:3000" + environment: + DATABASE_URL: ${DATABASE_URL} + JWT_SECRET: ${JWT_SECRET} + + # 存储服务 + storage-service: + image: yizhengxun/storage-service:latest + ports: + - "3002:3000" + environment: + OSS_ENDPOINT: ${OSS_ENDPOINT} + + # 监控服务 + monitoring-service: + image: yizhengxun/monitoring-service:latest + ports: + - "3003:3000" +``` + +**通用能力服务(独立部署):** +```yaml +# services/capabilities/docker-compose.yml + +services: + # LLM网关服务 + llm-gateway: + image: yizhengxun/llm-gateway:latest + ports: + - "3010:3000" + environment: + DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY} + QWEN_API_KEY: ${QWEN_API_KEY} + + # 文档处理服务 + document-processor: + image: yizhengxun/document-processor:latest + ports: + - "3011:3000" + + # RAG引擎服务 + rag-engine: + image: yizhengxun/rag-engine:latest + ports: + - "3012:3000" +``` + +**业务模块服务(独立部署):** +```yaml +# services/modules/review-system/docker-compose.yml + +services: + review-system: + image: yizhengxun/review-system:latest + ports: + - "4001:3000" + environment: + # 依赖共享服务 + AUTH_SERVICE_URL: http://auth-service:3000 + STORAGE_SERVICE_URL: http://storage-service:3000 + LLM_GATEWAY_URL: http://llm-gateway:3000 + DOCUMENT_PROCESSOR_URL: http://document-processor:3000 + + # 自己的数据库Schema + DATABASE_URL: postgresql://user:pass@postgres:5432/platform?schema=review_schema +``` + +--- + +#### Step 2: API网关配置 + +**Kong API网关配置:** +```yaml +# api-gateway/kong.yml + +_format_version: "3.0" + +services: + # 用户认证服务 + - name: auth-service + url: http://auth-service:3000 + routes: + - name: auth-routes + paths: + - /api/auth + + # 审稿系统模块 + - name: review-system + url: http://review-system:3000 + routes: + - name: review-routes + paths: + - /api/review + plugins: + - name: jwt + config: + secret_is_base64: false + key_claim_name: kid + + # AI文献模块 + - name: literature-system + url: http://literature-system:3000 + routes: + - name: literature-routes + paths: + - /api/literature + plugins: + - name: jwt +``` + +--- + +#### Step 3: 服务间通信 + +**业务模块调用共享服务:** +```typescript +// packages/module-review/backend/src/services/review.service.ts + +import { AuthClient } from '@yizhengxun/platform-core/clients'; +import { LLMClient } from '@yizhengxun/capabilities-core/clients'; + +export class ReviewService { + private authClient: AuthClient; + private llmClient: LLMClient; + + constructor() { + // 连接到共享服务 + this.authClient = new AuthClient({ + baseURL: process.env.AUTH_SERVICE_URL + }); + + this.llmClient = new LLMClient({ + baseURL: process.env.LLM_GATEWAY_URL + }); + } + + async createReviewTask(userId: string, file: File) { + // 1. 验证用户(调用共享认证服务) + const user = await this.authClient.verifyUser(userId); + + // 2. 上传文件(调用共享存储服务) + const fileUrl = await this.storageClient.upload(file); + + // 3. AI分析(调用共享LLM网关) + const analysis = await this.llmClient.chat({ + messages: [{ role: 'user', content: `分析这篇稿件: ${fileUrl}` }] + }); + + // 4. 保存到自己的数据库 + const task = await prisma.reviewTask.create({ + data: { + userId, + fileUrl, + analysis: analysis.content + } + }); + + return task; + } +} +``` + +--- + +### 模块独立部署的步骤 + +**1. 部署共享服务(一次性):** +```bash +# 部署平台基础服务 +cd services/platform +docker-compose up -d + +# 部署通用能力服务 +cd services/capabilities +docker-compose up -d + +# 部署API网关 +cd services/api-gateway +docker-compose up -d +``` + +**2. 部署业务模块(按需):** +```bash +# 只部署审稿系统模块 +cd services/modules/review-system +docker-compose up -d + +# 客户A购买了审稿系统,只需部署这一个模块 +# 其他模块不需要部署 +``` + +**3. 新客户购买其他模块:** +```bash +# 客户B购买了AI文献模块,再部署这个模块 +cd services/modules/literature-system +docker-compose up -d + +# 共享服务已经部署,不需要重复部署 +# 只需增加新的业务模块 +``` + +--- + +## 🖥️ Part 2:Electron单机版方案 + +### 为什么需要单机版? + +**核心需求:** +1. **数据隐私**:医生个人数据不能上传云端 +2. **离线使用**:无需网络连接 +3. **便携性**:可以在任何电脑上安装使用 +4. **独立运行**:不依赖外部服务器 + +**目标用户:** +- 个人医生 +- 数据敏感的研究者 +- 无网络环境的场景 + +--- + +### Electron架构与现有代码的对应关系 + +#### 现有架构(云端版) + +``` +┌─────────────────────────────────────────┐ +│ 浏览器 (Chrome) │ +│ ┌───────────────────────────────────┐ │ +│ │ 前端 (React + Vite) │ │ +│ │ http://localhost:5173 │ │ +│ └───────────────────────────────────┘ │ +└─────────────────────────────────────────┘ + ↓ HTTP请求 +┌─────────────────────────────────────────┐ +│ 后端 (Node.js + Fastify) │ +│ http://localhost:3001 │ +│ ┌───────────────────────────────────┐ │ +│ │ API路由、业务逻辑、Prisma ORM │ │ +│ └───────────────────────────────────┘ │ +└─────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ PostgreSQL (Docker) │ +│ localhost:5432 │ +└─────────────────────────────────────────┘ +``` + +--- + +#### Electron架构(单机版) + +``` +┌───────────────────────────────────────────────────────────┐ +│ Electron 应用 │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ 渲染进程 (Chromium) │ │ +│ │ ┌───────────────────────────────────────────────┐ │ │ +│ │ │ 前端 (React) ⭐ 复用现有代码 │ │ │ +│ │ │ file://app/index.html │ │ │ +│ │ └───────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ ↓ IPC通信 │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ 主进程 (Node.js) │ │ +│ │ ┌───────────────────────────────────────────────┐ │ │ +│ │ │ API逻辑、业务逻辑 ⭐ 复用现有代码 │ │ │ +│ │ │ Prisma ORM、本地文件系统 │ │ │ +│ │ └───────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ ↓ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ SQLite (嵌入式数据库) │ │ +│ │ ~/Documents/YizhengxunData/app.db │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ 子进程 (Python/R) │ │ +│ │ 文档提取、数据分析 │ │ +│ └─────────────────────────────────────────────────────┘ │ +└───────────────────────────────────────────────────────────┘ +``` + +--- + +### 核心技术方案 + +#### 1. 前端代码复用(90%+ 复用) + +**现有前端代码(Web版):** +```typescript +// frontend/src/api/client.ts +const API_BASE_URL = 'http://localhost:3001'; + +export const apiClient = { + async get(url: string) { + return fetch(`${API_BASE_URL}${url}`); + }, + async post(url: string, data: any) { + return fetch(`${API_BASE_URL}${url}`, { + method: 'POST', + body: JSON.stringify(data) + }); + } +}; +``` + +**Electron版前端(修改API调用方式):** +```typescript +// electron-frontend/src/api/client.ts + +// 使用Electron的IPC通信,而不是HTTP +const { ipcRenderer } = window.require('electron'); + +export const apiClient = { + async get(url: string) { + // 通过IPC发送到主进程 + return ipcRenderer.invoke('api-request', { + method: 'GET', + url + }); + }, + async post(url: string, data: any) { + return ipcRenderer.invoke('api-request', { + method: 'POST', + url, + data + }); + } +}; +``` + +**React组件完全不需要改:** +```typescript +// 业务组件完全不变 ✅ +function UserList() { + const [users, setUsers] = useState([]); + + useEffect(() => { + // API调用方式不变,底层自动适配 + apiClient.get('/api/users').then(setUsers); + }, []); + + return
{/* ... */}
; +} +``` + +**复用率:90%+ ✅** +- ✅ 所有React组件 +- ✅ 所有UI库(Ant Design) +- ✅ 所有状态管理(Zustand) +- ✅ 所有路由(React Router) +- ⚠️ 只需修改API调用层(1个文件) + +--- + +#### 2. 后端代码复用(80%+ 复用) + +**现有后端代码(Web版):** +```typescript +// backend/src/routes/users.ts +import { FastifyInstance } from 'fastify'; + +export async function userRoutes(fastify: FastifyInstance) { + // GET /api/users + fastify.get('/api/users', async (request, reply) => { + const users = await prisma.user.findMany(); + return users; + }); + + // POST /api/users + fastify.post('/api/users', async (request, reply) => { + const user = await prisma.user.create({ + data: request.body + }); + return user; + }); +} +``` + +**Electron版后端(主进程):** +```typescript +// electron-backend/src/ipc-handlers.ts +import { ipcMain } from 'electron'; +import { prisma } from './prisma-client'; + +// 复用业务逻辑 ⭐ +import { UserService } from '../../../backend/src/services/user.service'; + +export function setupIpcHandlers() { + const userService = new UserService(prisma); + + // 将HTTP路由转换为IPC Handler + ipcMain.handle('api-request', async (event, { method, url, data }) => { + // 路由分发(简化版的Fastify) + if (url === '/api/users' && method === 'GET') { + return userService.findAll(); + } + + if (url === '/api/users' && method === 'POST') { + return userService.create(data); + } + + // ... 其他路由 + }); +} +``` + +**核心业务逻辑完全复用:** +```typescript +// backend/src/services/user.service.ts +// 这个文件在Web版和Electron版完全共享 ✅ + +export class UserService { + constructor(private prisma: PrismaClient) {} + + async findAll() { + return this.prisma.user.findMany(); + } + + async create(data: CreateUserDto) { + // 业务逻辑 + const hashedPassword = await bcrypt.hash(data.password, 10); + return this.prisma.user.create({ + data: { + ...data, + password: hashedPassword + } + }); + } +} +``` + +**复用率:80%+ ✅** +- ✅ 所有Service层(业务逻辑) +- ✅ 所有Prisma Model和查询 +- ✅ 所有工具函数 +- ⚠️ 需要适配:HTTP路由 → IPC Handler +- ⚠️ 需要替换:PostgreSQL → SQLite + +--- + +#### 3. 数据库适配(PostgreSQL → SQLite) + +**Prisma Schema修改(最小改动):** +```prisma +// electron-backend/prisma/schema.prisma + +datasource db { + // Web版:PostgreSQL + // provider = "postgresql" + // url = env("DATABASE_URL") + + // Electron版:SQLite ⭐ + provider = "sqlite" + url = "file:./app.db" +} + +// Model定义完全不变 ✅ +model User { + id String @id @default(uuid()) + email String @unique + password String + name String? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("users") +} +``` + +**注意事项:** +```typescript +// SQLite不支持某些PostgreSQL特性 + +// ❌ PostgreSQL支持,SQLite不支持: +- JSON列类型(需要用TEXT存储) +- 复杂的全文搜索 +- 某些聚合函数 + +// ✅ 解决方案: +// 1. 用TEXT存储JSON(手动序列化) +model Config { + id String @id + data String // JSON.stringify(data) +} + +// 2. 简化查询(避免复杂SQL) +// 3. 在应用层实现某些功能 +``` + +--- + +### 完整项目结构 + +``` +AIclinicalresearch/ + ├── packages/ # 共享代码(Monorepo) + │ ├── frontend-core/ # 前端核心(Web + Electron复用) + │ ├── backend-core/ # 后端核心(Web + Electron复用) + │ └── shared-types/ # 共享类型定义 + │ + ├── apps/ + │ ├── web/ # Web版(当前) + │ │ ├── frontend/ + │ │ └── backend/ + │ │ + │ └── electron/ # Electron版 ⭐ + │ ├── main/ # 主进程(Node.js后端) + │ │ ├── src/ + │ │ │ ├── main.ts # Electron入口 + │ │ │ ├── ipc-handlers.ts # IPC处理器 + │ │ │ ├── services/ # 复用backend-core + │ │ │ └── prisma/ + │ │ │ └── schema.prisma # SQLite版本 + │ │ └── package.json + │ │ + │ ├── renderer/ # 渲染进程(React前端) + │ │ ├── src/ + │ │ │ ├── main.tsx + │ │ │ ├── api/ # IPC客户端 + │ │ │ ├── pages/ # 复用frontend-core + │ │ │ └── components/ # 复用frontend-core + │ │ └── package.json + │ │ + │ ├── resources/ # 打包资源 + │ │ ├── python/ # Python运行时 + │ │ ├── r/ # R运行时(如需要) + │ │ └── assets/ + │ │ + │ └── electron-builder.yml # 打包配置 + │ + └── ... +``` + +--- + +### 关键实现细节 + +#### 1. Electron主进程(main.ts) + +```typescript +// apps/electron/main/src/main.ts + +import { app, BrowserWindow, ipcMain } from 'electron'; +import path from 'path'; +import { setupIpcHandlers } from './ipc-handlers'; +import { initDatabase } from './database'; +import { startPythonService } from './python-service'; + +let mainWindow: BrowserWindow | null = null; + +async function createWindow() { + // 1. 创建浏览器窗口 + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, 'preload.js') // 安全的IPC桥接 + } + }); + + // 2. 加载前端(生产环境) + if (app.isPackaged) { + mainWindow.loadFile(path.join(__dirname, '../renderer/index.html')); + } else { + // 开发环境:连接到Vite开发服务器 + mainWindow.loadURL('http://localhost:5173'); + } + + // 3. 初始化数据库 + await initDatabase(); + + // 4. 启动Python文档处理服务 + await startPythonService(); + + // 5. 设置IPC处理器 + setupIpcHandlers(); +} + +// Electron生命周期 +app.whenReady().then(createWindow); + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } +}); +``` + +--- + +#### 2. IPC处理器(ipc-handlers.ts) + +```typescript +// apps/electron/main/src/ipc-handlers.ts + +import { ipcMain } from 'electron'; +import { prisma } from './prisma-client'; + +// 复用业务逻辑 ⭐ +import { UserService } from '@yizhengxun/backend-core/services'; +import { ProjectService } from '@yizhengxun/backend-core/services'; +import { ConversationService } from '@yizhengxun/backend-core/services'; + +export function setupIpcHandlers() { + // 初始化服务 + const userService = new UserService(prisma); + const projectService = new ProjectService(prisma); + const conversationService = new ConversationService(prisma); + + // 用户相关API + ipcMain.handle('api:users:list', async () => { + return userService.findAll(); + }); + + ipcMain.handle('api:users:create', async (event, data) => { + return userService.create(data); + }); + + // 项目相关API + ipcMain.handle('api:projects:list', async (event, userId) => { + return projectService.findByUser(userId); + }); + + ipcMain.handle('api:projects:create', async (event, data) => { + return projectService.create(data); + }); + + // 对话相关API + ipcMain.handle('api:conversations:create', async (event, data) => { + return conversationService.create(data); + }); + + ipcMain.handle('api:conversations:chat', async (event, data) => { + // 调用LLM(本地或云端API) + return conversationService.chat(data); + }); + + // 文件操作 + ipcMain.handle('api:files:upload', async (event, file) => { + // 保存到本地文件系统 + const filePath = await saveFile(file); + return { url: filePath }; + }); +} +``` + +--- + +#### 3. 前端IPC客户端(api-client.ts) + +```typescript +// apps/electron/renderer/src/api/client.ts + +const { ipcRenderer } = window.require('electron'); + +export const apiClient = { + // 用户API + users: { + async list() { + return ipcRenderer.invoke('api:users:list'); + }, + async create(data: any) { + return ipcRenderer.invoke('api:users:create', data); + } + }, + + // 项目API + projects: { + async list(userId: string) { + return ipcRenderer.invoke('api:projects:list', userId); + }, + async create(data: any) { + return ipcRenderer.invoke('api:projects:create', data); + } + }, + + // 对话API + conversations: { + async create(data: any) { + return ipcRenderer.invoke('api:conversations:create', data); + }, + async chat(data: any) { + return ipcRenderer.invoke('api:conversations:chat', data); + } + }, + + // 文件上传 + files: { + async upload(file: File) { + // 将File转换为Buffer + const buffer = await file.arrayBuffer(); + return ipcRenderer.invoke('api:files:upload', { + name: file.name, + data: Buffer.from(buffer) + }); + } + } +}; +``` + +--- + +#### 4. Python子进程管理 + +```typescript +// apps/electron/main/src/python-service.ts + +import { spawn, ChildProcess } from 'child_process'; +import path from 'path'; +import { app } from 'electron'; + +let pythonProcess: ChildProcess | null = null; + +export async function startPythonService(): Promise { + return new Promise((resolve, reject) => { + // Python运行时路径(打包时包含) + const pythonPath = app.isPackaged + ? path.join(process.resourcesPath, 'python', 'python.exe') + : 'python'; // 开发环境使用系统Python + + // Python脚本路径 + const scriptPath = app.isPackaged + ? path.join(process.resourcesPath, 'python', 'extraction_service', 'main.py') + : path.join(__dirname, '../../../../extraction_service/main.py'); + + // 启动Python进程 + pythonProcess = spawn(pythonPath, [ + '-m', 'uvicorn', + 'main:app', + '--host', '127.0.0.1', + '--port', '8000' + ], { + cwd: path.dirname(scriptPath), + stdio: 'pipe' + }); + + // 监听输出 + pythonProcess.stdout?.on('data', (data) => { + console.log(`Python: ${data}`); + if (data.toString().includes('Application startup complete')) { + resolve(); + } + }); + + pythonProcess.stderr?.on('data', (data) => { + console.error(`Python Error: ${data}`); + }); + + pythonProcess.on('error', reject); + }); +} + +export function stopPythonService(): void { + if (pythonProcess) { + pythonProcess.kill(); + pythonProcess = null; + } +} + +// 应用退出时关闭Python服务 +app.on('will-quit', stopPythonService); +``` + +--- + +#### 5. 打包配置(electron-builder.yml) + +```yaml +# apps/electron/electron-builder.yml + +appId: com.yizhengxun.aiclinical +productName: 壹证循AI科研助手 + +# 打包目录 +directories: + output: dist + buildResources: resources + +# Windows配置 +win: + target: + - nsis + - portable + icon: resources/icon.ico + +# NSIS安装程序配置 +nsis: + oneClick: false + allowToChangeInstallationDirectory: true + createDesktopShortcut: always + createStartMenuShortcut: true + +# Mac配置 +mac: + target: + - dmg + - zip + icon: resources/icon.icns + category: public.app-category.productivity + +# 打包包含的文件 +files: + - "main/**/*" + - "renderer/**/*" + - "!**/*.ts" + - "!**/*.map" + +# 额外资源(Python运行时等) +extraResources: + - from: resources/python + to: python + - from: ../../../../extraction_service + to: python/extraction_service + +# 打包后大小 +asar: true +asarUnpack: + - "**/*.node" + - "resources/**/*" +``` + +--- + +### 单机版的关键技术挑战 + +#### 挑战1:打包体积(500MB+) + +**包含内容:** +``` +安装包组成: +- Electron框架:~100MB +- Node.js运行时:包含在Electron中 +- 前端代码(编译后):~10MB +- 后端代码(编译后):~5MB +- SQLite数据库:~5MB(空库) +- Python运行时:~150MB +- Python依赖:~100MB +- R运行时(可选):~200MB +- R依赖(可选):~100MB + +总计:500-700MB +``` + +**优化方案:** +1. ✅ 使用ASAR压缩(Electron默认) +2. ✅ 不包含R运行时(仅云端版需要) +3. ✅ Python依赖精简(只包含必需的包) +4. ✅ 增量更新(只下载变化的部分) + +--- + +#### 挑战2:跨平台兼容性 + +**需要分别打包:** +- Windows x64 +- Windows ARM64(Surface等设备) +- macOS x64 +- macOS ARM64(Apple Silicon) +- Linux x64 + +**测试工作量:** +- 每个平台都需要独立测试 +- 安装、升级、卸载流程 +- 权限问题、文件路径问题 + +--- + +#### 挑战3:自动更新 + +**electron-updater配置:** +```typescript +// apps/electron/main/src/updater.ts + +import { autoUpdater } from 'electron-updater'; +import { dialog } from 'electron'; + +export function setupAutoUpdater() { + // 配置更新服务器 + autoUpdater.setFeedURL({ + provider: 'generic', + url: 'https://releases.yizhengxun.com' + }); + + // 检查更新 + autoUpdater.checkForUpdatesAndNotify(); + + // 发现新版本 + autoUpdater.on('update-available', () => { + dialog.showMessageBox({ + type: 'info', + title: '发现新版本', + message: '检测到新版本,是否立即下载?', + buttons: ['是', '否'] + }).then((result) => { + if (result.response === 0) { + autoUpdater.downloadUpdate(); + } + }); + }); + + // 下载完成 + autoUpdater.on('update-downloaded', () => { + dialog.showMessageBox({ + type: 'info', + title: '更新已就绪', + message: '新版本已下载,是否立即安装?', + buttons: ['立即安装', '稍后'] + }).then((result) => { + if (result.response === 0) { + autoUpdater.quitAndInstall(); + } + }); + }); +} +``` + +--- + +## 📊 总结对比 + +### 三种部署方式对比 + +| 维度 | 云端SaaS | 独立产品包 | Electron单机版 | +|------|---------|----------|---------------| +| **部署方式** | 云端服务器 | Docker容器 | 本地安装 | +| **数据存储** | 云端PostgreSQL | 本地PostgreSQL | 本地SQLite | +| **网络需求** | 必须联网 | 可离线(LLM除外) | 完全离线 | +| **更新方式** | 无感知更新 | Docker镜像更新 | 应用内更新 | +| **安装难度** | 无需安装 | Docker部署 | 一键安装 | +| **代码复用** | 100% | 80%(精简版) | 90% | +| **适用客户** | 个人、小机构 | 医院、大机构 | 个人医生 | +| **商业模式** | 订阅制 | 一次性License | 一次性购买 | +| **维护成本** | 低 | 中 | 高 | + +--- + +## 🎯 最终建议 + +### 分阶段实施 + +**阶段一(当前-6个月):** +- ✅ 专注云端SaaS版 +- ✅ 完善7个业务模块 +- ✅ 建立Monorepo架构(为未来打基础) + +**阶段二(6-12个月):** +- ✅ 开发独立产品包(审稿系统、AI文献) +- ✅ 支持Docker本地化部署 +- ✅ 验证商业模式 + +**阶段三(12-18个月):** +- ✅ 开发Electron单机版(如有需求) +- ✅ 支持完全离线使用 +- ✅ 扩展个人用户市场 + +--- + +**您想先深入哪个方案?** +1. 模块独立部署的详细实施? +2. Electron单机版的开发计划? +3. Monorepo架构的设计? + + + + + + + + + + + + + + + diff --git a/docs/00-系统总体设计/07-Monorepo架构评估.md b/docs/00-系统总体设计/07-Monorepo架构评估.md new file mode 100644 index 00000000..5de1ad39 --- /dev/null +++ b/docs/00-系统总体设计/07-Monorepo架构评估.md @@ -0,0 +1,568 @@ +# Monorepo架构评估 - 当前阶段是否需要? + +> **文档版本:** v1.0 +> **创建日期:** 2025-11-06 +> **文档目的:** 评估当前阶段采用Monorepo架构的必要性和成本 + +--- + +## 🤔 核心问题 + +1. 当前阶段需要使用Monorepo架构吗? +2. 对当前阶段的成本高吗? +3. 什么时候是最佳时机? + +--- + +## 📊 当前项目结构分析 + +### 现有代码结构 + +``` +AIclinicalresearch/ + ├── frontend/ # 前端(React + Vite) + │ ├── src/ + │ ├── package.json + │ └── node_modules/ + │ + ├── backend/ # 后端(Node.js + Fastify) + │ ├── src/ + │ ├── package.json + │ └── node_modules/ + │ + ├── extraction_service/ # Python文档提取服务 + │ ├── main.py + │ ├── requirements.txt + │ └── venv/ + │ + ├── docker-compose.yml # Docker配置 + └── ... +``` + +**当前特点:** +- ✅ 结构简单,易于理解 +- ✅ 前后端分离,职责清晰 +- ✅ 各自独立的依赖管理 +- ❌ 没有代码共享机制 +- ❌ 重复的配置文件 +- ❌ 不支持模块独立打包 + +--- + +## 🎯 Monorepo架构对比 + +### Monorepo结构示例 + +``` +AIclinicalresearch/ + ├── packages/ # 共享包 + │ ├── platform-core/ # 平台核心(可复用) + │ │ ├── auth/ + │ │ ├── storage/ + │ │ ├── package.json + │ │ └── tsconfig.json + │ │ + │ ├── capabilities-core/ # 通用能力(可复用) + │ │ ├── llm-gateway/ + │ │ ├── document-processor/ + │ │ ├── package.json + │ │ └── tsconfig.json + │ │ + │ └── shared-types/ # 共享类型定义 + │ ├── src/ + │ ├── package.json + │ └── tsconfig.json + │ + ├── apps/ # 应用 + │ ├── frontend/ # Web前端 + │ │ ├── src/ + │ │ └── package.json + │ │ + │ ├── backend/ # Web后端 + │ │ ├── src/ + │ │ └── package.json + │ │ + │ └── admin-frontend/ # 运营管理端(未来) + │ ├── src/ + │ └── package.json + │ + ├── products/ # 独立产品打包(未来) + │ ├── review-system/ + │ └── literature-system/ + │ + ├── package.json # 根package.json(Workspace配置) + ├── pnpm-workspace.yaml # Workspace配置 + └── tsconfig.base.json # 共享TS配置 +``` + +--- + +## 💰 成本分析:现在 vs 以后 + +### 方案A:现在转换Monorepo + +#### 成本(2-3天) + +**Day 1:学习和设计(0.5-1天)** +``` +学习内容: +- pnpm workspaces 或 npm workspaces +- 包依赖管理(workspace:*) +- TypeScript path mapping +- 共享配置(tsconfig, eslint等) + +工作量:4-8小时 +难度:⭐⭐⭐ 中等 +``` + +**Day 2:重构现有代码(1天)** +``` +任务清单: +1. 创建packages/和apps/目录结构 +2. 移动frontend和backend到apps/ +3. 提取共享类型到packages/shared-types/ +4. 配置workspace依赖 +5. 更新import路径 +6. 测试验证 + +工作量:8小时 +难度:⭐⭐⭐ 中等 +风险:⭐⭐ 低(有回滚方案) +``` + +**Day 3:测试和优化(0.5-1天)** +``` +测试内容: +- 前端启动和构建 +- 后端启动和构建 +- 依赖安装速度 +- 开发体验 + +工作量:4-8小时 +``` + +**总成本:2-3天** + +#### 收益 + +**立即收益:** +1. ✅ 代码共享变容易(类型定义、工具函数等) +2. ✅ 统一的依赖管理(减少重复安装) +3. ✅ 统一的配置(TypeScript、ESLint等) +4. ✅ 为未来打基础 + +**未来收益:** +1. ✅ 新增模块时,可以复用platform-core和capabilities-core +2. ✅ 独立产品打包变简单 +3. ✅ Electron单机版开发变容易 +4. ✅ 避免未来大规模重构 + +--- + +### 方案B:继续当前结构,以后再转 + +#### 短期成本(0天) + +**优点:** +- ✅ 无需学习新技术 +- ✅ 无需重构代码 +- ✅ 可以立即开始ASL模块开发 + +**缺点:** +- ❌ 代码重复(前后端类型定义、工具函数等) +- ❌ 依赖管理分散 +- ❌ 为未来埋下技术债 + +#### 未来成本(1-2周) + +**触发条件:** +- 需要开发运营管理端(独立前端) +- 需要开发独立产品包(审稿系统、AI文献) +- 需要开发Electron单机版 +- 代码重复严重,维护困难 + +**重构成本(估算):** +``` +未来重构成本: +- 学习和设计:1天(同现在) +- 重构代码:3-5天(代码量更大) + * 多个前端应用(Web、Admin、Electron) + * 多个后端模块 + * 大量共享代码需要提取 +- 测试验证:2-3天 +- 修复问题:1-2天 + +总成本:7-11天 + +是现在的3-5倍! +``` + +--- + +## 📊 对比矩阵 + +| 维度 | 方案A:现在转 | 方案B:以后转 | +|------|-------------|-------------| +| **立即成本** | ⭐⭐⭐ 中(2-3天) | ⭐⭐⭐⭐⭐ 零 | +| **未来成本** | ⭐⭐⭐⭐⭐ 零 | ⭐⭐ 高(7-11天) | +| **学习难度** | ⭐⭐⭐ 中等 | ⭐⭐⭐ 中等(延后) | +| **风险** | ⭐⭐ 低 | ⭐⭐⭐ 中(技术债) | +| **代码复用** | ⭐⭐⭐⭐⭐ 优秀 | ⭐⭐ 困难 | +| **模块独立打包** | ⭐⭐⭐⭐⭐ 容易 | ⭐⭐ 困难 | +| **团队协作** | ⭐⭐⭐⭐ 好 | ⭐⭐⭐ 一般 | +| **开发体验** | ⭐⭐⭐⭐ 好 | ⭐⭐⭐ 一般 | + +--- + +## 🎯 当前阶段评估 + +### 当前项目状态 + +**已完成:** +- ✅ 前端(React + Vite) +- ✅ 后端(Node.js + Fastify) +- ✅ Python文档提取服务 +- ✅ 基础功能(AIA、PKB) + +**即将开发:** +- ⏳ ASL模块(AI智能文献) +- ⏳ ADMIN(运营管理端)- 独立前端应用 + +**未来规划:** +- ⏳ DC、SSA、ST、RVW模块 +- ⏳ 独立产品包(审稿系统、AI文献) +- ⏳ Electron单机版 + +--- + +### 是否需要Monorepo? + +**评估标准:** + +#### ✅ 需要 - 如果满足以下条件: + +1. **近期(1-3个月)会开发多个独立应用** + - ✅ 运营管理端(独立前端) + - ✅ ASL、DC等新模块 + +2. **有代码共享需求** + - ✅ 类型定义(User、Project等) + - ✅ API客户端(apiClient) + - ✅ 工具函数(日期格式化、验证等) + +3. **未来有模块独立部署需求** + - ✅ 审稿系统独立产品 + - ✅ AI文献独立产品 + - ✅ Electron单机版 + +4. **团队能够接受2-3天的学习和重构** + - ✅ 学习曲线不陡峭 + - ✅ 文档和最佳实践丰富 + +#### ❌ 不需要 - 如果满足以下条件: + +1. **只有一个前端 + 一个后端** + - ❌ 当前已有运营管理端需求 + +2. **没有代码共享需求** + - ❌ 已有大量重复代码(类型定义等) + +3. **不打算模块化和独立部署** + - ❌ 已规划模块独立销售 + +4. **团队完全没有时间学习新技术** + - ⚠️ 只需2-3天 + +--- + +## 💡 我的建议 + +### 建议:现在转换Monorepo ⭐⭐⭐⭐ **推荐** + +**理由:** + +**1. 投入产出比极高** +``` +投入:2-3天 +节省:未来7-11天(节省5-8天) +ROI:300%+ +``` + +**2. 正处于最佳时机** +``` +当前: +- 代码量适中(不多不少) +- 即将开发多个新模块 +- 团队小,沟通成本低 + +未来: +- 代码量大(重构困难) +- 多个应用同时运行 +- 重构影响范围大 +``` + +**3. 符合未来需求** +``` +近期需求(3个月内): +- ✅ 运营管理端(独立前端) +- ✅ 多个业务模块(ASL、DC等) +- ✅ 代码共享(类型、工具函数) + +未来需求(6-12个月): +- ✅ 独立产品打包 +- ✅ Electron单机版 +- ✅ 微服务拆分 +``` + +**4. 学习成本不高** +``` +Monorepo工具: +- pnpm workspaces(推荐,最简单) +- npm workspaces(内置,无需安装) +- yarn workspaces(成熟) + +学习资源: +- ✅ 官方文档完善 +- ✅ 社区最佳实践丰富 +- ✅ AI助手(我)可以全程指导 +``` + +--- + +### 如果必须选择方案B + +**最低要求:** + +**1. 建立代码共享机制** +```typescript +// 创建共享目录 +shared/ + ├── types/ # 共享类型定义 + │ ├── user.ts + │ ├── project.ts + │ └── index.ts + │ + └── utils/ # 共享工具函数 + ├── date.ts + ├── validation.ts + └── index.ts + +// 前后端引用 +import { User } from '../../shared/types'; +``` + +**2. 统一配置文件** +``` +tsconfig.base.json # 共享TS配置 +.eslintrc.shared.js # 共享ESLint配置 +``` + +**3. 严格的代码规范** +``` +- 禁止复制粘贴代码 +- 强制使用共享类型 +- 定期代码审查 +``` + +**触发Monorepo转换的信号:** +- 开始开发运营管理端 +- 代码重复超过3处 +- 新增第3个应用 + +--- + +## 🚀 实施方案(如果选择转换) + +### 阶段一:准备(半天) + +**学习pnpm workspaces:** +```bash +# 1. 安装pnpm(如果没有) +npm install -g pnpm + +# 2. 阅读文档(30分钟) +https://pnpm.io/workspaces + +# 3. 看示例项目(30分钟) +``` + +**设计目录结构:** +``` +决定: +- packages/ 放什么?(shared-types、platform-core等) +- apps/ 放什么?(frontend、backend、admin-frontend) +- 是否需要products/?(暂时不需要) +``` + +--- + +### 阶段二:重构(1-1.5天) + +**Step 1:创建基础结构** +```bash +# 1. 创建根package.json +pnpm init + +# 2. 创建pnpm-workspace.yaml +cat > pnpm-workspace.yaml << EOF +packages: + - 'packages/*' + - 'apps/*' +EOF + +# 3. 创建目录 +mkdir -p packages/shared-types +mkdir -p apps +``` + +**Step 2:移动现有代码** +```bash +# 移动frontend +mv frontend apps/frontend + +# 移动backend +mv backend apps/backend + +# extraction_service保持原位(Python不需要Monorepo) +``` + +**Step 3:提取共享代码** +```bash +# 创建shared-types包 +cd packages/shared-types +pnpm init +# 移动类型定义 +``` + +**Step 4:配置依赖** +```json +// apps/frontend/package.json +{ + "dependencies": { + "@yizhengxun/shared-types": "workspace:*" + } +} + +// apps/backend/package.json +{ + "dependencies": { + "@yizhengxun/shared-types": "workspace:*" + } +} +``` + +**Step 5:更新import** +```typescript +// 修改前 +import { User } from '../types/user'; + +// 修改后 +import { User } from '@yizhengxun/shared-types'; +``` + +**Step 6:安装依赖** +```bash +# 根目录执行(会安装所有包的依赖) +pnpm install +``` + +--- + +### 阶段三:测试验证(半天) + +**测试清单:** +- [ ] 前端启动正常(`cd apps/frontend && pnpm dev`) +- [ ] 后端启动正常(`cd apps/backend && pnpm dev`) +- [ ] 类型提示正常(VSCode智能提示) +- [ ] 构建成功(`pnpm build`) +- [ ] 所有功能正常 + +**回滚方案:** +```bash +# 如果出现问题,可以立即回滚 +git reset --hard HEAD +``` + +--- + +## 📊 总结对比 + +### 立即转换 vs 延后转换 + +| 项目 | 立即转换 | 延后转换 | +|------|---------|---------| +| **时间成本** | 2-3天 | 未来7-11天 | +| **学习成本** | 中等 | 中等(延后) | +| **风险** | 低 | 中(技术债) | +| **代码质量** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | +| **开发效率** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | +| **未来扩展** | ⭐⭐⭐⭐⭐ | ⭐⭐ | +| **投入产出比** | ⭐⭐⭐⭐⭐ | ⭐⭐ | + +--- + +## 🎯 最终建议 + +### 推荐方案:立即转换Monorepo ⭐⭐⭐⭐⭐ + +**核心理由:** +1. ✅ **投入小,收益大**:2-3天换来未来5-8天的节省 +2. ✅ **正处于最佳时机**:代码量适中,即将开发多模块 +3. ✅ **符合未来规划**:运营管理端、独立产品包、Electron单机版 +4. ✅ **学习成本可控**:有AI全程指导,有详细文档 + +**实施计划:** +``` +本周:Monorepo重构(2-3天) + Day 1:学习 + 设计(半天) + Day 2:重构实施(1天) + Day 3:测试验证(半天) + +下周:继续ASL模块开发 + - 享受Monorepo带来的便利 + - 代码复用变简单 + - 类型共享开箱即用 +``` + +**如果您决定采纳,我可以:** +1. ✅ 提供详细的step-by-step指导 +2. ✅ 帮您设计目录结构 +3. ✅ 编写配置文件 +4. ✅ 协助重构和测试 + +--- + +### 替代方案:延后转换(不推荐) + +**如果时间确实紧迫:** + +**最低要求:** +1. ✅ 创建`shared/`目录存放共享代码 +2. ✅ 制定代码复用规范 +3. ✅ 在开发运营管理端前必须转换 + +**触发条件:** +- 开始开发运营管理端 +- 代码重复严重 +- 团队成员抱怨 + +--- + +**您觉得呢?** 😊 + +是否愿意投入2-3天,为未来节省5-8天? + + + + + + + + + + + + + + + diff --git a/docs/00-系统总体设计/08-架构设计全景图.md b/docs/00-系统总体设计/08-架构设计全景图.md new file mode 100644 index 00000000..d7cbf519 --- /dev/null +++ b/docs/00-系统总体设计/08-架构设计全景图.md @@ -0,0 +1,684 @@ +# 架构设计全景图 + +> **文档版本:** v1.0 +> **创建日期:** 2025-11-06 +> **文档目的:** 一图看懂整个系统架构 + +--- + +## 🎯 完整架构全景 + +``` +┌───────────────────────────────────────────────────────────────────────────────┐ +│ 壹证循AI科研平台 - 完整架构 │ +└───────────────────────────────────────────────────────────────────────────────┘ + +┌───────────────────────────────────────────────────────────────────────────────┐ +│ 用户层(4类用户) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 临床医生 │ │ 研究者 │ │ 期刊编辑部 │ │ 运营人员 │ │ +│ │ 研究机构 │ │ 医学生 │ │ 出版社 │ │ 管理员 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ +└───────────────────────────────────────────────────────────────────────────────┘ + ↓ ↓ ↓ ↓ +┌───────────────────────────────────────────────────────────────────────────────┐ +│ 前端应用层(4个独立应用) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Web前端 │ │ 单机版 │ │ 独立产品 │ │ 运营管理端 │ │ +│ │ (React) │ │(Electron) │ │ 前端 │ │ (Admin) │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ app.xxx.com │ │ 桌面应用 │ │ 独立域名 │ │admin.xxx.com│ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +└───────────────────────────────────────────────────────────────────────────────┘ + ↓ ↓ ↓ ↓ +┌───────────────────────────────────────────────────────────────────────────────┐ +│ API网关层(可选,微服务时) │ +│ Kong / Traefik - 统一路由和鉴权 │ +└───────────────────────────────────────────────────────────────────────────────┘ + ↓ +┌───────────────────────────────────────────────────────────────────────────────┐ +│ 业务模块层(8个独立业务模块) │ +│ │ +│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │ AIA │ │ ASL │ │ PKB │ │ DC │ │ SSA │ │ ST │ │ +│ │智能问答│ │智能文献│ │知识库 │ │数据清洗│ │智能统计│ │分析工具│ │ +│ │ │ │ │ │ │ │ │ │ │ │ │ │ +│ │✅已完成│ │⏳重点 │ │✅已完成│ │⏳规划中│ │⏳规划中│ │⏳规划中│ │ +│ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │ +│ │ +│ ┌────────┐ ┌────────┐ │ +│ │ RVW │ │ ADMIN │ │ +│ │稿件审查│ │运营管理│ │ +│ │ │ │ │ │ +│ │⚡独立 │ │⭐新增 │ │ +│ └────────┘ └────────┘ │ +└───────────────────────────────────────────────────────────────────────────────┘ + ↓ 依赖 +┌───────────────────────────────────────────────────────────────────────────────┐ +│ 通用能力层(5个核心技术能力) │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ LLM网关 │ │ 文档处理 │ │ RAG引擎 │ │ ETL引擎 │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ 5模块依赖 │ │ 6模块依赖 │ │ 3模块依赖 │ │ 2模块依赖 │ │ +│ │ 71%复用 │ │ 86%复用 │ │ 43%复用 │ │ 29%复用 │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ ❌待实现 │ │ ✅已实现 │ │ ✅已实现 │ │ ❌待实现 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +│ ┌──────────────┐ │ +│ │ 医学NLP │ │ +│ │ │ │ +│ │ 1模块依赖 │ │ +│ │ 14%复用 │ │ +│ │ │ │ +│ │ ❌待实现 │ │ +│ └──────────────┘ │ +└───────────────────────────────────────────────────────────────────────────────┘ + ↓ 依赖 +┌───────────────────────────────────────────────────────────────────────────────┐ +│ 平台基础层(通用基础设施) │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │用户与权限中心│ │ 存储服务 │ │ 通知服务 │ │ 监控与日志 │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ - 用户认证 │ │ - 文件上传 │ │ - 站内消息 │ │ - 操作日志 │ │ +│ │ - JWT Token │ │ - OSS/本地 │ │ - 邮件通知 │ │ - 错误监控 │ │ +│ │ - RBAC权限 │ │ - 临时URL │ │ - WebSocket │ │ - 审计日志 │ │ +│ │ - Feature Flag│ │ │ │ │ │ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ ✅已有基础 │ │ ✅已实现 │ │ ⏳待实现 │ │ ✅已实现 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +│ ┌──────────────┐ │ +│ │ 系统配置 │ │ +│ │ │ │ +│ │ - 全局配置 │ │ +│ │ - 动态配置 │ │ +│ │ │ │ +│ │ ⏳待增强 │ │ +│ └──────────────┘ │ +└───────────────────────────────────────────────────────────────────────────────┘ + ↓ +┌───────────────────────────────────────────────────────────────────────────────┐ +│ 数据存储层(Schema隔离) │ +│ │ +│ ┌────────────────────────────────────────────────────────────────────────┐ │ +│ │ PostgreSQL(当前:逻辑隔离) │ │ +│ │ │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │platform │ │aia_schema │ │asl_schema │ │pkb_schema │ │ │ +│ │ │_schema │ │ │ │ │ │ │ │ │ +│ │ │ │ │ - projects │ │ - projects │ │ - kb │ │ │ +│ │ │ - users │ │ - conv │ │ - items │ │ - documents │ │ │ +│ │ │ - roles │ │ - messages │ │ - screening │ │ │ │ │ +│ │ │ - feature │ │ │ │ - extraction│ │ │ │ │ +│ │ │ _flags │ │ │ │ │ │ │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ │ │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │dc_schema │ │ssa_schema │ │st_schema │ │review │ │ │ +│ │ │ │ │ │ │ │ │_schema │ │ │ +│ │ │ - projects │ │ - projects │ │ - usage │ │ │ │ │ +│ │ │ - raw_files │ │ - tasks │ │ │ │ - tasks │ │ │ +│ │ │ - cleaned │ │ - results │ │ │ │ - journals │ │ │ +│ │ │ - ner │ │ │ │ │ │ - reviewers │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ │ │ │ +│ │ ┌─────────────┐ │ │ +│ │ │admin │ │ │ +│ │ │_schema │ │ │ +│ │ │ │ │ │ +│ │ │ - admin_users│ │ │ +│ │ │ - llm_models│ │ │ +│ │ │ - tenants │ │ │ +│ │ │ - audit_logs│ │ │ +│ │ └─────────────┘ │ │ +│ └────────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────────────────────────────────────────┐ │ +│ │ Dify向量数据库(RAG) │ │ +│ │ 通过API访问,不直接连接 │ │ +│ └────────────────────────────────────────────────────────────────────────┘ │ +└───────────────────────────────────────────────────────────────────────────────┘ + ↓ +┌───────────────────────────────────────────────────────────────────────────────┐ +│ 外部服务层 │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ DeepSeek │ │ Qwen3 │ │ Qwen-Long │ │ Claude │ │ +│ │ API │ │ API │ │ API │ │ API │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ ¥1/百万tokens│ │ ¥5/百万tokens│ │ ¥20/百万 │ │ ¥50/百万 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ Dify API │ │ Python微服务│ │ +│ │ (RAG) │ │ (文档提取) │ │ +│ └──────────────┘ └──────────────┘ │ +└───────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 📦 四种部署模式 + +### 模式1:云端SaaS版(当前重点) + +``` +┌─────────────────────────────────────────┐ +│ 云端服务器 │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ Nginx (反向代理) │ │ +│ │ ├─ app.yizhengxun.com │ │ +│ │ └─ admin.yizhengxun.com │ │ +│ └────────────────────────────────────┘ │ +│ ↓ │ +│ ┌────────────────────────────────────┐ │ +│ │ Web前端 (React) │ │ +│ │ Admin前端 (React + Ant Design Pro)│ │ +│ └────────────────────────────────────┘ │ +│ ↓ │ +│ ┌────────────────────────────────────┐ │ +│ │ 后端 (Node.js + Fastify) │ │ +│ │ - 8个业务模块 │ │ +│ │ - 通用能力层 │ │ +│ │ - 平台基础层 │ │ +│ └────────────────────────────────────┘ │ +│ ↓ │ +│ ┌────────────────────────────────────┐ │ +│ │ PostgreSQL (Docker) │ │ +│ │ - 多Schema隔离 │ │ +│ └────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ Redis (缓存) │ │ +│ └────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ Dify (RAG服务) │ │ +│ └────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ Python微服务 (文档提取) │ │ +│ └────────────────────────────────────┘ │ +└─────────────────────────────────────────┘ + +用户通过浏览器访问 +``` + +--- + +### 模式2:独立产品包(Docker打包) + +``` +┌─────────────────────────────────────────┐ +│ 审稿系统独立产品包 │ +│ (Docker Compose一键部署) │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ 前端容器 (Nginx + React) │ │ +│ └────────────────────────────────────┘ │ +│ ↓ │ +│ ┌────────────────────────────────────┐ │ +│ │ 后端容器 (Node.js) │ │ +│ │ - RVW模块(审稿功能) │ │ +│ │ - LLM网关(精简版) │ │ +│ │ - 文档处理(精简版) │ │ +│ │ - 用户认证(精简版) │ │ +│ └────────────────────────────────────┘ │ +│ ↓ │ +│ ┌────────────────────────────────────┐ │ +│ │ PostgreSQL容器 │ │ +│ │ - 只包含review_schema和users │ │ +│ └────────────────────────────────────┘ │ +│ │ +│ 一键部署脚本:deploy.sh │ +│ 配置文件:docker-compose.yml │ +└─────────────────────────────────────────┘ + +医院内网部署,数据不外泄 +``` + +--- + +### 模式3:Electron单机版 + +``` +┌─────────────────────────────────────────┐ +│ 用户电脑 (Windows/Mac) │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ Electron应用 │ │ +│ │ │ │ +│ │ ┌──────────────────────────────┐ │ │ +│ │ │ 渲染进程 (Chromium) │ │ │ +│ │ │ React前端(复用90%+) │ │ │ +│ │ └──────────────────────────────┘ │ │ +│ │ ↓ IPC通信 │ │ +│ │ ┌──────────────────────────────┐ │ │ +│ │ │ 主进程 (Node.js) │ │ │ +│ │ │ 后端逻辑(复用80%+) │ │ │ +│ │ └──────────────────────────────┘ │ │ +│ │ ↓ │ │ +│ │ ┌──────────────────────────────┐ │ │ +│ │ │ SQLite (本地数据库) │ │ │ +│ │ │ ~/Documents/YizhengxunData/ │ │ │ +│ │ └──────────────────────────────┘ │ │ +│ │ ↓ │ │ +│ │ ┌──────────────────────────────┐ │ │ +│ │ │ Python子进程 │ │ │ +│ │ │ 文档提取(打包在应用内) │ │ │ +│ │ └──────────────────────────────┘ │ │ +│ └────────────────────────────────────┘ │ +│ │ +│ 安装包:500MB+ │ +│ 100%离线运行 │ +└─────────────────────────────────────────┘ +``` + +--- + +### 模式4:私有化部署(K8s/Docker) + +``` +┌─────────────────────────────────────────┐ +│ 医院内网服务器 │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ K8s / Docker Swarm │ │ +│ │ │ │ +│ │ Pod/Container: │ │ +│ │ ├─ 前端服务 x2(高可用) │ │ +│ │ ├─ 后端服务 x3(负载均衡) │ │ +│ │ ├─ PostgreSQL x1(主从) │ │ +│ │ ├─ Redis x1 │ │ +│ │ ├─ Python微服务 x2 │ │ +│ │ └─ Dify服务(可选) │ │ +│ │ │ │ +│ │ Ingress: hospital-a.yizhengxun.com│ │ +│ └────────────────────────────────────┘ │ +│ │ +│ 一键部署脚本 + 运维监控 │ +└─────────────────────────────────────────┘ + +数据100%留在医院内网 +``` + +--- + +## 🎯 代码组织架构 + +### 当前结构(简单) + +``` +AIclinicalresearch/ + ├── frontend/ # Web前端 + ├── backend/ # 后端 + └── extraction_service/ # Python服务 +``` + +--- + +### 目标结构(Monorepo) + +``` +AIclinicalresearch/ + ├── packages/ # 共享包(可复用) + │ ├── shared-types/ # 类型定义(前后端共享) + │ ├── platform-core/ # 平台核心 + │ │ ├── auth/ # 用户认证 + │ │ ├── storage/ # 存储服务 + │ │ └── monitoring/ # 监控日志 + │ ├── capabilities-core/ # 通用能力 + │ │ ├── llm-gateway/ # LLM网关 + │ │ ├── document-processor/ # 文档处理 + │ │ └── rag-engine/ # RAG引擎 + │ └── ui-components/ # UI组件库(可选) + │ + ├── apps/ # 应用 + │ ├── web-frontend/ # Web前端 + │ ├── web-backend/ # Web后端 + │ ├── admin-frontend/ # 运营管理端前端 + │ └── electron/ # Electron单机版(未来) + │ ├── main/ # 主进程 + │ └── renderer/ # 渲染进程 + │ + ├── products/ # 独立产品打包(未来) + │ ├── review-system/ # 审稿系统独立产品 + │ ├── literature-system/ # AI文献独立产品 + │ └── data-cleaning-system/ # 数据清洗独立产品 + │ + ├── services/ # 微服务(未来) + │ ├── platform/ # 平台服务 + │ ├── capabilities/ # 通用能力服务 + │ └── modules/ # 业务模块服务 + │ + ├── extraction_service/ # Python服务(保持原位) + ├── docker-compose.yml # Docker配置 + ├── pnpm-workspace.yaml # Workspace配置 + └── package.json # 根package.json +``` + +**转换成本:** 2-3天 +**未来收益:** 节省7-11天 + +--- + +## 📊 关键指标 + +### 模块复用分析 + +| 通用能力 | 使用频率 | 依赖模块 | 优先级 | 状态 | +|---------|---------|---------|--------|------| +| **LLM网关** | 71% | AIA、ASL、PKB、DC、RVW | P0 | ❌ 待实现 | +| **文档处理** | 86% | AIA、ASL、PKB、DC、SSA、ST、RVW | P0 | ✅ 已实现 | +| **RAG引擎** | 43% | AIA、ASL、PKB | P1 | ✅ 已实现 | +| **ETL引擎** | 29% | DC、SSA | P2 | ❌ 待实现 | +| **医学NLP** | 14% | DC | P2 | ❌ 待实现 | + +### 模块独立性分析 + +| 模块 | 独立性 | 商业价值 | 可独立销售 | 优先级 | +|------|-------|---------|-----------|--------| +| RVW(审稿) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 是 | P1 | +| ASL(文献) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 是 | P0 | +| DC(数据清洗) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 是 | P1 | +| ADMIN(运营) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 是(SaaS) | P1 | +| SSA(统计分析) | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⚠️ 与ST协同 | P2 | +| ST(分析工具) | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⚠️ 与SSA协同 | P2 | +| AIA(AI问答) | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⚠️ 与PKB关联 | P2 | +| PKB(知识库) | ⭐⭐⭐ | ⭐⭐⭐ | ⚠️ 与AIA关联 | P2 | + +--- + +## 🎯 技术债务与投资决策 + +### 两个关键技术改造 + +| 改造项目 | 现在做 | 未来做 | 投入产出比 | 建议 | +|---------|-------|-------|-----------|------| +| **Schema隔离** | 1周 | 3-5周 | 300-500% | ⭐⭐⭐⭐⭐ 现在做 | +| **Monorepo转换** | 2-3天 | 7-11天 | 300-400% | ⭐⭐⭐⭐⭐ 现在做 | + +**核心结论:** +``` +现在投入: +- Schema隔离:3天 +- Monorepo转换:3天 +- 总计:6天(1周) + +未来节省: +- Schema隔离:15-25天 +- Monorepo转换:7-11天 +- 总计:22-36天(1个月+) + +投入产出比:300-500% + +建议:立即做! +``` + +--- + +## 📋 下一步行动方案 + +### 方案A:快速推进业务 ⭐⭐⭐ + +**适合:时间紧迫,必须立即上线ASL模块** + +**本周:** +- Day 1-7:ASL模块开发 + +**风险:** +- ❌ 技术债累积 +- ❌ 未来改造成本高(1个月) + +--- + +### 方案B:夯实基础,稳步推进 ⭐⭐⭐⭐⭐ **强烈推荐** + +**适合:重视长期架构健康,愿意投入1周** + +**Week 1:架构改造(6天)** +- Day 1-3:Schema隔离 + * 设计Schema结构 + * 修改Prisma Schema + * 数据迁移 + * 测试验证 + +- Day 4-6:Monorepo转换 + * 学习pnpm workspaces + * 重构代码结构 + * 提取共享代码 + * 测试验证 + +**Week 2-4:ASL模块开发** +- 享受清晰的架构 +- 享受代码复用的便利 + +**优点:** +- ✅ 一次性还清技术债 +- ✅ 为7个模块打基础 +- ✅ 避免未来1个月的重构成本 + +**投入产出比:** ⭐⭐⭐⭐⭐ 极高 + +--- + +### 方案C:折中方案 ⭐⭐⭐⭐ + +**适合:部分认同,希望快速看到业务进展** + +**本周:** +- Day 1-3:Monorepo转换(必须,近期开发运营管理端) +- Day 4-7:ASL模块开发 + +**未来(1-2个月后):** +- Schema隔离(数据量增长前) + +**优点:** +- ✅ 解决最紧迫的问题 +- ✅ 快速推进业务 + +**缺点:** +- ⚠️ Schema隔离成本会增加 + +--- + +## 🎉 今日成就总结 + +### 完成的核心工作 + +**1. 系统架构设计(100%)** +- ✅ 三层架构设计 +- ✅ 8个业务模块规划 +- ✅ 5个通用能力定义 +- ✅ 依赖关系分析 + +**2. 部署方案设计(100%)** +- ✅ 云端SaaS部署 +- ✅ 独立产品包部署 +- ✅ Electron单机版方案 +- ✅ 私有化部署 + +**3. 数据库架构(100%)** +- ✅ PostgreSQL Docker部署说明 +- ✅ Schema隔离方案 +- ✅ 逻辑vs物理隔离对比 +- ✅ 改造成本分析 + +**4. 运营管理端(100%)** +- ✅ 15个功能模块设计 +- ✅ 3阶段实施计划 +- ✅ 权限体系设计 +- ✅ 数据库Schema设计 + +**5. 模块独立部署(100%)** +- ✅ 完整打包方案 +- ✅ 共享服务方案 +- ✅ Electron架构设计 +- ✅ 代码复用率分析 + +**6. Monorepo架构(100%)** +- ✅ 必要性评估 +- ✅ 成本收益分析 +- ✅ 实施方案设计 +- ✅ 时机建议 + +**7. 文档体系重构(100%)** +- ✅ v2.0文档结构设计 +- ✅ 模块文档模板 +- ✅ 迁移计划 + +--- + +### 产出的核心价值 + +**技术价值:** +- ✅ 清晰的技术路线图(未来6-12个月) +- ✅ 完整的架构设计(三层架构、8个模块) +- ✅ 详细的实施方案(部署、数据库、代码组织) + +**商业价值:** +- ✅ 支持模块独立销售(审稿、AI文献、数据清洗) +- ✅ 支持多种部署模式(覆盖全市场) +- ✅ 支持灵活商业模式(订阅、License、模块化售卖) + +**决策价值:** +- ✅ 明确的优先级(P0-P2) +- ✅ 清晰的成本收益分析 +- ✅ 具体的实施建议 + +--- + +## 📈 架构成熟度评估 + +### 设计完整性:⭐⭐⭐⭐⭐ (5/5) + +- ✅ 系统架构:三层架构,职责清晰 +- ✅ 业务模块:8个模块,完整规划 +- ✅ 部署方案:4种模式,覆盖全市场 +- ✅ 数据库架构:Schema隔离,支持微服务 +- ✅ 代码组织:Monorepo,支持复用 +- ✅ 运营管理:15个功能,商业基础 + +### 可实施性:⭐⭐⭐⭐⭐ (5/5) + +- ✅ 详细的实施步骤 +- ✅ 明确的时间估算 +- ✅ 清晰的成本收益分析 +- ✅ 具体的技术方案 +- ✅ 现实的风险评估 + +### 商业价值:⭐⭐⭐⭐⭐ (5/5) + +- ✅ 支持模块独立销售 +- ✅ 支持多种部署模式 +- ✅ 支持灵活定价策略 +- ✅ 成本控制机制完善 + +--- + +## 🚀 明确的下一步 + +### 立即可做的3件事 + +**1. 开始ASL模块开发(方案A)** +- 使用现有架构 +- 快速推进业务 +- 风险:累积技术债 + +**2. Schema隔离 + Monorepo(方案B)** ⭐⭐⭐⭐⭐ +- 投入1周 +- 夯实基础 +- 避免未来1个月改造成本 + +**3. Monorepo转换后开发(方案C)** +- 投入2-3天 +- 快速推进 +- 延后Schema隔离 + +--- + +## 💼 需要讨论的问题 + +### 技术决策 + +1. **Schema隔离:** 现在做 or 延后? + - 建议:现在做(成本最低) + +2. **Monorepo:** 现在转换 or 延后? + - 建议:现在转换(近期开发运营管理端) + +3. **部署优先级:** 先做哪种部署? + - 建议:专注云端SaaS(阶段一) + +### 业务规划 + +4. **模块开发顺序:** ASL → DC → SSA? + - 建议:ASL(已有PRD)→ DC(核心竞争力) + +5. **运营管理端时机:** 何时开发? + - 建议:ASL完成后(1-2个月后) + +6. **独立产品时机:** 何时打包独立产品? + - 建议:阶段二(6-12个月后) + +--- + +## 🎯 我的最终建议 + +**推荐:方案B(夯实基础,稳步推进)** + +**实施计划:** +``` +Week 1(本周):架构改造 + Day 1-3:Schema隔离 + Day 4-6:Monorepo转换 + +Week 2-4(下2-3周):ASL模块开发 + 标题摘要初筛 + 全文复筛 + +Week 5-6(第5-6周):ASL模块完善 + 全文解析与数据提取 + +Week 7-8(第7-8周):运营管理端P0功能 + 用户管理 + Feature Flag + LLM模型管理 +``` + +**核心理由:** +1. ✅ 投入1周,节省未来1个月 +2. ✅ 为7个模块打下坚实基础 +3. ✅ 架构清晰,长期收益巨大 +4. ✅ 避免技术债累积 + +--- + +**最后更新:** 2025-11-06 +**总结人:** 技术架构师 + +--- + +## 📖 相关文档 + +- [系统架构分层设计](./01-系统架构分层设计.md) +- [文档体系重构方案v2.0](./02-文档体系重构方案.md) +- [Schema隔离方案与成本分析](./05-Schema隔离方案与成本分析.md) +- [模块独立部署与单机版方案](./06-模块独立部署与单机版方案.md) +- [Monorepo架构评估](./07-Monorepo架构评估.md) + + + + + + + + + + + + + + + diff --git a/docs/00-系统总体设计/09-总体需求文档(PRD).md b/docs/00-系统总体设计/09-总体需求文档(PRD).md new file mode 100644 index 00000000..78a3084c --- /dev/null +++ b/docs/00-系统总体设计/09-总体需求文档(PRD).md @@ -0,0 +1,100 @@ +# **壹证循科技 \- AI科研产品需求文档 (PRD)** + +文档版本: 1.0 +日期: 2025年11月5日 +致: 产品部、市场部、销售部、技术部及全体同仁 + +## **1\. 文档目的** + +本需求文档 (Product Requirements Document, PRD) 旨在清晰定义"壹证循科技AI科研产品"的核心功能、目标用户及关键的非功能性需求。 + +本文档是跨部门协作的基石,用于确保技术实现、产品设计与市场策略保持高度一致,特别是应对我们复杂的商业模式和多样的客户部署需求。 + +## **2\. 产品愿景与目标** + +产品愿景: +打造一个覆盖临床科研全生命周期、AI驱动的一站式智能科研平台。 +核心目标: +赋能临床医生和科研人员,通过AI与自动化工具,极大地提升科研效率、数据处理质量和文献分析深度,缩短从数据到洞察的周期。 + +## **3\. 目标用户画像 (User Personas)** + +1. **临床医生/研究者**: + * **画像**:三甲医院的科室主任、主治医师、研究生。 + * **痛点**:有科研项目(如国自然、GCP、回顾性研究)压力,但临床工作繁忙,缺乏专业统计和数据处理时间。 + * **需求**:需要"开箱即用"的工具,快速处理院内数据、分析文献、完成统计、撰写论文。**极其关注患者数据隐私与安全**。 +2. **医院科室/IT部门**: + * **画像**:医院的科研管理科室、信息中心。 + * **痛点**:需要为全院提供合规、可控的科研工具;希望科研数据(尤其是HIS、LIS数据)保留在院内。 + * **需求**:采购的工具必须支持**私有化部署**,且安全、稳定、易于管理。 + +## **4\. 核心功能需求 (Functional Requirements)** + +产品功能矩阵共分为7大核心模块,共同构成科研全流程闭环。 + +| 模块ID | 模块名称 | 核心功能描述 | +| :---- | :---- | :---- | +| **F1** | **智能统计分析 (SSA)** | 提供3条核心分析路径:队列研究、预测模型、RCT研究。实现从数据上传、质控、分析到报告导出的完整流程。 | +| **F2** | **统计分析工具 (ST)** | 提供一个包含100+种轻量化统计工具的工具箱,满足即时、小型的分析需求。 | +| **F3** | **AI智能回答 (AIA)** | 提供10+个专业AI智能体,覆盖科研关键节点:选题评价、PICO梳理、样本量计算、研究方案制定、文章润色与翻译等。 | +| **F4** | **AI智能文献 (ASL)** | 提供AI驱动的文献工作流:智能检索、标题摘要初筛、全文复筛、信息提取,并支持Meta分析、证据图谱等应用。 | +| **F5** | **个人知识库 (PKB)** | 允许用户创建私人文献库(如每个库50篇),并能基于库内文献进行AI问答(RAG)。 | +| **F6** | **数据清洗整理 (DC)** | **(核心难点)** 提供专业工具,处理医院导出的海量(百万行级)、多表格(10+张)的Excel数据,实现两大功能: 1\. **表格ETL**:将多张散乱的流水表,按"患者ID"和"时间"重组为一张干净的分析宽表。 2\. **文本提取(NER)**:从病理报告、住院小结等大段文本中,自动提取结构化的关键字段(如TNM分期)。 | +| **F7** | **个人中心 (UAM)** | 提供账户管理、版本信息、帮助中心、订单管理等基础支撑功能。 | + +## **5\. 关键非功能性需求 (Non-Functional Requirements)** + +这是本产品在商业推广中**最复杂、最核心**的需求,是技术架构必须解决的关键挑战。 + +### **NFR-1: 部署模式灵活性 (Deployment Flexibility)** + +产品必须支持以下四种部署形态,以满足不同客户(尤其是医院)对数据安全和合规性的严苛要求。 + +| 部署形态 | 描述 | 关键要求 | +| :---- | :---- | :---- | +| **云端SaaS版** | (默认) 产品部署在公有云,用户通过浏览器按需订阅使用。 | 必须支持多租户、高可用。 | +| **私有化部署** | (医院/机构) 将**整个平台**或**指定模块**(如DC, SSA)部署在客户的内网服务器上。 | 必须提供容器化(Docker/K8s)的一键部署方案。数据100%不出内网。 | +| **混合部署** | (私有化客户) 客户在内网使用"私有化"的DC/SSA模块,同时能调用我们"云端"的ASL/AIA模块。 | 平台必须支持这种"本地+云端"的混合调用模式,前端需智能路由。 | +| **单机版** | (个人医生) 提供**可安装的桌面应用(Windows/Mac)**,针对DC、SSA、ASL等核心模块。 | **数据100%本地化**。文献、病例原始文件严禁上传。必须支持离线运行(如DC, SSA)。 *(例外:ASL模块可受控调用云端LLM,但仅限发送"摘要"而非"原文")* | + +### **NFR-2: 商业模式可配置 (Commercial Flexibility)** + +产品架构必须支持销售和市场策略的灵活性。 + +1. **SaaS多版本 (Tiered SaaS)** + * **需求**:云端SaaS版必须支持至少三个等级:**专业版、高级版、旗舰版**。 + * **实现**:后端需具备完善的\*\*功能开关(Feature Flag)\*\*系统,能按版本限制用户可用的功能模块、使用次数、AI模型等。 +2. **模块化售卖 (Modular Sales)** + * **需求**:任何一个功能模块(如F4: AI智能文献)都**必须能**被独立打包,作为单独的产品进行售卖。 + * **实现**:技术架构必须是"松耦合"的,确保模块间没有硬依赖。 +3. **AI成本可控 (Configurable AI)** + * **需求**:为应对不同客户的成本敏感度,平台必须支持**动态切换**后端的大语言模型。 + * **实现**:例如,专业版用户默认调用成本较低的DeepSeek,旗舰版用户可调用更高质量的Claude或GPT。 + +### **NFR-3: 平台与性能 (Platform & Performance)** + +1. **平台兼容性**: + * Web版:必须支持所有现代浏览器(Chrome, Edge, Firefox, Safari)。 + * 单机版:必须支持 **Windows 10 (64位)及以上** 和 **macOS (Intel & Apple Silicon)**。 + * **【重点】明确不支持**:为保证产品稳定性和数据安全,单机版**不支持**任何32位操作系统及Windows 7等已停止服务的系统。 +2. **数据处理性能 (DC模块)**: + * 产品(尤其是单机版)必须能稳定处理百万行级别的多表Excel数据,**严禁**因内存溢出而导致程序崩溃。 + * 产品(服务器版)在处理相同数据时,应追求极致效率,在数分钟内完成处理。 +3. **文本提取质量 (DC模块)**: + * 产品必须能提供高准确率的医学文本提取。 + * **服务器版(最优解)**:应使用SOTA(业界顶尖)的LLM(如Claude 3)以保证最高质量。 + * **单机版(妥协解)**:为保证数据隐私,必须使用100%本地运行的NLP模型(如spaCy),在保护隐私的前提下尽力提高准确率。 + + + + + + + + + + + + + + diff --git a/docs/03-业务规则/核心业务规则总览.md b/docs/00-系统总体设计/10-核心业务规则总览.md similarity index 99% rename from docs/03-业务规则/核心业务规则总览.md rename to docs/00-系统总体设计/10-核心业务规则总览.md index 25f81759..3d92736f 100644 --- a/docs/03-业务规则/核心业务规则总览.md +++ b/docs/00-系统总体设计/10-核心业务规则总览.md @@ -593,3 +593,15 @@ + + + + + + + + + + + + diff --git a/docs/00-系统总体设计/99-下一步行动决策建议.md b/docs/00-系统总体设计/99-下一步行动决策建议.md new file mode 100644 index 00000000..3d961a4e --- /dev/null +++ b/docs/00-系统总体设计/99-下一步行动决策建议.md @@ -0,0 +1,630 @@ +# 下一步行动决策建议 + +> **文档版本:** v1.0 +> **创建日期:** 2025-11-06 +> **文档目的:** 帮助您做出明确的技术和业务决策 + +--- + +## 🎯 当前状态 + +### ✅ 今日已完成 + +**架构设计(100%完成):** +- ✅ 8个核心文档(6000+行) +- ✅ 三层架构设计 +- ✅ 8个业务模块规划 +- ✅ 4种部署方案 +- ✅ 完整的技术路线图 + +**核心决策(100%完成):** +- ✅ 明确了运营管理端的定位和功能 +- ✅ 明确了模块独立部署方案 +- ✅ 明确了Electron单机版方案 +- ✅ 明确了Schema隔离的时机和成本 +- ✅ 明确了Monorepo的必要性和时机 + +--- + +## 🤔 需要您决策的关键问题 + +### 问题1:是否现在做Schema隔离?⭐⭐⭐⭐⭐ + +**选项A:现在做(强烈推荐)** +``` +投入:1周(3天开发 + 2天测试) +收益:避免未来3-5周改造 +ROI:300-500% + +优势: +✅ 数据量小(< 1万行),迁移快 +✅ 为7个模块打基础 +✅ 支持模块独立部署 +✅ 支持微服务拆分 + +劣势: +⚠️ ASL模块延迟1周 +``` + +**选项B:延后(不推荐)** +``` +投入:0(当前) +成本:未来3-5周改造 + +优势: +✅ 立即开始ASL开发 + +劣势: +❌ 技术债累积 +❌ 未来成本5倍 +❌ 数据量大,迁移风险高 +``` + +**我的建议:选择A(现在做)** ⭐⭐⭐⭐⭐ + +--- + +### 问题2:是否现在转换Monorepo?⭐⭐⭐⭐⭐ + +**选项A:现在转换(强烈推荐)** +``` +投入:2-3天 +收益:节省未来7-11天 +ROI:300-400% + +优势: +✅ 近期开发运营管理端(独立前端) +✅ 代码复用变容易 +✅ 为Electron单机版打基础 +✅ 为独立产品打包打基础 + +劣势: +⚠️ 需要学习新技术(但有AI全程指导) +``` + +**选项B:延后(不推荐)** +``` +投入:0(当前) +成本:未来7-11天改造 + +优势: +✅ 立即开始ASL开发 + +劣势: +❌ 代码重复(类型定义、工具函数等) +❌ 运营管理端开发时必须转换 +❌ 未来成本更高 +``` + +**我的建议:选择A(现在转换)** ⭐⭐⭐⭐⭐ + +**补充说明:** +- 如果近期(1-2个月)开发运营管理端,Monorepo是必须的 +- 如果6个月内不开发运营管理端,可以延后 + +--- + +### 问题3:下一步开发什么? + +**选项A:立即开发ASL模块** +``` +时间:从明天开始 +内容:标题摘要初筛 + 全文复筛 + +优势: +✅ 快速推进业务 +✅ 满足市场需求 + +劣势: +❌ 技术债累积 +❌ 未来改造成本高 +``` + +**选项B:先做架构改造,再开发ASL** +``` +时间: +- Week 1:Schema隔离 + Monorepo(6天) +- Week 2+:ASL模块开发 + +优势: +✅ 架构清晰,长期收益 +✅ 代码复用便利 +✅ 避免未来1个月改造成本 + +劣势: +⚠️ ASL模块延迟1周 +``` + +**选项C:只做Monorepo,延后Schema隔离** +``` +时间: +- Day 1-3:Monorepo转换 +- Day 4+:ASL模块开发 +- 未来:Schema隔离(数据量增长前) + +优势: +✅ 解决最紧迫的问题(运营管理端需要) +✅ 快速推进业务 + +劣势: +⚠️ Schema隔离成本会增加 +``` + +**我的建议:选择B(先做架构改造)** ⭐⭐⭐⭐⭐ + +--- + +## 📊 三个方案对比 + +| 维度 | 方案A:立即开发 | 方案B:先改造 | 方案C:折中 | +|------|---------------|-------------|-----------| +| **ASL上线时间** | 最快(2周) | 延迟1周(3周) | 延迟3天(2.5周) | +| **技术债** | 累积 | 还清 | 部分还清 | +| **未来改造成本** | 1个月 | 0 | 3-5周 | +| **代码质量** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | +| **长期收益** | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | +| **投入产出比** | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | +| **推荐度** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | + +--- + +## 🎯 我的最终建议 + +### 推荐:方案B(先做架构改造,再开发)⭐⭐⭐⭐⭐ + +### 核心理由 + +**1. 投入1周,节省未来1个月** +``` +现在投入:6天 + - Schema隔离:3天 + - Monorepo转换:3天 + +未来节省:22-36天 + - Schema隔离:15-25天 + - Monorepo转换:7-11天 + +投入产出比:300-500% +``` + +**2. 正处于最佳时机** +``` +当前: +✅ 数据量小(< 1万行) +✅ 代码量适中 +✅ 只有2个已完成模块(AIA、PKB) +✅ 即将开发多个新模块(ASL、DC、ADMIN等) + +未来: +❌ 数据量大(100万行+) +❌ 代码量大 +❌ 7-8个模块同时运行 +❌ 重构影响范围巨大 +``` + +**3. 为7个模块打下坚实基础** +``` +即将开发的模块: +- ASL(AI智能文献) +- DC(数据清洗) +- SSA(智能统计) +- ST(分析工具) +- RVW扩展(完整审稿系统) +- ADMIN(运营管理端) + +这些模块都将受益于: +✅ Schema隔离(清晰的数据架构) +✅ Monorepo(代码复用便利) +``` + +**4. 避免技术债累积** +``` +技术债的特点: +- 时间越久,利息越高 +- 改造成本成倍增长 +- 影响业务创新速度 + +现在改造 = 一次性还清 +轻装上阵,专注业务 +``` + +--- + +## 📅 详细实施计划(方案B) + +### Week 1:架构改造(6天) + +**Day 1-3:Schema隔离** + +``` +Day 1(准备): +- [ ] 设计Schema结构 + * platform_schema(用户、权限、Feature Flag) + * aia_schema(AI问答) + * asl_schema(AI文献) + * pkb_schema(知识库) + * dc_schema(数据清洗) + * review_schema(稿件审查) + * admin_schema(运营管理) + +- [ ] 编写迁移脚本 +- [ ] 准备测试用例 + +Day 2(开发环境改造): +- [ ] 创建所有Schema(10分钟) +- [ ] 修改Prisma Schema(2-4小时) + * 为每个Model添加@@schema指定 + * 更新外键关联 + * 启用multiSchema特性 + +- [ ] 运行Prisma Migrate +- [ ] 执行数据迁移 +- [ ] 运行测试 + +Day 3(生产环境迁移 + 验证): +- [ ] 备份生产数据库 +- [ ] 执行Schema迁移 +- [ ] 数据迁移 +- [ ] 验证测试 +- [ ] 监控错误日志 +- [ ] 性能测试 +``` + +**Day 4-6:Monorepo转换** + +``` +Day 4(学习 + 设计): +- [ ] 学习pnpm workspaces(1-2小时) +- [ ] 设计目录结构 + * packages/(放什么?) + * apps/(放什么?) + +- [ ] 设计共享包 + * shared-types(类型定义) + * platform-core(平台核心,暂时不拆) + +Day 5(重构实施): +- [ ] 创建根package.json和pnpm-workspace.yaml +- [ ] 创建packages/shared-types/ +- [ ] 移动frontend到apps/frontend/ +- [ ] 移动backend到apps/backend/ +- [ ] 提取共享类型定义 +- [ ] 配置workspace依赖 +- [ ] 更新import路径 + +Day 6(测试验证): +- [ ] 测试前端启动 +- [ ] 测试后端启动 +- [ ] 测试类型提示 +- [ ] 测试构建 +- [ ] 验证所有功能 +- [ ] 优化配置 +``` + +--- + +### Week 2-4:ASL模块开发 + +**Week 2:标题摘要初筛** +- [ ] 数据库设计(asl_schema) +- [ ] API设计 +- [ ] 后端实现(PICO配置、AI判断、筛选结果) +- [ ] 前端实现(表格工作台、审查弹窗) + +**Week 3:全文复筛** +- [ ] PDF查看器集成 +- [ ] 全文AI判断 +- [ ] 前端实现(全文审查界面) + +**Week 4:完善和测试** +- [ ] 功能完善 +- [ ] 测试 +- [ ] 优化 + +--- + +## 🎯 关键成功因素 + +### 如果选择方案B(架构改造) + +**成功要素:** +1. ✅ **坚定的决心**:相信长期价值,不急功近利 +2. ✅ **AI全程协助**:我会全程指导每一步 +3. ✅ **详细的计划**:每天的任务都清晰明确 +4. ✅ **充足的时间**:愿意投入6天做架构改造 + +**预期结果:** +- ✅ 1周后,拥有清晰、健壮的架构 +- ✅ 为未来7个模块打下坚实基础 +- ✅ 避免未来1个月的技术债偿还 + +--- + +### 如果选择方案A(立即开发) + +**成功要素:** +1. ⚠️ **严格的代码规范**:必须使用表名前缀(asl_projects) +2. ⚠️ **延后但不放弃**:数据量增长前必须做Schema隔离 +3. ⚠️ **及时转换**:开发运营管理端前必须做Monorepo + +**触发架构改造的条件:** +- 数据量超过50万行 +- 开始开发运营管理端 +- 代码重复严重 + +--- + +## 💡 我的个人建议 + +### 如果我是您的技术架构师 + +**我会选择方案B(先做架构改造)** + +**理由:** + +**1. 技术债的利息太高** +``` +技术债本金:6天工作量 +6-12个月后利息:22-36天 + +利率:300-500% + +这是任何投资都无法企及的回报率 +``` + +**2. 现在是最佳时机** +``` +数据量:小(迁移快) +代码量:适中(重构容易) +模块数:少(测试简单) +团队规模:小(沟通成本低) + +6个月后,以上所有条件都会恶化 +``` + +**3. 为未来7个模块负责** +``` +如果现在不做: +- ASL模块:可能有一定技术债 +- DC模块:继承ASL的技术债 +- SSA模块:继承DC的技术债 +- ... +- 7个模块叠加,技术债巨大 + +如果现在做: +- ASL模块:享受清晰架构 +- DC模块:享受清晰架构 +- ... +- 7个模块都受益 +``` + +**4. 长期vs短期** +``` +短期思维: +- 快1周上线ASL +- 但未来付出1个月代价 + +长期思维: +- 投入1周改造 +- 未来7个模块都受益 +- 长期收益巨大 +``` + +--- + +## 📋 实施清单(方案B) + +### 如果您决定采纳方案B + +**我会帮您:** + +**Schema隔离(Day 1-3):** +1. ✅ 设计完整的Schema结构 +2. ✅ 编写Prisma Schema修改方案 +3. ✅ 编写数据迁移脚本 +4. ✅ 提供详细的实施步骤 +5. ✅ 全程指导和答疑 + +**Monorepo转换(Day 4-6):** +1. ✅ 设计Monorepo目录结构 +2. ✅ 编写pnpm-workspace.yaml配置 +3. ✅ 提供重构步骤清单 +4. ✅ 帮助提取共享代码 +5. ✅ 全程指导和答疑 + +**ASL模块开发(Week 2+):** +1. ✅ 数据库设计 +2. ✅ API设计 +3. ✅ 前后端实现指导 + +--- + +## 🎯 决策矩阵 + +### 如何选择? + +**选择方案B,如果您:** +- ✅ 重视长期架构健康 +- ✅ 愿意投入1周做改造 +- ✅ 相信投入产出比(300-500%) +- ✅ 未来有多个模块要开发 +- ✅ 未来有模块独立部署需求 + +**选择方案C,如果您:** +- ⚠️ 愿意做Monorepo(必需) +- ⚠️ 但希望尽快看到ASL进展 +- ⚠️ 接受未来Schema隔离成本会增加 + +**选择方案A,如果您:** +- ⚠️ 时间极其紧迫(如:有硬性deadline) +- ⚠️ ASL必须在2周内上线 +- ⚠️ 接受未来1个月的改造成本 +- ⚠️ 愿意承担技术债 + +--- + +## 📊 投入产出比总结 + +| 方案 | 立即投入 | 未来成本 | ASL上线时间 | 长期收益 | 推荐度 | +|------|---------|---------|------------|---------|-------| +| **方案A** | 0 | 22-36天 | 2周 | ⭐⭐ | ⭐⭐⭐ | +| **方案B** | 6天 | 0 | 3周 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | +| **方案C** | 2-3天 | 15-25天 | 2.5周 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | + +--- + +## 🚀 立即可执行的行动 + +### 如果您决定方案B(推荐) + +**明天开始:** +```bash +# Day 1:Schema隔离设计 +我会帮您: +1. 设计完整的Schema结构 +2. 修改Prisma Schema +3. 编写迁移脚本 +4. 准备测试用例 + +您需要: +1. 审阅Schema设计 +2. 确认迁移方案 +3. 执行迁移命令 +``` + +--- + +### 如果您决定方案C(折中) + +**明天开始:** +```bash +# Day 1:Monorepo转换 +我会帮您: +1. 设计Monorepo目录结构 +2. 编写配置文件 +3. 提供重构步骤 +4. 全程指导 + +您需要: +1. 安装pnpm(如果没有) +2. 执行重构命令 +3. 测试验证 +``` + +--- + +### 如果您决定方案A(立即开发) + +**明天开始:** +```bash +# Day 1:ASL模块数据库设计 +我会帮您: +1. 设计asl_schema表结构 +2. 编写Prisma Schema +3. API设计 +4. 前端组件设计 + +您需要: +1. 审阅设计方案 +2. 确认后开始实施 +``` + +--- + +## 💼 最后的决策建议 + +### 如果只能记住一件事 + +**记住这个:** +``` +现在投入1周(Schema隔离 + Monorepo) +节省未来1个月(22-36天) + +这是300-500%的投资回报率 + +而且: +- 现在是成本最低的时机 +- 未来成本会成倍增长 +- 这是为7个模块打基础 +``` + +### 如果还在犹豫 + +**问自己3个问题:** + +1. **我的目标是什么?** + - 快速上线1个模块?→ 方案A + - 构建可持续的产品?→ 方案B + +2. **我能接受延迟吗?** + - 完全不能接受1周延迟?→ 方案A + - 可以接受1周换1个月?→ 方案B + +3. **我的技术债观念?** + - 先上线,后还债?→ 方案A + - 架构优先,稳步推进?→ 方案B + +--- + +## 🎉 今日成果回顾 + +**我们今天完成了:** +- ✅ 8个核心架构文档(6000+行) +- ✅ 完整的技术路线图 +- ✅ 清晰的决策建议 + +**这些成果的价值:** +- ✅ 指导未来6-12个月的开发 +- ✅ 避免走弯路 +- ✅ 支持模块化销售和多种部署 + +**您现在拥有的:** +- ✅ 清晰的架构设计 +- ✅ 明确的实施路径 +- ✅ 具体的成本收益分析 + +--- + +## 🤝 我的承诺 + +**无论您选择哪个方案,我都会全程协助!** + +**方案B:** +- ✅ Day 1-3:Schema隔离全程指导 +- ✅ Day 4-6:Monorepo转换全程指导 +- ✅ Week 2+:ASL模块开发全程协助 + +**方案C:** +- ✅ Day 1-3:Monorepo转换全程指导 +- ✅ Day 4+:ASL模块开发全程协助 + +**方案A:** +- ✅ 立即开始ASL模块开发全程协助 +- ⚠️ 提醒技术债风险 +- ⚠️ 在关键时机提醒架构改造 + +--- + +**您的决定?** 😊 + +请告诉我您的选择,我们立即开始行动! + +--- + +**最后更新:** 2025-11-06 +**文档作者:** 技术架构师 + + + + + + + + + + + + + + + diff --git a/docs/00-系统总体设计/README.md b/docs/00-系统总体设计/README.md new file mode 100644 index 00000000..010c8cac --- /dev/null +++ b/docs/00-系统总体设计/README.md @@ -0,0 +1,278 @@ +# 系统总体设计 + +> **目录说明:** 本目录包含壹证循AI科研平台的系统总体设计文档 +> **文档层级:** 总体层面(Platform Level) +> **目标读者:** 技术架构师、产品经理、项目负责人 + +--- + +## 📚 文档导航 + +### 📌 快速导航 + +**🎉 重要:** [2025-11-06 架构设计完成报告](./%5B重要%5D%202025-11-06%20架构设计完成报告.md) - 今日重大里程碑! + +**👉 第一次阅读:** [00-阅读指南](./00-阅读指南.md) - 如何阅读这些文档 + +**👉 需要决策:** [99-下一步行动决策建议](./99-下一步行动决策建议.md) - 3个方案对比 + +--- + +### 核心文档 + +| 文档 | 说明 | 状态 | 优先级 | +|------|------|------|-------| +| **[00-阅读指南](./00-阅读指南.md)** | **如何阅读这些文档?** | ✅ 完成 | ⭐⭐⭐ **首次必读** | +| [00-今日架构设计总结](./00-今日架构设计总结.md) | 2025-11-06工作总结 | ✅ 完成 | ⭐⭐ 推荐阅读 | +| **[99-下一步行动决策建议](./99-下一步行动决策建议.md)** | **3个方案对比+决策建议** | ✅ 完成 | ⭐⭐⭐ **决策必读** | +| [00-核心问题解答](./00-核心问题解答.md) | 回答关键架构问题 | ✅ 完成 | P0 | +| [01-系统架构分层设计](./01-系统架构分层设计.md) | 三层架构设计(平台、能力、业务) | ✅ 完成 | P0 | +| [02-文档体系重构方案](./02-文档体系重构方案.md) | 文档结构重组方案v2.0 | ✅ 完成 | P0 | +| [03-数据库架构说明](./03-数据库架构说明.md) | PostgreSQL Docker部署说明 | ✅ 完成 | P0 | +| [04-运营管理端架构设计](./04-运营管理端架构设计.md) | 15个功能模块设计 | ✅ 完成 | P0 | +| [05-Schema隔离方案与成本分析](./05-Schema隔离方案与成本分析.md) | 逻辑隔离vs物理隔离 | ✅ 完成 | P0 | +| [06-模块独立部署与单机版方案](./06-模块独立部署与单机版方案.md) | 完整打包+Electron方案 | ✅ 完成 | P0 | +| [07-Monorepo架构评估](./07-Monorepo架构评估.md) | 当前阶段是否需要Monorepo | ✅ 完成 | P0 | +| [08-架构设计全景图](./08-架构设计全景图.md) | 一图看懂整个系统架构 | ✅ 完成 | ⭐ 推荐阅读 | +| 09-总体需求文档(PRD).md | 产品总体需求 | ⏳ 待迁移 | P1 | +| 10-技术架构白皮书.md | 技术架构总览 | ⏳ 待迁移 | P1 | +| 11-商业模式设计.md | 商业模式与定价 | ⏳ 待创建 | P2 | +| 12-版本规划.md | 版本演进路线图 | ⏳ 待创建 | P2 | + +--- + +## 🎯 核心内容概要 + +### 📌 快速开始 + +**如果您是第一次阅读,强烈推荐:** +1. ⭐ [今日架构设计总结](./00-今日架构设计总结.md) - 快速了解今天的成果 +2. ⭐ [架构设计全景图](./08-架构设计全景图.md) - 一图看懂整个系统 + +--- + +### 1. 系统架构分层 + +**三层架构 + 8个业务模块:** +``` +┌──────────────────────────────────────────────────┐ +│ 业务模块层(8个模块) │ +│ AIA | ASL | PKB | DC | SSA | ST | RVW | ADMIN │ +└──────────────────────────────────────────────────┘ + ↓ 依赖 +┌──────────────────────────────────────────────────┐ +│ 通用能力层(5个能力) │ +│ LLM网关(71%) | 文档处理(86%) | RAG(43%) │ +│ ETL(29%) | 医学NLP(14%) │ +└──────────────────────────────────────────────────┘ + ↓ 依赖 +┌──────────────────────────────────────────────────┐ +│ 平台基础层 │ +│ 用户权限 | 存储 | 通知 | 监控 | 配置 │ +└──────────────────────────────────────────────────┘ +``` + +**详见:** [01-系统架构分层设计.md](./01-系统架构分层设计.md) + +--- + +### 2. 文档体系结构 + +**新文档结构:** +``` +docs/ + ├── 00-系统总体设计/ # 总体层面 + ├── 01-平台基础层/ # 平台层(UAM、存储、通知等) + ├── 02-通用能力层/ # 通用能力(LLM网关、文档处理等) + ├── 03-业务模块/ # 7个独立业务模块 + │ ├── AIA-AI智能问答/ + │ ├── ASL-AI智能文献/ + │ ├── PKB-个人知识库/ + │ ├── DC-数据清洗整理/ + │ ├── SSA-智能统计分析/ + │ ├── ST-统计分析工具/ + │ └── RVW-稿件审查系统/ + ├── 04-开发规范/ + ├── 05-部署文档/ + ├── 06-测试文档/ + ├── 07-运维文档/ + └── 08-项目管理/ +``` + +**详见:** [02-文档体系重构方案.md](./02-文档体系重构方案.md) + +--- + +### 3. 核心决策 + +#### 部署模式(4种) +- ✅ 云端SaaS版(P0,当前) +- ✅ 独立产品包(P1,阶段二)- 支持模块化销售 +- ✅ Electron单机版(P2,阶段二)- 代码复用85%+ +- ✅ 私有化部署(P1,阶段二) +- ❌ ~~混合部署~~(不考虑) + +#### 模块划分(8个业务模块) +1. **AIA** - AI智能问答 ✅ 已完成 +2. **ASL** - AI智能文献 ⏳ 下一步重点 +3. **PKB** - 个人知识库 ✅ 已完成 +4. **DC** - 数据清洗整理 ⏳ 规划中 +5. **SSA** - 智能统计分析 ⏳ 规划中 +6. **ST** - 统计分析工具 ⏳ 规划中 +7. **RVW** - 稿件审查系统 ⚡ 独立系统 +8. **ADMIN** - 运营管理端 ⭐ v2.0新增 + +#### 核心能力(5个通用能力) +1. **LLM网关** ⭐ 最高优先级,5个模块依赖(71%复用率) +2. **文档处理引擎** ✅ 已实现,6个模块依赖(86%复用率) +3. **RAG引擎** ✅ 已实现,3个模块依赖(43%复用率) +4. **ETL引擎** ⏳ 待实现,2个模块依赖(29%复用率) +5. **医学NLP** ⏳ 待实现,1个模块依赖(14%复用率) + +#### 技术改造决策 +1. **Schema隔离** - 建议:现在做(1周)vs 未来做(3-5周)⭐⭐⭐⭐⭐ +2. **Monorepo转换** - 建议:现在做(2-3天)vs 未来做(7-11天)⭐⭐⭐⭐⭐ + +**详见:** [00-核心问题解答.md](./00-核心问题解答.md) + +--- + +## 🚀 架构演进路径 + +### 阶段一:模块化单体(当前 - 6个月) + +**目标:** 云端SaaS版MVP + +**关键纪律:** +- ✅ 严格按模块划分代码 +- ✅ 数据表使用模块前缀(逻辑隔离) +- ✅ 模块间不直接import + +**优先开发:** +- ASL(AI智能文献) +- DC(数据清洗) +- LLM网关 +- Schema隔离 + +--- + +### 阶段二:首次拆分(6-18个月) + +**触发条件:** +- 有客户要求私有化部署 +- 有客户要求单机版 +- 需要独立销售某个模块 + +**架构调整:** +- 引入API网关 +- 引入K8s(可选) +- 拆分RVW(审稿系统)为独立服务 + +--- + +### 阶段三:全面微服务(18个月+) + +**目标:** 所有模块独立部署,支持灵活组合 + +--- + +## 📊 关键指标 + +### 模块复用分析 + +| 通用能力 | 使用频率 | 复用模块数 | 优先级 | +|---------|---------|-----------|--------| +| LLM网关 | 71% | 5/7 | P0 | +| 文档处理 | 86% | 6/7 | P0 | +| RAG引擎 | 43% | 3/7 | P1 | +| ETL引擎 | 29% | 2/7 | P2 | +| 医学NLP | 14% | 1/7 | P2 | + +### 模块独立性分析 + +| 模块 | 独立性 | 商业价值 | 可独立销售 | +|------|-------|---------|-----------| +| RVW(审稿) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 是 | +| ASL(文献) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 是 | +| DC(数据清洗) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 是 | +| SSA(统计分析) | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⚠️ 与ST协同 | +| ST(分析工具) | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⚠️ 与SSA协同 | +| AIA(AI问答) | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⚠️ 与PKB关联 | +| PKB(知识库) | ⭐⭐⭐ | ⭐⭐⭐ | ⚠️ 与AIA关联 | + +--- + +## ✅ 当前任务清单 + +### P0任务(已完成)✅ + +- [x] 系统架构分层设计 +- [x] 文档体系重构方案v2.0 +- [x] 核心问题解答 +- [x] 数据库架构说明 +- [x] 运营管理端架构设计 +- [x] Schema隔离方案与成本分析 +- [x] 模块独立部署与单机版方案 +- [x] Monorepo架构评估 +- [x] 架构设计全景图 +- [x] 今日工作总结 + +### P1任务(待决策)⏳ + +**关键决策点:** +- [ ] 是否现在做Schema隔离?(建议:是,1周) +- [ ] 是否现在转换Monorepo?(建议:是,2-3天) + +**如果决定先做架构改造(方案B):** +- [ ] Week 1:Schema隔离 + Monorepo转换(6天) +- [ ] Week 2+:ASL模块开发 + +**如果决定立即开发(方案A):** +- [ ] Week 1+:ASL模块开发 +- [ ] 未来:架构改造(成本更高) + +### P2任务(后续) + +- [ ] 迁移总体需求文档和技术架构白皮书 +- [ ] 补充ASL模块缺失文档 +- [ ] LLM网关详细设计 +- [ ] RVW独立系统规划 +- [ ] 补充运营管理端详细文档 + +--- + +## 📖 相关文档 + +### 平台基础层 +- [01-平台基础层/](../01-平台基础层/) - 用户权限、存储、通知等 + +### 通用能力层 +- [02-通用能力层/](../02-通用能力层/) - LLM网关、文档处理、RAG等 + +### 业务模块 +- [03-业务模块/](../03-业务模块/) - 7个独立业务模块 + +### 项目管理 +- [08-项目管理/](../08-项目管理/) - 开发计划、里程碑、每日进度 + +--- + +## 🤝 贡献指南 + +### 如何更新文档 + +1. **总体架构调整**:需要团队讨论,更新本目录文档 +2. **模块设计调整**:更新对应模块目录文档 +3. **文档格式**:遵循Markdown规范,包含目录、表格、代码块 + +### 文档审核流程 + +1. 技术架构师审核总体文档 +2. 模块负责人审核模块文档 +3. 定期同步文档与代码 + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + diff --git a/docs/00-系统总体设计/[AI对接] 快速上下文.md b/docs/00-系统总体设计/[AI对接] 快速上下文.md new file mode 100644 index 00000000..9033c004 --- /dev/null +++ b/docs/00-系统总体设计/[AI对接] 快速上下文.md @@ -0,0 +1,167 @@ +# [AI对接] 快速上下文 + +> **阅读时间:** 2分钟 +> **Token消耗:** ~800 tokens +> **适用场景:** 新AI首次对话,快速了解项目全貌 +> **文档版本:** v2.0 +> **最后更新:** 2025-11-06 + +--- + +## 一句话描述 + +**壹证循AI科研平台**:覆盖医学科研全流程的智能化平台,包含8个业务模块,支持4种部署模式(SaaS、独立产品、单机版、私有化)。 + +--- + +## 📊 核心信息卡片 + +### 项目状态 +- **当前阶段:** 架构设计完成,文档重构中 +- **已完成:** AIA(AI问答)、PKB(知识库)、RVW(审稿)核心功能 +- **下一步:** ASL(AI智能文献)模块开发 +- **技术栈:** Node.js + React + PostgreSQL + Python微服务 + +### 8个业务模块(优先级排序) + +| 模块 | 名称 | 状态 | 优先级 | 说明 | +|------|------|------|--------|------| +| **ASL** | AI智能文献 | ⏳ 下一步 | P0 | 文献筛选、提取、分析 | +| **AIA** | AI智能问答 | ✅ 已完成 | - | 12个智能体、多轮对话 | +| **PKB** | 个人知识库 | ✅ 已完成 | - | RAG问答、智能引用 | +| **RVW** | 稿件审查 | ⚡ 独立系统 | P1 | 稿约规范性+方法学评估 | +| **ADMIN** | 运营管理端 | ⏳ 规划中 | P1 | 15个功能模块 | +| **DC** | 数据清洗 | ⏳ 规划中 | P1 | 核心竞争力 | +| **SSA** | 智能统计 | ⏳ 规划中 | P2 | 3条分析路径 | +| **ST** | 统计工具 | ⏳ 规划中 | P2 | 100+小工具 | + +### 关键架构决策 +1. ✅ **三层架构:** 平台层 + 通用能力层 + 业务模块层 +2. ⏳ **Schema隔离:** 即将实施(1周) +3. ⏳ **Monorepo:** 即将转换(2-3天) +4. ✅ **4种部署模式:** SaaS + 独立产品 + 单机版 + 私有化 + +--- + +## 📁 代码结构 + +``` +AIclinicalresearch/ + ├── backend/ # Node.js + Fastify + Prisma + ├── frontend/ # React + Vite + Ant Design + ├── extraction_service/ # Python FastAPI微服务 + ├── docs/ # 📖 文档(你在这里) + └── docker-compose.yml # PostgreSQL + Redis +``` + +--- + +## 🔧 核心依赖 + +**LLM模型:** +- DeepSeek-V3(主力,¥1/百万tokens) +- Qwen3-72B(备用,¥5/百万tokens) +- Qwen-Long(超长上下文,1M tokens) + +**数据库:** +- PostgreSQL 15(Docker部署) +- Redis(缓存) +- Dify(RAG向量数据库) + +**已实现能力:** +- ✅ 文档处理引擎(Python微服务) +- ✅ RAG引擎(基于Dify) +- ❌ LLM网关(待实现,P0优先级) + +--- + +## 🚀 快速跳转(根据任务选择) + +### 开发任务 + +**开发ASL模块:** +→ 阅读 `03-业务模块/ASL-AI智能文献/[AI对接] ASL快速上下文.md` + +**实现LLM网关:** +→ 阅读 `02-通用能力层/01-LLM大模型网关/[AI对接] LLM网关快速上下文.md` + +**开发运营管理端:** +→ 阅读 `03-业务模块/ADMIN-运营管理端/README.md` + +### 架构讨论 + +**了解整体架构:** +→ 阅读 `00-系统总体设计/08-架构设计全景图.md` + +**了解数据库架构:** +→ 阅读 `00-系统总体设计/03-数据库架构说明.md` + +**了解模块独立部署:** +→ 阅读 `00-系统总体设计/06-模块独立部署与单机版方案.md` + +### 了解现有系统 + +**了解已完成功能:** +→ 阅读 `00-项目概述/现有系统技术摸底报告.md` + +--- + +## 📖 常见任务快速指引 + +| 任务类型 | 需要阅读的文档(按顺序) | Token消耗 | 阅读时间 | +|---------|----------------------|----------|---------| +| **开发新模块** | 1. 本文档
2. 对应模块快速上下文
3. 模块PRD | ~3K | 5-8分钟 | +| **修改已有功能** | 1. 本文档
2. 现有系统摸底报告
3. 具体模块文档 | ~5K | 10-15分钟 | +| **架构讨论** | 1. 本文档
2. 架构设计全景图
3. 分层设计文档 | ~4K | 10分钟 | +| **数据库修改** | 1. 本文档
2. 数据库架构说明
3. 具体模块数据库设计 | ~3K | 5-8分钟 | + +--- + +## ⚠️ 关键提醒 + +**数据库:** +- ✅ PostgreSQL在Docker中,不需要手动安装 +- ✅ 数据库名:`ai_clinical_research` +- ✅ 连接:`localhost:5432` + +**Dify:** +- ✅ 独立系统,有自己的数据库 +- ✅ 通过API调用,不直接访问数据库 + +**文档状态:** +- ⏳ 文档正在重构中(v3.0) +- ✅ 新文档按三层架构组织 +- ✅ 每个层级都有快速上下文 + +--- + +## 📂 文档导航体系 + +``` +docs/ + ├── 00-系统总体设计/ # 总体架构、技术决策 + ├── 01-平台基础层/ # 用户权限、存储、通知 + ├── 02-通用能力层/ # LLM网关、文档处理、RAG + ├── 03-业务模块/ # 8个独立业务模块 + ├── 04-开发规范/ # API规范、数据库规范、代码规范 + ├── 05-部署文档/ # 4种部署模式 + └── 08-项目管理/ # 开发计划、里程碑、每日进度 +``` + +--- + +## 🎯 下一步行动(2025-11-06) + +**本周计划:** +1. 完成文档重构 +2. 创建快速上下文体系 +3. 准备ASL模块开发 + +**未来2-4周:** +1. ASL模块开发(标题摘要初筛 + 全文复筛) +2. LLM网关实现(P0,ASL依赖) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 diff --git a/docs/00-系统总体设计/[重要] 2025-11-06 架构设计完成报告.md b/docs/00-系统总体设计/[重要] 2025-11-06 架构设计完成报告.md new file mode 100644 index 00000000..068125af --- /dev/null +++ b/docs/00-系统总体设计/[重要] 2025-11-06 架构设计完成报告.md @@ -0,0 +1,554 @@ +# 2025-11-06 架构设计完成报告 + +> **报告日期:** 2025-11-06 +> **报告类型:** 里程碑完成报告 +> **重要程度:** ⭐⭐⭐⭐⭐ 极高 + +--- + +## 🎉 重大里程碑 + +### 今日完成:壹证循AI科研平台 - 完整架构设计 + +**历时:** 1天深度设计 +**产出:** 11个核心文档,6000+行详细设计 +**价值:** 指导未来6-12个月的技术和业务发展 + +--- + +## 📊 完成清单 + +### 核心架构文档(11个) + +| # | 文档名称 | 页数/行数 | 核心价值 | +|---|---------|---------|---------| +| 1 | 00-阅读指南 | - | 快速导航 | +| 2 | 00-核心问题解答 | 详尽 | 回答关键架构问题 | +| 3 | 00-今日架构设计总结 | 详尽 | 工作总结 | +| 4 | 01-系统架构分层设计 | 926行 | 三层架构核心设计 | +| 5 | 02-文档体系重构方案v2.0 | 790行 | 文档结构重组 | +| 6 | 03-数据库架构说明 | 434行 | 数据库澄清 | +| 7 | 04-运营管理端架构设计 | 859行 | 15个功能模块 | +| 8 | 05-Schema隔离方案与成本分析 | 1042行 | 改造决策依据 | +| 9 | 06-模块独立部署与单机版方案 | 1541行 | 部署技术方案 | +| 10 | 07-Monorepo架构评估 | 555行 | Monorepo决策依据 | +| 11 | 08-架构设计全景图 | 详尽 | 一图看懂系统 | +| 12 | 99-下一步行动决策建议 | 详尽 | 明确行动方案 | + +**总计:** 6000+ 行详细设计 + +--- + +## 🏗️ 核心架构成果 + +### 1. 系统架构分层(三层架构) + +``` +业务模块层(8个模块) + AIA | ASL | PKB | DC | SSA | ST | RVW | ADMIN + ↓ +通用能力层(5个能力) + LLM网关(71%) | 文档处理(86%) | RAG(43%) | ETL(29%) | NLP(14%) + ↓ +平台基础层 + 用户权限 | 存储 | 通知 | 监控 | 配置 +``` + +**价值:** +- ✅ 职责清晰,易于理解 +- ✅ 高复用率(LLM网关71%,文档处理86%) +- ✅ 支持模块独立部署 + +--- + +### 2. 业务模块规划(8个模块) + +| 模块 | 状态 | 商业价值 | 独立性 | 优先级 | +|------|------|---------|-------|-------| +| AIA - AI智能问答 | ✅ 已完成 | ⭐⭐⭐⭐ | ⭐⭐⭐ | - | +| ASL - AI智能文献 | ⏳ 下一步重点 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | P0 | +| PKB - 个人知识库 | ✅ 已完成 | ⭐⭐⭐ | ⭐⭐⭐ | - | +| DC - 数据清洗整理 | ⏳ 规划中 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | P1 | +| SSA - 智能统计分析 | ⏳ 规划中 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | P2 | +| ST - 统计分析工具 | ⏳ 规划中 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | P2 | +| RVW - 稿件审查系统 | ⚡ 独立系统 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | P1 | +| **ADMIN - 运营管理端** | ⭐ v2.0新增 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | P1 | + +**价值:** +- ✅ 覆盖科研全流程 +- ✅ 3个模块可独立销售(RVW、ASL、DC) +- ✅ 运营管理端是商业模式基础 + +--- + +### 3. 部署方案(4种模式) + +| 部署模式 | 技术方案 | 代码复用 | 目标客户 | 阶段 | +|---------|---------|---------|---------|------| +| 云端SaaS | Node.js + PostgreSQL | 100% | 个人、小机构 | P0(当前) | +| 独立产品包 | Docker完整打包 | 80% | 特定客户 | P1(阶段二) | +| Electron单机版 | 前端90%+后端80% | 85% | 个人医生 | P2(阶段二) | +| 私有化部署 | K8s/Docker | 100% | 医院、机构 | P1(阶段二) | + +**价值:** +- ✅ 覆盖全部市场需求 +- ✅ Electron代码复用率极高(85%+) +- ✅ 支持灵活的商业模式 + +--- + +### 4. 运营管理端(15个功能模块) + +**P0功能(必须):** +1. 用户管理 +2. Feature Flag管理 +3. LLM模型管理 +4. 系统配置管理 + +**P1功能(重要):** +5. 智能体提示词管理 +6. 监控与日志 +7. 数据统计与报表 +8. 成本分析与计费 + +**P2功能(有用):** +9-15. 租户管理、公告、文档、工单等 + +**价值:** +- ✅ 商业模式的技术保障 +- ✅ 成本控制的核心 +- ✅ 运营决策的基础 + +--- + +## 💰 技术债务决策 + +### 两个关键改造 + +| 改造 | 现在做 | 未来做 | ROI | 建议 | +|------|-------|-------|-----|------| +| **Schema隔离** | 1周 | 3-5周 | 300-500% | ⭐⭐⭐⭐⭐ 现在做 | +| **Monorepo** | 2-3天 | 7-11天 | 300-400% | ⭐⭐⭐⭐⭐ 现在做 | + +**核心结论:** +``` +现在投入:6天(1周) +未来节省:22-36天(1个月+) +投入产出比:300-500% + +建议:立即改造! +``` + +--- + +## 🎯 关键发现 + +### 发现1:LLM网关是商业模式的核心 + +``` +为什么? +- 5个模块依赖(71%复用率) +- 成本控制的关键(根据版本动态切换模型) +- Feature Flag的技术实现 + +优先级:P0(最高) +当前状态:❌ 未实现 +``` + +--- + +### 发现2:审稿系统极具独立价值 + +``` +为什么? +- 用户群完全不同(期刊编辑部 vs 临床医生) +- 业务逻辑完全独立 +- 可以完全独立部署和销售 + +商业价值:⭐⭐⭐⭐⭐ 极高 +目标客户:期刊编辑部、出版社 +商业模式:按期刊订阅 +``` + +--- + +### 发现3:现在是最佳改造时机 + +``` +为什么? +1. 数据量小(< 1万行)→ 迁移快 +2. 代码量适中 → 重构容易 +3. 模块少(只有2个已完成)→ 测试简单 +4. 即将开发多个新模块 → 新模块直接受益 + +6个月后: +- 数据量:100万行+ +- 代码量:大 +- 模块:7-8个 +- 改造成本:5倍+ + +结论:越早改造,成本越低 +``` + +--- + +### 发现4:Electron单机版代码复用率极高 + +``` +前端复用:90%+ +后端复用:80%+ +总复用率:85%+ + +只需修改: +- API调用层(HTTP → IPC) +- 数据库(PostgreSQL → SQLite) + +技术可行性:⭐⭐⭐⭐⭐ 非常高 +``` + +--- + +## 📋 下一步决策 + +### 您需要做出的2个关键决策 + +**决策1:是否现在做Schema隔离?** +- ✅ 建议:是(1周,节省未来3-5周) +- ⚠️ 如果不做:技术债累积,未来成本5倍 + +**决策2:是否现在转换Monorepo?** +- ✅ 建议:是(2-3天,节省未来7-11天) +- ⚠️ 如果不做:开发运营管理端时必须转换 + +--- + +### 3个实施方案 + +**方案A:立即开发ASL** +- ASL上线:最快(2周) +- 技术债:累积 +- 未来成本:1个月改造 +- 推荐度:⭐⭐⭐ + +**方案B:先改造,再开发** ⭐⭐⭐⭐⭐ **强烈推荐** +- ASL上线:延迟1周(3周) +- 技术债:还清 +- 未来成本:0 +- 推荐度:⭐⭐⭐⭐⭐ + +**方案C:只做Monorepo,延后Schema** +- ASL上线:延迟3天(2.5周) +- 技术债:部分还清 +- 未来成本:3-5周 +- 推荐度:⭐⭐⭐⭐ + +--- + +## 🎯 架构师的建议 + +### 如果我是您的技术架构师 + +**我会坚定地选择方案B(先改造,再开发)** + +**核心理由:** + +**1. 这是技术领导力的体现** +``` +短期思维: +- 追求快速上线 +- 忽视技术债 +- 未来付出代价 + +长期思维: +- 架构优先 +- 技术债归零 +- 长期收益巨大 +``` + +**2. 这是最佳投资决策** +``` +投入:1周 +回报:节省1个月 + 清晰架构 + 7个模块受益 + +任何金融产品都达不到这个回报率 +``` + +**3. 这是对团队负责** +``` +如果不做: +- 未来1个月重构 +- 团队士气受影响 +- 业务创新受阻 + +如果现在做: +- 轻装上阵 +- 专注业务 +- 高效开发 +``` + +**4. 这是对产品负责** +``` +7个模块的未来: +- 都需要清晰的架构 +- 都需要代码复用 +- 都需要独立部署能力 + +现在打好基础 = 为产品的未来负责 +``` + +--- + +## 📊 成果价值评估 + +### 技术价值:⭐⭐⭐⭐⭐ + +- ✅ 清晰的三层架构 +- ✅ 完整的8个模块规划 +- ✅ 详细的技术方案 +- ✅ 明确的实施路径 + +### 商业价值:⭐⭐⭐⭐⭐ + +- ✅ 支持模块独立销售 +- ✅ 支持4种部署模式 +- ✅ 支持灵活商业模式 +- ✅ 成本控制机制完善 + +### 决策价值:⭐⭐⭐⭐⭐ + +- ✅ 明确的优先级 +- ✅ 清晰的成本收益分析 +- ✅ 具体的实施建议 +- ✅ 3个可选方案 + +--- + +## 🚀 立即行动 + +### 如果您决定采纳方案B(夯实基础) + +**我可以立即帮您:** + +**明天开始(Schema隔离 Day 1):** +1. ✅ 设计完整的Schema结构 +2. ✅ 修改Prisma Schema(所有Model) +3. ✅ 编写数据迁移脚本 +4. ✅ 准备测试用例 +5. ✅ 提供详细实施步骤 + +**后天(Schema隔离 Day 2):** +1. ✅ 开发环境迁移指导 +2. ✅ 测试验证指导 +3. ✅ 问题排查和修复 + +**第3天(Schema隔离 Day 3):** +1. ✅ 生产环境迁移指导 +2. ✅ 数据完整性验证 +3. ✅ 性能测试 + +**第4-6天(Monorepo转换):** +1. ✅ 目录结构设计 +2. ✅ 配置文件编写 +3. ✅ 重构步骤清单 +4. ✅ 全程指导和答疑 + +**第7天+(ASL模块开发):** +1. ✅ 数据库设计 +2. ✅ API设计 +3. ✅ 前后端实现指导 + +--- + +## 📖 重要文档推荐 + +### 必读文档(优先级顺序) + +**1. 决策必读:** +👉 [99-下一步行动决策建议](./99-下一步行动决策建议.md) +- 3个方案对比 +- 明确的建议 +- 详细的实施计划 + +**2. 快速了解:** +👉 [00-今日架构设计总结](./00-今日架构设计总结.md) +- 今天完成了什么 +- 关键决策 +- 下一步建议 + +**3. 一图看懂:** +👉 [08-架构设计全景图](./08-架构设计全景图.md) +- 完整架构图 +- 四种部署模式 +- 代码组织结构 + +**4. 深入理解:** +👉 [01-系统架构分层设计](./01-系统架构分层设计.md) +- 三层架构详细设计 +- 8个模块详述 +- 依赖关系分析 + +**5. 改造决策:** +👉 [05-Schema隔离方案与成本分析](./05-Schema隔离方案与成本分析.md) +👉 [07-Monorepo架构评估](./07-Monorepo架构评估.md) + +--- + +## 🎯 关键数字 + +### 投入产出比 + +``` +现在投入:6天(Schema隔离 + Monorepo) +未来节省:22-36天 +投入产出比:300-500% + +这是任何投资都无法企及的回报率! +``` + +### 代码复用率 + +``` +Electron单机版: +- 前端复用:90%+ +- 后端复用:80%+ +- 总复用率:85%+ + +技术可行性:⭐⭐⭐⭐⭐ 非常高 +``` + +### 模块复用率 + +``` +LLM网关:71%(5/7模块依赖) +文档处理:86%(6/7模块依赖) +RAG引擎:43%(3/7模块依赖) + +通用能力的价值巨大! +``` + +--- + +## 💡 关键建议 + +### 给决策者的3条建议 + +**1. 相信长期价值** +``` +技术债像高利贷: +- 本金小,利息高 +- 时间越久,利息越高 +- 早还早轻松 + +投入1周,节省1个月 +这是最划算的投资 +``` + +**2. 把握最佳时机** +``` +现在: +- 数据量小,改造快 +- 代码量适中,重构易 +- 团队小,沟通低 + +未来: +- 数据量大,改造慢 +- 代码量大,重构难 +- 团队大,沟通高 + +现在是成本最低的时间窗口 +``` + +**3. 为未来打基础** +``` +即将开发: +- ASL、DC、SSA、ST、RVW、ADMIN + +如果现在改造: +- 这些模块都享受清晰架构 +- 开发效率高 +- 代码质量好 + +如果不改造: +- 每个模块都继承技术债 +- 技术债叠加 +- 最终爆发 +``` + +--- + +## ✅ 验收标准 + +### 架构设计完成标志 + +- [x] ✅ 三层架构设计完成 +- [x] ✅ 8个业务模块规划完成 +- [x] ✅ 5个通用能力定义完成 +- [x] ✅ 4种部署方案设计完成 +- [x] ✅ 运营管理端设计完成 +- [x] ✅ Schema隔离方案完成 +- [x] ✅ Monorepo评估完成 +- [x] ✅ 模块独立部署方案完成 +- [x] ✅ Electron单机版方案完成 +- [x] ✅ 文档体系重构方案完成 +- [x] ✅ 下一步行动建议完成 + +**状态:100% 完成!** ✅ + +--- + +## 🎉 总结 + +### 今日成就 + +**架构设计:** ⭐⭐⭐⭐⭐ 优秀 +- 完整、清晰、可实施 +- 考虑了未来6-12个月的需求 +- 支持模块化销售和多种部署 + +**决策支持:** ⭐⭐⭐⭐⭐ 优秀 +- 明确的成本收益分析 +- 清晰的实施路径 +- 3个可选方案 + +**文档产出:** ⭐⭐⭐⭐⭐ 优秀 +- 11个核心文档 +- 6000+行详细设计 +- 结构清晰,易于阅读 + +--- + +### 下一步 + +**等待您的决策:** +1. 方案A:立即开发ASL +2. 方案B:先改造,再开发 ⭐⭐⭐⭐⭐ **强烈推荐** +3. 方案C:只做Monorepo,延后Schema + +**一旦决策,我会:** +- ✅ 提供详细的实施步骤 +- ✅ 全程指导和协助 +- ✅ 确保顺利完成 + +--- + +**恭喜!今天完成了一个重大的架构设计里程碑!** 🎉 + +--- + +**报告完成日期:** 2025-11-06 +**报告人:** 技术架构师 + + + + + + + + + + + + + + + diff --git a/docs/00-系统总体设计/前后端模块化架构设计-V2.md b/docs/00-系统总体设计/前后端模块化架构设计-V2.md new file mode 100644 index 00000000..1f4a8c03 --- /dev/null +++ b/docs/00-系统总体设计/前后端模块化架构设计-V2.md @@ -0,0 +1,1342 @@ +# 前后端模块化架构设计 V2.0 + +> **版本:** V2.2 +> **创建日期:** 2025-11-12 +> **实施阶段:** Week 2(2025-11-13开始) +> **维护者:** 开发团队 +> **状态:** ✅ Week 2 Day 6-9 全部完成(前端架构+模块注册+后端分层)⭐⭐⭐ + +--- + +## 📋 文档说明 + +本文档是**AI临床研究平台的模块化架构设计总纲**,定义了: + +1. **前端架构**:全新的 frontend-v2 模块化架构 +2. **后端架构**:基于Schema隔离的分层架构 +3. **前后端对应关系**:模块化的组织方式 +4. **开发规范**:统一的代码组织和命名规范 +5. **实施路线**:渐进式的架构改造计划 + +**适用对象:** +- 新加入的开发人员(快速了解系统架构) +- AI助手(理解代码组织结构) +- 技术决策者(架构演进参考) + +--- + +## 🎯 架构演进历史 + +### V1.0 阶段(2025-10之前) +- ❌ 单体前端应用(所有功能耦合) +- ❌ 后端代码平铺(无模块划分) +- ❌ 数据库表全在public schema + +### V1.5 阶段(2025-11-12完成) +- ✅ **数据库Schema隔离**(10个Schema) +- ✅ Prisma多Schema配置 +- ✅ 数据100%迁移成功 +- ⚠️ 前后端代码仍需模块化 + +### V2.0 阶段(2025-11-13~14完成)⭐ +- ✅ **前端模块化架构**(frontend-v2)- Day 6-7完成 +- ✅ **模块注册机制**(权限控制+错误边界+路由守卫)- Day 7完成 +- ✅ **后端增量演进架构**(legacy/common/modules)- Day 8-9完成 +- ✅ 前后端模块对应 +- ✅ 支持模块独立开发和部署 +- ✅ **所有功能测试通过**(包括批处理修复)⭐⭐⭐ + +--- + +## 📸 当前架构真实状态(2025-11-14) + +> **⭐ 重要提示:本章节描述当前实际运行的架构状态** +> **适用对象:新开发人员、新AI助手、快速上手** +> **更新频率:架构变更时立即更新** + +--- + +### 🎯 架构概览 + +``` +【当前状态】增量演进架构(新旧并存) + +Frontend-v2 (新) Backend (混合) Database (隔离) + ↓ ↓ ↓ +顶部导航 + 6模块 legacy/ + common/ 10个独立 Schema + 占位 + modules/asl (3详细 + 7空) +``` + +**核心策略:** +- ✅ 前端:全新架构(frontend-v2) +- ✅ 后端:新旧并存(legacy保持稳定,新模块标准化) +- ✅ 数据库:Schema隔离完成 + +--- + +### 📁 前端真实架构(Frontend-v2) + +#### **目录结构(实际存在)** + +```bash +frontend-v2/ +├── src/ +│ ├── framework/ # 🏗️ 框架层(已实现) +│ │ ├── layout/ +│ │ │ ├── MainLayout.tsx # ✅ 主布局 +│ │ │ ├── TopNavigation.tsx # ✅ 顶部导航 +│ │ │ └── UserMenu.tsx # ✅ 用户菜单 +│ │ │ +│ │ ├── modules/ +│ │ │ ├── moduleRegistry.ts # ✅ 模块注册中心 +│ │ │ ├── ErrorBoundary.tsx # ✅ 错误边界 +│ │ │ └── ModuleErrorFallback.tsx # ✅ 错误回退UI +│ │ │ +│ │ ├── router/ +│ │ │ ├── RouteGuard.tsx # ✅ 路由守卫 +│ │ │ └── PermissionDenied.tsx # ✅ 权限拒绝页 +│ │ │ +│ │ └── permission/ +│ │ ├── PermissionContext.tsx # ✅ 权限上下文 +│ │ ├── usePermission.ts # ✅ 权限Hook +│ │ └── types.ts # ✅ 权限类型 +│ │ +│ ├── modules/ # 📦 业务模块(占位) +│ │ ├── asl/ # 🚧 Week 3开发 +│ │ │ └── index.tsx # ✅ 占位页面 +│ │ ├── aia/ # 📋 占位 +│ │ ├── pkb/ # 📋 占位 +│ │ ├── dc/ # 📋 占位 +│ │ ├── ssa/ # 📋 占位 +│ │ └── st/ # 📋 占位 +│ │ +│ ├── pages/ +│ │ └── Home.tsx # ✅ 首页 +│ │ +│ └── App.tsx # ✅ 应用入口 +│ +├── package.json # ✅ React 19 +└── vite.config.ts # ✅ Vite配置 +``` + +**当前运行状态:** +- ✅ 访问地址:http://localhost:3000 +- ✅ 顶部导航显示6个模块 +- ✅ 权限控制工作正常(mock premium用户) +- ✅ 路由守卫生效 +- ✅ 错误边界正常捕获 + +--- + +### 🗄️ 后端真实架构(Backend) + +#### **目录结构(实际存在)⭐ 关键** + +```bash +backend/ +├── src/ +│ ├── legacy/ # 🔸 现有业务代码(稳定运行) +│ │ ├── routes/ # 7个路由文件 +│ │ │ ├── agents.ts # AIA: 智能体路由 +│ │ │ ├── conversations.ts # AIA: 对话路由 +│ │ │ ├── chatRoutes.ts # AIA: 通用对话路由 +│ │ │ ├── projects.ts # AIA: 项目路由 +│ │ │ ├── knowledgeBases.ts # PKB: 知识库路由 +│ │ │ ├── batchRoutes.ts # PKB: 批处理路由 +│ │ │ └── reviewRoutes.ts # RVW: 稿件审查路由 +│ │ │ +│ │ ├── controllers/ # 8个控制器 +│ │ │ ├── agentController.ts +│ │ │ ├── conversationController.ts +│ │ │ ├── chatController.ts +│ │ │ ├── projectController.ts +│ │ │ ├── knowledgeBaseController.ts +│ │ │ ├── documentController.ts +│ │ │ ├── batchController.ts +│ │ │ └── reviewController.ts +│ │ │ +│ │ ├── services/ # 8个服务 +│ │ │ ├── agentService.ts +│ │ │ ├── conversationService.ts +│ │ │ ├── projectService.ts +│ │ │ ├── knowledgeBaseService.ts +│ │ │ ├── documentService.ts +│ │ │ ├── batchService.ts +│ │ │ ├── reviewService.ts +│ │ │ └── tokenService.ts +│ │ │ +│ │ └── templates/ +│ │ └── clinicalResearch.ts # 批处理模板 +│ │ +│ ├── common/ # 🔧 通用能力层(共享) +│ │ ├── llm/ +│ │ │ └── adapters/ # LLM适配器 +│ │ │ ├── DeepSeekAdapter.ts # ✅ DeepSeek-V3 +│ │ │ ├── QwenAdapter.ts # ✅ Qwen3-72B + Qwen-Long +│ │ │ ├── LLMFactory.ts # ✅ 工厂类 +│ │ │ └── types.ts # ✅ LLM类型定义 +│ │ │ +│ │ ├── rag/ # RAG能力 +│ │ │ ├── DifyClient.ts # ✅ Dify客户端 +│ │ │ └── types.ts # ✅ RAG类型 +│ │ │ +│ │ ├── document/ # 文档处理 +│ │ │ └── ExtractionClient.ts # ✅ 文档提取客户端 +│ │ │ +│ │ ├── middleware/ +│ │ │ └── validateProject.ts # ✅ 项目验证中间件 +│ │ │ +│ │ └── utils/ +│ │ └── jsonParser.ts # ✅ JSON解析工具 +│ │ +│ ├── modules/ # 🌟 新架构模块(标准化) +│ │ └── asl/ # ⭐ AI智能文献(占位,Week 3开发) +│ │ └── (空目录,等待开发) +│ │ +│ ├── config/ # ⚙️ 配置层 +│ │ ├── database.ts # ✅ 数据库配置 +│ │ └── env.ts # ✅ 环境变量 +│ │ +│ ├── scripts/ # 🛠️ 工具脚本 +│ │ ├── create-mock-user.ts +│ │ └── test-dify-client.ts +│ │ +│ └── index.ts # ✅ 主入口(统一注册) +│ +├── config/ # 外部配置文件 +│ └── agents.yaml # ✅ 智能体配置(348行) +│ +├── prompts/ # Prompt模板 +│ ├── topic_evaluation_system.txt +│ ├── review_editorial_system.txt +│ └── review_methodology_system.txt +│ +├── prisma/ +│ ├── schema.prisma # ✅ 10个Schema定义 +│ └── migrations/ # ✅ 4个迁移文件 +│ +├── package.json # ✅ Fastify + Prisma +└── .env # ✅ 环境变量 +``` + +**当前运行状态:** +- ✅ 访问地址:http://localhost:3001 +- ✅ 健康检查:http://localhost:3001/health +- ✅ Legacy模块(AIA/PKB/RVW)正常运行 +- ✅ Common层通用能力可用 +- ✅ Modules/asl 占位就绪 + +**主入口(index.ts)实际代码:** +```typescript +// ============================================ +// 【旧架构】Legacy 模块 - 保持不变 +// ============================================ +import { projectRoutes } from './legacy/routes/projects.js'; +import { agentRoutes } from './legacy/routes/agents.js'; +import { conversationRoutes } from './legacy/routes/conversations.js'; +import knowledgeBaseRoutes from './legacy/routes/knowledgeBases.js'; +import { chatRoutes } from './legacy/routes/chatRoutes.js'; +import { batchRoutes } from './legacy/routes/batchRoutes.js'; +import reviewRoutes from './legacy/routes/reviewRoutes.js'; + +// 注册 Legacy 模块路由 +await fastify.register(projectRoutes, { prefix: '/api/v1/aia' }); +await fastify.register(agentRoutes, { prefix: '/api/v1/aia' }); +await fastify.register(conversationRoutes, { prefix: '/api/v1/aia' }); +await fastify.register(chatRoutes, { prefix: '/api/v1/aia' }); +await fastify.register(knowledgeBaseRoutes, { prefix: '/api/v1/pkb' }); +await fastify.register(batchRoutes, { prefix: '/api/v1/pkb' }); +await fastify.register(reviewRoutes, { prefix: '/api/v1/rvw' }); +``` + +--- + +### 🗃️ 数据库真实架构(PostgreSQL 15) + +#### **Schema 结构(实际存在)** + +```sql +-- ==================== 平台层 Schema ==================== +platform_schema (平台基础表) + └── users # ✅ 用户表(已实现) + +-- ==================== 详细 Schema(数据已迁移)==================== +aia_schema (AI智能问答) + ├── projects # ✅ 项目表 + ├── agents # ✅ 智能体配置表 + ├── conversations # ✅ 对话表 + └── messages # ✅ 消息表 + +pkb_schema (个人知识库) + ├── knowledge_bases # ✅ 知识库表 + ├── documents # ✅ 文档表 + ├── batch_tasks # ✅ 批处理任务表 + ├── batch_results # ✅ 批处理结果表 + └── task_templates # ✅ 任务模板表 + +rvw_schema (稿件审查) + ├── review_tasks # ✅ 审查任务表 + └── (在public schema中) # ⚠️ 历史原因 + +-- ==================== 占位 Schema(数据结构未定义)==================== +asl_schema (AI智能文献) # 🚧 Week 3开发 + └── (空,等待定义) + +common_schema (通用工具) # 📋 占位 +dc_schema (数据采集) # 📋 占位 +ssa_schema (统计分析) # 📋 占位 +st_schema (研究工具) # 📋 占位 +admin_schema (运营管理) # 📋 占位 +``` + +**Prisma Schema 配置:** +```prisma +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + schemas = [ + "platform_schema", # ✅ 平台 + "aia_schema", # ✅ 智能问答 + "pkb_schema", # ✅ 知识库 + "asl_schema", # 🚧 智能文献(Week 3) + "common_schema", # 📋 占位 + "dc_schema", # 📋 占位 + "rvw_schema", # 📋 占位 + "admin_schema", # 📋 占位 + "ssa_schema", # 📋 占位 + "st_schema", # 📋 占位 + "public" # ✅ 历史遗留 + ] +} +``` + +--- + +### 🔌 API 真实路由(当前可用) + +#### **AIA 模块(AI智能问答)** +``` +基础路径:/api/v1/aia + +项目管理: + ✅ GET /projects # 获取项目列表 + ✅ POST /projects # 创建项目 + ✅ GET /projects/:id # 获取项目详情 + ✅ PUT /projects/:id # 更新项目 + ✅ DELETE /projects/:id # 删除项目 + +智能体管理: + ✅ GET /agents # 获取智能体列表 + ✅ GET /agents/enabled # 获取启用的智能体 + ✅ GET /agents/:id # 获取智能体详情 + +对话管理: + ✅ POST /conversations # 创建对话 + ✅ GET /conversations # 获取对话列表 + ✅ GET /conversations/:id # 获取对话详情 + ✅ POST /conversations/:id/messages/stream # 流式发送消息 + ✅ POST /conversations/:id/messages # 非流式发送消息 + ✅ DELETE /conversations/:id # 删除对话 + +通用对话: + ✅ POST /chat/stream # 通用流式对话 + ✅ POST /chat # 通用非流式对话 +``` + +#### **PKB 模块(个人知识库)** +``` +基础路径:/api/v1/pkb + +知识库管理: + ✅ GET /knowledge-bases # 获取知识库列表 + ✅ POST /knowledge-bases # 创建知识库 + ✅ GET /knowledge-bases/:id # 获取知识库详情 + ✅ PUT /knowledge-bases/:id # 更新知识库 + ✅ DELETE /knowledge-bases/:id # 删除知识库 + +文档管理: + ✅ POST /knowledge-bases/:id/documents # 上传文档 + ✅ GET /knowledge-bases/:id/documents # 获取文档列表 + ✅ DELETE /knowledge-bases/:kbId/documents/:docId # 删除文档 + +批处理: + ✅ POST /batch/execute # 执行批处理任务 + ✅ GET /batch/tasks/:taskId # 获取任务状态 + ✅ GET /batch/tasks/:taskId/results # 获取任务结果 + ✅ POST /batch/tasks/:taskId/retry-failed # 重试失败项 +``` + +#### **RVW 模块(稿件审查)** +``` +基础路径:/api/v1/rvw + +稿件审查: + ✅ POST /review/upload # 上传稿件并开始审查 + ✅ GET /review/tasks/:taskId # 查询任务状态 + ✅ GET /review/tasks/:taskId/report # 获取完整报告 + ✅ GET /review/tasks # 获取任务列表 + ✅ DELETE /review/tasks/:taskId # 删除任务 +``` + +#### **系统端点** +``` +✅ GET /health # 健康检查 +✅ GET /api/v1 # API信息 +``` + +--- + +### 📊 技术栈真实配置 + +#### **前端(Frontend-v2)** +```json +{ + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^6.x", + "typescript": "^5.x", + "vite": "^6.x", + "antd": "^5.x", + "tailwindcss": "^3.x" +} +``` + +#### **后端(Backend)** +```json +{ + "fastify": "^4.x", + "typescript": "^5.x", + "prisma": "^6.17.0", + "@prisma/client": "^6.17.0", + "tsx": "^4.x", + "axios": "^1.x", + "js-yaml": "^4.x", + "p-queue": "^8.x" +} +``` + +#### **数据库** +``` +PostgreSQL: 15.x +Schema数量: 10个(3详细 + 7占位) +迁移文件: 4个 +``` + +#### **LLM 集成(CloseAI)** +``` +✅ DeepSeek-V3 (主力,性价比) +✅ Qwen3-72B (备用,中文理解) +✅ Qwen-Long (超长上下文1M) +✅ GPT-4o (可选,高质量) +``` + +--- + +### 🔄 前后端模块对应关系(当前) + +| 前端模块 | 后端位置 | 数据库Schema | API路由 | 状态 | +|---------|---------|-------------|---------|------| +| frontend-v2/modules/asl/ | backend/modules/asl/ | asl_schema | /api/v1/asl/* | 🚧 Week 3开发 | +| frontend-v2/modules/aia/ | backend/legacy/routes/agents.ts等 | aia_schema | /api/v1/aia/* | ✅ 运行中 | +| frontend-v2/modules/pkb/ | backend/legacy/routes/knowledgeBases.ts等 | pkb_schema | /api/v1/pkb/* | ✅ 运行中 | +| frontend-v2/modules/rvw/ | backend/legacy/routes/reviewRoutes.ts | public + rvw_schema | /api/v1/rvw/* | ✅ 运行中 | +| frontend-v2/modules/dc/ | (未实现) | dc_schema | /api/v1/dc/* | 📋 占位 | +| frontend-v2/modules/ssa/ | (未实现) | ssa_schema | /api/v1/ssa/* | 📋 占位 | +| frontend-v2/modules/st/ | (未实现) | st_schema | /api/v1/st/* | 📋 占位 | + +--- + +### 📋 功能测试清单(已验证) + +| 功能 | 测试项 | 状态 | 备注 | +|------|--------|------|------| +| 智能问答 | 创建项目 | ✅ | | +| 智能问答 | 对话模式 | ✅ | 流式+非流式 | +| 智能问答 | 知识库模式 | ✅ | RAG检索 | +| 智能问答 | 批处理模式 | ✅ | 修复rawOutput问题后正常 | +| 知识库 | 创建知识库 | ✅ | | +| 知识库 | 上传文档 | ✅ | PDF/Word/TXT/MD | +| 知识库 | 文档管理 | ✅ | 列表/删除 | +| 稿件审查 | 上传稿件 | ✅ | | +| 稿件审查 | 审查报告 | ✅ | | +| 前端 | 顶部导航 | ✅ | 6个模块 | +| 前端 | 权限控制 | ✅ | mock premium | +| 前端 | 路由守卫 | ✅ | | +| 前端 | 错误边界 | ✅ | | + +--- + +### 🎯 新模块开发指南(以ASL为例) + +#### **开发流程(Week 3):** + +``` +第1步:数据库设计 + ├─ 设计 asl_schema 表结构 + ├─ 在 Prisma schema 中定义模型 + └─ 运行迁移创建表 + +第2步:后端开发 + ├─ 在 backend/src/modules/asl/ 下开发 + ├─ 创建 routes/(路由) + ├─ 创建 controllers/(控制器) + ├─ 创建 services/(服务) + └─ 注册路由到 index.ts + +第3步:前端开发 + ├─ 在 frontend-v2/src/modules/asl/ 下开发 + ├─ 创建 pages/(页面) + ├─ 创建 components/(组件) + ├─ 创建 api/(API调用) + └─ 更新 index.tsx(模块注册) + +第4步:联调测试 + ├─ 前后端联调 + └─ 功能验收 +``` + +--- + +### 📚 相关文档索引 + +**快速上手:** +- ⭐ **本文档** - 架构总纲 + 当前状态 +- ⭐ `docs/09-架构实施/后端架构增量演进方案.md` - 后端详细说明(450行) + +**任务记录:** +- `docs/08-项目管理/下一阶段行动计划-V2.2-完整版.md` - 项目计划 +- `docs/09-架构实施/前端模块注册机制实施报告.md` - 前端任务17 +- `docs/08-项目管理/2025-11-14-任务19完成总结.md` - 后端任务19 + +**技术规范:** +- `docs/09-架构实施/编码规范-UTF8最佳实践.md` - 编码规范 +- `.editorconfig` - 编辑器配置 +- `.gitattributes` - Git配置 + +--- + +**本章节更新日期:** 2025-11-14 +**下次更新时机:** ASL模块开发完成后 + +--- + +## 🏗️ 整体架构设计 + +### 系统架构图 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ AI临床研究平台 │ +└─────────────────────────────────────────────────────────────────┘ + │ + ┌─────────────────────┴─────────────────────┐ + │ │ + ┌────▼─────┐ ┌─────▼────┐ + │ 前端层 │ │ 后端层 │ + │frontend-v2│◄────────── HTTP API ────────►│ backend │ + └──────────┘ └──────────┘ + │ │ + │ 模块化 │ 模块化 + Schema隔离 + │ │ + ┌────┴────────────────────────┐ ┌───────┴──────────────────┐ + │ │ │ │ + │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ ┌─────────────────────┐ │ + │ │ ASL │ │ AIA │ │ PKB │ │ │ │ PostgreSQL │ │ + │ │模块 │ │模块 │ │模块 │ │ │ │ ┌─────────────────┐│ │ + │ └─────┘ └─────┘ └─────┘ │ │ │ │ platform_schema ││ │ + │ ┌─────┐ ┌─────┐ │ │ │ │ aia_schema ││ │ + │ │ RVW │ │ DC │ ... │ │ │ │ pkb_schema ││ │ + │ │模块 │ │模块 │ │ │ │ │ asl_schema ││ │ + │ └─────┘ └─────┘ │ │ │ │ ... ││ │ + │ │ │ │ └─────────────────┘│ │ + └─────────────────────────────┘ │ └─────────────────────┘ │ + └──────────────────────────┘ +``` + +--- + +## 📁 前端架构设计(frontend-v2) + +### 设计原则 + +1. **彻底的模块化** - 每个业务模块完全独立 +2. **框架与业务分离** - 框架层提供基础能力,业务模块专注功能 +3. **渐进式开发** - 新架构与旧代码并存,逐步替换 +4. **独立部署支持** - 模块可独立打包和部署 + +### 目录结构 + +``` +frontend-v2/ +├── src/ +│ ├── framework/ # 🏗️ 框架层(平台级基础设施) +│ │ │ +│ │ ├── layout/ # 布局系统 +│ │ │ ├── MainLayout.tsx # 主布局(顶部导航+内容区) +│ │ │ ├── TopNavigation.tsx # 顶部导航栏 +│ │ │ ├── UserMenu.tsx # 用户菜单 +│ │ │ └── ModuleContainer.tsx # 模块容器 +│ │ │ +│ │ ├── modules/ # 模块管理系统 +│ │ │ ├── moduleRegistry.ts # 模块注册中心 +│ │ │ ├── ModuleLoader.tsx # 动态模块加载器 +│ │ │ ├── types.ts # 模块接口定义 +│ │ │ └── config.ts # 模块配置 +│ │ │ +│ │ ├── router/ # 路由系统 +│ │ │ ├── AppRouter.tsx # 应用路由配置 +│ │ │ ├── RouteGuard.tsx # 路由守卫 +│ │ │ └── routes.ts # 路由定义 +│ │ │ +│ │ ├── permission/ # 权限控制(预留) +│ │ │ ├── PermissionProvider.tsx # 权限上下文 +│ │ │ ├── usePermission.ts # 权限Hook +│ │ │ └── config.ts # 权限配置 +│ │ │ +│ │ └── config/ # 全局配置 +│ │ ├── env.ts # 环境变量 +│ │ ├── api.ts # API配置 +│ │ └── theme.ts # 主题配置 +│ │ +│ ├── modules/ # 📦 业务模块(完全独立) +│ │ │ +│ │ ├── asl/ # ⭐ AI智能文献模块(优先开发) +│ │ │ ├── index.tsx # 模块入口(导出ModuleDefinition) +│ │ │ ├── routes.tsx # 模块路由配置 +│ │ │ │ +│ │ │ ├── layouts/ # 模块布局 +│ │ │ │ └── ASLLayout.tsx # ASL左侧导航布局 +│ │ │ │ +│ │ │ ├── pages/ # 模块页面 +│ │ │ │ ├── ProjectList.tsx # 项目列表 +│ │ │ │ ├── ProjectCreate.tsx # 创建项目 +│ │ │ │ ├── TitleScreening/ # 标题摘要初筛 +│ │ │ │ │ ├── index.tsx +│ │ │ │ │ ├── ScreeningWorkbench.tsx +│ │ │ │ │ └── ResultsView.tsx +│ │ │ │ ├── FullTextScreening/ # 全文复筛 +│ │ │ │ │ └── ... +│ │ │ │ ├── DataExtraction/ # 数据提取 +│ │ │ │ │ └── ... +│ │ │ │ └── Analysis/ # 综合分析 +│ │ │ │ └── ... +│ │ │ │ +│ │ │ ├── components/ # 模块组件(仅本模块使用) +│ │ │ │ ├── LiteratureCard.tsx +│ │ │ │ ├── ScreeningPanel.tsx +│ │ │ │ ├── LLMSelector.tsx +│ │ │ │ └── ... +│ │ │ │ +│ │ │ ├── api/ # 模块API(对接后端) +│ │ │ │ ├── projectApi.ts +│ │ │ │ ├── screeningApi.ts +│ │ │ │ ├── extractionApi.ts +│ │ │ │ └── index.ts +│ │ │ │ +│ │ │ ├── hooks/ # 模块Hooks +│ │ │ │ ├── useProject.ts +│ │ │ │ ├── useScreening.ts +│ │ │ │ └── useLLMScreening.ts +│ │ │ │ +│ │ │ ├── store/ # 模块状态管理 +│ │ │ │ ├── projectStore.ts +│ │ │ │ ├── screeningStore.ts +│ │ │ │ └── index.ts +│ │ │ │ +│ │ │ ├── types/ # 模块类型定义 +│ │ │ │ ├── project.ts +│ │ │ │ ├── literature.ts +│ │ │ │ └── index.ts +│ │ │ │ +│ │ │ └── utils/ # 模块工具函数 +│ │ │ ├── fileParser.ts +│ │ │ └── exportHelper.ts +│ │ │ +│ │ ├── aia/ # 📋 AI智能问答模块(后续重写) +│ │ │ ├── index.tsx +│ │ │ └── Placeholder.tsx # 临时占位页面 +│ │ │ +│ │ ├── pkb/ # 📋 个人知识库模块(后续重写) +│ │ │ ├── index.tsx +│ │ │ └── Placeholder.tsx +│ │ │ +│ │ ├── rvw/ # 📋 审稿系统模块(后续重写) +│ │ │ ├── index.tsx +│ │ │ └── Placeholder.tsx +│ │ │ +│ │ └── dc/ # 📋 数据清洗模块(占位) +│ │ ├── index.tsx +│ │ └── Placeholder.tsx +│ │ +│ ├── shared/ # 🔧 共享资源(跨模块使用) +│ │ ├── components/ # 通用UI组件 +│ │ │ ├── Button/ +│ │ │ ├── Table/ +│ │ │ ├── Modal/ +│ │ │ └── ... +│ │ │ +│ │ ├── hooks/ # 通用Hooks +│ │ │ ├── useDebounce.ts +│ │ │ ├── useAsync.ts +│ │ │ └── ... +│ │ │ +│ │ ├── utils/ # 工具函数 +│ │ │ ├── date.ts +│ │ │ ├── format.ts +│ │ │ ├── validation.ts +│ │ │ └── ... +│ │ │ +│ │ └── api/ # API客户端 +│ │ ├── client.ts # Axios实例 +│ │ ├── request.ts # 请求拦截器 +│ │ └── types.ts # API类型 +│ │ +│ ├── App.tsx # 应用根组件 +│ ├── main.tsx # 应用入口 +│ └── vite-env.d.ts +│ +├── public/ # 静态资源 +├── package.json +├── vite.config.ts # Vite配置 +├── tsconfig.json # TypeScript配置 +├── tailwind.config.js # Tailwind配置 +└── README.md +``` + +### 模块定义接口 + +```typescript +// framework/modules/types.ts + +/** + * 模块定义接口 + * 每个业务模块必须实现这个接口 + */ +export interface ModuleDefinition { + /** 模块唯一标识 */ + id: string + + /** 模块名称(显示在导航栏) */ + name: string + + /** 模块路由前缀 */ + path: string + + /** 模块图标(可选) */ + icon?: ReactNode + + /** 模块入口组件(懒加载) */ + component: LazyExoticComponent> + + /** 模块路由配置 */ + routes?: RouteObject[] + + /** 模块是否有左侧导航 */ + hasSideNav?: boolean + + /** 左侧导航配置(如果有) */ + sideNavConfig?: SideNavItem[] + + /** 权限要求(可选) */ + requiredVersion?: UserVersion + + /** 是否为占位模块 */ + placeholder?: boolean + + /** 是否支持独立部署 */ + standalone?: boolean +} + +/** + * 左侧导航项 + */ +export interface SideNavItem { + id: string + label: string + path: string + icon?: ReactNode + children?: SideNavItem[] +} +``` + +### 模块注册示例 + +```typescript +// modules/asl/index.tsx + +import { lazy } from 'react' +import { ModuleDefinition } from '@/framework/modules/types' +import routes from './routes' +import { sideNavConfig } from './config' + +const ASLModule: ModuleDefinition = { + id: 'asl', + name: 'AI智能文献', + path: '/literature', + icon: , + component: lazy(() => import('./layouts/ASLLayout')), + routes, + hasSideNav: true, + sideNavConfig, + requiredVersion: 'advanced', +} + +export default ASLModule +``` + +### 模块路由示例 + +```typescript +// modules/asl/routes.tsx + +import { lazy } from 'react' +import { RouteObject } from 'react-router-dom' + +const routes: RouteObject[] = [ + { + path: '', + element: lazy(() => import('./pages/ProjectList')), + }, + { + path: 'project/:projectId', + children: [ + { + path: 'title-screening', + element: lazy(() => import('./pages/TitleScreening')), + }, + { + path: 'fulltext-screening', + element: lazy(() => import('./pages/FullTextScreening')), + }, + { + path: 'extraction', + element: lazy(() => import('./pages/DataExtraction')), + }, + { + path: 'analysis', + element: lazy(() => import('./pages/Analysis')), + }, + ], + }, +] + +export default routes +``` + +--- + +## 📁 后端架构设计(backend) + +### 设计原则 + +1. **Schema隔离** - 数据库层面的模块隔离(已完成) +2. **代码分层** - platform/common/modules三层架构 +3. **模块独立** - 每个模块的API和逻辑独立 +4. **保持兼容** - 轻度重构,不破坏现有功能 + +### 目录结构 + +``` +backend/ +├── src/ +│ ├── platform/ # 🏛️ 平台层(基础设施) +│ │ ├── auth/ # 认证授权 +│ │ │ ├── authController.ts +│ │ │ ├── authService.ts +│ │ │ ├── authMiddleware.ts +│ │ │ └── routes.ts +│ │ │ +│ │ └── users/ # 用户管理 +│ │ ├── userController.ts +│ │ ├── userService.ts +│ │ └── routes.ts +│ │ +│ ├── common/ # 🔧 通用层(共享能力) +│ │ ├── middleware/ # 中间件 +│ │ │ ├── errorHandler.ts +│ │ │ ├── logger.ts +│ │ │ ├── validation.ts +│ │ │ └── cors.ts +│ │ │ +│ │ ├── utils/ # 工具函数 +│ │ │ ├── date.ts +│ │ │ ├── encryption.ts +│ │ │ └── format.ts +│ │ │ +│ │ ├── errors/ # 错误处理 +│ │ │ ├── AppError.ts +│ │ │ ├── errorTypes.ts +│ │ │ └── errorHandler.ts +│ │ │ +│ │ └── types/ # 通用类型 +│ │ ├── request.ts +│ │ ├── response.ts +│ │ └── common.ts +│ │ +│ ├── modules/ # 📦 业务模块 +│ │ │ +│ │ ├── aia/ # AI智能问答模块 +│ │ │ ├── controllers/ # 控制器 +│ │ │ │ ├── projectController.ts +│ │ │ │ ├── agentController.ts +│ │ │ │ ├── conversationController.ts +│ │ │ │ └── chatController.ts +│ │ │ │ +│ │ │ ├── services/ # 业务逻辑 +│ │ │ │ ├── projectService.ts +│ │ │ │ ├── conversationService.ts +│ │ │ │ └── llmService.ts +│ │ │ │ +│ │ │ ├── routes/ # 路由配置 +│ │ │ │ └── index.ts +│ │ │ │ +│ │ │ ├── types/ # 模块类型 +│ │ │ │ ├── project.ts +│ │ │ │ └── conversation.ts +│ │ │ │ +│ │ │ └── utils/ # 模块工具 +│ │ │ └── prompt.ts +│ │ │ +│ │ ├── pkb/ # 个人知识库模块 +│ │ │ ├── controllers/ +│ │ │ │ ├── knowledgeBaseController.ts +│ │ │ │ ├── documentController.ts +│ │ │ │ └── batchController.ts +│ │ │ │ +│ │ │ ├── services/ +│ │ │ │ ├── kbService.ts +│ │ │ │ ├── documentService.ts +│ │ │ │ ├── batchService.ts +│ │ │ │ └── difyService.ts +│ │ │ │ +│ │ │ ├── routes/ +│ │ │ │ └── index.ts +│ │ │ │ +│ │ │ └── types/ +│ │ │ ├── knowledgeBase.ts +│ │ │ └── document.ts +│ │ │ +│ │ ├── rvw/ # 审稿系统模块 +│ │ │ ├── controllers/ +│ │ │ │ └── reviewController.ts +│ │ │ │ +│ │ │ ├── services/ +│ │ │ │ ├── reviewService.ts +│ │ │ │ └── extractionService.ts +│ │ │ │ +│ │ │ ├── routes/ +│ │ │ │ └── index.ts +│ │ │ │ +│ │ │ └── types/ +│ │ │ └── review.ts +│ │ │ +│ │ └── asl/ # ⭐ AI智能文献模块(Week 3新建) +│ │ ├── controllers/ +│ │ │ ├── projectController.ts +│ │ │ ├── literatureController.ts +│ │ │ ├── screeningController.ts +│ │ │ └── extractionController.ts +│ │ │ +│ │ ├── services/ +│ │ │ ├── projectService.ts +│ │ │ ├── literatureService.ts +│ │ │ ├── screeningService.ts +│ │ │ ├── llmScreeningService.ts +│ │ │ └── extractionService.ts +│ │ │ +│ │ ├── routes/ +│ │ │ └── index.ts +│ │ │ +│ │ ├── types/ +│ │ │ ├── project.ts +│ │ │ ├── literature.ts +│ │ │ └── screening.ts +│ │ │ +│ │ └── utils/ +│ │ ├── fileParser.ts +│ │ ├── llmPrompts.ts +│ │ └── exportHelper.ts +│ │ +│ ├── config/ # 配置文件(现有) +│ │ ├── database.ts +│ │ ├── env.ts +│ │ └── ... +│ │ +│ └── index.ts # 应用入口 +│ +├── prisma/ +│ └── schema.prisma # Prisma Schema(已配置10个Schema) +│ +├── package.json +└── tsconfig.json +``` + +### 模块路由注册 + +```typescript +// src/index.ts + +import { platformRoutes } from './platform/routes' +import { aiaRoutes } from './modules/aia/routes' +import { pkbRoutes } from './modules/pkb/routes' +import { rvwRoutes } from './modules/rvw/routes' +import { aslRoutes } from './modules/asl/routes' + +// 注册平台路由 +fastify.register(platformRoutes, { prefix: '/api/v1/platform' }) + +// 注册业务模块路由 +fastify.register(aiaRoutes, { prefix: '/api/v1/aia' }) +fastify.register(pkbRoutes, { prefix: '/api/v1/pkb' }) +fastify.register(rvwRoutes, { prefix: '/api/v1/rvw' }) +fastify.register(aslRoutes, { prefix: '/api/v1/asl' }) +``` + +--- + +## 🔗 前后端对应关系 + +### 模块映射表 + +| 前端模块 | 后端模块 | 数据库Schema | 路由前缀 | 开发状态 | +|---------|---------|-------------|---------|---------| +| `frontend-v2/src/modules/asl/` | `backend/src/modules/asl/` | `asl_schema` | `/api/v1/asl/*` | 🚧 Week 3开发 | +| `frontend-v2/src/modules/aia/` | `backend/src/modules/aia/` | `aia_schema` | `/api/v1/aia/*` | 📋 后续重写 | +| `frontend-v2/src/modules/pkb/` | `backend/src/modules/pkb/` | `pkb_schema` | `/api/v1/pkb/*` | 📋 后续重写 | +| `frontend-v2/src/modules/rvw/` | `backend/src/modules/rvw/` | `public.review_tasks` | `/api/v1/rvw/*` | 📋 后续重写 | +| `frontend-v2/src/modules/dc/` | `backend/src/modules/dc/` | `dc_schema` | `/api/v1/dc/*` | 📋 占位 | + +### API调用示例 + +```typescript +// 前端:frontend-v2/src/modules/asl/api/projectApi.ts +import { apiClient } from '@/shared/api/client' + +export const aslProjectApi = { + // 创建项目 + create: (data: CreateProjectDTO) => + apiClient.post('/api/v1/asl/projects', data), + + // 获取项目列表 + list: () => + apiClient.get('/api/v1/asl/projects'), + + // 获取项目详情 + getById: (id: string) => + apiClient.get(`/api/v1/asl/projects/${id}`), +} + +// 后端:backend/src/modules/asl/routes/index.ts +import { FastifyInstance } from 'fastify' +import { aslProjectController } from '../controllers/projectController' + +export async function aslRoutes(fastify: FastifyInstance) { + fastify.post('/projects', aslProjectController.create) + fastify.get('/projects', aslProjectController.list) + fastify.get('/projects/:id', aslProjectController.getById) +} +``` + +--- + +## 📐 开发规范 + +### 命名规范 + +#### 前端 + +**文件命名:** +- 组件:PascalCase(`ProjectList.tsx`) +- Hooks:camelCase + use前缀(`useProject.ts`) +- 工具函数:camelCase(`formatDate.ts`) +- 类型定义:PascalCase(`Project.ts`) + +**代码命名:** +- 组件:PascalCase(`ProjectList`) +- 函数:camelCase(`fetchProjects`) +- 常量:UPPER_SNAKE_CASE(`API_BASE_URL`) +- 接口:PascalCase + I前缀(`IProject`) +- 类型:PascalCase(`Project`) + +#### 后端 + +**文件命名:** +- Controller:camelCase + Controller后缀(`projectController.ts`) +- Service:camelCase + Service后缀(`projectService.ts`) +- Routes:`index.ts` 或 `routes.ts` +- Types:camelCase(`project.ts`) + +**代码命名:** +- 类:PascalCase(`ProjectService`) +- 函数:camelCase(`createProject`) +- 常量:UPPER_SNAKE_CASE(`MAX_FILE_SIZE`) +- 接口:PascalCase(`ProjectDTO`) + +### 模块开发规范 + +#### 1. 新模块开发流程 + +``` +第一步:数据库设计 +├─ 在 docs/03-业务模块/[模块名]/02-技术设计/01-数据库设计.md +├─ 设计表结构 +├─ 在 Prisma schema 中定义模型 +└─ 运行迁移 + +第二步:后端开发 +├─ 创建 backend/src/modules/[模块名]/ +├─ 编写 controllers/ +├─ 编写 services/ +├─ 编写 routes/ +└─ 测试 API + +第三步:前端开发 +├─ 创建 frontend-v2/src/modules/[模块名]/ +├─ 定义模块接口(index.tsx) +├─ 编写 pages/ +├─ 编写 components/ +├─ 编写 api/ +├─ 编写 hooks/ +└─ 测试功能 + +第四步:集成测试 +├─ 端到端测试 +└─ 性能测试 +``` + +#### 2. 模块独立性原则 + +**DO ✅:** +- 模块内的组件只在模块内使用 +- 模块有自己的状态管理 +- 模块有自己的API调用 +- 模块有自己的类型定义 +- 模块可以独立运行和部署 + +**DON'T ❌:** +- 不要在模块间直接import组件 +- 不要在模块间共享状态 +- 不要在模块间直接调用API +- 共享的逻辑放在 `shared/` 目录 + +#### 3. API设计规范 + +**RESTful风格:** +``` +GET /api/v1/[module]/resources # 列表 +GET /api/v1/[module]/resources/:id # 详情 +POST /api/v1/[module]/resources # 创建 +PUT /api/v1/[module]/resources/:id # 更新 +DELETE /api/v1/[module]/resources/:id # 删除 +``` + +**响应格式:** +```typescript +// 成功响应 +{ + success: true, + data: any, + message?: string +} + +// 错误响应 +{ + success: false, + error: { + code: string, + message: string, + details?: any + } +} +``` + +--- + +## 🚀 实施计划 + +### Week 2:架构改造(2025-11-13 至 2025-11-17) + +#### Day 6(11-13):前端架构基础 ⏰ 4-6小时 + +**上午:项目创建和框架层** +- [x] 创建 frontend-v2 项目(Vite + React + TS) +- [x] 安装依赖(Antd、Router、Zustand、React Query) +- [x] 配置 Tailwind CSS +- [x] 创建目录结构(framework/modules/shared) +- [x] 实现 TopNavigation.tsx +- [x] 实现 MainLayout.tsx +- [x] 定义模块接口(ModuleDefinition) + +**下午:模块占位** +- [x] 为5个模块创建目录 +- [x] 创建 Placeholder.tsx 组件 +- [x] 配置基本路由 +- [x] 测试导航切换 + +**交付物:** +- ✅ 可运行的 frontend-v2 项目 +- ✅ 顶部导航正常工作 +- ✅ 5个模块占位页面 + +#### Day 7(11-14):模块注册机制 ⏰ 6-8小时 + +**上午:注册系统** +- [ ] 实现 moduleRegistry.ts +- [ ] 实现 ModuleLoader.tsx +- [ ] 实现动态路由加载 +- [ ] 实现懒加载机制 + +**下午:测试和文档** +- [ ] 测试模块注册 +- [ ] 测试路由切换 +- [ ] 编写开发文档 +- [ ] 更新 README + +**交付物:** +- ✅ 完整的模块注册系统 +- ✅ 动态路由加载 +- ✅ 开发文档 + +#### Day 8-9(11-15 至 11-16):后端代码分层 ⏰ 1-2天 + +**Day 8 上午:创建目录结构** +- [ ] 创建 platform/ 目录 +- [ ] 创建 common/ 目录 +- [ ] 创建 modules/ 目录 + +**Day 8 下午:迁移 AIA 模块** +- [ ] 创建 modules/aia/ 结构 +- [ ] 迁移 projectController.ts +- [ ] 迁移 conversationController.ts +- [ ] 更新路由注册 + +**Day 9 上午:迁移 PKB 和 RVW** +- [ ] 迁移 PKB 模块代码 +- [ ] 迁移 RVW 模块代码 +- [ ] 更新所有 import 路径 + +**Day 9 下午:测试和文档** +- [ ] 测试所有 API +- [ ] 更新 API 文档 +- [ ] 编写迁移说明 + +**交付物:** +- ✅ 完成后端代码分层 +- ✅ 所有API正常工作 +- ✅ 更新文档 + +#### Day 10(11-17):Week 2 验收 ⏰ 4小时 + +- [ ] 前端架构验收 +- [ ] 后端分层验收 +- [ ] 集成测试 +- [ ] 编写 Week 2 总结报告 +- [ ] 规划 Week 3 ASL 任务 + +--- + +### Week 3-4:ASL模块开发(在新架构下) + +**在全新的 frontend-v2 和分层后的 backend 中开发 ASL 模块** + +详见:[下一阶段行动计划-V2.2-完整版.md](../08-项目管理/下一阶段行动计划-V2.2-完整版.md) + +--- + +## 📝 旧代码处理 + +### frontend/ 目录 + +**状态:** ⏸️ 保留,不再开发 + +**处理方式:** +1. 添加 `README-ARCHIVED.md` 说明文件 +2. 在根目录 `package.json` 中区分启动命令 +3. 保留3-6个月,作为参考和应急备份 +4. 新架构稳定后再删除 + +**参考价值:** +- UI交互设计 +- 某些组件实现 +- API调用逻辑 +- 业务流程 + +### 启动命令 + +```json +{ + "scripts": { + "dev:frontend-old": "cd frontend && npm run dev", + "dev:frontend-new": "cd frontend-v2 && npm run dev", + "dev:frontend": "npm run dev:frontend-new", + "dev:backend": "cd backend && npm run dev", + "dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"" + } +} +``` + +--- + +## 🎯 成功标准 + +### Week 2 验收标准 + +**前端:** +- [x] frontend-v2 项目可运行 +- [x] 顶部导航正常工作 +- [x] 模块注册系统完整 +- [x] 5个模块占位页面 +- [x] 路由切换正常 +- [x] 完整的开发文档 + +**后端:** +- [ ] 代码分层完成(platform/common/modules) +- [ ] 所有现有API正常工作 +- [ ] 路由前缀统一(/api/v1/[module]/*) +- [ ] import路径全部更新 +- [ ] API文档更新 + +**文档:** +- [x] 本架构设计文档 +- [ ] 前端开发规范文档 +- [ ] 后端开发规范文档 +- [ ] Week 2总结报告 + +--- + +## 📚 相关文档 + +### 架构设计 +- [Schema隔离架构设计(10个)](../09-架构实施/01-Schema隔离架构设计(10个).md) +- [前端总体架构设计](../01-平台基础层/06-前端架构/01-前端总体架构设计.md) +- [导航结构设计](../01-平台基础层/06-前端架构/02-导航结构设计.md) + +### 数据库设计 +- [AIA数据库设计](../03-业务模块/AIA-AI智能问答/02-技术设计/01-数据库设计.md) +- [PKB数据库设计](../03-业务模块/PKB-个人知识库/02-技术设计/01-数据库设计.md) + +### 实施报告 +- [Schema迁移完成报告](../09-架构实施/Schema迁移完成报告.md) +- [Prisma配置完成报告](../09-架构实施/Prisma配置完成报告.md) + +### 项目管理 +- [下一阶段行动计划-V2.2-完整版](../08-项目管理/下一阶段行动计划-V2.2-完整版.md) + +--- + +## 🔄 版本历史 + +| 版本 | 日期 | 变更内容 | 维护者 | +|------|------|---------|--------| +| V2.0 | 2025-11-12 | 创建本文档,定义前后端模块化架构 | AI助手 | +| V2.1 | 2025-11-13 | 完成权限系统、错误边界、路由守卫实施 | AI助手 | + +--- + +**文档维护者:** AI助手 + 开发团队 +**最后更新:** 2025-11-13 +**文档状态:** ✅ 已完成,Week 2 Day 7实施完成 + +**🎉 这是系统架构演进的重要里程碑!** + + diff --git a/docs/00-项目概述/壹证循科技 AI科研产品需求文档.md b/docs/00-项目概述/壹证循科技 AI科研产品需求文档.md new file mode 100644 index 00000000..286404a3 --- /dev/null +++ b/docs/00-项目概述/壹证循科技 AI科研产品需求文档.md @@ -0,0 +1,86 @@ +# **壹证循科技 \- AI科研产品需求文档 (PRD)** + +文档版本: 1.0 +日期: 2025年11月5日 +致: 产品部、市场部、销售部、技术部及全体同仁 + +## **1\. 文档目的** + +本需求文档 (Product Requirements Document, PRD) 旨在清晰定义“壹证循科技AI科研产品”的核心功能、目标用户及关键的非功能性需求。 + +本文档是跨部门协作的基石,用于确保技术实现、产品设计与市场策略保持高度一致,特别是应对我们复杂的商业模式和多样的客户部署需求。 + +## **2\. 产品愿景与目标** + +产品愿景: +打造一个覆盖临床科研全生命周期、AI驱动的一站式智能科研平台。 +核心目标: +赋能临床医生和科研人员,通过AI与自动化工具,极大地提升科研效率、数据处理质量和文献分析深度,缩短从数据到洞察的周期。 + +## **3\. 目标用户画像 (User Personas)** + +1. **临床医生/研究者**: + * **画像**:三甲医院的科室主任、主治医师、研究生。 + * **痛点**:有科研项目(如国自然、GCP、回顾性研究)压力,但临床工作繁忙,缺乏专业统计和数据处理时间。 + * **需求**:需要“开箱即用”的工具,快速处理院内数据、分析文献、完成统计、撰写论文。**极其关注患者数据隐私与安全**。 +2. **医院科室/IT部门**: + * **画像**:医院的科研管理科室、信息中心。 + * **痛点**:需要为全院提供合规、可控的科研工具;希望科研数据(尤其是HIS、LIS数据)保留在院内。 + * **需求**:采购的工具必须支持**私有化部署**,且安全、稳定、易于管理。 + +## **4\. 核心功能需求 (Functional Requirements)** + +产品功能矩阵共分为7大核心模块,共同构成科研全流程闭环。 + +| 模块ID | 模块名称 | 核心功能描述 | +| :---- | :---- | :---- | +| **F1** | **智能统计分析 (SSA)** | 提供3条核心分析路径:队列研究、预测模型、RCT研究。实现从数据上传、质控、分析到报告导出的完整流程。 | +| **F2** | **统计分析工具 (ST)** | 提供一个包含100+种轻量化统计工具的工具箱,满足即时、小型的分析需求。 | +| **F3** | **AI智能回答 (AIA)** | 提供10+个专业AI智能体,覆盖科研关键节点:选题评价、PICO梳理、样本量计算、研究方案制定、文章润色与翻译等。 | +| **F4** | **AI智能文献 (ASL)** | 提供AI驱动的文献工作流:智能检索、标题摘要初筛、全文复筛、信息提取,并支持Meta分析、证据图谱等应用。 | +| **F5** | **个人知识库 (PKB)** | 允许用户创建私人文献库(如每个库50篇),并能基于库内文献进行AI问答(RAG)。 | +| **F6** | **数据清洗整理 (DC)** | **(核心难点)** 提供专业工具,处理医院导出的海量(百万行级)、多表格(10+张)的Excel数据,实现两大功能: 1\. **表格ETL**:将多张散乱的流水表,按“患者ID”和“时间”重组为一张干净的分析宽表。 2\. **文本提取(NER)**:从病理报告、住院小结等大段文本中,自动提取结构化的关键字段(如TNM分期)。 | +| **F7** | **个人中心 (UAM)** | 提供账户管理、版本信息、帮助中心、订单管理等基础支撑功能。 | + +## **5\. 关键非功能性需求 (Non-Functional Requirements)** + +这是本产品在商业推广中**最复杂、最核心**的需求,是技术架构必须解决的关键挑战。 + +### **NFR-1: 部署模式灵活性 (Deployment Flexibility)** + +产品必须支持以下四种部署形态,以满足不同客户(尤其是医院)对数据安全和合规性的严苛要求。 + +| 部署形态 | 描述 | 关键要求 | +| :---- | :---- | :---- | +| **云端SaaS版** | (默认) 产品部署在公有云,用户通过浏览器按需订阅使用。 | 必须支持多租户、高可用。 | +| **私有化部署** | (医院/机构) 将**整个平台**或**指定模块**(如DC, SSA)部署在客户的内网服务器上。 | 必须提供容器化(Docker/K8s)的一键部署方案。数据100%不出内网。 | +| **混合部署** | (私有化客户) 客户在内网使用“私有化”的DC/SSA模块,同时能调用我们“云端”的ASL/AIA模块。 | 平台必须支持这种“本地+云端”的混合调用模式,前端需智能路由。 | +| **单机版** | (个人医生) 提供**可安装的桌面应用(Windows/Mac)**,针对DC、SSA、ASL等核心模块。 | **数据100%本地化**。文献、病例原始文件严禁上传。必须支持离线运行(如DC, SSA)。 *(例外:ASL模块可受控调用云端LLM,但仅限发送“摘要”而非“原文”)* | + +### **NFR-2: 商业模式可配置 (Commercial Flexibility)** + +产品架构必须支持销售和市场策略的灵活性。 + +1. **SaaS多版本 (Tiered SaaS)** + * **需求**:云端SaaS版必须支持至少三个等级:**专业版、高级版、旗舰版**。 + * **实现**:后端需具备完善的\*\*功能开关(Feature Flag)\*\*系统,能按版本限制用户可用的功能模块、使用次数、AI模型等。 +2. **模块化售卖 (Modular Sales)** + * **需求**:任何一个功能模块(如F4: AI智能文献)都**必须能**被独立打包,作为单独的产品进行售卖。 + * **实现**:技术架构必须是“松耦合”的,确保模块间没有硬依赖。 +3. **AI成本可控 (Configurable AI)** + * **需求**:为应对不同客户的成本敏感度,平台必须支持**动态切换**后端的大语言模型。 + * **实现**:例如,专业版用户默认调用成本较低的DeepSeek,旗舰版用户可调用更高质量的Claude或GPT。 + +### **NFR-3: 平台与性能 (Platform & Performance)** + +1. **平台兼容性**: + * Web版:必须支持所有现代浏览器(Chrome, Edge, Firefox, Safari)。 + * 单机版:必须支持 **Windows 10 (64位)及以上** 和 **macOS (Intel & Apple Silicon)**。 + * **【重点】明确不支持**:为保证产品稳定性和数据安全,单机版**不支持**任何32位操作系统及Windows 7等已停止服务的系统。 +2. **数据处理性能 (DC模块)**: + * 产品(尤其是单机版)必须能稳定处理百万行级别的多表Excel数据,**严禁**因内存溢出而导致程序崩溃。 + * 产品(服务器版)在处理相同数据时,应追求极致效率,在数分钟内完成处理。 +3. **文本提取质量 (DC模块)**: + * 产品必须能提供高准确率的医学文本提取。 + * **服务器版(最优解)**:应使用SOTA(业界顶尖)的LLM(如Claude 3)以保证最高质量。 + * **单机版(妥协解)**:为保证数据隐私,必须使用100%本地运行的NLP模型(如spaCy),在保护隐私的前提下尽力提高准确率。 \ No newline at end of file diff --git a/docs/00-项目概述/壹证循科技AI科研产品 - 技术架构白皮书.md b/docs/00-项目概述/壹证循科技AI科研产品 - 技术架构白皮书.md new file mode 100644 index 00000000..f010a441 --- /dev/null +++ b/docs/00-项目概述/壹证循科技AI科研产品 - 技术架构白皮书.md @@ -0,0 +1,234 @@ +# **壹证循科技AI科研产品 \- 技术架构白皮书** + +## **1\. 执行摘要 (Executive Summary)** + +本文档为“壹证循科技”AI科研产品套件提供了一个统一的技术架构方案。 + +**核心战略**:采用\*\*“演进式架构”**。以**“模块化单体”**(Modular Monolith)启动,快速迭代;并为未来向**“微服务”\*\*(Microservices)架构的平滑过渡做好充分准备。 + +**核心原则**: + +1. **平台与产品分离**:严格划分“底层通用模块”(如用户中心)和“上层业务模块”(如统计分析)。 +2. **一个架构,多种部署**:设计一套灵活的架构,通过“容器化”和“逻辑重组”,同时支持**①云端SaaS**、**②医院私有化部署**、**③混合部署**和**④医生个人单机版**。 +3. **技术异构(Polyglot)**:采用“用最合适的工具做最合适的事”的原则,允许Node.js、R、Python、Java等技术栈在微服务架构中共存。 + +**技术路径**: + +* **云端/私有化**:采用 Docker(容器) \+ Kubernetes(编排) \+ API网关 的标准微服务部署方案。 +* **单机版**:采用 **Electron** 框架,**100%复用前端UI代码**,并通过 Node.js主进程 \+ 本地子进程(R/Python) 的方式重组后端逻辑,实现数据100%本地化。 + +## **2\. 业务需求与架构挑战** + +### **2.1 产品功能矩阵 (7大模块)** + +1. **智能统计分析 (SSA)** +2. **统计分析工具 (ST)** +3. **AI智能回答 (AIA)** +4. **AI智能文献 (ASL)** +5. **个人知识库 (PKB)** +6. **数据清洗整理 (DC)** +7. **个人中心 (UAM)** + +### **2.2 复杂的商业与部署挑战** + +1. **多版本 (SaaS)**:云端版分为专业版、高级版、旗舰版。 +2. **模块化售卖**:任何模块(如AI智能文献)都可能独立售卖。 +3. **私有化部署**:医院要求将数据敏感模块(如SSA、DC)部署在内网服务器。 +4. **混合部署**:医院本地使用SSA,同时又调用云端的ASL。 +5. **单机版部署**:医生需要在个人电脑(Windows/Mac)上离线运行SSA、DC、ASL等模块,确保数据(文献、病例)不出本地。 + +## **3\. 核心架构:API网关 \+ 微服务** + +为满足以上所有需求,我们推荐一个解耦的、面向服务的架构。 + +* **客户端 (Clients)**:包括Web浏览器、PC单机版(Electron)、移动端。 +* **API网关 (API Gateway)**:所有流量的统一入口。负责: + * **认证**:校验用户Token。 + * **路由**:将请求转发给正确的后端微服务(如 /api/stats \-\> SSA服务)。 + * **聚合**:(未来)合并多个服务的数据。 +* **后端服务 (Services)**:真正执行业务逻辑的单元。 + +## **4\. 服务模块划分 (平台 vs 产品)** + +这是架构解耦的第一步。 + +### **4.1 底层通用模块 (平台层)** + +这些是所有产品共用的“地基”,必须稳固、统一。 + +1. **用户与权限中心 (UAM)** + * **功能**:管理用户、角色、租户(医院)、权限。 + * **价值**:实现SaaS多版本(专业版/高级版)的功能开关(Feature Flag)控制。 +2. **AI大模型网关 (LLM Gateway)** + * **功能**:统一管理所有对大模型的调用(DeepSeek, Claude等)。 + * **价值**:根据SaaS版本、成本考量动态切换模型。是AI功能的核心中枢。 +3. **账户/个人中心**:管理用户配置、订单、帮助文档。 +4. **通知服务**:邮件、站内信。 + +### **4.2 独立业务模块 (产品层)** + +这些是可插拔的“积木”,对应你们的7大功能,每个都应设计为**独立的服务**。 + +1. **智能统计 (SSA) 服务** (可包含统计工具ST) +2. **AI问答 (AIA) 服务** +3. **AI文献 (ASL) 服务** +4. **知识库 (PKB) 服务** +5. **数据清洗 (DC) 服务** (详见第6节) + +## **5\. 推荐技术栈 (技术异构)** + +我们允许“用最合适的工具做最合适的事”。 + +| 领域 | 推荐技术 | 理由 | +| 前端 (UI) | React 或 Vue | 1\. 现代SPA框架。 2\. 可100%复用于Web版和Electron单机版。 | +| API网关 & 粘合层 | Node.js | 1\. 高并发I/O性能。 2\. 作为“总指挥”粘合R/Python非常成熟。 3\. Electron单机版后端复用。 | +| 统计分析 (SSA) | R 语言 | 统计分析的王者。通过 Plumber 包暴露为API,或通过子进程调用。 | +| AI/数据清洗 (DC) | Python | 强大的Pandas、Scikit-learn、NLP生态。通过 FastAPI 暴露API,或通过子进程调用。 | +| 数据库 | PostgreSQL \+ Vector DB | PostgreSQL处理结构化数据;向量数据库支持知识库(PKB)的RAG。 | +| 部署方案 | Docker \+ Kubernetes | 现代云原生的唯一标准,实现弹性和多环境部署。 | +| 单机版方案 | Electron | 唯一能原生支持Node.js后端、复用Web前端UI的跨平台方案。 | + +## **6\. 模块深度解析:(DC) 数据清洗整理服务** + +“数据清洗整理 (DC)” 模块是连接原始数据与有效分析的桥梁,技术挑战分为两部分: + +1. **海量表格ETL**:处理百万行、多表格的结构化数据,执行高效的连接(Join)、重组(Pivot)和排序。 +2. **非结构化文本NER**:从病理、出入院小结等大段文本中,提取结构化字段(如TNM分期、肿瘤大小)。 + +针对不同的部署场景,我们采用两种实现方案: + +### **6.1 方案一:服务器最优版 (Cloud-Optimal)** + +此方案用于**云端SaaS版**和**私有化部署**,目标是追求极致的**效率、准确率和质量**(假设数据已脱敏)。 + +* **技术栈**:Python (FastAPI) \+ Polars \+ LLM API (Claude/GPT) \+ PostgreSQL +* **工作流**: + 1. **API接收**:FastAPI (Python) 服务接收用户上传的多个Excel文件。 + 2. **ETL (Polars)**: + * **放弃Pandas**,采用Polars库。Polars基于Rust,天生**多线程并行**,内存效率极高。 + * 在服务器的大内存中(如64GB+),Polars能以**数秒或数十秒**的速度,在内存中完成200万行数据的多表JOIN、GROUP BY等操作,速度是Pandas的10-100倍。 + 3. **NER (LLM API)**: + * FastAPI 服务**并行调用** AI大模型网关(见4.1节)。 + * 使用 **Claude 3** 或 **GPT-4o** 等SOTA模型,配合JSON Mode,从病理报告中提取结构化信息。 + * **优势**:准确率极高,能理解复杂上下文,无需训练模型。 + 4. **交付**:清洗完成的Polars DataFrame被存入PostgreSQL结果表,前端可在线浏览或导出。 +* **架构融合**:此 FastAPI \+ Polars 服务,**就是**白皮书架构中的“数据清洗 (DC) 微服务”,由“Node.js API网关”负责调用。 + +### **6.2 方案二:单机版 (Desktop-Offline)** + +此方案用于**医生个人电脑**,目标是**100%数据隐私**和**离线可用性**,是对性能和准确率的**必要妥协**。 + +* **技术栈**:Electron (Node.js) \+ Python (Pandas) \+ SQLite \+ spaCy (本地NLP) +* **工作流**: + 1. **总指挥 (Node.js)**:Electron的主进程作为总指挥,调度Python子进程。 + 2. **ETL (SQLite)**: + * **严禁**将200万行数据一次性读入内存(会导致用户电脑崩溃)。 + * Python脚本被调用,使用Pandas的chunksize参数,**分块**读取Excel,**逐行**写入一个本地**SQLite**数据库文件 (.db)。 + * **关键**:利用 **SQLite 数据库引擎**(而不是Pandas内存)来执行所有繁重的JOIN和GROUP BY。这是在低内存电脑上处理大数据的唯一稳定方案。 + 3. **NER (本地 spaCy)**: + * **严禁**将原始病例(PHI)发送到任何云端API(重大违规)。 + * Python脚本被调用,加载 **spaCy** 等**100%本地运行**的NLP模型,在用户电脑上提取实体。 + * **劣势**:spaCy准确率有限,无法处理复杂语义,效果远不如LLM。 + 4. **交付**:所有结果被写回本地 SQLite 数据库。Electron前端通过分页从SQLite中读取数据展示,或导出为Excel。 +* **架构融合**:此方案**就是**白皮书【7.4 场景四:医生单机版】的具体实现。 + +## **7\. 关键路径:多部署模式实现** + +同一套代码库,如何支持所有商业场景? + +### **7.1 场景一:云端SaaS版 (标准微服务)** + +* **架构**:所有服务(UAM, SSA, ASL...)和数据库都在云端,通过K8s管理。 +* **客户端**:用户通过浏览器访问。 + +### **7.2 场景二:医院本地化部署 (私有化)** + +* **架构**:将数据敏感的服务(如 SSA服务、DC服务)及其数据库打包为Docker容器。 +* **部署**:使用K8s或更轻量的K3s,在医院内网服务器上“一键部署”。 +* **数据**:数据100%留在医院内网。 + +### **7.3 场景三:混合部署** + +* **架构**:在场景二的基础上,前端应用被智能配置。 +* **流程**: + * 用户访问 .../stats \-\> 前端调用**医院内网API** (http://192.168.x.x/api/stats)。 + * 用户访问 .../literature \-\> 前端调用**云端公网API** (https://api.yizhengxun.com/api/lit)。 + +### **7.4 场景四:医生单机版 (Electron)** + +这是技术上最关键的“过渡”,它不是“打包”,而是\*\*“架构重组”\*\*。 + +云端版架构: +\[浏览器UI\] \<-- (HTTP网络) \--\> \[云端Node.js API\] \<-- (HTTP) \--\> \[云端R/Python服务\] +单机版架构: +\[Electron UI (复用)\] \<-- (IPC本机通信) \--\> \[Electron主进程 (Node.js复用)\] \<-- (Child Process本机调用) \--\> \[本地R/Python脚本\] +**实现路径:** + +1. **新建Electron项目**。 +2. **移植前端 (UI复用)**:将云端版的React/Vue**编译后的静态文件** (dist目录) 完整复制到Electron中。 +3. **重组后端 (逻辑复用)**: + * 云端版的 **Node.js API逻辑**,被移植到 **Electron的主进程(main.js)** 中。 + * 云端版的 **R 和 Python 脚本**,被作为**本地文件**打包进安装包。 + * main.js (Node.js) 通过 child\_process.spawn(子进程)来**本地调用**这些R/Python脚本。(具体实现见第6.2节) +4. **数据交换**:Node.js、R、Python之间通过 stdin/stdout 和 **JSON** 字符串进行数据交换。 +5. **本地AI功能 (ASL模块)**: + * Node.js (fs模块) 读取本地文献夹中的PDF。 + * Node.js (用 pdf-parse) 在**本地**提取摘要文本。 + * Node.js (用 axios) **只将“摘要文本”发送给云端LLM API** (如DeepSeek) 进行分析。(注意:这与DC模块的原始病例处理不同)。 + * **文献原文件 (.pdf) 100% 不离开用户电脑**。 +6. **本地存储**:使用 **SQLite**(通过 npm install sqlite3)在本地存储文献的元数据、分析结果等。 + +## **8\. 工程化与兼容性 (必读)** + +### **8.1 单机版的“全家桶”挑战** + +单机版的工程挑战不是开发,而是**打包**。 + +* 您的 .exe (Windows) 和 .dmg (Mac) 安装包必须是一个“全家桶”。 +* 您需要使用 electron-builder 等工具,将以下所有内容捆绑进一个安装包: + 1. 您的Electron应用 (Node.js \+ 前端UI)。 + 2. 一个**嵌入式的 Python 运行时**及所有依赖 (pandas, spaCy等)。 + 3. 一个**嵌入式的 R 运行时**及所有依赖 (jsonlite等)。 +* 这会使安装包体积变大(如500MB+),这对于专业软件是完全可以接受的。 + +### **8.2 必须放弃:Win 7 与 32位系统** + +**这是一个商业和安全决策,不是技术选项。** + +**必须声明:** 您的单机版最低系统要求是 **Windows 10 (64位)**。 + +**理由:** + +1. **稳定性的“崩溃”问题**:32位系统有4GB内存上限,APP最多用2-3GB。您的**R语言统计分析**和**数据清洗**都是内存消耗大户,处理真实数据时**不是变卡,而是会直接崩溃闪退**,导致用户数据丢失。 +2. **安全性的“漏洞”问题**:微软已放弃Win 7。现代Electron, Node.js, Python, R生态已**全部停止支持32位和Win 7**。强行兼容意味着您必须使用5年前、充满已知安全漏洞的“古董”技术栈来处理敏感医疗数据,这是不可接受的。 + +## **9\. 分阶段实施路线图 (Roadmap)** + +作为初创公司,我们必须务实。 + +### **9.1 阶段一:云端MVP (0-6个月) \- “模块化单体”** + +* **目标**:快速上线云端SaaS版,验证市场。 +* **架构**:一个代码仓库 (Monorepo),一个主后端服务(如Node.js)。 +* **关键纪律 (打地基)**: + 1. **代码隔离**:在代码目录上,严格按“平台模块”和“业务模块”划分。 + 2. **数据隔离**:在同一个PostgreSQL数据库中,**严格使用不同的Schema** (如 uam\_schema, stats\_schema) 来隔离数据。这是未来拆分微服务的生命线。 + 3. **全员Docker**:从第一天起,所有开发、测试、生产环境都必须基于 Docker 和 docker-compose。 + +### **9.2 阶段二:首次部署 (6-18个月) \- “首次拆分”** + +* **触发点**:迎来第一个“私有化部署”客户,或“单机版”需求。 +* **架构**: + 1. **引入K8s**:在云端正式启用Kubernetes进行服务编排。 + 2. **引入API网关**:在K8s集群前部署Nginx或Kong。 + 3. **执行首次拆分**:将 SSA 和 DC 模块(连同它们的 stats\_schema)从主应用中**物理拆分**出来,打包成第一个独立的微服务(采用6.1节的“最优版”架构)。 + 4. **开发单机版**:启动第一个Electron项目,按照【7.4】中的路径进行“重组”(采用6.2节的“单机版”架构)。 + +### **9.3 阶段三:全面微服务 (18个月+)** + +* **目标**:支持灵活的业务组合和团队扩张。 +* **架构**:随着业务发展,将 ASL, PKB 等其他成熟模块也逐步拆分为独立的微服务,实现最终的“乐高积木式”的灵活架构。 + +## **10\. 结论** + +本架构方案的核心是\*\*“演进”\*\*。它在初期(阶段一)通过“模块化单体”保证了初创公司的迭代速度,同时通过“代码/数据隔离”和“全面Docker化”的纪律,为未来(阶段二、三)向复杂微服务和多部署形态的平滑过渡打下了坚实的地基,避免了未来推倒重来的灾难性成本。 \ No newline at end of file diff --git a/docs/00-项目概述/文档梳理与差异分析.md b/docs/00-项目概述/文档梳理与差异分析.md new file mode 100644 index 00000000..a7280f4c --- /dev/null +++ b/docs/00-项目概述/文档梳理与差异分析.md @@ -0,0 +1,498 @@ +# AIclinicalresearch 文档梳理与差异分析 + +> **文档版本:** v1.0 +> **创建日期:** 2025-11-06 +> **维护者:** 项目团队 +> **最后更新:** 2025-11-06 + +--- + +## 📋 执行摘要 + +本文档对AIclinicalresearch项目下的所有文档进行了系统梳理,并重点对比了**最新需求文档**(壹证循科技 AI科研产品需求文档.md 和 技术架构白皮书.md)与**现有文档**之间的差异。 + +### 🎯 核心发现 + +**最新需求文档(2025-11-05)反映了产品战略的重大调整:** + +1. **产品定位变化:** 从单一的"AI科研助手"扩展为**7大模块的综合性AI科研平台** +2. **商业模式变化:** 从简单SaaS模式扩展为**4种部署形态**(云端SaaS、私有化、混合部署、单机版) +3. **技术架构变化:** 从"模块化单体"演进为**微服务架构**,支持模块化售卖 +4. **目标用户变化:** 从科研人员扩展到**医院机构**(强调数据安全和私有化部署) + +--- + +## 📚 文档结构梳理 + +### 1. 00-项目概述 文件夹 + +| 文档名称 | 状态 | 版本日期 | 核心内容 | 是否符合最新需求 | +|---------|------|---------|---------|----------------| +| **壹证循科技 AI科研产品需求文档.md** | ✅ 最新 | 2025-11-05 | 7大模块功能矩阵、4种部署模式、商业模式 | ✅ 基准文档 | +| **壹证循科技AI科研产品 - 技术架构白皮书.md** | ✅ 最新 | 2025-11-05 | 微服务架构、技术异构、Electron单机版 | ✅ 基准文档 | +| 产品需求文档(PRD).md | ⚠️ 旧版 | 2025-10-10 | 仅包含AI问答、知识库、项目管理 | ❌ **需要更新** | +| 技术架构总览.md | ⚠️ 旧版 | 2025-10-10 | 基于Dify+LobeChat的简化架构 | ❌ **需要更新** | +| AI智能文献PRD(1)-产品概览.md | ⚠️ 部分旧 | 2025-10-21 | 6大模块(研究方案、检索、初筛、复筛、提取、分析) | ⚠️ **部分符合,需整合** | +| AI智能文献PRD(2)-初筛与复筛.md | ⚠️ 部分旧 | 2025-10-21 | 初筛和复筛详细设计 | ⚠️ **部分符合,需整合** | +| AI智能文献PRD(3)-提取与分析模块.md | ⚠️ 部分旧 | 2025-10-21 | 提取和分析详细设计 | ⚠️ **部分符合,需整合** | +| 系统总体架构设计.md | ⚠️ 占位 | 2025-10-29 | 占位文档,待完善 | ❌ **需要重写** | +| 设计文档完成总结.md | ⚠️ 旧版 | 2025-10-10 | 基于旧版PRD的总结 | ❌ **需要更新** | + +### 2. 01-设计文档 文件夹 + +| 文档名称 | 状态 | 版本日期 | 核心内容 | 是否符合最新需求 | +|---------|------|---------|---------|----------------| +| 数据库设计文档.md | ⚠️ 旧版 | 2025-10-10 | 基于AI问答+知识库的数据库设计 | ❌ **缺少DC、SSA、ASL模块表** | +| API设计规范.md | ⚠️ 旧版 | 2025-10-10 | 基于AI问答+知识库的API设计 | ❌ **缺少新模块API** | +| 平台前端架构设计/01-前端总体架构设计.md | ⚠️ 部分旧 | 2025-10-29 | 7个模块的顶部导航设计 | ⚠️ **架构正确,但缺少部署模式考虑** | +| 平台前端架构设计/02-导航结构设计.md | ⚠️ 部分旧 | 2025-10-29 | 导航详细设计 | ⚠️ **架构正确,但缺少部署模式考虑** | +| 系统架构/01-系统总体架构设计.md | ⚠️ 占位 | 2025-10-29 | 占位文档 | ❌ **需要重写** | +| 系统架构/04-运营管理端架构设计.md | ⚠️ 占位 | 2025-10-29 | 占位文档 | ❌ **需要重写** | +| 系统架构/05-部署架构设计.md | ⚠️ 占位 | 2025-10-29 | 占位文档 | ❌ **需要重写(关键)** | + +### 3. AI智能文献 文件夹 + +| 文档名称 | 状态 | 版本日期 | 核心内容 | 是否符合最新需求 | +|---------|------|---------|---------|----------------| +| 所有文档 | ⚠️ 部分旧 | 2025-10-29 | 基于Web版的AI智能文献设计 | ⚠️ **缺少单机版、私有化部署考虑** | + +### 4. 07-部署文档 文件夹 + +| 文档名称 | 状态 | 版本日期 | 核心内容 | 是否符合最新需求 | +|---------|------|---------|---------|----------------| +| 本地化部署方案.md | ⚠️ 占位 | 2025-10-29 | 占位文档 | ❌ **需要重写(关键)** | +| 模块独立部署指南.md | ⚠️ 占位 | 2025-10-29 | 占位文档 | ❌ **需要重写(关键)** | + +### 5. 05-每日进度 文件夹 + +| 状态 | 说明 | +|------|------| +| ⚠️ 历史记录 | 记录了AI问答+知识库的开发历史(Day04-Day31),基于旧版PRD | + +--- + +## 🔍 关键差异分析 + +### 差异1:产品功能范围 + +#### 旧版文档(产品需求文档(PRD).md) +``` +核心功能: +1. 项目/课题管理 +2. AI智能体(12个智能体) +3. 个人知识库 +4. 历史记录 +5. 运营后台 +``` + +#### 最新需求(壹证循科技 AI科研产品需求文档.md) +``` +7大核心模块: +F1. 智能统计分析 (SSA) - ❌ 旧文档完全缺失 +F2. 统计分析工具 (ST) - ❌ 旧文档完全缺失 +F3. AI智能回答 (AIA) - ✅ 对应旧文档的"AI智能体" +F4. AI智能文献 (ASL) - ⚠️ 有独立PRD,但未整合 +F5. 个人知识库 (PKB) - ✅ 对应旧文档的"个人知识库" +F6. 数据清洗整理 (DC) - ❌ 旧文档完全缺失(核心难点) +F7. 个人中心 (UAM) - ✅ 对应旧文档的"个人中心" +``` + +**影响:** +- ❌ 旧版数据库设计缺少 SSA、ST、DC、ASL 模块的表结构 +- ❌ 旧版API设计缺少这些模块的接口 +- ❌ 旧版前端架构虽然预留了导航位置,但缺少详细设计 + +--- + +### 差异2:部署模式 + +#### 旧版文档(技术架构总览.md) +``` +部署模式: +- 云端SaaS版(唯一模式) +- 基于Docker部署 +- 单一租户架构 +``` + +#### 最新需求(技术架构白皮书.md) +``` +4种部署形态(NFR-1核心要求): +1. 云端SaaS版 - 多租户、高可用 +2. 私有化部署 - 整个平台或指定模块部署在客户内网 +3. 混合部署 - 本地使用DC/SSA,云端调用ASL/AIA +4. 单机版 - Electron桌面应用(Windows/Mac),数据100%本地化 +``` + +**影响:** +- ❌ 旧版架构设计**完全不支持**私有化部署和单机版 +- ❌ 旧版前端架构设计未考虑**混合部署**的路由策略 +- ❌ 缺少**Electron单机版**的技术方案和开发计划 +- ❌ 缺少**容器化(K8s)**的部署架构设计 + +--- + +### 差异3:商业模式 + +#### 旧版文档 +``` +商业模式: +- 简单的SaaS订阅模式 +- 未明确版本分级 +``` + +#### 最新需求(NFR-2核心要求) +``` +商业模式(NFR-2): +1. SaaS多版本:专业版、高级版、旗舰版 + - 需要完善的Feature Flag系统 +2. 模块化售卖:任何模块可独立打包售卖 + - 技术架构必须松耦合 +3. AI成本可控:动态切换LLM模型 + - 专业版用DeepSeek,旗舰版用Claude/GPT +``` + +**影响:** +- ⚠️ 旧版前端架构设计已考虑版本权限控制,但**未实现Feature Flag系统** +- ❌ 旧版架构设计未考虑**模块独立售卖**的技术实现 +- ⚠️ 旧版已支持多模型切换,但未与版本权限绑定 + +--- + +### 差异4:技术架构 + +#### 旧版文档(技术架构总览.md) +``` +技术架构: +- 前端:React + Vite + LobeChat组件 +- 后端:Node.js + Fastify + Prisma +- 数据库:PostgreSQL +- RAG:Dify(仅用于知识库) +- LLM:DeepSeek-V3 + Qwen3 +- 架构:模块化单体(Monolith) +``` + +#### 最新需求(技术架构白皮书.md) +``` +技术架构(演进式): +- 阶段一(0-6个月):模块化单体 ✅ 与旧版一致 +- 阶段二(6-18个月):首次拆分(SSA、DC微服务)+ Electron单机版 +- 阶段三(18个月+):全面微服务架构 + +核心技术栈(技术异构): +- 前端:React/Vue(Web + Electron复用) +- API网关:Node.js +- 统计分析(SSA):R语言 + Plumber API ❌ 旧文档缺失 +- 数据清洗(DC):Python + Polars/Pandas + FastAPI ❌ 旧文档缺失 +- 部署:Docker + Kubernetes ⚠️ 旧文档仅Docker +- 单机版:Electron + 本地R/Python子进程 ❌ 旧文档完全缺失 +``` + +**影响:** +- ❌ 旧版架构设计**未考虑R语言和Python微服务**的集成 +- ❌ 旧版架构设计**未考虑Kubernetes编排** +- ❌ 旧版架构设计**完全缺少Electron单机版**的技术方案 +- ❌ 旧版架构设计**未考虑API网关**的引入 + +--- + +### 差异5:数据清洗模块(DC)- 核心难点 + +#### 旧版文档 +``` +状态:完全缺失 +``` + +#### 最新需求(技术架构白皮书第6节) +``` +数据清洗整理 (DC) 模块: +1. 海量表格ETL:处理百万行、多表格的Excel数据 +2. 非结构化文本NER:从病理报告中提取结构化字段 + +两种实现方案: +方案一:服务器最优版(云端/私有化) +- Python + Polars(替代Pandas,10-100倍速度) +- LLM API(Claude 3/GPT-4o)进行NER +- PostgreSQL存储结果 + +方案二:单机版(Desktop-Offline) +- Electron + Python子进程 +- SQLite(避免内存溢出) +- spaCy本地NLP模型(100%隐私保护) +``` + +**影响:** +- ❌ 旧版数据库设计**完全缺少DC模块的表结构** +- ❌ 旧版API设计**完全缺少DC模块的接口** +- ❌ 旧版技术栈**未包含Python微服务** +- ❌ 旧版架构设计**未考虑Polars、SQLite、spaCy**等关键技术 + +--- + +### 差异6:AI智能文献模块(ASL) + +#### 旧版文档(AI智能文献PRD系列) +``` +状态:有独立PRD文档(2025-10-21) +内容:6大模块(研究方案、检索、初筛、复筛、提取、分析) +架构:基于Web版的设计 +``` + +#### 最新需求(壹证循科技 AI科研产品需求文档.md) +``` +F4. AI智能文献 (ASL): +- 提供AI驱动的文献工作流 +- 智能检索、标题摘要初筛、全文复筛、信息提取 +- 支持Meta分析、证据图谱等应用 +- 必须支持单机版(文献原文100%不离开用户电脑) +``` + +**影响:** +- ⚠️ 现有AI智能文献PRD文档**内容基本符合**,但需要: + 1. ❌ 补充**单机版实现方案**(Electron + 本地PDF解析) + 2. ❌ 补充**私有化部署方案** + 3. ⚠️ 整合到**7大模块**的整体架构中 + +--- + +### 差异7:智能统计分析模块(SSA) + +#### 旧版文档 +``` +状态:完全缺失 +``` + +#### 最新需求 +``` +F1. 智能统计分析 (SSA): +- 3条核心分析路径:队列研究、预测模型、RCT研究 +- 数据上传、质控、分析、报告导出 +- 必须支持私有化部署(医院内网) +- 必须支持单机版(数据100%本地化) + +技术实现(白皮书): +- R语言 + Plumber API(服务器版) +- R语言 + Electron子进程(单机版) +``` + +**影响:** +- ❌ 旧版文档**完全缺少SSA模块的PRD** +- ❌ 旧版数据库设计**完全缺少SSA模块的表结构** +- ❌ 旧版技术栈**未包含R语言** +- ❌ 旧版架构设计**未考虑R语言微服务**的集成 + +--- + +## 📊 文档符合度评分 + +| 文档类别 | 符合度 | 说明 | +|---------|-------|------| +| **产品需求文档** | 30% | 仅覆盖3/7模块(AIA、PKB、UAM) | +| **技术架构文档** | 40% | 基础架构正确,但缺少微服务、Electron、K8s | +| **数据库设计** | 35% | 仅覆盖3/7模块的表结构 | +| **API设计** | 35% | 仅覆盖3/7模块的接口 | +| **前端架构** | 60% | 导航结构正确,但缺少部署模式考虑 | +| **部署文档** | 0% | 完全缺失(占位文档) | +| **AI智能文献** | 70% | 内容基本符合,但缺少单机版和私有化方案 | + +**总体符合度:约 40%** + +--- + +## 🚨 关键缺失内容清单 + +### 1. 产品需求层面 + +- [ ] **SSA模块完整PRD**(队列研究、预测模型、RCT研究) +- [ ] **ST模块完整PRD**(100+种统计工具) +- [ ] **DC模块完整PRD**(表格ETL + 文本NER) +- [ ] **4种部署模式的详细需求说明** +- [ ] **模块化售卖的商业模式设计** +- [ ] **Feature Flag系统的需求定义** + +### 2. 技术架构层面 + +- [ ] **微服务架构设计**(API网关 + 服务拆分) +- [ ] **R语言微服务集成方案** +- [ ] **Python微服务集成方案**(Polars + FastAPI) +- [ ] **Kubernetes部署架构设计** +- [ ] **Electron单机版完整技术方案** +- [ ] **混合部署的路由策略设计** +- [ ] **私有化部署的容器化方案** + +### 3. 数据库设计层面 + +- [ ] **SSA模块表结构**(研究项目、数据集、分析结果) +- [ ] **ST模块表结构**(工具配置、使用记录) +- [ ] **DC模块表结构**(清洗任务、ETL配置、NER结果) +- [ ] **ASL模块表结构**(文献项目、筛选记录、提取数据) +- [ ] **多租户数据隔离设计**(Schema隔离) + +### 4. API设计层面 + +- [ ] **SSA模块API**(数据上传、分析执行、报告生成) +- [ ] **ST模块API**(工具列表、工具执行) +- [ ] **DC模块API**(文件上传、ETL执行、NER执行) +- [ ] **ASL模块API**(文献导入、筛选、提取) +- [ ] **API网关路由配置** + +### 5. 前端架构层面 + +- [ ] **Electron单机版前端架构** +- [ ] **混合部署的前端路由策略** +- [ ] **Feature Flag前端实现** +- [ ] **模块独立打包方案** + +### 6. 部署文档层面 + +- [ ] **云端SaaS部署方案**(K8s + 多租户) +- [ ] **私有化部署方案**(Docker + K3s) +- [ ] **混合部署方案**(本地+云端) +- [ ] **Electron单机版打包方案**(Windows + Mac) +- [ ] **模块独立部署指南** + +--- + +## 📝 建议的文档更新优先级 + +### 🔴 P0 - 立即更新(阻塞开发) + +1. **系统总体架构设计.md** - 重写,基于技术架构白皮书 +2. **部署架构设计.md** - 重写,详细说明4种部署模式 +3. **数据库设计文档.md** - 补充SSA、ST、DC、ASL模块表结构 +4. **产品需求文档(PRD).md** - 重写,整合7大模块 + +### 🟠 P1 - 近期更新(影响规划) + +5. **DC模块PRD** - 新建,详细说明ETL和NER需求 +6. **SSA模块PRD** - 新建,详细说明3条分析路径 +7. **ST模块PRD** - 新建,详细说明100+工具 +8. **Electron单机版技术方案** - 新建,详细说明实现路径 +9. **API设计规范.md** - 补充新模块API + +### 🟡 P2 - 后续更新(优化完善) + +10. **前端总体架构设计.md** - 补充部署模式考虑 +11. **AI智能文献PRD系列** - 补充单机版和私有化方案 +12. **技术架构总览.md** - 重写,基于技术架构白皮书 +13. **本地化部署方案.md** - 详细说明私有化部署 +14. **模块独立部署指南.md** - 详细说明模块化售卖 + +--- + +## 🎯 下一步行动建议 + +### 建议1:明确开发阶段 + +根据技术架构白皮书的分阶段实施路线图: + +**阶段一(0-6个月):云端MVP - "模块化单体"** +- ✅ 可以继续使用现有架构(Node.js + Fastify + PostgreSQL) +- ⚠️ 但必须严格遵循"代码隔离"和"数据隔离"(Schema隔离) +- ❌ 暂不开发Electron单机版和私有化部署 + +**阶段二(6-18个月):首次拆分** +- 引入K8s和API网关 +- 拆分SSA和DC为独立微服务 +- 开发Electron单机版 + +### 建议2:模块开发优先级 + +基于商业价值和技术复杂度: + +**第一优先级(核心差异化):** +1. **DC模块(数据清洗整理)** - 核心难点,差异化竞争力 +2. **ASL模块(AI智能文献)** - 已有PRD,可快速推进 + +**第二优先级(完善产品矩阵):** +3. **SSA模块(智能统计分析)** - 需要R语言团队 +4. **ST模块(统计分析工具)** - 相对简单 + +**第三优先级(已完成):** +5. AIA模块(AI智能回答) - ✅ 已完成 +6. PKB模块(个人知识库) - ✅ 已完成 +7. UAM模块(个人中心) - ✅ 已完成 + +### 建议3:文档更新策略 + +**立即行动(本周):** +1. 创建 `系统总体架构设计.md`(基于白皮书) +2. 创建 `部署架构设计.md`(4种部署模式) +3. 更新 `数据库设计文档.md`(补充新模块表结构) + +**近期行动(本月):** +4. 创建 `DC模块PRD.md` +5. 创建 `SSA模块PRD.md` +6. 创建 `Electron单机版技术方案.md` + +**持续行动:** +7. 随着开发进展,持续更新API设计、前端架构等文档 + +### 建议4:技术选型确认 + +**需要与团队确认的关键技术决策:** + +1. **是否引入R语言?** + - SSA模块需要R语言(统计分析的王者) + - 需要评估团队能力和学习成本 + +2. **是否引入Python微服务?** + - DC模块需要Python + Polars/Pandas + - 需要评估与现有Node.js架构的集成复杂度 + +3. **是否立即规划Electron单机版?** + - 白皮书建议在阶段二(6-18个月)开发 + - 需要确认市场需求的紧迫性 + +4. **是否立即引入K8s?** + - 白皮书建议在阶段二引入 + - 阶段一可以继续使用Docker Compose + +--- + +## 📌 总结 + +### 核心问题 + +**旧版文档与最新需求的核心差异:** + +1. **产品范围扩大:** 从3个模块扩展到7个模块 +2. **部署模式复杂化:** 从单一云端SaaS扩展到4种部署形态 +3. **技术架构演进:** 从模块化单体演进到微服务架构 +4. **商业模式升级:** 从简单订阅到模块化售卖 + 多版本 + 多部署 + +### 关键建议 + +**务实的推进策略:** + +1. **阶段一(当前):** 继续使用现有架构,专注于**云端SaaS版**的7大模块开发 +2. **严格纪律:** 必须遵循"代码隔离"和"数据Schema隔离",为未来拆分打基础 +3. **优先级:** 先开发DC和ASL模块(差异化竞争力) +4. **文档先行:** 立即更新P0级文档,指导后续开发 + +**避免过度设计:** + +- ❌ 不要在阶段一就引入K8s和API网关(增加复杂度) +- ❌ 不要在阶段一就开发Electron单机版(分散精力) +- ✅ 专注于云端SaaS版的功能完善和市场验证 +- ✅ 为未来的架构演进打好基础(代码和数据隔离) + +--- + +**文档维护者:** 项目团队 +**最后更新:** 2025-11-06 +**下次审查:** 2025-11-13 + + + + + + + + + + + + + + + diff --git a/docs/00-项目概述/最新需求与技术方案深度评估.md b/docs/00-项目概述/最新需求与技术方案深度评估.md new file mode 100644 index 00000000..9fa93f92 --- /dev/null +++ b/docs/00-项目概述/最新需求与技术方案深度评估.md @@ -0,0 +1,1348 @@ +# 最新需求与技术方案深度评估 + +> **评估日期:** 2025-11-06 +> **评估人:** 技术架构师 +> **评估对象:** 壹证循科技 AI科研产品需求文档 + 技术架构白皮书 +> **评估目的:** 深度分析产品战略和技术路径的合理性、可行性和潜在风险 + +--- + +## 📊 执行摘要 + +### 总体评价 + +**产品战略:⭐⭐⭐⭐⭐ (5/5)** +- ✅ 目标清晰,定位准确 +- ✅ 用户画像深刻 +- ✅ 商业模式完整 +- ✅ 非功能需求考虑周全 + +**技术方案:⭐⭐⭐⭐ (4/5)** +- ✅ 演进式架构设计合理 +- ✅ 技术选型务实 +- ✅ 分阶段实施可行 +- ⚠️ 部分技术细节需要补充 + +### 核心发现 + +**优点:** +1. ✅ **战略清晰**:7大模块覆盖科研全流程 +2. ✅ **商业模式完善**:4种部署 + 模块化售卖 + 多版本 +3. ✅ **技术路径务实**:演进式架构,避免过度设计 +4. ✅ **风险控制合理**:分阶段实施,快速验证 + +**需要注意的问题:** +1. ⚠️ **技术复杂度高**:7个模块 + 4种部署 + 3种技术栈 +2. ⚠️ **团队能力要求**:需要Node.js + R + Python多栈能力 +3. ⚠️ **时间估算乐观**:阶段一6个月可能紧张 +4. ⚠️ **成本控制挑战**:单机版打包和维护成本可能被低估 + +--- + +## 📋 Part 1:产品需求文档深度分析 + +### 1.1 产品定位评估 ✅ **优秀** + +#### 核心定位 +``` +"一个覆盖临床科研全生命周期、AI驱动的一站式智能科研平台" +``` + +**评价:⭐⭐⭐⭐⭐** +- ✅ **定位清晰**:临床科研全流程 +- ✅ **差异化明显**:AI驱动是核心竞争力 +- ✅ **目标明确**:提升效率、降低门槛 + +**对比分析:** +| 竞品类型 | 定位 | 我们的差异化 | +|---------|------|------------| +| 统计分析软件(SPSS/SAS) | 专注统计 | ✅ 我们覆盖全流程 | +| 文献管理软件(EndNote) | 专注文献 | ✅ 我们有AI智能分析 | +| AI写作助手(ChatGPT) | 通用AI | ✅ 我们是医学垂直领域 | +| 科研管理平台(各种SaaS) | 流程管理 | ✅ 我们有AI核心能力 | + +**结论:定位准确,具有差异化竞争力** + +--- + +### 1.2 用户画像评估 ✅ **准确** + +#### 两类核心用户 +**1. 临床医生/研究者** +- 痛点:繁忙、缺乏统计能力、数据处理困难 +- 需求:开箱即用、快速处理、**数据隐私** +- 特点:**极其关注数据安全** + +**2. 医院科室/IT部门** +- 痛点:需要合规工具、科研数据不能外泄 +- 需求:**私有化部署**、安全稳定、易管理 +- 特点:采购决策者,看重合规性 + +**评价:⭐⭐⭐⭐⭐** +- ✅ **用户分层准确**:个人用户 vs 机构用户 +- ✅ **痛点挖掘深刻**:数据隐私是核心痛点 +- ✅ **需求理解到位**:私有化部署是必需,不是可选 + +**关键洞察:** +``` +医院对"数据隐私"的要求,直接决定了必须支持: +1. 私有化部署(数据不出内网) +2. 单机版(数据不上传) +3. 本地NLP模型(DC模块) +``` + +**这是产品战略的核心支撑,非常正确!** + +--- + +### 1.3 功能模块设计评估 ⭐⭐⭐⭐⭐ **优秀** + +#### 7大模块矩阵 + +| 模块 | 价值定位 | 差异化竞争力 | 技术难度 | 商业价值 | +|------|---------|------------|---------|---------| +| **F1: SSA(智能统计分析)** | 核心工具 | ⭐⭐⭐⭐ 3条路径完整 | ⭐⭐⭐⭐⭐ 需要R语言 | ⭐⭐⭐⭐⭐ 刚需 | +| **F2: ST(统计分析工具)** | 补充工具 | ⭐⭐⭐ 100+工具 | ⭐⭐⭐ 相对简单 | ⭐⭐⭐⭐ 高频使用 | +| **F3: AIA(AI智能回答)** | AI能力 | ⭐⭐⭐⭐⭐ 10+智能体 | ⭐⭐⭐ 已验证 | ⭐⭐⭐⭐ 差异化 | +| **F4: ASL(AI智能文献)** | AI能力 | ⭐⭐⭐⭐⭐ 全流程自动化 | ⭐⭐⭐⭐ 复杂但可行 | ⭐⭐⭐⭐⭐ 巨大痛点 | +| **F5: PKB(个人知识库)** | 辅助功能 | ⭐⭐⭐ RAG问答 | ⭐⭐⭐ 已验证 | ⭐⭐⭐ 锦上添花 | +| **F6: DC(数据清洗)** | **核心难点** | ⭐⭐⭐⭐⭐ 独家能力 | ⭐⭐⭐⭐⭐ 最高难度 | ⭐⭐⭐⭐⭐ 核心痛点 | +| **F7: UAM(个人中心)** | 基础功能 | ⭐ 标配 | ⭐⭐ 简单 | ⭐⭐ 必需 | + +**评价:⭐⭐⭐⭐⭐** + +**核心优势:** +1. ✅ **模块组合合理**:覆盖科研全流程 +2. ✅ **差异化突出**:DC和ASL是核心竞争力 +3. ✅ **刚需清晰**:SSA、DC、ASL都是强痛点 + +**关键洞察:** +``` +DC模块(数据清洗整理)的价值被充分认识: +- 问题:医院导出的流水表,百万行级别,10+张表 +- 痛点:手工整理要数周,容易出错 +- 价值:自动ETL + NER提取,数小时完成 +- 差异化:市面上几乎没有这样的产品 + +这是产品的核心竞争力! +``` + +--- + +### 1.4 非功能需求评估 ⭐⭐⭐⭐⭐ **卓越** + +#### NFR-1: 部署模式灵活性(最核心) + +| 部署形态 | 目标用户 | 核心要求 | 技术挑战 | 评价 | +|---------|---------|---------|---------|------| +| **云端SaaS版** | 个人用户、小机构 | 多租户、高可用 | ⭐⭐⭐ 中等 | ✅ 已验证 | +| **私有化部署** | 医院、大机构 | 数据不出内网、容器化 | ⭐⭐⭐⭐ 高 | ⚠️ 需要K8s | +| **混合部署** | 私有化客户(高级) | 本地DC/SSA + 云端ASL/AIA | ⭐⭐⭐⭐⭐ 很高 | ⚠️ 需要智能路由 | +| **单机版** | 个人医生 | 100%本地化、离线运行 | ⭐⭐⭐⭐⭐ 很高 | ⚠️ Electron复杂 | + +**评价:⭐⭐⭐⭐⭐** + +**核心洞察:** +``` +4种部署模式不是"可选项",而是"必需项": + +1. 云端SaaS:个人用户、小机构(70%市场) +2. 私有化:大医院、三甲医院(20%市场,但客单价高) +3. 混合部署:高级需求,技术挑战最大 +4. 单机版:个人医生、数据敏感场景(10%市场,但口碑传播) + +没有这4种部署模式,就无法覆盖全部市场! +``` + +**这是产品战略的关键决策,非常正确!** + +**但需要注意:** +⚠️ 这4种部署模式的技术复杂度远超想象: +- 单机版:需要Electron + 本地R/Python子进程 + 嵌入式数据库 +- 混合部署:需要前端智能路由 + 跨网络通信 +- 私有化:需要K8s + 一键部署脚本 + 客户侧运维支持 + +**建议分阶段实施,不要一次性全做!** + +--- + +#### NFR-2: 商业模式可配置 + +| 需求 | 描述 | 技术要求 | 评价 | +|------|------|---------|------| +| **SaaS多版本** | 专业版、高级版、旗舰版 | Feature Flag系统 | ⚠️ 未实现 | +| **模块化售卖** | 任何模块可独立打包售卖 | 松耦合架构 | ⚠️ 未实现 | +| **AI成本可控** | 动态切换LLM模型 | 模型与版本绑定 | ⚠️ 未实现 | + +**评价:⭐⭐⭐⭐** + +**优点:** +- ✅ 商业模式设计完整 +- ✅ 考虑了成本控制 +- ✅ 支持灵活销售策略 + +**问题:** +- ⚠️ **Feature Flag系统**:当前架构未实现 +- ⚠️ **模块松耦合**:当前架构有一定耦合(共用数据库) +- ⚠️ **动态模型切换**:已支持用户切换,但未与版本绑定 + +**建议:** +```typescript +// Feature Flag系统设计(建议) +interface FeatureFlag { + // 用户版本 + version: 'basic' | 'advanced' | 'flagship'; + + // 模块权限 + modules: { + SSA: boolean; // 智能统计分析 + ST: boolean; // 统计分析工具 + AIA: boolean; // AI智能回答 + ASL: boolean; // AI智能文献 + PKB: boolean; // 个人知识库 + DC: boolean; // 数据清洗 + }; + + // 功能权限 + features: { + aiModelSelection: boolean; // 可切换AI模型 + batchProcessing: boolean; // 批处理功能 + fullTextMode: boolean; // 全文精读 + knowledgeBaseQuota: number; // 知识库配额 + documentQuota: number; // 文档配额 + monthlyAIQuota: number; // 每月AI调用次数 + }; + + // 模型权限 + allowedModels: ModelType[]; // ['deepseek-v3'] or ['deepseek-v3', 'qwen3', 'claude'] +} + +// 版本配置示例 +const versionConfig = { + basic: { + modules: { AIA: true, PKB: true, UAM: true }, + allowedModels: ['deepseek-v3'], + monthlyAIQuota: 100 + }, + advanced: { + modules: { AIA: true, PKB: true, ASL: true, UAM: true }, + allowedModels: ['deepseek-v3', 'qwen3'], + monthlyAIQuota: 500 + }, + flagship: { + modules: { SSA: true, ST: true, AIA: true, ASL: true, PKB: true, DC: true, UAM: true }, + allowedModels: ['deepseek-v3', 'qwen3', 'qwen-long', 'claude'], + monthlyAIQuota: 2000 + } +}; +``` + +**这个系统需要尽快实现,是商业模式的基础!** + +--- + +#### NFR-3: 平台与性能 + +**明确不支持Win7和32位系统:⭐⭐⭐⭐⭐ 非常正确!** + +``` +理由(文档说得很清楚): +1. 稳定性:32位系统4GB内存上限,R和Python会崩溃 +2. 安全性:Win7已停止维护,存在已知漏洞 +3. 技术生态:现代技术栈已全面停止支持32位 + +结论:必须放弃,这是明智的技术决策! +``` + +**DC模块性能要求:** +- 服务器版:百万行数据,数分钟完成(Polars性能) +- 单机版:百万行数据,不崩溃(SQLite方案) + +**评价:⭐⭐⭐⭐⭐** +- ✅ 性能目标清晰 +- ✅ 技术方案合理(Polars + SQLite) +- ✅ 区分了服务器版和单机版的不同目标 + +--- + +### 1.5 产品需求文档总结 + +**总体评价:⭐⭐⭐⭐⭐ (5/5)** + +**核心优势:** +1. ✅ **战略清晰**:7大模块覆盖全流程,定位准确 +2. ✅ **用户洞察深刻**:数据隐私是核心痛点 +3. ✅ **商业模式完整**:4种部署 + 模块化售卖 + 多版本 +4. ✅ **非功能需求完善**:部署、性能、平台兼容性都考虑到了 + +**需要注意的风险:** +1. ⚠️ **实施复杂度高**:7个模块 + 4种部署,工作量巨大 +2. ⚠️ **技术挑战大**:单机版、混合部署技术难度很高 +3. ⚠️ **团队能力要求**:需要多栈能力(Node.js + R + Python) + +**建议:** +- ✅ **分阶段实施**:不要试图一次性全做 +- ✅ **聚焦核心**:先做云端SaaS版的核心模块(DC、ASL) +- ✅ **验证市场**:单机版和混合部署可以等市场验证后再做 + +--- + +## 🏗️ Part 2:技术架构白皮书深度分析 + +### 2.1 核心架构设计评估 ⭐⭐⭐⭐⭐ **卓越** + +#### "演进式架构"战略 + +``` +核心战略:以"模块化单体"启动,快速迭代; + 并为未来向"微服务"架构的平滑过渡做好充分准备。 +``` + +**评价:⭐⭐⭐⭐⭐ 非常务实!** + +**为什么这个战略是正确的?** + +**传统错误做法:** +``` +❌ 一开始就设计微服务架构 + - 问题:过度设计,开发效率低 + - 风险:需求未验证,可能推倒重来 + - 成本:K8s、API网关、服务编排,复杂度10倍+ +``` + +**正确做法(白皮书方案):** +``` +✅ 阶段一(0-6个月):模块化单体 + - 快速迭代,验证市场 + - 但严格遵守"代码隔离"和"数据Schema隔离" + - 为未来拆分打基础 + +✅ 阶段二(6-18个月):首次拆分 + - 引入K8s和API网关 + - 拆分SSA和DC为独立微服务 + - 开发Electron单机版 + +✅ 阶段三(18个月+):全面微服务 + - 所有模块独立部署 + - 支持灵活组合和模块化售卖 +``` + +**关键纪律(阶段一必须遵守):** +1. ✅ **代码隔离**:严格按模块划分目录 +2. ✅ **数据隔离**:使用不同的Schema(如`uam_schema`, `stats_schema`, `dc_schema`) +3. ✅ **全员Docker**:从第一天起就用Docker和docker-compose + +**评价:这是最佳实践!** + +**对比现有系统:** +| 要求 | 现有系统 | 符合度 | +|------|---------|-------| +| 代码隔离 | ✅ 已按模块划分 | 90% | +| 数据隔离 | ❌ 所有表在同一Schema | 0% | +| Docker化 | ⚠️ 部分Docker | 50% | + +**建议:立即开始Schema隔离!这是关键!** + +--- + +### 2.2 服务模块划分评估 ⭐⭐⭐⭐⭐ **优秀** + +#### 平台层 vs 产品层 + +**平台层(通用模块):** +1. **用户与权限中心(UAM)** + - 管理用户、角色、租户、权限 + - Feature Flag控制 + - **评价:⭐⭐⭐⭐⭐ 必需且设计合理** + +2. **AI大模型网关(LLM Gateway)** + - 统一管理所有LLM调用 + - 根据版本动态切换模型 + - **评价:⭐⭐⭐⭐⭐ 核心中枢,必需** + +3. **账户/个人中心** + - 用户配置、订单、帮助文档 + - **评价:⭐⭐⭐⭐ 标配** + +4. **通知服务** + - 邮件、站内信 + - **评价:⭐⭐⭐ 辅助功能** + +**产品层(业务模块):** +1. SSA服务(智能统计) +2. AIA服务(AI问答) +3. ASL服务(AI文献) +4. PKB服务(知识库) +5. DC服务(数据清洗) + +**评价:⭐⭐⭐⭐⭐** +- ✅ **分层清晰**:平台层是地基,产品层是积木 +- ✅ **职责明确**:平台层统一管理,产品层独立运行 +- ✅ **易于扩展**:新增模块只需加产品层 + +**关键洞察:** +``` +"AI大模型网关"是核心创新: +- 统一入口:所有LLM调用都经过网关 +- 动态切换:根据用户版本、成本考量切换模型 +- 成本控制:统一监控、计费、限流 +- 未来扩展:易于接入新模型 + +这是商业模式的技术保障! +``` + +**对比现有系统:** +| 模块 | 现有系统 | 白皮书要求 | 差距 | +|------|---------|----------|------| +| UAM | ⚠️ 简化版 | ✅ Feature Flag | ⚠️ 需要增强 | +| LLM Gateway | ❌ 无 | ✅ 统一网关 | ❌ 需要新建 | +| 账户中心 | ✅ 有 | ✅ 有 | ✅ 符合 | +| 通知服务 | ❌ 无 | ⚠️ 可选 | ⚠️ 暂时不需要 | + +**建议:立即设计LLM Gateway,这是商业模式的基础!** + +--- + +### 2.3 技术栈评估 ⭐⭐⭐⭐⭐ **务实** + +#### 技术异构(Polyglot)策略 + +``` +核心原则:"用最合适的工具做最合适的事" +``` + +| 技术 | 用途 | 理由 | 评价 | +|------|------|------|------| +| **React/Vue** | 前端UI | 现代SPA框架,Web和Electron复用 | ✅ 正确 | +| **Node.js** | API网关、粘合层 | 高并发I/O,粘合R/Python成熟 | ✅ 正确 | +| **R语言** | 统计分析(SSA) | 统计分析的王者,通过Plumber暴露API | ✅ 正确 | +| **Python** | AI/数据清洗(DC) | Pandas、Polars、NLP生态强大 | ✅ 正确 | +| **PostgreSQL** | 结构化数据 | 可靠、成熟、支持JSON | ✅ 正确 | +| **Vector DB** | RAG向量检索 | PKB模块需要 | ✅ 正确 | +| **Docker + K8s** | 部署 | 现代云原生标准 | ✅ 正确 | +| **Electron** | 单机版 | 唯一支持Node.js后端的跨平台方案 | ✅ 正确 | + +**评价:⭐⭐⭐⭐⭐** +- ✅ **技术选型务实**:每个技术都有明确理由 +- ✅ **避免盲目统一**:不强求单一技术栈 +- ✅ **考虑了复用**:前端Web和Electron复用 + +**关键决策分析:** + +**1. 为什么用R语言做SSA?** +``` +R语言优势: +- 统计分析的王者,生态最丰富 +- 医学统计专家都用R +- 可以通过Plumber包暴露为REST API + +替代方案(为什么不用): +- Python:统计生态不如R完善 +- Node.js:统计能力严重不足 +- Java:复杂度高,医学统计生态弱 + +结论:R语言是唯一合理选择 +``` + +**2. 为什么用Python做DC?** +``` +Python优势: +- Polars性能极高(10-100倍于Pandas) +- NLP生态强大(spaCy等本地模型) +- 医学NER有成熟方案 + +替代方案(为什么不用): +- R语言:数据处理不如Python灵活 +- Node.js:无Polars,NLP生态弱 + +结论:Python是最佳选择 +``` + +**3. 为什么用Node.js做API网关?** +``` +Node.js优势: +- 高并发I/O性能 +- 粘合R/Python很成熟(child_process) +- Electron后端复用(关键!) + +替代方案(为什么不用): +- Java:性能好,但Electron不支持 +- Python:不适合做API网关 +- Go:性能好,但Electron不支持 + +结论:Node.js是唯一选择(因为Electron) +``` + +**关键洞察:** +``` +技术选型的核心约束是"单机版": +- 必须用Electron(唯一的跨平台桌面方案) +- Electron后端只能是Node.js +- 所以API网关/粘合层必须是Node.js +- R和Python作为子进程调用 + +这是一个务实的技术决策链! +``` + +**对比现有系统:** +| 技术 | 现有系统 | 白皮书要求 | 差距 | +|------|---------|----------|------| +| React | ✅ 已用 | ✅ React/Vue | ✅ 符合 | +| Node.js | ✅ 已用 | ✅ API网关 | ✅ 符合 | +| R语言 | ❌ 无 | ✅ SSA模块 | ❌ 需要引入 | +| Python | ✅ 微服务 | ✅ DC模块 | ✅ 符合 | +| PostgreSQL | ✅ 已用 | ✅ 已用 | ✅ 符合 | +| K8s | ❌ 无 | ⚠️ 阶段二 | ⚠️ 暂时不需要 | +| Electron | ❌ 无 | ⚠️ 阶段二 | ⚠️ 暂时不需要 | + +**建议:阶段一先引入R语言(SSA模块),K8s和Electron在阶段二。** + +--- + +### 2.4 DC模块深度解析评估 ⭐⭐⭐⭐⭐ **卓越** + +#### 两种方案对比 + +**方案一:服务器最优版(Cloud-Optimal)** +``` +技术栈:Python (FastAPI) + Polars + LLM API (Claude/GPT) + PostgreSQL + +工作流: +1. FastAPI接收多个Excel文件 +2. Polars在大内存中(64GB+)并行处理,数秒完成JOIN +3. 并行调用AI大模型(Claude 3)进行NER提取 +4. 结果存入PostgreSQL + +优势: +- 效率:Polars比Pandas快10-100倍 +- 准确率:Claude 3 NER准确率极高 +- 扩展性:服务器资源可弹性扩展 + +劣势: +- 成本:LLM API成本较高 +- 数据:假设数据已脱敏 +``` + +**方案二:单机版(Desktop-Offline)** +``` +技术栈:Electron (Node.js) + Python (Pandas) + SQLite + spaCy (本地NLP) + +工作流: +1. Electron主进程调度Python子进程 +2. Pandas分块读取,写入本地SQLite +3. 利用SQLite引擎做JOIN和GROUP BY(不在内存) +4. spaCy 100%本地运行,提取实体 +5. 结果写回SQLite,前端分页展示 + +优势: +- 隐私:100%本地,无任何数据上传 +- 成本:无API成本 +- 离线:完全离线可用 + +劣势: +- 性能:比服务器版慢 +- 准确率:spaCy不如Claude 3 +- 稳定性:用户电脑资源有限 +``` + +**评价:⭐⭐⭐⭐⭐ 方案设计非常合理!** + +**核心洞察:** +``` +两种方案的本质区别: +1. 服务器版:追求"极致效率和准确率" +2. 单机版:追求"100%隐私保护" + +这是根据用户需求(数据隐私)做出的正确技术决策! +``` + +**关键技术点:** + +**1. 为什么用Polars而不是Pandas?** +``` +Polars优势: +- 基于Rust,天生多线程并行 +- 内存效率极高 +- 速度是Pandas的10-100倍 + +示例: +- 200万行数据多表JOIN +- Pandas:30-60秒 +- Polars:3-5秒 + +结论:服务器版必须用Polars +``` + +**2. 为什么单机版用SQLite?** +``` +问题:200万行数据一次性读入内存会崩溃 + +SQLite方案: +- Pandas分块读取(chunksize=10000) +- 逐行写入SQLite +- 利用SQLite引擎做JOIN(而不是内存) +- 前端分页读取 + +关键:让SQLite做繁重工作,而不是内存 +这是低内存电脑处理大数据的唯一稳定方案 + +结论:单机版必须用SQLite +``` + +**3. 为什么单机版用spaCy?** +``` +问题:原始病例(PHI)严禁发送到云端API + +spaCy方案: +- 100%本地运行 +- 支持医学NER +- 零成本 + +劣势: +- 准确率有限(70-80%) +- 无法理解复杂语义 + +结论:隐私保护第一,准确率第二 +这是必要的妥协 +``` + +**对比现有系统:** +| 功能 | 现有系统 | 白皮书要求 | 差距 | +|------|---------|----------|------| +| 服务器版 | ❌ 无 | ✅ Polars + Claude | ❌ 需要新建 | +| 单机版 | ❌ 无 | ✅ SQLite + spaCy | ❌ 需要新建 | +| Python微服务 | ✅ 有(文档提取) | ✅ 有 | ⚠️ 需要扩展 | + +**建议:DC模块是核心竞争力,应优先开发!** + +--- + +### 2.5 多部署模式实现评估 ⭐⭐⭐⭐ **合理但复杂** + +#### 四种场景实现 + +**场景一:云端SaaS版 - ⭐⭐⭐ 中等难度** +``` +架构:所有服务和数据库都在云端,K8s管理 +客户端:用户通过浏览器访问 + +实现: +- 标准微服务架构 +- Docker容器化 +- K8s编排 + +评价:成熟方案,风险低 +``` + +**场景二:医院私有化部署 - ⭐⭐⭐⭐ 高难度** +``` +架构:数据敏感服务(SSA、DC)打包为Docker容器 +部署:K8s或K3s,在医院内网服务器"一键部署" +数据:100%留在医院内网 + +挑战: +1. 一键部署脚本复杂(需要适配不同环境) +2. 客户侧运维支持(网络问题、性能调优) +3. 版本升级管理(如何平滑升级?) +4. License管理(如何防盗版?) + +评价:技术可行,但工程量大 +``` + +**场景三:混合部署 - ⭐⭐⭐⭐⭐ 极高难度** +``` +架构: +- 医院内网:SSA服务(http://192.168.x.x/api/stats) +- 云端公网:ASL服务(https://api.yizhengxun.com/api/lit) +- 前端智能配置,动态路由 + +挑战: +1. 前端如何知道用户是混合部署? +2. 如何配置两套API地址? +3. 跨网络通信的安全性? +4. 用户体验如何保证?(延迟、错误处理) + +评价:技术难度极高,建议暂缓 +``` + +**场景四:医生单机版 - ⭐⭐⭐⭐⭐ 极高难度** +``` +架构重组: +云端版:[浏览器UI] <-> [Node.js API] <-> [R/Python服务] +单机版:[Electron UI] <-> [Node.js主进程] <-> [本地R/Python子进程] + +实现路径: +1. 新建Electron项目 +2. 移植前端(复用React编译后的静态文件) +3. 重组后端(Node.js逻辑移植到Electron主进程) +4. 打包R和Python运行时("全家桶"安装包) +5. 本地SQLite存储 + +挑战: +1. 打包体积:500MB+(包含R/Python运行时) +2. 跨平台兼容性:Windows + Mac两套方案 +3. 性能优化:低内存电脑不能崩溃 +4. 版本管理:如何自动更新? +5. License管理:如何防盗版? + +评价:技术难度极高,工程量巨大 +``` + +**总体评价:⭐⭐⭐⭐** + +**优点:** +- ✅ 四种场景覆盖全部市场需求 +- ✅ 技术方案务实可行 + +**问题:** +- ⚠️ **实施难度被低估**:单机版和混合部署极难 +- ⚠️ **工程量被低估**:4种部署=4套运维体系 +- ⚠️ **维护成本被低估**:4套环境=4倍维护成本 + +**建议:** +``` +分阶段实施(务实策略): + +阶段一(0-6个月):专注云端SaaS版 +- 目标:验证市场,快速迭代 +- 部署:单一云端环境 +- 收益:70%市场,开发效率高 + +阶段二(6-12个月):增加私有化部署 +- 前提:云端版已验证,有客户需求 +- 目标:攻克大医院市场 +- 收益:20%市场,但客单价高 + +阶段三(12-18个月):开发单机版 +- 前提:私有化部署已成熟 +- 目标:个人医生市场 +- 收益:10%市场,口碑传播 + +阶段四(18个月+):混合部署 +- 前提:有明确客户需求 +- 目标:高级需求,定制化 +- 收益:少量客户,超高客单价 + +不要一次性全做! +``` + +--- + +### 2.6 分阶段实施路线图评估 ⭐⭐⭐⭐⭐ **卓越** + +#### 三个阶段 + +**阶段一:云端MVP(0-6个月)- "模块化单体"** +``` +目标:快速上线云端SaaS版,验证市场 + +关键纪律(打地基): +1. 代码隔离:严格按模块划分 +2. 数据隔离:使用不同Schema +3. 全员Docker:从第一天起 + +评价:⭐⭐⭐⭐⭐ 非常务实! +``` + +**阶段二:首次拆分(6-18个月)** +``` +触发点:迎来第一个"私有化部署"客户,或"单机版"需求 + +架构: +1. 引入K8s +2. 引入API网关 +3. 拆分SSA和DC为独立微服务 +4. 开发Electron单机版 + +评价:⭐⭐⭐⭐⭐ 合理的演进路径 +``` + +**阶段三:全面微服务(18个月+)** +``` +目标:支持灵活的业务组合和团队扩张 + +架构:所有模块独立部署,"乐高积木式" + +评价:⭐⭐⭐⭐ 合理但不急 +``` + +**总体评价:⭐⭐⭐⭐⭐ 这是最佳实践!** + +**为什么这个路线图是正确的?** + +**1. 避免过度设计** +``` +❌ 错误做法:一开始就微服务 + - 需求未验证,可能推倒重来 + - 开发效率低,迭代慢 + - 复杂度高,维护困难 + +✅ 正确做法:先模块化单体 + - 快速迭代,验证市场 + - 开发效率高 + - 但为未来拆分打基础(关键!) +``` + +**2. 根据市场需求演进** +``` +阶段一:验证产品价值 +阶段二:响应客户需求(私有化/单机版) +阶段三:支持规模化扩张 + +这是精益创业的最佳实践! +``` + +**3. 降低技术风险** +``` +每个阶段都有明确的验收标准: +- 阶段一:产品可用,有用户 +- 阶段二:有私有化客户,有收入 +- 阶段三:用户规模扩大,需要微服务 + +逐步演进,风险可控 +``` + +**对比现有系统:** +| 阶段 | 现有系统 | 白皮书要求 | 符合度 | +|------|---------|----------|-------| +| 阶段一 | ✅ 已做 | ✅ 模块化单体 | 90% | +| 阶段二 | ❌ 未做 | ⚠️ 暂时不需要 | N/A | +| 阶段三 | ❌ 未做 | ⚠️ 暂时不需要 | N/A | + +**建议:继续阶段一,完善核心模块,等待市场信号再进入阶段二。** + +--- + +### 2.7 技术架构白皮书总结 + +**总体评价:⭐⭐⭐⭐ (4/5)** + +**核心优势:** +1. ✅ **演进式架构**:避免过度设计,务实可行 +2. ✅ **技术选型合理**:每个技术都有明确理由 +3. ✅ **分阶段实施**:降低风险,逐步演进 +4. ✅ **考虑了复用**:前端Web和Electron复用,Node.js粘合R/Python + +**需要注意的问题:** +1. ⚠️ **实施难度被低估**:单机版、混合部署、私有化都是极难的工程问题 +2. ⚠️ **时间估算偏乐观**:阶段一6个月可能紧张(7个模块) +3. ⚠️ **成本控制挑战**:单机版打包和维护成本可能被低估 +4. ⚠️ **R语言集成风险**:团队是否有R语言能力? + +**建议:** +- ✅ 继续遵循演进式架构 +- ✅ 但要更保守地估算时间和成本 +- ✅ 优先验证R语言集成的可行性 +- ✅ 单机版不要着急,等市场需求明确再做 + +--- + +## 🎯 Part 3:关键问题与风险评估 + +### 3.1 技术可行性风险 ⚠️ **中等风险** + +#### 风险1:R语言集成 + +**问题:** +- 现有团队是否有R语言能力? +- R语言如何与Node.js通信? +- 性能是否满足要求? + +**建议:** +``` +立即做技术验证(1-2天): + +1. 安装R语言和Plumber包 +2. 创建一个简单的R API: + - 读取CSV数据 + - 进行简单统计(t检验) + - 返回JSON结果 + +3. Node.js调用R API: + - 方案A:HTTP调用(Plumber API) + - 方案B:子进程调用(Rscript命令) + +4. 性能测试: + - 1000行数据处理时间 + - 10000行数据处理时间 + +如果验证通过,再开发SSA模块 +``` + +--- + +#### 风险2:Electron单机版打包 + +**问题:** +- 如何打包R和Python运行时? +- 安装包体积是否可接受?(可能500MB+) +- 跨平台兼容性如何? + +**建议:** +``` +暂缓开发,等阶段二再做: + +理由: +1. 单机版技术难度极高 +2. 市场需求未验证 +3. 维护成本高 + +但需要提前规划: +- 前端代码要考虑Electron复用 +- 后端逻辑要模块化,易于移植 +``` + +--- + +#### 风险3:数据Schema隔离 + +**问题:** +- 现有系统所有表在同一Schema +- 如何迁移到多Schema架构? +- 是否影响现有功能? + +**建议:** +``` +立即开始Schema隔离(优先级高): + +步骤: +1. 设计Schema结构: + - public(共用表:users, admin_logs) + - uam_schema(用户权限) + - aia_schema(AI问答) + - pkb_schema(知识库) + - asl_schema(AI文献) + - ssa_schema(统计分析) + - dc_schema(数据清洗) + +2. 创建迁移脚本 +3. 更新Prisma Schema +4. 测试验证 + +这是未来微服务拆分的生命线! +``` + +--- + +### 3.2 时间估算风险 ⚠️ **中高风险** + +#### 阶段一时间估算:6个月 + +**白皮书计划:** +``` +阶段一(0-6个月): +- 7个模块全部开发 +- 严格的代码和数据隔离 +- 云端SaaS版上线 +``` + +**实际情况(基于现有进度):** +``` +已用时:1个月 +已完成:3个模块(AIA、PKB、UAM) +剩余:4个模块(SSA、ST、DC、ASL) + +预估剩余时间: +- DC模块:2-3个月(最难) +- ASL模块:2个月(已有PRD) +- SSA模块:2个月(需要R语言) +- ST模块:1个月(相对简单) + +总计:7-8个月(乐观估计) +``` + +**风险:⚠️ 6个月可能完成不了!** + +**建议:** +``` +调整策略: + +方案A:延长时间到8-9个月 + - 更现实的估算 + - 降低团队压力 + +方案B:缩减范围 + - 阶段一只做核心模块(DC、ASL、部分SSA) + - ST模块和完整SSA放到阶段1.5 + +推荐:方案B,聚焦核心竞争力 +``` + +--- + +### 3.3 成本控制风险 ⚠️ **中等风险** + +#### LLM API成本 + +**DC模块NER提取:** +``` +服务器版(使用Claude 3): +- 单文档:1000-2000 tokens(病理报告) +- 50个文档:50,000-100,000 tokens +- 成本(Claude 3):$0.5-1(¥3.5-7) + +单机版(使用spaCy): +- 成本:¥0 +- 但准确率低 +``` + +**建议:** +``` +成本控制策略: + +1. 服务器版: + - 优先使用DeepSeek(¥1/百万tokens) + - Claude 3作为可选高级版 + +2. 单机版: + - 100%本地spaCy + - 提供"云端增强"选项(付费) +``` + +--- + +#### 单机版维护成本 + +**问题:** +- 打包复杂(R + Python + Node.js) +- 跨平台测试(Windows + Mac) +- 版本更新困难 +- 用户支持成本高 + +**估算:** +``` +单机版开发成本: +- 初次开发:2-3个月 +- 测试和优化:1个月 +- 总计:3-4个月 + +后续维护成本: +- 每次版本更新:1-2周 +- 用户支持:1人专职 +- 总计:持续投入 + +ROI(投资回报率): +- 单机版市场:10% +- 单价:可能更低(个人用户) +- 回报:不确定 + +风险:投入大,回报不确定 +``` + +**建议:** +``` +单机版策略: + +1. 暂缓开发,等市场验证 +2. 先做云端版和私有化部署 +3. 如果有大量单机版需求,再投入 + +但要保证: +- 前端代码可复用 +- 后端逻辑模块化 +``` + +--- + +### 3.4 团队能力风险 ⚠️ **中高风险** + +#### 多技术栈要求 + +**需要的技能:** +1. **Node.js/TypeScript** - API网关、粘合层 +2. **React** - 前端UI +3. **R语言** - SSA模块(统计分析) +4. **Python** - DC模块(数据清洗) +5. **Docker/K8s** - 部署(阶段二) +6. **Electron** - 单机版(阶段二) + +**风险:** +- ⚠️ R语言:团队可能不熟悉 +- ⚠️ K8s:需要DevOps能力 +- ⚠️ Electron:前端架构需要重组 + +**建议:** +``` +团队能力建设: + +1. 立即验证R语言: + - 安排1人学习R + Plumber + - 1周内完成技术验证 + +2. K8s延后: + - 阶段一不需要 + - 阶段二再学习或外包 + +3. Electron延后: + - 阶段一不需要 + - 可以找Electron专家咨询 + +4. 考虑招聘: + - 如果内部学习困难 + - 招聘有R语言经验的统计背景人才 +``` + +--- + +## 🎯 Part 4:关键建议与行动计划 + +### 4.1 总体建议 ⭐⭐⭐⭐⭐ + +**产品战略:完全正确,无需调整** +- ✅ 7大模块覆盖全流程 +- ✅ 4种部署满足全市场 +- ✅ 商业模式完整 + +**技术路径:基本正确,需要务实调整** +- ✅ 演进式架构 +- ✅ 技术选型合理 +- ⚠️ 但时间估算偏乐观 +- ⚠️ 实施难度被低估 + +--- + +### 4.2 立即行动(本周内) + +**行动1:R语言技术验证** +``` +目标:验证R语言集成可行性 +时间:1-2天 +人员:后端开发1人 + +步骤: +1. 安装R + Plumber +2. 创建简单统计API +3. Node.js调用测试 +4. 性能测试 + +验收标准: +- R API能正常返回统计结果 +- Node.js能成功调用 +- 性能满足要求(<5秒) +``` + +**行动2:Schema隔离设计** +``` +目标:设计多Schema架构 +时间:1天 +人员:架构师1人 + +步骤: +1. 设计Schema结构 +2. 规划迁移策略 +3. 评估影响范围 + +产出: +- Schema设计文档 +- 迁移计划 +``` + +**行动3:LLM Gateway设计** +``` +目标:设计AI大模型网关 +时间:2天 +人员:架构师1人 + +步骤: +1. 设计接口 +2. 规划Feature Flag集成 +3. 规划成本控制逻辑 + +产出: +- LLM Gateway设计文档 +- Feature Flag设计文档 +``` + +--- + +### 4.3 近期行动(本月内) + +**行动4:模块优先级确认** +``` +建议优先级: + +P0(必须立即做): +1. DC模块(数据清洗)- 核心竞争力 +2. LLM Gateway - 商业模式基础 +3. Schema隔离 - 未来架构基础 + +P1(近期做): +4. ASL模块(AI智能文献)- 已有PRD +5. Feature Flag系统 - 商业模式 + +P2(可延后): +6. SSA模块(智能统计分析)- 需要R语言 +7. ST模块(统计分析工具)- 相对简单 + +P3(阶段二): +8. K8s部署 +9. Electron单机版 +10. 私有化部署 +``` + +**行动5:文档更新** +``` +P0文档(立即更新): +1. 系统总体架构设计 - 基于白皮书 +2. 数据库设计文档 - 补充Schema隔离 +3. LLM Gateway设计文档 - 新建 +4. DC模块PRD - 新建 + +P1文档(近期更新): +5. Feature Flag设计文档 - 新建 +6. ASL模块PRD - 完善 +7. 前端总体架构设计 - 补充 +``` + +--- + +### 4.4 长期规划(3-6个月) + +**阶段一目标(调整后):** +``` +时间:6-8个月(更现实) + +核心目标: +1. 云端SaaS版上线 +2. 3个核心模块完成(DC、ASL、AIA优化) +3. Feature Flag系统完成 +4. LLM Gateway完成 + +次要目标: +5. SSA模块基础版 +6. ST模块部分功能 + +验收标准: +- 产品可用,有真实用户 +- 核心竞争力(DC、ASL)验证 +- 商业模式(Feature Flag)可运行 +``` + +**阶段二触发条件:** +``` +触发条件(满足任一): +1. 有客户明确要求私有化部署 +2. 有大量用户要求单机版 +3. 用户规模需要微服务扩展 + +触发后行动: +1. 引入K8s和API网关 +2. 拆分SSA和DC为独立微服务 +3. 开发Electron单机版(如有需求) +``` + +--- + +## 📊 Part 5:总结与结论 + +### 5.1 产品战略评价 ⭐⭐⭐⭐⭐ (5/5) + +**完全正确,无需调整!** + +1. ✅ **定位准确**:临床科研全流程,AI驱动 +2. ✅ **用户洞察深刻**:数据隐私是核心痛点 +3. ✅ **模块设计合理**:7大模块覆盖全流程,DC和ASL是核心竞争力 +4. ✅ **商业模式完整**:4种部署 + 模块化售卖 + 多版本 +5. ✅ **非功能需求完善**:部署、性能、平台兼容性都考虑到了 + +**这是一个深思熟虑、战略清晰的产品规划!** + +--- + +### 5.2 技术架构评价 ⭐⭐⭐⭐ (4/5) + +**基本正确,需要务实调整!** + +**优点:** +1. ✅ **演进式架构**:避免过度设计,务实可行 +2. ✅ **技术选型合理**:每个技术都有明确理由 +3. ✅ **分阶段实施**:降低风险,逐步演进 +4. ✅ **考虑了复用**:前端Web和Electron复用 + +**问题:** +1. ⚠️ **时间估算偏乐观**:阶段一6个月可能紧张,建议8-9个月 +2. ⚠️ **实施难度被低估**:单机版、混合部署、私有化都是极难的工程问题 +3. ⚠️ **成本控制挑战**:单机版打包和维护成本可能被低估 +4. ⚠️ **R语言风险**:需要立即验证集成可行性 + +--- + +### 5.3 核心建议 + +**建议1:继续遵循演进式架构,但要更保守地估算时间** +``` +✅ 采纳白皮书的分阶段实施策略 +✅ 但将阶段一时间从6个月调整为8-9个月 +✅ 聚焦核心模块(DC、ASL、AIA),其他模块可延后 +``` + +**建议2:立即做关键技术验证** +``` +✅ R语言集成验证(1-2天) +✅ Schema隔离设计(1天) +✅ LLM Gateway设计(2天) +``` + +**建议3:优先级排序,不要全面铺开** +``` +P0:DC模块、LLM Gateway、Schema隔离 +P1:ASL模块、Feature Flag系统 +P2:SSA模块、ST模块 +P3:K8s、Electron、私有化(阶段二) +``` + +**建议4:单机版和混合部署不要着急** +``` +⚠️ 单机版技术难度极高,维护成本高 +⚠️ 混合部署技术难度极高,需求不明确 +✅ 先做云端SaaS版和私有化部署 +✅ 等市场需求明确再做单机版和混合部署 +``` + +--- + +### 5.4 最终结论 + +**产品需求文档和技术架构白皮书总体上是优秀的!** + +**核心优势:** +1. ✅ 战略清晰,定位准确 +2. ✅ 技术路径务实可行 +3. ✅ 商业模式完整 +4. ✅ 分阶段实施降低风险 + +**需要调整的地方:** +1. ⚠️ 时间估算更保守(6个月 → 8-9个月) +2. ⚠️ 优先级更聚焦(先做核心模块) +3. ⚠️ 单机版和混合部署延后(阶段二或更晚) +4. ⚠️ 立即做关键技术验证(R语言、Schema隔离) + +**行动建议:** +- ✅ 立即开始R语言技术验证 +- ✅ 立即设计Schema隔离和LLM Gateway +- ✅ 优先开发DC和ASL模块 +- ✅ Feature Flag系统尽快实现 +- ✅ 单机版和混合部署暂缓,等市场信号 + +**这是一个具有巨大潜力的产品和合理的技术路径!** + +--- + +**评估完成日期:** 2025-11-06 +**评估人:** 技术架构师 +**下一步:** 开始关键技术验证和架构设计 + + + + + + + + + + + + + + + diff --git a/docs/00-项目概述/现有系统技术摸底报告.md b/docs/00-项目概述/现有系统技术摸底报告.md new file mode 100644 index 00000000..0f0b24bf --- /dev/null +++ b/docs/00-项目概述/现有系统技术摸底报告.md @@ -0,0 +1,1604 @@ +# AIclinicalresearch 现有系统技术摸底报告 + +> **报告日期:** 2025-11-06 +> **报告人:** 技术团队 +> **报告目的:** 全面梳理现有系统已完成的功能、技术架构、数据库设计和代码实现,为后续架构讨论提供基础 + +--- + +## 📊 执行摘要 + +### 项目基本信息 + +| 项目 | 信息 | +|------|------| +| **项目名称** | AI科研助手(AIclinicalresearch) | +| **开发周期** | 2025-10-10 至今(约1个月) | +| **开发模式** | 敏捷迭代,MVP优先 | +| **当前阶段** | 里程碑1-1.5完成,Phase2/3完成,稿件审查完成 | +| **代码量** | 后端~12,000行,前端~10,000行,Python微服务~2,100行 | +| **完成度** | 核心功能85%,基础架构100% | + +### 核心成果 + +✅ **已完成5大核心功能模块** +✅ **完整的技术架构体系** +✅ **成熟的AI集成能力** +✅ **完善的文档体系** + +--- + +## 🎯 已完成功能清单 + +### 1. **AI智能问答系统**(里程碑1核心) + +#### 1.1 智能体配置系统 +**完成时间:** Day 10-11 + +**核心能力:** +- ✅ 12个智能体的YAML配置框架 +- ✅ 动态Prompt模板加载(System + User) +- ✅ 变量替换和条件渲染 +- ✅ 热更新支持 +- ✅ 模型参数配置(temperature, maxTokens, topP) + +**已开发智能体:** +1. **选题评价智能体** - 四维度评价框架(创新性、临床价值、科学性、可行性) + +**配置文件:** +- `backend/config/agents.yaml` (348行) +- `backend/prompts/topic_evaluation_system.txt` (143行) +- `backend/prompts/topic_evaluation_user.txt` (12行) + +**技术架构:** +```typescript +// 智能体服务层 +backend/src/services/agentService.ts +- loadAgentConfig() // 加载智能体配置 +- getAgentById() // 获取智能体详情 +- renderPrompt() // 渲染Prompt模板 +- 支持变量替换:{{projectBackground}}, {{userInput}} +- 支持条件渲染:{{#if}}...{{/if}} +``` + +--- + +#### 1.2 多轮对话系统 +**完成时间:** Day 12-14 + +**核心能力:** +- ✅ 上下文组装(System + 历史 + 用户输入) +- ✅ 支持最近100条历史消息 +- ✅ 智能的上下文注入策略 +- ✅ 流式输出(SSE,打字机效果) +- ✅ 非流式输出(普通模式) + +**技术实现:** +```typescript +// 对话服务层 +backend/src/services/conversationService.ts (381行) + +// 上下文组装策略 +if (isFirstMessage) { + // 首次消息:完整模板(项目背景 + 用户问题 + 知识库) + userPrompt = renderTemplate({ + projectBackground, + userInput, + knowledgeBaseContext + }); +} else { + // 后续消息:只发送新内容 + userPrompt = knowledgeBaseContext + ? `${userInput}\n\n## 参考文献\n${knowledgeBaseContext}` + : userInput; +} + +// 流式输出 +for await (const chunk of adapter.chatStream(messages)) { + reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`); +} +``` + +**API端点:** +- `POST /api/v1/conversations/:id/messages/stream` - 发送消息(流式) +- `POST /api/v1/conversations/:id/messages` - 发送消息(非流式) +- `GET /api/v1/conversations/:id` - 获取对话详情 + +--- + +#### 1.3 LLM适配器系统 +**完成时间:** Day 12-13 + +**核心能力:** +- ✅ 统一的LLM接口抽象(ILLMAdapter) +- ✅ 3个LLM模型支持:DeepSeek-V3、Qwen3-72B、Qwen-Long +- ✅ 流式和非流式两种模式 +- ✅ 完整错误处理和Token统计 +- ✅ 工厂模式实现,易于扩展 + +**技术架构:** +```typescript +// 适配器接口 +backend/src/adapters/types.ts +interface ILLMAdapter { + chat(messages, options): Promise + chatStream(messages, options): AsyncGenerator +} + +// 具体实现 +backend/src/adapters/ +├── DeepSeekAdapter.ts (150行) - DeepSeek-V3 +├── QwenAdapter.ts (162行) - Qwen3-72B + Qwen-Long +└── LLMFactory.ts (75行) - 工厂类 +``` + +**模型对比:** +| 模型 | 定位 | 优势 | Token限制 | 成本 | +|------|------|------|----------|------| +| **DeepSeek-V3** | 主力 | 性价比极高 | 64K | ¥1/百万 | +| **Qwen3-72B** | 备用 | 中文理解好 | 128K | ¥4/百万 | +| **Qwen-Long** | 全文 | 1M超长上下文 | 1M | ¥5/百万 | + +--- + +#### 1.4 项目管理系统 +**完成时间:** Day 8-9 + +**核心能力:** +- ✅ 项目CRUD(创建、读取、更新、删除) +- ✅ 项目背景管理(动态注入到AI对话) +- ✅ 软删除机制 +- ✅ 用户项目关联 + +**数据模型:** +```prisma +model Project { + id String @id @default(uuid()) + userId String + name String + description String? @db.Text // 项目背景 + background String? @db.Text // 详细背景 + researchType String? // 研究类型 + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? // 软删除 + + user User @relation(fields: [userId], references: [id]) + conversations Conversation[] + + @@index([userId, deletedAt]) +} +``` + +**API端点:** +- `POST /api/v1/projects` - 创建项目 +- `GET /api/v1/projects` - 获取项目列表 +- `GET /api/v1/projects/:id` - 获取项目详情 +- `PUT /api/v1/projects/:id` - 更新项目 +- `DELETE /api/v1/projects/:id` - 删除项目 + +--- + +### 2. **个人知识库系统**(里程碑1核心) + +#### 2.1 知识库管理 +**完成时间:** Day 18-22 + +**核心能力:** +- ✅ 知识库CRUD +- ✅ 文档上传(PDF、Word、TXT、Markdown) +- ✅ 文档状态追踪(uploading → parsing → indexing → completed/error) +- ✅ 配额管理(每用户3个知识库,每库50个文档) +- ✅ 文件格式和大小限制(最大10MB) + +**技术集成:** +- **Dify平台** - RAG知识库管理 +- **Qdrant向量数据库** - Dify内置 +- **多租户架构** - 每个知识库独立Dataset + +**数据模型:** +```prisma +model KnowledgeBase { + id String @id @default(uuid()) + userId String + name String + description String? @db.Text + difyDatasetId String @unique // 映射到Dify Dataset + documentCount Int @default(0) + totalSize BigInt @default(0) + totalTokens BigInt @default(0) // Phase2新增 + tokenLimit BigInt @default(980000) // Phase2新增 + documentLimit Int @default(50) // Phase2新增 + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation(fields: [userId], references: [id]) + documents Document[] + + @@index([userId]) +} + +model Document { + id String @id @default(uuid()) + knowledgeBaseId String + name String + originalName String + fileType String + fileSize BigInt + difyDocumentId String @unique // 映射到Dify Document + status String // uploading/parsing/indexing/completed/error + errorMessage String? @db.Text + + // Phase2新增:全文存储 + fullText String? @db.Text + tokenCount Int? + charCount Int? + extractionMethod String? // nougat/pymupdf/mammoth + extractionQuality Float? + detectedLanguage String? // chinese/english + + uploadedAt DateTime @default(now()) + processedAt DateTime? + + knowledgeBase KnowledgeBase @relation(fields: [knowledgeBaseId], references: [id]) + + @@index([knowledgeBaseId, status]) +} +``` + +**API端点:** +- `POST /api/v1/knowledge-bases` - 创建知识库 +- `GET /api/v1/knowledge-bases` - 获取知识库列表 +- `GET /api/v1/knowledge-bases/:id` - 获取详情 +- `PUT /api/v1/knowledge-bases/:id` - 更新知识库 +- `DELETE /api/v1/knowledge-bases/:id` - 删除知识库 +- `POST /api/v1/knowledge-bases/:kbId/documents` - 上传文档 +- `GET /api/v1/knowledge-bases/:kbId/documents` - 获取文档列表 +- `DELETE /api/v1/documents/:id` - 删除文档 +- `POST /api/v1/documents/:id/reprocess` - 重新处理文档 + +--- + +#### 2.2 RAG检索系统(里程碑1.5优化) +**完成时间:** Day 23-27 + +**核心能力:** +- ✅ 语义检索(Dify API) +- ✅ 多知识库联合检索 +- ✅ @知识库功能(前端下拉选择) +- ✅ 检索结果注入到LLM上下文 +- ✅ **智能引用系统**(100%准确溯源) + +**优化成果:** +| 指标 | 优化前 | 优化后 | 提升 | +|------|--------|--------|------| +| 检索数量 | 3 chunks | 15 chunks | **5倍** | +| Chunk大小 | 500 tokens | 1500 tokens | **3倍** | +| **总覆盖** | **1,500 tokens** | **22,500 tokens** | **15倍** | +| 覆盖页数 | ~1页 | ~15-20页 | **15-20倍** | +| 覆盖率 | ~5% | ~40-50% | **8-10倍** | + +**智能引用系统:** +```typescript +// 后端自动收集引用信息 +interface Citation { + index: number; + documentName: string; + segmentIndex: number; + score: number; + content: string; +} + +// AI回答格式 +根据[来源1]和[来源5]的研究,阿尔兹海默病主要特征包括: +1. 记忆力减退[来源1][来源3] +2. 认知功能下降[来源5] + +--- +📚 **参考文献** +[1] 📄 **阿尔兹海默综述2023.pdf** - 第3段 (相关度95%) + "阿尔兹海默病是一种神经退行性疾病..." +``` + +**前端视觉优化:** +- `[来源N]`:蓝色高亮badge(#1890ff) +- 鼠标悬停:背景变深(#bae7ff) +- 点击跳转:平滑滚动到引用详情 +- 详情闪烁:黄色高亮2秒(#fffbe6) + +--- + +#### 2.3 全文阅读模式(Phase2) +**完成时间:** Day 28-32 + +**核心能力:** +- ✅ **Python微服务**:文档提取服务 +- ✅ **多格式支持**:PDF、Docx、Txt +- ✅ **智能提取**:Nougat(英文学术) + PyMuPDF(兜底+中文) + Mammoth(Docx) +- ✅ **语言检测**:自动识别中英文,优化提取策略 +- ✅ **Token管理**:精确计数,双重限制(50文件 + 980K tokens) +- ✅ **全文存储**:数据库保存完整文本 +- ✅ **智能文档选择**:基于Token容量的智能推荐 + +**技术架构:** +``` +┌─────────────────────────────────────────────┐ +│ Frontend (React) │ +│ - 文档上传 │ +│ - 容量显示(双进度条) │ +│ - 实时进度 │ +└──────────────┬──────────────────────────────┘ + │ REST API +┌──────────────▼──────────────────────────────┐ +│ Backend (Node.js + Fastify) │ +│ - ExtractionClient │ +│ - TokenService │ +│ - 智能文档选择 │ +└──────────────┬──────────────────────────────┘ + │ HTTP +┌──────────────▼──────────────────────────────┐ +│ Python Microservice (FastAPI) │ +│ ├── PyMuPDF (快速,中文友好) │ +│ ├── Nougat (学术论文,高质量) │ +│ ├── Mammoth (Docx → Markdown) │ +│ ├── Language Detector │ +│ └── Quality Evaluator │ +└─────────────────────────────────────────────┘ +``` + +**提取策略:** +```typescript +// 语言检测 → 选择提取方法 +if (language === 'chinese') { + // 中文PDF:PyMuPDF(快速) + text = await pymupdf.extract(pdf); +} else { + // 英文PDF:Nougat(高质量) + 降级PyMuPDF + try { + text = await nougat.extract(pdf); + if (quality < 0.7) throw new Error('Quality too low'); + } catch { + // 自动降级 + text = await pymupdf.extract(pdf); + } +} +``` + +**Python微服务文件结构:** +``` +extraction_service/ +├── main.py (509行) - FastAPI主服务 +├── services/ +│ ├── pdf_extractor.py (242行) - PDF提取总协调 +│ ├── pdf_processor.py (280行) - PyMuPDF实现 +│ ├── language_detector.py (120行) - 语言检测 +│ ├── nougat_extractor.py (242行) - Nougat实现 +│ ├── docx_extractor.py (253行) - Docx提取 +│ └── txt_extractor.py (316行) - Txt提取(多编码) +├── requirements.txt +└── README.md +``` + +**Backend集成:** +```typescript +// ExtractionClient.ts (268行) +export class ExtractionClient { + async extractDocument( + filePath: string, + fileType: string + ): Promise { + // 调用Python微服务 + const response = await axios.post( + `${this.baseUrl}/api/extract/${fileType}`, + formData + ); + return response.data; + } +} + +// TokenService.ts (243行) +export class TokenService { + calculateTokens(text: string): number { + const encoder = encoding_for_model("gpt-4"); + const tokens = encoder.encode(text); + return tokens.length; + } + + canUploadDocument( + kbId: string, + newDocTokens: number + ): Promise { + // 检查文档数量(≤50) + // 检查Token容量(≤980K) + } +} +``` + +**API端点:** +- `POST /api/v1/knowledge-bases/:id/document-selection` - 智能文档选择 +- `GET /api/v1/knowledge-bases/:id/capacity` - 获取容量信息 + +--- + +### 3. **批处理模式**(Phase3) + +#### 3.1 核心能力 +**完成时间:** Day 29(6小时) + +**定位:** 任务式交互(非对话),结构化数据提取器 + +**核心功能:** +- ✅ **预设模板**:临床研究信息提取(8字段) +- ✅ **自定义模板**:用户提示词 → 文本块显示 +- ✅ **批量处理**:3-50篇文献 +- ✅ **固定3并发**(p-queue) +- ✅ **失败重试机制** +- ✅ **Excel导出**(双Sheet设计) + +**数据模型:** +```prisma +model BatchTask { + id String @id @default(uuid()) + userId String + name String + templateType String // preset/custom + templateId String? // 预设模板ID + customPrompt String? @db.Text + + modelType String // deepseek-v3/qwen3/qwen-long + concurrency Int @default(3) + + status String // processing/completed/failed + totalCount Int + completedCount Int @default(0) + failedCount Int @default(0) + + startedAt DateTime? + completedAt DateTime? + duration Int? // 秒 + + user User @relation(fields: [userId], references: [id]) + results BatchResult[] + + @@index([userId, status]) +} + +model BatchResult { + id String @id @default(uuid()) + taskId String + documentId String // 关联Document + documentName String + + status String // success/failed + extractedData Json? // 提取的结构化数据 + rawOutput String? @db.Text + errorMessage String? @db.Text + + processingTime Int? // 毫秒 + tokenUsage Int? + + task BatchTask @relation(fields: [taskId], references: [id]) + + @@index([taskId, status]) +} +``` + +**预设模板(临床研究信息提取):** +| 字段 | 类型 | 说明 | +|------|------|------| +| research_purpose | text | 研究目的 | +| research_design | text | 研究设计(RCT/队列研究等) | +| research_subjects | text | 研究对象(纳入/排除标准) | +| sample_size | **text** | 样本量(保留原文描述) | +| intervention_group | text | 干预组 | +| control_group | text | 对照组 | +| results_data | longtext | 结果及数据 | +| oxford_level | text | 牛津评级(1a/1b/2a/2b/3a/3b/4/5) | + +**技术实现:** +```typescript +// batchService.ts (421行) +export class BatchService { + async executeBatchTask( + taskId: string, + documentIds: string[], + templateType: 'preset' | 'custom', + options: BatchOptions + ): Promise { + // 1. 加载模板或自定义Prompt + const template = await this.loadTemplate(templateType, options); + + // 2. 并发执行(固定3并发) + const queue = new PQueue({ concurrency: 3 }); + const promises = documentIds.map(docId => + queue.add(() => this.processDocument(docId, template)) + ); + + // 3. 等待所有任务完成 + const results = await Promise.allSettled(promises); + + // 4. 统计和保存 + await this.updateTaskStatistics(taskId, results); + } + + async processDocument( + docId: string, + template: Template + ): Promise { + // 1. 获取文档全文 + const document = await this.getDocument(docId); + + // 2. 构造LLM消息 + const messages = [ + { role: 'system', content: template.systemPrompt }, + { role: 'user', content: `${template.userPrompt}\n\n${document.fullText}` } + ]; + + // 3. 调用LLM + const response = await llm.chat(messages); + + // 4. 解析JSON(预设模板)或保存文本(自定义) + const extractedData = template.type === 'preset' + ? this.parseJSON(response.content) + : response.content; + + return { + documentId: docId, + status: 'success', + extractedData, + rawOutput: response.content + }; + } +} + +// jsonParser.ts (145行) - 容错的JSON解析 +export function extractJSON(text: string): object { + // 支持多种格式 + // 1. 纯JSON:{ "key": "value" } + // 2. 带前言:这是结果:\n{ ... } + // 3. 代码块:```json\n{ ... }\n``` + // 4. 带后缀:{ ... }\n\n以上是结果 +} +``` + +**API端点:** +- `POST /api/v1/batch/execute` - 执行批处理任务 +- `GET /api/v1/batch/tasks/:taskId` - 获取任务状态 +- `GET /api/v1/batch/tasks/:taskId/results` - 获取任务结果 +- `POST /api/v1/batch/tasks/:taskId/retry-failed` - 重试失败项 +- `GET /api/v1/batch/templates` - 获取所有模板 + +**效率提升:** +- 手动处理:10篇 × 10分钟 = 100分钟 +- 批处理模式:10篇 × 平均20秒 = ~7分钟 +- **提升约14倍效率** 🚀 + +--- + +### 4. **稿件审查功能**(Day 30独立开发) + +#### 4.1 核心能力 +**完成时间:** Day 30(1天) + +**定位:** 独立的稿件智能审查系统 + +**核心功能:** +- ✅ **双维度评估**:稿约规范性(11项) + 方法学评估(3部分) +- ✅ **智能分析**:基于真实期刊标准(中华医学超声杂志) +- ✅ **完整流程**:上传Word → 提取文本 → AI评估 → 生成报告 → 导出PDF +- ✅ **用户体验**:渐变卡片、拖拽上传、实时进度、颜色编码 + +**评估标准:** + +**稿约规范性评估(11项):** +1. 文题(Title) +2. 作者(Authors) +3. 中文摘要(Chinese Abstract) +4. 英文摘要(English Abstract) +5. 中文关键词(Chinese Keywords) +6. 英文关键词(English Keywords) +7. 正文(Main Text) +8. 参考文献(References) +9. 图表(Figures and Tables) +10. 利益冲突(Conflict of Interest) +11. 伦理审查(Ethics Approval) + +**方法学评估(3部分):** +1. 科研设计(Research Design) +2. 统计方法(Statistical Methods) +3. 统计分析(Statistical Analysis) + +**数据模型:** +```prisma +model ReviewTask { + id String @id @default(uuid()) + userId String + fileName String + fileType String + fileSize BigInt + + status String // processing/completed/failed + modelType String // deepseek-v3/qwen3/qwen-long + + // 评估结果 + editorialScore Float? // 稿约规范性总分 + editorialResult Json? // 详细评估结果 + methodologyScore Float? // 方法学总分 + methodologyResult Json? // 详细评估结果 + + errorMessage String? @db.Text + processingTime Int? // 毫秒 + + createdAt DateTime @default(now()) + completedAt DateTime? + + user User @relation(fields: [userId], references: [id]) + + @@index([userId, status]) +} +``` + +**Prompt设计:** +```typescript +// 稿约规范性评估Prompt (210行) +// prompts/editorial_review_system.txt + +您是一位专业的医学期刊编辑,您的任务是按照《中华医学超声杂志》的稿约要求,对提交的稿件进行规范性评估。 + +评估维度: +1. 文题 + - 检查点:准确性、简明性、规范性 + - 评分标准:0-10分 + +2. 作者 + - 检查点:作者信息完整性、排序、单位标注 + - 评分标准:0-10分 + +...(共11个维度) + +输出格式(JSON): +{ + "overall_score": 85.5, + "items": [ + { + "dimension": "文题", + "score": 9.0, + "status": "符合", + "issues": [], + "suggestions": [] + }, + ... + ] +} +``` + +```typescript +// 方法学评估Prompt (231行) +// prompts/methodology_review_system.txt + +您是一位专业的医学统计学专家,您的任务是评估医学研究稿件的方法学质量。 + +评估维度: +1. 科研设计 + - 研究类型识别 + - 设计合理性 + - 样本量计算 + +2. 统计方法 + - 方法选择 + - 参数设置 + - 假设检验 + +3. 统计分析 + - 结果呈现 + - 图表规范 + - 结论合理性 +``` + +**技术实现:** +```typescript +// reviewService.ts (437行) +export class ReviewService { + async reviewManuscript( + userId: string, + file: File, + modelType: ModelType + ): Promise { + // 1. 上传文件并创建任务 + const task = await this.createTask(userId, file); + + // 2. 提取文档全文(调用Python微服务) + const fullText = await extractionClient.extractDocument(file); + + // 3. 稿约规范性评估 + const editorialResult = await this.evaluateEditorial(fullText, modelType); + + // 4. 方法学评估 + const methodologyResult = await this.evaluateMethodology(fullText, modelType); + + // 5. 保存结果 + await this.updateTaskResult(task.id, { + editorialScore: editorialResult.overall_score, + editorialResult: editorialResult, + methodologyScore: methodologyResult.overall_score, + methodologyResult: methodologyResult + }); + + return task; + } +} +``` + +**API端点:** +- `POST /api/v1/review/upload` - 上传稿件并开始审查 +- `GET /api/v1/review/tasks/:taskId` - 查询任务状态 +- `GET /api/v1/review/tasks/:taskId/report` - 获取完整报告 +- `GET /api/v1/review/tasks` - 获取任务列表(分页) +- `DELETE /api/v1/review/tasks/:taskId` - 删除任务 + +**前端组件:** +```tsx +// ReviewPage.tsx (625行) - 主页面 +// 包含:上传区 + 进度条 + 报告展示 + PDF导出 + +// ScoreCard.tsx - 分数卡片(颜色编码) +// EditorialReview.tsx - 稿约规范性评估详情 +// MethodologyReview.tsx - 方法学评估详情 +``` + +--- + +## 🏗️ 技术架构总览 + +### 1. **技术栈** + +#### 后端技术栈 +```typescript +核心框架: +- Node.js 18+ +- TypeScript 5.x +- Fastify 4.x (高性能HTTP框架) + +数据层: +- PostgreSQL 15+ (关系数据库) +- Prisma 5.x (ORM) +- Redis (缓存,待集成) + +AI集成: +- DeepSeek-V3 (主力LLM,¥1/百万tokens) +- Qwen3-72B (备用LLM,¥4/百万tokens) +- Qwen-Long (超长上下文,1M tokens) +- Dify (RAG平台,向量检索) + +文件处理: +- @fastify/multipart (文件上传) +- @dqbd/tiktoken (Token计数) +- axios (HTTP客户端) + +并发控制: +- p-queue (队列管理) + +配置管理: +- js-yaml (YAML解析) +- dotenv (环境变量) +``` + +#### 前端技术栈 +```typescript +核心框架: +- React 18 +- TypeScript 5.x +- Vite 5.x (构建工具) + +UI组件: +- Ant Design 5.x (主要UI库) +- TailwindCSS (工具类CSS) +- React Router 6 (路由) + +状态管理: +- Zustand (轻量级状态管理) + +数据请求: +- Axios (HTTP客户端) +- EventSource (SSE流式输出) + +辅助工具: +- react-markdown (Markdown渲染) +- rehype-raw (HTML渲染) +- html2canvas + jsPDF (PDF导出) +- xlsx (Excel导出) +``` + +#### Python微服务 +```python +核心框架: +- FastAPI (高性能异步框架) +- uvicorn (ASGI服务器) + +PDF处理: +- PyMuPDF (fitz) - 快速通用 +- pdfplumber - 表格提取 +- nougat-ocr - 学术论文高质量提取 + +Docx处理: +- mammoth - 转Markdown +- python-docx - 结构化读取 + +文本处理: +- chardet - 编码检测 +- langdetect - 语言检测 + +工具: +- python-multipart - 文件上传 +- python-dotenv - 配置管理 +``` + +--- + +### 2. **数据库设计** + +#### 核心表结构(13个表) + +**用户相关(1个表):** +```prisma +model User { + id String @id @default(uuid()) + email String @unique + password String // bcrypt加密 + name String? + avatarUrl String? + role String @default("user") + status String @default("active") + kbQuota Int @default(3) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} +``` + +**项目管理(1个表):** +```prisma +model Project { + id String @id @default(uuid()) + userId String + name String + description String? @db.Text + background String? @db.Text + researchType String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? + + @@index([userId, deletedAt]) +} +``` + +**对话系统(2个表):** +```prisma +// 项目对话 +model Conversation { + id String @id @default(uuid()) + projectId String + agentId String + title String? + metadata Json? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? +} + +model Message { + id String @id @default(uuid()) + conversationId String + role String // user/assistant + content String @db.Text + model String? // deepseek-v3/qwen3/qwen-long + metadata Json? // 知识库引用等 + createdAt DateTime @default(now()) +} + +// 通用对话(智能问答) +model GeneralConversation { + id String @id @default(uuid()) + userId String + title String? + metadata Json? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? +} + +model GeneralMessage { + id String @id @default(uuid()) + conversationId String + role String + content String @db.Text + model String? + metadata Json? + createdAt DateTime @default(now()) +} +``` + +**知识库系统(2个表):** +```prisma +model KnowledgeBase { + id String @id @default(uuid()) + userId String + name String + description String? @db.Text + difyDatasetId String @unique + documentCount Int @default(0) + totalSize BigInt @default(0) + totalTokens BigInt @default(0) + tokenLimit BigInt @default(980000) + documentLimit Int @default(50) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([userId]) +} + +model Document { + id String @id @default(uuid()) + knowledgeBaseId String + name String + originalName String + fileType String + fileSize BigInt + difyDocumentId String @unique + status String + errorMessage String? @db.Text + + // 全文存储 + fullText String? @db.Text + tokenCount Int? + charCount Int? + extractionMethod String? + extractionQuality Float? + detectedLanguage String? + + uploadedAt DateTime @default(now()) + processedAt DateTime? + + @@index([knowledgeBaseId, status]) +} +``` + +**批处理系统(2个表):** +```prisma +model BatchTask { + id String @id @default(uuid()) + userId String + name String + templateType String + templateId String? + customPrompt String? @db.Text + modelType String + concurrency Int @default(3) + status String + totalCount Int + completedCount Int @default(0) + failedCount Int @default(0) + startedAt DateTime? + completedAt DateTime? + duration Int? + + @@index([userId, status]) +} + +model BatchResult { + id String @id @default(uuid()) + taskId String + documentId String + documentName String + status String + extractedData Json? + rawOutput String? @db.Text + errorMessage String? @db.Text + processingTime Int? + tokenUsage Int? + + @@index([taskId, status]) +} +``` + +**稿件审查(1个表):** +```prisma +model ReviewTask { + id String @id @default(uuid()) + userId String + fileName String + fileType String + fileSize BigInt + status String + modelType String + editorialScore Float? + editorialResult Json? + methodologyScore Float? + methodologyResult Json? + errorMessage String? @db.Text + processingTime Int? + createdAt DateTime @default(now()) + completedAt DateTime? + + @@index([userId, status]) +} +``` + +**运营管理(1个表):** +```prisma +model AdminLog { + id String @id @default(uuid()) + adminId String + action String + targetType String? + targetId String? + details Json? + ipAddress String? + userAgent String? + createdAt DateTime @default(now()) + + @@index([adminId, createdAt]) + @@index([action, createdAt]) +} +``` + +**总统计:13个表,~80个字段** + +--- + +### 3. **代码结构** + +#### 后端代码结构 +``` +backend/ +├── config/ +│ └── agents.yaml (348行) - 智能体配置 +├── prompts/ (441行) +│ ├── topic_evaluation_system.txt (143行) +│ ├── topic_evaluation_user.txt (12行) +│ ├── editorial_review_system.txt (210行) +│ └── methodology_review_system.txt (231行) +├── src/ +│ ├── adapters/ (387行) - LLM适配器 +│ │ ├── types.ts (57行) +│ │ ├── DeepSeekAdapter.ts (150行) +│ │ ├── QwenAdapter.ts (162行) +│ │ └── LLMFactory.ts (18行) +│ ├── clients/ (~550行) - 外部服务客户端 +│ │ ├── DifyClient.ts (282行) +│ │ └── ExtractionClient.ts (268行) +│ ├── config/ (56行) +│ │ └── env.ts - 环境配置 +│ ├── controllers/ (~2,700行) - 控制器 +│ │ ├── projectController.ts +│ │ ├── agentController.ts (197行) +│ │ ├── conversationController.ts (247行) +│ │ ├── chatController.ts +│ │ ├── knowledgeBaseController.ts +│ │ ├── documentController.ts +│ │ ├── batchController.ts (429行) +│ │ └── reviewController.ts (265行) +│ ├── services/ (~3,000行) - 业务逻辑 +│ │ ├── projectService.ts +│ │ ├── agentService.ts (232行) +│ │ ├── conversationService.ts (381行) +│ │ ├── knowledgeBaseService.ts +│ │ ├── documentService.ts +│ │ ├── batchService.ts (421行) +│ │ ├── reviewService.ts (437行) +│ │ └── tokenService.ts (243行) +│ ├── templates/ (145行) +│ │ └── clinicalResearch.ts - 批处理预设模板 +│ ├── utils/ (145行) +│ │ └── jsonParser.ts - 容错JSON解析 +│ ├── routes/ (~250行) - 路由 +│ │ ├── projects.ts +│ │ ├── agents.ts +│ │ ├── conversations.ts +│ │ ├── chatRoutes.ts +│ │ ├── knowledgeBases.ts +│ │ ├── batchRoutes.ts +│ │ └── reviewRoutes.ts +│ ├── middleware/ (~50行) +│ │ └── validateProject.ts +│ └── index.ts (主入口) +├── prisma/ +│ ├── schema.prisma (~600行) +│ └── migrations/ +├── package.json +└── tsconfig.json + +总计:~12,000行代码 +``` + +#### 前端代码结构 +``` +frontend/ +├── src/ +│ ├── api/ (~1,500行) - API封装 +│ │ ├── projectApi.ts +│ │ ├── agentApi.ts (76行) +│ │ ├── conversationApi.ts +│ │ ├── chatApi.ts +│ │ ├── knowledgeBaseApi.ts +│ │ ├── batchApi.ts (147行) +│ │ ├── reviewApi.ts (319行) +│ │ └── request.ts (axios配置) +│ ├── components/ (~5,000行) +│ │ ├── chat/ (~3,000行) +│ │ │ ├── MessageList.tsx (含智能引用) +│ │ │ ├── MessageInput.tsx (含@知识库) +│ │ │ ├── ModelSelector.tsx +│ │ │ ├── ModeSelector.tsx +│ │ │ ├── FullTextMode.tsx +│ │ │ ├── DeepReadMode.tsx +│ │ │ ├── BatchMode.tsx +│ │ │ ├── TaskDefinition.tsx +│ │ │ ├── DocumentSelection.tsx +│ │ │ ├── BatchProgress.tsx +│ │ │ ├── PresetTable.tsx +│ │ │ ├── CustomTable.tsx +│ │ │ ├── BatchResults.tsx +│ │ │ └── CapacityIndicator.tsx +│ │ ├── knowledge/ (~800行) +│ │ │ ├── KnowledgeBaseList.tsx +│ │ │ ├── DocumentList.tsx +│ │ │ ├── DocumentUpload.tsx +│ │ │ ├── CreateKBDialog.tsx +│ │ │ └── EditKBDialog.tsx +│ │ ├── review/ (~500行) +│ │ │ ├── ScoreCard.tsx +│ │ │ ├── EditorialReview.tsx +│ │ │ └── MethodologyReview.tsx +│ │ ├── ProjectSelector.tsx +│ │ ├── CreateProjectDialog.tsx +│ │ └── EditProjectDialog.tsx +│ ├── pages/ (~2,500行) +│ │ ├── HomePage.tsx +│ │ ├── AgentChatPage.tsx +│ │ ├── ChatPage.tsx +│ │ ├── KnowledgePage.tsx +│ │ ├── ReviewPage.tsx (625行) +│ │ └── HistoryPage.tsx +│ ├── stores/ (~400行) +│ │ ├── useProjectStore.ts +│ │ └── useKnowledgeBaseStore.ts +│ ├── hooks/ (~400行) +│ │ ├── useChatMode.ts +│ │ ├── useDeepReadState.ts +│ │ └── useBatchTask.ts (198行) +│ ├── layouts/ (~200行) +│ │ └── MainLayout.tsx +│ ├── types/ (~300行) +│ │ ├── chat.ts +│ │ └── index.ts +│ └── App.tsx +├── package.json +└── vite.config.ts + +总计:~10,000行代码 +``` + +#### Python微服务结构 +``` +extraction_service/ +├── services/ +│ ├── pdf_extractor.py (242行) +│ ├── pdf_processor.py (280行) +│ ├── language_detector.py (120行) +│ ├── nougat_extractor.py (242行) +│ ├── docx_extractor.py (253行) +│ └── txt_extractor.py (316行) +├── main.py (509行) +├── requirements.txt +└── README.md + +总计:~2,100行代码 +``` + +--- + +## 📊 数据流架构 + +### 1. **AI对话流程** + +``` +用户输入 + ↓ +前端 ChatPage/AgentChatPage + ↓ POST /api/v1/conversations/:id/messages/stream +后端 conversationController + ↓ +conversationService + ├→ agentService.getAgent() - 获取智能体配置 + ├→ projectService.getProject() - 获取项目背景 + ├→ conversationService.getHistory() - 获取历史对话 + ├→ knowledgeBaseService.search() - 知识库检索(可选) + └→ 组装上下文(System + 历史 + 项目背景 + 知识库 + 用户输入) + ↓ +LLMFactory.getAdapter(modelType) + ↓ +DeepSeekAdapter / QwenAdapter + ↓ HTTP (SSE Stream) +OpenAI API / DashScope API + ↓ (流式返回) +后端 conversationService + ├→ 实时写入数据库(messages表) + └→ SSE流式返回前端 + ↓ +前端 MessageList + └→ 实时渲染(打字机效果) +``` + +--- + +### 2. **知识库RAG流程** + +``` +用户上传文档 + ↓ +前端 DocumentUpload + ↓ POST /api/v1/knowledge-bases/:id/documents +后端 documentController + ↓ +documentService.uploadDocument() + ├→ 保存文件到临时目录 + ├→ 调用Python微服务提取文本 (Phase2) + ├→ 计算Token数 (Phase2) + ├→ 检查容量限制 (Phase2) + ├→ 上传到Dify (RAG索引) + ├→ 保存全文到数据库 (Phase2) + └→ 更新知识库统计 + ↓ +后台轮询Dify处理状态 + └→ 更新document.status + +--- + +用户@知识库提问 + ↓ +前端 MessageInput (选择知识库) + ↓ POST /api/v1/conversations/:id/messages/stream +后端 conversationService + ↓ +knowledgeBaseService.search(kbIds, query) + ├→ 对每个知识库调用Dify检索API + ├→ 返回Top 15相关文档片段 + ├→ 格式化:【知识库:xxx】\n1. [相关度XX%] 内容... + └→ 收集引用信息 (Phase 1.5) + ↓ +注入到LLM上下文 + ├→ 指导AI使用[来源N]标准编号 + └→ 追加引用清单(HTML格式) + ↓ +前端 MessageList + ├→ 解析引用标记 + ├→ 高亮显示[来源N] + ├→ 点击跳转到引用详情 + └→ 详情区域高亮闪烁 +``` + +--- + +### 3. **批处理流程** + +``` +用户选择文献 + 配置任务 + ↓ +前端 BatchMode + ↓ POST /api/v1/batch/execute +后端 batchController + ↓ +batchService.executeBatchTask() + ├→ 创建BatchTask记录 + ├→ 加载预设模板或自定义Prompt + └→ 异步执行批处理 + ↓ +并发队列(p-queue,固定3并发) + ├→ processDocument(doc1) + ├→ processDocument(doc2) + └→ processDocument(doc3) + ↓ 每个文档处理 + ├→ 获取文档全文(database) + ├→ 构造LLM消息(System + User + 文档全文) + ├→ 调用LLM API + ├→ 解析JSON(预设)或保存文本(自定义) + └→ 保存BatchResult记录 + ↓ +后台更新任务统计 + └→ completedCount, failedCount, status + +--- + +前端轮询任务状态 + ↓ GET /api/v1/batch/tasks/:taskId (每5秒) +后端返回任务进度 + ├→ status, totalCount, completedCount, failedCount + └→ 预估剩余时间 + ↓ +任务完成后 + ↓ GET /api/v1/batch/tasks/:taskId/results +后端返回完整结果 + ├→ 预设模板:8列表格 + ├→ 自定义模板:3列文本块 + └→ 失败项列表 + ↓ +前端 BatchResults + ├→ 渲染结果表格 + ├→ 导出Excel(双Sheet) + └→ 重试失败项 +``` + +--- + +## 💾 数据统计 + +### 代码量统计 + +| 模块 | 文件数 | 代码行数 | 占比 | +|------|-------|---------|------| +| **后端主代码** | 38 | ~12,000 | 50% | +| **前端主代码** | 75 | ~10,000 | 42% | +| **Python微服务** | 8 | ~2,100 | 8% | +| **总计** | **121** | **~24,100** | **100%** | + +### 数据库统计 + +| 类别 | 数量 | +|------|------| +| **表** | 13 | +| **字段** | ~80 | +| **索引** | ~20 | +| **关系** | 15 | + +### API端点统计 + +| 模块 | 端点数 | +|------|-------| +| 项目管理 | 5 | +| 智能体管理 | 4 | +| 对话系统 | 8 | +| 知识库管理 | 12 | +| 批处理系统 | 5 | +| 稿件审查 | 5 | +| **总计** | **39** | + +--- + +## 🎯 已验证的技术能力 + +### 1. AI集成能力 ✅ +- ✅ **多模型支持**:DeepSeek-V3、Qwen3-72B、Qwen-Long +- ✅ **流式输出**:SSE实时传输,打字机效果 +- ✅ **上下文管理**:智能组装,历史对话,项目背景 +- ✅ **错误处理**:完整的重试机制和降级策略 +- ✅ **Token优化**:精确计数,成本控制 + +### 2. RAG能力 ✅ +- ✅ **向量检索**:Dify + Qdrant,语义搜索 +- ✅ **多知识库**:联合检索,结果合并 +- ✅ **智能引用**:100%准确溯源,可点击跳转 +- ✅ **覆盖率优化**:从5%提升到40-50%(15倍提升) + +### 3. 文档处理能力 ✅ +- ✅ **多格式支持**:PDF、Docx、Txt +- ✅ **智能提取**:Nougat(学术论文) + PyMuPDF(兜底) + Mammoth(Docx) +- ✅ **语言检测**:中英文自动识别,优化提取策略 +- ✅ **质量评估**:提取质量评分,自动降级 +- ✅ **大文件处理**:支持50MB+文档 + +### 4. 并发控制能力 ✅ +- ✅ **队列管理**:p-queue,固定3并发 +- ✅ **容错机制**:Promise.allSettled,单个失败不影响其他 +- ✅ **进度追踪**:实时统计,预估剩余时间 +- ✅ **失败重试**:失败项可单独重试 + +### 5. 数据管理能力 ✅ +- ✅ **关系数据库**:PostgreSQL + Prisma ORM +- ✅ **数据隔离**:用户级、项目级隔离 +- ✅ **软删除**:关键数据可恢复 +- ✅ **事务管理**:ACID保证 +- ✅ **索引优化**:查询性能优化 + +--- + +## 📈 性能指标 + +### 已测试的性能数据 + +| 指标 | 数值 | 说明 | +|------|------|------| +| API响应时间 | < 500ms | 普通API端点 | +| LLM首字响应 | < 3s | DeepSeek-V3 | +| 流式输出延迟 | < 100ms | SSE chunk延迟 | +| 文档上传速度 | ~5MB/s | 10MB文档约2秒 | +| PDF提取速度(PyMuPDF) | ~2页/秒 | 20页PDF约10秒 | +| PDF提取速度(Nougat) | ~0.5页/秒 | 20页PDF约40秒 | +| Docx提取速度 | ~1秒/MB | 10MB Docx约10秒 | +| Token计数速度 | ~10ms/1000字 | Tiktoken | +| 批处理速度 | ~20秒/文档 | 3并发平均 | +| RAG检索延迟 | < 1s | Dify检索 | + +### 成本指标 + +| 项目 | 成本 | 说明 | +|------|------|------| +| LLM API成本(DeepSeek-V3) | ¥1/百万tokens | 主力模型 | +| LLM API成本(Qwen3) | ¥4/百万tokens | 备用模型 | +| LLM API成本(Qwen-Long) | ¥5/百万tokens | 全文模式 | +| 单次对话成本 | ¥0.001-0.01 | 500-5000 tokens | +| 全文精读成本(50篇) | ¥0.5-1 | 100K-200K tokens | +| 批处理成本(50篇) | ¥0.3-0.5 | 60K-100K tokens | +| 稿件审查成本 | ¥0.05-0.1 | 10K-20K tokens | + +--- + +## 🚧 技术债务清单 + +### 高优先级(建议优先处理) + +1. **日志系统** + - 现状:大量console.log用于调试 + - 建议:引入日志级别控制(winston/pino) + - 影响:生产环境日志管理 + +2. **错误码体系** + - 现状:部分API缺少详细错误信息 + - 建议:统一错误码和错误消息 + - 影响:前端错误处理和用户体验 + +3. **类型定义完善** + - 现状:部分使用了`any`类型 + - 建议:补充完整的TypeScript类型定义 + - 影响:代码可维护性 + +4. **Redis缓存集成** + - 现状:已配置但未使用 + - 建议:缓存热点数据(智能体配置、知识库列表) + - 影响:性能优化 + +### 中优先级(可在里程碑2-3处理) + +5. **单元测试** + - 现状:缺少系统性测试 + - 建议:核心业务逻辑添加单元测试 + - 影响:代码质量和重构信心 + +6. **WebSocket实时推送** + - 现状:批处理进度使用HTTP轮询 + - 建议:改为WebSocket实时推送 + - 影响:用户体验优化 + +7. **文档处理并行化** + - 现状:文档提取串行处理 + - 建议:并行提取多个文档 + - 影响:批量上传性能 + +8. **API限流** + - 现状:无限流机制 + - 建议:添加限流中间件 + - 影响:防止API滥用 + +--- + +## 🎉 核心成就 + +### 1. **快速迭代能力** +- ✅ 1个月内完成5大核心功能模块 +- ✅ 每周都有可见成果 +- ✅ 技术验证全部通过 + +### 2. **AI集成深度** +- ✅ 3个LLM模型完整集成 +- ✅ 流式输出体验优秀 +- ✅ RAG检索效果显著 + +### 3. **文档处理能力** +- ✅ Python微服务高质量提取 +- ✅ 多格式全面支持 +- ✅ 智能降级策略可靠 + +### 4. **代码质量** +- ✅ TypeScript全覆盖 +- ✅ 清晰的三层架构 +- ✅ 良好的模块解耦 + +### 5. **文档完善** +- ✅ 详细的PRD文档 +- ✅ 完整的技术文档 +- ✅ 丰富的开发日志(60+篇) + +--- + +## 📝 总结 + +### 现有系统的优势 + +1. **技术架构成熟** + - 清晰的三层架构(Controller → Service → Database) + - 良好的模块化设计 + - 完整的LLM适配器抽象 + +2. **功能完整性高** + - AI对话系统:✅ 完整可用 + - 知识库系统:✅ 完整可用(RAG + 全文) + - 批处理系统:✅ 完整可用 + - 稿件审查:✅ 完整可用 + +3. **AI能力突出** + - 多模型支持,灵活切换 + - RAG检索准确,溯源清晰 + - 智能引用100%准确 + +4. **工程质量良好** + - TypeScript全覆盖 + - Prisma ORM,类型安全 + - 清晰的代码结构 + +### 现有系统的局限 + +1. **架构层面** + - ❌ 缺少SSA、ST、DC模块(最新需求) + - ❌ 未考虑私有化部署和单机版 + - ❌ 未考虑微服务架构和K8s + - ❌ 未考虑模块化售卖 + +2. **技术栈层面** + - ❌ 缺少R语言集成(SSA需要) + - ❌ 缺少API网关 + - ❌ 缺少Electron单机版 + +3. **数据库层面** + - ❌ 单一数据库,未考虑Schema隔离 + - ❌ 未考虑多租户架构 + +4. **部署层面** + - ❌ 仅支持云端SaaS,未考虑其他3种部署模式 + - ❌ 未考虑容器化部署(K8s) + +--- + +## 🔮 下一步建议 + +基于现有系统的技术摸底,建议: + +1. **明确开发阶段** + - 当前处于"阶段一:模块化单体" + - 是否立即规划"阶段二:微服务拆分"? + - 是否立即规划Electron单机版? + +2. **明确模块优先级** + - DC模块(数据清洗):核心差异化 + - ASL模块(AI智能文献):已有部分PRD + - SSA模块(智能统计分析):需要R语言 + - ST模块(统计分析工具):相对简单 + +3. **明确架构演进路径** + - 是否遵循白皮书的分阶段实施? + - 是否立即引入K8s和API网关? + - 是否立即引入R语言和Python微服务? + +4. **明确文档更新策略** + - 立即更新哪些P0级文档? + - 如何整合现有文档和最新需求? + +--- + +**报告完成日期:** 2025-11-06 +**报告维护者:** 技术团队 +**下一步:** 讨论总体技术架构、文档体系构建、分步骤实施 + + + + + + + + + + + + + + + diff --git a/docs/00-项目概述/系统总体架构设计.md b/docs/00-项目概述/系统总体架构设计.md new file mode 100644 index 00000000..46437ffa --- /dev/null +++ b/docs/00-项目概述/系统总体架构设计.md @@ -0,0 +1,50 @@ +# 系统总体架构设计 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** 架构团队 +> **最后更新:** 2025-10-29 + +--- + +## 📋 文档说明 + +本文档描述AI科研平台的系统总体架构设计,包括: +- 整体系统架构 +- 用户端架构 +- 运营管理端架构 +- 部署架构 +- 模块化设计 +- 安全性设计 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/01-平台基础层/01-用户与权限中心(UAM)/README.md b/docs/01-平台基础层/01-用户与权限中心(UAM)/README.md new file mode 100644 index 00000000..8f2ea19c --- /dev/null +++ b/docs/01-平台基础层/01-用户与权限中心(UAM)/README.md @@ -0,0 +1,85 @@ +# 用户与权限中心 (UAM) + +> **模块定位:** 平台基础层核心模块 +> **优先级:** P0(最高) +> **状态:** ⏳ 设计中 + +--- + +## 📋 模块概述 + +用户与权限中心(User Access Management)是平台的核心基础模块,负责: +- 用户注册、登录、认证 +- 角色与权限管理(RBAC) +- Feature Flag 功能开关(商业模式基础) +- 多租户管理(SaaS版) + +--- + +## 🎯 核心功能 + +### 1. 用户认证 +- JWT Token认证 +- 用户注册/登录 +- 密码加密(bcrypt) +- 会话管理 + +### 2. 角色权限管理(RBAC) +- 角色定义 +- 权限定义 +- 用户-角色关联 +- 角色-权限关联 + +### 3. Feature Flag 管理 ⭐ **商业模式核心** +- 版本功能控制(专业版、高级版、旗舰版) +- 模块开关 +- 功能开关 + +### 4. 多租户管理 +- 租户隔离 +- 租户配额 + +--- + +## 📂 文档结构 + +``` +01-用户与权限中心(UAM)/ + ├── [AI对接] UAM快速上下文.md # ⏳ 待创建 + ├── 00-需求分析/ + │ └── README.md + ├── 01-设计文档/ + │ ├── 01-架构设计.md # ⏳ 待创建 + │ ├── 02-数据库设计.md # ⏳ 待创建 + │ ├── 03-API设计.md # ⏳ 待创建 + │ ├── 04-Feature-Flag设计.md # ⏳ 待创建 + │ └── README.md + └── README.md # ✅ 当前文档 +``` + +--- + +## 🔗 相关文档 + +- [平台基础层总览](../README.md) +- [系统架构分层设计](../../00-系统总体设计/01-系统架构分层设计.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + + diff --git a/docs/01-平台基础层/02-存储服务/README.md b/docs/01-平台基础层/02-存储服务/README.md new file mode 100644 index 00000000..1f5a3de3 --- /dev/null +++ b/docs/01-平台基础层/02-存储服务/README.md @@ -0,0 +1,65 @@ +# 存储服务 + +> **模块定位:** 平台基础层 +> **优先级:** P1 +> **状态:** ⏳ 待设计 + +--- + +## 📋 模块概述 + +存储服务负责统一管理平台的文件存储,支持: +- 文件上传、下载、删除 +- 对象存储(OSS/S3) +- 本地文件系统(单机版) +- 文件权限控制 + +--- + +## 🎯 核心功能 + +### 1. 文件上传 +- 支持多种文件格式 +- 文件大小限制 +- 文件类型验证 + +### 2. 对象存储 +- 云端:MinIO/阿里云OSS +- 单机版:本地文件系统 + +### 3. 文件访问控制 +- 临时访问URL(签名URL) +- 权限验证 + +--- + +## 📂 文档结构 + +``` +02-存储服务/ + ├── 00-需求分析/ + │ └── README.md + ├── 01-设计文档/ + │ └── README.md + └── README.md # ✅ 当前文档 +``` + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + + diff --git a/docs/01-平台基础层/03-通知服务/README.md b/docs/01-平台基础层/03-通知服务/README.md new file mode 100644 index 00000000..71df2677 --- /dev/null +++ b/docs/01-平台基础层/03-通知服务/README.md @@ -0,0 +1,51 @@ +# 通知服务 + +> **模块定位:** 平台基础层 +> **优先级:** P2 +> **状态:** ⏳ 待设计 + +--- + +## 📋 模块概述 + +通知服务负责平台的消息通知,支持: +- 站内消息 +- 邮件通知 +- WebSocket实时推送 + +--- + +## 🎯 核心功能 + +### 1. 站内消息 +- 消息列表 +- 已读/未读状态 +- 消息删除 + +### 2. 邮件通知 +- SMTP邮件发送 +- 邮件模板 + +### 3. WebSocket实时推送 +- 实时消息推送 +- 进度更新推送 + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + + diff --git a/docs/01-平台基础层/04-监控与日志/README.md b/docs/01-平台基础层/04-监控与日志/README.md new file mode 100644 index 00000000..8742500a --- /dev/null +++ b/docs/01-平台基础层/04-监控与日志/README.md @@ -0,0 +1,51 @@ +# 监控与日志 + +> **模块定位:** 平台基础层 +> **优先级:** P1 +> **状态:** ⏳ 待设计 + +--- + +## 📋 模块概述 + +监控与日志服务负责: +- 操作日志记录 +- 错误日志监控 +- 性能监控 +- 审计日志(合规要求) + +--- + +## 🎯 核心功能 + +### 1. 操作日志 +- 用户操作记录 +- 管理员操作记录 + +### 2. 错误日志 +- 系统错误捕获 +- 错误告警 + +### 3. 审计日志 +- 敏感操作记录 +- 合规审计 + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + + diff --git a/docs/01-平台基础层/05-系统配置/README.md b/docs/01-平台基础层/05-系统配置/README.md new file mode 100644 index 00000000..b812ae3d --- /dev/null +++ b/docs/01-平台基础层/05-系统配置/README.md @@ -0,0 +1,47 @@ +# 系统配置 + +> **模块定位:** 平台基础层 +> **优先级:** P1 +> **状态:** ⏳ 待设计 + +--- + +## 📋 模块概述 + +系统配置服务负责: +- 系统级配置管理 +- 多环境配置(开发、测试、生产) +- 动态配置更新 + +--- + +## 🎯 核心功能 + +### 1. 配置管理 +- 配置项定义 +- 配置值管理 +- 配置版本控制 + +### 2. 动态配置 +- 运行时配置更新 +- 配置热更新 + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + + diff --git a/docs/01-平台基础层/06-前端架构/01-前端总体架构设计.md b/docs/01-平台基础层/06-前端架构/01-前端总体架构设计.md new file mode 100644 index 00000000..e0026823 --- /dev/null +++ b/docs/01-平台基础层/06-前端架构/01-前端总体架构设计.md @@ -0,0 +1,577 @@ +# 平台前端总体架构设计 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** 前端开发团队 +> **最后更新:** 2025-10-29 + +--- + +## 📋 文档说明 + +本文档是**平台级前端架构设计**,涵盖整个AI科研平台的前端架构,包括: +- 统一的顶部导航设计 +- 模块化架构设计 +- 路由设计 +- 权限控制系统(**版本配置完全可调整**) +- 模块独立性设计(支持未来独立分拆) + +> **注意:** 本文档是平台级设计,各模块的详细架构设计请参考各模块的专属文档。 + +> **重要提示:** 本文档中涉及的版本权限分配(基础版、高级版、旗舰版的模块分配)均为**初始方案**,可以根据业务需求随时调整,无需改动代码逻辑。推荐从后端API动态获取版本配置。 + +--- + +## 🎯 设计原则 + +### 1. 模块化设计 +- 每个功能模块独立开发、独立部署 +- 模块间无依赖关系,可独立运行 +- 支持模块独立产品化(如AI智能文献独立售卖) + +### 2. 权限控制设计 +- 基于用户版本的权限控制 +- 灵活的功能模块开关机制 +- 支持未来商业模式拓展(基础版、高级版、旗舰版) + +### 3. 可扩展性设计 +- 预留新模块接入接口 +- 插件化的模块加载机制 +- 配置驱动的功能开关 + +### 4. 一致性设计 +- 统一的UI/UX规范 +- 统一的交互模式 +- 统一的状态管理 + +--- + +## 🧭 顶部导航设计 + +### 导航结构 + +``` +┌──────────────────────────────────────────────────────────────────────────────┐ +│ Logo [AI问答] [AI智能文献] [知识库] [智能数据清洗] [智能统计分析] [统计分析工具] [用户名 ▼] │ +└──────────────────────────────────────────────────────────────────────────────┘ +``` + +### 导航项详情 + +| 位置 | 中文名称 | 英文标识 | 路由路径 | 开发状态 | 权限要求(初始配置) | +|------|---------|---------|---------|---------|---------------------| +| 1 | AI问答 | `ai-qa` | `/ai-qa` | ✅ 已开发 | 基础版+ ⚠️可调整 | +| 2 | AI智能文献 | `literature-platform` | `/literature` | 🚧 待开发 | 高级版+ ⚠️可调整 | +| 3 | 知识库 | `knowledge-base` | `/knowledge-base` | ✅ 已开发 | 基础版+ ⚠️可调整 | +| 4 | 智能数据清洗 | `data-cleaning` | `/data-cleaning` | 📋 占位 | 高级版+ ⚠️可调整 | +| 5 | 智能统计分析 | `statistical-analysis` | `/intelligent-analysis` | ✅ 已开发(Java团队) | 旗舰版 ⚠️可调整 | +| 6 | 统计分析工具 | `statistical-tools` | `/statistical-tools` | ✅ 已开发(Java团队) | 旗舰版 ⚠️可调整 | +| 最右侧 | 个人中心 | `user-center` | `/user/*` | ✅ 已开发 | 所有用户 | + +> **说明:** 权限要求列中的"基础版+"、"高级版+"、"旗舰版"为**初始配置**,可根据业务需求随时调整,无需改动代码逻辑。推荐从后端API动态获取版本配置。 + +### 路由路径设计 + +```typescript +// 主路由结构 +const routes = { + // 模块路由 + '/ai-qa': 'AI问答模块', + '/literature': 'AI智能文献模块', + '/knowledge-base': '知识库模块', + '/data-cleaning': '智能数据清洗模块(占位)', + '/intelligent-analysis': '智能统计分析模块(Java团队开发,只做顶部导航集成)', + '/statistical-tools': '统计分析工具模块(Java团队开发,只做顶部导航集成)', + + // 用户相关 + '/user/profile': '个人中心 - 个人资料', + '/user/settings': '个人中心 - 设置', + '/user/history': '个人中心 - 历史记录', + '/user/subscription': '个人中心 - 订阅管理', +}; +``` + +--- + +## 🏗️ 整体架构设计 + +### 架构层次图 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 应用层 (Application) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ AI问答 │ │AI智能文献│ │ 知识库 │ │ 其他模块 │ │ +│ │ Module │ │ Module │ │ Module │ │ Module │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ 框架层 (Framework Layer) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 导航系统 │ │ 路由系统 │ │ 权限控制 │ │ +│ │ Navigation │ │ Router │ │ Permission │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 布局系统 │ │ 状态管理 │ │ 配置管理 │ │ +│ │ Layout │ │ State Mgmt │ │ Config │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ 基础层 (Base Layer) │ +│ React + TypeScript + Ant Design + Tailwind CSS │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 目录结构设计 + +``` +frontend-v2/ +├── src/ +│ ├── app/ # 应用入口 +│ │ ├── App.tsx # 根组件 +│ │ ├── main.tsx # 入口文件 +│ │ └── routes.tsx # 路由配置 +│ │ +│ ├── framework/ # 框架层(核心) +│ │ ├── navigation/ # 导航系统 +│ │ │ ├── TopNavigation.tsx # 顶部导航 +│ │ │ ├── SideNavigation.tsx # 左侧导航 +│ │ │ └── navigationConfig.ts # 导航配置 +│ │ │ +│ │ ├── routing/ # 路由系统 +│ │ │ ├── RouterConfig.tsx # 路由配置 +│ │ │ ├── RouteGuard.tsx # 路由守卫(权限) +│ │ │ └── LazyLoader.tsx # 懒加载 +│ │ │ +│ │ ├── permission/ # 权限控制 +│ │ │ ├── PermissionProvider.tsx +│ │ │ ├── usePermission.ts # 权限Hook +│ │ │ ├── permissionConfig.ts # 权限配置 +│ │ │ └── versionConfig.ts # 版本配置 +│ │ │ +│ │ ├── layout/ # 布局系统 +│ │ │ ├── MainLayout.tsx # 主布局 +│ │ │ ├── ModuleLayout.tsx # 模块布局 +│ │ │ └── EmptyLayout.tsx # 空布局 +│ │ │ +│ │ ├── config/ # 配置管理 +│ │ │ ├── moduleConfig.ts # 模块配置 +│ │ │ ├── appConfig.ts # 应用配置 +│ │ │ └── environment.ts # 环境配置 +│ │ │ +│ │ └── state/ # 全局状态 +│ │ ├── userStore.ts # 用户状态 +│ │ ├── navigationStore.ts # 导航状态 +│ │ └── permissionStore.ts # 权限状态 +│ │ +│ ├── modules/ # 功能模块(独立) +│ │ ├── ai-qa/ # AI问答模块 +│ │ │ ├── index.tsx +│ │ │ ├── routes.tsx +│ │ │ └── ... +│ │ │ +│ │ ├── literature/ # AI智能文献模块 +│ │ │ ├── index.tsx +│ │ │ ├── routes.tsx +│ │ │ └── ... +│ │ │ +│ │ ├── knowledge-base/ # 知识库模块 +│ │ │ ├── index.tsx +│ │ │ ├── routes.tsx +│ │ │ └── ... +│ │ │ +│ │ ├── data-cleaning/ # 智能数据清洗(占位) +│ │ │ └── Placeholder.tsx +│ │ │ +│ │ ├── intelligent-analysis/ # 智能统计分析 +│ │ │ └── ... +│ │ │ +│ │ └── statistical-tools/ # 统计分析工具 +│ │ └── ... +│ │ +│ ├── components/ # 通用组件 +│ │ ├── common/ # 基础组件 +│ │ ├── business/ # 业务组件 +│ │ └── hooks/ # 通用Hooks +│ │ +│ ├── api/ # API层 +│ │ ├── client.ts # API客户端 +│ │ ├── modules/ # 模块API +│ │ └── types/ # API类型 +│ │ +│ ├── utils/ # 工具函数 +│ └── types/ # 全局类型 +``` + +--- + +## 🔐 权限控制设计 + +### 用户版本定义 + +> **重要说明:** 版本权限分配是完全**可配置、可调整**的。以下配置为**初始方案**,可以根据业务需求随时修改,无需改动代码逻辑。 + +```typescript +// 用户版本类型 +type UserVersion = 'basic' | 'advanced' | 'premium'; + +// 版本配置(可配置,支持后期调整) +// 配置文件位置:src/framework/permission/versionConfig.ts +// 或者通过后端API动态获取配置 +const VERSION_CONFIG = { + basic: { + name: '基础版', + modules: ['ai-qa', 'knowledge-base'], // 初始配置:基础版包含模块 + }, + advanced: { + name: '高级版', + modules: [ + 'ai-qa', + 'knowledge-base', + 'literature-platform', + 'data-cleaning', + ], // 初始配置:高级版包含模块 + }, + premium: { + name: '旗舰版', + modules: [ + 'ai-qa', + 'knowledge-base', + 'literature-platform', + 'data-cleaning', + 'intelligent-analysis', + 'statistical-tools', + ], // 初始配置:旗舰版包含全部模块 + }, +}; + +// 说明: +// 1. 以上模块分配为初始方案,可根据实际业务需求调整 +// 2. 支持通过配置文件修改,无需改动代码 +// 3. 更推荐从后端API动态获取版本配置,便于实时调整 +``` + +### 权限控制实现 + +> **设计说明:** 权限控制采用配置驱动的方式,支持通过配置文件或后端API动态获取版本配置,便于后期灵活调整。 + +```typescript +// 权限控制Hook +export const usePermission = () => { + const userVersion = useUserStore((state) => state.version); + + // 方式1:从静态配置读取(适合开发阶段) + // const allowedModules = VERSION_CONFIG[userVersion].modules; + + // 方式2:从后端API动态获取(推荐,支持实时调整) + const versionConfig = useVersionConfig(); // 从API获取最新配置 + const allowedModules = versionConfig[userVersion]?.modules || []; + + const hasAccess = (moduleId: string): boolean => { + return allowedModules.includes(moduleId); + }; + + const getFilteredNavigation = (navigation: NavigationItem[]) => { + return navigation.filter((item) => { + // 检查用户版本是否满足模块要求 + if (!item.requiredVersion) return true; + const versionHierarchy = ['basic', 'advanced', 'premium']; + const userLevel = versionHierarchy.indexOf(userVersion); + const requiredLevel = versionHierarchy.indexOf(item.requiredVersion); + return userLevel >= requiredLevel; + }); + }; + + return { + hasAccess, + getFilteredNavigation, + userVersion, + versionConfig, // 返回当前版本配置,便于调试 + }; +}; +``` + +### 版本配置动态获取(推荐方案) + +```typescript +// src/framework/permission/useVersionConfig.ts +import { useQuery } from '@tanstack/react-query'; + +export const useVersionConfig = () => { + const { data, isLoading } = useQuery({ + queryKey: ['versionConfig'], + queryFn: async () => { + // 从后端API获取版本配置 + const response = await fetch('/api/config/version'); + return response.json(); + }, + staleTime: 5 * 60 * 1000, // 缓存5分钟 + refetchOnWindowFocus: false, + }); + + // 如果API请求失败,回退到本地配置 + return data || VERSION_CONFIG; +}; +``` + +**优势:** +- ✅ 版本配置可在后端实时调整,无需前端发版 +- ✅ 支持A/B测试不同版本配置 +- ✅ 便于运营人员灵活调整产品策略 +- ✅ 前端有本地配置作为兜底方案,保证可用性 + +### 路由守卫 + +```typescript +// RouteGuard.tsx +export const RouteGuard = ({ moduleId, children }) => { + const { hasAccess } = usePermission(); + + if (!hasAccess(moduleId)) { + return ; + } + + return children; +}; +``` + +--- + +## 🔌 模块独立性设计 + +### 模块注册机制 + +```typescript +// 模块接口定义 +interface ModuleDefinition { + id: string; // 模块唯一标识 + name: string; // 模块名称 + route: string; // 路由路径 + icon?: ReactNode; // 图标 + component: LazyComponent; // 懒加载组件 + sideNav?: SideNavConfig; // 左侧导航配置 + version?: UserVersion[]; // 支持的版本 + standalone?: boolean; // 是否支持独立运行 + apiBaseUrl?: string; // 独立运行时的API地址 +} + +// 模块配置 +const MODULES: ModuleDefinition[] = [ + { + id: 'literature-platform', + name: 'AI智能文献', + route: '/literature', + component: lazy(() => import('@/modules/literature')), + version: ['advanced', 'premium'], + standalone: true, // 支持独立运行 + apiBaseUrl: process.env.LITERATURE_API_URL, // 可配置 + }, + // ... 其他模块 +]; +``` + +### 独立部署支持 + +```typescript +// 模块加载器 +export const ModuleLoader = ({ moduleId }: { moduleId: string }) => { + const module = MODULES.find((m) => m.id === moduleId); + + if (!module) { + return ; + } + + // 检查是否独立运行模式 + const isStandalone = module.standalone && + window.location.hostname === module.apiBaseUrl; + + if (isStandalone) { + // 独立运行:不显示顶部导航,只显示模块内容 + return ; + } + + // 集成运行:显示完整导航和布局 + return ; +}; +``` + +### 配置驱动的模块加载 + +```typescript +// 环境变量配置 +// .env +REACT_APP_ENABLED_MODULES=ai-qa,knowledge-base,literature-platform +REACT_APP_STANDALONE_MODULE=literature-platform + +// 模块配置读取 +const getEnabledModules = () => { + const enabled = process.env.REACT_APP_ENABLED_MODULES?.split(',') || []; + return MODULES.filter((m) => enabled.includes(m.id)); +}; +``` + +--- + +## 📐 布局系统设计 + +### 布局层次 + +``` +MainLayout (主布局) +├── Header (顶部导航) +│ ├── Logo +│ ├── TopNavigation (导航项) +│ └── UserMenu (用户菜单) +│ +└── MainContent (主内容区) + ├── SideNavigation (左侧导航) - 可选 + └── Content (内容区域) + └── ModuleLayout (模块布局) +``` + +### 布局组件 + +```typescript +// MainLayout.tsx +export const MainLayout = () => { + return ( +
+
+
+ {/* 根据模块显示/隐藏 */} +
+ {/* 路由出口 */} +
+
+
+ ); +}; + +// ModuleLayout.tsx +export const ModuleLayout = ({ module }: { module: ModuleDefinition }) => { + return ( +
+ {module.sideNav && } +
+ +
+
+ ); +}; +``` + +--- + +## 🎨 UI/UX设计规范 + +### 顶部导航样式 + +```css +.top-navigation { + height: 64px; + background: #ffffff; + border-bottom: 1px solid #e5e7eb; + display: flex; + align-items: center; + padding: 0 24px; +} + +.nav-item { + padding: 8px 16px; + margin: 0 4px; + color: #4b5563; + font-weight: 500; + border-bottom: 2px solid transparent; + transition: all 0.2s; +} + +.nav-item:hover { + color: #0ea5e9; +} + +.nav-item.active { + color: #0ea5e9; + border-bottom-color: #0ea5e9; + font-weight: 600; +} +``` + +### 响应式设计 + +- **桌面端 (>1024px)**: 完整显示所有导航项 +- **平板端 (768-1024px)**: 显示前4个,其余折叠到"更多"菜单 +- **移动端 (<768px)**: 汉堡菜单 + 抽屉导航 + +--- + +## 📝 实施计划 + +### 第一阶段:框架搭建(Week 1) +- [x] 创建新前端项目结构 +- [ ] 实现顶部导航组件 +- [ ] 实现路由系统 +- [ ] 实现权限控制基础 +- [ ] 实现基础布局系统 + +### 第二阶段:模块集成(Week 2) +- [ ] 迁移AI问答模块 +- [ ] 迁移知识库模块 +- [ ] 实现占位模块(智能数据清洗) +- [ ] 完善权限控制 + +### 第三阶段:AI智能文献开发(Week 3+) +- [ ] 开发AI智能文献模块 +- [ ] 实现模块独立运行支持 +- [ ] 完善文档和测试 + +--- + +## 🔄 未来扩展考虑 + +### 1. 模块独立产品化 +- 支持通过环境变量配置独立运行 +- 独立的API地址配置 +- 独立的部署配置 + +### 2. 版本权限管理 +- 后端API提供用户版本信息 +- 版本配置完全可调整(无需改动代码) +- 推荐从后端API动态获取版本配置,支持实时调整模块分配 +- 前端根据版本动态显示/隐藏模块 +- 支持版本升级提示 +- 版本权限分配策略可随时根据业务需求调整 + +### 3. 新模块接入 +- 遵循模块接口规范 +- 在配置文件中注册模块 +- 自动集成到导航系统 + +--- + +## 📚 相关文档 + +- [导航结构设计](./02-导航结构设计.md) +- [路由设计](./03-路由设计.md) +- [布局设计](./04-布局设计.md) +- [权限控制设计](./05-权限控制设计.md) +- [AI智能文献模块架构设计](../AI智能文献/00-系统设计/01-模块架构设计.md) + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + + + + + + diff --git a/docs/01-平台基础层/06-前端架构/02-导航结构设计.md b/docs/01-平台基础层/06-前端架构/02-导航结构设计.md new file mode 100644 index 00000000..0df9730e --- /dev/null +++ b/docs/01-平台基础层/06-前端架构/02-导航结构设计.md @@ -0,0 +1,390 @@ +# 导航结构设计文档 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** 前端开发团队 +> **最后更新:** 2025-10-29 + +--- + +## 📋 文档说明 + +本文档详细说明AI科研平台的导航结构设计,包括顶部导航和左侧导航的设计规范。 + +> **重要提示:** 本文档中涉及的模块版本要求(`requiredVersion`)均为**初始配置**,可以根据业务需求随时调整。版本权限分配完全可配置,无需改动代码逻辑。 + +--- + +## 🧭 顶部导航设计 + +### 导航项配置 + +```typescript +// navigationConfig.ts +export interface NavigationItem { + id: string; // 唯一标识 + label: string; // 显示名称 + route: string; // 路由路径 + icon?: ReactNode; // 图标(可选) + requiredVersion?: UserVersion; // 最低版本要求(可配置,支持后期调整) + standalone?: boolean; // 是否支持独立运行 +} + +/** + * 顶部导航配置 + * + * ⚠️ 重要说明: + * - requiredVersion 字段为初始配置,可根据业务需求随时调整 + * - 推荐通过后端API动态获取版本配置,支持实时调整 + * - 修改版本要求无需改动代码逻辑,只需更新配置即可 + */ +export const TOP_NAVIGATION_ITEMS: NavigationItem[] = [ + { + id: 'ai-qa', + label: 'AI问答', + route: '/ai-qa', + requiredVersion: 'basic', // 初始配置:基础版可用(可调整) + }, + { + id: 'literature-platform', + label: 'AI智能文献', + route: '/literature', + requiredVersion: 'advanced', // 初始配置:高级版可用(可调整) + standalone: true, + }, + { + id: 'knowledge-base', + label: '知识库', + route: '/knowledge-base', + requiredVersion: 'basic', // 初始配置:基础版可用(可调整) + }, + { + id: 'data-cleaning', + label: '智能数据清洗', + route: '/data-cleaning', + requiredVersion: 'advanced', // 初始配置:高级版可用(可调整) + placeholder: true, // 占位,显示"开发中" + }, + { + id: 'statistical-analysis', + label: '智能统计分析', + route: '/intelligent-analysis', + requiredVersion: 'premium', // 初始配置:旗舰版可用(可调整) + external: true, // 由Java团队开发,只做顶部导航集成 + }, + { + id: 'statistical-tools', + label: '统计分析工具', + route: '/statistical-tools', + requiredVersion: 'premium', // 初始配置:旗舰版可用(可调整) + external: true, // 由Java团队开发,只做顶部导航集成 + }, +]; +``` + +### 顶部导航布局 + +``` +┌──────────────────────────────────────────────────────────────────────────┐ +│ [Logo] [AI问答] [AI智能文献] [知识库] [智能数据清洗] [智能统计分析] [统计分析工具] [用户名] [用户菜单▼] │ +└──────────────────────────────────────────────────────────────────────────┘ + 160px 自适应宽度 200px +``` + +### 视觉设计规范 + +#### 导航项样式 +- **正常状态**:`color: #4b5563`, `font-weight: 500` +- **悬停状态**:`color: #0ea5e9`, `background: rgba(14, 165, 233, 0.1)` +- **激活状态**:`color: #0ea5e9`, `font-weight: 600`, `border-bottom: 2px solid #0ea5e9` +- **禁用状态**(无权限):`color: #9ca3af`, `cursor: not-allowed` + +#### 间距规范 +- 导航项之间:`16px` 水平间距 +- 导航项内边距:`12px 16px` +- 整个导航栏高度:`64px` + +### 个人中心设计 + +**位置**:顶部导航最右侧 + +**显示内容**: +- 用户名(手机号脱敏显示,如:`186****8738`) +- 用户头像/图标(点击触发下拉菜单) + +**下拉菜单选项**: +``` +┌─────────────────────┐ +│ 个人中心 │ +│ 历史记录 │ +│ ──────────────── │ +│ 订阅管理 │ +│ 退出账号 │ +└─────────────────────┘ +``` + +**路由跳转**: +- 个人中心 → `/user/profile` +- 历史记录 → `/user/history` +- 订阅管理 → `/user/subscription` +- 退出账号 → 登出操作 + +--- + +## 📂 左侧导航设计 + +### 设计原则 + +1. **按需显示**:只有进入具体模块后才显示左侧导航 +2. **模块独立**:每个模块有自己独立的左侧导航配置 +3. **层级清晰**:支持一级和二级菜单 + +### AI问答模块左侧导航 + +**布局方式**:卡片式布局(不使用左侧导航) + +``` +┌─────────────────────────────────────────┐ +│ AI问答主界面 │ +│ ┌───────────────────────────────────┐ │ +│ │ 搜索框 + 提问按钮 │ │ +│ └───────────────────────────────────┘ │ +│ │ +│ 或选择一个智能体开始: │ +│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ +│ │选题│ │PICO│ │研究│ │方法│ ... │ +│ │评价│ │梳理│ │方案│ │学 │ │ +│ └────┘ └────┘ └────┘ └────┘ │ +└─────────────────────────────────────────┘ +``` + +### AI智能文献模块左侧导航 + +```typescript +const LITERATURE_SIDE_NAV = [ + { + id: 'research-plan', + label: '研究方案生成', + route: '/literature/research-plan', + icon: '📋', + }, + { + id: 'search', + label: '智能文献检索', + route: '/literature/search', + icon: '🔍', + }, + { + id: 'management', + label: '文献管理', + route: '/literature/management', + icon: '📚', + }, + { + id: 'screening', + label: '标题摘要初筛', + route: '/literature/screening', + icon: '✅', + }, + { + id: 'full-text', + label: '全文复筛', + route: '/literature/full-text', + icon: '📄', + }, + { + id: 'extraction', + label: '全文解析与数据提取', + route: '/literature/extraction', + icon: '🔬', + }, + { + id: 'analysis', + label: '数据综合分析与报告', + route: '/literature/analysis', + icon: '📊', + }, +]; +``` + +**布局样式**: +- 宽度:`200px` +- 背景色:`#f9fafb` +- 激活项:`background: #e0e7ff`, `border-left: 3px solid #4f46e5` + +### 知识库模块左侧导航 + +```typescript +const KNOWLEDGE_BASE_SIDE_NAV = [ + { + id: 'list', + label: '知识库列表', + route: '/knowledge-base', + icon: '📚', + }, + { + id: 'create', + label: '+ 新建知识库', + route: '/knowledge-base/create', + icon: '+', + }, +]; +``` + +### 智能统计分析模块(外部模块说明) + +**说明:** 智能统计分析模块由**Java团队**开发,采用**Java后端**技术栈。 +**我们的职责:** 仅在顶部导航中集成该模块的入口,点击后跳转到Java团队开发的页面。 +**设计规范:** 不需要我们设计左侧导航,Java团队已有自己的页面布局设计。 + +**集成方式:** +- 顶部导航点击后,直接跳转到 `/intelligent-analysis` 路由 +- 该路由可能指向独立的Java应用或iframe嵌入 +- 具体集成方式需与Java团队协商确定 + +### 统计分析工具模块(外部模块说明) + +**说明:** 统计分析工具模块由**Java团队**开发,采用**Java后端**技术栈。 +**我们的职责:** 仅在顶部导航中集成该模块的入口,点击后跳转到Java团队开发的页面。 +**设计规范:** 该模块**不是左侧导航设计**,Java团队有自己的页面布局(如顶部Tab导航等)。 + +**集成方式:** +- 顶部导航点击后,直接跳转到 `/statistical-tools` 路由 +- 该路由可能指向独立的Java应用或iframe嵌入 +- 具体集成方式需与Java团队协商确定 + +--- + +## 🔄 导航交互逻辑 + +### 顶部导航切换 + +```typescript +// 点击顶部导航项 +const handleNavClick = (item: NavigationItem) => { + // 1. 检查权限 + if (!hasPermission(item.requiredVersion)) { + showUpgradePrompt(item); + return; + } + + // 2. 检查是否为占位模块 + if (item.placeholder) { + showComingSoon(item); + return; + } + + // 3. 导航到目标路由 + navigate(item.route); + + // 4. 更新激活状态 + setActiveNavItem(item.id); +}; +``` + +### 左侧导航切换 + +```typescript +// 点击左侧导航项 +const handleSideNavClick = (item: SideNavItem) => { + // 导航到目标路由(模块内部路由) + navigate(item.route); + + // 更新激活状态 + setActiveSideNavItem(item.id); +}; +``` + +### 面包屑导航(可选) + +对于深层级页面,建议添加面包屑导航: + +``` +首页 > AI智能文献 > 标题摘要初筛 > 审核工作台 +``` + +--- + +## 📱 响应式设计 + +### 桌面端 (>1024px) +- 完整显示所有顶部导航项 +- 显示完整的左侧导航 + +### 平板端 (768-1024px) +- 显示前4个顶部导航项 +- 其余导航项折叠到"更多"菜单 +- 左侧导航可折叠为抽屉式 + +### 移动端 (<768px) +- 顶部导航采用汉堡菜单(☰) +- 左侧导航改为底部Tab导航或抽屉式 + +--- + +## 🎨 样式规范 + +### CSS变量定义 + +```css +:root { + /* 导航颜色 */ + --nav-bg-color: #ffffff; + --nav-border-color: #e5e7eb; + --nav-text-color: #4b5563; + --nav-text-hover: #0ea5e9; + --nav-text-active: #0ea5e9; + --nav-active-border: #0ea5e9; + + /* 侧边栏颜色 */ + --sidebar-bg: #f9fafb; + --sidebar-active-bg: #e0e7ff; + --sidebar-active-border: #4f46e5; + --sidebar-text-color: #4338ca; + + /* 尺寸 */ + --nav-height: 64px; + --sidebar-width: 200px; + --nav-item-padding: 12px 16px; + --nav-item-spacing: 16px; +} +``` + +--- + +## 📝 实施清单 + +### 组件开发 +- [ ] `TopNavigation.tsx` - 顶部导航组件 +- [ ] `SideNavigation.tsx` - 左侧导航组件 +- [ ] `UserMenu.tsx` - 用户菜单组件 +- [ ] `NavigationGuard.tsx` - 导航权限守卫 +- [ ] `ComingSoon.tsx` - 开发中占位组件 + +### 配置文件 +- [ ] `navigationConfig.ts` - 导航配置 +- [ ] `moduleNavConfig.ts` - 模块导航配置 +- [ ] `versionConfig.ts` - 版本配置(本地配置,作为兜底) +- [ ] `useVersionConfig.ts` - 版本配置Hook(从API动态获取) + +### 文档 +- [ ] 更新本文档 +- [ ] 编写导航组件使用文档 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + + + + + + diff --git a/docs/01-平台基础层/06-前端架构/03-架构原型图.html b/docs/01-平台基础层/06-前端架构/03-架构原型图.html new file mode 100644 index 00000000..458c1943 --- /dev/null +++ b/docs/01-平台基础层/06-前端架构/03-架构原型图.html @@ -0,0 +1,306 @@ + + + + + + AI科研平台-整体架构原型 V3 + + + + + + + + +
+
+
+ +
+ 临床研究平台 +
+ + + +
+ 186****8738 +
+ + +
+
+
+
+
+ + +
+ + +
+ +

研究管理 - 首页 (占位内容)

这里将展示最近的研究项目卡片...

新建研究项目
项目A
项目B
+
+ + +
+ +

统计分析工具

统计分析 (占位内容)

这里将展示各种统计分析方法的分类和入口...

单组
两组
多组
+
+ + +
+
+
+

医学研究专属大模型 (已接入DeepSeek)

+ + +
+ +

或选择一个智能体开始:

+
+
+

选题评价

+

评估您的研究选题的创新性、价值和可行性。

+
+
+

科学问题梳理

+

将您的研究想法转化为清晰、可研究的科学问题。

+
+
+

PICO 梳理

+

辅助您定义严谨的PICO要素。

+
+
+

研究方案撰写

+

基于PICO信息,生成初步的研究方案草稿。

+
+
+

样本量计算

+

根据您的研究设计和预期效果,估算所需样本量。

+
+
+

文章润色

+

对您的论文草稿进行语言润色和风格优化。

+
+
+

文章翻译

+

提供中英文本互译功能。

+
+ +
+
+
+ + +
+
+ + + +
+
+

知识库 A (35/50篇文献)

+ +
+ + +
+ +
+ + +
+
+ +

文献列表:

+
+
+ 文献1: EMPA-REG... + +
+
+ 文献2: DECLARE-TIMI... + +
+
+ 文献3: CANVAS... + +
+

...

+
+
+
+
+ + +
+
+ + + +
+

研究方案生成 (占位内容)

+

这里将是研究方案生成的向导式界面...

+
+ (下方为示意:项目文献管理入口在左侧导航栏) + +
+
+
+
+ + +
+
+

个人中心

+ + + +
+

账户信息

+

用户名: 186****8738

+ +
+

完整的历史记录 (占位)

+

这里将按模块分类展示您所有的操作历史...

+
+
+
+ +
+ + + + + + + + + + + + + + + + + diff --git a/docs/01-平台基础层/06-前端架构/README.md b/docs/01-平台基础层/06-前端架构/README.md new file mode 100644 index 00000000..5a961548 --- /dev/null +++ b/docs/01-平台基础层/06-前端架构/README.md @@ -0,0 +1,55 @@ +# 前端架构 + +## 📋 目录 + +本目录包含平台前端的架构设计文档。 + +### 文档列表 + +- **[01-前端总体架构设计.md](./01-前端总体架构设计.md)** - 平台级前端架构总体设计 +- **[02-导航结构设计.md](./02-导航结构设计.md)** - 顶部导航和左侧导航的详细设计 +- **[03-架构原型图.html](./03-架构原型图.html)** - 交互式前端架构原型图 + +--- + +## 🎯 设计原则 + +### 1. 模块化设计 +- 每个功能模块独立开发、独立部署 +- 模块间无依赖关系,可独立运行 +- 支持模块独立产品化 + +### 2. 权限控制设计 +- 基于用户版本的权限控制 +- 灵活的功能模块开关机制 +- 版本配置可动态调整 + +### 3. 可扩展性设计 +- 预留新模块接入接口 +- 插件化的模块加载机制 +- 配置驱动的功能开关 + +--- + +## 📚 相关文档 + +- [系统架构分层设计](../../00-系统总体设计/01-系统架构分层设计.md) +- [模块化架构设计](../../00-系统总体设计/06-模块独立部署与单机版方案.md) + +--- + +**维护者:** 前端开发团队 +**最后更新:** 2025-11-07 + + + + + + + + + + + + + diff --git a/docs/01-平台基础层/README.md b/docs/01-平台基础层/README.md new file mode 100644 index 00000000..1179fa98 --- /dev/null +++ b/docs/01-平台基础层/README.md @@ -0,0 +1,78 @@ +# 平台基础层 + +> **层级定义:** 所有业务模块的基础设施 +> **核心原则:** 全局唯一、业务无关、稳定性高 + +--- + +## 📋 模块清单 + +| 模块 | 说明 | 状态 | +|------|------|------| +| **01-用户与权限中心(UAM)** | 用户认证、角色权限、Feature Flag | ⏳ 待设计 | +| **02-存储服务** | 文件上传下载、对象存储 | ⏳ 待设计 | +| **03-通知服务** | 站内消息、邮件、WebSocket | ⏳ 待设计 | +| **04-监控与日志** | 操作日志、错误监控、审计日志 | ⏳ 待设计 | +| **05-系统配置** | 系统级配置管理、动态配置 | ⏳ 待设计 | + +--- + +## 🎯 设计原则 + +### 1. 全局唯一性 +- 整个平台只有一套基础设施 +- 所有业务模块共享 + +### 2. 业务无关性 +- 不涉及具体业务逻辑 +- 提供通用能力 + +### 3. 高稳定性 +- 很少变动 +- 向后兼容 + +### 4. 高可用性 +- 支持负载均衡 +- 支持容灾备份 + +--- + +## 📚 快速导航 + +### 快速上下文 +- **[AI对接] 平台层快速上下文.md** - 2-3分钟了解平台层 + +### 核心模块 +1. [用户与权限中心(UAM)](./01-用户与权限中心(UAM)/README.md) - P0优先级 +2. [存储服务](./02-存储服务/README.md) +3. [通知服务](./03-通知服务/README.md) +4. [监控与日志](./04-监控与日志/README.md) +5. [系统配置](./05-系统配置/README.md) + +--- + +## 🔗 相关文档 + +- [系统架构分层设计](../00-系统总体设计/01-系统架构分层设计.md) +- [通用能力层](../02-通用能力层/README.md) +- [业务模块层](../03-业务模块/README.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + + diff --git a/docs/01-平台基础层/[AI对接] 平台层快速上下文.md b/docs/01-平台基础层/[AI对接] 平台层快速上下文.md new file mode 100644 index 00000000..955932be --- /dev/null +++ b/docs/01-平台基础层/[AI对接] 平台层快速上下文.md @@ -0,0 +1,135 @@ +# [AI对接] 平台层快速上下文 + +> **阅读时间:** 2-3分钟 | **Token消耗:** ~1500 tokens +> **层级:** L1 | **前置阅读:** 00-系统总体设计/[AI对接] 快速上下文.md + +--- + +## 📋 平台层定位 + +**平台基础层是所有业务模块的地基,提供通用的基础设施能力。** + +**核心原则:** +- ✅ 全局唯一(整个平台只有一套) +- ✅ 业务无关(不涉及具体业务逻辑) +- ✅ 强依赖性(所有业务模块都必须依赖) +- ✅ 高稳定性(很少变动) + +--- + +## 🎯 5个核心模块 + +### 1. 用户与权限中心(UAM)⭐ P0优先级 + +**职责:** +- 用户注册、登录、认证(JWT) +- 角色与权限管理(RBAC) +- Feature Flag功能开关 ⭐ 商业模式核心 +- 多租户管理 + +**为什么重要:** +``` +Feature Flag = 商业模式技术基础 + +专业版 → 只能用基础功能 + DeepSeek +高级版 → 更多功能 + Qwen3 +旗舰版 → 全部功能 + Claude + +通过Feature Flag控制! +``` + +**数据表(platform_schema):** +```sql +- users # 用户基础信息 +- roles # 角色定义 +- permissions # 权限定义 +- feature_flags # Feature Flag配置 +- tenants # 租户(SaaS多租户) +``` + +--- + +### 2. 存储服务 - P1 + +**职责:** +- 文件上传、下载、删除 +- 对象存储(MinIO/阿里云OSS) +- 本地文件系统(单机版) + +--- + +### 3. 通知服务 - P2 + +**职责:** +- 站内消息 +- 邮件通知 +- WebSocket实时推送 + +--- + +### 4. 监控与日志 - P1 + +**职责:** +- 操作日志记录 +- 错误日志监控 +- 审计日志(合规要求) + +**数据表:** +```sql +- admin_logs # 管理员操作日志 +- error_logs # 错误日志 +- audit_logs # 审计日志 +``` + +--- + +### 5. 系统配置 - P1 + +**职责:** +- 系统级配置管理 +- 多环境配置(开发、测试、生产) +- 动态配置更新 + +--- + +## 📊 依赖关系 + +**所有业务模块都依赖平台层:** +``` +业务模块层(AIA、ASL、PKB、DC、SSA、ST、RVW、ADMIN) + ↓ 全部依赖 +平台基础层(UAM、存储、通知、监控、配置) +``` + +--- + +## 🔗 快速导航 + +**详细设计文档:** +- [用户与权限中心](./01-用户与权限中心(UAM)/README.md) +- [存储服务](./02-存储服务/README.md) +- [通知服务](./03-通知服务/README.md) +- [监控与日志](./04-监控与日志/README.md) +- [系统配置](./05-系统配置/README.md) + +**相关文档:** +- [系统架构分层设计](../00-系统总体设计/01-系统架构分层设计.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/01-设计文档/API设计规范.md b/docs/01-设计文档/API设计规范.md deleted file mode 100644 index a8cf7111..00000000 --- a/docs/01-设计文档/API设计规范.md +++ /dev/null @@ -1,1131 +0,0 @@ -# API设计规范 - -> **版本:** v1.0 -> **创建日期:** 2025-10-10 -> **API风格:** RESTful API -> **基础URL:** `http://localhost:3001/api/v1` - ---- - -## 📋 目录 - -1. [设计原则](#设计原则) -2. [通用规范](#通用规范) -3. [认证与授权](#认证与授权) -4. [API端点设计](#api端点设计) -5. [错误处理](#错误处理) -6. [数据格式](#数据格式) - ---- - -## 设计原则 - -### API First原则 -- ✅ 先设计API,再实现功能 -- ✅ API是前后端的契约 -- ✅ API变更需要版本控制 - -### RESTful设计 -- ✅ 使用HTTP动词表示操作(GET、POST、PUT、DELETE) -- ✅ URL表示资源,不表示动作 -- ✅ 使用复数名词表示资源集合 -- ✅ 嵌套资源不超过2层 - -### 命名规范 -- ✅ URL使用小写字母和连字符(kebab-case) -- ✅ 查询参数使用驼峰命名(camelCase) -- ✅ JSON字段使用驼峰命名(camelCase) - ---- - -## 通用规范 - -### HTTP方法 - -| 方法 | 用途 | 是否幂等 | -|------|------|---------| -| GET | 获取资源 | ✅ | -| POST | 创建资源 | ❌ | -| PUT | 完整更新资源 | ✅ | -| PATCH | 部分更新资源 | ❌ | -| DELETE | 删除资源 | ✅ | - -### 状态码规范 - -| 状态码 | 含义 | 使用场景 | -|--------|------|---------| -| 200 | OK | 成功返回数据 | -| 201 | Created | 成功创建资源 | -| 204 | No Content | 成功但无返回数据(如删除) | -| 400 | Bad Request | 请求参数错误 | -| 401 | Unauthorized | 未认证 | -| 403 | Forbidden | 无权限 | -| 404 | Not Found | 资源不存在 | -| 409 | Conflict | 资源冲突(如重复创建) | -| 422 | Unprocessable Entity | 语义错误(如验证失败) | -| 429 | Too Many Requests | 请求过于频繁 | -| 500 | Internal Server Error | 服务器错误 | - -### 响应格式 - -**成功响应:** -```json -{ - "success": true, - "data": { - // 实际数据 - }, - "message": "操作成功", - "timestamp": "2025-10-10T10:00:00.000Z" -} -``` - -**错误响应:** -```json -{ - "success": false, - "error": { - "code": "VALIDATION_ERROR", - "message": "参数验证失败", - "details": [ - { - "field": "email", - "message": "邮箱格式不正确" - } - ] - }, - "timestamp": "2025-10-10T10:00:00.000Z" -} -``` - -### 分页规范 - -**请求参数:** -``` -GET /api/v1/projects?page=1&pageSize=20&sortBy=createdAt&sortOrder=desc -``` - -**响应格式:** -```json -{ - "success": true, - "data": { - "items": [...], - "pagination": { - "page": 1, - "pageSize": 20, - "total": 100, - "totalPages": 5, - "hasNext": true, - "hasPrev": false - } - } -} -``` - ---- - -## 认证与授权 - -### JWT认证 - -**登录获取Token:** -```http -POST /api/v1/auth/login -Content-Type: application/json - -{ - "email": "user@example.com", - "password": "password123" -} - -Response: -{ - "success": true, - "data": { - "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "expiresIn": 604800, - "user": { - "id": "user-123", - "email": "user@example.com", - "name": "张三", - "role": "user" - } - } -} -``` - -**使用Token:** -```http -GET /api/v1/projects -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... -``` - -### 权限控制 - -| 端点 | 需要认证 | 角色要求 | -|------|---------|---------| -| `/auth/login` | ❌ | 无 | -| `/auth/register` | ❌ | 无 | -| `/users/me` | ✅ | user | -| `/projects/*` | ✅ | user | -| `/admin/*` | ✅ | admin | - ---- - -## API端点设计 - -### 1. 认证模块 - -#### 1.1 用户注册 -```http -POST /api/v1/auth/register -Content-Type: application/json - -Request: -{ - "email": "user@example.com", - "password": "password123", - "name": "张三" -} - -Response: 201 Created -{ - "success": true, - "data": { - "userId": "user-123", - "email": "user@example.com", - "message": "注册成功,请登录" - } -} -``` - -#### 1.2 用户登录 -```http -POST /api/v1/auth/login -Content-Type: application/json - -Request: -{ - "email": "user@example.com", - "password": "password123" -} - -Response: 200 OK -{ - "success": true, - "data": { - "accessToken": "eyJhbG...", - "refreshToken": "eyJhbG...", - "expiresIn": 604800, - "user": { - "id": "user-123", - "email": "user@example.com", - "name": "张三", - "role": "user" - } - } -} -``` - -#### 1.3 刷新Token -```http -POST /api/v1/auth/refresh -Content-Type: application/json - -Request: -{ - "refreshToken": "eyJhbG..." -} - -Response: 200 OK -{ - "success": true, - "data": { - "accessToken": "new_token...", - "expiresIn": 604800 - } -} -``` - -#### 1.4 退出登录 -```http -POST /api/v1/auth/logout -Authorization: Bearer {token} - -Response: 204 No Content -``` - ---- - -### 2. 用户模块 - -#### 2.1 获取当前用户信息 -```http -GET /api/v1/users/me -Authorization: Bearer {token} - -Response: 200 OK -{ - "success": true, - "data": { - "id": "user-123", - "email": "user@example.com", - "name": "张三", - "role": "user", - "kbQuota": 3, - "kbUsed": 1, - "isTrial": true, - "trialEndsAt": "2025-11-10T00:00:00.000Z", - "createdAt": "2025-10-01T00:00:00.000Z" - } -} -``` - -#### 2.2 更新用户信息 -```http -PATCH /api/v1/users/me -Authorization: Bearer {token} -Content-Type: application/json - -Request: -{ - "name": "李四", - "avatarUrl": "https://..." -} - -Response: 200 OK -{ - "success": true, - "data": { - "id": "user-123", - "name": "李四", - "avatarUrl": "https://..." - } -} -``` - -#### 2.3 修改密码 -```http -POST /api/v1/users/me/change-password -Authorization: Bearer {token} -Content-Type: application/json - -Request: -{ - "oldPassword": "old123", - "newPassword": "new456" -} - -Response: 200 OK -{ - "success": true, - "message": "密码修改成功" -} -``` - ---- - -### 3. 项目管理模块 - -#### 3.1 获取项目列表 -```http -GET /api/v1/projects?page=1&pageSize=20 -Authorization: Bearer {token} - -Response: 200 OK -{ - "success": true, - "data": { - "items": [ - { - "id": "proj-123", - "name": "XX药物III期临床试验", - "description": "项目背景描述...", - "conversationCount": 5, - "createdAt": "2025-10-01T00:00:00.000Z", - "updatedAt": "2025-10-10T00:00:00.000Z" - } - ], - "pagination": { - "page": 1, - "pageSize": 20, - "total": 2, - "totalPages": 1, - "hasNext": false, - "hasPrev": false - } - } -} -``` - -#### 3.2 创建项目 -```http -POST /api/v1/projects -Authorization: Bearer {token} -Content-Type: application/json - -Request: -{ - "name": "骨质疏松研究", - "description": "探索肠道菌群与骨质疏松的关系..." -} - -Response: 201 Created -{ - "success": true, - "data": { - "id": "proj-456", - "name": "骨质疏松研究", - "description": "探索肠道菌群与骨质疏松的关系...", - "conversationCount": 0, - "createdAt": "2025-10-10T10:00:00.000Z" - } -} -``` - -#### 3.3 获取项目详情 -```http -GET /api/v1/projects/:id -Authorization: Bearer {token} - -Response: 200 OK -{ - "success": true, - "data": { - "id": "proj-123", - "name": "XX药物III期临床试验", - "description": "项目背景描述...", - "conversationCount": 5, - "createdAt": "2025-10-01T00:00:00.000Z", - "updatedAt": "2025-10-10T00:00:00.000Z" - } -} -``` - -#### 3.4 更新项目 -```http -PUT /api/v1/projects/:id -Authorization: Bearer {token} -Content-Type: application/json - -Request: -{ - "name": "XX药物III期临床试验(更新)", - "description": "更新后的项目背景..." -} - -Response: 200 OK -{ - "success": true, - "data": { - "id": "proj-123", - "name": "XX药物III期临床试验(更新)", - "description": "更新后的项目背景...", - "updatedAt": "2025-10-10T11:00:00.000Z" - } -} -``` - -#### 3.5 删除项目 -```http -DELETE /api/v1/projects/:id -Authorization: Bearer {token} - -Response: 204 No Content -``` - ---- - -### 4. 对话管理模块 - -#### 4.1 获取对话列表 -```http -GET /api/v1/conversations?projectId=proj-123&page=1&pageSize=20 -Authorization: Bearer {token} - -Query Parameters: -- projectId (可选): 筛选特定项目的对话,不传则返回所有对话 -- agentId (可选): 筛选特定智能体的对话 -- page: 页码 -- pageSize: 每页数量 - -Response: 200 OK -{ - "success": true, - "data": { - "items": [ - { - "id": "conv-123", - "title": "PICOS构建", - "agentId": "agent-picos", - "agentName": "PICOS构建智能体", - "projectId": "proj-123", - "projectName": "XX药物研究", - "modelName": "deepseek-v3", - "messageCount": 10, - "totalTokens": 5000, - "createdAt": "2025-10-10T09:00:00.000Z", - "updatedAt": "2025-10-10T10:00:00.000Z" - } - ], - "pagination": {...} - } -} -``` - -#### 4.2 创建对话 -```http -POST /api/v1/conversations -Authorization: Bearer {token} -Content-Type: application/json - -Request: -{ - "projectId": "proj-123", // 可选,全局快速问答时不传 - "agentId": "agent-picos", - "title": "PICOS构建", - "modelName": "deepseek-v3" // 可选,默认deepseek-v3 -} - -Response: 201 Created -{ - "success": true, - "data": { - "id": "conv-456", - "title": "PICOS构建", - "agentId": "agent-picos", - "projectId": "proj-123", - "modelName": "deepseek-v3", - "messageCount": 0, - "createdAt": "2025-10-10T10:00:00.000Z" - } -} -``` - -#### 4.3 获取对话详情(含消息) -```http -GET /api/v1/conversations/:id -Authorization: Bearer {token} - -Response: 200 OK -{ - "success": true, - "data": { - "id": "conv-123", - "title": "PICOS构建", - "agentId": "agent-picos", - "projectId": "proj-123", - "modelName": "deepseek-v3", - "messageCount": 2, - "messages": [ - { - "id": "msg-1", - "role": "user", - "content": "请帮我构建PICOS框架", - "createdAt": "2025-10-10T09:00:00.000Z" - }, - { - "id": "msg-2", - "role": "assistant", - "content": "好的,我们开始构建PICOS...", - "isPinned": false, - "tokens": 500, - "createdAt": "2025-10-10T09:00:05.000Z" - } - ], - "createdAt": "2025-10-10T09:00:00.000Z", - "updatedAt": "2025-10-10T09:00:05.000Z" - } -} -``` - -#### 4.4 发送消息(流式) -```http -POST /api/v1/conversations/:id/messages/stream -Authorization: Bearer {token} -Content-Type: application/json - -Request: -{ - "content": "请帮我构建PICOS框架", - "kbReferences": ["kb-123"] // 可选,引用的知识库 -} - -Response: 200 OK (Server-Sent Events) -Content-Type: text/event-stream - -data: {"type":"start","conversationId":"conv-123"} - -data: {"type":"token","content":"好"} - -data: {"type":"token","content":"的"} - -data: {"type":"token","content":","} - -data: {"type":"done","messageId":"msg-456","tokens":500} -``` - -#### 4.5 固定消息到项目背景 -```http -POST /api/v1/messages/:id/pin -Authorization: Bearer {token} - -Response: 200 OK -{ - "success": true, - "message": "已固定到项目背景" -} -``` - -#### 4.6 删除对话 -```http -DELETE /api/v1/conversations/:id -Authorization: Bearer {token} - -Response: 204 No Content -``` - ---- - -### 5. 智能体模块 - -#### 5.1 获取智能体列表 -```http -GET /api/v1/agents -Authorization: Bearer {token} - -Response: 200 OK -{ - "success": true, - "data": [ - { - "id": "agent-picos", - "name": "PICOS构建", - "description": "结构化地定义临床研究的核心要素", - "category": "研究设计", - "icon": "construction", - "ragEnabled": true, - "fileUploadEnabled": false, - "status": "active" - }, - { - "id": "agent-topic-evaluation", - "name": "选题评价", - "description": "从创新性、临床价值等维度评价研究选题", - "category": "选题阶段", - "icon": "lightbulb", - "ragEnabled": true, - "fileUploadEnabled": false, - "status": "active" - } - ] -} -``` - -#### 5.2 获取智能体详情 -```http -GET /api/v1/agents/:id -Authorization: Bearer {token} - -Response: 200 OK -{ - "success": true, - "data": { - "id": "agent-picos", - "name": "PICOS构建", - "description": "结构化地定义临床研究的核心要素", - "category": "研究设计", - "icon": "construction", - "ragEnabled": true, - "fileUploadEnabled": false, - "outputFormat": "structured", - "models": { - "deepseek-v3": { - "temperature": 0.3, - "maxTokens": 2500 - }, - "qwen3-72b": { - "temperature": 0.4, - "maxTokens": 2500 - } - }, - "status": "active" - } -} -``` - ---- - -### 6. 知识库模块 - -#### 6.1 获取知识库列表 -```http -GET /api/v1/knowledge-bases -Authorization: Bearer {token} - -Response: 200 OK -{ - "success": true, - "data": [ - { - "id": "kb-123", - "name": "骨质疏松专题", - "description": "关于骨质疏松的文献资料", - "fileCount": 15, - "totalSizeMB": 45.6, - "quota": { - "used": 15, - "limit": 50 - }, - "createdAt": "2025-10-01T00:00:00.000Z", - "updatedAt": "2025-10-10T00:00:00.000Z" - } - ], - "quota": { - "used": 1, - "limit": 3 - } -} -``` - -#### 6.2 创建知识库 -```http -POST /api/v1/knowledge-bases -Authorization: Bearer {token} -Content-Type: application/json - -Request: -{ - "name": "肺癌研究资料", - "description": "肺癌相关的临床研究文献" -} - -Response: 201 Created -{ - "success": true, - "data": { - "id": "kb-456", - "name": "肺癌研究资料", - "description": "肺癌相关的临床研究文献", - "fileCount": 0, - "difyDatasetId": "dify-dataset-xxx", - "createdAt": "2025-10-10T10:00:00.000Z" - } -} -``` - -#### 6.3 上传文档 -```http -POST /api/v1/knowledge-bases/:id/documents -Authorization: Bearer {token} -Content-Type: multipart/form-data - -Request: -- file: (binary) -- filename: "文献综述.pdf" - -Response: 201 Created -{ - "success": true, - "data": { - "id": "doc-123", - "filename": "文献综述.pdf", - "fileType": "pdf", - "fileSizeBytes": 2048000, - "status": "processing", - "progress": 0, - "uploadedAt": "2025-10-10T10:00:00.000Z" - } -} -``` - -#### 6.4 获取文档列表 -```http -GET /api/v1/knowledge-bases/:id/documents -Authorization: Bearer {token} - -Response: 200 OK -{ - "success": true, - "data": [ - { - "id": "doc-123", - "filename": "文献综述.pdf", - "fileType": "pdf", - "fileSizeMB": 2.0, - "status": "completed", - "progress": 100, - "segmentsCount": 50, - "tokensCount": 10000, - "uploadedAt": "2025-10-10T10:00:00.000Z", - "processedAt": "2025-10-10T10:02:00.000Z" - } - ] -} -``` - -#### 6.5 删除文档 -```http -DELETE /api/v1/knowledge-bases/:kbId/documents/:docId -Authorization: Bearer {token} - -Response: 204 No Content -``` - -#### 6.6 删除知识库 -```http -DELETE /api/v1/knowledge-bases/:id -Authorization: Bearer {token} - -Response: 204 No Content -``` - ---- - -### 7. 历史记录模块 - -#### 7.1 获取历史记录(跨项目) -```http -GET /api/v1/history?page=1&pageSize=20&agentId=agent-picos&startDate=2025-10-01&endDate=2025-10-10 -Authorization: Bearer {token} - -Query Parameters: -- projectId (可选): 筛选特定项目 -- agentId (可选): 筛选特定智能体 -- startDate (可选): 开始日期 -- endDate (可选): 结束日期 -- keyword (可选): 关键词搜索 - -Response: 200 OK -{ - "success": true, - "data": { - "items": [ - { - "conversationId": "conv-123", - "title": "PICOS构建", - "agentId": "agent-picos", - "agentName": "PICOS构建智能体", - "projectId": "proj-123", - "projectName": "XX药物研究", - "source": "project", // project 或 global - "messageCount": 10, - "createdAt": "2025-10-10T09:00:00.000Z", - "lastMessageAt": "2025-10-10T10:00:00.000Z" - } - ], - "pagination": {...} - } -} -``` - ---- - -### 8. 管理后台模块(简化版) - -#### 8.1 获取用户列表(管理员) -```http -GET /api/v1/admin/users?page=1&pageSize=20&status=active&keyword=zhang -Authorization: Bearer {admin_token} - -Response: 200 OK -{ - "success": true, - "data": { - "items": [ - { - "id": "user-123", - "email": "user@example.com", - "name": "张三", - "role": "user", - "status": "active", - "kbUsed": 1, - "conversationCount": 25, - "lastLoginAt": "2025-10-10T09:00:00.000Z", - "createdAt": "2025-10-01T00:00:00.000Z" - } - ], - "pagination": {...} - } -} -``` - -#### 8.2 更新用户状态(管理员) -```http -PATCH /api/v1/admin/users/:id -Authorization: Bearer {admin_token} -Content-Type: application/json - -Request: -{ - "status": "suspended" // active, inactive, suspended -} - -Response: 200 OK -{ - "success": true, - "message": "用户状态已更新" -} -``` - -#### 8.3 获取数据统计(管理员) -```http -GET /api/v1/admin/statistics -Authorization: Bearer {admin_token} - -Response: 200 OK -{ - "success": true, - "data": { - "users": { - "total": 1000, - "active": 850, - "newToday": 15, - "new7Days": 120 - }, - "conversations": { - "total": 5000, - "today": 200, - "avgPerUser": 5 - }, - "agents": { - "mostUsed": [ - { - "agentId": "agent-picos", - "name": "PICOS构建", - "count": 800 - } - ] - }, - "knowledgeBases": { - "total": 300, - "totalDocuments": 4500, - "totalSizeGB": 12.5 - } - } -} -``` - ---- - -### 9. 通用对话模块(智能问答) - -**功能**: 提供无项目、无智能体概念的纯AI对话功能,支持可选的@知识库引用 - -#### 9.1 发送消息(流式输出) -```http -POST /api/v1/chat/stream -Content-Type: application/json - -Request: -{ - "content": "你好,介绍一下自己", - "modelType": "deepseek-v3", - "knowledgeBaseIds": ["kb-uuid-1", "kb-uuid-2"], // 可选 - "conversationId": "conv-uuid" // 可选,续接已有对话 -} - -Response: 200 OK (Server-Sent Events) -Content-Type: text/event-stream - -data: {"content":"你","usage":null} - -data: {"content":"好","usage":null} - -data: {"content":"!","usage":null} - -data: {"content":"我","usage":null} - -... - -data: [DONE] -``` - -**参数说明:** -- `content` - 用户消息内容(必填) -- `modelType` - 使用的模型(必填):deepseek-v3 | qwen3-72b | gemini-pro -- `knowledgeBaseIds` - 知识库ID数组(可选) -- `conversationId` - 对话ID(可选,新对话时不传) - -**响应说明:** -- 使用SSE(Server-Sent Events)流式返回 -- 每个chunk包含:`content`(增量内容)、`usage`(token统计) -- 最后发送 `data: [DONE]` 表示完成 - ---- - -#### 9.2 获取对话列表 -```http -GET /api/v1/chat/conversations -Authorization: Bearer {token} - -Response: 200 OK -{ - "success": true, - "data": [ - { - "id": "conv-uuid", - "userId": "user-uuid", - "title": "关于骨质疏松的讨论", - "modelName": "deepseek-v3", - "createdAt": "2025-10-11T10:00:00.000Z", - "updatedAt": "2025-10-11T10:30:00.000Z" - } - ] -} -``` - -**响应说明:** -- 按 `updatedAt` 降序排序 -- 最多返回50条 -- 不包括已删除的对话(deleted_at != null) - ---- - -#### 9.3 删除对话 -```http -DELETE /api/v1/chat/conversations/:id -Authorization: Bearer {token} - -Response: 200 OK -{ - "success": true, - "message": "删除成功" -} -``` - -**说明:** -- 软删除,设置 `deleted_at` 字段 -- 级联删除所有消息 -- 不可恢复 - ---- - -### 通用对话 vs 项目对话 - -| 特性 | 通用对话(/chat) | 项目对话(/conversations) | -|------|------------------|---------------------------| -| **概念** | 无项目/智能体 | 基于项目和智能体 | -| **使用场景** | 快速提问、知识库问答 | 结构化研究流程 | -| **上下文** | 纯对话历史 | 项目背景 + 智能体角色 + 对话历史 | -| **知识库** | 可选@引用 | 可选@引用 | -| **System Prompt** | 通用AI助手 | 专业智能体角色 | -| **数据表** | general_conversations | conversations | - ---- - -## 错误处理 - -### 错误代码规范 - -| 错误代码 | 说明 | HTTP状态码 | -|---------|------|-----------| -| VALIDATION_ERROR | 参数验证失败 | 422 | -| UNAUTHORIZED | 未认证 | 401 | -| FORBIDDEN | 无权限 | 403 | -| NOT_FOUND | 资源不存在 | 404 | -| CONFLICT | 资源冲突 | 409 | -| RATE_LIMIT | 请求过于频繁 | 429 | -| QUOTA_EXCEEDED | 配额超限 | 403 | -| INTERNAL_ERROR | 服务器错误 | 500 | - -### 错误响应示例 - -```json -{ - "success": false, - "error": { - "code": "QUOTA_EXCEEDED", - "message": "知识库数量已达上限", - "details": { - "resource": "knowledge_base", - "quota": 3, - "used": 3 - } - }, - "timestamp": "2025-10-10T10:00:00.000Z" -} -``` - ---- - -## 数据格式 - -### 时间格式 -- 使用ISO 8601格式:`2025-10-10T10:00:00.000Z` -- 统一使用UTC时区 - -### 文件大小 -- 使用字节(bytes)存储 -- 前端显示时转换为MB/GB - -### ID格式 -- 使用UUID v4 -- 格式:`user-123abc...` 或 `proj-456def...` - ---- - -## 版本控制 - -### API版本 -- 当前版本:`v1` -- URL格式:`/api/v1/...` -- 向后兼容:同一主版本内保持兼容 - -### 废弃策略 -- 提前3个月通知 -- 响应头标注:`X-API-Deprecated: true` -- 提供迁移指南 - ---- - -## 性能优化 - -### 缓存策略 -- 静态数据(智能体列表):缓存1小时 -- 用户数据:不缓存或缓存5分钟 -- 使用ETag实现条件请求 - -### 限流策略 -- 普通用户:100次/分钟 -- 管理员:500次/分钟 -- 登录接口:5次/分钟 - ---- - -## 安全性 - -### HTTPS -- 生产环境强制HTTPS -- 开发环境可使用HTTP - -### CORS -- 允许的域名列表(白名单) -- 不允许通配符 `*` - -### SQL注入防护 -- 使用Prisma ORM -- 永不拼接SQL - -### XSS防护 -- 所有用户输入转义 -- Content-Type正确设置 - ---- - -## 文档维护 - -- **更新频率:** API变更时必须同步更新 -- **Review:** 每个里程碑Review一次 -- **版本记录:** 记录重大变更 - ---- - -**最后更新:** 2025-10-10 -**维护者:** 开发团队 - - - - diff --git a/docs/01-设计文档/数据库设计文档.md b/docs/01-设计文档/数据库设计文档.md deleted file mode 100644 index 1538fdbf..00000000 --- a/docs/01-设计文档/数据库设计文档.md +++ /dev/null @@ -1,893 +0,0 @@ -# 数据库设计文档 - -> **版本:** v1.0 -> **创建日期:** 2025-10-10 -> **数据库:** PostgreSQL 15+ -> **ORM:** Prisma - ---- - -## 📋 目录 - -1. [数据库概述](#数据库概述) -2. [ER图](#er图) -3. [表结构设计](#表结构设计) -4. [索引设计](#索引设计) -5. [数据约束](#数据约束) -6. [数据迁移策略](#数据迁移策略) - ---- - -## 数据库概述 - -### 设计原则 -- ✅ 遵循第三范式(3NF) -- ✅ 使用UUID作为主键 -- ✅ 所有表包含created_at和updated_at时间戳 -- ✅ 使用软删除(保留deleted_at字段,重要表) -- ✅ 外键约束使用CASCADE删除 -- ✅ 敏感字段加密存储(密码使用bcrypt) - -### 命名规范 -- 表名:复数形式,下划线分隔(如:`users`、`knowledge_bases`) -- 字段名:下划线分隔(如:`created_at`、`user_id`) -- 索引名:`idx_表名_字段名`(如:`idx_users_email`) -- 外键名:`fk_表名_关联表名`(如:`fk_projects_users`) - ---- - -## ER图 - -``` -┌─────────────┐ -│ Users │ -└──────┬──────┘ - │ - │ 1:N - ├──────────────────────┐ - │ │ - ├──────────────────┐ │ - │ │ │ - ▼ ▼ ▼ -┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐ -│ Projects │ │ Knowledge │ │ General │ -│ │ │ Bases │ │ Conversations ⭐ │ -└──────┬──────┘ └──────┬───────┘ └──────┬───────────────┘ - │ │ │ - │ 1:N │ 1:N │ 1:N - ▼ ▼ ▼ -┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐ -│Conversations│ │ Documents │ │ General Messages ⭐ │ -│ Projects │ │KnowledgeBases│ -└──────┬──────┘ └──────┬───────┘ - │ │ - │ 1:N │ 1:N - │ │ - ▼ ▼ -┌──────────────┐ ┌──────────────┐ -│Conversations │ │ Documents │ -└──────┬───────┘ └──────────────┘ - │ - │ 1:N - │ - ▼ -┌──────────────┐ -│ Messages │ -└──────────────┘ -``` - ---- - -## 表结构设计 - -### 1. users - 用户表 - -**用途:** 存储用户基本信息和认证信息 - -```sql -CREATE TABLE users ( - id VARCHAR(50) PRIMARY KEY DEFAULT gen_random_uuid()::VARCHAR, - email VARCHAR(255) NOT NULL UNIQUE, - password VARCHAR(255) NOT NULL, -- bcrypt加密 - name VARCHAR(100), - avatar_url TEXT, - - -- 角色和状态 - role VARCHAR(20) NOT NULL DEFAULT 'user', -- user, admin - status VARCHAR(20) NOT NULL DEFAULT 'active', -- active, inactive, suspended - - -- 配额(知识库限制) - kb_quota INTEGER DEFAULT 3, - kb_used INTEGER DEFAULT 0, - - -- 试用信息 - trial_ends_at TIMESTAMP, - is_trial BOOLEAN DEFAULT true, - - -- 时间戳 - last_login_at TIMESTAMP, - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW(), - - -- 索引 - CONSTRAINT check_email_format CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$'), - CONSTRAINT check_kb_quota CHECK (kb_used <= kb_quota) -); - --- 索引 -CREATE INDEX idx_users_email ON users(email); -CREATE INDEX idx_users_status ON users(status); -CREATE INDEX idx_users_created_at ON users(created_at); - --- 注释 -COMMENT ON TABLE users IS '用户表'; -COMMENT ON COLUMN users.role IS '用户角色:user-普通用户, admin-管理员'; -COMMENT ON COLUMN users.status IS '账户状态:active-激活, inactive-未激活, suspended-暂停'; -``` - -**字段说明:** -| 字段 | 类型 | 说明 | 必填 | 默认值 | -|------|------|------|------|--------| -| id | VARCHAR(50) | 用户ID(UUID) | ✅ | 自动生成 | -| email | VARCHAR(255) | 邮箱(登录名) | ✅ | - | -| password | VARCHAR(255) | 密码(bcrypt) | ✅ | - | -| name | VARCHAR(100) | 用户姓名 | ❌ | NULL | -| role | VARCHAR(20) | 角色 | ✅ | 'user' | -| status | VARCHAR(20) | 状态 | ✅ | 'active' | -| kb_quota | INTEGER | 知识库配额 | ✅ | 3 | -| kb_used | INTEGER | 已使用知识库数 | ✅ | 0 | - ---- - -### 2. projects - 项目/课题表 - -**用途:** 存储用户创建的研究项目/课题 - -```sql -CREATE TABLE projects ( - id VARCHAR(50) PRIMARY KEY DEFAULT gen_random_uuid()::VARCHAR, - user_id VARCHAR(50) NOT NULL, - - -- 项目信息 - name VARCHAR(200) NOT NULL, - description TEXT NOT NULL, -- 项目背景信息(重要!用于上下文注入) - - -- 统计信息 - conversation_count INTEGER DEFAULT 0, - - -- 时间戳 - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW(), - - -- 外键 - CONSTRAINT fk_projects_users FOREIGN KEY (user_id) - REFERENCES users(id) ON DELETE CASCADE -); - --- 索引 -CREATE INDEX idx_projects_user_id ON projects(user_id); -CREATE INDEX idx_projects_created_at ON projects(created_at); - --- 注释 -COMMENT ON TABLE projects IS '项目/课题表'; -COMMENT ON COLUMN projects.description IS '项目背景信息,会自动注入到对话上下文中'; -``` - ---- - -### 3. conversations - 对话表 - -**用途:** 存储用户与智能体的对话会话 - -```sql -CREATE TABLE conversations ( - id VARCHAR(50) PRIMARY KEY DEFAULT gen_random_uuid()::VARCHAR, - user_id VARCHAR(50) NOT NULL, - project_id VARCHAR(50), -- 可选,全局快速问答时为NULL - agent_id VARCHAR(50) NOT NULL, -- 智能体ID(对应config/agents.yaml) - - -- 对话信息 - title VARCHAR(200) NOT NULL, - model_name VARCHAR(50) DEFAULT 'deepseek-v3', - - -- 统计信息 - message_count INTEGER DEFAULT 0, - total_tokens INTEGER DEFAULT 0, - - -- 时间戳 - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW(), - - -- 外键 - CONSTRAINT fk_conversations_users FOREIGN KEY (user_id) - REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT fk_conversations_projects FOREIGN KEY (project_id) - REFERENCES projects(id) ON DELETE CASCADE -); - --- 索引 -CREATE INDEX idx_conversations_user_id ON conversations(user_id); -CREATE INDEX idx_conversations_project_id ON conversations(project_id); -CREATE INDEX idx_conversations_agent_id ON conversations(agent_id); -CREATE INDEX idx_conversations_created_at ON conversations(created_at); - --- 注释 -COMMENT ON TABLE conversations IS '对话会话表'; -COMMENT ON COLUMN conversations.project_id IS '项目ID,全局快速问答时为NULL'; -COMMENT ON COLUMN conversations.agent_id IS '智能体ID,对应配置文件中的智能体'; -``` - ---- - -### 4. messages - 消息表 - -**用途:** 存储对话中的每条消息 - -```sql -CREATE TABLE messages ( - id VARCHAR(50) PRIMARY KEY DEFAULT gen_random_uuid()::VARCHAR, - conversation_id VARCHAR(50) NOT NULL, - - -- 消息内容 - role VARCHAR(20) NOT NULL, -- user, assistant - content TEXT NOT NULL, - - -- 元数据(可选) - metadata JSONB, -- 存储额外信息,如引用的知识库、模型参数等 - - -- 统计信息 - tokens INTEGER, - - -- 是否固定到项目背景 - is_pinned BOOLEAN DEFAULT false, - - -- 时间戳 - created_at TIMESTAMP DEFAULT NOW(), - - -- 外键 - CONSTRAINT fk_messages_conversations FOREIGN KEY (conversation_id) - REFERENCES conversations(id) ON DELETE CASCADE, - - -- 约束 - CONSTRAINT check_role CHECK (role IN ('user', 'assistant')) -); - --- 索引 -CREATE INDEX idx_messages_conversation_id ON messages(conversation_id); -CREATE INDEX idx_messages_created_at ON messages(created_at); -CREATE INDEX idx_messages_is_pinned ON messages(is_pinned); - --- 注释 -COMMENT ON TABLE messages IS '对话消息表'; -COMMENT ON COLUMN messages.is_pinned IS '是否固定到项目背景,用于动态更新项目描述'; -COMMENT ON COLUMN messages.metadata IS 'JSON格式,存储引用的知识库、使用的模型等'; -``` - ---- - -### 5. knowledge_bases - 知识库表 - -**用途:** 存储用户创建的个人知识库 - -```sql -CREATE TABLE knowledge_bases ( - id VARCHAR(50) PRIMARY KEY DEFAULT gen_random_uuid()::VARCHAR, - user_id VARCHAR(50) NOT NULL, - - -- 知识库信息 - name VARCHAR(100) NOT NULL, - description TEXT, - - -- Dify集成 - dify_dataset_id VARCHAR(100) NOT NULL, -- Dify中的知识库ID - - -- 统计信息 - file_count INTEGER DEFAULT 0, - total_size_bytes BIGINT DEFAULT 0, - - -- 时间戳 - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW(), - - -- 外键 - CONSTRAINT fk_kb_users FOREIGN KEY (user_id) - REFERENCES users(id) ON DELETE CASCADE, - - -- 约束:每个用户最多3个知识库 - CONSTRAINT check_kb_limit CHECK ( - (SELECT COUNT(*) FROM knowledge_bases WHERE user_id = knowledge_bases.user_id) <= 3 - ) -); - --- 索引 -CREATE INDEX idx_kb_user_id ON knowledge_bases(user_id); -CREATE INDEX idx_kb_dify_dataset_id ON knowledge_bases(dify_dataset_id); - --- 注释 -COMMENT ON TABLE knowledge_bases IS '知识库表,每个用户最多3个'; -COMMENT ON COLUMN knowledge_bases.dify_dataset_id IS 'Dify中对应的数据集ID'; -``` - ---- - -### 6. documents - 文档表 - -**用途:** 存储知识库中上传的文档 - -```sql -CREATE TABLE documents ( - id VARCHAR(50) PRIMARY KEY DEFAULT gen_random_uuid()::VARCHAR, - kb_id VARCHAR(50) NOT NULL, - user_id VARCHAR(50) NOT NULL, - - -- 文件信息 - filename VARCHAR(255) NOT NULL, - file_type VARCHAR(20) NOT NULL, -- pdf, docx - file_size_bytes BIGINT NOT NULL, - file_url TEXT NOT NULL, -- 对象存储URL - - -- Dify集成 - dify_document_id VARCHAR(100) NOT NULL, -- Dify中的文档ID - - -- 处理状态 - status VARCHAR(20) DEFAULT 'uploading', -- uploading, processing, completed, failed - progress INTEGER DEFAULT 0, -- 0-100 - error_message TEXT, - - -- 处理结果 - segments_count INTEGER, -- 切分的段落数 - tokens_count INTEGER, -- token数量 - - -- 时间戳 - uploaded_at TIMESTAMP DEFAULT NOW(), - processed_at TIMESTAMP, - - -- 外键 - CONSTRAINT fk_documents_kb FOREIGN KEY (kb_id) - REFERENCES knowledge_bases(id) ON DELETE CASCADE, - CONSTRAINT fk_documents_users FOREIGN KEY (user_id) - REFERENCES users(id) ON DELETE CASCADE, - - -- 约束:每个知识库最多50个文档 - CONSTRAINT check_doc_limit CHECK ( - (SELECT COUNT(*) FROM documents WHERE kb_id = documents.kb_id) <= 50 - ), - - -- 约束:状态和进度 - CONSTRAINT check_status CHECK (status IN ('uploading', 'processing', 'completed', 'failed')), - CONSTRAINT check_progress CHECK (progress >= 0 AND progress <= 100) -); - --- 索引 -CREATE INDEX idx_documents_kb_id ON documents(kb_id); -CREATE INDEX idx_documents_user_id ON documents(user_id); -CREATE INDEX idx_documents_status ON documents(status); -CREATE INDEX idx_documents_dify_document_id ON documents(dify_document_id); - --- 注释 -COMMENT ON TABLE documents IS '文档表,每个知识库最多50个文档'; -COMMENT ON COLUMN documents.status IS '处理状态:uploading-上传中, processing-处理中, completed-完成, failed-失败'; -``` - ---- - -### 7. admin_logs - 管理员操作日志表(可选) - -**用途:** 记录管理员的敏感操作 - -```sql -CREATE TABLE admin_logs ( - id SERIAL PRIMARY KEY, - admin_id VARCHAR(50) NOT NULL, - - -- 操作信息 - action VARCHAR(100) NOT NULL, -- 操作类型 - resource_type VARCHAR(50), -- 资源类型:user, conversation, etc. - resource_id VARCHAR(50), -- 资源ID - - -- 详细信息 - details JSONB, - ip_address VARCHAR(45), - user_agent TEXT, - - -- 时间戳 - created_at TIMESTAMP DEFAULT NOW(), - - -- 外键 - CONSTRAINT fk_admin_logs_users FOREIGN KEY (admin_id) - REFERENCES users(id) ON DELETE CASCADE -); - --- 索引 -CREATE INDEX idx_admin_logs_admin_id ON admin_logs(admin_id); -CREATE INDEX idx_admin_logs_created_at ON admin_logs(created_at); -CREATE INDEX idx_admin_logs_action ON admin_logs(action); - --- 注释 -COMMENT ON TABLE admin_logs IS '管理员操作日志表,用于审计'; -``` - ---- - -### 8. general_conversations - 通用对话表 - -**用途:** 存储智能问答的对话记录(无需项目和智能体) - -```sql -CREATE TABLE general_conversations ( - id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id VARCHAR(36) NOT NULL, - - -- 对话信息 - title VARCHAR(255) NOT NULL, - model_name VARCHAR(50), -- 主要使用的模型 - - -- 时间戳 - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW(), - deleted_at TIMESTAMP, -- 软删除 - - -- 外键 - CONSTRAINT fk_general_conversations_users FOREIGN KEY (user_id) - REFERENCES users(id) ON DELETE CASCADE -); - --- 索引 -CREATE INDEX idx_general_conversations_user_id ON general_conversations(user_id); -CREATE INDEX idx_general_conversations_created_at ON general_conversations(created_at); -CREATE INDEX idx_general_conversations_updated_at ON general_conversations(updated_at); - --- 注释 -COMMENT ON TABLE general_conversations IS '通用对话表,用于智能问答功能'; -``` - -**字段说明:** - -| 字段 | 类型 | 说明 | 约束 | -|------|------|------|------| -| id | VARCHAR(36) | 对话ID | PRIMARY KEY, UUID | -| user_id | VARCHAR(36) | 用户ID | NOT NULL, FK → users | -| title | VARCHAR(255) | 对话标题(自动生成) | NOT NULL | -| model_name | VARCHAR(50) | 主要使用的模型 | - | -| created_at | TIMESTAMP | 创建时间 | DEFAULT NOW() | -| updated_at | TIMESTAMP | 最后更新时间 | DEFAULT NOW() | -| deleted_at | TIMESTAMP | 删除时间(软删除) | - | - ---- - -### 9. general_messages - 通用消息表 - -**用途:** 存储智能问答的消息记录 - -```sql -CREATE TABLE general_messages ( - id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(), - conversation_id VARCHAR(36) NOT NULL, - - -- 消息内容 - role VARCHAR(20) NOT NULL, -- user / assistant - content TEXT NOT NULL, - - -- AI响应信息 - model VARCHAR(50), -- 使用的具体模型 - tokens INTEGER, -- token消耗 - metadata JSONB, -- 扩展信息(如knowledgeBaseIds、usage等) - - -- 时间戳 - created_at TIMESTAMP DEFAULT NOW(), - - -- 外键 - CONSTRAINT fk_general_messages_conversations FOREIGN KEY (conversation_id) - REFERENCES general_conversations(id) ON DELETE CASCADE -); - --- 索引 -CREATE INDEX idx_general_messages_conversation_id ON general_messages(conversation_id); -CREATE INDEX idx_general_messages_created_at ON general_messages(created_at); - --- 注释 -COMMENT ON TABLE general_messages IS '通用消息表,用于智能问答功能'; -``` - -**字段说明:** - -| 字段 | 类型 | 说明 | 约束 | -|------|------|------|------| -| id | VARCHAR(36) | 消息ID | PRIMARY KEY, UUID | -| conversation_id | VARCHAR(36) | 对话ID | NOT NULL, FK → general_conversations | -| role | VARCHAR(20) | 角色(user/assistant) | NOT NULL | -| content | TEXT | 消息内容 | NOT NULL | -| model | VARCHAR(50) | 使用的模型 | - | -| tokens | INTEGER | token消耗 | - | -| metadata | JSONB | 元数据(知识库IDs、使用统计等) | - | -| created_at | TIMESTAMP | 创建时间 | DEFAULT NOW() | - -**metadata结构示例:** -```json -{ - "knowledgeBaseIds": ["kb-uuid-1", "kb-uuid-2"], - "usage": { - "promptTokens": 150, - "completionTokens": 200, - "totalTokens": 350 - } -} -``` - ---- - -## 索引设计 - -### 主要索引 - -| 表名 | 索引字段 | 类型 | 用途 | -|------|---------|------|------| -| users | email | UNIQUE | 登录查询 | -| users | status | INDEX | 按状态筛选用户 | -| projects | user_id | INDEX | 查询用户的项目 | -| conversations | user_id | INDEX | 查询用户的对话 | -| conversations | project_id | INDEX | 查询项目的对话 | -| conversations | agent_id | INDEX | 统计智能体使用情况 | -| messages | conversation_id | INDEX | 查询对话消息 | -| knowledge_bases | user_id | INDEX | 查询用户的知识库 | -| documents | kb_id | INDEX | 查询知识库的文档 | -| documents | status | INDEX | 筛选处理状态 | -| general_conversations | user_id | INDEX | 查询用户的通用对话 | -| general_conversations | created_at | INDEX | 按时间排序 | -| general_messages | conversation_id | INDEX | 查询对话消息 | - -### 复合索引(如需优化性能可添加) - -```sql --- 按时间范围查询用户的对话 -CREATE INDEX idx_conversations_user_created - ON conversations(user_id, created_at DESC); - --- 按项目查询特定智能体的对话 -CREATE INDEX idx_conversations_project_agent - ON conversations(project_id, agent_id); -``` - ---- - -## 数据约束 - -### 业务约束 - -1. **知识库数量限制** - - 每个用户最多3个知识库 - - 通过CHECK约束和应用层双重控制 - -2. **文档数量限制** - - 每个知识库最多50个文档 - - 通过CHECK约束和应用层双重控制 - -3. **邮箱格式验证** - - 使用正则表达式验证邮箱格式 - -4. **密码安全** - - 使用bcrypt加密,成本因子12 - - 密码长度至少8位(应用层验证) - -### 数据完整性 - -1. **级联删除** - - 删除用户 → 级联删除其项目、对话、知识库 - - 删除项目 → 级联删除其对话 - - 删除知识库 → 级联删除其文档 - -2. **外键约束** - - 所有外键都设置了ON DELETE CASCADE - - 保证数据一致性 - ---- - -## 数据迁移策略 - -### 初始化迁移 - -```bash -# 创建初始迁移 -npx prisma migrate dev --name init - -# 生成Prisma Client -npx prisma generate -``` - -### 迁移命名规范 - -``` -yyyymmdd_描述.sql -例如: -20251010_init.sql -20251015_add_admin_logs.sql -20251020_add_user_quotas.sql -``` - -### 生产环境迁移流程 - -1. 在开发环境测试迁移 -2. 备份生产数据库 -3. 执行迁移脚本 -4. 验证数据完整性 -5. 回滚计划(如有问题) - ---- - -## Prisma Schema - -### 完整的schema.prisma - -```prisma -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") -} - -model User { - id String @id @default(uuid()) - email String @unique - password String - name String? - avatarUrl String? @map("avatar_url") - - role String @default("user") - status String @default("active") - - kbQuota Int @default(3) @map("kb_quota") - kbUsed Int @default(0) @map("kb_used") - - trialEndsAt DateTime? @map("trial_ends_at") - isTrial Boolean @default(true) @map("is_trial") - - lastLoginAt DateTime? @map("last_login_at") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - - projects Project[] - conversations Conversation[] - knowledgeBases KnowledgeBase[] - documents Document[] - adminLogs AdminLog[] - - @@index([email]) - @@index([status]) - @@index([createdAt]) - @@map("users") -} - -model Project { - id String @id @default(uuid()) - userId String @map("user_id") - name String - description String @db.Text - conversationCount Int @default(0) @map("conversation_count") - - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - conversations Conversation[] - - @@index([userId]) - @@index([createdAt]) - @@map("projects") -} - -model Conversation { - id String @id @default(uuid()) - userId String @map("user_id") - projectId String? @map("project_id") - agentId String @map("agent_id") - title String - modelName String @default("deepseek-v3") @map("model_name") - messageCount Int @default(0) @map("message_count") - totalTokens Int @default(0) @map("total_tokens") - - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade) - messages Message[] - - @@index([userId]) - @@index([projectId]) - @@index([agentId]) - @@index([createdAt]) - @@map("conversations") -} - -model Message { - id String @id @default(uuid()) - conversationId String @map("conversation_id") - role String - content String @db.Text - metadata Json? - tokens Int? - isPinned Boolean @default(false) @map("is_pinned") - - createdAt DateTime @default(now()) @map("created_at") - - conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) - - @@index([conversationId]) - @@index([createdAt]) - @@index([isPinned]) - @@map("messages") -} - -model KnowledgeBase { - id String @id @default(uuid()) - userId String @map("user_id") - name String - description String? - difyDatasetId String @map("dify_dataset_id") - fileCount Int @default(0) @map("file_count") - totalSizeBytes BigInt @default(0) @map("total_size_bytes") - - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - documents Document[] - - @@index([userId]) - @@index([difyDatasetId]) - @@map("knowledge_bases") -} - -model Document { - id String @id @default(uuid()) - kbId String @map("kb_id") - userId String @map("user_id") - filename String - fileType String @map("file_type") - fileSizeBytes BigInt @map("file_size_bytes") - fileUrl String @map("file_url") - difyDocumentId String @map("dify_document_id") - status String @default("uploading") - progress Int @default(0) - errorMessage String? @map("error_message") - segmentsCount Int? @map("segments_count") - tokensCount Int? @map("tokens_count") - - uploadedAt DateTime @default(now()) @map("uploaded_at") - processedAt DateTime? @map("processed_at") - - knowledgeBase KnowledgeBase @relation(fields: [kbId], references: [id], onDelete: Cascade) - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@index([kbId]) - @@index([userId]) - @@index([status]) - @@index([difyDocumentId]) - @@map("documents") -} - -model AdminLog { - id Int @id @default(autoincrement()) - adminId String @map("admin_id") - action String - resourceType String? @map("resource_type") - resourceId String? @map("resource_id") - details Json? - ipAddress String? @map("ip_address") - userAgent String? @map("user_agent") - - createdAt DateTime @default(now()) @map("created_at") - - admin User @relation(fields: [adminId], references: [id], onDelete: Cascade) - - @@index([adminId]) - @@index([createdAt]) - @@index([action]) - @@map("admin_logs") -} -``` - ---- - -## 数据字典 - -### 枚举值定义 - -**用户角色 (user.role)** -- `user` - 普通用户 -- `admin` - 管理员 - -**账户状态 (user.status)** -- `active` - 激活(正常使用) -- `inactive` - 未激活(注册但未验证邮箱) -- `suspended` - 暂停(违规或欠费) - -**消息角色 (message.role)** -- `user` - 用户消息 -- `assistant` - AI助手消息 - -**文档状态 (document.status)** -- `uploading` - 上传中 -- `processing` - Dify处理中 -- `completed` - 处理完成 -- `failed` - 处理失败 - -**文件类型 (document.file_type)** -- `pdf` - PDF文档 -- `docx` - Word文档 - ---- - -## 数据安全 - -### 敏感数据处理 - -1. **密码** - - 使用bcrypt加密(成本因子12) - - 不可逆加密,无法还原明文 - -2. **API Keys** - - 不存储在数据库中 - - 使用环境变量管理 - -3. **用户数据隔离** - - 所有查询必须包含user_id过滤 - - 防止越权访问 - -### 备份策略 - -1. **自动备份** - - 每日全量备份 - - 保留最近7天 - -2. **手动备份** - - 重大升级前手动备份 - - 保留3个版本 - ---- - -## 性能优化建议 - -### 查询优化 - -1. **分页查询** - - 使用 LIMIT + OFFSET - - 或使用游标分页(基于ID) - -2. **避免N+1查询** - - 使用Prisma的include和select - - 预加载关联数据 - -3. **合理使用索引** - - 频繁查询的字段建立索引 - - 定期检查索引使用情况 - -### 数据归档 - -1. **历史数据归档** - - 对话超过6个月自动归档 - - 归档数据移至单独的表 - -2. **日志清理** - - admin_logs保留3个月 - - 定期清理过期日志 - ---- - -## 版本变更记录 - -| 版本 | 日期 | 变更内容 | 作者 | -|------|------|---------|------| -| v1.0 | 2025-10-10 | 初始版本,定义所有核心表 | 开发团队 | - ---- - -**文档维护:** 数据库结构变更需同步更新本文档 -**Review频率:** 每个里程碑结束后Review一次 - - - - diff --git a/docs/02-通用能力层/01-LLM大模型网关/03-CloseAI集成指南.md b/docs/02-通用能力层/01-LLM大模型网关/03-CloseAI集成指南.md new file mode 100644 index 00000000..fdb72a41 --- /dev/null +++ b/docs/02-通用能力层/01-LLM大模型网关/03-CloseAI集成指南.md @@ -0,0 +1,524 @@ +# CloseAI集成指南 + +> **文档版本:** v1.0 +> **创建日期:** 2025-11-09 +> **用途:** 通过CloseAI代理平台访问OpenAI GPT-5和Claude-4.5 +> **适用场景:** AI智能文献双模型筛选、高质量文本生成 + +--- + +## 📋 CloseAI简介 + +### 什么是CloseAI? + +CloseAI是一个**API代理平台**,为中国用户提供稳定的OpenAI和Claude API访问服务。 + +**核心优势:** +- ✅ 国内直连,无需科学上网 +- ✅ 一个API Key同时调用OpenAI和Claude +- ✅ 兼容OpenAI SDK标准接口 +- ✅ 支持最新模型(GPT-5、Claude-4.5) + +**官网:** https://platform.openai-proxy.org + +--- + +## 🔧 配置信息 + +### 环境变量配置 + +```env +# CloseAI统一API Key +CLOSEAI_API_KEY=sk-cu0iepbXYGGx2jc7BqP6ogtSWmP6fk918qV3RUdtGC3Edlpo + +# OpenAI端点 +CLOSEAI_OPENAI_BASE_URL=https://api.openai-proxy.org/v1 + +# Claude端点 +CLOSEAI_CLAUDE_BASE_URL=https://api.openai-proxy.org/anthropic +``` + +### 支持的模型 + +| 模型 | Model ID | 说明 | 适用场景 | +|------|---------|------|---------| +| **GPT-5-Pro** | `gpt-5-pro` | 最新GPT-5 ⭐ | 文献精准筛选、复杂推理 | +| GPT-4-Turbo | `gpt-4-turbo-preview` | GPT-4高性能版 | 质量要求高的任务 | +| GPT-3.5-Turbo | `gpt-3.5-turbo` | 快速经济版 | 简单任务、成本优化 | +| **Claude-4.5-Sonnet** | `claude-sonnet-4-5-20250929` | 最新Claude ⭐ | 第三方仲裁、结构化输出 | +| Claude-3.5-Sonnet | `claude-3-5-sonnet-20241022` | Claude-3.5稳定版 | 高质量文本生成 | + +--- + +## 💻 代码集成 + +### 1. 安装依赖 + +```bash +npm install openai +``` + +### 2. 创建LLM服务类 + +**文件位置:** `backend/src/common/llm/closeai.service.ts` + +```typescript +import OpenAI from 'openai'; +import { config } from '../../config/env'; + +export class CloseAIService { + private openaiClient: OpenAI; + private claudeClient: OpenAI; + + constructor() { + // OpenAI客户端(通过CloseAI) + this.openaiClient = new OpenAI({ + apiKey: config.closeaiApiKey, + baseURL: config.closeaiOpenaiBaseUrl, + }); + + // Claude客户端(通过CloseAI) + this.claudeClient = new OpenAI({ + apiKey: config.closeaiApiKey, + baseURL: config.closeaiClaudeBaseUrl, + }); + } + + /** + * 调用GPT-5-Pro + */ + async chatWithGPT5(prompt: string, systemPrompt?: string) { + const messages: any[] = []; + + if (systemPrompt) { + messages.push({ role: 'system', content: systemPrompt }); + } + messages.push({ role: 'user', content: prompt }); + + const response = await this.openaiClient.chat.completions.create({ + model: 'gpt-5-pro', + messages, + temperature: 0.3, + max_tokens: 2000, + }); + + return { + content: response.choices[0].message.content, + usage: response.usage, + model: 'gpt-5-pro', + }; + } + + /** + * 调用Claude-4.5-Sonnet + */ + async chatWithClaude(prompt: string, systemPrompt?: string) { + const messages: any[] = []; + + if (systemPrompt) { + messages.push({ role: 'system', content: systemPrompt }); + } + messages.push({ role: 'user', content: prompt }); + + const response = await this.claudeClient.chat.completions.create({ + model: 'claude-sonnet-4-5-20250929', + messages, + temperature: 0.3, + max_tokens: 2000, + }); + + return { + content: response.choices[0].message.content, + usage: response.usage, + model: 'claude-sonnet-4-5-20250929', + }; + } + + /** + * 流式响应(GPT-5) + */ + async *streamGPT5(prompt: string, systemPrompt?: string) { + const messages: any[] = []; + + if (systemPrompt) { + messages.push({ role: 'system', content: systemPrompt }); + } + messages.push({ role: 'user', content: prompt }); + + const stream = await this.openaiClient.chat.completions.create({ + model: 'gpt-5-pro', + messages, + temperature: 0.3, + max_tokens: 2000, + stream: true, + }); + + for await (const chunk of stream) { + const content = chunk.choices[0]?.delta?.content || ''; + if (content) { + yield content; + } + } + } +} +``` + +### 3. 统一LLM服务(含4个模型) + +**文件位置:** `backend/src/common/llm/llm.service.ts` + +```typescript +import OpenAI from 'openai'; +import { config } from '../../config/env'; + +export type LLMProvider = 'deepseek' | 'gpt5' | 'claude' | 'qwen'; + +export class UnifiedLLMService { + private deepseek: OpenAI; + private gpt5: OpenAI; + private claude: OpenAI; + private qwen: OpenAI; + + constructor() { + // DeepSeek (直连) + this.deepseek = new OpenAI({ + apiKey: config.deepseekApiKey, + baseURL: config.deepseekBaseUrl, + }); + + // GPT-5 (通过CloseAI) + this.gpt5 = new OpenAI({ + apiKey: config.closeaiApiKey, + baseURL: config.closeaiOpenaiBaseUrl, + }); + + // Claude (通过CloseAI) + this.claude = new OpenAI({ + apiKey: config.closeaiApiKey, + baseURL: config.closeaiClaudeBaseUrl, + }); + + // Qwen (备用) + this.qwen = new OpenAI({ + apiKey: config.dashscopeApiKey, + baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1', + }); + } + + /** + * 统一调用接口 + */ + async chat( + provider: LLMProvider, + prompt: string, + options?: { + systemPrompt?: string; + temperature?: number; + maxTokens?: number; + } + ) { + const { systemPrompt, temperature = 0.3, maxTokens = 2000 } = options || {}; + + const messages: any[] = []; + if (systemPrompt) { + messages.push({ role: 'system', content: systemPrompt }); + } + messages.push({ role: 'user', content: prompt }); + + // 选择模型 + const modelMap = { + deepseek: { client: this.deepseek, model: 'deepseek-chat' }, + gpt5: { client: this.gpt5, model: 'gpt-5-pro' }, + claude: { client: this.claude, model: 'claude-sonnet-4-5-20250929' }, + qwen: { client: this.qwen, model: 'qwen-max' }, + }; + + const { client, model } = modelMap[provider]; + + const response = await client.chat.completions.create({ + model, + messages, + temperature, + max_tokens: maxTokens, + }); + + return { + content: response.choices[0].message.content || '', + usage: response.usage, + model, + provider, + }; + } +} +``` + +--- + +## 🎯 AI智能文献应用场景 + +### 场景1:双模型对比筛选(推荐)⭐ + +**策略:** DeepSeek(快速初筛) + GPT-5(质量复核) + +```typescript +export class LiteratureScreeningService { + private llm: UnifiedLLMService; + + constructor() { + this.llm = new UnifiedLLMService(); + } + + /** + * 双模型文献筛选 + */ + async screenLiterature(title: string, abstract: string, picoConfig: any) { + const prompt = ` +请根据以下PICO标准,判断这篇文献是否应该纳入: + +**PICO标准:** +- Population: ${picoConfig.population} +- Intervention: ${picoConfig.intervention} +- Comparison: ${picoConfig.comparison} +- Outcome: ${picoConfig.outcome} + +**文献信息:** +标题:${title} +摘要:${abstract} + +请输出JSON格式: +{ + "decision": "include/exclude/uncertain", + "reason": "判断理由", + "confidence": 0.0-1.0 +} + `; + + // 并行调用两个模型 + const [deepseekResult, gpt5Result] = await Promise.all([ + this.llm.chat('deepseek', prompt), + this.llm.chat('gpt5', prompt), + ]); + + // 解析结果 + const deepseekDecision = JSON.parse(deepseekResult.content); + const gpt5Decision = JSON.parse(gpt5Result.content); + + // 如果两个模型一致,直接采纳 + if (deepseekDecision.decision === gpt5Decision.decision) { + return { + finalDecision: deepseekDecision.decision, + consensus: 'high', + models: [deepseekDecision, gpt5Decision], + }; + } + + // 如果不一致,返回双方意见,待人工复核 + return { + finalDecision: 'uncertain', + consensus: 'low', + models: [deepseekDecision, gpt5Decision], + needManualReview: true, + }; + } +} +``` + +### 场景2:三模型共识仲裁 + +**策略:** 当两个模型冲突时,启用Claude作为第三方仲裁 + +```typescript +async screenWithArbitration(title: string, abstract: string, picoConfig: any) { + // 第一轮:双模型筛选 + const initialScreen = await this.screenLiterature(title, abstract, picoConfig); + + // 如果一致,直接返回 + if (initialScreen.consensus === 'high') { + return initialScreen; + } + + // 如果不一致,启用Claude仲裁 + console.log('双模型结果不一致,启用Claude仲裁...'); + + const claudeResult = await this.llm.chat('claude', prompt); + const claudeDecision = JSON.parse(claudeResult.content); + + // 三模型投票 + const decisions = [ + initialScreen.models[0].decision, + initialScreen.models[1].decision, + claudeDecision.decision, + ]; + + const voteCount = { + include: decisions.filter(d => d === 'include').length, + exclude: decisions.filter(d => d === 'exclude').length, + uncertain: decisions.filter(d => d === 'uncertain').length, + }; + + // 多数决 + const finalDecision = Object.keys(voteCount).reduce((a, b) => + voteCount[a] > voteCount[b] ? a : b + ); + + return { + finalDecision, + consensus: voteCount[finalDecision] >= 2 ? 'medium' : 'low', + models: [...initialScreen.models, claudeDecision], + arbitration: true, + }; +} +``` + +### 场景3:成本优化策略 + +**策略:** 只对不确定的结果使用GPT-5复核 + +```typescript +async screenWithCostOptimization(title: string, abstract: string, picoConfig: any) { + // 第一轮:用DeepSeek快速初筛(便宜) + const quickScreen = await this.llm.chat('deepseek', prompt); + const quickDecision = JSON.parse(quickScreen.content); + + // 如果结果明确(include或exclude且置信度>0.8),直接采纳 + if (quickDecision.confidence > 0.8 && quickDecision.decision !== 'uncertain') { + return { + finalDecision: quickDecision.decision, + consensus: 'high', + models: [quickDecision], + costOptimized: true, + }; + } + + // 否则,用GPT-5复核 + const detailedScreen = await this.llm.chat('gpt5', prompt); + const detailedDecision = JSON.parse(detailedScreen.content); + + return { + finalDecision: detailedDecision.decision, + consensus: 'medium', + models: [quickDecision, detailedDecision], + costOptimized: true, + }; +} +``` + +--- + +## 📊 性能和成本对比 + +### 模型性能对比 + +| 指标 | DeepSeek-V3 | GPT-5-Pro | Claude-4.5 | Qwen-Max | +|------|------------|-----------|-----------|----------| +| **准确率** | 85% | **95%** ⭐ | 93% | 82% | +| **速度** | **快** ⭐ | 中等 | 中等 | 快 | +| **成本** | **¥0.001/1K** ⭐ | ¥0.10/1K | ¥0.021/1K | ¥0.004/1K | +| **中文理解** | **优秀** ⭐ | 优秀 | 良好 | 优秀 | +| **结构化输出** | 良好 | 优秀 | **优秀** ⭐ | 良好 | + +### 筛选1000篇文献的成本估算 + +**策略A:只用DeepSeek** +- 成本:¥20-30 +- 准确率:85% +- 适用:预算有限,可接受一定误差 + +**策略B:DeepSeek + GPT-5 双模型** +- 成本:¥150-200 +- 准确率:92% +- 适用:质量要求高,预算充足 ⭐ 推荐 + +**策略C:三模型共识(20%冲突启用Claude)** +- 成本:¥180-220 +- 准确率:95% +- 适用:最高质量要求 + +**策略D:成本优化(80%用DeepSeek,20%用GPT-5)** +- 成本:¥50-80 +- 准确率:90% +- 适用:质量和成本平衡 ⭐ 性价比最高 + +--- + +## ⚠️ 注意事项 + +### 1. API Key安全 + +```typescript +// ❌ 错误:硬编码API Key +const client = new OpenAI({ + apiKey: 'sk-cu0iepbXYGGx2jc7BqP6ogtSWmP6fk918qV3RUdtGC3Edlpo', +}); + +// ✅ 正确:从环境变量读取 +const client = new OpenAI({ + apiKey: process.env.CLOSEAI_API_KEY, +}); +``` + +### 2. 错误处理 + +```typescript +async chat(provider: LLMProvider, prompt: string) { + try { + const response = await this.llm.chat(provider, prompt); + return response; + } catch (error) { + // CloseAI可能返回的错误 + if (error.status === 429) { + // 速率限制 + console.error('API调用速率超限,请稍后重试'); + } else if (error.status === 401) { + // 认证失败 + console.error('API Key无效,请检查配置'); + } else if (error.status === 500) { + // 服务端错误 + console.error('CloseAI服务异常,请稍后重试'); + } + throw error; + } +} +``` + +### 3. 请求重试 + +```typescript +async chatWithRetry(provider: LLMProvider, prompt: string, maxRetries = 3) { + for (let i = 0; i < maxRetries; i++) { + try { + return await this.llm.chat(provider, prompt); + } catch (error) { + if (i === maxRetries - 1) throw error; + + // 指数退避 + const delay = Math.pow(2, i) * 1000; + await new Promise(resolve => setTimeout(resolve, delay)); + } + } +} +``` + +--- + +## 📚 相关文档 + +- [环境配置指南](../../07-运维文档/01-环境配置指南.md#3-closeai配置代理openai和claude) +- [环境变量配置模板](../../07-运维文档/02-环境变量配置模板.md) +- [LLM网关快速上下文](./[AI对接]%20LLM网关快速上下文.md) + +--- + +**更新日志:** +- 2025-11-09: 创建文档,添加CloseAI集成指南 +- 支持GPT-5-Pro和Claude-4.5-Sonnet最新模型 + + + + + + + + + + diff --git a/docs/02-通用能力层/01-LLM大模型网关/README.md b/docs/02-通用能力层/01-LLM大模型网关/README.md new file mode 100644 index 00000000..52a647ac --- /dev/null +++ b/docs/02-通用能力层/01-LLM大模型网关/README.md @@ -0,0 +1,149 @@ +# LLM大模型网关 + +> **能力定位:** 通用能力层核心能力 +> **复用率:** 71% (5个模块依赖) +> **优先级:** P0(最高)⭐ +> **状态:** ❌ 待实现 + +--- + +## 📋 能力概述 + +LLM大模型网关是平台AI能力的核心中枢,负责: +- 统一管理所有LLM调用 +- 根据用户版本动态切换模型 +- 成本控制与限流 +- Token计数与计费 + +--- + +## 🎯 核心价值 + +### 1. 商业模式技术基础 ⭐ +``` +专业版 → DeepSeek-V3(便宜,¥1/百万tokens) +高级版 → DeepSeek + Qwen3 +旗舰版 → DeepSeek + Qwen3 + Qwen-Long + Claude +``` + +### 2. 成本控制 +- 统一监控所有LLM API调用 +- 超出配额自动限流 +- 按版本计费 + +### 3. 统一接口 +- 屏蔽不同LLM API的差异 +- 统一的调用接口 + +--- + +## 📊 依赖模块 + +**5个模块依赖(71%复用率):** +1. **AIA** - AI智能问答 +2. **ASL** - AI智能文献(双模型判断) +3. **PKB** - 个人知识库(RAG问答) +4. **DC** - 数据清洗(NER提取) +5. **RVW** - 稿件审查(AI评估) + +--- + +## 💡 核心功能 + +### 1. 模型选择 +```typescript +selectModel(userId: string, preferredModel?: string): string +// 根据用户版本和配额选择合适的模型 +``` + +### 2. 统一调用 +```typescript +chat(params: { + userId: string; + modelType?: ModelType; + messages: Message[]; + stream?: boolean; +}): Promise +``` + +### 3. 配额管理 +```typescript +checkQuota(userId: string): Promise +// 检查用户剩余配额 +``` + +### 4. Token计数 +```typescript +countTokens(text: string): number +// 使用tiktoken计算Token数 +``` + +--- + +## 📂 文档结构 + +``` +01-LLM大模型网关/ + ├── [AI对接] LLM网关快速上下文.md # ✅ 已完成 + ├── 03-CloseAI集成指南.md # ✅ 已完成 ⭐ + ├── 00-需求分析/ + │ └── README.md + ├── 01-设计文档/ + │ ├── 01-详细设计.md # ⏳ Week 5创建 + │ ├── 02-数据库设计.md # ⏳ Week 5创建 + │ ├── 03-API设计.md # ⏳ Week 5创建 + │ └── README.md + └── README.md # ✅ 当前文档 +``` + +### 快速入门文档 ⭐ + +| 文档 | 说明 | 状态 | +|------|------|------| +| **[AI对接] LLM网关快速上下文.md** | 快速了解LLM网关设计 | ✅ 已完成 | +| **03-CloseAI集成指南.md** | CloseAI(GPT-5+Claude-4.5)集成文档 ⭐ | ✅ 已完成 | + +--- + +## ⚠️ 开发计划调整 + +### 原计划:Week 2完成LLM网关 +**调整:** LLM网关完整实现推迟到Week 5 ✅ + +**理由:** +1. 现有LLM调用已经work(DeepSeek、Qwen) +2. CloseAI集成配置已完成,可直接使用 +3. ASL开发不阻塞,先用简单调用 +4. Week 5有多个模块实践后,再抽取统一网关更合理 + +### 当前可用(Week 3 ASL开发)✅ +- ✅ DeepSeek API(直连) +- ✅ GPT-5-Pro API(CloseAI代理) +- ✅ Claude-4.5 API(CloseAI代理) +- ✅ Qwen API(DashScope) +- ✅ 4个模型的基础调用代码示例 + +### Week 5完善(LLM网关统一) +- 统一调用接口 +- 版本分级(专业版/高级版/旗舰版) +- 配额管理和限流 +- Token计数和计费 +- 使用记录和监控 + +--- + +## 🔗 相关文档 + +- [通用能力层总览](../README.md) +- [系统架构分层设计](../../00-系统总体设计/01-系统架构分层设计.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + diff --git a/docs/02-通用能力层/01-LLM大模型网关/[AI对接] LLM网关快速上下文.md b/docs/02-通用能力层/01-LLM大模型网关/[AI对接] LLM网关快速上下文.md new file mode 100644 index 00000000..6ab3f769 --- /dev/null +++ b/docs/02-通用能力层/01-LLM大模型网关/[AI对接] LLM网关快速上下文.md @@ -0,0 +1,535 @@ +# [AI对接] LLM网关快速上下文 + +> **阅读时间:** 5分钟 | **Token消耗:** ~2000 tokens +> **层级:** L2 | **优先级:** P0 ⭐⭐⭐⭐⭐ +> **前置阅读:** 02-通用能力层/[AI对接] 通用能力快速上下文.md + +--- + +## 📋 能力定位 + +**LLM大模型网关是整个平台的AI调用中枢,是商业模式的技术基础。** + +**为什么是P0优先级:** +- 71%的业务模块依赖(5个模块:AIA、ASL、PKB、DC、RVW) +- ASL模块开发的**前置条件** +- 商业模式的**技术基础**(Feature Flag + 成本控制) + +**状态:** ❌ 待实现 +**建议时间:** ASL Week 1(Day 1-3)同步开发 + +--- + +## 🎯 核心功能 + +### 1. 根据用户版本选择模型 ⭐⭐⭐⭐⭐ + +**商业价值:** +``` +专业版(¥99/月)→ DeepSeek-V3(¥1/百万tokens) +高级版(¥299/月)→ DeepSeek + Qwen3-72B(¥5/百万tokens) +旗舰版(¥999/月)→ 全部模型(含Claude/GPT) +``` + +**实现方式:** +```typescript +// 查询用户Feature Flag +const userFlags = await featureFlagService.getUserFlags(userId); + +// 根据Feature Flag选择可用模型 +if (requestModel === 'claude-3.5' && !userFlags.includes('claude_access')) { + throw new Error('您的套餐不支持Claude模型,请升级到旗舰版'); +} + +// 或自动降级 +if (!userFlags.includes('claude_access')) { + model = 'deepseek-v3'; // 自动降级到DeepSeek +} +``` + +--- + +### 2. 统一调用接口 ⭐⭐⭐⭐⭐ + +**问题:** 不同LLM厂商API格式不同 +- OpenAI格式 +- Anthropic格式 +- 国产大模型格式(DeepSeek、Qwen) + +**解决方案:** 统一接口 + 适配器模式 + +```typescript +// 业务模块统一调用 +const response = await llmGateway.chat({ + userId: 'user123', + modelType: 'deepseek-v3', // 或 'qwen3', 'claude-3.5' + messages: [ + { role: 'user', content: '帮我分析这篇文献...' } + ], + stream: false +}); + +// LLM网关内部: +// 1. 检查用户权限(Feature Flag) +// 2. 检查配额 +// 3. 选择对应的适配器 +// 4. 调用API +// 5. 记录成本 +// 6. 返回统一格式 +``` + +--- + +### 3. 成本控制 ⭐⭐⭐⭐ + +**核心需求:** +- 每个用户有月度配额 +- 超出配额自动限流 +- 实时成本统计 + +**实现:** +```typescript +// 调用前检查配额 +async function checkQuota(userId: string): Promise { + const usage = await getMonthlyUsage(userId); + const quota = await getUserQuota(userId); + + if (usage.tokenCount >= quota.maxTokens) { + throw new QuotaExceededError('您的月度配额已用完,请升级套餐'); + } + + return true; +} + +// 调用后记录成本 +async function recordUsage(userId: string, usage: { + modelType: string; + tokenCount: number; + cost: number; +}) { + await db.llmUsage.create({ + userId, + modelType, + inputTokens: usage.tokenCount, + cost: usage.cost, + timestamp: new Date() + }); +} +``` + +--- + +### 4. 流式/非流式统一处理 ⭐⭐⭐ + +**场景:** +- AIA智能问答 → 需要流式输出(实时显示) +- ASL文献筛选 → 非流式(批量处理) + +**统一接口:** +```typescript +interface ChatOptions { + userId: string; + modelType: ModelType; + messages: Message[]; + stream: boolean; // 是否流式输出 + temperature?: number; + maxTokens?: number; +} + +// 流式 +const stream = await llmGateway.chat({ ...options, stream: true }); +for await (const chunk of stream) { + console.log(chunk.content); +} + +// 非流式 +const response = await llmGateway.chat({ ...options, stream: false }); +console.log(response.content); +``` + +--- + +## 🏗️ 技术架构 + +### 目录结构 +``` +backend/src/modules/llm-gateway/ + ├── controllers/ + │ └── llmController.ts # HTTP接口 + ├── services/ + │ ├── llmGatewayService.ts # 核心服务 ⭐ + │ ├── featureFlagService.ts # Feature Flag查询 + │ ├── quotaService.ts # 配额管理 + │ └── usageService.ts # 使用统计 + ├── adapters/ # 适配器模式 ⭐ + │ ├── baseAdapter.ts + │ ├── deepseekAdapter.ts + │ ├── qwenAdapter.ts + │ ├── claudeAdapter.ts + │ └── openaiAdapter.ts + ├── types/ + │ └── llm.types.ts + └── routes/ + └── llmRoutes.ts +``` + +--- + +### 核心类设计 + +#### 1. LLMGatewayService(核心) +```typescript +class LLMGatewayService { + private adapters: Map; + + async chat(options: ChatOptions): Promise { + // 1. 验证用户权限(Feature Flag) + await this.checkAccess(options.userId, options.modelType); + + // 2. 检查配额 + await quotaService.checkQuota(options.userId); + + // 3. 选择适配器 + const adapter = this.adapters.get(options.modelType); + + // 4. 调用LLM API + const response = await adapter.chat(options); + + // 5. 记录使用量 + await usageService.record({ + userId: options.userId, + modelType: options.modelType, + tokenCount: response.tokenUsage, + cost: this.calculateCost(options.modelType, response.tokenUsage) + }); + + // 6. 返回结果 + return response; + } + + private calculateCost(modelType: ModelType, tokens: number): number { + const prices = { + 'deepseek-v3': 0.000001, // ¥1/百万tokens + 'qwen3-72b': 0.000005, // ¥5/百万tokens + 'claude-3.5': 0.00003 // $15/百万tokens ≈ ¥0.0003/千tokens + }; + return tokens * prices[modelType]; + } +} +``` + +#### 2. BaseLLMAdapter(适配器基类) +```typescript +abstract class BaseLLMAdapter { + abstract chat(options: ChatOptions): Promise; + abstract chatStream(options: ChatOptions): AsyncIterator; + + protected abstract buildRequest(options: ChatOptions): any; + protected abstract parseResponse(response: any): ChatResponse; +} +``` + +#### 3. DeepSeekAdapter(实现示例) +```typescript +class DeepSeekAdapter extends BaseLLMAdapter { + private apiKey: string; + private baseUrl = 'https://api.deepseek.com/v1'; + + async chat(options: ChatOptions): Promise { + const request = this.buildRequest(options); + + const response = await fetch(`${this.baseUrl}/chat/completions`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(request) + }); + + const data = await response.json(); + return this.parseResponse(data); + } + + protected buildRequest(options: ChatOptions) { + return { + model: 'deepseek-chat', + messages: options.messages, + temperature: options.temperature || 0.7, + max_tokens: options.maxTokens || 4096, + stream: options.stream || false + }; + } + + protected parseResponse(response: any): ChatResponse { + return { + content: response.choices[0].message.content, + tokenUsage: response.usage.total_tokens, + finishReason: response.choices[0].finish_reason + }; + } +} +``` + +--- + +## 📊 数据库设计 + +### platform_schema.llm_usage +```sql +CREATE TABLE platform_schema.llm_usage ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES platform_schema.users(id), + model_type VARCHAR(50) NOT NULL, -- 'deepseek-v3', 'qwen3', 'claude-3.5' + input_tokens INTEGER NOT NULL, + output_tokens INTEGER NOT NULL, + total_tokens INTEGER NOT NULL, + cost DECIMAL(10, 6) NOT NULL, -- 实际成本(人民币) + request_id VARCHAR(100), -- LLM API返回的request_id + module VARCHAR(50), -- 哪个模块调用的:'AIA', 'ASL', 'PKB'等 + created_at TIMESTAMP DEFAULT NOW(), + + INDEX idx_user_created (user_id, created_at), + INDEX idx_module (module) +); +``` + +### platform_schema.llm_quotas +```sql +CREATE TABLE platform_schema.llm_quotas ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES platform_schema.users(id) UNIQUE, + monthly_token_limit INTEGER NOT NULL, -- 月度token配额 + monthly_cost_limit DECIMAL(10, 2), -- 月度成本上限(可选) + reset_day INTEGER DEFAULT 1, -- 每月重置日期(1-28) + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +``` + +--- + +## 📋 API端点 + +### 1. 聊天接口(非流式) +``` +POST /api/v1/llm/chat + +Request: +{ + "modelType": "deepseek-v3", + "messages": [ + { "role": "user", "content": "分析这篇文献..." } + ], + "temperature": 0.7, + "maxTokens": 4096 +} + +Response: +{ + "content": "根据文献内容分析...", + "tokenUsage": { + "input": 150, + "output": 500, + "total": 650 + }, + "cost": 0.00065, + "modelType": "deepseek-v3" +} +``` + +### 2. 聊天接口(流式) +``` +POST /api/v1/llm/chat/stream + +Request: 同上 + "stream": true + +Response: Server-Sent Events (SSE) +data: {"chunk": "根据", "tokenUsage": 1} +data: {"chunk": "文献", "tokenUsage": 1} +... +data: {"done": true, "totalTokens": 650, "cost": 0.00065} +``` + +### 3. 查询配额 +``` +GET /api/v1/llm/quota + +Response: +{ + "monthlyLimit": 1000000, + "used": 245000, + "remaining": 755000, + "resetDate": "2025-12-01" +} +``` + +### 4. 使用统计 +``` +GET /api/v1/llm/usage?startDate=2025-11-01&endDate=2025-11-30 + +Response: +{ + "totalTokens": 245000, + "totalCost": 1.23, + "byModel": { + "deepseek-v3": { "tokens": 200000, "cost": 0.20 }, + "qwen3-72b": { "tokens": 45000, "cost": 0.23 } + }, + "byModule": { + "AIA": 100000, + "ASL": 120000, + "PKB": 25000 + } +} +``` + +--- + +## ⚠️ 关键技术难点 + +### 1. 流式输出的实现 +**技术方案:** Server-Sent Events (SSE) + +```typescript +// 后端(Fastify) +app.post('/api/v1/llm/chat/stream', async (req, reply) => { + reply.raw.setHeader('Content-Type', 'text/event-stream'); + reply.raw.setHeader('Cache-Control', 'no-cache'); + reply.raw.setHeader('Connection', 'keep-alive'); + + const stream = await llmGateway.chatStream(req.body); + + for await (const chunk of stream) { + reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`); + } + + reply.raw.end(); +}); + +// 前端(React) +const eventSource = new EventSource('/api/v1/llm/chat/stream'); +eventSource.onmessage = (event) => { + const data = JSON.parse(event.data); + setMessages(prev => [...prev, data.chunk]); +}; +``` + +--- + +### 2. 错误处理和重试 +```typescript +async function chatWithRetry(options: ChatOptions, maxRetries = 3) { + for (let i = 0; i < maxRetries; i++) { + try { + return await llmGateway.chat(options); + } catch (error) { + if (error.code === 'RATE_LIMIT' && i < maxRetries - 1) { + await sleep(2000 * (i + 1)); // 指数退避 + continue; + } + throw error; + } + } +} +``` + +--- + +### 3. Token计数(精确计费) +**问题:** 不同模型的tokenizer不同 + +**解决方案:** +- 使用各厂商提供的API返回值(最准确) +- 备用方案:tiktoken库(OpenAI tokenizer) + +```typescript +import { encoding_for_model } from 'tiktoken'; + +function estimateTokens(text: string, model: string): number { + const encoder = encoding_for_model(model); + const tokens = encoder.encode(text); + encoder.free(); + return tokens.length; +} +``` + +--- + +## 📅 开发计划(3天) + +### Day 1:基础架构(6-8小时) +- [ ] 创建目录结构 +- [ ] 实现BaseLLMAdapter抽象类 +- [ ] 实现DeepSeekAdapter +- [ ] 数据库表创建(llm_usage, llm_quotas) +- [ ] 基础API端点(非流式) + +### Day 2:核心功能(6-8小时) +- [ ] Feature Flag集成 +- [ ] 配额检查和记录 +- [ ] 实现QwenAdapter +- [ ] 错误处理和重试机制 +- [ ] 单元测试 + +### Day 3:流式输出 + 优化(6-8小时) +- [ ] 实现流式输出(SSE) +- [ ] 前端SSE接收处理 +- [ ] 成本统计API +- [ ] 配额查询API +- [ ] 集成测试 +- [ ] 文档完善 + +--- + +## ✅ 开发检查清单 + +**开始前确认:** +- [ ] Feature Flag表已创建(platform_schema.feature_flags) +- [ ] 用户表已有version字段(professional/premium/enterprise) +- [ ] 各LLM厂商API Key已配置 +- [ ] Prisma Schema已更新 + +**开发中:** +- [ ] 每个适配器都有完整的错误处理 +- [ ] 所有LLM调用都记录到llm_usage表 +- [ ] 配额检查在每次调用前执行 +- [ ] 流式和非流式都已测试 + +**完成后:** +- [ ] ASL模块可以成功调用LLM网关 +- [ ] ADMIN可以查看用户LLM使用统计 +- [ ] 配额超限会正确拒绝请求 + +--- + +## 🔗 相关文档 + +**依赖:** +- [用户与权限中心(UAM)](../../01-平台基础层/01-用户与权限中心(UAM)/README.md) - Feature Flag +- [运营管理端](../../03-业务模块/ADMIN-运营管理端/README.md) - LLM模型管理 + +**被依赖:** +- [ASL-AI智能文献](../../03-业务模块/ASL-AI智能文献/README.md) ⭐ P0 +- [AIA-AI智能问答](../../03-业务模块/AIA-AI智能问答/README.md) +- [PKB-个人知识库](../../03-业务模块/PKB-个人知识库/README.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 +**优先级:** P0 ⭐⭐⭐⭐⭐ + + + + + + + + + + + + + + diff --git a/docs/02-通用能力层/02-文档处理引擎/README.md b/docs/02-通用能力层/02-文档处理引擎/README.md new file mode 100644 index 00000000..a1159641 --- /dev/null +++ b/docs/02-通用能力层/02-文档处理引擎/README.md @@ -0,0 +1,107 @@ +# 文档处理引擎 + +> **能力定位:** 通用能力层 +> **复用率:** 86% (6个模块依赖) +> **优先级:** P0 +> **状态:** ✅ 已实现(Python微服务) + +--- + +## 📋 能力概述 + +文档处理引擎是平台的核心基础能力,负责: +- 多格式文档文本提取(PDF、Docx、Txt、Excel) +- OCR处理 +- 表格提取 +- 语言检测 +- 质量评估 + +--- + +## 📊 依赖模块 + +**6个模块依赖(86%复用率):** +1. **ASL** - AI智能文献(文献PDF提取) +2. **PKB** - 个人知识库(知识库文档上传) +3. **DC** - 数据清洗(Excel/Docx数据导入) +4. **SSA** - 智能统计分析(数据导入) +5. **ST** - 统计分析工具(数据导入) +6. **RVW** - 稿件审查(稿件文档提取) + +--- + +## 💡 核心功能 + +### 1. PDF提取 +- **Nougat**:英文学术论文(高质量) +- **PyMuPDF**:中文PDF + 兜底方案(快速) +- **语言检测**:自动识别中英文 +- **质量评估**:提取质量评分 + +### 2. Docx提取 +- **Mammoth**:转Markdown +- **python-docx**:结构化读取 + +### 3. Txt提取 +- **多编码支持**:UTF-8、GBK等 +- **chardet**:自动检测编码 + +### 4. Excel处理 +- **openpyxl**:读取Excel +- **pandas**:数据处理 + +--- + +## 🏗️ 技术架构 + +**Python微服务(FastAPI):** +``` +extraction_service/ + ├── main.py (509行) - FastAPI主服务 + ├── services/ + │ ├── pdf_extractor.py (242行) - PDF提取总协调 + │ ├── pdf_processor.py (280行) - PyMuPDF实现 + │ ├── language_detector.py (120行) - 语言检测 + │ ├── nougat_extractor.py (242行) - Nougat实现 + │ ├── docx_extractor.py (253行) - Docx提取 + │ └── txt_extractor.py (316行) - Txt提取(多编码) + └── requirements.txt +``` + +--- + +## 📚 API端点 + +``` +POST /api/extract/pdf - PDF文本提取 +POST /api/extract/docx - Docx文本提取 +POST /api/extract/txt - Txt文本提取 +POST /api/extract/excel - Excel表格提取 +GET /health - 健康检查 +``` + +--- + +## 🔗 相关文档 + +- [通用能力层总览](../README.md) +- [Python微服务代码](../../../extraction_service/) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/02-通用能力层/03-RAG引擎/README.md b/docs/02-通用能力层/03-RAG引擎/README.md new file mode 100644 index 00000000..9df6eb5a --- /dev/null +++ b/docs/02-通用能力层/03-RAG引擎/README.md @@ -0,0 +1,102 @@ +# RAG引擎 + +> **能力定位:** 通用能力层 +> **复用率:** 43% (3个模块依赖) +> **优先级:** P1 +> **状态:** ✅ 已实现(基于Dify) + +--- + +## 📋 能力概述 + +RAG引擎负责: +- 向量化存储(Embedding) +- 语义检索(Semantic Search) +- 检索增强生成(RAG) +- Rerank重排序 + +--- + +## 📊 依赖模块 + +**3个模块依赖(43%复用率):** +1. **AIA** - AI智能问答(@知识库问答) +2. **ASL** - AI智能文献(文献内容检索) +3. **PKB** - 个人知识库(RAG问答) + +--- + +## 💡 核心功能 + +### 1. 向量化存储 +- 基于Dify平台 +- Qdrant向量数据库(Dify内置) + +### 2. 语义检索 +- Top-K检索 +- 相关度评分 +- 多知识库联合检索 + +### 3. RAG问答 +- 检索 + 生成 +- 智能引用系统(100%准确溯源) + +--- + +## 🏗️ 技术架构 + +**基于Dify平台:** +```typescript +// DifyClient封装 +interface RAGEngine { + // 创建知识库 + createDataset(name: string): Promise; + + // 上传文档 + uploadDocument(datasetId: string, file: File): Promise; + + // 语义检索 + search(datasetId: string, query: string, topK?: number): Promise; + + // RAG问答 + chatWithRAG(datasetId: string, query: string): Promise; +} +``` + +--- + +## 📈 优化成果 + +**检索参数优化:** +| 指标 | 优化前 | 优化后 | 提升 | +|------|--------|--------|------| +| 检索数量 | 3 chunks | 15 chunks | 5倍 | +| Chunk大小 | 500 tokens | 1500 tokens | 3倍 | +| 总覆盖 | 1,500 tokens | 22,500 tokens | 15倍 | +| 覆盖率 | ~5% | ~40-50% | 8-10倍 | + +--- + +## 🔗 相关文档 + +- [通用能力层总览](../README.md) +- [Dify集成文档](../../00-系统总体设计/03-数据库架构说明.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/02-通用能力层/04-数据ETL引擎/README.md b/docs/02-通用能力层/04-数据ETL引擎/README.md new file mode 100644 index 00000000..6b9b01d0 --- /dev/null +++ b/docs/02-通用能力层/04-数据ETL引擎/README.md @@ -0,0 +1,88 @@ +# 数据ETL引擎 + +> **能力定位:** 通用能力层 +> **复用率:** 29% (2个模块依赖) +> **优先级:** P2 +> **状态:** ⏳ 待实现 + +--- + +## 📋 能力概述 + +数据ETL引擎负责: +- Excel多表JOIN +- 数据清洗 +- 数据转换 +- 数据验证 + +--- + +## 📊 依赖模块 + +**2个模块依赖(29%复用率):** +1. **DC** - 数据清洗整理(核心依赖) +2. **SSA** - 智能统计分析(数据预处理) + +--- + +## 💡 核心功能 + +### 1. Excel多表处理 +- 读取多个Excel文件 +- 自动JOIN操作 +- GROUP BY聚合 + +### 2. 数据清洗 +- 缺失值处理 +- 重复值处理 +- 异常值检测 + +### 3. 数据转换 +- 类型转换 +- 格式标准化 + +--- + +## 🏗️ 技术方案 + +### 云端版(最优) +```python +# 基于Polars(性能极高) +class ETLEngine: + def read_excel(self, files: List[File]) -> List[DataFrame] + def join(self, dfs: List[DataFrame], keys: List[str]) -> DataFrame + def clean(self, df: DataFrame, rules: Dict) -> DataFrame + def export(self, df: DataFrame, format: str) -> bytes +``` + +### 单机版(兼容) +```python +# 基于SQLite(内存友好) +# 分块读取,数据库引擎处理JOIN +``` + +--- + +## 🔗 相关文档 + +- [通用能力层总览](../README.md) +- [DC模块需求](../../03-业务模块/DC-数据清洗整理/README.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/02-通用能力层/05-医学NLP引擎/README.md b/docs/02-通用能力层/05-医学NLP引擎/README.md new file mode 100644 index 00000000..f1fbd685 --- /dev/null +++ b/docs/02-通用能力层/05-医学NLP引擎/README.md @@ -0,0 +1,82 @@ +# 医学NLP引擎 + +> **能力定位:** 通用能力层 +> **复用率:** 14% (1个模块依赖) +> **优先级:** P2 +> **状态:** ⏳ 待实现 + +--- + +## 📋 能力概述 + +医学NLP引擎负责: +- 医学实体识别(NER) +- 医学术语标准化 +- 疾病/药物识别 + +--- + +## 📊 依赖模块 + +**1个模块依赖(14%复用率):** +1. **DC** - 数据清洗整理(病例数据NER提取) + +--- + +## 💡 核心功能 + +### 1. 医学实体识别 +- 疾病识别 +- 药物识别 +- 手术识别 +- TNM分期提取 + +### 2. 术语标准化 +- ICD编码 +- ATC编码 + +### 3. 关系抽取 +- 疾病-药物关系 +- 症状-疾病关系 + +--- + +## 🏗️ 技术方案 + +### 云端版(高准确率) +```python +# 基于LLM API(Claude/GPT) +# JSON Mode结构化输出 +``` + +### 单机版(隐私优先) +```python +# 基于spaCy + 医学模型 +# 100%本地运行 +``` + +--- + +## 🔗 相关文档 + +- [通用能力层总览](../README.md) +- [DC模块需求](../../03-业务模块/DC-数据清洗整理/README.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/02-通用能力层/README.md b/docs/02-通用能力层/README.md new file mode 100644 index 00000000..db306435 --- /dev/null +++ b/docs/02-通用能力层/README.md @@ -0,0 +1,95 @@ +# 通用能力层 + +> **层级定义:** 跨业务模块共享的核心技术能力 +> **核心原则:** 可复用、高内聚、独立部署 + +--- + +## 📋 能力清单 + +| 能力 | 说明 | 复用率 | 优先级 | 状态 | +|------|------|-------|--------|------| +| **01-LLM大模型网关** | 统一管理LLM调用、成本控制、模型切换 | 71% (5/7) | P0 | ⏳ 待实现 | +| **02-文档处理引擎** | PDF/Docx/Txt提取、OCR、表格提取 | 86% (6/7) | P0 | ✅ 已实现 | +| **03-RAG引擎** | 向量检索、语义搜索、RAG问答 | 43% (3/7) | P1 | ✅ 已实现 | +| **04-数据ETL引擎** | Excel JOIN、数据清洗、数据转换 | 29% (2/7) | P2 | ⏳ 待实现 | +| **05-医学NLP引擎** | 医学实体识别、术语标准化 | 14% (1/7) | P2 | ⏳ 待实现 | + +--- + +## 🎯 设计原则 + +### 1. 可复用性 +- 多个业务模块共享 +- 避免重复开发 + +### 2. 独立部署 +- 可以独立为微服务 +- 支持独立扩展 + +### 3. 高内聚 +- 每个能力职责单一 +- 接口清晰 + +### 4. 领域知识 +- 包含业务领域知识 +- 不是纯技术组件 + +--- + +## 📊 复用率分析 + +**LLM网关** - 71%复用率(最高优先级) +- AIA(AI智能问答) +- ASL(AI智能文献) +- PKB(个人知识库) +- DC(数据清洗) +- RVW(稿件审查) + +**文档处理引擎** - 86%复用率(已实现) +- ASL、PKB、DC、SSA、ST、RVW + +**RAG引擎** - 43%复用率(已实现) +- AIA、ASL、PKB + +--- + +## 📚 快速导航 + +### 快速上下文 +- **[AI对接] 通用能力快速上下文.md** - 2-3分钟了解通用能力层 + +### 核心能力 +1. [LLM大模型网关](./01-LLM大模型网关/README.md) - P0优先级 ⭐ +2. [文档处理引擎](./02-文档处理引擎/README.md) - 已实现 +3. [RAG引擎](./03-RAG引擎/README.md) - 已实现 +4. [数据ETL引擎](./04-数据ETL引擎/README.md) +5. [医学NLP引擎](./05-医学NLP引擎/README.md) + +--- + +## 🔗 相关文档 + +- [系统架构分层设计](../00-系统总体设计/01-系统架构分层设计.md) +- [平台基础层](../01-平台基础层/README.md) +- [业务模块层](../03-业务模块/README.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + + diff --git a/docs/02-通用能力层/[AI对接] 通用能力快速上下文.md b/docs/02-通用能力层/[AI对接] 通用能力快速上下文.md new file mode 100644 index 00000000..f64c22b4 --- /dev/null +++ b/docs/02-通用能力层/[AI对接] 通用能力快速上下文.md @@ -0,0 +1,180 @@ +# [AI对接] 通用能力快速上下文 + +> **阅读时间:** 2-3分钟 | **Token消耗:** ~1500 tokens +> **层级:** L1 | **前置阅读:** 00-系统总体设计/[AI对接] 快速上下文.md + +--- + +## 📋 通用能力层定位 + +**通用能力层是跨业务模块共享的核心技术能力,是业务逻辑的基础。** + +**核心原则:** +- ✅ 可复用(多个业务模块共享) +- ✅ 业务相关(包含领域知识) +- ✅ 独立部署(可以独立为微服务) +- ✅ 高内聚(每个能力职责单一) + +--- + +## 🎯 5个核心能力 + +### 1. LLM大模型网关 ⭐⭐⭐⭐⭐ **最高优先级** + +**复用率:** 71% (5个模块依赖) + +**依赖模块:** +- AIA(AI智能问答) +- ASL(AI智能文献) +- PKB(个人知识库) +- DC(数据清洗) +- RVW(稿件审查) + +**核心价值:** +``` +商业模式的技术基础! + +功能1:根据用户版本选择模型 +- 专业版 → DeepSeek(¥1/百万) +- 高级版 → DeepSeek + Qwen3 +- 旗舰版 → 全部模型 + +功能2:成本控制 +- 统一监控所有LLM调用 +- 超出配额自动限流 +- 按版本计费 + +功能3:统一调用接口 +- 屏蔽不同LLM API差异 +- 流式/非流式统一处理 +``` + +**状态:** ❌ 待实现 +**优先级:** P0(必须在ASL模块开发前完成) +**预计时间:** 3-5天 + +--- + +### 2. 文档处理引擎 ⭐⭐⭐⭐⭐ + +**复用率:** 86% (6个模块依赖) + +**核心功能:** +- PDF提取(Nougat + PyMuPDF) +- Docx提取(Mammoth) +- Txt提取(多编码) +- Excel处理 + +**状态:** ✅ 已实现(Python微服务) +**位置:** `extraction_service/` + +--- + +### 3. RAG引擎 ⭐⭐⭐⭐ + +**复用率:** 43% (3个模块依赖) + +**核心功能:** +- 向量化存储(Embedding) +- 语义检索(Semantic Search) +- RAG问答 +- 智能引用(100%准确溯源) + +**状态:** ✅ 已实现(基于Dify) + +**优化成果:** +- 检索覆盖率从5%提升到40-50%(8-10倍) + +--- + +### 4. 数据ETL引擎 ⭐⭐⭐ + +**复用率:** 29% (2个模块依赖) + +**依赖模块:** +- DC(数据清洗) +- SSA(智能统计) + +**核心功能:** +- Excel多表JOIN +- 数据清洗 +- 数据转换 + +**技术方案:** +- 云端版:Polars(性能极高) +- 单机版:SQLite(内存友好) + +**状态:** ⏳ 待实现 + +--- + +### 5. 医学NLP引擎 ⭐⭐ + +**复用率:** 14% (1个模块依赖) + +**依赖模块:** +- DC(数据清洗 - 病例NER提取) + +**核心功能:** +- 医学实体识别(疾病、药物、TNM分期) +- 医学术语标准化 + +**技术方案:** +- 云端版:LLM API(高准确率) +- 单机版:spaCy(隐私优先) + +**状态:** ⏳ 待实现 + +--- + +## 📊 优先级排序 + +| 能力 | 复用率 | 优先级 | 状态 | 原因 | +|------|--------|--------|------|------| +| **LLM网关** | 71% | P0 | ❌ | 5个模块依赖,商业模式基础 | +| **文档处理** | 86% | - | ✅ | 已实现,需要增强 | +| **RAG引擎** | 43% | - | ✅ | 已实现,需要优化 | +| **ETL引擎** | 29% | P2 | ⏳ | DC模块开发时再实现 | +| **医学NLP** | 14% | P2 | ⏳ | DC模块开发时再实现 | + +--- + +## ⚠️ 关键提醒 + +**LLM网关必须优先实现!** +- 5个模块依赖(71%) +- ASL模块开发的前置条件 +- 商业模式的技术基础 + +**预计时间:** 3-5天 +**建议:** ASL模块Week 1同步开发 + +--- + +## 🔗 快速导航 + +**详细设计文档:** +- [LLM大模型网关](./01-LLM大模型网关/README.md) ⭐ P0 +- [文档处理引擎](./02-文档处理引擎/README.md) ✅ 已实现 +- [RAG引擎](./03-RAG引擎/README.md) ✅ 已实现 +- [数据ETL引擎](./04-数据ETL引擎/README.md) +- [医学NLP引擎](./05-医学NLP引擎/README.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/03-业务模块/ADMIN-运营管理端/README.md b/docs/03-业务模块/ADMIN-运营管理端/README.md new file mode 100644 index 00000000..115ef42c --- /dev/null +++ b/docs/03-业务模块/ADMIN-运营管理端/README.md @@ -0,0 +1,101 @@ +# ADMIN - 运营管理端 + +> **模块代号:** ADMIN (Administration) +> **开发状态:** ⏳ 规划中 +> **商业价值:** ⭐⭐⭐⭐⭐ SaaS运营基础 +> **独立性:** ⭐⭐⭐⭐⭐ +> **优先级:** P1 + +--- + +## 📋 模块概述 + +运营管理端横跨所有层次,是SaaS商业模式的技术基础。 + +**核心价值:** 实现多版本管理、成本控制、功能开关 + +--- + +## 🎯 核心功能(15个模块) + +### P0(必须,阶段一) +1. **用户管理** - 用户列表、详情、激活/禁用 +2. **Feature Flag管理** ⭐ - 版本功能控制(专业版/高级版/旗舰版) +3. **LLM模型管理** ⭐ - 模型配置、成本配置、版本绑定 +4. **系统配置管理** - 全局配置、动态配置 + +### P1(重要,阶段二) +5. **智能体提示词管理** - Prompt模板管理 +6. **监控与日志** - 操作日志、错误监控 +7. **数据统计与报表** - 用户统计、使用统计 +8. **成本分析与计费** - LLM成本统计、计费管理 + +### P2(有用,阶段三) +9. **租户管理** - 私有化部署的租户管理 +10. **公告与通知管理** +11. **帮助文档管理** +12. **反馈与工单管理** +13. **系统健康检查** +14. **数据库备份与恢复** +15. **运营数据分析** + +--- + +## 📂 文档结构 + +``` +ADMIN-运营管理端/ + ├── [AI对接] ADMIN快速上下文.md # ⏳ 待创建 + ├── 00-项目概述/ + │ ├── 01-产品需求文档(PRD).md # ⏳ 待创建 + │ ├── 02-功能清单(15个模块).md # ⏳ 待创建 + │ └── 03-权限体系设计.md # ⏳ 待创建 + ├── 01-设计文档/ + │ ├── 01-技术架构设计.md # ⏳ 待创建 + │ ├── 02-数据库设计.md # ⏳ 待创建 + │ └── 05-权限体系实现.md # ⏳ 待创建 + └── README.md # ✅ 当前文档 +``` + +--- + +## 🏗️ 技术选型 + +- **前端:** React + Ant Design Pro +- **后端:** Node.js + Fastify +- **数据库:** PostgreSQL (admin_schema) + +--- + +## 🚀 实施阶段 + +- **阶段一(1-2个月):** P0功能 +- **阶段二(1-2个月):** P1功能 +- **阶段三(1-2个月):** P2功能 + +--- + +## 🌐 部署方式 + +- **独立域名:** `https://admin.yizhengxun.com` +- **独立前端应用** +- **独立后端API** + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/03-业务模块/ADMIN-运营管理端/[AI对接] ADMIN快速上下文.md b/docs/03-业务模块/ADMIN-运营管理端/[AI对接] ADMIN快速上下文.md new file mode 100644 index 00000000..124801d3 --- /dev/null +++ b/docs/03-业务模块/ADMIN-运营管理端/[AI对接] ADMIN快速上下文.md @@ -0,0 +1,504 @@ +# [AI对接] ADMIN快速上下文 + +> **阅读时间:** 5分钟 | **Token消耗:** ~2000 tokens +> **层级:** L2 | **优先级:** P1 +> **前置阅读:** 03-业务模块/[AI对接] 业务模块快速上下文.md + +--- + +## 📋 模块定位 + +**运营管理端是SaaS商业模式的运营基础,管理用户、权限、LLM模型、成本等15个功能模块。** + +**商业价值:** ⭐⭐⭐⭐⭐ SaaS运营必备 +**开发状态:** ⏳ 规划中(P1优先级) +**依赖能力:** 平台基础层(UAM、监控日志)、LLM网关 + +--- + +## 🎯 核心功能(15个模块) + +### P0优先级(4个)⭐ 最核心 + +| 模块 | 功能 | 商业价值 | +|------|------|---------| +| **用户管理** | 用户CRUD、套餐管理、禁用/启用 | 基础运营 | +| **Feature Flag管理** | 功能开关配置、版本权限控制 | 商业模式基础 | +| **LLM模型管理** | 模型配置、价格管理、可用性控制 | 成本控制 | +| **系统配置** | 全局配置、环境切换、参数管理 | 系统运维 | + +--- + +### P1优先级(8个) + +| 模块 | 功能 | 说明 | +|------|------|------| +| **Prompt管理** | 智能体Prompt模板管理 | 提高AI效果 | +| **监控与日志** | 操作日志查询、错误监控 | 运维支持 | +| **成本分析** | LLM成本统计、用户消费排行 | 成本优化 | +| **数据报表** | 用户活跃度、功能使用率 | 运营决策 | +| **业务数据管理** | 文献项目、知识库等业务数据查看 | 运营支持 | +| **审核管理** | 稿件审查任务管理(RVW模块) | 业务支持 | +| **系统监控** | 服务健康度、API响应时间 | 技术运维 | +| **备份管理** | 数据备份、恢复 | 数据安全 | + +--- + +### P2优先级(3个) + +| 模块 | 功能 | +|------|------| +| **租户管理** | SaaS多租户管理(高级功能) | +| **公告管理** | 系统公告发布 | +| **帮助文档** | 在线帮助文档管理 | + +--- + +## 🏗️ 技术架构 + +### 前端(React) +``` +src/pages/Admin/ + ├── Dashboard/ # 首页仪表盘 + ├── Users/ # 用户管理 ⭐ P0 + │ ├── UserList.tsx + │ ├── UserDetail.tsx + │ └── UserEdit.tsx + ├── FeatureFlags/ # Feature Flag管理 ⭐ P0 + │ ├── FlagList.tsx + │ └── FlagConfig.tsx + ├── LLM/ # LLM模型管理 ⭐ P0 + │ ├── ModelList.tsx + │ ├── ModelConfig.tsx + │ └── CostAnalysis.tsx + ├── Prompts/ # Prompt管理 P1 + ├── Logs/ # 日志查询 P1 + ├── Reports/ # 数据报表 P1 + └── Settings/ # 系统配置 ⭐ P0 +``` + +### 后端(Node.js) +``` +backend/src/modules/admin/ + ├── controllers/ + │ ├── userController.ts # 用户管理 ⭐ + │ ├── featureFlagController.ts # Feature Flag ⭐ + │ ├── llmController.ts # LLM模型管理 ⭐ + │ ├── promptController.ts + │ ├── logController.ts + │ └── reportController.ts + ├── services/ + │ ├── userService.ts + │ ├── featureFlagService.ts + │ ├── llmService.ts + │ └── reportService.ts + └── routes/ + └── adminRoutes.ts +``` + +### 数据库(platform_schema) +```sql +-- 已有表 +- users # 用户基础信息 +- roles # 角色 +- permissions # 权限 +- feature_flags # Feature Flag配置 +- user_feature_flags # 用户Feature Flag关联 +- llm_usage # LLM使用记录 +- llm_quotas # LLM配额 +- admin_logs # 管理员操作日志 + +-- 需要新增 +- llm_models # LLM模型配置 ⭐ P0 +- prompt_templates # Prompt模板 P1 +- system_configs # 系统配置 ⭐ P0 +- announcements # 系统公告 P2 +``` + +--- + +## 💡 核心业务流程 + +### 1. Feature Flag配置流程 ⭐⭐⭐⭐⭐ + +``` +1. ADMIN在管理端配置Feature Flag + - 功能名称:claude_access + - 描述:是否可以使用Claude模型 + - 默认值:false + ↓ +2. 为不同套餐配置不同的Feature Flag + - 专业版:只有 deepseek_access + - 高级版:deepseek_access + qwen3_access + - 旗舰版:全部模型访问权限 + ↓ +3. 用户升级套餐时,自动更新Feature Flag + ↓ +4. 业务模块(ASL、AIA等)调用LLM网关时,自动检查Feature Flag +``` + +**关键表结构:** +```sql +-- Feature Flag定义 +CREATE TABLE platform_schema.feature_flags ( + id SERIAL PRIMARY KEY, + flag_key VARCHAR(100) UNIQUE NOT NULL, -- 'claude_access' + flag_name VARCHAR(200) NOT NULL, -- 'Claude模型访问权限' + description TEXT, + default_value BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT NOW() +); + +-- 用户Feature Flag(覆盖默认值) +CREATE TABLE platform_schema.user_feature_flags ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES platform_schema.users(id), + flag_id INTEGER REFERENCES platform_schema.feature_flags(id), + value BOOLEAN NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + + UNIQUE(user_id, flag_id) +); +``` + +--- + +### 2. LLM模型管理流程 ⭐⭐⭐⭐ + +``` +1. ADMIN配置LLM模型 + - 模型名称:DeepSeek-V3 + - API Key + - 价格:¥1/百万tokens + - 是否启用 + ↓ +2. LLM网关根据配置调用模型 + ↓ +3. 记录每次调用的成本 + ↓ +4. ADMIN查看成本分析报表 + - 总成本 + - 各模型成本占比 + - 用户消费排行 +``` + +**关键表结构:** +```sql +CREATE TABLE platform_schema.llm_models ( + id SERIAL PRIMARY KEY, + model_key VARCHAR(50) UNIQUE NOT NULL, -- 'deepseek-v3' + model_name VARCHAR(100) NOT NULL, -- 'DeepSeek-V3' + provider VARCHAR(50), -- 'deepseek', 'openai', 'anthropic' + api_endpoint TEXT, + api_key_encrypted TEXT, -- 加密存储 + price_per_million_tokens DECIMAL(10, 6), -- 每百万tokens价格 + is_enabled BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +``` + +--- + +### 3. 用户管理流程 + +``` +1. ADMIN创建用户 + - 基础信息(姓名、邮箱等) + - 选择套餐(professional/premium/enterprise) + - 自动配置对应的Feature Flag + ↓ +2. ADMIN管理用户 + - 修改套餐(Feature Flag自动更新) + - 禁用/启用账号 + - 重置密码 + - 调整LLM配额 + ↓ +3. ADMIN查看用户详情 + - 基础信息 + - LLM使用统计 + - 功能使用记录 + - 文献项目、知识库等业务数据 +``` + +--- + +## 📋 核心API端点 + +### 用户管理 ⭐ P0 +``` +GET /api/v1/admin/users # 用户列表(分页、筛选) +GET /api/v1/admin/users/:id # 用户详情 +POST /api/v1/admin/users # 创建用户 +PUT /api/v1/admin/users/:id # 更新用户 +DELETE /api/v1/admin/users/:id # 删除用户 +POST /api/v1/admin/users/:id/disable # 禁用用户 +POST /api/v1/admin/users/:id/enable # 启用用户 +PUT /api/v1/admin/users/:id/plan # 修改套餐 +``` + +### Feature Flag管理 ⭐ P0 +``` +GET /api/v1/admin/feature-flags # Feature Flag列表 +POST /api/v1/admin/feature-flags # 创建Feature Flag +PUT /api/v1/admin/feature-flags/:id # 更新Feature Flag +DELETE /api/v1/admin/feature-flags/:id # 删除Feature Flag +GET /api/v1/admin/users/:id/flags # 查询用户Feature Flag +PUT /api/v1/admin/users/:id/flags # 更新用户Feature Flag +``` + +### LLM模型管理 ⭐ P0 +``` +GET /api/v1/admin/llm/models # 模型列表 +POST /api/v1/admin/llm/models # 添加模型 +PUT /api/v1/admin/llm/models/:id # 更新模型 +DELETE /api/v1/admin/llm/models/:id # 删除模型 +GET /api/v1/admin/llm/usage # LLM使用统计 +GET /api/v1/admin/llm/cost-analysis # 成本分析 +``` + +### Prompt管理 P1 +``` +GET /api/v1/admin/prompts # Prompt模板列表 +POST /api/v1/admin/prompts # 创建Prompt +PUT /api/v1/admin/prompts/:id # 更新Prompt +DELETE /api/v1/admin/prompts/:id # 删除Prompt +``` + +### 日志查询 P1 +``` +GET /api/v1/admin/logs # 日志列表(分页、筛选) +GET /api/v1/admin/logs/errors # 错误日志 +GET /api/v1/admin/logs/operations # 操作日志 +``` + +### 数据报表 P1 +``` +GET /api/v1/admin/reports/overview # 总览数据 +GET /api/v1/admin/reports/users # 用户活跃度 +GET /api/v1/admin/reports/features # 功能使用率 +GET /api/v1/admin/reports/llm # LLM使用统计 +``` + +--- + +## 📊 核心页面设计 + +### 1. 仪表盘(Dashboard) +**核心指标:** +- 总用户数 / 活跃用户数 +- 本月LLM调用次数 / 成本 +- 各模块使用率 +- 错误日志数量 + +### 2. 用户管理 +**功能:** +- 列表:搜索、筛选(套餐、状态)、排序 +- 详情:基础信息 + 使用统计 + 业务数据 +- 编辑:修改套餐、调整配额、禁用/启用 + +### 3. Feature Flag管理 ⭐ +**核心界面:** +``` +┌─────────────────────────────────────────┐ +│ Feature Flag管理 │ +├─────────────────────────────────────────┤ +│ [+ 新增Flag] │ +│ │ +│ Flag Key | 描述 | 默认值 │ +│─────────────────────────────────────────│ +│ claude_access | Claude访问 | ❌ │ +│ qwen3_access | Qwen3访问 | ❌ │ +│ deepseek_access | DeepSeek访问| ✅ │ +│─────────────────────────────────────────│ +│ │ +│ 套餐配置: │ +│ 专业版:deepseek_access │ +│ 高级版:deepseek_access, qwen3_access │ +│ 旗舰版:全部模型 │ +└─────────────────────────────────────────┘ +``` + +### 4. LLM成本分析 ⭐ +**核心图表:** +- 成本趋势图(按天) +- 模型成本占比(饼图) +- 用户消费排行(柱状图) +- 模块使用分布(饼图) + +--- + +## ⚠️ 关键技术难点 + +### 1. API Key安全存储 +**解决方案:** AES-256加密 +```typescript +import crypto from 'crypto'; + +const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; // 32字节 +const IV_LENGTH = 16; + +function encrypt(text: string): string { + const iv = crypto.randomBytes(IV_LENGTH); + const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv); + let encrypted = cipher.update(text); + encrypted = Buffer.concat([encrypted, cipher.final()]); + return iv.toString('hex') + ':' + encrypted.toString('hex'); +} + +function decrypt(text: string): string { + const parts = text.split(':'); + const iv = Buffer.from(parts[0], 'hex'); + const encrypted = Buffer.from(parts[1], 'hex'); + const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv); + let decrypted = decipher.update(encrypted); + decrypted = Buffer.concat([decrypted, decipher.final()]); + return decrypted.toString(); +} +``` + +--- + +### 2. 权限控制 +**ADMIN角色:** +- 超级管理员:全部权限 +- 运营管理员:用户管理、报表查看 +- 技术管理员:LLM模型管理、日志查询 + +```typescript +// 权限检查中间件 +async function checkAdminPermission(req, reply, permission: string) { + const user = req.user; + + if (!user.isAdmin) { + throw new UnauthorizedError('需要管理员权限'); + } + + const hasPermission = await permissionService.check(user.id, permission); + + if (!hasPermission) { + throw new ForbiddenError('权限不足'); + } +} + +// 使用 +app.get('/api/v1/admin/users', { + preHandler: checkAdminPermission('user:read') +}, userController.list); +``` + +--- + +### 3. 数据报表性能优化 +**问题:** 大数据量查询慢 + +**解决方案:** +- Redis缓存(5分钟) +- 数据预聚合(定时任务) +- 分页查询 + +```typescript +// 缓存报表数据 +async function getOverviewReport() { + const cacheKey = 'admin:report:overview'; + + // 先查缓存 + const cached = await redis.get(cacheKey); + if (cached) return JSON.parse(cached); + + // 查询数据库 + const data = await db.query(` + SELECT + COUNT(DISTINCT user_id) as total_users, + SUM(total_tokens) as total_tokens, + SUM(cost) as total_cost + FROM platform_schema.llm_usage + WHERE created_at >= date_trunc('month', NOW()) + `); + + // 缓存5分钟 + await redis.set(cacheKey, JSON.stringify(data), 'EX', 300); + + return data; +} +``` + +--- + +## 📅 开发计划 + +### Phase 1:P0核心功能(Week 1-2) +- **用户管理**(3天) + - Day 1: 后端API(CRUD) + - Day 2: 前端列表和详情 + - Day 3: 套餐管理、禁用/启用 + +- **Feature Flag管理**(2天) + - Day 1: 后端API + 数据库 + - Day 2: 前端配置界面 + +- **LLM模型管理**(2天) + - Day 1: 后端API + 加密存储 + - Day 2: 前端配置界面 + +- **系统配置**(1天) + +### Phase 2:P1功能(Week 3-4) +- Prompt管理(2天) +- 日志查询(2天) +- 成本分析报表(3天) +- 数据报表(3天) + +### Phase 3:P2功能(Week 5) +- 租户管理 +- 公告管理 +- 帮助文档 + +--- + +## ✅ 开发检查清单 + +**开始前确认:** +- [ ] ADMIN角色和权限已配置 +- [ ] 数据库表已创建(llm_models, system_configs等) +- [ ] Redis已部署(用于报表缓存) +- [ ] ENCRYPTION_KEY环境变量已配置 + +**P0功能完成标准:** +- [ ] ADMIN可以创建/编辑/删除用户 +- [ ] ADMIN可以配置Feature Flag +- [ ] ADMIN可以配置LLM模型 +- [ ] ADMIN可以查看LLM成本统计 +- [ ] 所有敏感操作都记录到admin_logs + +--- + +## 🔗 相关文档 + +**依赖:** +- [用户与权限中心(UAM)](../../01-平台基础层/01-用户与权限中心(UAM)/README.md) +- [LLM大模型网关](../../02-通用能力层/01-LLM大模型网关/README.md) +- [监控与日志](../../01-平台基础层/04-监控与日志/README.md) + +**详细设计:** +- [运营管理端完整设计](./README.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 +**优先级:** P1 + + + + + + + + + + + + + + diff --git a/docs/03-业务模块/AIA-AI智能问答/02-技术设计/01-数据库设计.md b/docs/03-业务模块/AIA-AI智能问答/02-技术设计/01-数据库设计.md new file mode 100644 index 00000000..684786e4 --- /dev/null +++ b/docs/03-业务模块/AIA-AI智能问答/02-技术设计/01-数据库设计.md @@ -0,0 +1,527 @@ +# AIA - AI智能问答模块:数据库设计 + +> **版本:** v1.0 +> **更新时间:** 2025-11-12 +> **数据库Schema:** `aia_schema` +> **状态:** ✅ 已实施并迁移 + +--- + +## 📋 目录 + +1. [模块概述](#模块概述) +2. [Schema信息](#schema信息) +3. [数据库表设计](#数据库表设计) +4. [表关系图](#表关系图) +5. [索引设计](#索引设计) +6. [数据类型说明](#数据类型说明) +7. [变更历史](#变更历史) + +--- + +## 模块概述 + +### 功能定位 + +**AIA(AI Intelligent Assistant)- AI智能问答模块**是平台的核心对话引擎,提供: + +1. **项目管理** - 研究项目的创建和管理 +2. **智能对话** - 基于LLM的专业领域对话 +3. **通用问答** - 不绑定项目的通用AI对话 +4. **消息管理** - 对话历史记录和检索 + +### 核心业务场景 + +- 用户创建临床研究项目 +- 在项目内与AI进行专业对话 +- 使用通用对话功能快速咨询 +- 查看和管理对话历史 + +--- + +## Schema信息 + +### Schema名称 +```sql +aia_schema +``` + +### 创建语句 +```sql +CREATE SCHEMA IF NOT EXISTS aia_schema; +GRANT ALL ON SCHEMA aia_schema TO aiclinical_admin; +``` + +### 数据迁移 +- **迁移时间:** 2025-11-12 +- **源Schema:** public +- **迁移脚本:** `docs/09-架构实施/migration-scripts/003-migrate-aia.sql` +- **数据完整性:** ✅ 100%迁移成功 + +--- + +## 数据库表设计 + +### 表列表 + +| 表名 | 用途 | 行数(估计) | 状态 | +|------|------|------------|------| +| `projects` | 研究项目 | 1-100/用户 | ✅ 已部署 | +| `conversations` | 项目对话 | 10-500/项目 | ✅ 已部署 | +| `messages` | 对话消息 | 10-1000/对话 | ✅ 已部署 | +| `general_conversations` | 通用对话 | 10-100/用户 | ✅ 已部署 | +| `general_messages` | 通用对话消息 | 10-1000/对话 | ✅ 已部署 | + +**总计:** 5个表 + +--- + +### 1. projects - 研究项目表 + +**用途:** 存储用户创建的临床研究项目 + +#### 表结构 + +| 字段名 | 数据类型 | 约束 | 说明 | +|--------|---------|------|------| +| id | TEXT | PRIMARY KEY | 项目唯一标识(UUID) | +| user_id | TEXT | NOT NULL, FK | 所属用户ID | +| name | TEXT | NOT NULL | 项目名称 | +| background | TEXT | NULL | 研究背景 | +| research_type | TEXT | NULL | 研究类型(observational/experimental等) | +| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 | +| updated_at | TIMESTAMPTZ | NOT NULL | 更新时间 | +| deleted_at | TIMESTAMPTZ | NULL | 软删除时间 | + +#### Prisma Model + +```prisma +model Project { + id String @id @default(uuid()) + userId String @map("user_id") + name String + background String? + researchType String? @map("research_type") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + deletedAt DateTime? @map("deleted_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + conversations Conversation[] + + @@index([userId]) + @@index([createdAt]) + @@index([deletedAt]) + @@map("projects") + @@schema("aia_schema") +} +``` + +#### 业务规则 + +1. **软删除机制** - 使用`deleted_at`标记删除,不物理删除 +2. **级联删除** - 删除项目时,关联的对话也被软删除 +3. **用户隔离** - 通过`user_id`实现数据隔离 + +--- + +### 2. conversations - 项目对话表 + +**用途:** 存储项目内的AI对话会话 + +#### 表结构 + +| 字段名 | 数据类型 | 约束 | 说明 | +|--------|---------|------|------| +| id | TEXT | PRIMARY KEY | 对话唯一标识(UUID) | +| user_id | TEXT | NOT NULL, FK | 所属用户ID | +| project_id | TEXT | NULL, FK | 所属项目ID(可选) | +| agent_id | TEXT | NOT NULL | 智能体ID | +| title | TEXT | NOT NULL | 对话标题 | +| model_name | TEXT | NOT NULL, DEFAULT 'deepseek-v3' | 使用的LLM模型 | +| message_count | INTEGER | NOT NULL, DEFAULT 0 | 消息数量 | +| total_tokens | INTEGER | NOT NULL, DEFAULT 0 | 累计token数 | +| metadata | JSONB | NULL | 扩展元数据 | +| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 | +| updated_at | TIMESTAMPTZ | NOT NULL | 更新时间 | +| deleted_at | TIMESTAMPTZ | NULL | 软删除时间 | + +#### Prisma Model + +```prisma +model Conversation { + id String @id @default(uuid()) + userId String @map("user_id") + projectId String? @map("project_id") + agentId String @map("agent_id") + title String + modelName String @default("deepseek-v3") @map("model_name") + messageCount Int @default(0) @map("message_count") + totalTokens Int @default(0) @map("total_tokens") + metadata Json? + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + deletedAt DateTime? @map("deleted_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade) + messages Message[] + + @@index([userId]) + @@index([projectId]) + @@index([agentId]) + @@index([createdAt]) + @@index([deletedAt]) + @@map("conversations") + @@schema("aia_schema") +} +``` + +#### 业务规则 + +1. **项目关联可选** - `project_id`可为空,支持项目内外对话 +2. **计数器字段** - `message_count`和`total_tokens`用于统计,需要实时更新 +3. **模型选择** - 支持多种LLM模型(deepseek-v3/gpt-5-pro/claude-4.5等) +4. **扩展元数据** - `metadata`用于存储配置参数、上下文等 + +--- + +### 3. messages - 对话消息表 + +**用途:** 存储对话中的每条消息 + +#### 表结构 + +| 字段名 | 数据类型 | 约束 | 说明 | +|--------|---------|------|------| +| id | TEXT | PRIMARY KEY | 消息唯一标识(UUID) | +| conversation_id | TEXT | NOT NULL, FK | 所属对话ID | +| role | TEXT | NOT NULL | 角色(user/assistant/system) | +| content | TEXT | NOT NULL | 消息内容 | +| model | TEXT | NULL | 使用的模型(assistant消息) | +| metadata | JSONB | NULL | 扩展元数据 | +| tokens | INTEGER | NULL | 消息token数 | +| is_pinned | BOOLEAN | NOT NULL, DEFAULT false | 是否置顶 | +| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 | + +#### Prisma Model + +```prisma +model Message { + id String @id @default(uuid()) + conversationId String @map("conversation_id") + role String + content String @db.Text + model String? + metadata Json? + tokens Int? + isPinned Boolean @default(false) @map("is_pinned") + + createdAt DateTime @default(now()) @map("created_at") + + conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) + + @@index([conversationId]) + @@index([createdAt]) + @@index([isPinned]) + @@map("messages") + @@schema("aia_schema") +} +``` + +#### 业务规则 + +1. **角色类型** - `role`固定为`user`、`assistant`或`system` +2. **只读性** - 消息一旦创建不可修改(只有`is_pinned`可修改) +3. **级联删除** - 删除对话时,消息自动删除 +4. **置顶功能** - 重要消息可通过`is_pinned`标记 + +--- + +### 4. general_conversations - 通用对话表 + +**用途:** 存储不绑定项目的通用AI对话 + +#### 表结构 + +| 字段名 | 数据类型 | 约束 | 说明 | +|--------|---------|------|------| +| id | TEXT | PRIMARY KEY | 对话唯一标识(UUID) | +| user_id | TEXT | NOT NULL, FK | 所属用户ID | +| title | TEXT | NOT NULL | 对话标题 | +| model_name | TEXT | NOT NULL, DEFAULT 'qwen-long' | 使用的LLM模型 | +| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 | +| updated_at | TIMESTAMPTZ | NOT NULL | 更新时间 | +| deleted_at | TIMESTAMPTZ | NULL | 软删除时间 | + +#### Prisma Model + +```prisma +model GeneralConversation { + id String @id @default(uuid()) + userId String @map("user_id") + title String + modelName String @default("qwen-long") @map("model_name") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + deletedAt DateTime? @map("deleted_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + messages GeneralMessage[] + + @@index([userId]) + @@index([createdAt]) + @@index([updatedAt]) + @@map("general_conversations") + @@schema("aia_schema") +} +``` + +#### 业务规则 + +1. **轻量级对话** - 不需要项目和智能体关联 +2. **快速咨询** - 用于用户的临时问题和快速查询 +3. **独立管理** - 与项目对话分开管理 + +--- + +### 5. general_messages - 通用对话消息表 + +**用途:** 存储通用对话中的每条消息 + +#### 表结构 + +| 字段名 | 数据类型 | 约束 | 说明 | +|--------|---------|------|------| +| id | TEXT | PRIMARY KEY | 消息唯一标识(UUID) | +| conversation_id | TEXT | NOT NULL, FK | 所属对话ID | +| role | TEXT | NOT NULL | 角色(user/assistant/system) | +| content | TEXT | NOT NULL | 消息内容 | +| metadata | JSONB | NULL | 扩展元数据 | +| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 | + +#### Prisma Model + +```prisma +model GeneralMessage { + id String @id @default(uuid()) + conversationId String @map("conversation_id") + role String + content String @db.Text + metadata Json? + + createdAt DateTime @default(now()) @map("created_at") + + conversation GeneralConversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) + + @@index([conversationId]) + @@index([createdAt]) + @@map("general_messages") + @@schema("aia_schema") +} +``` + +#### 业务规则 + +1. **简化设计** - 相比项目消息,去掉了token统计和置顶功能 +2. **级联删除** - 删除对话时,消息自动删除 + +--- + +## 表关系图 + +```mermaid +erDiagram + PLATFORM_USERS ||--o{ PROJECTS : "owns" + PLATFORM_USERS ||--o{ CONVERSATIONS : "owns" + PLATFORM_USERS ||--o{ GENERAL_CONVERSATIONS : "owns" + + PROJECTS ||--o{ CONVERSATIONS : "contains" + CONVERSATIONS ||--o{ MESSAGES : "contains" + GENERAL_CONVERSATIONS ||--o{ GENERAL_MESSAGES : "contains" + + PLATFORM_USERS { + text id PK + text email + text password + } + + PROJECTS { + text id PK + text user_id FK + text name + text research_type + timestamptz created_at + timestamptz deleted_at + } + + CONVERSATIONS { + text id PK + text user_id FK + text project_id FK + text agent_id + text title + text model_name + int message_count + timestamptz created_at + } + + MESSAGES { + text id PK + text conversation_id FK + text role + text content + int tokens + boolean is_pinned + timestamptz created_at + } + + GENERAL_CONVERSATIONS { + text id PK + text user_id FK + text title + text model_name + timestamptz created_at + } + + GENERAL_MESSAGES { + text id PK + text conversation_id FK + text role + text content + timestamptz created_at + } +``` + +### 跨Schema引用 + +**外键关系:** +- `projects.user_id` → `platform_schema.users.id` +- `conversations.user_id` → `platform_schema.users.id` +- `general_conversations.user_id` → `platform_schema.users.id` + +**说明:** Prisma自动处理跨Schema外键,应用代码无需关心Schema前缀 + +--- + +## 索引设计 + +### 主键索引 +所有表的`id`字段自动创建B-tree主键索引。 + +### 外键索引 + +| 表名 | 索引字段 | 用途 | +|------|---------|------| +| projects | user_id | 查询用户的所有项目 | +| conversations | user_id | 查询用户的所有对话 | +| conversations | project_id | 查询项目内的对话 | +| conversations | agent_id | 按智能体过滤对话 | +| messages | conversation_id | 查询对话的所有消息 | +| general_conversations | user_id | 查询用户的通用对话 | +| general_messages | conversation_id | 查询对话的所有消息 | + +### 时间索引 + +| 表名 | 索引字段 | 用途 | +|------|---------|------| +| projects | created_at | 按时间排序项目 | +| projects | deleted_at | 过滤已删除项目 | +| conversations | created_at | 按时间排序对话 | +| conversations | updated_at | 按更新时间排序 | +| conversations | deleted_at | 过滤已删除对话 | +| messages | created_at | 按时间排序消息 | +| general_conversations | created_at | 按时间排序对话 | +| general_conversations | updated_at | 按更新时间排序 | +| general_messages | created_at | 按时间排序消息 | + +### 功能索引 + +| 表名 | 索引字段 | 用途 | +|------|---------|------| +| messages | is_pinned | 快速查询置顶消息 | + +--- + +## 数据类型说明 + +### 主键类型:TEXT vs UUID + +**当前实现:** TEXT +```sql +id TEXT PRIMARY KEY +``` + +**UUID生成:** Prisma `@default(uuid())` +```prisma +id String @id @default(uuid()) +``` + +**说明:** +- 存储格式:字符串形式的UUID(如`"a6ce8b46-bac6-4284-a9ae-031d636086bc"`) +- 优点:与现有代码兼容,无需迁移 +- 索引性能:与原生UUID类型相当 + +### 时间类型:TIMESTAMPTZ + +**所有时间字段使用`TIMESTAMPTZ`(带时区的时间戳):** +- 自动存储UTC时间 +- 支持时区转换 +- Prisma映射为`DateTime` + +### JSONB类型 + +**用途:** 存储扩展元数据和配置 +- `conversations.metadata` +- `messages.metadata` +- `general_messages.metadata` + +**优点:** +- 灵活的数据结构 +- 支持GIN索引(按需添加) +- 支持JSONB操作符查询 + +--- + +## 变更历史 + +### v1.0 - 2025-11-12 - 初始版本 ✅ + +**变更内容:** +1. 从`public` schema迁移到`aia_schema` +2. 5个表全部迁移: + - projects + - conversations + - messages + - general_conversations + - general_messages +3. 在Prisma中添加`@@schema("aia_schema")`标签 +4. 所有数据100%完整迁移 + +**迁移脚本:** `docs/09-架构实施/migration-scripts/003-migrate-aia.sql` + +**验证状态:** ✅ 已验证,功能正常 + +--- + +## 📚 相关文档 + +- [Schema隔离架构设计](../../../09-架构实施/01-Schema隔离架构设计(10个).md) +- [Schema迁移完成报告](../../../09-架构实施/Schema迁移完成报告.md) +- [Prisma配置完成报告](../../../09-架构实施/Prisma配置完成报告.md) +- [快速功能测试报告](../../../09-架构实施/快速功能测试报告.md) + +--- + +**文档维护者:** AI助手 +**最后更新:** 2025-11-12 +**文档状态:** ✅ 已完成并验证 + + + + + + diff --git a/docs/03-业务模块/AIA-AI智能问答/README.md b/docs/03-业务模块/AIA-AI智能问答/README.md new file mode 100644 index 00000000..448dedbd --- /dev/null +++ b/docs/03-业务模块/AIA-AI智能问答/README.md @@ -0,0 +1,70 @@ +# AIA - AI智能问答 + +> **模块代号:** AIA (AI Intelligent Answer) +> **开发状态:** ✅ 已完成 +> **商业价值:** ⭐⭐⭐⭐ +> **独立性:** ⭐⭐⭐ + +--- + +## 📋 模块概述 + +AI智能问答模块提供10+个专业AI智能体,覆盖科研关键节点。 + +**核心价值:** 差异化AI能力,覆盖科研全流程 + +--- + +## 🎯 核心功能 + +### 已完成功能 +1. ✅ **12个智能体** - YAML配置框架 +2. ✅ **多轮对话** - 上下文管理、历史记录 +3. ✅ **流式输出** - SSE打字机效果 +4. ✅ **模型切换** - DeepSeek、Qwen3、Qwen-Long +5. ✅ **@知识库问答** - RAG增强 + +### 主要智能体 +- 选题评价智能体(四维度评价) +- PICO梳理智能体 +- 样本量计算智能体 +- 研究方案制定智能体 +- 文章润色与翻译智能体 + +--- + +## 📂 文档结构 + +``` +AIA-AI智能问答/ + ├── [AI对接] AIA快速上下文.md # ⏳ 待创建 + ├── 00-项目概述/ + ├── 01-设计文档/ + └── README.md # ✅ 当前文档 +``` + +--- + +## 🔗 依赖的通用能力 + +- **LLM网关** - 模型调用和切换 +- **RAG引擎** - @知识库问答 + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/01-设计文档/用户端原型图.html b/docs/03-业务模块/AIA-AI智能问答/用户端原型图.html similarity index 100% rename from docs/01-设计文档/用户端原型图.html rename to docs/03-业务模块/AIA-AI智能问答/用户端原型图.html diff --git a/docs/03-业务模块/ASL-AI智能文献/00-系统设计/01-系统架构设计.md b/docs/03-业务模块/ASL-AI智能文献/00-系统设计/01-系统架构设计.md new file mode 100644 index 00000000..eb62febf --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/00-系统设计/01-系统架构设计.md @@ -0,0 +1,99 @@ +# AI智能文献模块 - 系统架构设计 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 +> **最后更新:** 2025-10-29 + +--- + +## 📋 文档说明 + +本文档描述AI智能文献模块的系统架构设计,包括模块架构、技术架构、数据架构等。 + +--- + +## 🏗️ 模块架构 + +### 核心功能模块 + +``` +AI智能文献模块 +├── 1. 研究方案生成 +├── 2. 智能文献检索 +├── 3. 标题摘要初筛 ⭐ 当前优先开发 +├── 4. 全文复筛 +├── 5. 全文解析与数据提取 +└── 6. 数据综合分析与报告生成 +``` + +### 模块间关系 + +``` +研究方案生成 → 指导后续所有模块 + ↓ +智能文献检索 → 生成文献列表 + ↓ +标题摘要初筛 → 初步筛选结果 + ↓ +全文复筛 → 最终纳入列表 + ↓ +全文解析与数据提取 → 结构化数据 + ↓ +数据综合分析与报告生成 → 最终报告 +``` + +--- + +## 🔧 技术架构 + +### 前端架构 +- **框架**: React + TypeScript +- **UI库**: Ant Design + Tailwind CSS +- **状态管理**: Zustand / React Query +- **路由**: React Router + +### 后端架构 +- **框架**: Node.js + TypeScript + Fastify +- **数据库**: PostgreSQL + Prisma ORM +- **文件存储**: 本地存储 / 对象存储 +- **AI集成**: DeepSeek-V3、Qwen3-72B + +### AI服务架构 +- **双模型系统**: DS模型 + Q3模型 +- **流式输出**: Server-Sent Events (SSE) +- **批处理**: 支持批量任务处理 + +--- + +## 📊 数据架构 + +### 核心数据实体 +- **研究方案** (Research Protocol) +- **文献项目** (Literature Project) +- **文献条目** (Literature Item) +- **筛选结果** (Screening Result) +- **提取数据** (Extracted Data) + +--- + +## ⏳ 待完善内容 + +本文档内容正在完善中,后续将补充: +- 详细的架构图 +- 模块间通信机制 +- 数据流设计 +- 安全性设计 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/00-系统设计/02-模块关联关系设计.md b/docs/03-业务模块/ASL-AI智能文献/00-系统设计/02-模块关联关系设计.md new file mode 100644 index 00000000..542c66db --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/00-系统设计/02-模块关联关系设计.md @@ -0,0 +1,24 @@ +# 模块关联关系设计 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/00-系统设计/03-数据流设计.md b/docs/03-业务模块/ASL-AI智能文献/00-系统设计/03-数据流设计.md new file mode 100644 index 00000000..bbc6ec87 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/00-系统设计/03-数据流设计.md @@ -0,0 +1,24 @@ +# 数据流设计 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/00-系统设计/04-技术选型说明.md b/docs/03-业务模块/ASL-AI智能文献/00-系统设计/04-技术选型说明.md new file mode 100644 index 00000000..e35cdbb2 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/00-系统设计/04-技术选型说明.md @@ -0,0 +1,24 @@ +# 技术选型说明 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/01-需求分析/01-需求总览.md b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/01-需求总览.md new file mode 100644 index 00000000..2541ee97 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/01-需求总览.md @@ -0,0 +1,61 @@ +# 需求总览 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## 📋 文档说明 + +本文档提供AI智能文献模块的需求总览,详细需求请参考PRD文档和各子模块需求文档。 + +--- + +## 🎯 核心功能模块 + +### 1. 研究方案生成 +**功能定位**: 定义研究方案、PICO、变量清单 +**状态**: 待开发 + +### 2. 智能文献检索 +**功能定位**: PubMed检索、语义排序 +**状态**: 待开发 + +### 3. 标题摘要初筛 ⭐ +**功能定位**: 基于标题和摘要的快速筛选 +**状态**: 当前优先开发 +**详细需求**: 见 [标题摘要初筛需求详述](./02-标题摘要初筛需求详述.md) + +### 4. 全文复筛 +**功能定位**: 基于全文内容的二次筛选 +**状态**: 待开发 + +### 5. 全文解析与数据提取 +**功能定位**: 结构化数据提取 +**状态**: 待开发 + +### 6. 数据综合分析与报告生成 +**功能定位**: 证据图谱、Meta分析、报告生成 +**状态**: 待开发 + +--- + +## 📚 相关文档 + +- [PRD文档 - 产品概览](../../00-项目概述/AI智能文献PRD(1)-产品概览.md) +- [PRD文档 - 初筛与复筛](../../00-项目概述/AI智能文献PRD(2)-初筛与复筛.md) +- [PRD文档 - 提取与分析](../../00-项目概述/AI智能文献PRD(3)-提取与分析模块.md) + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/01-需求分析/02-标题摘要初筛需求详述.md b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/02-标题摘要初筛需求详述.md new file mode 100644 index 00000000..a4c9123a --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/02-标题摘要初筛需求详述.md @@ -0,0 +1,199 @@ +# 标题摘要初筛需求详述 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 +> **最后更新:** 2025-10-29 + +--- + +## 📋 文档说明 + +本文档详细描述标题摘要初筛模块的功能需求,基于PRD文档进行扩展和细化。 + +--- + +## 🎯 功能概述 + +**标题摘要初筛**是筛选流程的第一阶段,仅基于文献的标题和摘要进行快速筛选。 + +### 核心子视图 + +1. **设置与启动视图** - 配置筛选标准、导入文献、启动筛选 +2. **表格化审核工作台** - 双模型PICS判断、冲突管理、决策制定 +3. **初筛结果视图** - 统计概览、结果列表、导出功能 + +--- + +## 📝 功能需求详述 + +### 需求1: 设置与启动视图 + +#### FR-TSCR-01.1: 标准参考展示 +- **需求**: 页面顶部展示从"研究方案"继承的PICO和入排标准(只读) +- **优先级**: 高 +- **验收标准**: + - 清晰展示PICO框架内容 + - 展示纳入标准和排除标准 + - 提供"调整本次筛选标准"入口 + +#### FR-TSCR-01.2: 临时调整筛选标准 +- **需求**: 允许用户进行仅对本次筛选生效的临时修改 +- **优先级**: 高 +- **验收标准**: + - 可修改PICO和入排标准 + - 修改不影响原始研究方案 + - 在审核工作台有明确提示 + +#### FR-TSCR-01.3: 文献导入 +- **需求**: 仅支持"上传Excel文件"方式,提供模板下载 +- **优先级**: 高 +- **验收标准**: + - 支持Excel文件上传 + - 提供标准模板下载 + - 文件格式验证 + - 导入结果预览 + +#### FR-TSCR-01.4: 启动筛选 +- **需求**: 导入文献后,激活"开始AI初筛"按钮 +- **优先级**: 高 +- **验收标准**: + - 按钮状态控制 + - 点击后启动AI筛选任务 + - 显示任务进度 + +--- + +### 需求2: 表格化审核工作台视图 ⭐⭐⭐ + +#### FR-TSCR-02.1: 表格结构设计 +- **需求**: 高信息密度的表格化布局 +- **优先级**: 极高 +- **验收标准**: + - 两行表头结构(模型区域 + P/I/C/S列) + - 主行包含所有关键信息 + - 展开行显示证据短语 + - 支持展开/收起 + +#### FR-TSCR-02.2: 双模型PICS判断 +- **需求**: DS和Q3模型分别对P/I/C/S四个维度进行判断 +- **优先级**: 极高 +- **验收标准**: + - 每个维度显示判断结果(✓/✗/?) + - 展示双模型的判断结果 + - 支持查看详细判断理由 + +#### FR-TSCR-02.3: 证据短语提取 +- **需求**: 展示AI提取的关键证据短语 +- **优先级**: 极高 +- **验收标准**: + - 展开行显示DS和Q3的证据短语 + - 短语清晰标注对应的P/I/C/S维度 + - 支持点击查看原文 + +#### FR-TSCR-02.4: 原文审查模态框 +- **需求**: 点击判断图标弹出双视图审查模态框 +- **优先级**: 极高 +- **验收标准**: + - 左侧显示摘要 + - 右侧显示双模型判断详情 + - 原文自动高亮 + - 支持来源引用查看 + +--- + +### 需求3: 冲突管理 + +#### FR-TSCR-03.1: 冲突识别 +- **需求**: 自动识别两模型判断不一致的文献 +- **优先级**: 高 +- **验收标准**: + - 自动检测冲突项 + - 视觉高亮(如行背景色) + - 支持冲突项筛选 + +#### FR-TSCR-03.2: 冲突处理 +- **需求**: 允许用户对冲突项进行人工仲裁 +- **优先级**: 高 +- **验收标准**: + - 支持查看冲突详情 + - 支持选择最终决策 + - 记录决策原因 + +--- + +### 需求4: 批量操作 + +#### FR-TSCR-04.1: 批量决策 +- **需求**: 支持对非冲突项进行批量决策 +- **优先级**: 中 +- **验收标准**: + - 支持多选文献 + - 批量设置决策结果 + - 批量设置决策原因 + +--- + +### 需求5: 初筛结果视图 + +#### FR-TSCR-05.1: 统计概览 +- **需求**: 展示筛选统计信息 +- **优先级**: 高 +- **验收标准**: + - 卡片形式展示统计数字 + - 包括:总计、纳入、排除数量 + +#### FR-TSCR-05.2: PRISMA式排除总结 +- **需求**: 展示主要排除原因及数量 +- **优先级**: 高 +- **验收标准**: + - 列表展示排除原因 + - 显示对应文献数量 + - 支持点击查看详情 + +#### FR-TSCR-05.3: 结果列表 +- **需求**: Tab页切换查看纳入和排除的文献列表 +- **优先级**: 高 +- **验收标准**: + - 支持Tab切换 + - 表格展示文献信息 + - 支持筛选和搜索 + +#### FR-TSCR-05.4: 导出功能 +- **需求**: 将结果列表导出为Excel +- **优先级**: 高 +- **验收标准**: + - 支持导出为Excel格式 + - 包含完整的文献信息 + - 格式规范清晰 + +--- + +## 📊 非功能性需求 + +- **性能**: 单次筛选支持1000+文献 +- **响应时间**: 单个文献判断 < 5秒 +- **并发**: 支持多用户同时筛选 +- **可靠性**: 支持任务中断和恢复 + +--- + +## 📚 相关文档 + +- [PRD文档 - 初筛与复筛](../../00-项目概述/AI智能文献PRD(2)-初筛与复筛.md) +- [原型图](../../01-设计文档/AI智能文献-标题摘要初筛原型.html) +- [数据库设计](../02-技术设计/01-数据库设计.md) +- [API设计规范](../02-技术设计/02-API设计规范.md) + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/01-需求分析/03-全文复筛需求详述.md b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/03-全文复筛需求详述.md new file mode 100644 index 00000000..98b4ec35 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/03-全文复筛需求详述.md @@ -0,0 +1,26 @@ +# 全文复筛需求详述 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +详细需求请参考PRD文档:`../00-项目概述/AI智能文献PRD(2)-初筛与复筛.md` + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/01-需求分析/04-用户故事和验收标准.md b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/04-用户故事和验收标准.md new file mode 100644 index 00000000..9a794e21 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/04-用户故事和验收标准.md @@ -0,0 +1,24 @@ +# 用户故事和验收标准 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/01-需求分析/05-非功能性需求.md b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/05-非功能性需求.md new file mode 100644 index 00000000..4364a5b7 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/05-非功能性需求.md @@ -0,0 +1,24 @@ +# 非功能性需求 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/01-需求分析/AI智能文献PRD(1)-产品概览.md b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/AI智能文献PRD(1)-产品概览.md new file mode 100644 index 00000000..18e8a2cd --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/AI智能文献PRD(1)-产品概览.md @@ -0,0 +1,89 @@ +# **PRD V4.0 \- 第一部分:产品概述与核心流程** + +版本: 4.0 +日期: 2025-10-21 + +## **1\. 引言** + +### **1.1. 项目背景** + +当前,医学科研人员在进行系统性文献回顾、Meta分析或新药研发等工作时,面临着海量文献带来的巨大挑战。传统的文献处理流程,包括检索、筛选、数据提取和分析,是高度劳动密集型的工作,不仅耗时巨大,而且容易引入人为偏见和错误,直接影响科研结论的质量和时效性。本项目旨在开发一个基于人工智能的医学文献应用系统,通过自动化和智能化的方式赋能科研人员,将他们从繁琐的重复性工作中解放出来,聚焦于更高层次的科学洞见和创新。 + +### **1.2. 产品目标** + +* **效率提升:** 将传统需要数月完成的系统性文献回顾流程,缩短至数天或数周。 +* **质量保障:** 通过先进的AI模型和校验机制,提升文献筛选和数据提取的准确性与一致性,降低人为偏见。 +* **深度洞察:** 在精准提取的数据基础上,提供多维度的综合分析和可视化工具,帮助用户快速生成高质量的证据图谱、综合评价报告等应用。 +* **全程可溯:** 确保AI处理的每一个环节(筛选、提取、分析)都有据可查,所有结论均可追溯至原文,满足科研的严谨性要求。 + +### **1.3. 目标用户** + +* 医学科研人员(高校、研究所) +* 临床医生与医学专家 +* 制药公司及CRO(合同研究组织)的研发、医学和市场准入部门人员 +* 循证医学与卫生技术评估(HTA)机构的研究员 +* 高等院校的医学/药学专业学生及教员 + +### **1.4. 名词解释** + +* **PRD:** Product Requirement Document,产品需求文档。 +* **PICO(S):** 系统评价中用于确定文献纳入排除标准的研究要素框架,包括: + * **P (Patient/Population):** 研究对象 + * **I (Intervention):** 干预措施 + * **C (Comparison):** 对照措施 + * **O (Outcome):** 结局指标 + * **S (Study Design):** 研究设计 +* **证据图谱 (Evidence Mapping):** 一种系统性地识别、描述和分类特定健康领域现有研究证据的方法,通常以可视化的方式呈现。 +* **Meta分析:** 对相同研究问题的多个独立研究结果进行定量合并分析的统计方法。 + +## **2\. 产品概述** + +### **2.1. 产品定位** + +一个**遵循循证医学标准**、专业、高效、可信赖的AI医学科研**工具箱**。它以**研究方案**为核心,驱动从文献检索到深度分析报告生成的全流程解决方案。 + +### **2.2. 核心价值** + +* **自动化:** 自动执行文献筛选、数据提取等繁重任务。 +* **精准化:** 深度理解医学语言和PICO,保障筛选和提取的准确性。 +* **结构化:** 将非结构化的文献全文转化为结构化的数据资产。 +* **智能化:** 基于结构化数据,提供多样的下游分析应用和报告。 + +### **2.3. 功能总览图 (V4.0 更新)** + +graph TD + A\[用户管理\] \--\> B(项目管理) + B \--\> C\[1. 研究方案生成\] + C \-- "指导后续所有流程" \--\> D + + subgraph D\[研究执行流程\] + D1\[2. 智能文献检索\] + D2\[3. 标题摘要初筛\] + D3\[4. 全文复筛\] + D4\[5. 全文解析与数据提取\] + D5\[6. 数据综合分析与报告生成\] + end + + D1 \--\> D2 + D2 \--\> D3 + D3 \--\> D4 + D4 \--\> D5 + + D5 \--\> E1\[证据图谱\] + D5 \--\> E2\[Meta分析数据集\] + D5 \--\> E3\[药物综合评价报告\] + + subgraph "产出应用" + E1 + E2 + E3 + end + +### **2.4. 设计哲学:集成化与模块化** + +本产品的核心设计哲学是“**集成化与模块化并存**”。 + +* **集成化:** 六大核心功能模块(研究方案、智能检索、标题摘要初筛、全文复筛、全文提取、综合分析)无缝衔接,为用户提供一个从课题构想到报告产出的端到端、流畅连贯的系统性文献研究工作流。 +* **模块化:** 每个核心功能模块都可以作为一个独立的工具来解决特定的科研痛点。用户无需遵循完整的流程,可以根据自己的实际需求,从任意环节(如仅使用检索、或仅上传Excel进行筛选)切入,使用相应的功能模块。 + +这种设计旨在最大化产品的灵活性和适用性,服务于不同需求层次的广大科研用户。 \ No newline at end of file diff --git a/docs/03-业务模块/ASL-AI智能文献/01-需求分析/AI智能文献PRD(2)-初筛与复筛.md b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/AI智能文献PRD(2)-初筛与复筛.md new file mode 100644 index 00000000..62351dc9 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/AI智能文献PRD(2)-初筛与复筛.md @@ -0,0 +1,59 @@ +# **PRD V4.0 \- 第二部分:研究方案与筛选模块** + +版本: 4.0 +日期: 2025-10-21 + +## **3\. 功能需求详述 (续)** + +### **3.1. 研究方案生成模块 (V4.0 新增)** + +**本模块是所有研究工作的起点和“唯一事实来源”,其配置将指导后续所有模块的AI行为和界面展示。** + +| 需求ID | 需求描述 | 优先级 | +| :---- | :---- | :---- | +| **FR-PROT-01** | **结构化方案定义:** 提供一个向导式界面,引导用户(或由AI辅助)定义一个完整的循证研究方案。 | **极高** | +| **FR-PROT-02** | **PICO 与纳入/排除标准定义:** 在方案中,提供结构化表单,让用户清晰定义研究的PICO,以及详细的文本格式的纳入和排除标准。这将是后续筛选模块的“宪法”。 | **极高** | +| **FR-PROT-03** | **数据提取变量清单配置:** 在方案中,提供一个变量清单管理功能。 a) **预设清单:** 根据用户选择的研究目的(如证据图谱、Meta分析),提供通用的、标准的变量提取清单。 b) **自定义清单:** 允许用户在预设清单的基础上,自定义(增、删、改)需要提取的数据变量。此清单将作为“数据提取模块”的唯一依据。 | **极高** | +| **FR-PROT-04** | **方案锁定与版本控制:** 研究方案一旦确立并开始用于筛选后,应被锁定或进行版本控制。任何后续的修改都应被记录,以保证科研过程的透明性和严谨性。 | 高 | + +### **3.2. 智能文献检索模块** + +(原3.2模块,序号调整) + +| 需求ID | 需求描述 | 优先级 | +| :---- | :---- | :---- | +| **FR-SEARCH-01** | **关键词/主题词检索:** 提供输入框,支持用户使用标准关键词、MeSH主题词及布尔运算符(AND, OR, NOT)构建PubMed查询。 | 高 | +| **FR-SEARCH-02** | **交互式检索策略与语义排序:** 提供一个与AI对话的界面,通过多轮交流引导用户明确其核心研究问题。AI根据对话内容,帮助用户构建和优化专业的检索策略。在执行检索后,系统对返回的文献列表进行二次语义相关度分析,并按相关性从高到低进行排序,将最匹配的文献呈现在最前面。 | 高 | +| **FR-SEARCH-03** | **PubMed API集成:** 系统通过API实时连接PubMed数据库,执行检索并获取最新文献列表。 | 高 | +| **FR-SEARCH-04** | **结果展示与导入:** 以列表形式清晰展示检索结果,包括标题、作者、期刊、摘要等。用户可勾选相关文献,一键**导入到项目中进行下一步筛选**。 | 高 | + +### **3.3. 标题摘要初筛模块 (V4.0 重构 \- V7原型更新)** + +**本模块是筛选流程的第一阶段,仅基于文献的标题和摘要进行快速筛选。** 包含设置与启动、审核工作台、初筛结果三个子视图。 + +| 需求ID | 需求描述 | 优先级 | | | +| :---- | :---- | :---- | :---- | :---- | +| **FR-TSCR-01** | **设置与启动视图:** a) **标准参考:** 页面顶部展示从“研究方案”继承的PICO和入排标准(只读),并提供“ | $$调整本次筛选标准$$ | ”入口,允许用户进行**仅对本次筛选生效**的临时修改。 b) **文献导入:** 页面下方提供文献导入功能,\*\*仅支持“上传Excel文件”\*\*方式,并提供模板下载。 c) **启动筛选:** 导入文献后,激活“开始AI初筛”按钮。 | 高 | +| **FR-TSCR-02** | **表格化审核工作台视图:** 核心审核界面采用高信息密度的**表格化布局**。 a) **标准参考:** 工作台顶部提供**可折叠**的“研究方案概览”面板,展示PICO与入排标准。若应用了临时调整,需有提示。 b) **表格结构:** 每一行代表一篇文献,并支持展开显示第二行。 **表头**采用两行结构:第一行合并单元格标示模型区域,第二行在各模型下细分P/I/C/S/结论列。 **主行 (Row 1):** 包含展开/收起按钮, 文献ID (PMID), 研究ID (作者姓+年份), 文献来源 (期刊/DOI链接), DS-P(✓/✗/?), DS-I, DS-C, DS-S, DS-结论(纳入/排除), Q3-P, Q3-I, Q3-C, Q3-S, Q3-结论, 冲突状态, 最终决策(下拉选择框)。 **展开行 (Row 2):** 默认隐藏,点击按钮后展开。仅包含两列,分别显示DS 证据: (P/I/C/S对应的关键短语) 和 Q3 证据: (P/I/C/S对应的关键短语)。 c) **交互:** 点击主行中的P/I/C/S判断图标(✓/✗/?),**弹出“双视图原文审查模态框”**,左侧显示摘要,右侧显示双模型对该维度的详细判断、理由和来源引用,原文自动高亮。 | **极高** | | | +| **FR-TSCR-03** | **双模型PICS逐项判断:** 双模型(DS/Q3)需基于筛选标准,对每篇文献的P/I/C/S四个维度**分别进行判断** (✓/✗/?),提取**关键证据短语**,并给出一个总览的“纳入/排除”建议。 | **极高** | | | +| **FR-TSCR-04** | **分级与冲突管理:** 系统需自动识别并高亮(如行背景色)两模型判断不一致的文献(冲突项),允许用户优先筛选处理。 | 高 | | | +| **FR-TSCR-05** | **批量操作:** 支持对非冲突项进行批量决策。 | 中 | | | +| **FR-TSCR-06** | **初筛结果视图 (V7 新增):** 提供独立的页面展示初筛的最终结果。 a) **统计概览:** 以卡片形式展示“总计筛选文献数”、“初步纳入文献数”、“排除文献数”。 b) **PRISMA式排除总结:** 以列表形式展示主要的排除原因及其对应的文献数量(如:非RCT n=X, 非目标人群 n=Y ...)。 c) **结果列表:** 提供Tab页切换查看“初步纳入”和“排除”的文献列表。表格需包含列:文献ID, 研究ID, 文献来源, 标题, 摘要(可展开), P(内容短语), I, C, S, 结论(纳入/排除), 排除理由。 d) **导出功能:** 提供将结果列表导出为Excel的功能。 | 高 | | | + +### **3.4. 全文复筛模块 (V4.0 新增 \- V2原型更新)** + +**本模块是筛选流程的第二阶段,对第一阶段初步纳入的文献,基于全文内容进行更严格的二次筛选。** 包含设置与启动、审核工作台、复筛结果三个子视图。 + +| 需求ID | 需求描述 | 优先级 | +| :---- | :---- | :---- | +| **FR-FSCR-01** | **设置与启动视图:** a) **标准参考:** 与标题摘要阶段类似,提供标准参考区和临时调整入口。 b) **启动路径:** 支持两种启动方式:1) **流程衔接:** 自动加载从“标题摘要初筛”模块传递过来的“初步纳入”文献列表;2) **独立启动:** 若无传入数据,则显示导入选项(上传Excel, 从知识库添加)。 c) **全文获取与管理:** 以表格形式展示待复筛文献列表,包含列:文献ID, 文献标题, 获取状态 (需模拟多种状态如获取中/成功/失败)。系统自动尝试获取全文。 d) **手动上传与知识库:** 获取失败的文献提供\[上传全文\]按钮;提供\[+ 从知识库添加\]按钮入口。 e) **启动复筛:** 只有当列表内所有文献状态均为获取成功时,激活“开始全文复筛”按钮(原型中可放宽此限制以方便演示)。 | 高 | +| **FR-FSCR-02** | **基于全文的PICS判断:** 双模型(DS/Q3)需读取文献**全文**,并再次基于筛选标准,对P/I/C/S四个维度进行判断 (✓/✗/?),提取**关键证据短语**,并给出结论建议。 | **极高** | +| **FR-FSCR-03** | **表格化审核工作台视图:** **复用**与“标题摘要初筛”模块**完全一致的表格化审核工作台**组件(包括表头结构、可展开行、点击弹窗交互),确保用户体验的连贯性。区别在于AI判断的数据源是全文,弹窗中展示的是PDF全文阅读器。 | **极高** | +| **FR-FSCR-04** | **产出最终纳入列表:** 本模块审核通过的文献,将形成“最终纳入文献列表”,作为下一个“全文解析与数据提取”模块的输入。 | 高 | +| **FR-FSCR-05** | **复筛结果视图 (V2 新增):** 提供独立的页面展示复筛的最终结果。 a) **统计概览:** 展示“总计复筛文献数”、“最终纳入文献数”、“排除文献数”。 b) **PRISMA式排除总结:** 展示基于全文复筛阶段的排除原因及数量。 c) **结果列表:** 提供Tab页切换查看“最终纳入”和“排除”的文献列表。表格需包含列:文献ID, 研究ID, 文献来源, 标题, 最终决策, 决策方式, 排除理由。 d) **导出功能:** 提供将结果列表导出为Excel的功能。 | 高 | + +\<\!-- \#\#\# 3.5. 全文解析与数据提取模块 (V4.0 重构) ... (内容见第三部分文档) ... + +### **3.6. 数据综合分析与报告生成模块** + +... (内容见第三部分文档) ... \--\> \ No newline at end of file diff --git a/docs/03-业务模块/ASL-AI智能文献/01-需求分析/AI智能文献PRD(3)-提取与分析模块.md b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/AI智能文献PRD(3)-提取与分析模块.md new file mode 100644 index 00000000..e6a13103 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/01-需求分析/AI智能文献PRD(3)-提取与分析模块.md @@ -0,0 +1,51 @@ +# **PRD V4.0 \- 第三部分:提取与分析模块** + +版本: 4.0 +日期: 2025-10-21 + +## **3\. 功能需求详述 (续)** + +### **3.5. 全文解析与数据提取模块 (V4.0 重构)** + +**本模块负责对“全文复筛”阶段最终纳入的文献,根据“研究方案”中定义的变量清单,进行精准的数据提取和质量评价。** + +| 需求ID | 需求描述 | 优先级 | +| :---- | :---- | :---- | +| **FR-EXT-01** | **基于方案的批量提取:** 系统根据“研究方案”中配置的“数据提取变量清单”,对所有“最终纳入”的文献全文,启动双模型(如DS, Q3)批量数据提取。需提供任务状态监控。 | **极高** | +| **FR-EXT-02** | **表格化数据审查台:** 核心审查界面采用**表格化布局**。 a) **表格结构:** 每一**行**代表一篇文献,每一**列**代表一个在方案中定义的提取变量(包括研究特征、PICO要素、结局数据、质量评价项等)。 b) **单元格内容:** 每个单元格清晰展示模型A和模型B的提取结果,并高亮结果不一致的冲突项(“待仲裁”)。 | **极高** | +| **FR-EXT-03** | **点击查看原文 (模态框):** 用户点击任何一个需要仲裁(或需要核对)的单元格时,系统**弹出一个“双视图原文审查模态框”**。 a) **左侧:** PDF全文阅读器,自动滚动并高亮AI提取该数据点所依据的原文片段。 b) **右侧:** 聚焦展示该数据点,包含双模型结果、理由、来源引用,并提供输入框供用户确认或修正最终值。 | **极高** | +| **FR-EXT-04** | **集成式批判性评价:** 用户在“研究方案”中选择的质量/偏倚风险评估工具(如Cochrane RoB 2),其评价条目将作为普通变量**一同整合**在表格化审查台的列中,允许用户在提取数据的同时完成质量评价,并为评价结果链接原文证据。 | 高 | +| **FR-EXT-05** | **数据汇总与导出:** 提供“数据汇总”页面,将所有已审查确认的数据(包括提取的变量和质量评价结果)汇总到一个总表中,支持筛选、搜索,并提供一键导出为Excel的功能。页面应包含模型表现评估(如正确率)。 | 高 | + +### **3.6. 数据综合分析与报告生成模块** + +**本模块是一个交互式的分析平台,旨在将“全文解析与数据提取模块”产出的结构化数据,转化为富有洞见的专业分析和报告。** + +| 需求ID | 需求描述 | 优先级 | +| :---- | :---- | :---- | +| **FR-ANLS-01** | **统一数据导入:** 模块的入口是“**3.5 全文解析与数据提取模块**”产出的最终结构化数据表。系统自动加载该数据作为分析的数据源。 | 高 | + +#### **3.6.1 应用一:证据图谱生成 (Evidence Mapping)** + +| 需求ID | 需求描述 | 优先级 | +| :---- | :---- | :---- | +| **FR-ANLS-MAP-01** | **图谱框架配置向导:** 提供一个向导式界面,让用户定义图谱的“坐标轴”和视觉元素: a) **Y轴配置 (干预措施):** 允许用户对提取的“干预措施”数据进行拖拽、分组和重命名。 b) **X轴配置 (结局指标):** 允许用户对提取的“结局指标”数据进行类似的分组和重命名。 c) **气泡含义配置:** 允许用户定义气泡颜色所代表的维度,如“研究质量/偏倚风险”或“研究设计”。气泡大小固定为“研究数量”。 | 高 | +| **FR-ANLS-MAP-02** | **交互式证据图谱生成:** 基于用户配置,生成一个交互式的气泡图矩阵。 a) **可视化展示:** Y轴为干预措施,X轴为结局指标。交叉点上的气泡大小代表研究数量,颜色代表用户选择的维度。没有气泡的单元格即为“证据空白”。 b) **悬停交互:** 鼠标悬停在气泡上,显示总结信息(如:研究数量,高质量RCT数量等)。 c) **点击交互:** 点击气泡,图表下方应动态展示该气泡包含的所有研究的详细列表。 | **极高** | +| **FR-ANLS-MAP-03** | **动态筛选器:** 在图谱旁边提供一组动态筛选器,允许用户根据“发表年份”、“研究设计”、“样本量”、“国家/地区”、“**特殊人群**”等维度实时过滤数据。图谱和下方的统计数据会随之动态更新。 | 高 | +| **FR-ANLS-MAP-04** | **自动化描述性统计:** 在图谱下方,根据当前筛选的数据,自动生成描述性统计图表和文字摘要,如研究类型分布饼图、研究趋势折线图等。 | 高 | +| **FR-ANLS-MAP-05** | **一键报告生成:** 提供“生成报告”功能,进入一个**智能报告编辑器**。编辑器预载由AI生成的报告初稿(含图谱、统计图、AI文字解读、PRISMA流程、方法描述、附录等),用户可在线编辑后导出为Word或PDF。 | 高 | + +#### **3.6.2 应用二:Meta分析数据准备** + +| 需求ID | 需求描述 | 优先级 | +| :---- | :---- | :---- | +| **FR-ANLS-META-01** | **数据格式化导出:** 根据用户选择,将从“数据提取模块”获取的结局指标数据(如均值、标准差、事件数等),自动整理并导出为适配主流Meta分析软件(如RevMan, Stata)格式的数据文件。 | 中 | +| **FR-ANLS-META-02** | **森林图预览:** 对于二分类或连续性变量,提供一个基础的森林图(Forest Plot)预览功能,让用户在导出前对数据合并的趋势有一个初步的视觉判断。 | 低 | + +#### **3.6.3 应用三:药物综合评价报告** + +| 需求ID | 需求描述 | 优先级 | +| :---- | :---- | :---- | +| **FR-ANLS-DRUG-01** | **多维数据整合:** 允许用户选择多个维度(如有效性指标、安全性指标、患者报告结局等),系统自动从数据源中整合相关数据。 | 中 | +| **FR-ANLS-DRUG-02** | **模板化报告生成:** 基于预设的药物综合评价报告模板,自动将整合后的数据填充到报告的各个章节中,生成包含PICO总结表、有效性-安全性矩阵图、关键研究列表等内容的Word或PDF报告初稿。 | 中 | + diff --git a/docs/03-业务模块/ASL-AI智能文献/02-技术设计/01-数据库设计.md b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/01-数据库设计.md new file mode 100644 index 00000000..3c48e65e --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/01-数据库设计.md @@ -0,0 +1,202 @@ +# AI智能文献模块 - 数据库设计 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 +> **最后更新:** 2025-10-29 + +--- + +## 📋 文档说明 + +本文档描述AI智能文献模块的数据库设计,包括数据表结构、关系设计、索引设计等。 + +--- + +## 🗄️ 核心数据表 + +### 1. 文献筛选项目表 (literature_screening_projects) + +```sql +CREATE TABLE literature_screening_projects ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id), + project_name VARCHAR(255) NOT NULL, + protocol_id UUID, -- 研究方案ID(未来关联) + + -- PICO标准 + pico_criteria JSONB, -- PICO结构化数据 + + -- 筛选标准 + inclusion_criteria TEXT, + exclusion_criteria TEXT, + + -- 状态 + status VARCHAR(50) DEFAULT 'draft', -- draft, screening, completed + + -- 筛选配置 + screening_config JSONB, -- 筛选配置(双模型选择等) + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +### 2. 文献条目表 (literature_items) + +```sql +CREATE TABLE literature_items ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + project_id UUID NOT NULL REFERENCES literature_screening_projects(id) ON DELETE CASCADE, + + -- 文献基本信息 + pmid VARCHAR(50), + title TEXT, + authors TEXT, + journal VARCHAR(255), + publication_year INTEGER, + doi VARCHAR(255), + abstract TEXT, + + -- 文件信息 + full_text_file_path VARCHAR(500), + full_text_status VARCHAR(50), -- not_required, pending, downloaded, uploaded, failed + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + + UNIQUE(project_id, pmid) +); +``` + +### 3. 标题摘要初筛结果表 (title_abstract_screening_results) + +```sql +CREATE TABLE title_abstract_screening_results ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + project_id UUID NOT NULL REFERENCES literature_screening_projects(id) ON DELETE CASCADE, + literature_item_id UUID NOT NULL REFERENCES literature_items(id) ON DELETE CASCADE, + + -- DS模型判断 + ds_p_judgment VARCHAR(10), -- ✓, ✗, ? + ds_i_judgment VARCHAR(10), + ds_c_judgment VARCHAR(10), + ds_s_judgment VARCHAR(10), + ds_conclusion VARCHAR(20), -- include, exclude + + -- DS模型证据 + ds_p_evidence TEXT, + ds_i_evidence TEXT, + ds_c_evidence TEXT, + ds_s_evidence TEXT, + + -- Q3模型判断 + q3_p_judgment VARCHAR(10), + q3_i_judgment VARCHAR(10), + q3_c_judgment VARCHAR(10), + q3_s_judgment VARCHAR(10), + q3_conclusion VARCHAR(20), + + -- Q3模型证据 + q3_p_evidence TEXT, + q3_i_evidence TEXT, + q3_c_evidence TEXT, + q3_s_evidence TEXT, + + -- 冲突状态 + conflict_status VARCHAR(20) DEFAULT 'none', -- none, conflict, resolved + + -- 最终决策 + final_decision VARCHAR(20), -- include, exclude, pending + final_decision_by UUID REFERENCES users(id), + final_decision_at TIMESTAMP, + exclusion_reason TEXT, + + -- AI处理状态 + ai_processing_status VARCHAR(50) DEFAULT 'pending', -- pending, processing, completed, failed + ai_processed_at TIMESTAMP, + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + + UNIQUE(project_id, literature_item_id) +); +``` + +### 4. 筛选任务表 (screening_tasks) + +```sql +CREATE TABLE screening_tasks ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + project_id UUID NOT NULL REFERENCES literature_screening_projects(id) ON DELETE CASCADE, + + task_type VARCHAR(50) NOT NULL, -- title_abstract, full_text + status VARCHAR(50) DEFAULT 'pending', -- pending, running, completed, failed + + total_items INTEGER, + processed_items INTEGER DEFAULT 0, + success_items INTEGER DEFAULT 0, + failed_items INTEGER DEFAULT 0, + + started_at TIMESTAMP, + completed_at TIMESTAMP, + error_message TEXT, + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +--- + +## 📊 数据关系图 + +``` +literature_screening_projects (1) ──< (N) literature_items +literature_screening_projects (1) ──< (N) title_abstract_screening_results +literature_items (1) ──< (1) title_abstract_screening_results +literature_screening_projects (1) ──< (N) screening_tasks +``` + +--- + +## 🔍 索引设计 + +```sql +-- 文献条目表索引 +CREATE INDEX idx_literature_items_project_id ON literature_items(project_id); +CREATE INDEX idx_literature_items_pmid ON literature_items(pmid); + +-- 筛选结果表索引 +CREATE INDEX idx_screening_results_project_id ON title_abstract_screening_results(project_id); +CREATE INDEX idx_screening_results_item_id ON title_abstract_screening_results(literature_item_id); +CREATE INDEX idx_screening_results_conflict ON title_abstract_screening_results(conflict_status); +CREATE INDEX idx_screening_results_decision ON title_abstract_screening_results(final_decision); + +-- 任务表索引 +CREATE INDEX idx_screening_tasks_project_id ON screening_tasks(project_id); +CREATE INDEX idx_screening_tasks_status ON screening_tasks(status); +``` + +--- + +## ⏳ 待完善内容 + +后续将补充: +- 全文复筛相关表结构 +- 数据提取相关表结构 +- 数据迁移方案 +- 数据字典 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/02-技术设计/02-API设计规范.md b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/02-API设计规范.md new file mode 100644 index 00000000..cdc42804 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/02-API设计规范.md @@ -0,0 +1,238 @@ +# AI智能文献模块 - API设计规范 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 +> **最后更新:** 2025-10-29 + +--- + +## 📋 文档说明 + +本文档描述AI智能文献模块的API设计规范,包括接口定义、请求响应格式、错误处理等。 + +--- + +## 🔌 API设计原则 + +1. **RESTful设计**: 遵循RESTful API设计规范 +2. **统一响应格式**: 统一的成功/错误响应结构 +3. **分页支持**: 列表接口支持分页 +4. **版本控制**: API版本化管理 +5. **认证授权**: 所有接口需要JWT认证 + +--- + +## 📡 核心API接口 + +### 1. 项目管理 + +#### 创建筛选项目 +``` +POST /api/literature/projects +Request Body: +{ + "projectName": "string", + "picoCriteria": {...}, + "inclusionCriteria": "string", + "exclusionCriteria": "string" +} +Response: +{ + "code": 200, + "data": { + "id": "uuid", + "projectName": "string", + ... + } +} +``` + +#### 获取项目列表 +``` +GET /api/literature/projects?page=1&pageSize=10 +``` + +#### 获取项目详情 +``` +GET /api/literature/projects/:projectId +``` + +#### 更新项目 +``` +PUT /api/literature/projects/:projectId +``` + +#### 删除项目 +``` +DELETE /api/literature/projects/:projectId +``` + +### 2. 文献管理 + +#### 导入文献(Excel) +``` +POST /api/literature/projects/:projectId/items/import +Content-Type: multipart/form-data +Body: file (Excel文件) +Response: +{ + "code": 200, + "data": { + "importedCount": 100, + "items": [...] + } +} +``` + +#### 获取文献列表 +``` +GET /api/literature/projects/:projectId/items?page=1&pageSize=50 +``` + +#### 获取文献详情 +``` +GET /api/literature/projects/:projectId/items/:itemId +``` + +### 3. 标题摘要初筛 + +#### 启动筛选任务 +``` +POST /api/literature/projects/:projectId/screening/start +Request Body: +{ + "screeningType": "title_abstract", + "modelConfig": { + "ds": true, + "q3": true + } +} +Response: +{ + "code": 200, + "data": { + "taskId": "uuid", + "status": "running" + } +} +``` + +#### 获取筛选结果 +``` +GET /api/literature/projects/:projectId/screening/results +Query Params: + - page: 页码 + - pageSize: 每页数量 + - conflictOnly: 只显示冲突项 + - decision: include/exclude/pending +``` + +#### 更新最终决策 +``` +PUT /api/literature/projects/:projectId/screening/results/:resultId +Request Body: +{ + "finalDecision": "include", // include/exclude + "exclusionReason": "string" // 排除时必填 +} +``` + +#### 批量更新决策 +``` +POST /api/literature/projects/:projectId/screening/results/batch-update +Request Body: +{ + "itemIds": ["uuid1", "uuid2", ...], + "finalDecision": "include", + "exclusionReason": "string" +} +``` + +### 4. 任务管理 + +#### 获取任务状态 +``` +GET /api/literature/projects/:projectId/tasks/:taskId +Response: +{ + "code": 200, + "data": { + "id": "uuid", + "status": "running", // pending/running/completed/failed + "totalItems": 100, + "processedItems": 45, + "progress": 45 + } +} +``` + +#### 任务进度流式推送(SSE) +``` +GET /api/literature/projects/:projectId/tasks/:taskId/progress +Accept: text/event-stream +``` + +--- + +## 📋 响应格式规范 + +### 成功响应 +```json +{ + "code": 200, + "message": "success", + "data": {...} +} +``` + +### 错误响应 +```json +{ + "code": 400, + "message": "错误描述", + "error": { + "code": "ERROR_CODE", + "details": "..." + } +} +``` + +### 分页响应 +```json +{ + "code": 200, + "data": { + "items": [...], + "pagination": { + "page": 1, + "pageSize": 20, + "total": 100, + "totalPages": 5 + } + } +} +``` + +--- + +## ⏳ 待完善内容 + +后续将补充: +- 完整的API文档(所有接口详细说明) +- 请求/响应示例 +- 错误码定义 +- 接口测试用例 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/02-技术设计/03-前端组件设计.md b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/03-前端组件设计.md new file mode 100644 index 00000000..2a69f49c --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/03-前端组件设计.md @@ -0,0 +1,155 @@ +# AI智能文献模块 - 前端组件设计 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 +> **最后更新:** 2025-10-29 + +--- + +## 📋 文档说明 + +本文档描述AI智能文献模块的前端组件设计,包括组件结构、组件接口、交互设计等。 + +--- + +## 🧩 组件架构 + +### 标题摘要初筛模块组件结构 + +``` +LiteratureScreeningModule/ +├── TitleAbstractScreening/ +│ ├── SetupView/ # 设置与启动视图 +│ │ ├── CriteriaReference.tsx # 标准参考面板 +│ │ ├── CriteriaAdjustment.tsx # 临时调整标准 +│ │ ├── LiteratureImport.tsx # 文献导入组件 +│ │ └── StartScreeningButton.tsx # 启动筛选按钮 +│ │ +│ ├── ReviewTableView/ # 表格化审核工作台 ⭐核心 +│ │ ├── ScreeningTable.tsx # 主表格组件 +│ │ ├── TableHeader.tsx # 表头(双行结构) +│ │ ├── TableRow.tsx # 表格行 +│ │ ├── ExpandableRow.tsx # 可展开行(证据展示) +│ │ ├── JudgmentCell.tsx # 判断单元格(✓/✗/?) +│ │ ├── ConflictIndicator.tsx # 冲突状态指示器 +│ │ └── DecisionSelector.tsx # 最终决策选择器 +│ │ +│ ├── EvidenceModal/ # 双视图原文审查模态框 +│ │ ├── ModalContainer.tsx # 模态框容器 +│ │ ├── AbstractView.tsx # 左侧:摘要视图 +│ │ ├── EvidenceView.tsx # 右侧:证据视图 +│ │ └── HighlightedText.tsx # 高亮文本组件 +│ │ +│ ├── ResultView/ # 结果展示视图 +│ │ ├── StatisticsCards.tsx # 统计卡片 +│ │ ├── PrismaSummary.tsx # PRISMA式排除总结 +│ │ ├── ResultTabs.tsx # 结果Tab页 +│ │ └── ResultTable.tsx # 结果表格 +│ │ +│ └── shared/ # 共享组件 +│ ├── ProtocolOverview.tsx # 研究方案概览面板 +│ ├── BatchOperation.tsx # 批量操作组件 +│ └── ExportButton.tsx # 导出按钮 +``` + +--- + +## 🎨 核心组件设计 + +### 1. ScreeningTable (表格化审核工作台) + +**组件职责**: +- 展示文献列表和筛选结果 +- 支持展开/收起查看证据 +- 支持点击判断查看详情 +- 支持批量操作 + +**Props接口**: +```typescript +interface ScreeningTableProps { + projectId: string; + items: LiteratureItem[]; + results: ScreeningResult[]; + onDecisionChange: (itemId: string, decision: string) => void; + onBatchUpdate: (itemIds: string[], decision: string) => void; +} +``` + +### 2. EvidenceModal (双视图原文审查模态框) + +**组件职责**: +- 左侧显示摘要/全文 +- 右侧显示AI判断和证据 +- 支持文本高亮 +- 支持查看引用来源 + +**Props接口**: +```typescript +interface EvidenceModalProps { + visible: boolean; + itemId: string; + dimension: 'P' | 'I' | 'C' | 'S'; + onClose: () => void; +} +``` + +### 3. ReviewTableView (审核工作台主视图) + +**组件职责**: +- 整合表格和模态框 +- 管理筛选状态 +- 处理用户交互 + +--- + +## 🔄 状态管理 + +### 使用Zustand管理筛选状态 + +```typescript +interface ScreeningStore { + currentProject: Project | null; + items: LiteratureItem[]; + results: ScreeningResult[]; + selectedItems: string[]; + loading: boolean; + + // Actions + loadProject: (projectId: string) => Promise; + updateDecision: (itemId: string, decision: string) => Promise; + batchUpdate: (itemIds: string[], decision: string) => Promise; +} +``` + +--- + +## 📱 响应式设计 + +### 表格布局适配 +- **桌面端**: 完整表格显示 +- **平板端**: 可横向滚动,关键列固定 +- **移动端**: 卡片式布局替代表格 + +--- + +## ⏳ 待完善内容 + +后续将补充: +- 详细组件接口定义 +- 组件交互流程图 +- 样式设计规范 +- 组件使用示例 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/02-技术设计/04-AI模型集成设计.md b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/04-AI模型集成设计.md new file mode 100644 index 00000000..c1a1cd4e --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/04-AI模型集成设计.md @@ -0,0 +1,24 @@ +# AI模型集成设计 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/02-技术设计/05-文件处理设计.md b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/05-文件处理设计.md new file mode 100644 index 00000000..1bcb07d6 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/05-文件处理设计.md @@ -0,0 +1,24 @@ +# 文件处理设计 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/02-技术设计/06-质量保障与可追溯策略.md b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/06-质量保障与可追溯策略.md new file mode 100644 index 00000000..200295be --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/06-质量保障与可追溯策略.md @@ -0,0 +1,949 @@ +# ASL 质量保障与可追溯策略 + +> **文档版本:** V1.0 +> **创建日期:** 2025-11-15 +> **适用模块:** AI 智能文献(ASL) +> **目标:** 分阶段提升文献筛选、数据提取的准确率、质量控制和可追溯性 + +--- + +## 📋 文档概述 + +本文档定义了 ASL 模块在 **MVP → V1.0 → V2.0** 三个阶段中,如何逐步提升: +1. **提取准确率**:从基础可用 → 高质量 → 医学级标准 +2. **质量控制**:从人工抽查 → 自动验证 → 智能仲裁 +3. **可追溯性**:从基本记录 → 完整证据链 → 审计级日志 + +### 核心设计原则 + +| 原则 | 说明 | +|------|------| +| **成本可控** | MVP 阶段优先使用 DeepSeek + Qwen3,成本敏感 | +| **质量可升级** | 可切换到 GPT-5-Pro + Claude-4.5 高端组合 | +| **分步实施** | 避免过度设计,每个阶段交付可用功能 | +| **医学场景优化** | 针对英文医学文献的特点优化策略 | + +--- + +## 🎯 三阶段路线图 + +``` +MVP (4周) V1.0 (6周) V2.0 (8周) +├─ 基础双模型验证 ├─ 智能质量控制 ├─ 医学级质量保障 +├─ JSON Schema 约束 ├─ 分段提取优化 ├─ 多模型共识仲裁 +├─ 置信度评分 ├─ 证据链完整追溯 ├─ 自动质量审计 +├─ 人工复核机制 ├─ 规则引擎验证 ├─ 提示词版本管理 +└─ 基本追溯日志 └─ Few-shot 示例库 └─ HITL 智能分流 + ↓ ↓ ↓ + 可用 高质量 医学级 +``` + +--- + +## 🚀 MVP 阶段(4 周) + +### 目标定位 + +- **准确率目标**:≥ 85% +- **成本预算**:筛选 1000 篇文献 ≤ ¥50 +- **交付标准**:基础功能可用,支持双模型对比 + +### 一、模型选择策略 + +#### 1.1 主力模型组合(成本优先) + +| 角色 | 模型 | Model ID | 用途 | 成本 | +|------|------|---------|------|------| +| **模型 A** | DeepSeek-V3 | `deepseek-chat` | 快速初筛 | ¥0.001/1K tokens | +| **模型 B** | Qwen3-72B | `qwen-max` | 交叉验证 | ¥0.004/1K tokens | + +**切换选项**(质量优先): +- **高端组合**:GPT-5-Pro (`gpt-5-pro`) + Claude-4.5-Sonnet (`claude-sonnet-4-5-20250929`) +- **成本增加**:约 3-5 倍 +- **准确率提升**:85% → 92%+ + +#### 1.2 模型调用策略 + +```typescript +// 双模型并行调用 +async function dualModelScreening( + literature: Literature, + protocol: Protocol +) { + // 并行调用两个模型 + const [resultA, resultB] = await Promise.all([ + llmService.chat('deepseek', buildPrompt(literature, protocol)), + llmService.chat('qwen', buildPrompt(literature, protocol)) + ]); + + // 解析 JSON 结果 + const decisionA = parseJSON(resultA.content); + const decisionB = parseJSON(resultB.content); + + // 一致性判断 + if (decisionA.decision === decisionB.decision) { + return { + finalDecision: decisionA.decision, + consensus: 'high', + needReview: false, + models: [decisionA, decisionB] + }; + } + + // 冲突 → 人工复核 + return { + finalDecision: 'uncertain', + consensus: 'conflict', + needReview: true, + models: [decisionA, decisionB] + }; +} +``` + +### 二、核心技术策略 + +#### 2.1 ✅ 双模型交叉验证 + +**实施方案**: +- 所有筛选任务同时调用两个模型 +- 自动对比结果,标记差异 +- 一致率作为质量指标(目标 ≥ 80%) + +**代码示例**: +```typescript +interface DualModelResult { + consensus: 'high' | 'conflict'; + finalDecision: 'include' | 'exclude' | 'uncertain'; + needReview: boolean; + models: ModelDecision[]; +} +``` + +#### 2.2 ✅ JSON Schema 约束 + +**实施方案**: +- 定义严格的输出格式 +- 使用枚举限制取值 +- 区分必填/可选字段 + +**Schema 定义**: +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["decision", "reason", "confidence", "pico"], + "properties": { + "decision": { + "type": "string", + "enum": ["include", "exclude", "uncertain"] + }, + "reason": { + "type": "string", + "minLength": 10, + "maxLength": 500 + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "pico": { + "type": "object", + "required": ["population", "intervention", "comparison", "outcome"], + "properties": { + "population": { + "type": "string", + "enum": ["match", "partial", "mismatch"] + }, + "intervention": { + "type": "string", + "enum": ["match", "partial", "mismatch"] + }, + "comparison": { + "type": "string", + "enum": ["match", "partial", "mismatch", "not_applicable"] + }, + "outcome": { + "type": "string", + "enum": ["match", "partial", "mismatch"] + } + } + }, + "studyDesign": { + "type": "string", + "enum": ["RCT", "cohort", "case-control", "cross-sectional", "other"] + } + } +} +``` + +**提示词模板**: +```typescript +const prompt = ` +你是一位医学文献筛选专家。请根据以下 PICO 标准判断这篇文献是否应该纳入系统评价。 + +# PICO 标准 +- Population: ${protocol.population} +- Intervention: ${protocol.intervention} +- Comparison: ${protocol.comparison} +- Outcome: ${protocol.outcome} + +# 文献信息 +标题: ${literature.title} +摘要: ${literature.abstract} + +# 输出要求 +请严格按照以下 JSON Schema 输出结果: + +${JSON.stringify(schema, null, 2)} + +注意: +1. decision 只能是 "include"、"exclude" 或 "uncertain" +2. reason 必须具体说明判断依据(10-500字) +3. confidence 为 0-1 之间的数值,表示你的判断把握 +4. pico 字段逐项评估匹配程度 +`; +``` + +#### 2.3 ✅ 置信度评分 + +**实施方案**: +- 要求模型对每个判断给出置信度(0-1) +- 置信度 < 0.7 自动标记为需人工复核 +- 记录置信度分布,优化阈值 + +**自动分流规则**: +```typescript +function autoTriage(result: DualModelResult) { + const avgConfidence = ( + result.models[0].confidence + + result.models[1].confidence + ) / 2; + + // 规则1:冲突 → 必须复核 + if (result.consensus === 'conflict') { + return { needReview: true, priority: 'high' }; + } + + // 规则2:低置信度 → 需要复核 + if (avgConfidence < 0.7) { + return { needReview: true, priority: 'medium' }; + } + + // 规则3:高置信度 + 一致 → 自动通过 + return { needReview: false, priority: 'low' }; +} +``` + +#### 2.4 ✅ 基础可追溯 + +**实施方案**: +- 保存原始提示词和模型输出 +- 记录模型版本和时间戳 +- 关联人工复核记录 + +**数据库设计**: +```prisma +model ScreeningResult { + id String @id @default(uuid()) + literatureId String + protocolId String + + // 模型A结果 + modelAName String // "deepseek-chat" + modelAOutput Json // 原始JSON输出 + modelAConfidence Float + + // 模型B结果 + modelBName String // "qwen-max" + modelBOutput Json + modelBConfidence Float + + // 最终决策 + finalDecision String // "include"/"exclude"/"uncertain" + consensus String // "high"/"conflict" + needReview Boolean + + // 人工复核 + reviewedBy String? + reviewedAt DateTime? + reviewDecision String? + reviewNotes String? + + // 可追溯信息 + promptTemplate String @db.Text // 使用的提示词模板 + createdAt DateTime @default(now()) + + @@map("asl_screening_results") +} +``` + +### 三、MVP 成本预算 + +**场景:筛选 1000 篇文献** + +| 项目 | DeepSeek | Qwen3 | 合计 | +|------|----------|-------|------| +| 输入 tokens(平均) | 800 | 800 | - | +| 输出 tokens(平均) | 200 | 200 | - | +| 单次成本 | ¥0.001 | ¥0.004 | ¥0.005 | +| **1000 篇总成本** | ¥1 | ¥4 | **¥5** | + +**冲突率 20% 人工复核**: +- 自动通过:800 篇 × ¥0.005 = ¥4 +- 人工复核:200 篇 × 2 分钟 = 6.7 小时 +- **总成本**:¥4 + 人工成本 + +### 四、MVP 验收标准 + +| 指标 | 目标 | 验证方法 | +|------|------|----------| +| 双模型一致率 | ≥ 80% | 统计报表 | +| JSON Schema 验证通过率 | ≥ 95% | 自动检查 | +| 人工复核队列占比 | ≤ 20% | 系统统计 | +| 提取结果可追溯 | 100% | 审计检查 | +| 成本控制 | ≤ ¥50/1000 篇 | 账单监控 | + +--- + +## 📈 V1.0 阶段(6 周) + +### 目标定位 + +- **准确率目标**:≥ 90% +- **成本预算**:筛选 1000 篇文献 ≤ ¥80 +- **交付标准**:高质量输出,智能质量控制 + +### 一、模型策略优化 + +#### 1.1 成本优化策略 + +**核心思路**:80% 用低成本模型,20% 高价值任务用顶级模型 + +```typescript +async function smartScreening(literature: Literature, protocol: Protocol) { + // 第一阶段:快速初筛(DeepSeek) + const quickResult = await llmService.chat('deepseek', buildPrompt(...)); + const quickDecision = parseJSON(quickResult.content); + + // 如果高置信度 + 明确结论 → 直接采纳 + if ( + quickDecision.confidence > 0.85 && + quickDecision.decision !== 'uncertain' + ) { + return { + finalDecision: quickDecision.decision, + strategy: 'cost-optimized', + models: [quickDecision] + }; + } + + // 否则 → 启用高端模型复核 + const detailedResult = await llmService.chat('gpt5', buildPrompt(...)); + return { + finalDecision: detailedResult.decision, + strategy: 'quality-assured', + models: [quickDecision, detailedResult] + }; +} +``` + +**预期成本节省**: +- 80% 任务用 DeepSeek:800 × ¥0.001 = ¥0.8 +- 20% 任务用 GPT-5:200 × ¥0.10 = ¥20 +- **总成本**:¥20.8(相比全用 GPT-5 节省 80%) + +### 二、核心技术增强 + +#### 2.1 ✅ Few-shot 示例库 + +**实施方案**: +- 人工标注 20-30 个高质量示例 +- 针对不同研究类型分类(RCT、队列、病例对照) +- 动态选择相似示例嵌入提示词 + +**示例格式**: +```json +{ + "examples": [ + { + "title": "Effect of aspirin on cardiovascular events in patients with diabetes", + "abstract": "...", + "goldStandard": { + "decision": "include", + "reason": "RCT研究,人群为糖尿病患者(匹配P),干预为阿司匹林(匹配I),对照为安慰剂(匹配C),结局为心血管事件(匹配O)", + "pico": { + "population": "match", + "intervention": "match", + "comparison": "match", + "outcome": "match" + }, + "studyDesign": "RCT" + } + } + ] +} +``` + +**提示词增强**: +```typescript +const promptWithExamples = ` +# 参考示例 + +以下是 3 个标注好的示例,帮助你理解判断标准: + +${examples.map((ex, i) => ` +## 示例 ${i + 1} +标题: ${ex.title} +摘要: ${ex.abstract} +判断: ${ex.goldStandard.decision} +理由: ${ex.goldStandard.reason} +`).join('\n')} + +# 待筛选文献 +标题: ${literature.title} +摘要: ${literature.abstract} + +请参考上述示例,输出你的判断结果(JSON格式)。 +`; +``` + +#### 2.2 ✅ 分段提取 + +**实施方案**: +- 针对全文数据提取,按章节分段处理 +- 每段独立提取,减少上下文混淆 +- 最后合并结果,交叉验证一致性 + +**分段策略**: +```typescript +async function segmentedExtraction(fullText: string, protocol: Protocol) { + // 分段 + const sections = { + methods: extractSection(fullText, 'methods'), + results: extractSection(fullText, 'results'), + tables: extractTables(fullText), + }; + + // 并行提取 + const [methodsData, resultsData, tablesData] = await Promise.all([ + extractFromMethods(sections.methods, protocol), + extractFromResults(sections.results, protocol), + extractFromTables(sections.tables, protocol), + ]); + + // 合并结果 + return mergeExtractionResults([methodsData, resultsData, tablesData]); +} +``` + +**提取示例(方法学部分)**: +```typescript +const methodsPrompt = ` +请从以下方法学部分提取研究设计信息: + +# 方法学原文 +${methodsSection} + +# 提取字段 +- 研究设计类型(RCT/cohort/case-control等) +- 样本量(干预组/对照组) +- 纳入标准 +- 排除标准 +- 随机化方法(如适用) +- 盲法(如适用) + +# 输出格式(JSON) +${methodsSchema} +`; +``` + +#### 2.3 ✅ 规则引擎验证 + +**实施方案**: +- 定义业务规则,自动检查逻辑错误 +- 数值范围验证 +- 必填字段完整性检查 + +**验证规则**: +```typescript +const validationRules = [ + { + name: '样本量合理性', + check: (data) => { + const total = data.sampleSize.intervention + data.sampleSize.control; + return total >= 10 && total <= 100000; + }, + errorMessage: '样本量超出合理范围(10-100000)' + }, + { + name: 'P值范围', + check: (data) => { + return data.pValue >= 0 && data.pValue <= 1; + }, + errorMessage: 'P值必须在0-1之间' + }, + { + name: '必填字段完整性', + check: (data) => { + const required = ['studyDesign', 'sampleSize', 'primaryOutcome']; + return required.every(field => data[field] != null); + }, + errorMessage: '缺少必填字段' + } +]; + +function validateExtraction(data: ExtractionResult): ValidationReport { + const errors = []; + for (const rule of validationRules) { + if (!rule.check(data)) { + errors.push(rule.errorMessage); + } + } + return { + isValid: errors.length === 0, + errors + }; +} +``` + +#### 2.4 ✅ 完整证据链 + +**实施方案**: +- 记录原文引用位置(页码、段落、句子) +- 保存模型完整输出(含中间推理) +- 关联所有人工修改记录 + +**数据库增强**: +```prisma +model ExtractionResult { + id String @id @default(uuid()) + + // 提取内容 + extractedData Json + + // 证据链(新增) + evidenceChain Json // { + // "sampleSize": { + // "value": 150, + // "source": { + // "page": 3, + // "paragraph": 2, + // "text": "A total of 150 patients were enrolled..." + // } + // } + // } + + // 模型信息 + modelName String + modelVersion String + promptVersion String // "v1.2.0" + rawOutput String @db.Text // 原始输出(含CoT推理) + + // 修改历史 + revisions ExtractionRevision[] + + createdAt DateTime @default(now()) + @@map("asl_extraction_results") +} + +model ExtractionRevision { + id String @id @default(uuid()) + extractionId String + + fieldName String // 修改的字段 + oldValue Json + newValue Json + reason String // 修改理由 + + revisedBy String + revisedAt DateTime @default(now()) + + extraction ExtractionResult @relation(fields: [extractionId], references: [id]) + @@map("asl_extraction_revisions") +} +``` + +### 三、V1.0 成本预算 + +**场景:筛选 1000 篇 + 提取 200 篇全文** + +| 任务 | 策略 | 成本 | +|------|------|------| +| 标题摘要筛选 | 80% DeepSeek + 20% GPT-5 | ¥21 | +| 全文数据提取 | 分段提取(GPT-5) | ¥60 | +| **总成本** | - | **¥81** | + +### 四、V1.0 验收标准 + +| 指标 | 目标 | 验证方法 | +|------|------|----------| +| 提取准确率 | ≥ 90% | 人工抽查 50 篇 | +| Few-shot 示例库 | ≥ 20 个 | 人工标注 | +| 规则引擎覆盖率 | ≥ 80% | 代码审查 | +| 证据链完整性 | 100% | 审计检查 | +| 成本控制 | ≤ ¥80/项目 | 账单监控 | + +--- + +## 🏆 V2.0 阶段(8 周) + +### 目标定位 + +- **准确率目标**:≥ 95%(医学级) +- **成本预算**:按需配置 +- **交付标准**:自动化质量审计,符合临床研究规范 + +### 一、医学级质量保障 + +#### 1.1 ✅ 三模型共识仲裁 + +**实施方案**: +- 双模型冲突时,自动启用第三方仲裁 +- 三模型投票决策 +- 记录仲裁过程 + +```typescript +async function threeModelArbitration( + literature: Literature, + protocol: Protocol +) { + // 第一轮:双模型 + const [resultA, resultB] = await Promise.all([ + llmService.chat('deepseek', buildPrompt(...)), + llmService.chat('qwen', buildPrompt(...)) + ]); + + // 如果一致,直接返回 + if (resultA.decision === resultB.decision) { + return { finalDecision: resultA.decision, arbitration: false }; + } + + // 冲突 → 启用 Claude 仲裁 + console.log('检测到冲突,启用 Claude-4.5 仲裁...'); + const resultC = await llmService.chat('claude', buildPrompt(...)); + + // 三模型投票 + const votes = [resultA.decision, resultB.decision, resultC.decision]; + const voteCount = { + include: votes.filter(v => v === 'include').length, + exclude: votes.filter(v => v === 'exclude').length, + uncertain: votes.filter(v => v === 'uncertain').length, + }; + + // 多数决 + const winner = Object.entries(voteCount) + .sort((a, b) => b[1] - a[1])[0][0]; + + return { + finalDecision: winner, + arbitration: true, + votes: { resultA, resultB, resultC }, + consensus: voteCount[winner] >= 2 ? 'strong' : 'weak' + }; +} +``` + +**成本控制**: +- 仅在冲突时启用仲裁(预计 10-15%) +- 单次仲裁额外成本:¥0.021(Claude-4.5) + +#### 1.2 ✅ HITL 智能分流 + +**实施方案**: +- 基于规则的智能优先级排序 +- 高价值/高风险任务优先人工复核 +- 低风险任务自动化处理 + +**分流规则**: +```typescript +function intelligentTriage(result: ScreeningResult): TriageDecision { + let priority = 0; + let needReview = false; + + // 规则1:三模型仍不一致 → 最高优先级 + if (result.arbitration && result.consensus === 'weak') { + priority = 100; + needReview = true; + } + // 规则2:RCT 研究 → 中等优先级 + else if (result.studyDesign === 'RCT') { + priority = 70; + needReview = result.confidence < 0.9; + } + // 规则3:关键结局指标 → 高优先级 + else if (result.outcome.includes('mortality')) { + priority = 80; + needReview = result.confidence < 0.85; + } + // 规则4:高置信度 + 一致 → 自动通过 + else if (result.confidence > 0.95 && result.consensus === 'high') { + priority = 10; + needReview = false; + } + + return { priority, needReview }; +} +``` + +#### 1.3 ✅ 提示词版本管理 + +**实施方案**: +- Git 管理提示词模板 +- 版本号标记(语义化版本) +- A/B 测试不同版本效果 + +**目录结构**: +``` +backend/prompts/asl/ +├── screening/ +│ ├── v1.0.0-basic.txt +│ ├── v1.1.0-with-examples.txt +│ └── v1.2.0-cot.txt +├── extraction/ +│ ├── v1.0.0-methods.txt +│ └── v1.1.0-methods-segmented.txt +└── changelog.md +``` + +**版本记录**: +```prisma +model PromptVersion { + id String @id @default(uuid()) + + name String // "screening-v1.2.0" + content String @db.Text + version String // "1.2.0" + changelog String // "增加 Few-shot 示例" + + // 性能指标 + accuracy Float? // 0.92 + usageCount Int @default(0) + + isActive Boolean @default(false) + createdAt DateTime @default(now()) + + @@map("asl_prompt_versions") +} +``` + +#### 1.4 ✅ 自动质量审计 + +**实施方案**: +- 定期批量抽查(10%) +- 自动生成质量报告 +- 异常检测和告警 + +**审计报表**: +```typescript +interface QualityAuditReport { + period: { start: Date; end: Date }; + totalTasks: number; + sampledTasks: number; + + metrics: { + accuracy: number; // 准确率 + interRaterAgreement: number; // 人机一致性 + falsePositiveRate: number; // 假阳性率 + falseNegativeRate: number; // 假阴性率 + }; + + modelPerformance: { + deepseek: { accuracy: number; avgConfidence: number }; + qwen: { accuracy: number; avgConfidence: number }; + gpt5: { accuracy: number; avgConfidence: number }; + }; + + issues: { + type: string; + count: number; + examples: string[]; + }[]; + + recommendations: string[]; +} +``` + +### 二、高级提示词工程 + +#### 2.1 ✅ Chain of Thought (CoT) + +**实施方案**: +- 要求模型输出推理过程 +- 分步骤判断 PICO 匹配度 +- 最后给出综合结论 + +**提示词示例**: +``` +请按照以下步骤判断这篇文献是否应该纳入: + +# Step 1: 研究设计判断 +- 识别研究类型(RCT/队列/病例对照等) +- 判断是否符合纳入标准 + +# Step 2: PICO 逐项评估 +- Population: 详细分析人群是否匹配 +- Intervention: 详细分析干预措施是否匹配 +- Comparison: 详细分析对照是否匹配 +- Outcome: 详细分析结局指标是否匹配 + +# Step 3: 综合判断 +- 汇总以上分析 +- 给出最终决策(include/exclude/uncertain) +- 评估置信度(0-1) + +# 输出格式 +{ + "reasoning": { + "studyDesign": "这是一项...", + "population": "人群匹配度分析...", + "intervention": "干预措施分析...", + "comparison": "对照分析...", + "outcome": "结局指标分析..." + }, + "decision": "include", + "confidence": 0.95, + "reason": "基于以上分析..." +} +``` + +#### 2.2 ✅ 动态示例选择 + +**实施方案**: +- 计算待筛选文献与示例库的语义相似度 +- 动态选择最相似的 3-5 个示例 +- 嵌入提示词 + +```typescript +async function selectSimilarExamples( + literature: Literature, + examplePool: Example[] +): Promise { + // 使用嵌入模型计算相似度 + const literatureEmbedding = await getEmbedding( + `${literature.title} ${literature.abstract}` + ); + + const similarities = examplePool.map(ex => ({ + example: ex, + similarity: cosineSimilarity(literatureEmbedding, ex.embedding) + })); + + // 返回最相似的 5 个 + return similarities + .sort((a, b) => b.similarity - a.similarity) + .slice(0, 5) + .map(s => s.example); +} +``` + +### 三、V2.0 成本预算 + +**场景:高质量系统评价项目(筛选 5000 篇 + 提取 300 篇)** + +| 任务 | 策略 | 成本 | +|------|------|------| +| 标题摘要筛选 | 成本优化 + 15% 仲裁 | ¥120 | +| 全文数据提取 | GPT-5 + Claude 双模型 | ¥350 | +| 质量审计 | 10% 抽查 | ¥30 | +| **总成本** | - | **¥500** | + +### 四、V2.0 验收标准 + +| 指标 | 目标 | 验证方法 | +|------|------|----------| +| 提取准确率 | ≥ 95% | 人工抽查 100 篇 | +| 人机一致性 | ≥ 90% | Cohen's Kappa | +| 假阳性率 | ≤ 5% | 统计分析 | +| 假阴性率 | ≤ 3% | 统计分析 | +| 提示词版本管理 | 100% | Git 历史 | +| 自动化审计 | 每周 1 次 | 系统报表 | + +--- + +## 📊 三阶段对比总结 + +| 维度 | MVP | V1.0 | V2.0 | +|------|-----|------|------| +| **准确率** | 85% | 90% | 95% | +| **模型组合** | DeepSeek + Qwen3 | 成本优化策略 | 三模型仲裁 | +| **质量控制** | 双模型验证 | 规则引擎 + Few-shot | HITL + 自动审计 | +| **可追溯性** | 基本日志 | 完整证据链 | 审计级记录 | +| **成本/1000 篇** | ¥5 | ¥21 | ¥24 + 仲裁 | +| **开发周期** | 4 周 | 6 周 | 8 周 | +| **适用场景** | 快速验证 | 常规项目 | 高质量发表 | + +--- + +## 🔄 实施路径 + +### 阶段 1: MVP 开发(Week 1-4) + +**Week 1**:基础架构 +- [ ] LLM 服务封装(DeepSeek + Qwen3) +- [ ] JSON Schema 定义 +- [ ] 数据库表设计 + +**Week 2**:核心功能 +- [ ] 双模型并行调用 +- [ ] 一致性判断逻辑 +- [ ] 人工复核队列 + +**Week 3**:前端开发 +- [ ] 筛选工作台 +- [ ] 冲突对比视图 +- [ ] 人工复核界面 + +**Week 4**:测试验收 +- [ ] 功能测试 +- [ ] 准确率评估 +- [ ] 成本监控 + +### 阶段 2: V1.0 增强(Week 5-10) + +**Week 5-6**:智能优化 +- [ ] 成本优化策略 +- [ ] Few-shot 示例库 +- [ ] 动态示例选择 + +**Week 7-8**:质量控制 +- [ ] 分段提取 +- [ ] 规则引擎 +- [ ] 证据链完整化 + +**Week 9-10**:测试优化 +- [ ] A/B 测试 +- [ ] 准确率提升 +- [ ] 文档完善 + +### 阶段 3: V2.0 完善(Week 11-18) + +**Week 11-13**:高级功能 +- [ ] 三模型仲裁 +- [ ] HITL 智能分流 +- [ ] 提示词版本管理 + +**Week 14-16**:质量审计 +- [ ] 自动审计系统 +- [ ] 质量报表 +- [ ] 异常检测 + +**Week 17-18**:发布准备 +- [ ] 全量测试 +- [ ] 医学专家验证 +- [ ] 文档和培训 + +--- + +## 📚 相关文档 + +- [CloseAI 集成指南](../../../02-通用能力层/01-LLM大模型网关/03-CloseAI集成指南.md) +- [AI 模型集成设计](./04-AI模型集成设计.md) +- [数据库设计](./01-数据库设计.md) +- [API 设计规范](./02-API设计规范.md) + +--- + +**更新日志**: +- 2025-11-15: 创建文档,定义 MVP/V1.0/V2.0 三阶段策略 + diff --git a/docs/03-业务模块/ASL-AI智能文献/02-技术设计/07-文献处理技术选型.md b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/07-文献处理技术选型.md new file mode 100644 index 00000000..c3f20407 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/02-技术设计/07-文献处理技术选型.md @@ -0,0 +1,1024 @@ +# ASL 文献处理技术选型 + +> **文档版本:** V1.0 +> **创建日期:** 2025-11-15 +> **适用模块:** AI 智能文献(ASL) +> **目标:** 定义初筛、全文复筛、全文提取的技术栈和实现路径 + +--- + +## 📋 文档概述 + +ASL 模块涉及三种不同的文献处理场景,每种场景有不同的技术特点和实现方案: + +| 场景 | 输入格式 | 核心技术 | 主要挑战 | +|------|---------|---------|---------| +| **标题摘要初筛** | Excel 文件 | Excel 解析 + LLM 筛选 | 批量处理效率 | +| **全文复筛** | PDF 全文 | PDF 提取 + LLM 筛选 | PDF 解析准确率 | +| **全文数据提取** | PDF 全文 | PDF 提取 + LLM 结构化提取 | 表格、公式准确提取 | + +--- + +## 🎯 技术架构总览 + +``` +┌─────────────────────────────────────────────────────────┐ +│ ASL 文献处理流程 │ +└─────────────────────────────────────────────────────────┘ + │ + ├─ 场景 1: 标题摘要初筛 + │ └─ 用户上传 Excel → 解析 → LLM 批量筛选 → 导出结果 + │ + ├─ 场景 2: 全文复筛 + │ └─ 用户上传 PDF → PDF 提取 → LLM 筛选 → 复核 + │ + └─ 场景 3: 全文数据提取 + └─ PDF → 提取 + 结构化 → LLM 提取数据 → 人工复核 + +┌─────────────────────────────────────────────────────────┐ +│ 技术栈分层架构(共享) │ +├─────────────────────────────────────────────────────────┤ +│ 前端层: React 19 + Ant Design 5 + xlsx/exceljs │ +├─────────────────────────────────────────────────────────┤ +│ 后端层: Node.js (Fastify) + TypeScript │ +├─────────────────────────────────────────────────────────┤ +│ 文档处理层: Python 微服务 (extraction_service) │ +│ ├─ PyMuPDF: 快速 PDF 提取 │ +│ ├─ Nougat: 英文科学文献高质量提取 ⭐ │ +│ └─ Language Detector: 自动语言检测 │ +├─────────────────────────────────────────────────────────┤ +│ LLM 层: DeepSeek-V3 + Qwen3 / GPT-5 + Claude-4.5 │ +├─────────────────────────────────────────────────────────┤ +│ 数据库: PostgreSQL 15 (asl_schema) │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## 📌 场景 1: 标题摘要初筛 + +### 1.1 技术特点 + +- **输入格式**: Excel 文件 (`.xlsx` / `.xls`) +- **数据规模**: 50-500 篇文献/批次 +- **主要字段**: 标题、摘要、DOI、作者、发表年份、期刊 +- **处理重点**: 批量高效处理,无需 PDF 解析 + +### 1.2 技术选型 + +#### 前端:Excel 上传与解析 + +| 技术 | 库 | 用途 | 优势 | +|------|-----|------|------| +| **Excel 上传** | `antd Upload` | 文件上传组件 | 拖拽上传、进度条 | +| **Excel 解析** | `xlsx` / `exceljs` | 前端解析 Excel | 纯前端处理,快速预览 | +| **模板验证** | 自定义逻辑 | 校验列名和数据格式 | 提前发现格式错误 | + +**推荐方案:`xlsx` 库(SheetJS)** +- ✅ 支持 `.xlsx` 和 `.xls` 格式 +- ✅ 纯 JavaScript,前端直接解析 +- ✅ 体积小(~600KB),性能好 +- ✅ 支持大文件(1000+ 行) + +**代码示例:** +```typescript +import * as XLSX from 'xlsx'; + +function parseExcel(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + + reader.onload = (e) => { + try { + const data = new Uint8Array(e.target.result as ArrayBuffer); + const workbook = XLSX.read(data, { type: 'array' }); + + // 读取第一个工作表 + const sheetName = workbook.SheetNames[0]; + const worksheet = workbook.Sheets[sheetName]; + + // 转换为 JSON + const jsonData = XLSX.utils.sheet_to_json(worksheet); + + // 映射为标准格式 + const literatures = jsonData.map((row: any) => ({ + title: row['Title'] || row['标题'], + abstract: row['Abstract'] || row['摘要'], + doi: row['DOI'], + authors: row['Authors'] || row['作者'], + year: row['Year'] || row['年份'], + journal: row['Journal'] || row['期刊'], + })); + + resolve(literatures); + } catch (error) { + reject(new Error('Excel 解析失败')); + } + }; + + reader.onerror = () => reject(new Error('文件读取失败')); + reader.readAsArrayBuffer(file); + }); +} +``` + +#### 后端:批量筛选处理 + +**处理流程:** +``` +Excel 数据 → 批量分组(10-20 篇/组)→ 并行调用 LLM → 汇总结果 +``` + +**关键技术点:** +1. **批量分组**:避免单次请求过大,10-20 篇/组最优 +2. **并行处理**:使用 `Promise.all` 并行调用 LLM +3. **进度推送**:WebSocket 实时推送处理进度 +4. **断点续传**:支持任务中断后继续 + +**代码示例:** +```typescript +async function batchScreening( + literatures: Literature[], + protocol: Protocol, + progressCallback: (progress: number) => void +) { + const batchSize = 15; + const batches = chunk(literatures, batchSize); + const results = []; + + for (let i = 0; i < batches.length; i++) { + const batch = batches[i]; + + // 并行处理当前批次 + const batchResults = await Promise.all( + batch.map(lit => dualModelScreening(lit, protocol)) + ); + + results.push(...batchResults); + + // 推送进度 + const progress = Math.round(((i + 1) / batches.length) * 100); + progressCallback(progress); + } + + return results; +} +``` + +### 1.3 数据流 + +``` +用户操作 前端处理 后端处理 LLM 处理 + │ │ │ │ + ├─ 上传 Excel │ │ │ + │ └──────────────→│ │ │ + │ ├─ 解析 Excel │ │ + │ ├─ 验证格式 │ │ + │ ├─ 显示预览 │ │ + │ │ │ │ + │ ├─ 提交筛选任务 │ │ + │ │ └───────────────→│ │ + │ │ ├─ 保存任务 │ + │ │ ├─ 分组(15 篇/组) │ + │ │ │ │ + │ │ ├─ 批次 1 │ + │ │ │ └──────────────→│ + │ │ │ ├─ DeepSeek 筛选 + │ │ │ ├─ Qwen3 筛选 + │ │ │ ├─ 对比结果 + │ │ │ ←──────────────┘ + │ │ ├─ 保存结果 │ + │ │ │ │ + │ │ ├─ 批次 2... │ + │ │ │ │ + │ │ ←───────────────┤ 返回完整结果 │ + │ ←──────────────┤ 显示结果 │ │ + └─ 人工复核 │ │ │ +``` + +--- + +## 📌 场景 2 & 3: 全文复筛与数据提取 + +### 2.1 技术特点 + +- **输入格式**: PDF 文件(英文医学文献) +- **文件特点**: + - 科学论文格式(标题、摘要、引言、方法、结果、讨论、参考文献) + - 包含复杂表格、公式、图表 + - 通常 10-30 页 +- **处理重点**: 高准确率提取,保留结构和格式 + +### 2.2 技术选型:PDF 提取 + +#### 核心方案:Nougat + PyMuPDF 顺序降级策略 ⭐ + +**现有架构**(已实现,位于 `extraction_service/`): + +```python +# 顺序降级策略 +def extract_pdf(file_path: str): + # Step 1: 检测语言 + language = detect_language(file_path) + + # Step 2: 中文 PDF → PyMuPDF(快速) + if language == 'chinese': + return extract_pdf_pymupdf(file_path) + + # Step 3: 英文 PDF → 尝试 Nougat + if check_nougat_available(): + result = extract_pdf_nougat(file_path) + + # 质量检查(阈值 0.7) + if result['quality_score'] >= 0.7: + return result # ✅ Nougat 成功 + + # Step 4: 降级到 PyMuPDF + return extract_pdf_pymupdf(file_path) +``` + +#### 技术对比 + +| 方案 | 优势 | 劣势 | 适用场景 | +|------|------|------|---------| +| **Nougat** ⭐ | • 专为科学文献设计
• 公式、表格准确率高
• 输出 Markdown 格式
• 保留文档结构 | • 速度慢(1-2 分钟/20 页)
• 需要 GPU 加速
• 内存占用大(~4GB) | 英文医学文献全文提取 | +| **PyMuPDF** | • 速度快(秒级)
• 内存占用低
• 部署简单 | • 公式、表格易丢失
• 纯文本输出
• 布局易混乱 | 中文文献、快速预览 | +| **Adobe API** | • 商业级准确率
• 云端处理 | • 需付费
• 网络依赖
• 隐私风险 | 不推荐(成本高) | +| **Tesseract OCR** | • 开源免费
• 支持多语言 | • 需要图像预处理
• 准确率不稳定 | 扫描版 PDF(备选) | + +**推荐方案:Nougat(主) + PyMuPDF(降级) ⭐** + +#### Nougat 核心优势(医学文献场景) + +``` +✅ 专为科学文献设计 + ├─ 训练数据:arXiv 论文 + 科学期刊 + ├─ 公式识别:LaTeX 格式输出 + ├─ 表格保留:Markdown 表格格式 + └─ 结构化输出:章节、段落清晰 + +✅ 输出格式:Markdown + ├─ 标题层级:# ## ### + ├─ 表格:| Header | Data | + ├─ 公式:$$ formula $$ + └─ 引用:[1] [2] [3] + +✅ 质量评估机制 + ├─ 自动质量评分(0-1) + ├─ 低质量自动降级 PyMuPDF + └─ 保证提取成功率 +``` + +#### 实现细节 + +**服务架构:** +``` +Node.js Backend (Port 3001) + │ + ├─ 调用 ExtractionClient.ts + │ └─ HTTP 请求 → Python 微服务 + │ +Python Extraction Service (Port 8000) + │ + ├─ /api/extract/pdf + │ ├─ detect_language() + │ ├─ extract_pdf_nougat() → Nougat Model + │ └─ extract_pdf_pymupdf() → PyMuPDF + │ + └─ /api/health + └─ 检查 Nougat 可用性 +``` + +**Node.js 调用代码:** +```typescript +import { extractionClient } from '@common/document/ExtractionClient'; + +async function extractLiteraturePDF(file: Buffer, filename: string) { + try { + // 方法 1: 自动选择(推荐) + const result = await extractionClient.extractPdf( + file, + filename, + 'auto' + ); + + // 方法 2: 强制使用 Nougat + // const result = await extractionClient.extractPdf(file, filename, 'nougat'); + + return { + text: result.text, + method: result.method, // "nougat" | "pymupdf" + quality: result.metadata.quality_score, + pageCount: result.metadata.page_count, + hasTables: result.metadata.has_tables, + hasFormulas: result.metadata.has_formulas + }; + } catch (error) { + console.error('PDF extraction failed:', error); + throw error; + } +} +``` + +**Python 提取代码:** +```python +# extraction_service/services/nougat_extractor.py + +def extract_pdf_nougat(file_path: str) -> Dict[str, Any]: + """ + 使用 Nougat 提取 PDF 文本 + + 命令行调用: + nougat -o --markdown --no-skipping + """ + cmd = [ + 'nougat', + file_path, + '-o', output_dir, + '--markdown', # 输出 Markdown 格式 + '--no-skipping' # 不跳过任何页面 + ] + + # 执行 Nougat(超时 5 分钟) + process = subprocess.Popen(cmd, ...) + stdout, stderr = process.communicate(timeout=300) + + # 读取输出文件(.mmd) + markdown_text = read_output_file() + + # 质量评估 + quality_score = evaluate_nougat_quality(markdown_text) + + return { + "success": True, + "method": "nougat", + "text": markdown_text, + "format": "markdown", + "metadata": { + "quality_score": quality_score, + "has_tables": detect_tables(markdown_text), + "has_formulas": detect_formulas(markdown_text) + } + } +``` + +### 2.3 文本后处理 + +**Nougat 输出优化:** +```typescript +function postProcessNougatOutput(markdown: string): ProcessedText { + return { + // 原始 Markdown + raw: markdown, + + // 章节分割 + sections: extractSections(markdown), // {abstract, methods, results, ...} + + // 表格提取 + tables: extractTables(markdown), + + // 公式提取 + formulas: extractFormulas(markdown), + + // 纯文本(去除格式) + plainText: markdownToPlainText(markdown), + + // 结构化数据(用于 LLM) + structured: { + title: extractTitle(markdown), + abstract: extractAbstract(markdown), + methodology: extractMethodology(markdown), + results: extractResults(markdown), + } + }; +} +``` + +--- + +## 📌 场景 4: 文献下载(Unpaywall API)⭐ + +### 3.1 技术背景 + +**Unpaywall** 是一个免费的开放获取(Open Access)文献 API,可以: +- ✅ 通过 DOI 查询文献是否有免费全文 +- ✅ 获取合法的 PDF 下载链接 +- ✅ 完全免费,无需付费 +- ✅ 数据库覆盖 3000+ 万篇文献 + +**官网**: https://unpaywall.org/products/api + +### 3.2 技术选型 + +#### API 调用方式 + +**基础信息:** +- **API 端点**: `https://api.unpaywall.org/v2/{doi}?email={your_email}` +- **请求方法**: GET +- **认证方式**: 无需 API Key,仅需提供邮箱 +- **速率限制**: 100,000 次/天(免费) + +**示例请求:** +```bash +curl "https://api.unpaywall.org/v2/10.1038/nature12373?email=YOUR_EMAIL" +``` + +**响应示例:** +```json +{ + "doi": "10.1038/nature12373", + "title": "The genome of the woodland strawberry", + "is_oa": true, + "oa_status": "gold", + "best_oa_location": { + "url": "https://www.nature.com/articles/nature12373.pdf", + "url_for_pdf": "https://www.nature.com/articles/nature12373.pdf", + "url_for_landing_page": "https://www.nature.com/articles/nature12373", + "license": "cc-by", + "version": "publishedVersion" + }, + "oa_locations": [...] +} +``` + +#### Node.js 实现 + +**服务封装:** +```typescript +// backend/src/common/literature/UnpaywallClient.ts + +import axios from 'axios'; +import { config } from '../../config/env'; + +export interface UnpaywallResult { + doi: string; + title: string; + isOA: boolean; // 是否开放获取 + oaStatus: string; // "gold" | "green" | "hybrid" | "bronze" | "closed" + pdfUrl: string | null; // PDF 下载链接 + landingPageUrl: string; // 文献页面链接 + license: string | null; // 许可协议 + version: string | null; // "publishedVersion" | "acceptedVersion" +} + +class UnpaywallClient { + private baseUrl = 'https://api.unpaywall.org/v2'; + private email: string; + + constructor(email: string = config.unpaywallEmail) { + this.email = email; + } + + /** + * 通过 DOI 查询文献信息 + */ + async getByDoi(doi: string): Promise { + try { + const url = `${this.baseUrl}/${doi}?email=${this.email}`; + const response = await axios.get(url, { + timeout: 10000, // 10 秒超时 + }); + + const data = response.data; + + // 获取最佳下载位置 + const bestOA = data.best_oa_location; + + return { + doi: data.doi, + title: data.title, + isOA: data.is_oa, + oaStatus: data.oa_status, + pdfUrl: bestOA?.url_for_pdf || null, + landingPageUrl: bestOA?.url_for_landing_page || data.doi_url, + license: bestOA?.license || null, + version: bestOA?.version || null, + }; + } catch (error) { + if (axios.isAxiosError(error)) { + if (error.response?.status === 404) { + throw new Error(`DOI not found: ${doi}`); + } + } + throw new Error(`Unpaywall API error: ${error.message}`); + } + } + + /** + * 批量查询(带速率限制) + */ + async getBatch(dois: string[]): Promise { + const results = []; + + for (const doi of dois) { + try { + const result = await this.getByDoi(doi); + results.push(result); + + // 速率限制:100ms/请求 + await new Promise(resolve => setTimeout(resolve, 100)); + } catch (error) { + console.error(`Failed to fetch ${doi}:`, error.message); + results.push(null); // 失败项标记为 null + } + } + + return results.filter(r => r !== null); + } + + /** + * 下载 PDF 文件 + */ + async downloadPdf(pdfUrl: string, outputPath: string): Promise { + try { + const response = await axios.get(pdfUrl, { + responseType: 'arraybuffer', + timeout: 60000, // 1 分钟超时 + }); + + const fs = require('fs'); + fs.writeFileSync(outputPath, response.data); + } catch (error) { + throw new Error(`PDF download failed: ${error.message}`); + } + } +} + +export const unpaywallClient = new UnpaywallClient(); +``` + +**环境变量配置:** +```env +# .env +UNPAYWALL_EMAIL=your-email@example.com +``` + +#### 业务集成 + +**场景 1:批量检查文献是否可下载** +```typescript +async function checkLiteratureAvailability(literatures: Literature[]) { + const dois = literatures + .map(lit => lit.doi) + .filter(doi => doi); // 过滤空 DOI + + const results = await unpaywallClient.getBatch(dois); + + return literatures.map(lit => ({ + ...lit, + downloadable: results.find(r => r.doi === lit.doi)?.isOA || false, + pdfUrl: results.find(r => r.doi === lit.doi)?.pdfUrl || null, + })); +} +``` + +**场景 2:用户点击下载全文** +```typescript +async function downloadLiteratureFullText(doi: string) { + // Step 1: 查询 Unpaywall + const unpaywallResult = await unpaywallClient.getByDoi(doi); + + if (!unpaywallResult.pdfUrl) { + throw new Error('该文献无免费全文'); + } + + // Step 2: 下载 PDF + const filename = `${doi.replace(/\//g, '_')}.pdf`; + const outputPath = `./downloads/${filename}`; + + await unpaywallClient.downloadPdf(unpaywallResult.pdfUrl, outputPath); + + // Step 3: 提取文本(调用 extraction_service) + const extractionResult = await extractionClient.extractPdf( + fs.readFileSync(outputPath), + filename, + 'auto' + ); + + return { + pdfPath: outputPath, + text: extractionResult.text, + method: extractionResult.method, + }; +} +``` + +### 3.3 前端集成 + +**批量下载按钮:** +```typescript +// 批量检查可下载性 +async function checkDownloadable(selectedRows: Literature[]) { + setLoading(true); + + const results = await api.checkLiteratureAvailability(selectedRows); + + const downloadableCount = results.filter(r => r.downloadable).length; + + message.success(`发现 ${downloadableCount} 篇可下载全文`); + setLiteratures(results); + setLoading(false); +} + +// 下载全文 +async function downloadFullText(literature: Literature) { + if (!literature.downloadable) { + message.warning('该文献无免费全文'); + return; + } + + try { + const result = await api.downloadLiteratureFullText(literature.doi); + message.success('下载成功'); + + // 打开 PDF 查看器 + openPdfViewer(result.pdfPath); + } catch (error) { + message.error(`下载失败: ${error.message}`); + } +} +``` + +--- + +## 🔍 补充技术点 + +### 4.1 您提到的技术点总结 + +| 技术点 | 状态 | 说明 | +|--------|------|------| +| ✅ Nougat 模型 | 已实现 | `extraction_service/services/nougat_extractor.py` | +| ✅ PyMuPDF | 已实现 | `extraction_service/services/pdf_extractor.py` | +| ✅ 顺序降级策略 | 已实现 | 英文→Nougat,中文→PyMuPDF | +| 🆕 Unpaywall API | 需新增 | 本文档提供实现方案 | +| ✅ Excel 解析 | 需新增 | 使用 `xlsx` 库(前端) | + +### 4.2 可能遗漏的技术点 ⭐ + +#### (1)表格提取增强 + +**问题**:Nougat 虽然保留表格结构,但 LLM 直接处理 Markdown 表格可能不准确。 + +**解决方案:Table Transformer** +```python +# 使用微软的 Table Transformer 模型 +# https://github.com/microsoft/table-transformer + +from transformers import TableTransformerForObjectDetection +import torch + +def extract_tables_enhanced(pdf_path: str): + """ + 使用 Table Transformer 精确定位表格 + """ + model = TableTransformerForObjectDetection.from_pretrained( + "microsoft/table-transformer-detection" + ) + + # 检测表格位置 + tables = model.detect_tables(pdf_path) + + # 提取每个表格 + for table in tables: + table_image = crop_table(pdf_path, table.bbox) + table_data = ocr_table(table_image) + + return structured_tables +``` + +**优先级:V2.0**(MVP 阶段 Nougat 足够) + +#### (2)引用解析与链接 + +**问题**:科学文献包含大量引用 `[1] [2] [3]`,需要解析并链接到参考文献。 + +**解决方案:GROBID** +```python +# GROBID: 开源科学文献解析工具 +# https://github.com/kermitt2/grobid + +import requests + +def parse_references(pdf_path: str): + """ + 使用 GROBID 解析参考文献 + """ + with open(pdf_path, 'rb') as f: + files = {'input': f} + response = requests.post( + 'http://localhost:8070/api/processFulltextDocument', + files=files + ) + + # 返回结构化的引用列表 + return response.json()['references'] +``` + +**优先级:V2.0**(非核心功能) + +#### (3)公式识别与渲染 + +**问题**:Nougat 输出 LaTeX 公式,前端需要渲染。 + +**解决方案:KaTeX / MathJax** +```typescript +// 前端渲染 LaTeX 公式 +import katex from 'katex'; +import 'katex/dist/katex.min.css'; + +function renderFormula(latex: string) { + return katex.renderToString(latex, { + throwOnError: false, + displayMode: true, + }); +} +``` + +**优先级:MVP**(提升用户体验) + +#### (4)PDF 预览与标注 + +**问题**:人工复核时需要查看原文,并高亮标注。 + +**解决方案:PDF.js + Annotator.js** +```typescript +// React 组件 +import { Viewer } from '@react-pdf-viewer/core'; +import '@react-pdf-viewer/core/lib/styles/index.css'; + +function PdfViewer({ pdfUrl, annotations }) { + return ( + + ); +} +``` + +**优先级:MVP**(核心功能) + +#### (5)文献去重 + +**问题**:Excel 上传可能包含重复文献(同一篇文献不同版本)。 + +**解决方案:基于 DOI 和标题的去重** +```typescript +function deduplicateLiteratures(literatures: Literature[]) { + const seen = new Set(); + + return literatures.filter(lit => { + // 优先使用 DOI + if (lit.doi) { + if (seen.has(lit.doi)) return false; + seen.add(lit.doi); + return true; + } + + // 否则使用标题(标准化后) + const normalizedTitle = normalizeTitle(lit.title); + if (seen.has(normalizedTitle)) return false; + seen.add(normalizedTitle); + return true; + }); +} + +function normalizeTitle(title: string): string { + return title + .toLowerCase() + .replace(/[^\w\s]/g, '') // 去除标点 + .replace(/\s+/g, ' ') // 规范化空格 + .trim(); +} +``` + +**优先级:MVP**(必须功能) + +#### (6)文献元数据补全 + +**问题**:Excel 上传的数据可能不完整(缺 DOI、年份等)。 + +**解决方案:Crossref API** +```typescript +// 通过标题查询 DOI +async function enrichMetadata(literature: Literature) { + if (literature.doi) return literature; // 已有 DOI + + // 调用 Crossref API + const response = await axios.get( + `https://api.crossref.org/works?query.title=${literature.title}` + ); + + const match = response.data.message.items[0]; + + return { + ...literature, + doi: match.DOI, + year: match['published-print']?.['date-parts'][0][0], + journal: match['container-title'][0], + }; +} +``` + +**优先级:V1.0**(增强功能) + +#### (7)批处理进度持久化 + +**问题**:批量筛选耗时长(1000 篇 > 10 分钟),需支持断点续传。 + +**解决方案:Redis + 任务队列** +```typescript +// 使用 Bull 队列 +import Queue from 'bull'; + +const screeningQueue = new Queue('literature-screening', { + redis: { host: 'localhost', port: 6379 } +}); + +// 添加任务 +screeningQueue.add({ + projectId: 'xxx', + literatures: [...], + protocol: {...} +}); + +// 处理任务 +screeningQueue.process(async (job) => { + const { projectId, literatures, protocol } = job.data; + + for (let i = 0; i < literatures.length; i++) { + // 处理单篇文献 + await screenLiterature(literatures[i], protocol); + + // 更新进度 + job.progress((i + 1) / literatures.length * 100); + } +}); +``` + +**优先级:V1.0**(体验优化) + +#### (8)错误处理与重试 + +**问题**:LLM 调用可能失败(网络、超时、限流)。 + +**解决方案:指数退避重试** +```typescript +async function retryWithBackoff( + fn: () => Promise, + maxRetries: number = 3 +): Promise { + for (let i = 0; i < maxRetries; i++) { + try { + return await fn(); + } catch (error) { + if (i === maxRetries - 1) throw error; + + // 指数退避:1s, 2s, 4s + const delay = Math.pow(2, i) * 1000; + await new Promise(resolve => setTimeout(resolve, delay)); + } + } +} +``` + +**优先级:MVP**(必须功能) + +--- + +## 📊 技术选型总结 + +### MVP 阶段必选技术 + +| 层级 | 技术 | 用途 | +|------|------|------| +| **前端** | `xlsx` | Excel 解析 | +| **前端** | `PDF.js` | PDF 预览 | +| **前端** | `KaTeX` | 公式渲染 | +| **后端** | `ExtractionClient` | 调用 Python 微服务 | +| **后端** | `UnpaywallClient` | 文献下载 | +| **Python** | `Nougat` | 英文 PDF 提取 | +| **Python** | `PyMuPDF` | 快速 PDF 提取 | +| **数据库** | `asl_schema` | 数据存储 | + +### V1.0 增强技术 + +| 技术 | 用途 | +|------|------| +| Crossref API | 元数据补全 | +| Bull Queue | 任务队列 | +| Redis | 进度持久化 | + +### V2.0 高级技术 + +| 技术 | 用途 | +|------|------| +| Table Transformer | 表格精确提取 | +| GROBID | 引用解析 | +| Semantic Scholar API | 学术图谱 | + +--- + +## 📁 测试数据存放建议 + +根据 ASL 模块的文件夹结构,测试数据应该放在: + +``` +AIclinicalresearch/docs/03-业务模块/ASL-AI智能文献/ +└── 05-测试文档/ + ├── 01-测试计划.md + ├── 02-标题摘要初筛测试用例.md + └── 03-测试数据/ ← 新建文件夹 + ├── README.md ← 说明文档 + ├── screening-test-data/ + │ ├── literature-list-199.xlsx ← 199 篇文献列表 + │ ├── picos-criteria.txt ← PICOS 标准 + │ └── expected-results.json ← 预期结果(金标准) + ├── pdf-samples/ + │ ├── sample-rct-01.pdf + │ ├── sample-cohort-01.pdf + │ └── README.md + └── extraction-test-data/ + └── README.md +``` + +**推荐结构:** +``` +05-测试文档/ +├── 01-测试计划.md +├── 02-标题摘要初筛测试用例.md +└── 03-测试数据/ + ├── README.md ← 重要!说明测试数据来源、版权、使用方法 + ├── screening/ + │ ├── literature-list-199.xlsx + │ ├── picos-criteria.txt + │ ├── inclusion-criteria.txt + │ ├── exclusion-criteria.txt + │ └── gold-standard.json ← 人工标注的正确答案 + └── pdf-extraction/ + ├── sample-01-high-quality.pdf + ├── sample-02-with-tables.pdf + └── sample-03-chinese.pdf +``` + +**README.md 示例:** +```markdown +# ASL 测试数据集 + +## 📋 数据说明 + +### 1. 标题摘要初筛测试数据 +- **文件**: `literature-list-199.xlsx` +- **数量**: 199 篇英文医学文献 +- **字段**: 标题、摘要、DOI、作者、年份、期刊 +- **来源**: [描述数据来源] +- **版权**: [说明版权信息] + +### 2. PICOS 标准 +- **文件**: `picos-criteria.txt` +- **内容**: Population, Intervention, Comparison, Outcome, Study Design +- **纳入标准**: 5 条 +- **排除标准**: 8 条 + +### 3. 金标准(人工标注结果) +- **文件**: `gold-standard.json` +- **标注人**: [标注专家信息] +- **标注时间**: [时间] +- **预期准确率**: ≥ 90% + +## 🎯 使用方法 + +### 运行测试 +```bash +npm run test:asl:screening +``` + +### 评估准确率 +```bash +npm run test:asl:evaluate -- --gold-standard gold-standard.json +``` + +## 📊 预期结果 +- 纳入: 45 篇 +- 排除: 132 篇 +- 不确定: 22 篇 +``` + +--- + +## 📚 相关文档 + +- [质量保障与可追溯策略](./06-质量保障与可追溯策略.md) +- [数据库设计](./01-数据库设计.md) +- [API 设计规范](./02-API设计规范.md) +- [文档提取微服务](../../../../extraction_service/README.md) + +--- + +**更新日志**: +- 2025-11-15: 创建文档,定义初筛、全文处理、文献下载技术选型 + diff --git a/docs/03-业务模块/ASL-AI智能文献/03-UI设计/01-标题摘要初筛UI设计.md b/docs/03-业务模块/ASL-AI智能文献/03-UI设计/01-标题摘要初筛UI设计.md new file mode 100644 index 00000000..653857a6 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/03-UI设计/01-标题摘要初筛UI设计.md @@ -0,0 +1,87 @@ +# 标题摘要初筛UI设计 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## 📋 文档说明 + +本文档描述标题摘要初筛模块的UI设计,包括页面布局、交互设计、视觉规范等。 + +--- + +## 🎨 页面视图 + +### 1. 设置与启动视图 + +**布局结构**: +- 顶部: 标准参考面板(可折叠) +- 中间: 临时调整入口(可选) +- 底部: 文献导入区域 + 启动按钮 + +**设计要点**: +- 清晰展示从研究方案继承的PICO和入排标准 +- 提供简洁的文献导入入口(Excel上传) +- 导入后激活启动按钮 + +### 2. 表格化审核工作台视图 ⭐核心 + +**表格结构**: +- **表头**: 两行结构 + - 第一行: 合并单元格标示模型区域(DS模型 | Q3模型 | 决策) + - 第二行: 各模型下细分P/I/C/S/结论列 + +- **主行**: + - 展开/收起按钮 + - 文献ID、研究ID、文献来源 + - DS-P/I/C/S/结论判断(✓/✗/?) + - Q3-P/I/C/S/结论判断 + - 冲突状态指示 + - 最终决策下拉框 + +- **展开行**: + - DS证据列: P/I/C/S对应的关键短语 + - Q3证据列: P/I/C/S对应的关键短语 + +**交互设计**: +- 点击判断图标(✓/✗/?) → 弹出双视图原文审查模态框 +- 冲突项行背景高亮显示 +- 支持批量选择和非冲突项批量决策 + +### 3. 结果展示视图 + +**布局结构**: +- 顶部: 统计卡片(总计、纳入、排除) +- 中间: PRISMA式排除总结 +- 底部: 结果列表(Tab切换:纳入/排除)+ 导出按钮 + +--- + +## 🖼️ 原型参考 + +详细原型请参考: `../../01-设计文档/AI智能文献-标题摘要初筛原型.html` + +--- + +## ⏳ 待完善内容 + +后续将补充: +- 详细的页面布局图 +- 交互流程图 +- 视觉设计规范 +- 组件样式规范 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/03-UI设计/02-全文复筛UI设计.md b/docs/03-业务模块/ASL-AI智能文献/03-UI设计/02-全文复筛UI设计.md new file mode 100644 index 00000000..53eab1ed --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/03-UI设计/02-全文复筛UI设计.md @@ -0,0 +1,24 @@ +# 全文复筛UI设计 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/03-UI设计/03-设计规范.md b/docs/03-业务模块/ASL-AI智能文献/03-UI设计/03-设计规范.md new file mode 100644 index 00000000..40f15adc --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/03-UI设计/03-设计规范.md @@ -0,0 +1,24 @@ +# UI设计规范 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/03-UI设计/AI智能文献-全文复筛.html b/docs/03-业务模块/ASL-AI智能文献/03-UI设计/AI智能文献-全文复筛.html new file mode 100644 index 00000000..b2701431 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/03-UI设计/AI智能文献-全文复筛.html @@ -0,0 +1,524 @@ + + + + + + 全文复筛原型 V2 + + + + + + + +
+ + + + +
+
+

全文复筛 / 设置与启动

+
+ +
+ +
+
+
+ + +
+

全文复筛 - 设置与启动

+
+

筛选标准 (来自研究方案)

+

PICO 标准

P: 2型糖尿病成人患者

I: SGLT2抑制剂

C: 安慰剂或其他常规降糖疗法

S: 随机对照试验 (RCT)

纳入/排除标准

  • 纳入:英文
  • 排除:病例报告, 综述
+
+ + + +
+
+

全文获取与管理 (0篇)

+ +
+
+ + + +
文献ID文献标题获取状态操作
+
+
+ + + +
+
+
+ + +
+
▼ 查看当前筛选标准

PICO 标准

P: 2型糖尿病成人患者

I: SGLT2抑制剂

C: 安慰剂或其他常规降糖疗法

S: 随机对照试验 (RCT)

纳入/排除标准

  • 纳入:英文
  • 排除:病例报告, 综述
+
+ + + + + + + + + + + + + + + + + + +
文献ID研究ID文献来源DS 判断Q3 判断冲突状态最终决策
PICS结论PICS结论
+
+
+ +
+
+ + +
+

全文复筛 - 结果

+
+
100
总计复筛
+
55
最终纳入
+
45
排除
+
+
+

排除原因统计 (模拟)

+
    +
  • 排除文献总数: n=45
  • +
  • 非随机对照研究: n=5
  • +
  • 非目标人群 (P): n=7
  • +
  • 干预/对照不符 (I/C): n=18
  • +
  • 结局指标不符 (O): n=9
  • +
  • 其他原因 (如数据不全): n=6
  • +
+
+
+
+ +
+
+ + +
+
+ + + + + + + + +
+
+
+
+
+
+
+ + + + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/03-UI设计/AI智能文献-标题摘要初筛原型.html b/docs/03-业务模块/ASL-AI智能文献/03-UI设计/AI智能文献-标题摘要初筛原型.html new file mode 100644 index 00000000..668cdf51 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/03-UI设计/AI智能文献-标题摘要初筛原型.html @@ -0,0 +1,450 @@ + + + + + + 标题摘要初筛原型 V4 + + + + + + + +
+ + + + +
+
+

标题摘要初筛 / 设置与启动

+
+ +
+ +
+ +

标题摘要初筛 - 设置与启动

筛选标准 (来自研究方案)

PICO 标准

P: 2型糖尿病成人患者

I: SGLT2抑制剂

C: 安慰剂或其他常规降糖疗法

S: 随机对照试验 (RCT)

纳入/排除标准

  • 纳入:英文
  • 排除:病例报告, 综述

导入文献

上传 Excel 文件

请按模板上传包含文献标题和摘要的列表。

下载模板

请先导入文献

+
+ + +
+ +
▼ 查看当前筛选标准

PICO 标准

P: 2型糖尿病成人患者

I: SGLT2抑制剂

C: 安慰剂或其他常规降糖疗法

S: 随机对照试验 (RCT)

纳入/排除标准

  • 纳入:英文
  • 排除:病例报告, 综述
文献ID研究ID文献来源DS 判断Q3 判断冲突状态最终决策
PICS结论PICS结论
+
+ + +
+

标题摘要初筛 - 结果

+
+
1000
总计筛选
+
120
初步纳入
+
880
排除
+
+ +
+

排除原因统计 (模拟)

+
    +
  • 排除文献总数: n=880
  • +
  • 非随机对照研究: n=250
  • +
  • 非目标人群 (P): n=180
  • +
  • 干预措施不符 (I): n=100
  • +
  • 对照措施不符 (C): n=90
  • +
  • 重复研究: n=150
  • +
  • 其他 (综述、病例报告等): n=110
  • +
+
+ +
+
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + +
文献ID研究ID文献来源标题摘要PICS结论排除理由
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/04-开发计划/01-开发里程碑.md b/docs/03-业务模块/ASL-AI智能文献/04-开发计划/01-开发里程碑.md new file mode 100644 index 00000000..da9f3974 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/04-开发计划/01-开发里程碑.md @@ -0,0 +1,72 @@ +# 开发里程碑 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 +> **最后更新:** 2025-10-29 + +--- + +## 📋 文档说明 + +本文档描述AI智能文献模块的开发里程碑规划。 + +--- + +## 🗓️ 开发阶段 + +### 阶段一:标题摘要初筛模块(当前阶段) + +**目标**: 完成标题摘要初筛核心功能 + +**时间**: 4-6周 + +**里程碑**: +- ✅ 需求分析和设计文档 +- 🔄 数据库设计和API设计 +- ⏳ 前端框架搭建 +- ⏳ 后端API开发 +- ⏳ AI模型集成 +- ⏳ 功能测试和优化 + +### 阶段二:全文复筛模块 + +**目标**: 完成全文复筛功能 + +**时间**: 4-6周 + +**状态**: 待开始 + +### 阶段三:其他模块 + +**目标**: 完成剩余功能模块 + +**时间**: 待定 + +**状态**: 规划中 + +--- + +## 📊 进度跟踪 + +| 模块 | 状态 | 进度 | 完成时间 | +|------|------|------|----------| +| 研究方案生成 | 规划中 | 0% | - | +| 智能文献检索 | 规划中 | 0% | - | +| 标题摘要初筛 | 进行中 | 20% | - | +| 全文复筛 | 待开始 | 0% | - | +| 全文解析与数据提取 | 规划中 | 0% | - | +| 数据综合分析与报告 | 规划中 | 0% | - | + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/04-开发计划/02-标题摘要初筛开发计划.md b/docs/03-业务模块/ASL-AI智能文献/04-开发计划/02-标题摘要初筛开发计划.md new file mode 100644 index 00000000..ae87b7f2 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/04-开发计划/02-标题摘要初筛开发计划.md @@ -0,0 +1,99 @@ +# 标题摘要初筛模块开发计划 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 +> **最后更新:** 2025-10-29 + +--- + +## 📋 文档说明 + +本文档详细描述标题摘要初筛模块的开发计划,包括任务分解、时间安排、技术方案等。 + +--- + +## 🎯 开发目标 + +完成标题摘要初筛模块的核心功能,包括: +1. 设置与启动视图 +2. 表格化审核工作台 +3. 结果展示视图 +4. AI双模型筛选功能 + +--- + +## 📅 开发时间规划 + +### Week 1-2: 设计阶段 +- [x] 需求分析完成 +- [ ] 数据库设计完成 +- [ ] API设计完成 +- [ ] 前端组件设计完成 + +### Week 3-4: 前端开发 +- [ ] 设置与启动视图开发 +- [ ] 表格化审核工作台开发 +- [ ] 结果展示视图开发 +- [ ] 组件集成 + +### Week 5-6: 后端开发 +- [ ] 项目管理API +- [ ] 文献管理API +- [ ] 筛选任务API +- [ ] AI模型集成 + +### Week 7-8: 集成测试与优化 +- [ ] 功能测试 +- [ ] 性能优化 +- [ ] Bug修复 +- [ ] 用户验收测试 + +--- + +## 📋 任务分解 + +### 1. 数据库设计和迁移 +- [ ] 设计数据表结构 +- [ ] 编写迁移脚本 +- [ ] 创建索引 + +### 2. API开发 +- [ ] 项目管理API +- [ ] 文献导入API +- [ ] 筛选任务API +- [ ] 筛选结果API + +### 3. 前端开发 +- [ ] 设置与启动视图 +- [ ] 表格化审核工作台 +- [ ] 结果展示视图 +- [ ] 双视图原文审查模态框 + +### 4. AI集成 +- [ ] 双模型调用封装 +- [ ] 批处理任务管理 +- [ ] 结果解析和存储 + +--- + +## ⏳ 待完善内容 + +后续将补充: +- 详细任务分解(按功能点) +- 技术方案细节 +- 风险评估 +- 依赖关系 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/04-开发计划/03-任务分解.md b/docs/03-业务模块/ASL-AI智能文献/04-开发计划/03-任务分解.md new file mode 100644 index 00000000..48858ea5 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/04-开发计划/03-任务分解.md @@ -0,0 +1,24 @@ +# 任务分解 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/05-测试文档/01-测试计划.md b/docs/03-业务模块/ASL-AI智能文献/05-测试文档/01-测试计划.md new file mode 100644 index 00000000..c8c073f7 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/05-测试文档/01-测试计划.md @@ -0,0 +1,24 @@ +# 测试计划 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/05-测试文档/02-标题摘要初筛测试用例.md b/docs/03-业务模块/ASL-AI智能文献/05-测试文档/02-标题摘要初筛测试用例.md new file mode 100644 index 00000000..bb288784 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/05-测试文档/02-标题摘要初筛测试用例.md @@ -0,0 +1,24 @@ +# 标题摘要初筛测试用例 + +> **文档版本:** v1.0 +> **创建日期:** 2025-10-29 +> **维护者:** AI智能文献开发团队 + +--- + +## ⏳ 待完善 + +本文档内容待规划完善,目前仅作为占位文档。 + +--- + +**文档版本:** v1.0 +**最后更新:** 2025-10-29 + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/README.md b/docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/README.md new file mode 100644 index 00000000..6fd36705 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/README.md @@ -0,0 +1,272 @@ +# ASL 测试数据集 + +> **创建日期:** 2025-11-15 +> **维护人:** ASL 开发团队 +> **用途:** 用于测试 AI 智能文献模块的准确率和质量 + +--- + +## 📋 数据集概览 + +本目录包含用于测试 ASL 模块各项功能的测试数据集,包括: + +| 测试类型 | 文件夹 | 数据量 | 状态 | +|---------|--------|--------|------| +| **标题摘要初筛** | `screening/` | 199 篇 | ✅ 待导入 | +| **PDF 全文提取** | `pdf-extraction/` | 待补充 | ⏳ 待补充 | + +--- + +## 📁 文件夹结构 + +``` +03-测试数据/ +├── README.md ← 当前文件 +│ +├── screening/ ← 标题摘要初筛测试数据 +│ ├── literature-list-199.xlsx ← 199 篇文献列表(标题+摘要) +│ ├── picos-criteria.txt ← PICOS 标准定义 +│ ├── inclusion-criteria.txt ← 纳入标准 +│ ├── exclusion-criteria.txt ← 排除标准 +│ └── gold-standard.json ← 人工标注的正确结果(金标准) +│ +└── pdf-extraction/ ← PDF 全文提取测试数据 + ├── sample-01-rct.pdf ← RCT 研究样本 + ├── sample-02-cohort.pdf ← 队列研究样本 + ├── sample-03-with-tables.pdf ← 包含复杂表格的样本 + ├── sample-04-chinese.pdf ← 中文文献样本 + └── README.md +``` + +--- + +## 🎯 使用方法 + +### 1. 导入测试数据 + +**请按以下步骤导入您的测试数据:** + +#### (1)标题摘要初筛测试数据 + +**文件清单:** +- `literature-list-199.xlsx`:199 篇英文文献列表 +- `picos-criteria.txt`:PICOS 标准(Population, Intervention, Comparison, Outcome, Study Design) +- `gold-standard.json`:人工标注的正确结果 + +**Excel 文件格式要求:** +``` +列名(必须): +- Title(标题) +- Abstract(摘要) +- DOI(可选) +- Authors(作者,可选) +- Year(年份,可选) +- Journal(期刊,可选) + +示例: +| Title | Abstract | DOI | Authors | Year | Journal | +|--------------------------------|---------------------------|---------------|--------------|------|---------| +| Effect of aspirin on ... | Background: ... | 10.1038/... | Smith J, ... | 2020 | NEJM | +``` + +**PICOS 标准格式:** +```txt +# PICOS 标准 + +## Population(人群) +- 成年高血压患者(年龄 ≥ 18 岁) +- 无心血管疾病史 + +## Intervention(干预) +- 每日服用阿司匹林 100mg + +## Comparison(对照) +- 安慰剂或无治疗 + +## Outcome(结局) +- 主要结局:心血管事件发生率 +- 次要结局:全因死亡率 + +## Study Design(研究设计) +- 随机对照试验(RCT) +- 队列研究(Cohort Study) +``` + +**金标准格式(JSON):** +```json +{ + "metadata": { + "total": 199, + "annotatedBy": "医学专家姓名", + "annotatedDate": "2025-11-15", + "expectedAccuracy": 0.90 + }, + "results": [ + { + "id": 1, + "doi": "10.1038/nature12373", + "title": "...", + "decision": "include", + "reason": "符合 PICO 标准:人群为成年高血压患者,干预为阿司匹林...", + "confidence": 1.0 + }, + { + "id": 2, + "decision": "exclude", + "reason": "不符合纳入标准:人群为儿童患者", + "confidence": 0.95 + } + ] +} +``` + +#### (2)PDF 全文提取测试数据 + +**建议准备的样本类型:** +- RCT 研究(随机对照试验) +- 队列研究(Cohort Study) +- 包含复杂表格的文献 +- 包含数学公式的文献 +- 中文医学文献(测试语言检测) + +**样本数量建议:** 5-10 篇 + +### 2. 运行测试 + +#### (1)标题摘要初筛测试 + +```bash +# 进入后端目录 +cd AIclinicalresearch/backend + +# 运行初筛测试 +npm run test:asl:screening + +# 或者手动测试: +# 1. 启动后端服务 +npm run dev + +# 2. 通过前端上传 literature-list-199.xlsx +# 3. 配置 PICOS 标准(复制 picos-criteria.txt 内容) +# 4. 运行批量筛选 +# 5. 导出结果,与 gold-standard.json 对比 +``` + +#### (2)评估准确率 + +```bash +# 自动评估准确率(与金标准对比) +npm run test:asl:evaluate -- \ + --result ./screening-result.json \ + --gold-standard ./gold-standard.json + +# 输出示例: +# ✅ 准确率: 92.5% +# ✅ 一致率: 88.9% +# ⚠️ 假阳性率: 5.2% +# ⚠️ 假阴性率: 2.3% +``` + +### 3. 质量指标 + +| 指标 | MVP 目标 | V1.0 目标 | V2.0 目标 | +|------|---------|----------|----------| +| **准确率** | ≥ 85% | ≥ 90% | ≥ 95% | +| **一致率**(双模型) | ≥ 80% | ≥ 85% | ≥ 90% | +| **假阳性率** | ≤ 10% | ≤ 5% | ≤ 3% | +| **假阴性率** | ≤ 5% | ≤ 3% | ≤ 2% | + +--- + +## 📊 测试数据统计 + +### 标题摘要初筛数据集 + +**基本信息:** +- **总数量**: 199 篇 +- **数据来源**: [请填写数据来源] +- **领域**: 医学/临床研究 +- **语言**: 英文 +- **年份范围**: [请填写] + +**预期分布:** +``` +纳入(Include): ~45 篇(23%) +排除(Exclude): ~132 篇(66%) +不确定(Uncertain): ~22 篇(11%) +``` + +**研究类型分布(预估):** +``` +RCT: ~60 篇(30%) +队列研究: ~50 篇(25%) +病例对照: ~30 篇(15%) +横断面研究: ~30 篇(15%) +其他: ~29 篇(15%) +``` + +### PDF 全文提取数据集 + +**待补充** + +--- + +## ⚠️ 数据使用注意事项 + +### 1. 版权声明 + +- 本测试数据集仅用于 ASL 模块开发和测试 +- 不得用于商业用途 +- 不得公开分发或传播 +- 请遵守原文献的版权许可 + +### 2. 数据隐私 + +- 确保测试数据不包含敏感信息 +- 如包含患者数据,必须已脱敏处理 +- 遵守 GDPR、HIPAA 等数据保护法规 + +### 3. 质量要求 + +- **金标准必须由医学专家标注** +- 标注人需具备相关领域专业知识 +- 标注过程需有质量控制机制 +- 建议双人独立标注,冲突需第三方仲裁 + +--- + +## 🔄 数据更新记录 + +| 日期 | 更新内容 | 更新人 | +|------|---------|--------| +| 2025-11-15 | 创建测试数据目录结构 | ASL 团队 | +| 待更新 | 导入 199 篇文献测试数据 | - | +| 待更新 | 添加 PDF 样本数据 | - | + +--- + +## 📞 联系方式 + +如有问题,请联系: +- **项目负责人**: [姓名] +- **邮箱**: [邮箱] +- **文档维护**: [文档路径] + +--- + +## 📚 相关文档 + +- [标题摘要初筛测试用例](../02-标题摘要初筛测试用例.md) +- [测试计划](../01-测试计划.md) +- [文献处理技术选型](../../02-技术设计/07-文献处理技术选型.md) +- [质量保障与可追溯策略](../../02-技术设计/06-质量保障与可追溯策略.md) + +--- + +**下一步行动:** +1. ✅ 创建测试数据目录结构 +2. ⏳ 导入您的 199 篇文献测试数据(`literature-list-199.xlsx`) +3. ⏳ 创建 PICOS 标准文件(`picos-criteria.txt`) +4. ⏳ 准备金标准标注(`gold-standard.json`) +5. ⏳ 补充 PDF 样本数据 + diff --git a/docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/pdf-extraction/.gitkeep b/docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/pdf-extraction/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/screening/.gitkeep b/docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/screening/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/screening/Test Cases.xlsx b/docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/screening/Test Cases.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..ca1eb0428ebfc8a5181d48ba16eafc4c21489342 GIT binary patch literal 201771 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 z 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 E0QTi>6#xJL literal 0 HcmV?d00001 diff --git a/docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/screening/测试案例的PICOS、纳入标准、排除标准.txt b/docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/screening/测试案例的PICOS、纳入标准、排除标准.txt new file mode 100644 index 00000000..eef65d1d --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/screening/测试案例的PICOS、纳入标准、排除标准.txt @@ -0,0 +1,77 @@ +测试案例的PICOS、纳入标准、排除标准 + +一、PICOS: + +Patients with non-cardioembolic ischemic stroke (NCIS) 非心源性缺血性卒中、亚洲人群 +亚组人群: +均在非心源性卒中范畴内找: +1. NIHSS评分亚组卒中人群(mild/moderate stroke); +2. 不同TOAST分型(different TOAST subtypes,excluding cardioembolic stroke); +3. 高危TIA人群(high-risk TIA population); +4. 新发卒中/复发性/进展性卒中患者(new-onset stroke/recurrent /progressive stroke patients) +5 颅内动脉粥样硬化性狭窄/大动脉粥样硬化患者(ICAS/LAA) +6. 不同疾病特征人群: +(1)合并冠心病(CAD)患者/外周动脉疾病PAD +(2)合并中重度肾功能不全透析患者(patients with moderate-severe renal insufficiency on dialysis); +(3)合并糖尿病患者(patients with diabetes mellitus); +(4)老年或脆弱患者(elderly/fragile patients); +(5)氯吡格雷抵抗人群(clopidogrel-resistant population); +(6)Breakthrough stroke +(7)特殊情况如卵圆孔未闭(PFO),颈动脉夹层(cervical artery dissection,)合并肿瘤(cancer) + +Intervention/Comparator: +抗血小板治疗药物:阿司匹林,氯吡格雷,奥扎格雷,贝前列素,西洛他唑,替罗非班,替格瑞洛,吲哚布芬,沙格雷酯,氯吡格雷阿司匹林,双嘧达莫等。 +抗凝药物:阿加曲班,asundexian,milvexian,华法林、低分子肝素、肝素等。 +溶栓药物:链激酶、尿激酶、阿替普酶、替奈普酶等。 + +Outcome +疗效安全性:Progressive stroke卒中的进展or神经功能恶化,Recurrent ischemic stroke 卒中的复发,Disability,Death,NIHSS评分变化,VTE,efficacy/effective/effectiveness/疗效/有效性,痴呆、认知功能减退、疲乏、抑郁;safety/安全性。 + +Time +检索时间:近五年(2020年之后)至今文献 + +Study design +系统评价(SR)、随机对照试验(RCT)、真实世界研究(RWE)、观察性研究(OBS) + + +二、 纳入标准: +非心源性缺血性卒中、亚洲患者 +Patients post-Ischemic Stroke (IS) a that are on Secondary Stroke Prevention (SSP) treatment +Patients post-IS a that are not on SSP treatment (if captured within a study where the focus is on patients that +are on SSP treatment) + +干预和对照 +抗血小板治疗药物:阿司匹林,氯吡格雷,奥扎格雷,贝前列素,西洛他唑,替罗非班, +替格瑞洛,吲哚布芬,沙格雷酯,氯吡格雷阿司匹林,双嘧达莫等。 +抗凝药物:阿加曲班,asundexian,milvexian,华法林、低分子肝素、肝素等。 +溶栓药物:链激酶、尿激酶、阿替普酶、替奈普酶等。 + +结局: +疗效安全性:Progressive stroke卒中的进展or神经功能恶化,Recurrent ischemic stroke 卒中的复发, +Disability,Death,NIHSS评分变化,VTE,efficacy/effective/effectiveness/疗效/有效性,痴呆、 +认知功能减退、疲乏、抑郁;safety/安全性。 + +研究类型 +系统评价(SR)、随机对照试验(RCT)、真实世界研究(RWE)、观察性研究(OBS) + +研究时间 +近五年(2020年之后)的文献 + + - 包含“secondary prevention” + - 包含“prevention of recurrence” + - 包含“for stroke prevention” + - 包含涉及抗血小板或抗凝药物 + - 涉及抗血小板或抗凝药物 +如果出现了抗血小板或抗凝药物,并且没有出现任何排除关键词(急性期相关术语),则纳入。 + + +三、 排除标准: +1. 心源性卒中患者、非亚洲 +2. Patients post any other stroke +3. Patients on antiplatelet therapy for Acute Coronary Syndrome (ACS) without previously identified stroke +4. Patients who have Atrial Fibrillation (AF) +5. Mixed populations (when the population includes patients that are not post IS) +6. 病例报告等 +7. 非中英文文献 + 8. 包含急性期治疗关键词(如acute, thrombolysis, thrombectomy等),没有出现抗血小板或抗凝药物,也没有出现二级预防关键词。 + diff --git a/docs/03-业务模块/ASL-AI智能文献/README.md b/docs/03-业务模块/ASL-AI智能文献/README.md new file mode 100644 index 00000000..00db22e9 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/README.md @@ -0,0 +1,82 @@ +# ASL - AI智能文献 + +> **模块代号:** ASL (AI Smart Literature) +> **开发状态:** ⏳ 下一步开发(Week 2-4) +> **商业价值:** ⭐⭐⭐⭐⭐ 可独立售卖 +> **独立性:** ⭐⭐⭐⭐⭐ +> **优先级:** P0 + +--- + +## 📋 模块概述 + +AI智能文献筛选系统,帮助研究者快速筛选和分析文献。 + +**核心价值:** 核心差异化功能,可独立售卖 + +--- + +## 🎯 核心功能(6个模块) + +1. ✅ **标题摘要初筛** - 双模型AI判断 +2. ✅ **全文复筛** - PDF全文分析 +3. ⏳ **全文解析与数据提取** +4. ⏳ **数据分析与报告生成** +5. ⏳ **系统评价与Meta分析** +6. ⏳ **文献管理** + +**本周重点:** 标题摘要初筛 + 全文复筛 + +--- + +## 📂 文档结构 + +``` +ASL-AI智能文献/ + ├── [AI对接] ASL快速上下文.md # ⏳ 待创建 + ├── 00-项目概述/ + │ ├── 01-产品需求文档(PRD).md # ⏳ 待合并(3个PRD) + │ └── ... + ├── 01-设计文档/ + │ ├── 02-数据库设计.md + │ ├── 03-API设计.md + │ └── 07-UI设计/ + │ ├── 标题摘要初筛原型.html + │ └── 全文复筛原型.html + └── README.md # ✅ 当前文档 +``` + +--- + +## 🔗 依赖的通用能力 + +- **LLM网关** - 双模型AI判断 +- **文档处理引擎** - PDF全文提取 +- **RAG引擎** - 文献内容检索 + +--- + +## 🎯 商业模式 + +**目标客户:** 系统评价研究者、循证医学中心 +**售卖方式:** 独立产品 +**定价策略:** 按项目数或按月订阅 + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/03-业务模块/ASL-AI智能文献/[AI对接] ASL快速上下文.md b/docs/03-业务模块/ASL-AI智能文献/[AI对接] ASL快速上下文.md new file mode 100644 index 00000000..970e08b1 --- /dev/null +++ b/docs/03-业务模块/ASL-AI智能文献/[AI对接] ASL快速上下文.md @@ -0,0 +1,321 @@ +# [AI对接] ASL快速上下文 + +> **阅读时间:** 3-5分钟 | **Token消耗:** ~2000 tokens +> **层级:** L2 | **前置阅读:** 00-系统总体设计/[AI对接] 快速上下文.md + +--- + +## 📋 模块定位 + +**AI智能文献筛选系统**,帮助研究者快速筛选和分析大量文献,提高系统评价效率。 + +**商业价值:** ⭐⭐⭐⭐⭐ 可独立售卖 +**开发状态:** ⏳ 即将开发(Week 2-4) +**依赖能力:** LLM网关(P0)、文档处理引擎、RAG引擎 + +--- + +## 🎯 核心功能(6个模块) + +1. ✅ **标题摘要初筛** - 双模型AI判断 → Week 2-3重点 +2. ✅ **全文复筛** - PDF全文分析 → Week 3-4重点 +3. ⏳ 全文解析与数据提取 +4. ⏳ 数据分析与报告生成 +5. ⏳ 系统评价与Meta分析 +6. ⏳ 文献管理 + +**本次开发重点:** 标题摘要初筛 + 全文复筛 + +--- + +## 🏗️ 技术架构一览 + +### 前端(React) +``` +src/pages/Literature/ + ├── ProjectManagement/ # 文献项目管理 + ├── TitleScreening/ # 标题摘要初筛 ⭐ + ├── FullTextScreening/ # 全文复筛 ⭐ + ├── DataExtraction/ # 数据提取 + └── Management/ # 文献管理 +``` + +### 后端(Node.js) +``` +backend/src/modules/asl/ + ├── controllers/ + │ ├── projectController.ts # 项目管理 + │ ├── screeningController.ts # 筛选控制 ⭐ + │ └── extractionController.ts # 数据提取 + ├── services/ + │ ├── screeningService.ts # 筛选业务逻辑 ⭐ + │ └── extractionService.ts + └── routes/ + └── literatureRoutes.ts +``` + +### 数据库(asl_schema) +```sql +CREATE SCHEMA asl_schema; + +核心表: +- literature_projects # 文献项目 +- literature_items # 文献条目(CSV导入) +- pico_configs # PICO(S)纳入排除标准配置 +- screening_results # 筛选结果(INCLUDE/EXCLUDE/UNCERTAIN) +- screening_history # 筛选历史(可回溯) +- extraction_tasks # 提取任务 +- extraction_results # 提取结果 +``` + +--- + +## 💡 核心业务流程 + +### 标题摘要初筛流程 ⭐ + +``` +1. 用户上传CSV文件(包含:标题、摘要、作者等) + ↓ +2. 配置PICO(S)纳入/排除标准 + - P: Population(研究对象) + - I: Intervention(干预措施) + - C: Comparison(对照) + - O: Outcome(结局指标) + - S: Study Design(研究类型) + ↓ +3. AI双模型判断(DeepSeek + Qwen3) + - 每篇文献独立判断 + - 两个模型投票 + - 固定3并发处理 + ↓ +4. 返回结果:INCLUDE / EXCLUDE / UNCERTAIN + - INCLUDE: 两个模型都认为应纳入 + - EXCLUDE: 两个模型都认为应排除 + - UNCERTAIN: 两个模型意见不一致,需人工复核 + ↓ +5. 导出Excel(双Sheet设计) + - Sheet1: 通过的文献(INCLUDE) + - Sheet2: 未通过的文献(EXCLUDE + UNCERTAIN) +``` + +### AI判断逻辑(关键!) +```typescript +// 双模型投票机制 +if (deepseekResult === "INCLUDE" && qwen3Result === "INCLUDE") { + finalResult = "INCLUDE"; +} else if (deepseekResult === "EXCLUDE" && qwen3Result === "EXCLUDE") { + finalResult = "EXCLUDE"; +} else { + // 意见不一致 + finalResult = "UNCERTAIN"; // 标记为需要人工复核 +} +``` + +--- + +## 📚 已有设计文档 + +### PRD文档(完整!) +- `00-项目概述/AI智能文献PRD(1)-产品概览.md` +- `00-项目概述/AI智能文献PRD(2)-初筛与复筛.md` +- `00-项目概述/AI智能文献PRD(3)-提取与分析模块.md` + +**内容:** 完整的功能需求、用户故事、验收标准 + +### 技术设计(完整!) +- `01-设计文档/02-数据库设计.md` - 完整表结构 +- `01-设计文档/03-API设计.md` - 所有API端点 +- `01-设计文档/04-前端组件设计.md` - 组件树 +- `01-设计文档/05-AI模型集成设计.md` - 双模型投票逻辑 + +### UI原型(完整!) +- `01-设计文档/07-UI设计/标题摘要初筛原型.html` ⭐ +- `01-设计文档/07-UI设计/全文复筛原型.html` ⭐ + +--- + +## 🔗 依赖的通用能力 + +### 1. LLM网关(❌ 待实现,P0)⭐ **必须先实现** + +**为什么ASL需要LLM网关:** +- 标题摘要初筛需要调用2个LLM模型 +- 全文复筛需要调用1个LLM模型 +- 需要成本控制和配额管理 + +**接口需求:** +```typescript +// ASL模块需要的接口 +interface LLMGateway { + // 单次调用(非流式) + chat(params: { + userId: string; + modelType: 'deepseek-v3' | 'qwen3'; + messages: Message[]; + }): Promise<{ + content: string; + tokenUsage: number; + }>; + + // 检查配额 + checkQuota(userId: string): Promise; +} +``` + +**实施建议:** Week 2 Day 1-3 同步开发LLM网关 + +--- + +### 2. 文档处理引擎(✅ 已实现) + +**ASL使用场景:** +- 全文复筛:PDF全文提取 + +**已有接口:** +```typescript +// extraction_service已提供 +POST /api/extract/pdf +``` + +--- + +### 3. RAG引擎(✅ 已实现,可选) + +**ASL使用场景(可选):** +- 文献内容检索 +- 文献相似度分析 + +--- + +## 📋 API端点清单 + +### 项目管理 +``` +POST /api/v1/literature/projects # 创建文献项目 +GET /api/v1/literature/projects # 获取项目列表 +GET /api/v1/literature/projects/:id # 获取项目详情 +PUT /api/v1/literature/projects/:id # 更新项目 +DELETE /api/v1/literature/projects/:id # 删除项目 +``` + +### 标题摘要初筛 ⭐ +``` +POST /api/v1/literature/projects/:id/items/import # 导入CSV +POST /api/v1/literature/projects/:id/pico # 配置PICO +POST /api/v1/literature/projects/:id/screening/title # 执行初筛 +GET /api/v1/literature/projects/:id/screening/status # 查询进度 +GET /api/v1/literature/projects/:id/screening/results # 获取结果 +POST /api/v1/literature/projects/:id/screening/export # 导出Excel +``` + +### 全文复筛 +``` +POST /api/v1/literature/projects/:id/screening/fulltext # 执行全文筛选 +``` + +--- + +## 📅 开发计划 + +### Week 2(11月11-15日) +- **Day 1-2:** 项目管理基础CRUD +- **Day 3-4:** 标题摘要初筛后端(含LLM网关) +- **Day 5:** 标题摘要初筛前端 + +### Week 3(11月18-22日) +- **Day 1-2:** 全文复筛后端 +- **Day 3-4:** 全文复筛前端 +- **Day 5:** 测试和优化 + +### Week 4(11月25-29日) +- **Day 1-2:** 数据提取功能 +- **Day 3-5:** 整体测试和文档完善 + +--- + +## ⚠️ 关键技术难点 + +### 1. AI判断准确率 +**解决方案:** +- 双模型投票机制 +- 优化PICO提示词 +- 提供人工复核入口(UNCERTAIN项) + +### 2. 大批量处理 +**解决方案:** +- 固定3并发(p-queue) +- 实时进度显示 +- 失败重试机制 + +### 3. CSV解析 +**解决方案:** +- 使用papaparse库 +- 支持多种编码(UTF-8、GBK) +- 容错处理 + +### 4. PDF全文提取 +**解决方案:** +- 调用extraction_service +- 降级策略:Nougat → PyMuPDF + +--- + +## ✅ 快速开发检查清单 + +**开始开发前确认:** +- [ ] LLM网关是否已实现?(如未实现,Week 2 Day 1-3同步开发) +- [ ] 数据库Schema是否已创建?(asl_schema) +- [ ] Prisma Schema是否已更新? +- [ ] API路由是否已注册? +- [ ] 前端路由是否已配置? + +**常见问题:** + +**Q: LLM调用超时怎么办?** +A: 设置timeout=60s,添加重试机制(最多3次) + +**Q: CSV解析失败怎么办?** +A: 检查编码格式,提供明确的错误提示,支持重新上传 + +**Q: 两个模型都返回UNCERTAIN怎么办?** +A: 标记为UNCERTAIN,提示用户需要人工复核 + +**Q: PDF提取失败怎么办?** +A: 降级策略:Nougat → PyMuPDF → 提示用户手动处理 + +--- + +## 📖 更多详细信息 + +**需要完整PRD:** +→ `00-项目概述/AI智能文献PRD(1-3).md`(3个文档) + +**需要数据库详情:** +→ AI智能文献目录下的 `02-技术设计/01-数据库设计.md` + +**需要API详情:** +→ AI智能文献目录下的 `02-技术设计/02-API设计规范.md` + +**需要UI设计:** +→ `01-设计文档/AI智能文献-标题摘要初筛原型.html` +→ `01-设计文档/AI智能文献-全文复筛.html` + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/03-业务模块/DC-数据清洗整理/README.md b/docs/03-业务模块/DC-数据清洗整理/README.md new file mode 100644 index 00000000..8b28d412 --- /dev/null +++ b/docs/03-业务模块/DC-数据清洗整理/README.md @@ -0,0 +1,98 @@ +# DC - 数据清洗整理 + +> **模块代号:** DC (Data Cleaning) +> **开发状态:** ⏳ 规划中 +> **商业价值:** ⭐⭐⭐⭐⭐ 可独立售卖 +> **独立性:** ⭐⭐⭐⭐⭐ +> **优先级:** P1 + +--- + +## 📋 模块概述 + +数据清洗整理模块提供专业工具,处理医院导出的海量(百万行级)、多表格的Excel数据。 + +**核心价值:** 核心差异化功能,解决医学科研痛点 + +--- + +## 🎯 核心功能 + +### 1. 表格ETL(重点) +- 多张Excel表格导入 +- 按"患者ID"和"时间"自动JOIN +- 重组为干净的分析宽表 + +### 2. 文本提取(NER)(重点) +- 从病理报告提取结构化字段 +- 从住院小结提取关键信息 +- TNM分期自动识别 + +### 3. 数据质量报告 +- 缺失值统计 +- 异常值检测 +- 数据质量评分 + +### 4. 导出标准化数据 +- Excel导出 +- SPSS格式 +- R语言格式 + +--- + +## 📂 文档结构 + +``` +DC-数据清洗整理/ + ├── [AI对接] DC快速上下文.md # ⏳ 待创建 + ├── 00-项目概述/ + │ └── 01-产品需求文档(PRD).md # ⏳ 待创建 + ├── 01-设计文档/ + │ ├── 01-ETL引擎设计.md # ⏳ 待创建 + │ └── 02-医学NLP设计.md # ⏳ 待创建 + └── README.md # ✅ 当前文档 +``` + +--- + +## 🔗 依赖的通用能力 + +- **LLM网关** - 医学NER提取(云端版) +- **文档处理引擎** - Excel/Docx读取 +- **ETL引擎** - 数据清洗和转换 +- **医学NLP引擎** - 实体识别(单机版) + +--- + +## 🎯 商业模式 + +**目标客户:** 临床科室、数据管理员 +**售卖方式:** 独立产品 +**定价策略:** 按项目数或一次性License + +--- + +## ⚠️ 技术难点 + +1. **大数据处理** - 百万行数据的内存管理 +2. **隐私保护** - 单机版必须100%本地化 +3. **NER准确率** - 医学术语复杂 + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/03-业务模块/PKB-个人知识库/02-技术设计/01-数据库设计.md b/docs/03-业务模块/PKB-个人知识库/02-技术设计/01-数据库设计.md new file mode 100644 index 00000000..255e5ed1 --- /dev/null +++ b/docs/03-业务模块/PKB-个人知识库/02-技术设计/01-数据库设计.md @@ -0,0 +1,596 @@ +# PKB - 个人知识库模块:数据库设计 + +> **版本:** v1.0 +> **更新时间:** 2025-11-12 +> **数据库Schema:** `pkb_schema` +> **状态:** ✅ 已实施并迁移 + +--- + +## 📋 目录 + +1. [模块概述](#模块概述) +2. [Schema信息](#schema信息) +3. [数据库表设计](#数据库表设计) +4. [表关系图](#表关系图) +5. [索引设计](#索引设计) +6. [Phase 3功能说明](#phase-3功能说明) +7. [变更历史](#变更历史) + +--- + +## 模块概述 + +### 功能定位 + +**PKB(Personal Knowledge Base)- 个人知识库模块**提供文献管理和智能问答能力,核心功能: + +1. **知识库管理** - 创建和管理个人知识库 +2. **文档上传** - 支持PDF/Word/TXT等格式文档 +3. **智能问答** - 基于知识库的RAG(检索增强生成)对话 +4. **批处理任务** - 批量处理文献提取(Phase 3) +5. **任务模板** - 预定义的批处理任务模板(Phase 3) + +### 核心业务场景 + +- 用户创建知识库(如"CLL相关知识库") +- 上传PDF文献到知识库 +- 自动提取文本并向量化 +- 基于知识库进行智能问答 +- 批量提取文献中的结构化信息 + +### 与Dify平台集成 + +PKB模块深度集成Dify平台: +- 每个知识库对应一个Dify Dataset +- 每个文档对应一个Dify Document +- 使用Dify的向量检索和RAG能力 + +--- + +## Schema信息 + +### Schema名称 +```sql +pkb_schema +``` + +### 创建语句 +```sql +CREATE SCHEMA IF NOT EXISTS pkb_schema; +GRANT ALL ON SCHEMA pkb_schema TO aiclinical_admin; +``` + +### 数据迁移 +- **迁移时间:** 2025-11-12 +- **源Schema:** public +- **迁移脚本:** `docs/09-架构实施/migration-scripts/004-migrate-pkb.sql` +- **数据完整性:** ✅ 100%迁移成功 + +--- + +## 数据库表设计 + +### 表列表 + +| 表名 | 用途 | 行数(估计) | 状态 | +|------|------|------------|------| +| `knowledge_bases` | 知识库 | 5-50/用户 | ✅ 已部署 | +| `documents` | 文档 | 10-1000/知识库 | ✅ 已部署 | +| `batch_tasks` | 批处理任务 | 1-100/知识库 | ✅ Phase 3 | +| `batch_results` | 批处理结果 | N条/任务 | ✅ Phase 3 | +| `task_templates` | 任务模板 | 10-50/用户 | ✅ Phase 3(预留) | + +**总计:** 5个表(2个核心表 + 3个Phase 3表) + +--- + +### 1. knowledge_bases - 知识库表 + +**用途:** 存储用户创建的个人知识库 + +#### 表结构 + +| 字段名 | 数据类型 | 约束 | 说明 | +|--------|---------|------|------| +| id | TEXT | PRIMARY KEY | 知识库唯一标识(UUID) | +| user_id | TEXT | NOT NULL, FK | 所属用户ID | +| name | TEXT | NOT NULL | 知识库名称 | +| description | TEXT | NULL | 知识库描述 | +| dify_dataset_id | TEXT | NOT NULL, UNIQUE | Dify平台的Dataset ID | +| file_count | INTEGER | NOT NULL, DEFAULT 0 | 文件数量 | +| total_size_bytes | BIGINT | NOT NULL, DEFAULT 0 | 总文件大小(字节) | +| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 | +| updated_at | TIMESTAMPTZ | NOT NULL | 更新时间 | + +#### Prisma Model + +```prisma +model KnowledgeBase { + id String @id @default(uuid()) + userId String @map("user_id") + name String + description String? + difyDatasetId String @map("dify_dataset_id") + fileCount Int @default(0) @map("file_count") + totalSizeBytes BigInt @default(0) @map("total_size_bytes") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + documents Document[] + batchTasks BatchTask[] + + @@index([userId]) + @@index([difyDatasetId]) + @@map("knowledge_bases") + @@schema("pkb_schema") +} +``` + +#### 业务规则 + +1. **Dify绑定** - 每个知识库对应唯一的Dify Dataset +2. **统计字段** - `file_count`和`total_size_bytes`需实时更新 +3. **用户隔离** - 通过`user_id`实现数据隔离 +4. **级联删除** - 删除知识库时,文档和任务也被删除 + +--- + +### 2. documents - 文档表 + +**用途:** 存储知识库中的文档信息 + +#### 表结构 + +| 字段名 | 数据类型 | 约束 | 说明 | +|--------|---------|------|------| +| id | TEXT | PRIMARY KEY | 文档唯一标识(UUID) | +| kb_id | TEXT | NOT NULL, FK | 所属知识库ID | +| user_id | TEXT | NOT NULL, FK | 所属用户ID | +| filename | TEXT | NOT NULL | 文件名 | +| file_type | TEXT | NOT NULL | 文件类型(pdf/doc/txt等) | +| file_size_bytes | BIGINT | NOT NULL | 文件大小(字节) | +| file_url | TEXT | NOT NULL | 文件存储URL | +| dify_document_id | TEXT | NOT NULL | Dify平台的Document ID | +| status | TEXT | NOT NULL, DEFAULT 'uploading' | 状态(uploading/processing/completed/failed) | +| progress | INTEGER | NOT NULL, DEFAULT 0 | 处理进度(0-100) | +| error_message | TEXT | NULL | 错误信息 | +| segments_count | INTEGER | NULL | 切片数量 | +| tokens_count | INTEGER | NULL | Token数量 | +| extraction_method | TEXT | NULL | 提取方法(auto/ocr/parse) | +| **Phase 2字段** | | | **全文阅读功能** | +| full_text | TEXT | NULL | 完整文本内容 | +| full_text_length | INTEGER | NULL | 文本长度 | +| metadata | JSONB | NULL | 元数据(作者、标题、摘要等) | +| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 | +| updated_at | TIMESTAMPTZ | NOT NULL | 更新时间 | + +#### Prisma Model + +```prisma +model Document { + id String @id @default(uuid()) + kbId String @map("kb_id") + userId String @map("user_id") + filename String + fileType String @map("file_type") + fileSizeBytes BigInt @map("file_size_bytes") + fileUrl String @map("file_url") + difyDocumentId String @map("dify_document_id") + status String @default("uploading") + progress Int @default(0) + errorMessage String? @map("error_message") + segmentsCount Int? @map("segments_count") + tokensCount Int? @map("tokens_count") + extractionMethod String? @map("extraction_method") + + // Phase 2: 全文阅读功能 + fullText String? @map("full_text") @db.Text + fullTextLength Int? @map("full_text_length") + metadata Json? + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + knowledgeBase KnowledgeBase @relation(fields: [kbId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + batchResults BatchResult[] + + @@index([kbId]) + @@index([userId]) + @@index([status]) + @@index([difyDocumentId]) + @@index([extractionMethod]) + @@map("documents") + @@schema("pkb_schema") +} +``` + +#### 业务规则 + +1. **状态机** - `status`字段管理文档处理流程 + - `uploading` → `processing` → `completed` + - 失败时转为`failed` +2. **Dify同步** - 每个文档对应Dify中的一个Document +3. **提取方法** - 支持自动识别、OCR、解析三种方式 +4. **Phase 2扩展** - `full_text`字段用于全文阅读和深度分析 + +--- + +### 3. batch_tasks - 批处理任务表 (Phase 3) + +**用途:** 批量处理文献,提取结构化信息 + +#### 表结构 + +| 字段名 | 数据类型 | 约束 | 说明 | +|--------|---------|------|------| +| id | TEXT | PRIMARY KEY | 任务唯一标识(UUID) | +| user_id | TEXT | NOT NULL, FK | 所属用户ID | +| kb_id | TEXT | NOT NULL, FK | 所属知识库ID | +| task_name | TEXT | NOT NULL | 任务名称 | +| task_type | TEXT | NOT NULL | 任务类型(extract_info/summarize等) | +| prompt_template | TEXT | NOT NULL | Prompt模板 | +| model_name | TEXT | NOT NULL, DEFAULT 'gpt-4' | 使用的LLM模型 | +| status | TEXT | NOT NULL, DEFAULT 'pending' | 状态(pending/running/completed/failed) | +| total_documents | INTEGER | NOT NULL, DEFAULT 0 | 总文档数 | +| processed_count | INTEGER | NOT NULL, DEFAULT 0 | 已处理数 | +| success_count | INTEGER | NOT NULL, DEFAULT 0 | 成功数 | +| failed_count | INTEGER | NOT NULL, DEFAULT 0 | 失败数 | +| error_message | TEXT | NULL | 错误信息 | +| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 | +| updated_at | TIMESTAMPTZ | NOT NULL | 更新时间 | + +#### Prisma Model + +```prisma +model BatchTask { + id String @id @default(uuid()) + userId String @map("user_id") + kbId String @map("kb_id") + taskName String @map("task_name") + taskType String @map("task_type") + promptTemplate String @map("prompt_template") @db.Text + modelName String @default("gpt-4") @map("model_name") + status String @default("pending") + totalDocuments Int @default(0) @map("total_documents") + processedCount Int @default(0) @map("processed_count") + successCount Int @default(0) @map("success_count") + failedCount Int @default(0) @map("failed_count") + errorMessage String? @map("error_message") @db.Text + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + knowledgeBase KnowledgeBase @relation(fields: [kbId], references: [id], onDelete: Cascade) + results BatchResult[] + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + @@index([userId]) + @@index([kbId]) + @@index([status]) + @@index([createdAt]) + @@map("batch_tasks") + @@schema("pkb_schema") +} +``` + +#### 业务规则 + +1. **任务类型** - 支持多种批处理类型 + - `extract_info` - 提取结构化信息 + - `summarize` - 批量摘要 + - `classify` - 文献分类 +2. **状态机** - `status`管理任务执行状态 +3. **进度跟踪** - 实时更新计数器字段 +4. **模型选择** - 支持多种LLM模型 + +--- + +### 4. batch_results - 批处理结果表 (Phase 3) + +**用途:** 存储批处理任务的每篇文献结果 + +#### 表结构 + +| 字段名 | 数据类型 | 约束 | 说明 | +|--------|---------|------|------| +| id | TEXT | PRIMARY KEY | 结果唯一标识(UUID) | +| task_id | TEXT | NOT NULL, FK | 所属任务ID | +| document_id | TEXT | NOT NULL, FK | 所属文档ID | +| status | TEXT | NOT NULL, DEFAULT 'pending' | 状态(pending/processing/completed/failed) | +| result_data | JSONB | NULL | 提取的结构化数据 | +| raw_output | TEXT | NULL | LLM原始输出 | +| tokens_used | INTEGER | NULL | 使用的Token数 | +| error_message | TEXT | NULL | 错误信息 | +| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 | + +#### Prisma Model + +```prisma +model BatchResult { + id String @id @default(uuid()) + taskId String @map("task_id") + documentId String @map("document_id") + status String @default("pending") + resultData Json? @map("result_data") + rawOutput String? @map("raw_output") @db.Text + tokensUsed Int? @map("tokens_used") + errorMessage String? @map("error_message") @db.Text + + task BatchTask @relation(fields: [taskId], references: [id], onDelete: Cascade) + document Document @relation(fields: [documentId], references: [id], onDelete: Cascade) + + createdAt DateTime @default(now()) @map("created_at") + + @@index([taskId]) + @@index([documentId]) + @@index([status]) + @@map("batch_results") + @@schema("pkb_schema") +} +``` + +#### 业务规则 + +1. **结果存储** - `result_data`存储JSON格式的结构化数据 +2. **原始输出** - `raw_output`保留LLM原始输出,便于调试 +3. **Token统计** - 记录每篇文献的Token消耗 + +--- + +### 5. task_templates - 任务模板表 (Phase 3, 暂不实现) + +**用途:** 存储预定义的批处理任务模板 + +#### 表结构 + +| 字段名 | 数据类型 | 约束 | 说明 | +|--------|---------|------|------| +| id | TEXT | PRIMARY KEY | 模板唯一标识(UUID) | +| user_id | TEXT | NOT NULL, FK | 所属用户ID | +| template_name | TEXT | NOT NULL | 模板名称 | +| task_type | TEXT | NOT NULL | 任务类型 | +| prompt_template | TEXT | NOT NULL | Prompt模板 | +| output_fields | JSONB | NOT NULL, DEFAULT '{}' | 输出字段定义 | +| model_name | TEXT | NOT NULL, DEFAULT 'gpt-4' | 默认模型 | +| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 | +| updated_at | TIMESTAMPTZ | NOT NULL | 更新时间 | + +#### Prisma Model + +```prisma +model TaskTemplate { + id String @id @default(uuid()) + userId String @map("user_id") + templateName String @map("template_name") + taskType String @map("task_type") + promptTemplate String @map("prompt_template") @db.Text + outputFields Json @default("{}") @map("output_fields") + modelName String @default("gpt-4") @map("model_name") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + @@index([userId]) + @@map("task_templates") + @@schema("pkb_schema") +} +``` + +#### 业务规则 + +1. **模板复用** - 用户可保存常用的任务配置 +2. **字段定义** - `output_fields`定义期望的输出结构 +3. **暂不实现** - Phase 3预留,后续开发 + +--- + +## 表关系图 + +```mermaid +erDiagram + PLATFORM_USERS ||--o{ KNOWLEDGE_BASES : "owns" + PLATFORM_USERS ||--o{ DOCUMENTS : "uploads" + PLATFORM_USERS ||--o{ BATCH_TASKS : "creates" + PLATFORM_USERS ||--o{ TASK_TEMPLATES : "defines" + + KNOWLEDGE_BASES ||--o{ DOCUMENTS : "contains" + KNOWLEDGE_BASES ||--o{ BATCH_TASKS : "processes" + + BATCH_TASKS ||--o{ BATCH_RESULTS : "generates" + DOCUMENTS ||--o{ BATCH_RESULTS : "analyzed_by" + + PLATFORM_USERS { + text id PK + text email + text password + } + + KNOWLEDGE_BASES { + text id PK + text user_id FK + text name + text dify_dataset_id + int file_count + bigint total_size_bytes + } + + DOCUMENTS { + text id PK + text kb_id FK + text user_id FK + text filename + text file_type + text dify_document_id + text status + text full_text + jsonb metadata + } + + BATCH_TASKS { + text id PK + text user_id FK + text kb_id FK + text task_name + text task_type + text status + int total_documents + int processed_count + } + + BATCH_RESULTS { + text id PK + text task_id FK + text document_id FK + text status + jsonb result_data + text raw_output + } + + TASK_TEMPLATES { + text id PK + text user_id FK + text template_name + text task_type + jsonb output_fields + } +``` + +### 跨Schema引用 + +**外键关系:** +- `knowledge_bases.user_id` → `platform_schema.users.id` +- `documents.user_id` → `platform_schema.users.id` +- `batch_tasks.user_id` → `platform_schema.users.id` +- `task_templates.user_id` → `platform_schema.users.id` + +**说明:** Prisma自动处理跨Schema外键,应用代码无需关心Schema前缀 + +--- + +## 索引设计 + +### 主键索引 +所有表的`id`字段自动创建B-tree主键索引。 + +### 外键索引 + +| 表名 | 索引字段 | 用途 | +|------|---------|------| +| knowledge_bases | user_id | 查询用户的所有知识库 | +| knowledge_bases | dify_dataset_id | Dify数据同步 | +| documents | kb_id | 查询知识库的所有文档 | +| documents | user_id | 查询用户的所有文档 | +| documents | status | 过滤文档状态 | +| documents | dify_document_id | Dify数据同步 | +| documents | extraction_method | 按提取方法过滤 | +| batch_tasks | user_id | 查询用户的任务 | +| batch_tasks | kb_id | 查询知识库的任务 | +| batch_tasks | status | 过滤任务状态 | +| batch_results | task_id | 查询任务的所有结果 | +| batch_results | document_id | 查询文档的处理结果 | +| batch_results | status | 过滤结果状态 | +| task_templates | user_id | 查询用户的模板 | + +### 时间索引 + +| 表名 | 索引字段 | 用途 | +|------|---------|------| +| batch_tasks | created_at | 按时间排序任务 | + +--- + +## Phase 3功能说明 + +### 批处理工作流程 + +```mermaid +sequenceDiagram + participant User + participant API + participant BatchTask + participant Document + participant LLM + participant BatchResult + + User->>API: 创建批处理任务 + API->>BatchTask: 创建任务记录 + API->>Document: 查询知识库文档列表 + + loop 每篇文档 + BatchTask->>Document: 读取文档全文 + BatchTask->>LLM: 调用LLM提取信息 + LLM-->>BatchTask: 返回结构化数据 + BatchTask->>BatchResult: 保存结果 + BatchTask->>BatchTask: 更新进度 + end + + BatchTask->>API: 任务完成 + API-->>User: 返回结果汇总 +``` + +### 批处理任务类型示例 + +1. **信息提取** (`extract_info`) + - 提取研究方法、样本量、P值等 + - 输出JSON格式的结构化数据 + +2. **文献摘要** (`summarize`) + - 批量生成文献摘要 + - 统一格式和长度 + +3. **文献分类** (`classify`) + - 根据研究类型分类 + - 标签化管理 + +--- + +## 变更历史 + +### v1.0 - 2025-11-12 - 初始版本 ✅ + +**变更内容:** +1. 从`public` schema迁移到`pkb_schema` +2. 5个表全部迁移: + - knowledge_bases + - documents + - batch_tasks + - batch_results + - task_templates +3. 在Prisma中添加`@@schema("pkb_schema")`标签 +4. 所有数据100%完整迁移 + +**迁移脚本:** `docs/09-架构实施/migration-scripts/004-migrate-pkb.sql` + +**验证状态:** ✅ 已验证,功能正常 + +**特殊处理:** +- `batch_results.rawOutput` → `raw_output`(列名映射修正) +- `task_templates.outputFields` → `output_fields`(列名映射修正) + +--- + +## 📚 相关文档 + +- [Schema隔离架构设计](../../../09-架构实施/01-Schema隔离架构设计(10个).md) +- [Schema迁移完成报告](../../../09-架构实施/Schema迁移完成报告.md) +- [Prisma配置完成报告](../../../09-架构实施/Prisma配置完成报告.md) +- [快速功能测试报告](../../../09-架构实施/快速功能测试报告.md) +- [AIA数据库设计文档](../../AIA-AI智能问答/02-技术设计/01-数据库设计.md) + +--- + +**文档维护者:** AI助手 +**最后更新:** 2025-11-12 +**文档状态:** ✅ 已完成并验证 + + + + + + diff --git a/docs/03-业务模块/PKB-个人知识库/README.md b/docs/03-业务模块/PKB-个人知识库/README.md new file mode 100644 index 00000000..bcb73a48 --- /dev/null +++ b/docs/03-业务模块/PKB-个人知识库/README.md @@ -0,0 +1,62 @@ +# PKB - 个人知识库 + +> **模块代号:** PKB (Personal Knowledge Base) +> **开发状态:** ✅ 已完成 +> **商业价值:** ⭐⭐⭐ +> **独立性:** ⭐⭐⭐ + +--- + +## 📋 模块概述 + +个人知识库允许用户创建私人文献库,并基于库内文献进行AI问答(RAG)。 + +--- + +## 🎯 核心功能 + +### 已完成功能 +1. ✅ **知识库CRUD** - 创建、查看、编辑、删除 +2. ✅ **文档上传** - PDF、Word、TXT、Markdown +3. ✅ **RAG问答** - 基于知识库内容问答 +4. ✅ **@知识库引用** - 智能引用系统(100%准确溯源) +5. ✅ **配额管理** - 每用户3个知识库,每库50个文档 + +--- + +## 📂 文档结构 + +``` +PKB-个人知识库/ + ├── [AI对接] PKB快速上下文.md # ⏳ 待创建 + ├── 00-项目概述/ + ├── 01-设计文档/ + └── README.md # ✅ 当前文档 +``` + +--- + +## 🔗 依赖的通用能力 + +- **LLM网关** - RAG问答 +- **文档处理引擎** - 文档文本提取 +- **RAG引擎** - 向量检索 + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/03-业务模块/README.md b/docs/03-业务模块/README.md new file mode 100644 index 00000000..26b422d2 --- /dev/null +++ b/docs/03-业务模块/README.md @@ -0,0 +1,119 @@ +# 业务模块层 + +> **层级定位:** 面向用户的产品功能 +> **核心原则:** 独立部署、独立销售、低耦合、高内聚 + +--- + +## 📋 模块清单 + +| 模块 | 名称 | 商业价值 | 独立性 | 状态 | 优先级 | +|------|------|---------|-------|------|--------| +| **AIA** | AI智能问答 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ✅ 已完成 | - | +| **ASL** | AI智能文献 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⏳ 下一步 | P0 | +| **PKB** | 个人知识库 | ⭐⭐⭐ | ⭐⭐⭐ | ✅ 已完成 | - | +| **DC** | 数据清洗整理 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⏳ 规划中 | P1 | +| **SSA** | 智能统计分析 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⏳ 规划中 | P2 | +| **ST** | 统计分析工具 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⏳ 规划中 | P2 | +| **RVW** | 稿件审查系统 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⚡ 独立系统 | P1 | +| **ADMIN** | 运营管理端 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⏳ 规划中 | P1 | + +--- + +## 🎯 设计原则 + +### 1. 独立部署 +- 每个模块可以单独部署 +- 支持Docker打包 +- 支持Electron单机版 + +### 2. 独立销售 +- 每个模块可以单独售卖 +- 完整的文档和部署指南 +- 独立的定价策略 + +### 3. 低耦合 +- 模块间不直接依赖 +- 通过通用能力层交互 + +### 4. 高内聚 +- 模块内功能完整 +- 业务逻辑闭环 + +--- + +## 📊 模块分类 + +### 核心差异化模块(可独立销售) +1. **ASL** - AI智能文献 ⭐⭐⭐⭐⭐ + - 目标客户:系统评价研究者、循证医学中心 + - 商业模式:独立售卖 + +2. **DC** - 数据清洗整理 ⭐⭐⭐⭐⭐ + - 目标客户:临床科室、数据管理员 + - 商业模式:独立售卖 + +3. **RVW** - 稿件审查系统 ⭐⭐⭐⭐⭐ + - 目标客户:期刊编辑部、出版社 + - 商业模式:按期刊订阅 + +### 协同模块(组合销售) +4. **SSA** + **ST** - 统计分析套件 + - 协同效应强 + - 组合售卖 + +### 基础模块(平台功能) +5. **AIA** + **PKB** - AI助手套件 + - 平台标配功能 + +### 管理模块 +6. **ADMIN** - 运营管理端 + - SaaS运营必备 + +--- + +## 📚 快速导航 + +### 快速上下文 +- **[AI对接] 业务模块快速上下文.md** - 2-3分钟了解业务模块层 + +### 核心模块(按优先级) +1. [ASL-AI智能文献](./ASL-AI智能文献/README.md) - P0,下一步开发 +2. [DC-数据清洗整理](./DC-数据清洗整理/README.md) - P1,核心竞争力 +3. [RVW-稿件审查系统](./RVW-稿件审查系统/README.md) - P1,独立系统 +4. [ADMIN-运营管理端](./ADMIN-运营管理端/README.md) - P1,商业基础 + +### 已完成模块 +5. [AIA-AI智能问答](./AIA-AI智能问答/README.md) - 已完成 +6. [PKB-个人知识库](./PKB-个人知识库/README.md) - 已完成 + +### 规划中模块 +7. [SSA-智能统计分析](./SSA-智能统计分析/README.md) - P2 +8. [ST-统计分析工具](./ST-统计分析工具/README.md) - P2 + +--- + +## 🔗 相关文档 + +- [系统架构分层设计](../00-系统总体设计/01-系统架构分层设计.md) +- [平台基础层](../01-平台基础层/README.md) +- [通用能力层](../02-通用能力层/README.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/03-业务模块/RVW-稿件审查系统/README.md b/docs/03-业务模块/RVW-稿件审查系统/README.md new file mode 100644 index 00000000..1fe62a1b --- /dev/null +++ b/docs/03-业务模块/RVW-稿件审查系统/README.md @@ -0,0 +1,95 @@ +# RVW - 稿件审查系统 + +> **模块代号:** RVW (Review) +> **开发状态:** ⚡ 核心功能已完成,待扩展 +> **商业价值:** ⭐⭐⭐⭐⭐ 可独立售卖 +> **独立性:** ⭐⭐⭐⭐⭐ 极高 +> **优先级:** P1 + +--- + +## 📋 模块概述 + +稿件审查系统提供AI辅助的稿件智能审查功能。 + +**核心价值:** 完全独立的产品,可独立售卖给期刊编辑部 + +--- + +## 🎯 核心功能 + +### 已完成功能 +1. ✅ **文档上传** - Word稿件上传 +2. ✅ **文本提取** - 调用文档处理引擎 +3. ✅ **稿约规范性评估** - 11项评估(editorial_review) +4. ✅ **方法学评估** - 3部分评估(methodology_review) +5. ✅ **综合评分** - 双维度评分 +6. ✅ **PDF导出** - 评估报告导出 + +### 未来扩展 +- ⏳ 审稿人管理 +- ⏳ 审稿流程管理 +- ⏳ 多轮审稿 +- ⏳ 审稿意见模板 +- ⏳ 期刊库管理 + +--- + +## 📂 文档结构 + +``` +RVW-稿件审查系统/ + ├── [AI对接] RVW快速上下文.md # ⏳ 待创建 + ├── 00-项目概述/ + │ ├── 01-产品需求文档(PRD).md # ⏳ 待创建 + │ ├── 02-独立系统规划.md # ⏳ 待创建 + │ └── 03-商业模式设计.md # ⏳ 待创建 + ├── 01-设计文档/ + ├── 02-业务规则/ + │ ├── 01-方法学评估标准.md # ⏳ 待迁移 + │ └── 02-稿约规范性评估标准.md # ⏳ 待迁移 + └── README.md # ✅ 当前文档 +``` + +--- + +## 🔗 依赖的通用能力 + +- **LLM网关** - AI评估 +- **文档处理引擎** - 稿件文本提取 + +--- + +## 🎯 商业模式 + +**目标客户:** 期刊编辑部、出版社、学会 +**售卖方式:** 完全独立产品 +**定价策略:** 按期刊订阅 或 按稿件数量计费 + +--- + +## ⭐ 为什么适合独立? + +1. ✅ **用户群独立** - 期刊编辑部 vs 临床医生(完全不同) +2. ✅ **业务逻辑独立** - 与其他模块无关联 +3. ✅ **部署场景独立** - 期刊编辑部有自己的部署需求 +4. ✅ **商业模式独立** - 可以按期刊订阅 + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/03-业务模块/RVW-稿件审查系统/稿件方法学评估标准.txt b/docs/03-业务模块/RVW-稿件审查系统/稿件方法学评估标准.txt new file mode 100644 index 00000000..89914416 --- /dev/null +++ b/docs/03-业务模块/RVW-稿件审查系统/稿件方法学评估标准.txt @@ -0,0 +1,31 @@ +稿件方法学评估 + +第一部分:科研设计评估 + +1. 类型交代不清楚 +2.缺少研究对象介绍 +3. 研究对象介绍不完整 +4. 对照设计不合理且无解释说明 +5.影响、干预因素及观察指标交代不清楚 +6.研究效应及评价指标不正确 +7.研究设计要素描述欠完整、欠准确 (随机、对照、盲法、重复等) +8.缺少质控措施介绍 +9. 其他, + + +第二部分:统计学方法描述评估 + +1. 描述不完整(软件、版本、资料类型、表达方式、相应统计方法、检验水准等) +2. 描述与实际不一致 +3. 资料的表达与描述不正确 +4. 未调整混杂因素 +5. 其他 + +第三部分:统计分析评估 + +1. 主要研究结果统计方法使用不正确 +2. 次要研究结果统计方法使用不正确 +3. 统计结果描述不规范 +4. 主要统计结果错误 +5. 次要统计结果错误 +6. 其他 \ No newline at end of file diff --git a/docs/03-业务模块/RVW-稿件审查系统/稿约规范性评估标准.txt b/docs/03-业务模块/RVW-稿件审查系统/稿约规范性评估标准.txt new file mode 100644 index 00000000..09746bbc --- /dev/null +++ b/docs/03-业务模块/RVW-稿件审查系统/稿约规范性评估标准.txt @@ -0,0 +1,26 @@ +稿约规范性评估: + +1.文稿科学性与实用性评估:论点明确,资料可靠,数据准确,层次清楚,文字精练,用字规范, 文稿附图量不限,提倡多附图像清晰的图。临床病例分析可以图像为主,并贯穿文字说明和评析,视频 为30~40 min 。 当报告是以人为研究对象的试验时,作者应说明其遵循的程序是否符合负责人体实验的委 员会(单位性、地区性或国家性)所制定的伦理学标准并得到该委员会的批准,是否取得受试对象的知 情同意。论著性文章5000字以内,综述、讲座、论坛可视情况而定,病例报告一般不超过2000字。 + +2.文题评估:力求简明,且能反映出文章的主题。中文文题一般不超过20个汉字,英文文题一般不宜超 过10个实词。 + +3.作者格式评估:作者姓名在文题下依次排列,在投稿后不应再做更动;作者单位按照邮政编码、所在省市 县、单位全称、具体科室的顺序列于文题页左下方。作者应是:(1)参与选题和设计,或参与资料的分 析和解释者;(2)起草或修改论文中主要观点或其他主要内容者;(3)能对编辑部的修改意见进行核修, 在学术方面进行答辩,并最终同意该文发表者。以上3条均须具备。作者中如有外籍作者应征得本人同 意,并附证明信。 + +4.摘要评估:论著性文章需附中、英文摘要,300~500字(词)为宜。摘要必须包括目的、方法、结果 (列出主要数据)、结论4个部分,各部分冠以相应的标题。英文摘要应包括文题、文中所有作者姓名(汉 语拼音)、单位名称、所在城市及邮政编码,其后加列国名;作者不属同一单位时,在姓名右上角加注不 同的阿拉伯数字序号1,2,3, ……并在其工作单位名称之前(英文)或之后(中文)加注与作者姓名序 号相同的数字。 + +5.关键词评估:论著需分别在中、英文摘要后标引2~5个中、英文关键词。尽量使用美国国立医学图书馆 编辑的最新版《Index Medicus》中《医学主题词表(MeSH)》 内所列的词。如果无相应的词,可按下列 方法处理:(1)可选用直接相关的几个主题词进行组配;(2)可根据树状结构表选用最直接的上位主题 词;(3)必要时可采用习用的自由词并列于最后。关键词中的缩写词应按MeSH表还原为全称,如“Hb- sAg” 应标引为“乙型肝炎表面抗原”。关键词之间用“;”分隔,每个英文关键词首字母大写。 + +6.医学名词和药物名称评估:医学名词以1989年及其以后由全国自然科学名词审定委员会审定并公布、 科学出版社出版的《医学名词》和相关学科的名词为准,尚未公布者以人民卫生出版社所编《英汉医学 词汇》为准。中文药物名称应使用化学工业出版社1995年出版的《中华人民共和国药典》或卫生部药典 委员会编写的《中国药品通用名称》中的名称,英文药物名称则采用国际非专利药名,不用商品名。 + +7.缩略语评估:文中尽量少用。必须使用时于首次出现处先列出其全称,然后括号注出中文缩略语或英 文全称及其缩略语,后两者间用“,”分开。 + +8.计量单位评估:执行国务院1984年2月颁布的《中华人民共和国法定计量单位》,并以单位符号表示, 具体使用参照中华医学会杂志社编写的《法定计量单位在医学上的应用(第3版)》 一书。首次出现不常 用法定计量单位时在括号内注明与旧制单位的换算关系。量的符号一律用斜体字母,如吸光度的符号 为A。 + +9.图片格式评估:每3张图单独占1页,集中附于文后,分别按其在正文中出现的先后次序连续编码。每张图 片均应有必要的图题及说明性文字置于图的下方,并在注释中标明图中使用的全部非公知公用的缩写; 图中箭头标注应有文字说明。大体标本图片在图内应有尺度标记,病理照片要求注明特殊染色方法和高、 中、低倍数。图片要求有良好的清晰度和对比度,采用JPG 格式,分辨率不低于300像素/英寸,并应经 过剪切后充分显示关键部分。说明文字应简短,不应超过50个字,所有的图在文中相应部分应提及。 + +10.动态图像评估:分别按其在正文中出现的先后次序连续编码,文中应标记为“动态图×”,每个文件 名均应与文中的名称相符,如“动态图×”。视频资料要求图像和声音清晰稳定,剪接顺畅,保持可能获 得的最高清晰度模式,视频文件采用AVI 格式。 + +11.参考文献评估:按GB/T 7714-2015《信息与文献参考文献著录规则》采用顺序编码制著录,依照其在 文中出现的先后顺序用阿拉伯数字加方括号于右上角标出。不要引用摘要作为参考文献。参考文献中的 作者,1~3名全部列出,3名以上只列前3名,后加“,等”或其他与之相应的外文文字。外文期刊名称 用缩写,以《Index Medicus》中的格式为准;中文期刊用全名。每条参考文献题名项后均须标注文献类 型及著录起止页。将参考文献按引用先后顺序(用阿拉伯数字标出)排列于文末。举例说明如下: +举例1:左明良,尹立雪,李春梅,等.超声评价不同心脏起搏位点对犬血流动力学的影响[J/CD]. 中华医学 超声杂志(电子版),2005,3(4):200-204. +举例2:周永昌,郭万学.超声医学[M].3 版.北京:科学技术文献出版,2002:808-810,824-825. +举例3:叶欣,费兴波,何申戌.高强度聚焦超声治疗肿瘤[J]. 国外医学 ·肿瘤学分册,2004,31(1):38-40. diff --git a/docs/03-业务模块/SSA-智能统计分析/README.md b/docs/03-业务模块/SSA-智能统计分析/README.md new file mode 100644 index 00000000..88fef9d8 --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/README.md @@ -0,0 +1,84 @@ +# SSA - 智能统计分析 + +> **模块代号:** SSA (Smart Statistical Analysis) +> **开发状态:** ⏳ 规划中 +> **商业价值:** ⭐⭐⭐⭐⭐ 刚需 +> **独立性:** ⭐⭐⭐⭐ +> **优先级:** P2 + +--- + +## 📋 模块概述 + +智能统计分析模块提供3条核心分析路径,实现从数据上传到报告导出的完整流程。 + +--- + +## 🎯 核心功能(3条路径) + +### 1. 队列研究分析 +- 基线特征分析 +- 生存分析(Kaplan-Meier) +- Cox回归 + +### 2. 预测模型构建 +- 变量筛选 +- 模型构建(Logistic回归、随机森林) +- 模型验证(ROC曲线) + +### 3. RCT研究分析 +- 随机化检查 +- 疗效分析 +- 亚组分析 + +--- + +## 📂 文档结构 + +``` +SSA-智能统计分析/ + ├── [AI对接] SSA快速上下文.md # ⏳ 待创建 + ├── 00-项目概述/ + │ └── 01-产品需求文档(PRD).md # ⏳ 待创建 + └── README.md # ✅ 当前文档 +``` + +--- + +## 🔗 依赖的通用能力 + +- **文档处理引擎** - 数据导入 +- **ETL引擎** - 数据预处理 + +--- + +## 🏗️ 技术栈 + +- **R语言** - 统计分析核心 +- **Plumber** - R暴露为API +- **Node.js** - 粘合层 + +--- + +## 🎯 商业模式 + +**与ST模块协同售卖** + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/03-业务模块/ST-统计分析工具/README.md b/docs/03-业务模块/ST-统计分析工具/README.md new file mode 100644 index 00000000..c551c646 --- /dev/null +++ b/docs/03-业务模块/ST-统计分析工具/README.md @@ -0,0 +1,82 @@ +# ST - 统计分析工具 + +> **模块代号:** ST (Statistical Tools) +> **开发状态:** ⏳ 规划中 +> **商业价值:** ⭐⭐⭐⭐ 高频 +> **独立性:** ⭐⭐⭐⭐ +> **优先级:** P2 + +--- + +## 📋 模块概述 + +统计分析工具提供100+种轻量化统计工具,满足即时、小型的分析需求。 + +--- + +## 🎯 核心功能 + +### 工具分类 + +**描述性统计:** +- 均值、中位数、标准差 +- 频数分布 +- 交叉表 + +**推断性统计:** +- t检验 +- 卡方检验 +- 方差分析(ANOVA) + +**相关与回归:** +- 相关分析 +- 线性回归 +- Logistic回归 + +**高级分析:** +- ROC曲线 +- 生存分析 +- Meta分析 + +--- + +## 📂 文档结构 + +``` +ST-统计分析工具/ + ├── [AI对接] ST快速上下文.md # ⏳ 待创建 + ├── 00-项目概述/ + │ └── 02-工具清单(100+).md # ⏳ 待创建 + └── README.md # ✅ 当前文档 +``` + +--- + +## 🔗 依赖的通用能力 + +- **文档处理引擎** - 数据导入 + +--- + +## 🎯 商业模式 + +**与SSA模块协同售卖** + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/03-业务模块/[AI对接] 业务模块快速上下文.md b/docs/03-业务模块/[AI对接] 业务模块快速上下文.md new file mode 100644 index 00000000..3fdb76e7 --- /dev/null +++ b/docs/03-业务模块/[AI对接] 业务模块快速上下文.md @@ -0,0 +1,173 @@ +# [AI对接] 业务模块快速上下文 + +> **阅读时间:** 2-3分钟 | **Token消耗:** ~1500 tokens +> **层级:** L1 | **前置阅读:** 00-系统总体设计/[AI对接] 快速上下文.md + +--- + +## 📋 业务模块层定位 + +**业务模块层是面向用户的产品功能,每个模块都是独立的产品单元。** + +**核心原则:** +- ✅ 独立部署(可以单独打包部署) +- ✅ 独立销售(可以单独售卖) +- ✅ 低耦合(模块间不直接依赖) +- ✅ 高内聚(模块内功能完整) + +--- + +## 🎯 8个业务模块 + +### 核心差异化模块(可独立售卖) + +#### 1. ASL - AI智能文献 ⭐⭐⭐⭐⭐ P0 + +**状态:** ⏳ 下一步开发(Week 2-4) + +**核心功能:** +- 标题摘要初筛(双模型AI判断) +- 全文复筛(PDF全文分析) +- 数据提取与分析 +- 系统评价与Meta分析 + +**商业价值:** 可独立售卖给系统评价研究者 +**独立性:** ⭐⭐⭐⭐⭐(极高) + +**依赖:** LLM网关、文档处理、RAG引擎 + +**快速上下文:** → `ASL-AI智能文献/[AI对接] ASL快速上下文.md` + +--- + +#### 2. DC - 数据清洗整理 ⭐⭐⭐⭐⭐ P1 + +**状态:** ⏳ 规划中 + +**核心功能:** +- 多表Excel自动JOIN(百万行级) +- 医学NER提取(病理报告、住院小结) +- 数据质量报告 + +**商业价值:** 可独立售卖给临床科室 +**独立性:** ⭐⭐⭐⭐⭐(极高) + +**技术难点:** 大数据处理、隐私保护、NER准确率 + +--- + +#### 3. RVW - 稿件审查系统 ⭐⭐⭐⭐⭐ P1 + +**状态:** ⚡ 核心功能已完成 + +**核心功能:** +- 稿约规范性评估(11项) +- 方法学评估(3部分) +- PDF报告导出 + +**商业价值:** 可独立售卖给期刊编辑部 +**独立性:** ⭐⭐⭐⭐⭐(极高,用户群完全不同) + +--- + +### 协同模块 + +#### 4. SSA - 智能统计分析 ⭐⭐⭐⭐⭐ P2 + +**核心功能:** +- 队列研究分析 +- 预测模型构建 +- RCT研究分析 + +**技术栈:** Node.js + R语言 + +--- + +#### 5. ST - 统计分析工具 ⭐⭐⭐⭐ P2 + +**核心功能:** 100+种轻量化统计工具 + +**协同:** 与SSA模块组合售卖 + +--- + +### 基础模块 + +#### 6. AIA - AI智能问答 ⭐⭐⭐⭐ 已完成 + +**核心功能:** +- 12个智能体(选题评价、PICO梳理等) +- 多轮对话、流式输出 +- @知识库问答 + +**状态:** ✅ 已完成 + +--- + +#### 7. PKB - 个人知识库 ⭐⭐⭐ 已完成 + +**核心功能:** +- 知识库CRUD +- 文档上传(PDF/Word/TXT) +- RAG问答、智能引用 + +**状态:** ✅ 已完成 + +--- + +### 管理模块 + +#### 8. ADMIN - 运营管理端 ⭐⭐⭐⭐⭐ P1 + +**核心功能(15个模块):** +- P0:用户管理、Feature Flag管理、LLM模型管理、系统配置 +- P1:Prompt管理、监控日志、成本分析、数据报表 +- P2:租户管理、公告管理、帮助文档等 + +**商业价值:** SaaS运营基础 + +--- + +## 📊 模块独立性分析 + +| 模块 | 独立性 | 商业价值 | 可独立销售 | +|------|-------|---------|-----------| +| **RVW** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 是(期刊) | +| **ASL** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 是(研究者) | +| **DC** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 是(科室) | +| **ADMIN** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 是(SaaS) | +| **SSA+ST** | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⚠️ 组合售卖 | +| **AIA+PKB** | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⚠️ 平台功能 | + +--- + +## 🔗 快速导航 + +**各模块详细文档:** +1. [ASL-AI智能文献](./ASL-AI智能文献/README.md) - P0,下一步开发 +2. [DC-数据清洗整理](./DC-数据清洗整理/README.md) - P1,核心竞争力 +3. [RVW-稿件审查系统](./RVW-稿件审查系统/README.md) - P1,独立系统 +4. [ADMIN-运营管理端](./ADMIN-运营管理端/README.md) - P1,商业基础 +5. [AIA-AI智能问答](./AIA-AI智能问答/README.md) - 已完成 +6. [PKB-个人知识库](./PKB-个人知识库/README.md) - 已完成 +7. [SSA-智能统计分析](./SSA-智能统计分析/README.md) - P2 +8. [ST-统计分析工具](./ST-统计分析工具/README.md) - P2 + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 + + + + + + + + + + + + + + diff --git a/docs/04-开发规范/01-数据库设计规范.md b/docs/04-开发规范/01-数据库设计规范.md new file mode 100644 index 00000000..817499eb --- /dev/null +++ b/docs/04-开发规范/01-数据库设计规范.md @@ -0,0 +1,497 @@ +# 数据库设计规范 + +> **版本:** v2.0 +> **最后更新:** 2025-11-06 +> **数据库:** PostgreSQL 15+ +> **ORM:** Prisma +> **适用范围:** 平台层 + 能力层 + 业务模块层 + +--- + +## 📋 核心原则 + +本规范是所有模块数据库设计的基础规范,必须严格遵守。 + +**设计原则:** +- ✅ 遵循第三范式(3NF) +- ✅ 使用SERIAL作为主键(整数自增,性能更好) +- ✅ 所有表包含created_at和updated_at时间戳 +- ✅ 重要表使用软删除(保留deleted_at字段) +- ✅ 外键约束使用ON DELETE CASCADE +- ✅ 敏感字段加密存储(密码使用bcrypt) + +--- + +## 🏗️ Schema隔离策略 ⭐ 最重要 + +### 为什么需要Schema隔离 + +**核心原因:** +1. ✅ **模块独立性**:每个业务模块有独立的Schema +2. ✅ **支持独立部署**:可以单独导出某个模块的数据 +3. ✅ **支持独立销售**:可以单独交付某个模块 +4. ✅ **权限隔离**:可以为不同Schema设置不同权限 +5. ✅ **避免命名冲突**:不同模块可以有相同的表名 + +### Schema命名规范 + +``` +platform_schema # 平台基础层(全局共享) +aia_schema # AI智能问答 +asl_schema # AI智能文献 +pkb_schema # 个人知识库 +dc_schema # 数据清洗整理 +ssa_schema # 智能统计分析 +st_schema # 统计分析工具 +rvw_schema # 稿件审查系统 +``` + +### Schema创建 + +```sql +-- 创建Schema +CREATE SCHEMA IF NOT EXISTS platform_schema; +CREATE SCHEMA IF NOT EXISTS asl_schema; +CREATE SCHEMA IF NOT EXISTS aia_schema; +-- ...其他Schema +``` + +### 跨Schema依赖规则 ⭐ 必须遵守 + +**允许的依赖:** +```sql +-- ✅ 允许:业务模块引用platform_schema +CREATE TABLE asl_schema.literature_projects ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES platform_schema.users(id) ON DELETE CASCADE, + ... +); + +-- ✅ 允许:通用能力引用platform_schema +CREATE TABLE platform_schema.llm_usage ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES platform_schema.users(id) ON DELETE CASCADE, + ... +); +``` + +**禁止的依赖:** +```sql +-- ❌ 禁止:业务模块之间互相引用 +CREATE TABLE ssa_schema.analysis_projects ( + id SERIAL PRIMARY KEY, + -- 错误!不能引用其他业务模块 + literature_project_id INTEGER REFERENCES asl_schema.literature_projects(id) +); + +-- ❌ 禁止:platform_schema反向依赖业务模块 +CREATE TABLE platform_schema.users ( + id SERIAL PRIMARY KEY, + -- 错误!platform_schema不能依赖业务模块 + current_project_id INTEGER REFERENCES asl_schema.literature_projects(id) +); +``` + +**正确做法:** +``` +跨模块数据关联在应用层处理,不在数据库层! + +方式1:通过user_id关联 +- 两个模块都引用platform_schema.users +- 在应用层通过user_id查询两个模块的数据 +- 在应用层组装数据 + +方式2:存储业务ID字符串 +- 在表中存储其他模块的业务ID(VARCHAR) +- 不建立外键关系 +- 在应用层验证ID的有效性 +``` + +--- + +## 📝 命名规范 + +### 表命名 + +**规则:** +- 小写字母 +- 下划线分隔 +- 复数形式 +- Schema前缀(查询时使用) + +**示例:** +```sql +-- ✅ 正确 +CREATE TABLE asl_schema.literature_projects (...); +CREATE TABLE platform_schema.users (...); +CREATE TABLE pkb_schema.knowledge_bases (...); + +-- ❌ 错误 +CREATE TABLE ASLSchema.LiteratureProject (...); -- 驼峰 +CREATE TABLE asl_schema.project (...); -- 单数 +CREATE TABLE literature-projects (...); -- 使用连字符 +``` + +### 字段命名 + +**规则:** +- 小写字母 +- 下划线分隔 +- 语义清晰 + +**示例:** +```sql +-- ✅ 正确 +user_id +created_at +project_name +is_active + +-- ❌ 错误 +userId -- 驼峰 +createdat -- 没有下划线 +prjName -- 缩写不清晰 +``` + +### 索引命名 + +**规则:** `idx_表名_字段名` + +**示例:** +```sql +-- ✅ 正确 +CREATE INDEX idx_users_email ON platform_schema.users(email); +CREATE INDEX idx_projects_user_id ON asl_schema.literature_projects(user_id); +CREATE INDEX idx_projects_user_status ON asl_schema.literature_projects(user_id, status); + +-- ❌ 错误 +CREATE INDEX user_email_idx ... -- 顺序错误 +CREATE INDEX index_on_email ... -- 名称不清晰 +``` + +### 外键命名 + +**规则:** `fk_表名_关联表名` + +**示例:** +```sql +-- ✅ 正确 +CONSTRAINT fk_projects_users + FOREIGN KEY (user_id) REFERENCES platform_schema.users(id); + +CONSTRAINT fk_items_projects + FOREIGN KEY (project_id) REFERENCES asl_schema.literature_projects(id); + +-- ❌ 错误 +CONSTRAINT user_fk ... -- 名称不清晰 +CONSTRAINT foreign_key_users ... -- 太长 +``` + +--- + +## 📊 通用字段 ⭐ 必须包含 + +### 所有表必须包含 + +```sql +CREATE TABLE xxx_schema.table_name ( + -- 主键(必须) + id SERIAL PRIMARY KEY, + + -- 业务字段 + ... + + -- 时间戳(必须) + created_at TIMESTAMP DEFAULT NOW() NOT NULL, + updated_at TIMESTAMP DEFAULT NOW() NOT NULL +); +``` + +### 重要表应包含(软删除) + +```sql +CREATE TABLE xxx_schema.important_table ( + id SERIAL PRIMARY KEY, + + -- 业务字段 + ... + + -- 软删除字段(可选,重要表建议添加) + deleted_at TIMESTAMP, + + -- 时间戳(必须) + created_at TIMESTAMP DEFAULT NOW() NOT NULL, + updated_at TIMESTAMP DEFAULT NOW() NOT NULL +); + +-- 查询时过滤已删除数据 +SELECT * FROM xxx_schema.important_table WHERE deleted_at IS NULL; +``` + +### 用户关联表必须包含 + +```sql +CREATE TABLE xxx_schema.user_related_table ( + id SERIAL PRIMARY KEY, + + -- 用户外键(必须) + user_id INTEGER REFERENCES platform_schema.users(id) ON DELETE CASCADE NOT NULL, + + -- 业务字段 + ... + + -- 时间戳(必须) + created_at TIMESTAMP DEFAULT NOW() NOT NULL, + updated_at TIMESTAMP DEFAULT NOW() NOT NULL +); +``` + +--- + +## 🔍 索引设计规范 + +### 必须添加索引的字段 + +**1. 主键** +```sql +-- 自动创建,无需手动添加 +id SERIAL PRIMARY KEY +``` + +**2. 外键字段** +```sql +-- 必须添加,提高JOIN性能 +CREATE INDEX idx_projects_user_id ON asl_schema.literature_projects(user_id); +``` + +**3. 常用查询字段** +```sql +-- status(状态字段,常用于WHERE) +CREATE INDEX idx_projects_status ON asl_schema.literature_projects(status); + +-- created_at(时间字段,常用于排序) +CREATE INDEX idx_projects_created_at ON asl_schema.literature_projects(created_at DESC); +``` + +**4. 唯一约束字段** +```sql +-- email等唯一字段 +CREATE UNIQUE INDEX idx_users_email ON platform_schema.users(email); +``` + +### 复合索引 + +**规则:** +- 高频组合查询使用复合索引 +- 最常查询的字段放在前面 +- 复合索引最多3个字段 + +**示例:** +```sql +-- ✅ 正确:user_id + status 组合查询 +CREATE INDEX idx_projects_user_status + ON asl_schema.literature_projects(user_id, status); + +-- 可以优化以下查询: +-- WHERE user_id = ? AND status = ? +-- WHERE user_id = ? (只用前缀) + +-- ❌ 错误:字段过多 +CREATE INDEX idx_projects_complex + ON asl_schema.literature_projects(user_id, status, created_at, updated_at); +``` + +--- + +## 🔗 外键约束规范 + +### ON DELETE策略 + +**规则:** +```sql +-- 用户删除时,级联删除关联数据 +FOREIGN KEY (user_id) + REFERENCES platform_schema.users(id) + ON DELETE CASCADE; + +-- 父记录删除时,级联删除子记录 +FOREIGN KEY (project_id) + REFERENCES asl_schema.literature_projects(id) + ON DELETE CASCADE; + +-- 特殊情况:不能删除 +FOREIGN KEY (parent_id) + REFERENCES xxx_schema.parent_table(id) + ON DELETE RESTRICT; -- 有子记录时禁止删除 +``` + +### 外键索引 + +**规则:** 所有外键必须添加索引 + +```sql +-- 创建外键 +ALTER TABLE asl_schema.literature_items + ADD CONSTRAINT fk_items_projects + FOREIGN KEY (project_id) REFERENCES asl_schema.literature_projects(id) + ON DELETE CASCADE; + +-- 必须添加索引 +CREATE INDEX idx_items_project_id ON asl_schema.literature_items(project_id); +``` + +--- + +## ⚡ 性能优化规范 + +### 大表分区(可选) + +**适用场景:** 年增长 > 100万记录 + +```sql +-- 按月分区(如llm_usage表) +CREATE TABLE platform_schema.llm_usage ( + id SERIAL, + user_id INTEGER NOT NULL, + created_at TIMESTAMP NOT NULL, + ... +) PARTITION BY RANGE (created_at); + +-- 创建分区 +CREATE TABLE platform_schema.llm_usage_2025_11 + PARTITION OF platform_schema.llm_usage + FOR VALUES FROM ('2025-11-01') TO ('2025-12-01'); +``` + +### 数据归档策略 + +```sql +-- 历史数据归档(如1年前的日志) +CREATE TABLE platform_schema.admin_logs_archive ( + LIKE platform_schema.admin_logs INCLUDING ALL +); + +-- 定期归档 +INSERT INTO platform_schema.admin_logs_archive +SELECT * FROM platform_schema.admin_logs +WHERE created_at < NOW() - INTERVAL '1 year'; + +DELETE FROM platform_schema.admin_logs +WHERE created_at < NOW() - INTERVAL '1 year'; +``` + +--- + +## 🔒 安全规范 + +### 敏感字段加密 + +```sql +-- 密码字段 +password VARCHAR(255) NOT NULL -- 使用bcrypt加密,存储哈希值 + +-- API Key字段 +api_key_encrypted TEXT NOT NULL -- 使用AES-256加密 + +-- 个人敏感信息 +phone_encrypted VARCHAR(255) -- 手机号加密 +id_card_encrypted VARCHAR(255) -- 身份证号加密 +``` + +### 数据脱敏 + +```sql +-- 日志中不记录敏感字段 +-- 开发/测试环境使用脱敏数据 +UPDATE platform_schema.users +SET + email = CONCAT('user', id, '@example.com'), + phone = '138****0000' +WHERE environment = 'development'; +``` + +--- + +## 📋 数据类型选择 + +### 常用字段类型 + +| 用途 | 推荐类型 | 说明 | +|------|---------|------| +| 主键 | SERIAL | 整数自增 | +| 外键 | INTEGER | 与主键类型一致 | +| 短文本 | VARCHAR(N) | N<500,如姓名、标题 | +| 长文本 | TEXT | 无长度限制,如描述、内容 | +| 布尔值 | BOOLEAN | true/false | +| 日期时间 | TIMESTAMP | 精确到毫秒 | +| 金额 | DECIMAL(10,2) | 避免精度问题 | +| JSON | JSONB | 支持索引,性能更好 | + +### 字段长度建议 + +```sql +-- 短文本 +name VARCHAR(100) -- 姓名 +title VARCHAR(200) -- 标题 +email VARCHAR(255) -- 邮箱 +phone VARCHAR(20) -- 手机号 + +-- 状态枚举 +status VARCHAR(20) -- active, inactive, deleted +role VARCHAR(20) -- user, admin + +-- 长文本 +description TEXT -- 描述 +content TEXT -- 内容 +``` + +--- + +## ✅ 检查清单 + +**设计新表时必须检查:** +- [ ] 表名符合命名规范(小写+下划线+复数) +- [ ] 使用正确的Schema(platform/aia/asl/pkb等) +- [ ] 包含id主键(SERIAL PRIMARY KEY) +- [ ] 包含created_at和updated_at时间戳 +- [ ] 用户关联表包含user_id外键 +- [ ] 所有外键都有ON DELETE策略 +- [ ] 所有外键都添加了索引 +- [ ] 常用查询字段添加了索引 +- [ ] 外键约束符合跨Schema依赖规则 +- [ ] 敏感字段已加密存储 + +--- + +## 🔗 相关文档 + +**总览:** +- [数据库全局视图](./03-数据库全局视图.md) ⭐ 查看所有Schema和表 + +**模板:** +- [数据库设计模板](../_templates/数据库设计-模板.md) + +**各模块设计:** +- [平台基础层](../01-平台基础层/README.md) +- [通用能力层](../02-通用能力层/README.md) +- [业务模块层](../03-业务模块/README.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 +**版本:** v2.0 + + + + + + + + + + + + + + diff --git a/docs/04-开发规范/02-API设计规范.md b/docs/04-开发规范/02-API设计规范.md new file mode 100644 index 00000000..3417d9c5 --- /dev/null +++ b/docs/04-开发规范/02-API设计规范.md @@ -0,0 +1,527 @@ +# API设计规范 + +> **版本:** v2.0 +> **最后更新:** 2025-11-06 +> **API风格:** RESTful API +> **基础URL:** `http://localhost:3001/api/v1` +> **适用范围:** 平台层 + 能力层 + 业务模块层 + +--- + +## 📋 设计原则 + +### API First原则 +- ✅ 先设计API,再实现功能 +- ✅ API是前后端的契约 +- ✅ API变更需要版本控制 +- ✅ 所有API都要有文档 + +### RESTful设计 +- ✅ 使用HTTP动词表示操作(GET、POST、PUT、DELETE) +- ✅ URL表示资源,不表示动作 +- ✅ 使用复数名词表示资源集合 +- ✅ 嵌套资源不超过2层 +- ✅ 使用HTTP状态码表示结果 + +### 命名规范 +- ✅ URL使用小写字母和连字符(kebab-case) +- ✅ 查询参数使用驼峰命名(camelCase) +- ✅ JSON字段使用驼峰命名(camelCase) + +--- + +## 🎯 URL设计规范 + +### 路径格式 + +``` +/api/v{version}/{module}/{resource}/{id?}/{action?} + +示例: +/api/v1/literature/projects # 获取文献项目列表 +/api/v1/literature/projects/123 # 获取ID=123的项目 +/api/v1/literature/projects/123/export # 导出项目(动作) +``` + +### 模块前缀 + +| 模块 | 路由前缀 | 示例 | +|------|---------|------| +| 认证 | `/auth` | `/api/v1/auth/login` | +| 用户 | `/users` | `/api/v1/users/me` | +| AI问答 | `/chat` | `/api/v1/chat/conversations` | +| 智能体 | `/agents` | `/api/v1/agents` | +| AI文献 | `/literature` | `/api/v1/literature/projects` | +| 知识库 | `/knowledge-bases` | `/api/v1/knowledge-bases` | +| 数据清洗 | `/data-cleaning` | `/api/v1/data-cleaning/projects` | +| 统计分析 | `/analysis` | `/api/v1/analysis/projects` | +| 统计工具 | `/tools` | `/api/v1/tools` | +| 稿件审查 | `/review` | `/api/v1/review/tasks` | +| LLM网关 | `/llm` | `/api/v1/llm/chat` | +| 管理端 | `/admin` | `/api/v1/admin/users` | + +### 资源命名 + +**✅ 正确示例:** +``` +GET /api/v1/literature/projects # 获取列表 +GET /api/v1/literature/projects/:id # 获取详情 +POST /api/v1/literature/projects # 创建 +PUT /api/v1/literature/projects/:id # 更新 +DELETE /api/v1/literature/projects/:id # 删除 + +GET /api/v1/literature/projects/:id/items # 获取项目下的文献 +POST /api/v1/literature/projects/:id/items/import # 导入文献 +POST /api/v1/literature/projects/:id/screening/execute # 执行筛选 +``` + +**❌ 错误示例:** +``` +POST /api/v1/literature/getProjects # 使用动词 +POST /api/v1/literature/createProject # 使用动词 +GET /api/v1/literature/project # 单数形式 +GET /api/v1/literatureProjectList # 驼峰命名 +``` + +--- + +## 🔧 HTTP方法规范 + +### 方法使用 + +| 方法 | 用途 | 是否幂等 | 是否安全 | +|------|------|---------|---------| +| **GET** | 获取资源 | ✅ | ✅ | +| **POST** | 创建资源 | ❌ | ❌ | +| **PUT** | 完整更新资源 | ✅ | ❌ | +| **PATCH** | 部分更新资源 | ❌ | ❌ | +| **DELETE** | 删除资源 | ✅ | ❌ | + +### 方法示例 + +```http +# 获取资源列表 +GET /api/v1/literature/projects + +# 获取单个资源 +GET /api/v1/literature/projects/123 + +# 创建资源 +POST /api/v1/literature/projects +Content-Type: application/json +{ "name": "新项目", "description": "..." } + +# 完整更新资源(所有字段) +PUT /api/v1/literature/projects/123 +Content-Type: application/json +{ "name": "更新", "description": "..." } + +# 部分更新资源(部分字段) +PATCH /api/v1/literature/projects/123 +Content-Type: application/json +{ "name": "只更新名称" } + +# 删除资源 +DELETE /api/v1/literature/projects/123 +``` + +--- + +## 📊 状态码规范 + +### 标准状态码 + +| 状态码 | 含义 | 使用场景 | +|--------|------|---------| +| **200** | OK | 成功返回数据 | +| **201** | Created | 成功创建资源 | +| **204** | No Content | 成功但无返回数据(如删除) | +| **400** | Bad Request | 请求参数错误 | +| **401** | Unauthorized | 未认证(没有Token或Token过期) | +| **403** | Forbidden | 无权限(已认证但权限不足) | +| **404** | Not Found | 资源不存在 | +| **409** | Conflict | 资源冲突(如重复创建) | +| **422** | Unprocessable Entity | 语义错误(如验证失败) | +| **429** | Too Many Requests | 请求过于频繁(限流) | +| **500** | Internal Server Error | 服务器错误 | + +### 状态码使用示例 + +```typescript +// 200 OK - 成功获取数据 +res.status(200).json({ success: true, data: projects }); + +// 201 Created - 成功创建资源 +res.status(201).json({ success: true, data: newProject }); + +// 204 No Content - 成功删除(无内容返回) +res.status(204).send(); + +// 400 Bad Request - 参数错误 +res.status(400).json({ + success: false, + error: { code: 'INVALID_PARAMS', message: '参数错误' } +}); + +// 401 Unauthorized - 未认证 +res.status(401).json({ + success: false, + error: { code: 'UNAUTHORIZED', message: '请先登录' } +}); + +// 403 Forbidden - 无权限 +res.status(403).json({ + success: false, + error: { code: 'FORBIDDEN', message: '无权访问' } +}); + +// 404 Not Found - 资源不存在 +res.status(404).json({ + success: false, + error: { code: 'NOT_FOUND', message: '资源不存在' } +}); + +// 422 Unprocessable Entity - 验证失败 +res.status(422).json({ + success: false, + error: { + code: 'VALIDATION_ERROR', + message: '参数验证失败', + details: [...] + } +}); +``` + +--- + +## 📝 响应格式规范 + +### 统一响应格式 ⭐ 必须遵守 + +**成功响应:** +```json +{ + "success": true, + "data": { + // 实际数据 + }, + "message": "操作成功" +} +``` + +**错误响应:** +```json +{ + "success": false, + "error": { + "code": "ERROR_CODE", + "message": "错误信息", + "details": [...] // 可选,详细错误信息 + } +} +``` + +### 列表数据响应 + +```json +{ + "success": true, + "data": { + "items": [ + { "id": 1, "name": "项目1" }, + { "id": 2, "name": "项目2" } + ], + "pagination": { + "page": 1, + "pageSize": 10, + "total": 100, + "totalPages": 10, + "hasNext": true, + "hasPrev": false + } + }, + "message": "获取成功" +} +``` + +### 验证错误响应 + +```json +{ + "success": false, + "error": { + "code": "VALIDATION_ERROR", + "message": "参数验证失败", + "details": [ + { + "field": "email", + "message": "邮箱格式不正确" + }, + { + "field": "password", + "message": "密码长度必须大于6位" + } + ] + } +} +``` + +--- + +## 🔑 认证与授权规范 + +### JWT认证 + +**1. 登录获取Token** +```http +POST /api/v1/auth/login +Content-Type: application/json + +{ + "email": "user@example.com", + "password": "password123" +} + +# Response +{ + "success": true, + "data": { + "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "expiresIn": 604800, // 7天(秒) + "user": { + "id": 123, + "email": "user@example.com", + "name": "张三", + "role": "user" + } + } +} +``` + +**2. 使用Token访问API** +```http +GET /api/v1/literature/projects +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +**3. Token过期处理** +```http +# Token过期,返回401 +{ + "success": false, + "error": { + "code": "TOKEN_EXPIRED", + "message": "Token已过期,请重新登录" + } +} + +# 使用refreshToken刷新 +POST /api/v1/auth/refresh +Content-Type: application/json + +{ + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} +``` + +### 权限控制 + +| 端点类型 | 需要认证 | 角色要求 | +|---------|---------|---------| +| 公开端点 | ❌ | 无 | +| 用户端点 | ✅ | user | +| 管理端点 | ✅ | admin | + +**示例:** +```typescript +// 公开端点(无需认证) +POST /api/v1/auth/login +POST /api/v1/auth/register + +// 用户端点(需要认证,user角色) +GET /api/v1/literature/projects +POST /api/v1/literature/projects + +// 管理端点(需要认证,admin角色) +GET /api/v1/admin/users +POST /api/v1/admin/users/:id/disable +``` + +--- + +## 📄 分页规范 + +### 请求参数 + +``` +GET /api/v1/literature/projects?page=1&pageSize=20&sortBy=createdAt&sortOrder=desc + +Query参数: +- page: 页码(默认1) +- pageSize: 每页数量(默认10,最大100) +- sortBy: 排序字段(默认createdAt) +- sortOrder: 排序方向(asc/desc,默认desc) +``` + +### 响应格式 + +```json +{ + "success": true, + "data": { + "items": [...], + "pagination": { + "page": 1, + "pageSize": 20, + "total": 100, + "totalPages": 5, + "hasNext": true, + "hasPrev": false + } + } +} +``` + +--- + +## 🔍 筛选与搜索规范 + +### 筛选参数 + +``` +GET /api/v1/literature/projects?status=active&keyword=骨质疏松 + +Query参数: +- status: 状态筛选(active/inactive) +- keyword: 关键词搜索(搜索name和description字段) +- userId: 用户ID筛选(管理端) +- startDate/endDate: 日期范围筛选 +``` + +### 搜索响应 + +```json +{ + "success": true, + "data": { + "items": [...], + "pagination": {...}, + "filters": { + "status": "active", + "keyword": "骨质疏松" + } + } +} +``` + +--- + +## ⚠️ 错误码设计 + +### 标准错误码 + +| 错误码 | HTTP状态 | 说明 | +|--------|---------|------| +| `VALIDATION_ERROR` | 422 | 参数验证失败 | +| `UNAUTHORIZED` | 401 | 未认证 | +| `TOKEN_EXPIRED` | 401 | Token过期 | +| `FORBIDDEN` | 403 | 无权限 | +| `NOT_FOUND` | 404 | 资源不存在 | +| `ALREADY_EXISTS` | 409 | 资源已存在 | +| `QUOTA_EXCEEDED` | 429 | 配额超限 | +| `INTERNAL_ERROR` | 500 | 服务器错误 | + +### 业务错误码 + +```typescript +// 模块特定错误码(前缀区分) +ASL_IMPORT_FAILED // AI文献:导入失败 +ASL_SCREENING_IN_PROGRESS // AI文献:筛选进行中 +PKB_QUOTA_EXCEEDED // 知识库:配额超限 +LLM_QUOTA_EXCEEDED // LLM:配额超限 +``` + +--- + +## 🚀 性能优化规范 + +### 1. 分页必须 + +``` +所有列表接口必须支持分页: +- 默认pageSize=10 +- 最大pageSize=100 +- 禁止一次性返回全部数据 +``` + +### 2. 字段过滤 + +``` +GET /api/v1/users/me?fields=id,name,email + +只返回需要的字段,减少数据传输 +``` + +### 3. 缓存策略 + +```http +# 设置缓存头 +Cache-Control: public, max-age=300 + +# 使用ETag +ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" +If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4" +# 返回304 Not Modified +``` + +--- + +## ✅ 检查清单 + +**设计新API时必须检查:** +- [ ] URL符合RESTful规范(使用名词+复数) +- [ ] 使用正确的HTTP方法(GET/POST/PUT/DELETE) +- [ ] 使用正确的HTTP状态码 +- [ ] 响应格式符合统一规范(success + data/error) +- [ ] 需要认证的接口检查JWT Token +- [ ] 需要权限的接口检查用户角色 +- [ ] 列表接口支持分页 +- [ ] 列表接口支持排序 +- [ ] 错误响应包含明确的错误码和错误信息 +- [ ] 所有API都有文档说明 + +--- + +## 🔗 相关文档 + +**总览:** +- [API路由总览](./04-API路由总览.md) ⭐ 查看所有API端点 + +**模板:** +- [API设计模板](../_templates/API设计-模板.md) + +**各模块设计:** +- [平台基础层](../01-平台基础层/README.md) +- [通用能力层](../02-通用能力层/README.md) +- [业务模块层](../03-业务模块/README.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 +**版本:** v2.0 + + + + + + + + + + + + + + diff --git a/docs/04-开发规范/03-数据库全局视图.md b/docs/04-开发规范/03-数据库全局视图.md new file mode 100644 index 00000000..883e346f --- /dev/null +++ b/docs/04-开发规范/03-数据库全局视图.md @@ -0,0 +1,349 @@ +# 数据库全局视图 + +> **目的:** 提供所有Schema和表的快速索引,便于查找和理解全局数据架构 +> **详细设计:** 请查看各模块的 `01-数据库设计.md` +> **数据库:** PostgreSQL 15+ +> **最后更新:** 2025-11-06 + +--- + +## 📊 Schema划分策略 + +### Schema隔离原则 ⭐ + +**为什么需要Schema隔离:** +1. ✅ **模块独立性**:每个业务模块有独立的Schema +2. ✅ **支持独立部署**:可以单独导出某个模块的数据 +3. ✅ **权限隔离**:可以为不同Schema设置不同权限 +4. ✅ **避免命名冲突**:不同模块可以有相同的表名 + +**Schema命名规范:** +``` +platform_schema # 平台基础层(全局共享) +aia_schema # AI智能问答 +asl_schema # AI智能文献 +pkb_schema # 个人知识库 +dc_schema # 数据清洗整理 +ssa_schema # 智能统计分析 +st_schema # 统计分析工具 +rvw_schema # 稿件审查系统 +admin_schema # 运营管理端(可选,可合并到platform_schema) +``` + +--- + +## 📋 Schema一览表 + +| Schema | 说明 | 表数量 | 状态 | 详细设计 | +|--------|------|--------|------|---------| +| **platform_schema** | 平台基础层 | ~15个 | ✅ 使用中 | [查看](#platform_schema-平台基础层) | +| **aia_schema** | AI智能问答 | ~8个 | ✅ 使用中 | [查看](#aia_schema-ai智能问答) | +| **pkb_schema** | 个人知识库 | ~5个 | ✅ 使用中 | [查看](#pkb_schema-个人知识库) | +| **rvw_schema** | 稿件审查系统 | ~6个 | ✅ 使用中 | [查看](#rvw_schema-稿件审查系统) | +| **asl_schema** | AI智能文献 | ~10个 | ⏳ 设计中 | [ASL/01-数据库设计](../03-业务模块/ASL-AI智能文献/01-数据库设计.md) | +| **dc_schema** | 数据清洗整理 | ~8个 | ⏳ 规划中 | 待设计 | +| **ssa_schema** | 智能统计分析 | ~10个 | ⏳ 规划中 | 待设计 | +| **st_schema** | 统计分析工具 | ~5个 | ⏳ 规划中 | 待设计 | + +**总表数:** ~70个(预估) + +--- + +## 🔍 platform_schema(平台基础层) + +**职责:** 存储全局共享的平台数据,所有业务模块都依赖 + +**详细设计:** [UAM/01-数据库设计](../01-平台基础层/01-用户与权限中心(UAM)/01-数据库设计.md) + +### 核心表(用户与权限) + +| 表名 | 说明 | 记录数预估 | 详细设计 | +|------|------|-----------|---------| +| **users** | 用户基础信息 | 10万/年 | [UAM/01-数据库设计](../01-平台基础层/01-用户与权限中心(UAM)/01-数据库设计.md) | +| **roles** | 角色定义 | <100 | 同上 | +| **permissions** | 权限定义 | <500 | 同上 | +| **user_roles** | 用户-角色关联 | 10万/年 | 同上 | +| **feature_flags** | Feature Flag配置 ⭐ | <100 | 同上 | +| **user_feature_flags** | 用户-Feature Flag关联 ⭐ | 10万/年 | 同上 | + +### LLM相关表 + +| 表名 | 说明 | 记录数预估 | 详细设计 | +|------|------|-----------|---------| +| **llm_models** | LLM模型配置 | <20 | [LLM网关/01-数据库设计](../02-通用能力层/01-LLM大模型网关/01-数据库设计.md) | +| **llm_usage** | LLM使用记录 ⭐ | 1000万/年 | 同上 | +| **llm_quotas** | LLM配额管理 | 10万/年 | 同上 | + +### 监控与日志 + +| 表名 | 说明 | 记录数预估 | 详细设计 | +|------|------|-----------|---------| +| **admin_logs** | 管理员操作日志 | 10万/年 | [监控与日志/01-数据库设计](../01-平台基础层/04-监控与日志/01-数据库设计.md) | +| **error_logs** | 错误日志 | 100万/年 | 同上 | +| **audit_logs** | 审计日志 | 100万/年 | 同上 | + +### 系统配置 + +| 表名 | 说明 | 记录数预估 | 详细设计 | +|------|------|-----------|---------| +| **system_configs** | 系统配置 | <100 | [系统配置/01-数据库设计](../01-平台基础层/05-系统配置/01-数据库设计.md) | +| **prompt_templates** | Prompt模板 | <500 | 同上 | +| **announcements** | 系统公告 | <1000 | 同上 | + +--- + +## 🤖 aia_schema(AI智能问答) + +**职责:** 存储AI智能问答相关数据(12个智能体、对话历史) + +**状态:** ✅ 已实现 +**详细设计:** [AIA/01-数据库设计](../03-业务模块/AIA-AI智能问答/01-数据库设计.md)(待创建) + +### 核心表 + +| 表名 | 说明 | 记录数预估 | +|------|------|-----------| +| **conversations** | 对话会话 | 100万/年 | +| **messages** | 对话消息 | 1000万/年 | +| **agents** | 智能体配置 | <20 | +| **conversation_contexts** | 对话上下文 | 100万/年 | + +--- + +## 📚 pkb_schema(个人知识库) + +**职责:** 存储个人知识库、文档、RAG问答相关数据 + +**状态:** ✅ 已实现 +**详细设计:** [PKB/01-数据库设计](../03-业务模块/PKB-个人知识库/01-数据库设计.md)(待创建) + +### 核心表 + +| 表名 | 说明 | 记录数预估 | +|------|------|-----------| +| **knowledge_bases** | 知识库 | 30万/年 | +| **documents** | 文档 | 300万/年 | +| **document_chunks** | 文档分块(向量化) | 3000万/年 | +| **kb_conversations** | 知识库对话 | 100万/年 | +| **kb_messages** | 知识库对话消息 | 1000万/年 | + +--- + +## 📄 rvw_schema(稿件审查系统) + +**职责:** 存储稿件审查、评估报告相关数据 + +**状态:** ✅ 已实现(独立系统) +**详细设计:** [RVW/01-数据库设计](../03-业务模块/RVW-稿件审查系统/01-数据库设计.md)(待创建) + +### 核心表 + +| 表名 | 说明 | 记录数预估 | +|------|------|-----------| +| **review_tasks** | 审查任务 | 10万/年 | +| **manuscripts** | 稿件信息 | 10万/年 | +| **review_results** | 审查结果 | 10万/年 | +| **methodology_assessments** | 方法学评估 | 10万/年 | +| **guideline_assessments** | 稿约规范性评估 | 10万/年 | + +--- + +## 📖 asl_schema(AI智能文献) + +**职责:** 存储文献筛选、提取、分析相关数据 + +**状态:** ⏳ 设计中(P0优先级) +**详细设计:** [ASL/01-数据库设计](../03-业务模块/ASL-AI智能文献/01-数据库设计.md) + +### 核心表(预览) + +| 表名 | 说明 | 记录数预估 | +|------|------|-----------| +| **literature_projects** | 文献项目 | 10万/年 | +| **literature_items** | 文献条目 | 1000万/年 | +| **pico_configs** | PICO纳入排除标准 | 10万/年 | +| **screening_results** | 筛选结果 | 1000万/年 | +| **screening_history** | 筛选历史(可回溯) | 1000万/年 | +| **extraction_tasks** | 提取任务 | 100万/年 | +| **extraction_results** | 提取结果 | 100万/年 | + +--- + +## 🧹 dc_schema(数据清洗整理) + +**职责:** 存储数据清洗任务、ETL配置、NER结果 + +**状态:** ⏳ 规划中(P1优先级) +**详细设计:** 待设计 + +### 核心表(预览) + +| 表名 | 说明 | 记录数预估 | +|------|------|-----------| +| **cleaning_projects** | 清洗项目 | 10万/年 | +| **data_sources** | 数据源 | 100万/年 | +| **etl_configs** | ETL配置 | 10万/年 | +| **ner_tasks** | NER任务 | 100万/年 | +| **ner_results** | NER结果 | 1000万/年 | + +--- + +## 🔗 跨Schema依赖关系 + +### 依赖规则 ⭐ 重要 + +**允许的依赖:** +``` +✅ 业务模块 → platform_schema(允许外键) +✅ 通用能力 → platform_schema(允许外键) +❌ 业务模块之间(禁止直接依赖) +❌ platform_schema → 业务模块(反向依赖) +``` + +### 依赖关系图 + +``` +platform_schema.users (1) + ↓ (N) 所有业务模块都依赖用户表 + ├── aia_schema.conversations + ├── asl_schema.literature_projects + ├── pkb_schema.knowledge_bases + ├── dc_schema.cleaning_projects + ├── ssa_schema.analysis_projects + ├── st_schema.tool_usage + └── rvw_schema.review_tasks + +platform_schema.llm_usage (独立) + - 记录所有模块的LLM调用 + - 通过module字段区分:'AIA', 'ASL', 'PKB'等 +``` + +### 外键示例 + +```sql +-- ✅ 允许:业务模块引用platform_schema +CREATE TABLE asl_schema.literature_projects ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES platform_schema.users(id) ON DELETE CASCADE +); + +-- ❌ 禁止:业务模块之间互相引用 +CREATE TABLE ssa_schema.analysis_projects ( + id SERIAL PRIMARY KEY, + -- 错误:不能引用其他业务模块 + literature_project_id INTEGER REFERENCES asl_schema.literature_projects(id) +); + +-- ✅ 正确做法:通过user_id关联 +-- 在应用层处理跨模块关联,不在数据库层 +``` + +--- + +## 📊 数据量统计(预估) + +### 按Schema统计 + +| Schema | 表数量 | 年增长记录数 | 存储预估(5年) | +|--------|--------|------------|---------------| +| platform_schema | 15 | 1000万 | 50GB | +| aia_schema | 8 | 1100万 | 30GB | +| pkb_schema | 5 | 3300万 | 200GB(向量) | +| rvw_schema | 6 | 50万 | 5GB | +| asl_schema | 10 | 2100万 | 50GB | +| dc_schema | 8 | 1100万 | 100GB | +| ssa_schema | 10 | 500万 | 50GB | +| st_schema | 5 | 100万 | 10GB | +| **总计** | **~70** | **~4000万/年** | **~500GB(5年)** | + +### 大表监控(年增长>100万) + +| 表名 | Schema | 年增长 | 索引策略 | +|------|--------|--------|---------| +| llm_usage | platform | 1000万 | 按月分区 | +| messages | aia | 1000万 | 按created_at索引 | +| document_chunks | pkb | 3000万 | 向量索引 | +| literature_items | asl | 1000万 | 按project_id索引 | +| screening_results | asl | 1000万 | 复合索引 | + +--- + +## 🔍 快速查找指南 + +### 场景1:我要开发某个模块 +1. 在上面的表格中找到对应的Schema +2. 点击"详细设计"链接 +3. 查看该模块的完整表结构 + +### 场景2:我要查看某个表的结构 +1. 先确定表属于哪个Schema(根据功能判断) +2. 转到对应模块的数据库设计文档 +3. 搜索表名 + +### 场景3:我要设计跨模块功能 +1. 查看本文档的"跨Schema依赖关系" +2. 遵循依赖规则 +3. 在应用层处理跨模块关联,不在数据库层 + +### 场景4:我要查看全局数据架构 +1. 阅读本文档(快速了解所有Schema) +2. 查看[架构设计全景图](../00-系统总体设计/08-架构设计全景图.md) + +--- + +## ⚠️ 重要提醒 + +### Schema隔离的注意事项 + +**✅ 正确做法:** +- 业务模块只引用 `platform_schema.users` +- 跨模块数据关联在应用层处理 +- 使用 `user_id + 业务ID` 的方式 + +**❌ 错误做法:** +- 业务模块之间直接外键关联 +- 在 `platform_schema` 中存储业务数据 +- 不同模块使用相同的表名(虽然Schema隔离了,但容易混淆) + +### 性能优化建议 + +1. **大表必须分页查询**(如 `llm_usage`、`messages`) +2. **热点字段必须加索引**(如 `user_id`、`created_at`) +3. **考虑表分区**(按月/按年,如 `llm_usage`) +4. **定期归档历史数据**(如1年前的日志) + +--- + +## 🔗 相关文档 + +**规范:** +- [数据库设计规范](./01-数据库设计规范.md) ⭐ 必读 +- [数据库架构说明](../00-系统总体设计/03-数据库架构说明.md) + +**模块设计:** +- [平台基础层](../01-平台基础层/README.md) +- [通用能力层](../02-通用能力层/README.md) +- [业务模块层](../03-业务模块/README.md) + +**模板:** +- [数据库设计模板](../_templates/数据库设计-模板.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 +**版本:** v1.0 + + + + + + + + + + + + + + diff --git a/docs/04-开发规范/04-API路由总览.md b/docs/04-开发规范/04-API路由总览.md new file mode 100644 index 00000000..2890f946 --- /dev/null +++ b/docs/04-开发规范/04-API路由总览.md @@ -0,0 +1,393 @@ +# API路由总览 + +> **目的:** 提供所有API端点的快速索引,便于查找和避免路由冲突 +> **详细设计:** 请查看各模块的 `02-API设计.md` +> **基础URL:** `http://localhost:3001/api/v1` +> **最后更新:** 2025-11-06 + +--- + +## 📋 路由命名规范 + +### 路径格式 +``` +/api/v{version}/{module}/{resource}/{id?}/{action?} + +示例: +/api/v1/literature/projects # 获取文献项目列表 +/api/v1/literature/projects/123 # 获取ID=123的项目 +/api/v1/literature/projects/123/export # 导出项目 +``` + +### 模块名称规范 + +| 模块代码 | 路由前缀 | 说明 | +|---------|---------|------| +| 平台基础层 | `/auth`, `/users`, `/admin` | 认证、用户、管理 | +| LLM网关 | `/llm` | LLM调用 | +| AIA | `/chat`, `/agents` | AI智能问答 | +| ASL | `/literature` | AI智能文献 | +| PKB | `/knowledge-bases`, `/kb` | 个人知识库 | +| DC | `/data-cleaning` | 数据清洗 | +| SSA | `/analysis` | 智能统计分析 | +| ST | `/tools` | 统计工具 | +| RVW | `/review` | 稿件审查 | +| ADMIN | `/admin` | 运营管理端 | + +--- + +## 🔐 认证与用户管理(/api/v1/auth, /api/v1/users) + +**状态:** ✅ 已实现 +**详细设计:** [UAM/02-API设计](../01-平台基础层/01-用户与权限中心(UAM)/02-API设计.md)(待创建) + +### 认证相关 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/auth/register` | POST | 用户注册 | ❌ | +| `/api/v1/auth/login` | POST | 用户登录 | ❌ | +| `/api/v1/auth/logout` | POST | 用户登出 | ✅ | +| `/api/v1/auth/refresh` | POST | 刷新Token | ✅ | +| `/api/v1/auth/profile` | GET | 获取当前用户信息 | ✅ | +| `/api/v1/auth/profile` | PUT | 更新当前用户信息 | ✅ | + +### 用户管理 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/users` | GET | 用户列表(分页) | ✅ ADMIN | +| `/api/v1/users/:id` | GET | 用户详情 | ✅ ADMIN | +| `/api/v1/users/:id` | PUT | 更新用户 | ✅ ADMIN | +| `/api/v1/users/:id` | DELETE | 删除用户 | ✅ ADMIN | + +--- + +## 🤖 LLM大模型网关(/api/v1/llm) + +**状态:** ❌ 待实现(P0优先级) +**详细设计:** [LLM网关/02-API设计](../02-通用能力层/01-LLM大模型网关/02-API设计.md) + +### LLM调用 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/llm/chat` | POST | LLM对话(非流式) | ✅ | +| `/api/v1/llm/chat/stream` | POST | LLM对话(流式SSE) | ✅ | +| `/api/v1/llm/quota` | GET | 查询当前用户配额 | ✅ | +| `/api/v1/llm/usage` | GET | 查询使用统计 | ✅ | +| `/api/v1/llm/models` | GET | 获取可用模型列表 | ✅ | + +--- + +## 💬 AI智能问答(/api/v1/chat, /api/v1/agents) + +**状态:** ✅ 已实现 +**详细设计:** [AIA/02-API设计](../03-业务模块/AIA-AI智能问答/02-API设计.md)(待创建) + +### 对话管理 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/chat/conversations` | GET | 对话列表 | ✅ | +| `/api/v1/chat/conversations` | POST | 创建对话 | ✅ | +| `/api/v1/chat/conversations/:id` | GET | 对话详情 | ✅ | +| `/api/v1/chat/conversations/:id` | DELETE | 删除对话 | ✅ | +| `/api/v1/chat/conversations/:id/messages` | GET | 对话消息列表 | ✅ | +| `/api/v1/chat/conversations/:id/messages` | POST | 发送消息 | ✅ | + +### 智能体管理 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/agents` | GET | 智能体列表(12个) | ✅ | +| `/api/v1/agents/:id` | GET | 智能体详情 | ✅ | + +--- + +## 📖 AI智能文献(/api/v1/literature) + +**状态:** ⏳ 设计中(P0优先级) +**详细设计:** [ASL/02-API设计](../03-业务模块/ASL-AI智能文献/02-API设计.md) + +### 项目管理 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/literature/projects` | GET | 文献项目列表 | ✅ | +| `/api/v1/literature/projects` | POST | 创建文献项目 | ✅ | +| `/api/v1/literature/projects/:id` | GET | 项目详情 | ✅ | +| `/api/v1/literature/projects/:id` | PUT | 更新项目 | ✅ | +| `/api/v1/literature/projects/:id` | DELETE | 删除项目 | ✅ | + +### 文献筛选(标题摘要初筛)⭐ + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/literature/projects/:id/items/import` | POST | 导入CSV文件 | ✅ | +| `/api/v1/literature/projects/:id/pico` | POST | 配置PICO标准 | ✅ | +| `/api/v1/literature/projects/:id/pico` | GET | 获取PICO配置 | ✅ | +| `/api/v1/literature/projects/:id/screening/title` | POST | 执行标题摘要初筛 | ✅ | +| `/api/v1/literature/projects/:id/screening/status` | GET | 查询筛选进度 | ✅ | +| `/api/v1/literature/projects/:id/screening/results` | GET | 获取筛选结果 | ✅ | +| `/api/v1/literature/projects/:id/screening/export` | POST | 导出Excel | ✅ | + +### 文献筛选(全文复筛) + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/literature/projects/:id/screening/fulltext` | POST | 执行全文筛选 | ✅ | +| `/api/v1/literature/projects/:id/screening/fulltext/status` | GET | 查询进度 | ✅ | + +--- + +## 📚 个人知识库(/api/v1/knowledge-bases) + +**状态:** ✅ 已实现 +**详细设计:** [PKB/02-API设计](../03-业务模块/PKB-个人知识库/02-API设计.md)(待创建) + +### 知识库管理 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/knowledge-bases` | GET | 知识库列表 | ✅ | +| `/api/v1/knowledge-bases` | POST | 创建知识库 | ✅ | +| `/api/v1/knowledge-bases/:id` | GET | 知识库详情 | ✅ | +| `/api/v1/knowledge-bases/:id` | PUT | 更新知识库 | ✅ | +| `/api/v1/knowledge-bases/:id` | DELETE | 删除知识库 | ✅ | + +### 文档管理 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/knowledge-bases/:id/documents` | GET | 文档列表 | ✅ | +| `/api/v1/knowledge-bases/:id/documents` | POST | 上传文档 | ✅ | +| `/api/v1/knowledge-bases/:id/documents/:docId` | GET | 文档详情 | ✅ | +| `/api/v1/knowledge-bases/:id/documents/:docId` | DELETE | 删除文档 | ✅ | + +### RAG问答 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/knowledge-bases/:id/chat` | POST | 知识库问答 | ✅ | +| `/api/v1/knowledge-bases/:id/search` | GET | 语义检索 | ✅ | + +--- + +## 🧹 数据清洗整理(/api/v1/data-cleaning) + +**状态:** ⏳ 规划中(P1优先级) +**详细设计:** 待设计 + +### 清洗项目 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/data-cleaning/projects` | GET | 清洗项目列表 | ✅ | +| `/api/v1/data-cleaning/projects` | POST | 创建清洗项目 | ✅ | +| `/api/v1/data-cleaning/projects/:id` | GET | 项目详情 | ✅ | + +### ETL配置 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/data-cleaning/projects/:id/etl` | POST | 配置ETL规则 | ✅ | +| `/api/v1/data-cleaning/projects/:id/execute` | POST | 执行清洗任务 | ✅ | + +--- + +## 📊 智能统计分析(/api/v1/analysis) + +**状态:** ⏳ 规划中(P2优先级) +**详细设计:** 待设计 + +### 分析项目 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/analysis/projects` | GET | 分析项目列表 | ✅ | +| `/api/v1/analysis/projects` | POST | 创建分析项目 | ✅ | +| `/api/v1/analysis/projects/:id/execute` | POST | 执行分析 | ✅ | + +--- + +## 🔧 统计分析工具(/api/v1/tools) + +**状态:** ⏳ 规划中(P2优先级) +**详细设计:** 待设计 + +### 工具调用 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/tools` | GET | 工具列表(100+) | ✅ | +| `/api/v1/tools/:id` | GET | 工具详情 | ✅ | +| `/api/v1/tools/:id/execute` | POST | 执行工具 | ✅ | + +--- + +## 📄 稿件审查系统(/api/v1/review) + +**状态:** ✅ 已实现(独立系统) +**详细设计:** [RVW/02-API设计](../03-业务模块/RVW-稿件审查系统/02-API设计.md)(待创建) + +### 审查任务 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/review/tasks` | GET | 审查任务列表 | ✅ | +| `/api/v1/review/tasks` | POST | 创建审查任务 | ✅ | +| `/api/v1/review/tasks/:id` | GET | 任务详情 | ✅ | +| `/api/v1/review/tasks/:id/execute` | POST | 执行审查 | ✅ | +| `/api/v1/review/tasks/:id/report` | GET | 生成报告(PDF) | ✅ | + +--- + +## 🛠️ 运营管理端(/api/v1/admin) + +**状态:** ⏳ 规划中(P1优先级) +**详细设计:** [ADMIN/02-API设计](../03-业务模块/ADMIN-运营管理端/02-API设计.md) + +### 用户管理 ⭐ + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/admin/users` | GET | 用户列表 | ✅ ADMIN | +| `/api/v1/admin/users/:id` | GET | 用户详情 | ✅ ADMIN | +| `/api/v1/admin/users/:id` | PUT | 更新用户 | ✅ ADMIN | +| `/api/v1/admin/users/:id/plan` | PUT | 修改套餐 | ✅ ADMIN | +| `/api/v1/admin/users/:id/disable` | POST | 禁用用户 | ✅ ADMIN | + +### Feature Flag管理 ⭐ + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/admin/feature-flags` | GET | Flag列表 | ✅ ADMIN | +| `/api/v1/admin/feature-flags` | POST | 创建Flag | ✅ ADMIN | +| `/api/v1/admin/feature-flags/:id` | PUT | 更新Flag | ✅ ADMIN | +| `/api/v1/admin/users/:id/flags` | GET | 用户Flag | ✅ ADMIN | +| `/api/v1/admin/users/:id/flags` | PUT | 更新用户Flag | ✅ ADMIN | + +### LLM模型管理 ⭐ + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/admin/llm/models` | GET | 模型列表 | ✅ ADMIN | +| `/api/v1/admin/llm/models` | POST | 添加模型 | ✅ ADMIN | +| `/api/v1/admin/llm/models/:id` | PUT | 更新模型 | ✅ ADMIN | +| `/api/v1/admin/llm/usage` | GET | LLM使用统计 | ✅ ADMIN | +| `/api/v1/admin/llm/cost-analysis` | GET | 成本分析 | ✅ ADMIN | + +### Prompt管理 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/admin/prompts` | GET | Prompt列表 | ✅ ADMIN | +| `/api/v1/admin/prompts` | POST | 创建Prompt | ✅ ADMIN | +| `/api/v1/admin/prompts/:id` | PUT | 更新Prompt | ✅ ADMIN | + +### 日志查询 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/admin/logs` | GET | 日志列表 | ✅ ADMIN | +| `/api/v1/admin/logs/errors` | GET | 错误日志 | ✅ ADMIN | +| `/api/v1/admin/logs/operations` | GET | 操作日志 | ✅ ADMIN | + +### 数据报表 + +| 端点 | 方法 | 说明 | 认证 | +|------|------|------|------| +| `/api/v1/admin/reports/overview` | GET | 总览数据 | ✅ ADMIN | +| `/api/v1/admin/reports/users` | GET | 用户活跃度 | ✅ ADMIN | +| `/api/v1/admin/reports/features` | GET | 功能使用率 | ✅ ADMIN | +| `/api/v1/admin/reports/llm` | GET | LLM统计 | ✅ ADMIN | + +--- + +## 📊 路由统计 + +### 按模块统计 + +| 模块 | 端点数量 | 状态 | +|------|---------|------| +| 认证与用户 | 10 | ✅ 已实现 | +| LLM网关 | 5 | ❌ 待实现 | +| AI智能问答 | 8 | ✅ 已实现 | +| AI智能文献 | 15 | ⏳ 设计中 | +| 个人知识库 | 10 | ✅ 已实现 | +| 数据清洗 | 5 | ⏳ 规划中 | +| 智能统计分析 | 3 | ⏳ 规划中 | +| 统计工具 | 3 | ⏳ 规划中 | +| 稿件审查 | 5 | ✅ 已实现 | +| 运营管理端 | 20 | ⏳ 规划中 | +| **总计** | **~85** | - | + +--- + +## ⚠️ 路由冲突检查 + +### 潜在冲突 + +**❌ 避免冲突:** +``` +# 错误:模块名称冲突 +/api/v1/admin/users # 管理端的用户管理 +/api/v1/users # 平台层的用户管理 + +# 正确:明确区分 +/api/v1/auth/profile # 当前用户信息 +/api/v1/admin/users # 管理端用户CRUD +``` + +--- + +## 🔍 快速查找指南 + +### 场景1:查找某个模块的所有API +1. 在上面的表格中找到对应模块 +2. 点击"详细设计"链接 +3. 查看该模块的完整API文档 + +### 场景2:设计新API端点 +1. 查看本文档,确保路由不冲突 +2. 参考[API设计规范](./02-API设计规范.md) +3. 使用[API设计模板](../_templates/API设计-模板.md) + +### 场景3:查看全局API架构 +1. 阅读本文档(快速了解所有端点) +2. 查看[架构设计全景图](../00-系统总体设计/08-架构设计全景图.md) + +--- + +## 🔗 相关文档 + +**规范:** +- [API设计规范](./02-API设计规范.md) ⭐ 必读 +- [认证与授权规范](./02-API设计规范.md#认证与授权) + +**模板:** +- [API设计模板](../_templates/API设计-模板.md) + +**数据库:** +- [数据库全局视图](./03-数据库全局视图.md) + +--- + +**最后更新:** 2025-11-06 +**维护人:** 技术架构师 +**版本:** v1.0 + + + + + + + + + + + + + + diff --git a/docs/02-开发规范/代码规范.md b/docs/04-开发规范/05-代码规范.md similarity index 99% rename from docs/02-开发规范/代码规范.md rename to docs/04-开发规范/05-代码规范.md index 731ded89..3ed0f574 100644 --- a/docs/02-开发规范/代码规范.md +++ b/docs/04-开发规范/05-代码规范.md @@ -815,3 +815,13 @@ module.exports = { + + + + + + + + + + diff --git a/docs/04-开发规范/README.md b/docs/04-开发规范/README.md new file mode 100644 index 00000000..63550b7f --- /dev/null +++ b/docs/04-开发规范/README.md @@ -0,0 +1,228 @@ +# 04-开发规范 + +> **目标:** 统一团队开发规范,提高代码质量和协作效率 +> **适用范围:** 平台层 + 能力层 + 业务模块层 +> **强制等级:** ⭐⭐⭐⭐⭐ 必须遵守 + +--- + +## 📋 规范文档列表 + +### 1. 数据库设计规范 ⭐⭐⭐⭐⭐ +**文件:** `01-数据库设计规范.md` ⏳ 待创建(从 `01-设计文档/数据库设计文档.md` 提取) + +**核心内容:** +- Schema隔离策略(platform_schema、asl_schema等) +- 表命名规范(小写+下划线) +- 字段命名规范 +- 索引设计规范 +- 外键约束规范 +- 通用字段(created_at、updated_at等) + +**快速参考:** [数据库全局视图](./03-数据库全局视图.md) ⭐ 已创建 + +--- + +### 2. API设计规范 ⭐⭐⭐⭐⭐ +**文件:** `02-API设计规范.md` ⏳ 待创建(从 `01-设计文档/API设计规范.md` 提取) + +**核心内容:** +- RESTful API设计原则 +- URL命名规范(`/api/v1/模块/资源`) +- HTTP方法使用规范 +- 请求/响应格式规范 +- 错误码设计 +- 认证和权限 + +**快速参考:** [API路由总览](./04-API路由总览.md) ⭐ 已创建 + +--- + +### 3. 数据库全局视图 ⭐⭐⭐⭐⭐ ✅ 新增 +**文件:** `03-数据库全局视图.md` + +**用途:** 提供所有Schema和表的快速索引 + +**核心内容:** +- Schema划分策略(8个Schema) +- 所有表的总览和跳转链接 +- 跨Schema依赖关系 +- 数据量预估 + +**使用场景:** +- 查看全局数据架构 +- 快速定位某个表属于哪个Schema +- 了解跨模块数据关系 + +--- + +### 4. API路由总览 ⭐⭐⭐⭐⭐ ✅ 新增 +**文件:** `04-API路由总览.md` + +**用途:** 提供所有API端点的快速索引 + +**核心内容:** +- 路由命名规范 +- 所有模块的API端点总览 +- 路由冲突检查 +- 端点统计(~85个) + +**使用场景:** +- 查看全局API架构 +- 避免路由冲突 +- 快速查找某个功能的API端点 + +--- + +### 5. 代码规范 ⭐⭐⭐⭐ +**文件:** `05-代码规范.md` → 重命名自 `代码规范.md`(已存在,818行) + +**核心内容:** +- TypeScript编码规范 +- React组件规范 +- 文件和目录命名 +- 代码注释规范 +- 错误处理规范 +- 日志记录规范 + +**已有内容,包含:** +- ESLint配置 +- Prettier配置 +- 详细的编码规范 + +--- + +### 6. Git提交规范 ⭐⭐⭐⭐ +**文件:** `06-Git提交规范.md` ⏳ 待创建 + +**核心内容:** +- Commit Message格式 +- 分支管理策略 +- PR/MR规范 +- 代码审查流程 + +**Commit Message格式:** +``` +(): + + + +