chore: project initialization - Day 4 environment setup

This commit is contained in:
AI Clinical Dev Team
2025-10-10 15:14:54 +08:00
commit bdc7de8043
22 changed files with 12276 additions and 0 deletions

View File

@@ -0,0 +1,359 @@
# AI科研助手产品需求文档 (PRD)
> **版本:** v1.0
> **创建日期:** 2025-10-10
> **文档状态:** 正式版
---
## 📋 目录
1. [产品概述](#产品概述)
2. [功能需求详述](#功能需求详述)
3. [用户端功能](#用户端功能)
4. [运营端/后台功能](#运营端后台功能)
---
## 1. 产品概述
### 1.1 产品简介
**AI科研助手**是一款专注于赋能临床及科研人员的智能化平台。它通过集成一系列基于大语言模型的AI智能体为科研工作者在从课题选择、方案设计、数据收集、论文撰写到最终发表的全流程中提供精准、高效、专业的AI辅助旨在解决科研过程中耗时耗力、信息壁垒、方法学门槛高等痛点全面提升科研工作的效率与质量。
### 1.2 目标用户
本平台的核心用户群体包括:
**(1) 临床医生/科研人员**
- 需要在繁忙的临床工作之余,高效开展高质量科学研究的核心人群
**(2) 在读硕/博士研究生**
- 在科研学习阶段,需要专业指导和高效工具来完成研究课题与毕业论文的学生
**(3) 医药企业研发人员R&D**
- 从事新药研发、临床试验等工作,需要快速获取文献信息、设计研究方案的专业人士
### 1.3 产品目标
**(1) 工具集覆盖**
- 打造一套覆盖临床研究核心流程的AI智能体工具集解决用户在选题、研究设计及论文写作阶段的关键需求
**(2) 交互优化**
- 优化人机交互体验,确保用户可以低门槛、便捷地使用各项功能
**(3) 行业领先**
- 发展成为科研领域内领先的一站式AI解决方案平台
**(4) 未来规划**
- 后期考虑增加基于大规模1000篇以内文献的读取、识别、内容提取的工作
---
## 2. 功能需求详述 (Functional Requirements)
### 2.1 用户端功能
#### 2.1.1 核心功能:项目/课题管理 (Context Persistence)
为解决用户需要反复输入背景信息的问题引入项目制管理确保AI对话的上下文连贯性。
**基础功能:**
**(1) 项目创建与管理**
- 用户可以创建、编辑和删除不同的"项目"或"课题"
**(2) 项目背景信息预设**
- 在每个项目中,用户可以预先填入该项目的核心背景信息
**(3) 上下文自动关联**
- 在特定项目内发起的所有AI智能体对话都将自动继承该项目的背景信息
**动态背景信息管理:**
**(1) 宏观编辑**
- 在项目主页,用户可通过点击编辑按钮,在弹出的模态框中对项目背景信息进行全面的、多行的编辑与修改
**(2) 微观追加**
- 在项目内的对话中用户可将AI生成的关键回答如PICOS框架、核心结论等通过"固定"操作,一键追加到当前项目的背景信息中,实现项目信息的动态演进
---
#### 2.1.2 核心功能AI智能体 (Agent)
AI智能体是产品的核心用户可以通过以下两种主要路径与智能体进行交互
**交互路径:**
**1. 全局快速问答 (Global Quick Chat)**
- 用户可直接在主界面访问所有智能体,用于处理与特定项目无关的一次性问题
**2. 项目内深度研究 (In-Project Deep Research)**
- 用户进入一个具体的"项目/课题"空间后调用智能体,所有对话将自动继承该项目的背景信息
---
**(重点)智能体功能列表:**
##### 选题阶段
**① 选题评价智能体**
- **功能描述:** 从创新性、临床价值、科学性和可行性等维度对用户提出的临床问题进行全面评价并提供初步的SWOT分析
- **输入:** 用户输入的临床问题描述(自由文本),可关联项目背景
- **输出:** 结构化的评价报告,包含创新性、临床价值、科学性、可行性分析及综合建议
**② 科学问题梳理智能体**
- **功能描述:** 辅助用户将一个模糊或宽泛的研究想法,提炼成一个清晰、具体、可验证的科学问题
- **输入:** 一个初步的研究想法,如"研究肠道菌群与抑郁症的关系"
- **输出:** 多个精炼后的科学问题选项,供用户选择或参考
##### 研究设计阶段
**③ PICOS构建智能体**
- **功能描述:** 引导用户按照PICOS原则来结构化地定义临床研究的核心要素
- **输入:** 引导式表单或自由文本描述的研究问题
- **输出:** 一个标准化的PICOS表格清晰列出各项具体内容
**④ 观察指标设计智能体**
- **功能描述:** 基于研究设计和因果关系,为用户的研究推荐合适的主要、次要及安全性观察指标集
- **输入:** 已确定的PICOS框架或研究问题
- **输出:** 包含具体指标、定义、测量方法建议的清单
**⑤ CRF制定智能体**
- **功能描述:** 基于研究方案和观察指标自动生成一份结构化、符合规范的病例报告表CRF框架
- **输入:** 研究方案、观察指标清单
- **输出:** 一份可下载的CRF框架草稿如Word/Excel格式
**⑥ 样本量计算智能体**
- **功能描述:** 根据研究类型、检验水准、把握度、效应量等核心参数,提供科学合理的样本量估算结果
- **输入:** 引导式表单输入研究设计、α、β、效应量等
- **输出:** 详细的样本量计算结果,包含公式、参数说明和最终建议样本量
**⑦ 临床研究方案撰写智能体**
- **功能描述:** 基于科学问题、PICOS等信息自动生成一份初步但结构完整的临床研究设计方案
- **输入:** 已确定的科学问题、PICOS、样本量等
- **输出:** 一份包含研究背景、目的、设计、流程等章节的Word文档初稿
##### 论文阶段
**⑧ 论文润色智能体**
- **功能描述:** 针对用户上传的稿件,提供专业级的语言润色,修正语法、拼写和表达方式
- **输入:** 上传的论文稿件Word/Text目标期刊名称可选
- **输出:** 一份带修订标记的润色后文稿及修改摘要说明
**⑨ 论文翻译智能体**
- **功能描述:** 提供专业、精准的中英互译服务,特别针对医学科研领域的术语进行优化
- **输入:** 上传的论文稿件Word/Text选择源语言和目标语言
- **输出:** 翻译完成的稿件,保留原格式
**⑩ 方法学评审智能体**
- **功能描述:** 从研究问题、方案设计、实施可行性和临床意义等维度,对研究方案或论文进行全面的方法学评审
- **输入:** 上传的研究方案或论文稿件
- **输出:** 一份评审报告,分点列出方法学上的优点、潜在缺陷及改进建议
**⑪ 期刊方法学评审智能体**
- **功能描述:** 模拟期刊审稿人视角,针对投稿文章中可能存在的统计学、方法学问题进行提问和挑战
- **输入:** 上传的论文稿件,目标期刊
- **输出:** 一份模拟的"审稿意见",列出可能被挑战的问题点
**⑫ 期刊稿约评审智能体**
- **功能描述:** 依据目标期刊的投稿要求,自动检查文章在格式、字数、参考文献规范等方面是否符合标准
- **输入:** 上传的论文稿件,目标期刊的投稿指南
- **输出:** 一份格式审查报告,清晰列出所有不符合项和修改建议
---
**在智能体对话中的重点功能:**
**(1) 模型即时切换**
- 在对话界面用户可随时通过下拉菜单切换当前对话所使用的大语言模型如Gemini, DeepSeek等以应对不同任务
- 用户选择模型,后续对话由新选定的模型驱动
**(2) 临时附件上传**
- 在对话输入框旁提供上传按钮支持用户上传临时文件如PDF, Word供AI进行一次性分析、总结或评审
- 用户上传的单个文件
- AI基于该附件内容进行回答
**(3) 知识库融合对话** ⭐
- 在任何对话窗口中,用户可随时通过"@"操作调用个人知识库中的分类让AI优先基于所选内容回答
- 示例:`@骨质疏松专题`
- 输出:融合了私有知识和通用知识的回答
**(4) 答案溯源与引用**
- 当AI回答引用自个人知识库时必须生成清晰、可点击的引用标记
- 回答中包含指向源文档具体位置的引用
**(5) 对话内追加背景**
- 在项目对话中提供将AI回答"固定"到项目背景的功能
- 用户点击"固定"按钮
- 该条回答被追加到项目描述中
**(6) 历史对话管理**
- 自动保存所有对话记录,支持用户在独立的"历史记录"页面中进行查看、搜索和筛选
---
#### 2.1.3 核心功能:个人知识库 (Personal Knowledge Base)
个人知识库是用户专属的、私密的知识管理中心。
**(1) 层级化管理**
- 采用两级结构进行管理
- **第一级:** 知识库列表,用户可在此创建、编辑知识库分类
- **第二级:** 知识库详情页,用户点击某个知识库后,可查看和管理该库内的所有文件
**(2) 文档上传与处理**
- 支持在知识库详情页上传多种格式的文档
**(3) 文档处理状态透明化**
- 每份上传的文档都应有明确的状态标识
- 状态:**"处理中"**、**"已就绪"**、**"处理失败"**
**(4) 失败原因提示**
- 若文档处理失败,系统需向用户提供清晰的原因说明
**(5) 在对话中使用**
- 用户在智能体问答过程中,可以基于创建的个人知识库来进行问答
---
#### 2.1.4 核心功能:历史记录 (History)
提供一个独立的顶级功能入口,用于管理用户的所有对话。
**(1) 统一列表**
- 按时间倒序展示用户所有的对话记录
**(2) 来源标识**
- 每条记录都清晰标识其来源是"全局快速问答"还是具体的某个"项目"
**(3) 搜索与筛选**
- 提供强大的搜索框
- 支持按项目、智能体、日期范围进行筛选
- 帮助用户快速定位历史对话
---
#### 2.1.5 辅助功能 (Support)
**(1) 快速上手**
- 在侧边栏提供独立入口
- 点击后展示产品核心功能引导和使用教程
**(2) 帮助中心**
- 在侧边栏提供独立入口
- 点击后展示FAQ和详细的用户手册
---
### 2.2 运营端/后台功能
#### (1) 仪表盘
**核心数据概览**
- 以图表形式展示关键运营指标
- 指标包括:
- 今日活跃用户
- 7日活跃用户
- 总用户数
- 今日对话数等
- 目的:方便运营人员快速了解产品状态
---
#### (2) 智能体管理
**智能体列表**
- 以列表形式展示所有前端可用的AI智能体
- 支持上下线操作
---
#### (3) 智能体配置
**配置管理**
- 管理员可新增、编辑智能体
- 配置项:名称、图标、描述、所属分类等
---
#### (4) Prompt管理
**核心功能**
- 管理员可为每个智能体精细化地调试和优化其背后的系统级提示词System Prompt
- 支持版本管理
- **重要性:** 这是保障智能体专业性的关键
---
#### (5) 模型管理
**模型接入与配置**
- 管理后台接入的各类大语言模型如Gemini, DeepSeek
- 可配置:
- API密钥
- 调用参数
- 启用/禁用状态
---
#### (6) 用户管理
**用户列表与检索**
- 查看所有用户信息
- 信息包括ID、手机号/邮箱、注册时间、状态
- 支持多条件搜索
**账户详情与操作**
- 管理员可查看单个用户的详情
- 可进行的操作:
- 设置/延长试用期
- 启用或禁用账户
- 查看操作日志
---
#### (7) 数据统计
**用户行为统计**
- 查看用户的登录历史记录(登录时间)
- 各个核心功能的使用频次统计(如不同智能体)
- 目的:帮助分析用户偏好和功能热度
---
#### (8) 对话管理
**用户历史对话查看**
- ⚠️ **重要:** 需建立严格的权限分级
- 访问条件:
- 只有在处理用户申诉或技术排障时
- 由授权的高级管理员
- 在获得审批后
- 才能查看经过脱敏和屏蔽隐私信息的对话数据
**安全与隐私强化**
- 所有查看行为必须记录在案,以备审计
---
## 附录
### 版本变更记录
| 版本 | 日期 | 变更内容 | 修改人 |
|------|------|---------|--------|
| v1.0 | 2025-10-10 | 初始版本从TXT转换为MD格式 | 项目团队 |
### 相关文档
- [数据库设计文档](../01-设计文档/数据库设计文档.md)
- [API设计规范](../01-设计文档/API设计规范.md)
- [核心业务规则](../03-业务规则/核心业务规则总览.md)
- [用户端原型图](../01-设计文档/用户端原型图.html)
---
**文档维护者:** 产品经理
**最后更新:** 2025-10-10

View File

@@ -0,0 +1,419 @@
# AI科研助手 - 技术架构总览
> **最后更新2025-10-10**
> **方案版本v2.0(最终优化版)**
> **文档说明:** 本文档是技术架构的快速参考指南,适合新人快速了解整体技术方案
---
## 📊 核心数据一览
| 指标 | 数值 |
|------|------|
| **开发周期** | 2.5个月10周 |
| **开发成本** | ¥49,300 |
| **技术难度** | ⭐⭐⭐(中等) |
| **月度成本** | ¥16,5201000用户 |
| **团队规模** | 2人1全栈 + 1前端 |
---
## 🎯 关键需求澄清
### 知识库规模(重要变更)
| 项目 | 限制 |
|------|------|
| 知识库数量 | 3个/用户 |
| 文件数量 | 50个/知识库 |
| 文件格式 | PDF、DOCX |
| 单用户最大 | 150个文件 |
**影响:** 技术难度从⭐⭐⭐⭐⭐降至⭐⭐⭐Dify完全满足需求
---
## 🏗️ 技术架构(一图看懂)
```
┌─────────────────────────────────────────┐
│ 前端React + Vite
│ - 项目管理(自研) │
│ - 智能体选择(自研) │
│ - 聊天界面参考LobeChat⭐ │
│ - 知识库管理(自研) │
└─────────────────────────────────────────┘
↓ REST API
┌─────────────────────────────────────────┐
│ 业务层 (Node.js/TypeScript) │
│ - 对话逻辑(自研)⭐ │
│ - 项目/课题管理 │
│ - 智能体路由(配置化)⭐ │
│ - 简化运营后台⭐ │
└─────────────────────────────────────────┘
↓ ↓
┌──────────────────┐ ┌─────────────────┐
│ Dify │ │ LLM API │
仅RAG⭐ │ │ - DeepSeek-V3⭐│
│ - 知识库检索 │ │ - Qwen3 ⭐ │
│ - Qdrant(内置)⭐ │ │ │
└──────────────────┘ └─────────────────┘
```
---
## 🔑 核心决策
### 1. 聊天功能实现方式
| 方案 | 选择 | 原因 |
|------|------|------|
| **前端UI** | 参考LobeChat组件 ✅ | 成熟稳定节省9.5天 |
| **对话逻辑** | 自研调用LLM API✅ | 业务可控核心只要4步 |
| Dify对话功能 | ❌ 不用 | 不适合12个智能体管理 |
| LobeChat整体 | ❌ 不用 | 缺少项目管理功能 |
### 2. RAG系统
| 功能 | 方案 |
|------|------|
| **知识库管理** | Dify ✅ |
| **文档解析** | Dify内置 ✅ |
| **向量数据库** | Dify内置Qdrant ✅ |
| **检索** | Dify API ✅ |
| 自建RAG | ❌ 不需要 |
### 3. 运营后台
| 功能 | 状态 | 实现方式 |
|------|------|---------|
| 用户管理 | ✅ 保留 | 简化版 |
| 数据统计 | ✅ 保留 | 基础仪表盘 |
| 模型管理 | ❌ 删除 | `config/models.yaml` |
| 智能体管理 | ❌ 删除 | `config/agents.yaml` |
| Prompt配置 | ❌ 删除 | `prompts/*.txt` |
### 4. 大模型选择
| 优先级 | 模型 | 价格 | 用途 |
|--------|------|------|------|
| **主力** | DeepSeek-V3 | ¥1/百万tokens | 90%的请求 |
| **备用** | Qwen3-72B | ¥4/百万tokens | 需要更强中文理解时 |
| 可选 | Gemini 2.0 | ¥2.7/百万tokens | 国际用户 |
---
## 💰 成本明细
### 开发成本
| 项目 | 金额 |
|------|------|
| 人力4人月 | ¥48,000 |
| 服务器(开发环境) | ¥1,250 |
| LLM API测试 | ¥50 |
| **总计** | **¥49,300** |
### 月度运营成本1000用户
| 项目 | 金额 |
|------|------|
| 基础设施(服务器) | ¥1,200 |
| LLM APIDeepSeek-V3 | ¥180 |
| 对象存储(知识库) | ¥140 |
| 人力1人运维 | ¥15,000 |
| **总计** | **¥16,520/月** |
### 知识库存储详情
```
单用户150个文件 × 5MB = 750MB
1000用户750GB总存储
对象存储阿里云OSS
- 存储费¥90/月
- 流量费¥50/月
- 总计¥140/月
```
---
## 📅 开发计划10周
| 阶段 | 时间 | 主要任务 |
|------|------|---------|
| **阶段1** | 1.5周 | 基础搭建 + 复用LobeChat组件 |
| **阶段2** | 3.5周 | 核心功能12个智能体 + 对话) |
| **阶段3** | 2周 | 高级功能RAG + 文档生成) |
| **阶段4** | 1周 | 简化运营后台 |
| **阶段5** | 2周 | 测试优化 |
### 阶段1基础搭建1.5周)
- [ ] 前端框架React + Vite + TailwindCSS
- [ ] **复用LobeChat聊天UI组件**
- [ ] 后端框架Fastify + Prisma + PostgreSQL
- [ ] Dify部署Docker
- [ ] DeepSeek-V3 + Qwen3接入
### 阶段2核心功能3.5周)
- [ ] 用户认证JWT
- [ ] 项目/课题CRUD
- [ ] 12个智能体配置`agents.yaml`
- [ ] 对话系统(上下文组装 + 流式输出)
- [ ] 知识库集成Dify API
### 阶段3高级功能2周
- [ ] 多模型切换DeepSeek/Qwen
- [ ] 历史记录管理
- [ ] 固定到项目背景功能
- [ ] 文档生成CRF、研究方案
### 阶段4简化运营后台1周
- [ ] 用户列表与管理
- [ ] 基础数据统计
- [ ] 对话记录查看
### 阶段5测试优化2周
- [ ] 功能测试
- [ ] 性能优化
- [ ] DeepSeek-V3效果调优
---
## 🛠️ 技术栈
### 前端
```
- React 18 + TypeScript
- Vite构建工具
- TailwindCSSUI框架
- Zustand状态管理
- LobeChat组件聊天UI
- react-markdownMarkdown渲染
```
### 后端
```
- Node.js + Fastify + TypeScript
- PrismaORM
- PostgreSQL数据库
- Redis缓存
```
### 第三方服务
```
- DifyRAG知识库
- DeepSeek-V3主力LLM
- Qwen3备用LLM
- 阿里云OSS对象存储
```
---
## ✅ 核心优势
### 1. 开发效率高
- ✅ 复用LobeChat聊天UI节省9.5天
- ✅ 使用Dify RAG节省40天
- ✅ 配置化智能体节省5天
- ✅ 总节省:**约54.5天**
### 2. 成本可控
- ✅ 开发成本¥49,300vs 纯手写¥80k+
- ✅ 月度LLM成本¥180vs GPT-4 ¥2,500
- ✅ 知识库存储¥140/月(适度规模)
### 3. 技术风险低
- ✅ Dify50k+ Stars生产级稳定
- ✅ LobeChat40k+ Stars成熟方案
- ✅ DeepSeek-V3性价比极高
### 4. 架构灵活
- ✅ 业务逻辑完全可控
- ✅ 可随时替换RAG引擎
- ✅ 可随时增减LLM模型
---
## 📋 核心文件结构
```
项目根目录/
├── frontend/ # 前端
│ ├── src/
│ │ ├── components/
│ │ │ ├── chat/ # 聊天组件参考LobeChat
│ │ │ │ ├── ChatMessage.tsx
│ │ │ │ ├── ChatInput.tsx
│ │ │ │ └── StreamRenderer.tsx
│ │ │ ├── project/ # 项目管理
│ │ │ └── kb/ # 知识库管理
│ │ ├── pages/
│ │ └── services/
│ └── package.json
├── backend/ # 后端
│ ├── src/
│ │ ├── services/
│ │ │ ├── chat.service.ts # 对话服务 ⭐
│ │ │ ├── kb.service.ts # 知识库服务
│ │ │ └── project.service.ts
│ │ ├── adapters/
│ │ │ └── llm-factory.ts # LLM适配器 ⭐
│ │ ├── clients/
│ │ │ └── dify.ts # Dify客户端
│ │ └── config/
│ │ └── agent-loader.ts # 智能体配置加载 ⭐
│ ├── config/
│ │ ├── agents.yaml # 智能体配置 ⭐
│ │ └── models.yaml # 模型配置 ⭐
│ ├── prompts/ # Prompt文件 ⭐
│ │ ├── picos_system.txt
│ │ ├── topic_evaluation_system.txt
│ │ └── ...共12个智能体
│ └── package.json
└── docker-compose.yml # Dify部署
```
---
## 🚀 快速启动
### 1. 准备工作
**申请API Key**
- [ ] DeepSeek API Key (https://platform.deepseek.com)
- [ ] 阿里云DashScope Key (https://dashscope.aliyun.com)
- [ ] 阿里云OSS (https://oss.console.aliyun.com)
**准备服务器:**
- [ ] 云服务器 4核8G开发环境
- [ ] PostgreSQL 数据库
- [ ] Redis 缓存
### 2. 部署Dify
```bash
# 克隆Dify
git clone https://github.com/langgenius/dify.git
cd dify/docker
# 启动
docker-compose up -d
# 访问 http://localhost:3000
# 创建账号并获取API Key
```
### 3. 后端开发
```bash
cd backend
npm install
# 配置环境变量
cp .env.example .env
# 填入:
# - DATABASE_URL
# - REDIS_URL
# - DEEPSEEK_API_KEY
# - DASHSCOPE_API_KEY
# - DIFY_API_KEY
# 运行数据库迁移
npx prisma migrate dev
# 启动开发服务器
npm run dev
```
### 4. 前端开发
```bash
cd frontend
npm install
# 配置API地址
# .env.local
VITE_API_URL=http://localhost:3001
# 启动开发服务器
npm run dev
```
---
## 📚 相关文档
### 设计文档
- [产品需求文档(PRD)](./产品需求文档(PRD).md) - 完整的产品需求
- [数据库设计文档](../01-设计文档/数据库设计文档.md) - 数据库表结构
- [API设计规范](../01-设计文档/API设计规范.md) - 所有API定义
- [代码规范](../02-开发规范/代码规范.md) - 代码风格规范
- [核心业务规则](../03-业务规则/核心业务规则总览.md) - 业务逻辑规则
### 开发计划
- [开发里程碑](../04-开发计划/开发里程碑.md) - 详细的10周开发计划
### 参考文档
- **技术架构选型对比方案.md** - 完整技术方案(在项目根目录)
- **对话系统实现方案对比.md** - 对话功能详细说明(在项目根目录)
- **知识库需求调整说明.md** - 知识库实现方案(在项目根目录)
---
## 🎯 关键决策清单
**在开始开发前,请确认:**
- [ ] **聊天实现方式:** 参考LobeChat + 自研对话逻辑 ✅
- [ ] **RAG系统** 使用Dify无需自建
- [ ] **向量数据库:** Dify内置Qdrant无需关心
- [ ] **运营后台:** 简化版(配置文件管理)✅
- [ ] **主力LLM** DeepSeek-V3 ✅
- [ ] **备用LLM** Qwen3 ✅
- [ ] **知识库限制:** 3个/用户50个文件/库 ✅
- [ ] **开发周期:** 2.5个月 ✅
- [ ] **团队规模:** 2人 ✅
---
## 💡 常见问题 FAQ
### Q1: 为什么不直接用Dify的对话功能
**A:** Dify对话功能需要为每个智能体创建独立应用12个智能体管理复杂且缺少项目管理功能。我们只用Dify做RAG检索。
### Q2: 对话功能自己实现会很复杂吗?
**A:** 不复杂核心只要4步接收消息 → 组装上下文 → 调用LLM → 返回流式结果。参考LobeChat的实现约需4天。
### Q3: 150个文件/用户Dify够用吗
**A:** 完全够用Dify单个知识库支持上万个文档片段我们的需求远低于上限。
### Q4: 为什么选择DeepSeek-V3
**A:** 性价比极高¥1/百万tokens性能接近GPT-4年度可节省¥27,840。
### Q5: 2.5个月真的能完成吗?
**A:** 可以通过复用LobeChat组件、使用Dify RAG、配置化智能体我们节省了约54天开发时间。
---
## 🎉 总结
**这是一个经过充分优化、成本可控、技术可行的方案:**
**开发周期:** 2.5个月
**开发成本:** ¥49,300
**月度成本:** ¥16,5201000用户
**技术难度:** ⭐⭐⭐(中等)
**风险等级:** 低(使用成熟组件和服务)
**立即可以开始!** 🚀
---
**文档版本v2.0**
**最后更新2025-10-10**
**文档位置:** `docs/00-项目概述/技术架构总览.md`
**作者:** AI技术顾问

View File

@@ -0,0 +1,297 @@
# 设计文档完成总结
> **完成时间:** 2025-10-10
> **耗时:** 约3小时
> **状态:** ✅ 已完成
---
## 🎉 恭喜!核心设计文档已全部完成
我们按照您提出的专业开发流程,成功创建了完整的设计文档体系。这为接下来的开发工作奠定了坚实的基础。
---
## 📚 已完成的文档清单
### ✅ 1. 文档目录结构
**文件:** `docs/README.md`
- 建立了8个文档分类目录
- 清晰的文档导航系统
- 文档维护指南
### ✅ 2. 数据库设计文档771行
**文件:** `docs/01-设计文档/数据库设计文档.md`
**包含内容:**
- 7个核心数据表设计
- 完整的ER图
- 字段说明和约束
- 索引设计
- Prisma Schema
- 数据安全策略
- 性能优化建议
**核心表:**
1. users - 用户表
2. projects - 项目/课题表
3. conversations - 对话表
4. messages - 消息表
5. knowledge_bases - 知识库表
6. documents - 文档表
7. admin_logs - 管理员日志表
### ✅ 3. API设计规范1023行
**文件:** `docs/01-设计文档/API设计规范.md`
**包含内容:**
- RESTful API设计原则
- 完整的8个模块API定义
- 请求/响应格式规范
- 错误处理规范
- 认证授权机制
- 分页规范
- 性能优化建议
**API模块**
1. 认证模块(注册/登录/刷新Token
2. 用户模块(个人信息管理)
3. 项目管理模块
4. 对话管理模块(含流式输出)
5. 智能体模块
6. 知识库模块
7. 历史记录模块
8. 管理后台模块
### ✅ 4. 开发里程碑586行
**文件:** `docs/04-开发计划/开发里程碑.md`
**包含内容:**
- 10周详细开发计划
- 5个里程碑定义
- 每日任务分解Day 1-70
- 验收标准
- 风险管理
- 进度跟踪机制
**里程碑:**
- 设计阶段Day 1-3 ✅
- 里程碑1基础架构 + 用户认证Week 1-2
- 里程碑2项目管理 + 3个智能体Week 3-4
- 里程碑312个智能体 + 知识库Week 5-6
- 里程碑4历史记录 + 文档生成Week 7-8
- 里程碑5运营后台 + 测试优化Week 9-10
### ✅ 5. 代码规范600+行)
**文件:** `docs/02-开发规范/代码规范.md`
**包含内容:**
- TypeScript规范
- React组件规范
- Node.js后端规范
- 命名规范
- 注释规范
- Git提交规范
- ESLint/Prettier配置
- Code Review检查清单
### ✅ 6. 核心业务规则700+行)
**文件:** `docs/03-业务规则/核心业务规则总览.md`
**包含内容:**
- 用户管理规则(注册/登录/权限)
- 项目管理规则CRUD/权限验证)
- 智能体管理规则(配置/Prompt
- 对话管理规则(上下文/流式输出)
- 知识库管理规则(配额/文档处理)
- 权限控制规则RBAC/数据隔离)
- 配额限制规则(知识库/API限流
**关键规则:**
- BR-K001: 每用户最多3个知识库
- BR-K002: 每知识库最多50个文档
- BR-K003: 只支持PDF和DOCX格式
- BR-K004: 单文件最大50MB
- BR-C003: 项目背景自动注入上下文
- BR-C006: 固定消息到项目背景
---
## 📊 文档统计
| 文档类型 | 文件数 | 总行数 | 状态 |
|---------|-------|--------|------|
| 数据库设计 | 1 | 771 | ✅ |
| API设计 | 1 | 1,023 | ✅ |
| 开发计划 | 1 | 586 | ✅ |
| 代码规范 | 1 | 600+ | ✅ |
| 业务规则 | 1 | 700+ | ✅ |
| **总计** | **5** | **3,680+** | **✅** |
---
## 🎯 设计文档的核心价值
### 1. 为前后端开发提供契约
- ✅ API设计规范是前后端协作的契约
- ✅ 可以并行开发,互不阻塞
- ✅ 避免接口频繁变更
### 2. 避免开发过程中的返工
- ✅ 数据库设计提前确定,改动成本低
- ✅ 业务规则明确,减少理解偏差
- ✅ 代码规范统一,提高质量
### 3. 提高团队协作效率
- ✅ 新成员快速上手
- ✅ 减少沟通成本
- ✅ Review有据可依
### 4. 便于后期维护和扩展
- ✅ 文档化的设计决策
- ✅ 清晰的业务逻辑
- ✅ 可追溯的变更历史
---
## 🚀 下一步行动
### 立即可做(今天)
**1. Review设计文档**
- [ ] 仔细阅读数据库设计文档
- [ ] 仔细阅读API设计规范
- [ ] 仔细阅读业务规则
- [ ] 提出疑问和优化建议
**2. 团队讨论(如有团队)**
- [ ] 召开设计Review会议
- [ ] 确认技术细节
- [ ] 调整不合理的地方
**3. 准备开发环境**
- [ ] 安装Docker Desktop ✅(已完成)
- [ ] 申请DeepSeek API Key ✅(已完成)
- [ ] 申请Qwen API Key ✅(已完成)
### 明天开始
**Day 4: 环境搭建**
- [ ] 创建项目目录结构
- [ ] 启动Docker服务
- [ ] 初始化Git仓库
- [ ] 验证所有服务
**Day 5-7: 基础框架**
- [ ] 后端框架搭建
- [ ] 前端框架搭建
- [ ] 数据库迁移
---
## 📂 文档位置
所有文档都保存在:
```
AIclinicalresearch/docs/
├── README.md # 文档导航
├── 00-项目概述/
│ └── 设计文档完成总结.md # 本文档
├── 01-设计文档/
│ ├── 数据库设计文档.md ⭐
│ └── API设计规范.md ⭐
├── 02-开发规范/
│ └── 代码规范.md ⭐
├── 03-业务规则/
│ └── 核心业务规则总览.md ⭐
└── 04-开发计划/
└── 开发里程碑.md ⭐
```
---
## 💡 使用建议
### 对于开发者
1. **开发前必读:**
- 数据库设计文档(了解表结构)
- API设计规范了解接口定义
- 代码规范(统一代码风格)
2. **开发中参考:**
- 业务规则总览(理解业务逻辑)
- 开发里程碑(明确当前任务)
3. **遇到问题时:**
- 先查阅相关文档
- 文档不清楚时再讨论
- 讨论结果更新文档
### 对于项目经理
1. **进度管理:**
- 参考开发里程碑
- 每周更新进度
- 风险及时沟通
2. **质量把控:**
- Code Review参考代码规范
- 验收参考验收标准
- 业务逻辑参考业务规则
---
## ✅ 验收检查
### 设计文档质量检查
- [x] 数据库设计完整,包含所有核心表
- [x] API设计覆盖所有功能模块
- [x] 业务规则清晰,无二义性
- [x] 开发计划详细,可执行性强
- [x] 代码规范全面,有示例代码
### 可用性检查
- [x] 文档结构清晰,易于查找
- [x] 术语统一,无矛盾之处
- [x] 有充足的示例和说明
- [x] 便于维护和更新
---
## 🎊 总结
**我们完成了一件非常重要的工作!**
通过这3小时的努力我们建立了
-**完整的文档体系**5个核心文档3680+行)
-**清晰的开发路线图**10周70天详细计划
-**统一的技术规范**数据库、API、代码
-**明确的业务规则**70+条业务规则)
**这些文档的价值:**
- 💰 **节省时间**避免后期返工预计节省15-20天
- 📈 **提高质量**:统一规范,代码质量更高
- 🤝 **降低风险**:设计明确,技术风险可控
- 🚀 **加快开发**:任务清晰,并行开发更高效
---
## 🎯 现在可以信心满满地开始开发了!
**基于这些文档,我们可以:**
1. ✅ 前后端并行开发API已定义
2. ✅ 数据库结构稳定(很少需要迁移)
3. ✅ 业务逻辑明确(减少理解偏差)
4. ✅ 代码风格统一便于协作和Review
5. ✅ 进度可控(每日任务清晰)
---
**准备好了吗让我们开始Day 4的开发工作吧** 🚀
---
**文档版本:** v1.0
**完成时间:** 2025-10-10
**创建者:** AI技术顾问 + 项目团队

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,770 @@
# 数据库设计文档
> **版本:** 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 │ │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) | 用户IDUUID | ✅ | 自动生成 |
| 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 '管理员操作日志表,用于审计';
```
---
## 索引设计
### 主要索引
| 表名 | 索引字段 | 类型 | 用途 |
|------|---------|------|------|
| 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 | 筛选处理状态 |
### 复合索引(如需优化性能可添加)
```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一次

View File

@@ -0,0 +1,537 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI科研助手产品原型 V6</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/lucide@latest"></script>
<style>
body {
font-family: 'Inter', sans-serif;
}
.sidebar-icon {
stroke-width: 1.5;
}
.main-content {
transition: opacity 0.3s ease-in-out;
}
.agent-card, .kb-card {
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
.agent-card:hover, .kb-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
}
.project-list-item.active {
background-color: #eef2ff;
color: #4f46e5;
font-weight: 600;
}
.nav-item.active {
background-color: #4f46e5;
color: white;
}
.chat-bubble-actions {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
.chat-bubble:hover .chat-bubble-actions {
opacity: 1;
}
.typing-indicator span {
animation: bounce 1.4s infinite ease-in-out both;
}
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1.0); }
}
/* Custom dropdown */
.dropdown { position: relative; display: inline-block; }
.dropdown-content {
display: none;
position: absolute;
background-color: white;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.1);
z-index: 50;
border-radius: 0.5rem;
border: 1px solid #e5e7eb;
padding: 0.5rem;
}
.dropdown-top .dropdown-content {
bottom: 100%;
margin-bottom: 0.5rem;
}
.dropdown-content a { color: black; padding: 8px 12px; text-decoration: none; display: block; border-radius: 0.25rem; font-size: 0.875rem; }
.dropdown-content a:hover { background-color: #f3f4f6; }
.dropdown:hover .dropdown-content { display: block; }
/* Custom scrollbar for better aesthetics */
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: #f1f5f9; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
</style>
</head>
<body class="bg-gray-100 text-gray-800 h-screen flex overflow-hidden">
<!-- Sidebar -->
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col">
<div class="px-6 py-4 border-b border-gray-200">
<h1 class="text-xl font-bold text-gray-900">AI科研助手</h1>
</div>
<nav class="flex-1 px-4 py-4 space-y-2">
<a href="#" id="nav-agents" class="nav-item flex items-center px-4 py-2 text-gray-700 rounded-lg hover:bg-gray-100">
<i data-lucide="brain-circuit" class="w-5 h-5 mr-3 sidebar-icon"></i>
智能体
</a>
<a href="#" id="nav-history" class="nav-item flex items-center px-4 py-2 text-gray-700 rounded-lg hover:bg-gray-100">
<i data-lucide="history" class="w-5 h-5 mr-3 sidebar-icon"></i>
历史记录
</a>
<a href="#" id="nav-kb" class="nav-item flex items-center px-4 py-2 text-gray-700 rounded-lg hover:bg-gray-100">
<i data-lucide="book-marked" class="w-5 h-5 mr-3 sidebar-icon"></i>
知识库
</a>
<div class="pt-4 mt-4 border-t border-gray-200">
<div class="flex justify-between items-center px-4 mb-2">
<h2 class="text-sm font-semibold text-gray-500 uppercase">我的项目</h2>
<button id="add-project-btn" class="text-gray-400 hover:text-indigo-600" title="创建新项目">
<i data-lucide="plus-circle" class="w-5 h-5"></i>
</button>
</div>
<div id="projectsList" class="space-y-1">
<!-- Projects will be rendered here -->
</div>
</div>
</nav>
<div class="px-4 py-4 border-t border-gray-200 space-y-2">
<a href="#" id="nav-quickstart" class="nav-item flex items-center px-4 py-2 text-gray-700 rounded-lg hover:bg-gray-100">
<i data-lucide="rocket" class="w-5 h-5 mr-3 sidebar-icon"></i>
快速上手
</a>
<a href="#" id="nav-help" class="nav-item flex items-center px-4 py-2 text-gray-700 rounded-lg hover:bg-gray-100">
<i data-lucide="help-circle" class="w-5 h-5 mr-3 sidebar-icon"></i>
帮助中心
</a>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 p-4 md:p-8 overflow-y-auto bg-gray-50">
<div id="agents-view" class="main-content"></div>
<div id="chat-view" class="main-content hidden h-full flex flex-col"></div>
<div id="knowledge-base-view" class="main-content hidden"></div>
<div id="kb-detail-view" class="main-content hidden"></div>
<div id="history-view" class="main-content hidden"></div>
<div id="quickstart-view" class="main-content hidden"></div>
<div id="help-view" class="main-content hidden"></div>
</main>
<!-- Modal for knowledge base selection -->
<div id="kb-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">选择知识库</h3>
<div id="kb-modal-list" class="space-y-2 max-h-60 overflow-y-auto"></div>
<div class="mt-6 flex justify-end space-x-3">
<button id="kb-modal-cancel" class="px-4 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300">取消</button>
</div>
</div>
</div>
<!-- Modal for Project Edit -->
<div id="project-edit-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white rounded-lg shadow-xl w-full max-w-2xl p-6">
<h3 id="project-edit-title" class="text-lg font-medium leading-6 text-gray-900 mb-4">编辑项目信息</h3>
<textarea id="project-edit-textarea" class="w-full h-48 p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"></textarea>
<div class="mt-6 flex justify-end space-x-3">
<button id="project-edit-cancel" class="px-4 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300">取消</button>
<button id="project-edit-save" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">保存</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- DATA ---
const agentsData = [ { id: 'agent-1', name: '选题评价', description: '从创新性、价值、可行性等维度评价临床问题。', icon: 'lightbulb' }, { id: 'agent-2', name: '科学问题梳理', description: '将模糊想法提炼成清晰、可验证的科学问题。', icon: 'list-filter' }, { id: 'agent-3', name: 'PICOS构建', description: '结构化地定义临床研究的核心要素。', icon: 'construction' }, { id: 'agent-4', name: '观察指标设计', description: '推荐主要、次要及安全性观察指标。', icon: 'target' }, { id: 'agent-5', name: 'CRF制定', description: '生成符合规范的病例报告表CRF框架。', icon: 'file-text' }, { id: 'agent-6', name: '样本量计算', description: '提供科学的样本量估算。', icon: 'calculator' }, { id: 'agent-7', name: '临床研究方案撰写', description: '生成结构完整的临床研究方案初稿。', icon: 'file-plus-2' }, { id: 'agent-8', name: '论文润色', description: '专业级语言润色,修正语法、拼写和表达。', icon: 'file-signature' }, { id: 'agent-9', name: '论文翻译', description: '精准的中英互译,优化科研术语。', icon: 'languages' }, { id: 'agent-10', name: '方法学评审', description: '对研究方案或论文进行全面的方法学评审。', icon: 'shield-check' }, { id: 'agent-11', name: '期刊方法学评审', description: '模拟期刊审稿人视角,提出可能被质疑的问题。', icon: 'file-search-2' }, { id: 'agent-12', name: '期刊稿约评审', description: '检查论文格式、字数等是否符合期刊要求。', icon: 'clipboard-check' } ];
let projectsData = [ { id: 'proj-1', name: 'XX药物III期临床试验', description: '一项多中心、随机、双盲、安慰剂平行对照的临床试验旨在评估XX药物在治疗阿尔兹海默症中的有效性和安全性。' }, { id: 'proj-2', name: '骨质疏松与肠道菌群关联研究', description: '探索特定肠道菌群与老年女性骨质疏松发生发展的关系。' } ];
let knowledgeBaseData = [ { id: 'kb-1', name: '骨质疏松专题', files: [{name:'文献综述.pdf', status:'ready'}, {name:'实验数据.docx', status:'ready'}]}, { id: 'kb-2', name: '肺癌研究资料', files: [{name:'临床指南_2024.pdf', status:'ready'}, {name:'病例报告汇总.docx', status:'processing'}, {name:'损坏的文件.pdf', status:'failed'}] }, { id: 'kb-3', name: '通用方法学', files: []} ];
let historyData = [ {id: 'hist-1', title: '关于XX药物的创新性评估', agentId: 'agent-1', projectId: 'proj-1', date: '2025-10-08'}, {id: 'hist-2', title: '润色论文摘要部分', agentId: 'agent-8', projectId: null, date: '2025-10-07'}, {id: 'hist-3', title: '骨质疏松研究PICOS构建', agentId: 'agent-3', projectId: 'proj-2', date: '2025-10-06'} ];
let chatData = { 'hist-1': [ {sender: 'user', text: '请帮我评估一下关于XX药物用于治疗阿尔兹海默症的选题。'}, {sender: 'ai', text: '好的,这是一个非常有前景的方向。从创新性来看,目前针对该靶点的研究尚属少数;从临床价值来看,若成功将为患者提供全新的治疗选择...'} ], 'hist-2': [ {sender: 'user', text: '请帮我润色这段摘要...'}, {sender: 'ai', text: '当然,这是修改后的版本...'} ], 'hist-3': [ {sender: 'user', text: '我们来为骨质疏松和肠道菌群的研究构建PICOS。'}, {sender: 'ai', text: '好的,我们开始吧。\n\n**P (Patient):** 绝经后、年龄在60-75岁、被诊断为骨质疏松的女性患者。\n**I (Intervention):** 接受特定益生菌制剂XXX治疗每日一次持续6个月。\n**C (Comparison):** 接受外观、味道、包装完全相同的安慰剂每日一次持续6个月。\n**O (Outcome):** 主要观察指标为治疗6个月后腰椎L1-4骨密度的变化值。次要观察指标包括股骨颈骨密度变化、血清骨转换标志物水平等。\n**S (Study Design):** 随机、双盲、安慰剂对照临床试验。'} ] };
// --- STATE ---
let state = {
activeView: 'agents', // agents, chat, kb, kb-detail, history, quickstart, help
activeProject: null,
activeChatId: null,
activeKbId: null,
activeModel: 'Gemini Pro',
editingProjectId: null,
isAiTyping: false,
};
const availableModels = ['Gemini Pro', 'DeepSeek-V2', 'Qwen2-72B'];
// --- DOM ELEMENTS ---
const dom = {
navAgents: document.getElementById('nav-agents'),
navHistory: document.getElementById('nav-history'),
navKb: document.getElementById('nav-kb'),
navQuickstart: document.getElementById('nav-quickstart'),
navHelp: document.getElementById('nav-help'),
projectsList: document.getElementById('projectsList'),
views: {
'agents': document.getElementById('agents-view'),
'chat': document.getElementById('chat-view'),
'kb': document.getElementById('knowledge-base-view'),
'kb-detail': document.getElementById('kb-detail-view'),
'history': document.getElementById('history-view'),
'quickstart': document.getElementById('quickstart-view'),
'help': document.getElementById('help-view'),
},
kbModal: document.getElementById('kb-modal'),
kbModalList: document.getElementById('kb-modal-list'),
kbModalCancel: document.getElementById('kb-modal-cancel'),
addProjectBtn: document.getElementById('add-project-btn'),
projectEditModal: {
modal: document.getElementById('project-edit-modal'),
title: document.getElementById('project-edit-title'),
textarea: document.getElementById('project-edit-textarea'),
cancel: document.getElementById('project-edit-cancel'),
save: document.getElementById('project-edit-save'),
}
};
// --- RENDER & LOGIC ---
function setState(newState) {
Object.assign(state, newState);
render();
}
function render() {
// Nav state
document.querySelectorAll('.nav-item').forEach(item => item.classList.remove('active'));
if (['agents', 'chat'].includes(state.activeView)) dom.navAgents.classList.add('active');
if (['kb', 'kb-detail'].includes(state.activeView)) dom.navKb.classList.add('active');
if (state.activeView === 'history') dom.navHistory.classList.add('active');
if (state.activeView === 'quickstart') dom.navQuickstart.classList.add('active');
if (state.activeView === 'help') dom.navHelp.classList.add('active');
Object.values(dom.views).forEach(view => view.classList.add('hidden'));
// Render view using a stable switch statement
switch (state.activeView) {
case 'agents':
dom.views.agents.classList.remove('hidden');
renderAgentsView();
break;
case 'chat':
dom.views.chat.classList.remove('hidden');
renderChatView();
break;
case 'kb':
dom.views.kb.classList.remove('hidden');
renderKnowledgeBaseListView();
break;
case 'kb-detail':
dom.views['kb-detail'].classList.remove('hidden');
renderKbDetailView();
break;
case 'history':
dom.views.history.classList.remove('hidden');
renderHistoryView();
break;
case 'quickstart':
dom.views.quickstart.classList.remove('hidden');
renderQuickstartView();
break;
case 'help':
dom.views.help.classList.remove('hidden');
renderHelpView();
break;
}
renderProjectsList();
lucide.createIcons();
}
window.renderProjectsList = function() {
dom.projectsList.innerHTML = `
<div onclick="handleProjectClick(null)" class="project-list-item px-4 py-2 text-sm text-gray-600 rounded-md cursor-pointer hover:bg-gray-100 ${!state.activeProject ? 'active' : ''}">
<i data-lucide="globe" class="inline-block w-4 h-4 mr-2"></i> 全局快速问答
</div>
${projectsData.map(p => `
<div onclick="handleProjectClick('${p.id}')" class="project-list-item px-4 py-2 text-sm text-gray-600 rounded-md cursor-pointer hover:bg-gray-100 ${state.activeProject === p.id ? 'active' : ''}">
<i data-lucide="folder" class="inline-block w-4 h-4 mr-2"></i> ${p.name}
</div>
`).join('')}`;
};
window.renderAgentsView = function() {
const view = dom.views.agents;
const project = state.activeProject ? projectsData.find(p => p.id === state.activeProject) : null;
let breadcrumbHtml = project
? `<span class="cursor-pointer hover:text-indigo-600" onclick="handleProjectClick(null)">全局</span> > <span>${project.name}</span>`
: `<span>全局</span>`;
view.innerHTML = `
<header class="mb-8">
<div class="text-sm text-gray-500 mb-2">${breadcrumbHtml}</div>
<div class="flex items-center space-x-3">
<h1 class="text-3xl font-bold text-gray-900">${project ? project.name : '智能体'}</h1>
${project ? `<button onclick="handleEditProject('${project.id}')" class="text-gray-400 hover:text-indigo-600 p-1 rounded-full hover:bg-gray-200" title="编辑项目信息"><i data-lucide="pencil" class="w-5 h-5"></i></button>` : ''}
</div>
<p class="text-gray-500 mt-2 whitespace-pre-wrap">${project ? project.description : '你好!我可以为你做什么?选择一个智能体或项目开始吧。'}</p>
</header>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
${agentsData.map(agent => `
<div onclick="handleAgentClick('${agent.id}')" class="agent-card bg-white p-5 rounded-xl border border-gray-200 cursor-pointer flex items-start space-x-4">
<div class="bg-indigo-100 text-indigo-600 p-3 rounded-lg"><i data-lucide="${agent.icon}" class="w-6 h-6"></i></div>
<div>
<h3 class="font-semibold text-gray-900">${agent.name}</h3>
<p class="text-sm text-gray-500 mt-1">${agent.description}</p>
</div>
</div>
`).join('')}
</div>`;
};
window.renderChatView = function() {
const view = dom.views.chat;
const historyItem = historyData.find(h => h.id === state.activeChatId);
if (!historyItem) { view.innerHTML = "错误:找不到对话记录。"; return; }
const agent = agentsData.find(a => a.id === historyItem.agentId);
const project = state.activeProject ? projectsData.find(p => p.id === state.activeProject) : null;
const messages = chatData[state.activeChatId] || [];
let breadcrumbHtml = `<span class="cursor-pointer hover:text-indigo-600" onclick="handleProjectClick(null)">全局</span>`;
if (project) {
breadcrumbHtml += ` > <span class="cursor-pointer hover:text-indigo-600" onclick="handleProjectHomeClick('${project.id}')">${project.name}</span>`;
}
breadcrumbHtml += ` > <span>${agent.name}</span>`;
view.innerHTML = `
<header class="p-4 border-b border-gray-200 bg-white flex-shrink-0">
<div class="text-sm text-gray-500 mb-2">${breadcrumbHtml}</div>
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<div class="bg-indigo-100 text-indigo-600 p-2 rounded-lg"><i data-lucide="${agent.icon}" class="w-5 h-5"></i></div>
<div>
<h2 class="text-xl font-bold text-gray-900">${agent.name}</h2>
<p class="text-sm text-gray-500">${agent.description}</p>
</div>
</div>
</div>
</header>
<div id="chatMessages" class="flex-1 overflow-y-auto p-6 space-y-6">
${messages.map((msg, index) => renderChatMessage(msg, index)).join('')}
${state.isAiTyping ? renderTypingIndicator() : ''}
</div>
<div class="p-4 bg-gray-50 border-t border-gray-200 flex-shrink-0">
<div class="flex items-center bg-white border border-gray-300 rounded-lg p-2 focus-within:ring-2 focus-within:ring-indigo-500">
<textarea id="message-input" rows="1" class="flex-1 bg-transparent border-none focus:ring-0 resize-none text-sm" placeholder="输入您的问题..."></textarea>
<button onclick="document.getElementById('file-upload').click()" class="p-2 text-gray-500 hover:text-indigo-600 rounded-full hover:bg-gray-100" title="上传附件">
<i data-lucide="paperclip" class="w-5 h-5"></i>
</button>
<input type="file" id="file-upload" class="hidden"/>
<button id="kb-button" class="p-2 text-gray-500 hover:text-indigo-600 rounded-full hover:bg-gray-100" title="引用知识库"><i data-lucide="at-sign" class="w-5 h-5"></i></button>
<button id="send-button" class="ml-2 px-4 py-2 bg-indigo-600 text-white text-sm font-semibold rounded-lg hover:bg-indigo-700 disabled:bg-indigo-300">发送</button>
</div>
<div class="mt-2 flex items-center justify-between">
<div class="dropdown dropdown-top">
<button class="flex items-center space-x-2 px-3 py-1.5 border rounded-lg text-sm text-gray-600 hover:bg-gray-100">
<i data-lucide="cpu" class="w-4 h-4"></i>
<span>模型: ${state.activeModel}</span>
<i data-lucide="chevron-up" class="w-4 h-4"></i>
</button>
<div class="dropdown-content">
${availableModels.map(m => `<a href="#" onclick="handleModelChange('${m}')">${m === state.activeModel ? '<span class="font-bold text-indigo-600">'+m+'</span>' : m}</a>`).join('')}
</div>
</div>
<span class="text-xs text-gray-400">Shift+Enter 换行</span>
</div>
</div>`;
// Event listeners
const messageInput = document.getElementById('message-input');
messageInput.addEventListener('input', () => { messageInput.style.height = 'auto'; messageInput.style.height = `${messageInput.scrollHeight}px`; });
document.getElementById('send-button').onclick = () => handleSendMessage();
messageInput.onkeydown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSendMessage(); } };
document.getElementById('kb-button').onclick = () => dom.kbModal.classList.remove('hidden');
document.getElementById('file-upload').onchange = (e) => alert(`已选择文件: ${e.target.files[0].name}`);
document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight;
};
window.renderChatMessage = function(msg, index) {
const isUser = msg.sender === 'user';
return `
<div class="chat-bubble flex items-start gap-3 ${isUser ? 'justify-end' : ''}">
${!isUser ? `<div class="w-8 h-8 rounded-full bg-indigo-500 text-white flex items-center justify-center flex-shrink-0"><i data-lucide="brain" class="w-5 h-5"></i></div>` : ''}
<div class="flex flex-col ${isUser ? 'items-end' : 'items-start'}">
<div class="max-w-xl p-4 rounded-xl ${isUser ? 'bg-indigo-600 text-white' : 'bg-white border'}">
<p class="text-sm leading-relaxed whitespace-pre-wrap">${msg.text}</p>
</div>
<div class="chat-bubble-actions mt-1.5 flex items-center gap-2 px-2 h-5">
<button onclick="alert('内容已复制')" title="复制" class="text-gray-400 hover:text-gray-600"><i data-lucide="copy" class="w-3.5 h-3.5"></i></button>
${!isUser ? `
<button onclick="alert('正在重新生成...')" title="重新生成" class="text-gray-400 hover:text-gray-600"><i data-lucide="refresh-cw" class="w-3.5 h-3.5"></i></button>
${state.activeProject ? `<button onclick="handlePinMessage('${state.activeChatId}', ${index})" title="固定到项目背景" class="text-gray-400 hover:text-gray-600"><i data-lucide="pin" class="w-3.5 h-3.5"></i></button>` : ''}
` : ''}
</div>
</div>
${isUser ? `<div class="w-8 h-8 rounded-full bg-gray-200 text-gray-600 flex items-center justify-center flex-shrink-0"><i data-lucide="user" class="w-5 h-5"></i></div>` : ''}
</div>`;
};
window.renderTypingIndicator = function() { return `<div class="flex items-start gap-3"><div class="w-8 h-8 rounded-full bg-indigo-500 text-white flex items-center justify-center flex-shrink-0"><i data-lucide="brain" class="w-5 h-5"></i></div><div class="max-w-lg p-4 rounded-xl bg-white border"><div class="typing-indicator flex items-center space-x-1.5"><span class="w-2 h-2 bg-gray-400 rounded-full"></span><span class="w-2 h-2 bg-gray-400 rounded-full"></span><span class="w-2 h-2 bg-gray-400 rounded-full"></span></div></div></div>`; }
window.renderKnowledgeBaseListView = function() {
const v = dom.views['kb'];
v.innerHTML = `<header class="mb-8 flex justify-between items-center"><div><h1 class="text-3xl font-bold text-gray-900">个人知识库</h1><p class="text-gray-500 mt-2">在这里管理您的私人研究资料AI可随时调用。</p></div><button class="bg-indigo-600 text-white px-4 py-2 rounded-lg font-semibold flex items-center space-x-2 hover:bg-indigo-700"><i data-lucide="plus" class="w-5 h-5"></i><span>创建新知识库</span></button></header><div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">${knowledgeBaseData.map(kb => `<div onclick="handleKbClick('${kb.id}')" class="kb-card bg-white p-5 rounded-xl border border-gray-200 cursor-pointer flex flex-col justify-between h-32"><div><div class="flex items-center space-x-3"><i data-lucide="folder" class="w-6 h-6 text-indigo-500"></i><h3 class="font-semibold text-gray-900">${kb.name}</h3></div></div><p class="text-sm text-gray-500 mt-2">${kb.files.length}个文档</p></div>`).join('')}</div>`;
}
window.renderKbDetailView = function() {
const v = dom.views['kb-detail'];
const kb = knowledgeBaseData.find(k => k.id === state.activeKbId);
if(!kb) { v.innerHTML = '错误: 找不到知识库。'; return; }
const s={'ready':{bg:'bg-green-100',text:'text-green-800',icon:'check-circle-2'},'processing':{bg:'bg-yellow-100',text:'text-yellow-800',icon:'loader'},'failed':{bg:'bg-red-100',text:'text-red-800',icon:'alert-circle'}};
v.innerHTML = `<header class="mb-8"><div class="text-sm text-gray-500 mb-2 cursor-pointer hover:text-indigo-600 flex items-center" onclick="handleNavClick('kb')"><i data-lucide="arrow-left" class="w-4 h-4 mr-1"></i>返回知识库列表</div><div class="flex justify-between items-center mt-2"><div><h1 class="text-3xl font-bold text-gray-900">${kb.name}</h1><p class="text-gray-500 mt-2">管理"${kb.name}"知识库中的所有文档。</p></div><button class="bg-indigo-600 text-white px-4 py-2 rounded-lg font-semibold flex items-center space-x-2 hover:bg-indigo-700"><i data-lucide="upload-cloud" class="w-5 h-5"></i><span>上传文件</span></button></div></header><div class="bg-white border border-gray-200 rounded-lg"><ul class="divide-y divide-gray-200">${kb.files.length > 0 ? kb.files.map(f => `<li class="p-4 flex items-center justify-between"><div class="flex items-center space-x-3"><i data-lucide="file-text" class="w-5 h-5 text-gray-400"></i><p class="font-medium text-gray-800">${f.name}</p></div><div class="flex items-center space-x-4"><span class="px-2 py-1 text-xs font-medium rounded-full ${s[f.status].bg} ${s[f.status].text}"><i data-lucide="${s[f.status].icon}" class="inline-block w-3 h-3 mr-1 ${f.status === 'processing' ? 'animate-spin' : ''}"></i>${{ready:'已就绪',processing:'处理中',failed:'失败'}[f.status]}</span><button class="text-gray-400 hover:text-red-600" title="删除"><i data-lucide="trash-2" class="w-4 h-4"></i></button></div></li>`).join('') : `<li class="p-8 text-center text-gray-500">这个知识库还没有文件。</li>`}</ul></div>`;
}
window.renderHistoryView = function() {
const v = dom.views.history;
v.innerHTML = `<header class="mb-8"><h1 class="text-3xl font-bold text-gray-900">历史记录</h1><p class="text-gray-500 mt-2">查看您所有的对话记录。</p></header><div class="mb-6 flex space-x-4"><div class="relative flex-1"><i data-lucide="search" class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"></i><input type="text" placeholder="搜索对话内容..." class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"></div><select class="border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"><option>所有项目</option><option>全局快速问答</option>${projectsData.map(p => `<option>${p.name}</option>`).join('')}</select><select class="border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"><option>所有智能体</option>${agentsData.map(a => `<option>${a.name}</option>`).join('')}</select></div><div class="bg-white border border-gray-200 rounded-lg"><ul class="divide-y divide-gray-200">${historyData.map(h => { const a = agentsData.find(ag => ag.id === h.agentId), p = projectsData.find(pr => pr.id === h.projectId); return `<li onclick="handleHistoryClick('${h.id}')" class="p-4 hover:bg-gray-50 cursor-pointer"><div class="flex justify-between items-center mb-1"><p class="font-medium text-gray-800">${h.title}</p><p class="text-xs text-gray-400">${h.date}</p></div><div class="flex items-center space-x-2 text-sm text-gray-500"><span class="px-2 py-0.5 rounded-full bg-gray-100 flex items-center space-x-1.5"><i data-lucide="${a.icon}" class="w-3.5 h-3.5"></i><span>${a.name}</span></span><span class="px-2 py-0.5 rounded-full ${p ? 'bg-indigo-100 text-indigo-800' : 'bg-green-100 text-green-800'} flex items-center space-x-1.5"><i data-lucide="${p ? 'folder' : 'globe'}" class="w-3.5 h-3.5"></i><span>${p ? p.name : '全局'}</span></span></div></li>`; }).join('')}</ul></div>`;
}
window.renderQuickstartView = function() { dom.views.quickstart.innerHTML = `<h1 class="text-3xl font-bold">快速上手</h1><p class="mt-2 text-gray-600">这里是产品引导和核心功能介绍...</p>`; }
window.renderHelpView = function() { dom.views.help.innerHTML = `<h1 class="text-3xl font-bold">帮助中心</h1><p class="mt-2 text-gray-600">这里是FAQ和用户手册...</p>`; }
// --- EVENT HANDLERS ---
window.handleProjectClick = (projectId) => setState({ activeProject: projectId, activeView: 'agents', activeChatId: null });
window.handleProjectHomeClick = (projectId) => setState({ activeProject: projectId, activeView: 'agents', activeChatId: null });
window.handleAgentClick = (agentId) => {
const newHistoryId = `hist-${Date.now()}`;
const agentName = agentsData.find(a => a.id === agentId).name;
historyData.unshift({ id: newHistoryId, title: `与"${agentName}"的新对话`, agentId: agentId, projectId: state.activeProject, date: new Date().toISOString().split('T')[0] });
chatData[newHistoryId] = [{sender: 'ai', text: `你好,我是${agentName},使用 ${state.activeModel} 模型为您服务。有什么可以帮助你?`}];
setState({ activeView: 'chat', activeChatId: newHistoryId });
};
window.handleNavClick = (view) => setState({ activeView: view, activeKbId: null, activeChatId: null });
window.handleKbClick = (kbId) => setState({ activeView: 'kb-detail', activeKbId: kbId });
window.handleHistoryClick = (historyId) => {
const historyItem = historyData.find(h => h.id === historyId);
setState({ activeView: 'chat', activeProject: historyItem.projectId, activeChatId: historyId });
};
window.handleSendMessage = function() {
const input = document.getElementById('message-input');
const text = input.value.trim();
if (!text || !state.activeChatId) return;
chatData[state.activeChatId].push({ sender: 'user', text });
input.value = ''; input.style.height = 'auto';
setState({ isAiTyping: true });
setTimeout(() => {
chatData[state.activeChatId].push({ sender: 'ai', text: `[${state.activeModel} 模型回复] 这是一个关于"${text}"的模拟回复。` });
setState({ isAiTyping: false });
}, 1500);
}
window.handleAddProject = function() {
const projectName = prompt("请输入新项目的名称:", `新研究项目 ${projectsData.length + 1}`);
if(projectName && projectName.trim()) {
const newProject = { id: `proj-${Date.now()}`, name: projectName.trim(), description: '新项目的简要描述。' };
projectsData.push(newProject);
setState({ activeProject: newProject.id, activeView: 'agents' });
}
}
window.handleEditProject = function(projectId) {
const project = projectsData.find(p => p.id === projectId);
if(!project) return;
state.editingProjectId = projectId;
const modal = dom.projectEditModal;
modal.title.textContent = `编辑项目: ${project.name}`;
modal.textarea.value = project.description;
modal.modal.classList.remove('hidden');
}
window.handleSaveProject = function() {
const project = projectsData.find(p => p.id === state.editingProjectId);
if(project) {
project.description = dom.projectEditModal.textarea.value;
}
dom.projectEditModal.modal.classList.add('hidden');
state.editingProjectId = null;
render();
}
window.handleCancelEditProject = function() {
dom.projectEditModal.modal.classList.add('hidden');
state.editingProjectId = null;
}
window.handlePinMessage = function(chatId, messageIndex) {
const project = projectsData.find(p => p.id === state.activeProject);
if (!project) { alert("错误:必须在项目中才能固定信息!"); return; }
const messageText = chatData[chatId][messageIndex].text;
const confirmation = confirm(`您确定要将以下内容追加到项目背景中吗?\n\n"${messageText.substring(0, 100)}..."`);
if(confirmation) {
project.description += `\n\n--- 来自对话的补充 ---\n${messageText}`;
alert("已成功追加到项目背景!");
}
}
window.handleModelChange = function(modelName) {
setState({ activeModel: modelName });
}
// --- MODAL LOGIC ---
function setupModal() {
dom.kbModalList.innerHTML = knowledgeBaseData.map(kb => `<div onclick="handleKbSelect('${kb.name}')" class="p-2 rounded-md hover:bg-gray-100 cursor-pointer">${kb.name}</div>`).join('');
dom.kbModalCancel.onclick = () => dom.kbModal.classList.add('hidden');
dom.projectEditModal.save.onclick = handleSaveProject;
dom.projectEditModal.cancel.onclick = handleCancelEditProject;
}
window.handleKbSelect = (kbName) => {
const input = document.getElementById('message-input');
input.value += ` @${kbName} `;
dom.kbModal.classList.add('hidden');
input.focus();
};
// --- INITIALIZATION ---
dom.navAgents.onclick = (e) => { e.preventDefault(); handleNavClick('agents'); };
dom.navHistory.onclick = (e) => { e.preventDefault(); handleNavClick('history'); };
dom.navKb.onclick = (e) => { e.preventDefault(); handleNavClick('kb'); };
dom.navQuickstart.onclick = (e) => { e.preventDefault(); handleNavClick('quickstart'); };
dom.navHelp.onclick = (e) => { e.preventDefault(); handleNavClick('help'); };
dom.addProjectBtn.onclick = handleAddProject;
// Make handlers globally accessible for inline onclicks
window.handleProjectClick = handleProjectClick;
window.handleProjectHomeClick = handleProjectHomeClick;
window.handleAgentClick = handleAgentClick;
window.handleNavClick = handleNavClick;
window.handleKbClick = handleKbClick;
window.handleHistoryClick = handleHistoryClick;
window.handleSendMessage = handleSendMessage;
window.handleAddProject = handleAddProject;
window.handleEditProject = handleEditProject;
window.handlePinMessage = handlePinMessage;
window.handleModelChange = handleModelChange;
window.handleKbSelect = handleKbSelect;
setupModal();
render();
});
</script>
</body>
</html>

View File

@@ -0,0 +1,814 @@
# 代码规范
> **版本:** v1.0
> **创建日期:** 2025-10-10
> **适用范围:** 前端React/TypeScript+ 后端Node.js/TypeScript
---
## 📋 目录
1. [通用规范](#通用规范)
2. [TypeScript规范](#typescript规范)
3. [React规范](#react规范)
4. [Node.js后端规范](#nodejs后端规范)
5. [命名规范](#命名规范)
6. [注释规范](#注释规范)
7. [Git提交规范](#git提交规范)
---
## 通用规范
### 代码风格
- ✅ 使用ESLint和Prettier统一代码风格
- ✅ 缩进2个空格
- ✅ 字符串:优先使用单引号 `'`
- ✅ 行尾:不加分号(除非必要)
- ✅ 单行最大长度100字符
- ✅ 使用尾随逗号(对象、数组)
### 文件组织
- ✅ 一个文件一个组件/类
- ✅ 相关文件放在同一目录
- ✅ 使用barrel exportsindex.ts
- ✅ 测试文件与源文件同目录
```
src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.test.tsx
│ │ ├── Button.styles.ts
│ │ └── index.ts # export { Button } from './Button'
```
### 代码注释
- ✅ 复杂逻辑必须注释
- ✅ 公共API必须注释
- ✅ 避免无用注释
- ✅ 使用JSDoc格式
---
## TypeScript规范
### 类型定义
**✅ 推荐:**
```typescript
// 使用interface定义对象结构
interface User {
id: string
email: string
name?: string
}
// 使用type定义联合类型
type Status = 'active' | 'inactive' | 'suspended'
// 使用enum定义常量集合
enum UserRole {
USER = 'user',
ADMIN = 'admin',
}
```
**❌ 避免:**
```typescript
// 不要使用any
function process(data: any) { // ❌
// ...
}
// 应该明确类型
function process(data: ProcessData) { // ✅
// ...
}
```
### 类型导入导出
```typescript
// types.ts
export interface Project {
id: string
name: string
description: string
}
export type ProjectStatus = 'active' | 'archived'
// project.service.ts
import type { Project, ProjectStatus } from './types'
```
### 严格模式
```json
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
```
---
## React规范
### 组件定义
**✅ 推荐:函数组件 + Hooks**
```tsx
import { useState } from 'react'
interface ButtonProps {
label: string
onClick: () => void
variant?: 'primary' | 'secondary'
disabled?: boolean
}
export function Button({
label,
onClick,
variant = 'primary',
disabled = false
}: ButtonProps) {
const [isLoading, setIsLoading] = useState(false)
const handleClick = async () => {
setIsLoading(true)
try {
await onClick()
} finally {
setIsLoading(false)
}
}
return (
<button
onClick={handleClick}
disabled={disabled || isLoading}
className={`btn btn-${variant}`}
>
{isLoading ? 'Loading...' : label}
</button>
)
}
```
**❌ 避免:类组件**
```tsx
// 除非有特殊需求,否则不使用类组件
class Button extends React.Component { ... } // ❌
```
### Hooks规范
**✅ 推荐自定义Hooks**
```typescript
// useProjects.ts
import { useState, useEffect } from 'react'
import { projectService } from '@/services'
import type { Project } from '@/types'
export function useProjects() {
const [projects, setProjects] = useState<Project[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
loadProjects()
}, [])
const loadProjects = async () => {
setLoading(true)
setError(null)
try {
const data = await projectService.getProjects()
setProjects(data)
} catch (err) {
setError(err as Error)
} finally {
setLoading(false)
}
}
return { projects, loading, error, reload: loadProjects }
}
// 使用
function ProjectList() {
const { projects, loading, error } = useProjects()
if (loading) return <Loading />
if (error) return <Error message={error.message} />
return (
<ul>
{projects.map(project => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}
```
### 组件组织
```tsx
// ✅ 推荐的组件结构
import { useState, useEffect, useMemo, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import { SomeComponent } from '@/components'
import { useCustomHook } from '@/hooks'
import type { SomeType } from '@/types'
interface ComponentProps {
// props定义
}
export function Component({ prop1, prop2 }: ComponentProps) {
// 1. Hooks
const navigate = useNavigate()
const [state, setState] = useState()
const { data } = useCustomHook()
// 2. 派生状态useMemo
const computedValue = useMemo(() => {
return heavyComputation(data)
}, [data])
// 3. 事件处理useCallback
const handleClick = useCallback(() => {
// 处理逻辑
}, [])
// 4. Effects
useEffect(() => {
// 副作用
}, [])
// 5. 早期返回Loading/Error
if (!data) return <Loading />
// 6. 渲染
return (
<div>
{/* JSX */}
</div>
)
}
```
### 条件渲染
**✅ 推荐:**
```tsx
// 简单条件:使用 &&
{isLoggedIn && <UserMenu />}
// if-else使用三元运算符
{isLoggedIn ? <UserMenu /> : <LoginButton />}
// 多条件:提取为函数或组件
function renderContent() {
if (loading) return <Loading />
if (error) return <Error />
if (data.length === 0) return <Empty />
return <DataList data={data} />
}
return <div>{renderContent()}</div>
```
**❌ 避免:**
```tsx
// 避免复杂的嵌套三元运算符
{condition1 ? (
condition2 ? <A /> : <B />
) : (
condition3 ? <C /> : <D />
)} // ❌ 难以理解
```
---
## Node.js后端规范
### 文件组织
```
backend/src/
├── routes/ # 路由定义
│ ├── auth.routes.ts
│ └── project.routes.ts
├── services/ # 业务逻辑
│ ├── auth.service.ts
│ └── project.service.ts
├── controllers/ # 控制器(可选)
├── models/ # Prisma模型
├── utils/ # 工具函数
├── config/ # 配置加载
├── types/ # 类型定义
└── server.ts # 入口文件
```
### 路由定义
```typescript
// routes/project.routes.ts
import { FastifyInstance } from 'fastify'
import { projectService } from '../services/project.service'
import { authMiddleware } from '../middleware/auth'
export async function projectRoutes(server: FastifyInstance) {
// 获取项目列表
server.get(
'/api/v1/projects',
{
preHandler: [authMiddleware],
schema: {
querystring: {
type: 'object',
properties: {
page: { type: 'number' },
pageSize: { type: 'number' },
},
},
},
},
async (request, reply) => {
const { page = 1, pageSize = 20 } = request.query as any
const userId = request.user.id
const result = await projectService.getProjects(userId, {
page,
pageSize,
})
return reply.send({
success: true,
data: result,
})
}
)
// 创建项目
server.post(
'/api/v1/projects',
{
preHandler: [authMiddleware],
schema: {
body: {
type: 'object',
required: ['name', 'description'],
properties: {
name: { type: 'string', minLength: 1, maxLength: 200 },
description: { type: 'string', minLength: 1 },
},
},
},
},
async (request, reply) => {
const userId = request.user.id
const data = request.body as CreateProjectDto
const project = await projectService.createProject(userId, data)
return reply.code(201).send({
success: true,
data: project,
})
}
)
}
```
### Service层
```typescript
// services/project.service.ts
import { prisma } from '../lib/prisma'
import type { CreateProjectDto, UpdateProjectDto } from '../types'
export class ProjectService {
/**
* 获取用户的项目列表
*/
async getProjects(userId: string, options: PaginationOptions) {
const { page, pageSize } = options
const [items, total] = await Promise.all([
prisma.project.findMany({
where: { userId },
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createdAt: 'desc' },
}),
prisma.project.count({ where: { userId } }),
])
return {
items,
pagination: {
page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize),
hasNext: page * pageSize < total,
hasPrev: page > 1,
},
}
}
/**
* 创建项目
*/
async createProject(userId: string, data: CreateProjectDto) {
return prisma.project.create({
data: {
userId,
name: data.name,
description: data.description,
},
})
}
/**
* 更新项目
*/
async updateProject(
userId: string,
projectId: string,
data: UpdateProjectDto
) {
// 验证权限
const project = await prisma.project.findFirst({
where: { id: projectId, userId },
})
if (!project) {
throw new Error('Project not found or unauthorized')
}
return prisma.project.update({
where: { id: projectId },
data,
})
}
/**
* 删除项目
*/
async deleteProject(userId: string, projectId: string) {
// 验证权限
const project = await prisma.project.findFirst({
where: { id: projectId, userId },
})
if (!project) {
throw new Error('Project not found or unauthorized')
}
await prisma.project.delete({
where: { id: projectId },
})
}
}
export const projectService = new ProjectService()
```
### 错误处理
```typescript
// utils/errors.ts
export class AppError extends Error {
constructor(
public code: string,
public message: string,
public statusCode: number = 400,
public details?: any
) {
super(message)
this.name = 'AppError'
}
}
export class ValidationError extends AppError {
constructor(message: string, details?: any) {
super('VALIDATION_ERROR', message, 422, details)
}
}
export class UnauthorizedError extends AppError {
constructor(message: string = 'Unauthorized') {
super('UNAUTHORIZED', message, 401)
}
}
export class NotFoundError extends AppError {
constructor(resource: string) {
super('NOT_FOUND', `${resource} not found`, 404)
}
}
// 使用
async function getProject(id: string) {
const project = await prisma.project.findUnique({ where: { id } })
if (!project) {
throw new NotFoundError('Project')
}
return project
}
```
### 错误处理中间件
```typescript
// middleware/error-handler.ts
import { FastifyError, FastifyReply, FastifyRequest } from 'fastify'
import { AppError } from '../utils/errors'
export async function errorHandler(
error: FastifyError | AppError,
request: FastifyRequest,
reply: FastifyReply
) {
// 记录错误
request.log.error(error)
// 自定义错误
if (error instanceof AppError) {
return reply.code(error.statusCode).send({
success: false,
error: {
code: error.code,
message: error.message,
details: error.details,
},
timestamp: new Date().toISOString(),
})
}
// Prisma错误
if (error.name === 'PrismaClientKnownRequestError') {
// 处理Prisma特定错误
return reply.code(400).send({
success: false,
error: {
code: 'DATABASE_ERROR',
message: 'Database operation failed',
},
timestamp: new Date().toISOString(),
})
}
// 默认错误
return reply.code(500).send({
success: false,
error: {
code: 'INTERNAL_ERROR',
message: 'Internal server error',
},
timestamp: new Date().toISOString(),
})
}
```
---
## 命名规范
### 文件命名
| 类型 | 命名方式 | 示例 |
|------|---------|------|
| React组件 | PascalCase | `Button.tsx`, `ProjectList.tsx` |
| Hooks | camelCase + use前缀 | `useProjects.ts`, `useAuth.ts` |
| 工具函数 | camelCase | `formatDate.ts`, `api.ts` |
| 类型定义 | camelCase + .types | `user.types.ts`, `api.types.ts` |
| 常量 | camelCase + .constants | `routes.constants.ts` |
| 测试文件 | 同源文件 + .test | `Button.test.tsx` |
### 变量命名
```typescript
// ✅ 推荐
const userName = 'John' // camelCase
const USER_ROLE = 'admin' // 常量用UPPER_SNAKE_CASE
const isLoading = false // 布尔值用is/has/can前缀
const hasPermission = true
const canEdit = false
// ❌ 避免
const user_name = 'John' // 不用snake_case
const loading = false // 布尔值缺少is前缀
const x = 10 // 无意义的变量名
```
### 函数命名
```typescript
// ✅ 推荐
function getUser() { } // get: 获取数据
function fetchProjects() { } // fetch: 异步获取
function createProject() { } // create: 创建
function updateProject() { } // update: 更新
function deleteProject() { } // delete: 删除
function handleClick() { } // handle: 事件处理
function validateEmail() { } // validate: 验证
function formatDate() { } // format: 格式化
// ❌ 避免
function data() { } // 不清楚功能
function doSomething() { } // 太模糊
function process() { } // 不明确
```
### 组件命名
```typescript
// ✅ 推荐
<Button /> // 基础组件
<UserProfile /> // 业务组件
<ProjectList /> // 列表组件
<CreateProjectModal /> // 弹窗组件
// ❌ 避免
<button /> // 不用小写
<user_profile /> // 不用snake_case
<ListProjects /> // 动词不要在前
```
---
## 注释规范
### JSDoc注释
```typescript
/**
* 创建新项目
* @param userId - 用户ID
* @param data - 项目数据
* @returns 创建的项目对象
* @throws {ValidationError} 当数据验证失败时
*/
async function createProject(
userId: string,
data: CreateProjectDto
): Promise<Project> {
// 实现...
}
```
### 代码注释
```typescript
// ✅ 好的注释:解释为什么
// 使用setTimeout避免阻塞UI渲染
setTimeout(() => {
processLargeData()
}, 0)
// 等待Dify处理文档最多重试10次
for (let i = 0; i < 10; i++) {
const status = await checkStatus()
if (status === 'completed') break
await sleep(2000)
}
// ❌ 坏的注释:重复代码
// 设置loading为true
setLoading(true)
// 调用API
await api.getData()
```
---
## Git提交规范
### Commit Message格式
```
<type>(<scope>): <subject>
<body>
<footer>
```
### Type类型
| 类型 | 说明 |
|------|------|
| feat | 新功能 |
| fix | Bug修复 |
| docs | 文档更新 |
| style | 代码格式(不影响功能) |
| refactor | 重构 |
| perf | 性能优化 |
| test | 测试相关 |
| chore | 构建/工具变动 |
### 示例
```bash
# 好的提交
git commit -m "feat(auth): 实现用户登录功能"
git commit -m "fix(project): 修复项目删除权限问题"
git commit -m "docs(api): 更新API文档"
git commit -m "refactor(chat): 优化消息组件结构"
# 不好的提交
git commit -m "update" # ❌ 太模糊
git commit -m "fix bug" # ❌ 没有说明是什么bug
git commit -m "完成功能" # ❌ 没有说明是什么功能
```
---
## ESLint配置
```javascript
// .eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier',
],
rules: {
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
},
}
```
---
## Prettier配置
```json
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "avoid"
}
```
---
## 代码Review检查清单
### 功能
- [ ] 功能是否完整实现
- [ ] 是否有遗漏的边界情况
- [ ] 错误处理是否完善
### 代码质量
- [ ] 代码是否易读易理解
- [ ] 是否有重复代码
- [ ] 函数是否过长(建议<50行
- [ ] 是否遵守命名规范
### 性能
- [ ] 是否有性能问题
- [ ] 是否有不必要的重渲染
- [ ] 数据库查询是否优化
### 安全
- [ ] 是否有SQL注入风险
- [ ] 是否有XSS风险
- [ ] 权限验证是否完善
### 测试
- [ ] 是否有单元测试
- [ ] 测试覆盖率是否足够
---
**文档维护:** 规范更新需同步此文档
**最后更新:** 2025-10-10
**维护者:** 技术负责人

View File

@@ -0,0 +1,592 @@
# 核心业务规则总览
> **版本:** v1.0
> **创建日期:** 2025-10-10
> **适用范围:** 整个系统
---
## 📋 目录
1. [用户管理规则](#用户管理规则)
2. [项目管理规则](#项目管理规则)
3. [智能体管理规则](#智能体管理规则)
4. [对话管理规则](#对话管理规则)
5. [知识库管理规则](#知识库管理规则)
6. [权限控制规则](#权限控制规则)
7. [配额限制规则](#配额限制规则)
---
## 用户管理规则
### 注册规则
**BR-U001: 邮箱唯一性**
- 规则:每个邮箱只能注册一个账号
- 验证时机:注册时
- 错误提示:"该邮箱已被注册"
**BR-U002: 邮箱格式验证**
- 规则:必须是有效的邮箱格式
- 正则:`^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$`
- 错误提示:"邮箱格式不正确"
**BR-U003: 密码强度**
- 规则密码至少8位包含字母和数字
- 正则:`^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$`
- 错误提示:"密码至少8位需包含字母和数字"
**BR-U004: 默认试用期**
- 规则新用户默认获得30天试用期
- 实现:`trial_ends_at = now() + 30天`
### 登录规则
**BR-U005: 账户状态检查**
- 规则只有status=active的用户可以登录
- 状态:
- `active` - 可登录
- `inactive` - 未激活,不可登录
- `suspended` - 已暂停,不可登录
**BR-U006: 密码错误次数限制**
- 规则5分钟内最多5次错误尝试
- 超过次数临时锁定账户15分钟
- 实现使用Redis记录失败次数
**BR-U007: JWT Token有效期**
- Access Token7天
- Refresh Token30天
- 过期后需要重新登录
---
## 项目管理规则
### 创建规则
**BR-P001: 项目数量无限制**
- 规则:用户可以创建任意数量的项目
- 理由:项目是核心功能,不应限制
**BR-P002: 项目名称必填**
- 规则:项目名称不能为空
- 长度1-200字符
- 错误提示:"项目名称不能为空"
**BR-P003: 项目描述必填**
- 规则:项目描述不能为空(用于上下文注入)
- 最小长度10字符
- 错误提示:"请输入项目背景信息至少10个字符"
### 更新规则
**BR-P004: 项目背景动态更新**
- 规则:用户可以随时编辑项目描述
- 规则:可以"固定"AI回复到项目描述中
- 实现追加到description字段末尾
**BR-P005: 项目所有权验证**
- 规则:只能修改/删除自己的项目
- 验证:`project.userId === currentUser.id`
- 错误提示:"无权限操作该项目"
### 删除规则
**BR-P006: 级联删除**
- 规则:删除项目时,级联删除其所有对话和消息
- 实现数据库ON DELETE CASCADE
- 确认:前端需要二次确认
**BR-P007: 软删除(可选)**
- 规则重要项目可以先软删除保留30天
- 实现添加deleted_at字段
- 备注:当前版本暂不实现
---
## 智能体管理规则
### 智能体配置
**BR-A001: 智能体配置化管理**
- 规则:智能体配置存储在`config/agents.yaml`
- 规则:不通过数据库管理
- 规则:修改配置后需要重启服务
**BR-A002: 智能体状态**
- `active` - 可用,用户可以选择
- `inactive` - 不可用,隐藏
- `testing` - 测试中,仅管理员可见
**BR-A003: 智能体分类**
- 选题阶段:选题评价、科学问题梳理
- 研究设计PICOS构建、观察指标设计、CRF制定、样本量计算、临床研究方案撰写
- 论文阶段:论文润色、论文翻译、方法学评审、期刊方法学评审、期刊稿约评审
### Prompt管理
**BR-A004: Prompt文件管理**
- 规则Prompt存储在`backend/prompts/`目录
- 命名:`{agent_id}_system.txt``{agent_id}_user.txt`
- 版本通过Git管理版本
**BR-A005: Prompt变量注入**
- 规则Prompt中可以使用变量
- 格式:`{variable_name}`
- 示例:`{project_description}`, `{user_question}`
### 模型配置
**BR-A006: 模型参数配置**
- 规则:每个智能体可以配置不同模型的参数
- 参数:
- `temperature`: 0.0-1.0
- `max_tokens`: 最大输出token数
- `top_p`: 0.0-1.0
**BR-A007: 模型切换**
- 规则:用户可以在对话中切换模型
- 可选模型:
- `deepseek-v3` (默认)
- `qwen3-72b`
- `gemini-2.0-flash` (可选)
---
## 对话管理规则
### 创建对话
**BR-C001: 对话归属**
- 规则:对话可以属于项目,也可以是全局快速问答
- 项目对话:`projectId` 不为null
- 全局快速问答:`projectId` 为null
**BR-C002: 对话标题自动生成**
- 规则:首次创建对话时,标题为"与{智能体名称}的对话"
- 规则:可以手动修改标题
### 上下文管理
**BR-C003: 项目背景自动注入**
- 规则如果对话属于项目自动将项目description注入上下文
- 注入位置System prompt之后
- 格式:`# 项目背景\n{project.description}`
**BR-C004: 历史对话管理**
- 规则保留最近10轮对话作为上下文
- 规则超过10轮的对话进行摘要压缩
- Token限制上下文总token数不超过6000
**BR-C005: 知识库引用**
- 规则:用户可以通过`@知识库名称`引用知识库
- 规则最多同时引用3个知识库
- 检索数量每个知识库检索Top 5结果
### 消息固定
**BR-C006: 固定消息到项目背景**
- 规则只有AI回复可以被固定
- 规则:只有项目内对话可以固定(全局对话不可)
- 实现追加到project.description末尾
- 格式:
```
--- 来自对话的补充 ---
{message.content}
```
**BR-C007: 固定消息标记**
- 规则被固定的消息isPinned字段设为true
- 规则:固定后不可取消(简化实现)
### 流式输出
**BR-C008: Server-Sent Events**
- 规则使用SSE实现流式输出
- 事件类型:
- `start`: 开始生成
- `token`: 每个token
- `done`: 完成
- `error`: 错误
**BR-C009: 流式输出超时**
- 规则单个请求最长60秒
- 超时后:返回已生成的内容
- 错误提示:"生成超时,请重试"
---
## 知识库管理规则
### 数量限制
**BR-K001: 知识库数量限制**
- 规则每个用户最多创建3个知识库
- 验证时机:创建知识库时
- 错误提示:"已达到知识库数量上限3个"
**BR-K002: 文档数量限制**
- 规则每个知识库最多上传50个文档
- 验证时机:上传文档时
- 错误提示:"该知识库文档数量已达上限50个"
### 文档上传
**BR-K003: 文件格式限制**
- 规则只支持PDF和DOCX格式
- MIME类型
- `application/pdf`
- `application/vnd.openxmlformats-officedocument.wordprocessingml.document`
- 错误提示:"仅支持PDF和DOCX格式"
**BR-K004: 文件大小限制**
- 规则单个文件最大50MB
- 验证时机:上传前(前端)和上传后(后端)
- 错误提示:"文件大小超过限制最大50MB"
**BR-K005: 文件名唯一性**
- 规则:同一知识库内,文件名不能重复
- 验证:检查是否已存在同名文件
- 处理:自动重命名(添加时间戳)
### 文档处理
**BR-K006: 文档处理状态**
- 状态流转:
1. `uploading` - 上传中
2. `processing` - Dify处理中
3. `completed` - 处理完成
4. `failed` - 处理失败
**BR-K007: 处理失败重试**
- 规则:处理失败的文档不自动重试
- 规则:用户可以删除后重新上传
- 错误信息记录在error_message字段
**BR-K008: 异步处理**
- 规则:文档上传后立即返回
- 规则后台异步提交到Dify处理
- 规则:前端轮询获取处理状态
### 知识库检索
**BR-K009: 混合检索**
- 规则:使用关键词检索 + 语义检索
- 规则启用Reranking重排序
- Top K5个结果
- 相似度阈值0.5
**BR-K010: 检索结果引用**
- 规则AI回答必须注明引用来源
- 格式:`[文档名] 内容...`
- 规则:前端显示可点击的引用标记
---
## 权限控制规则
### 用户权限
**BR-P001: 角色定义**
- `user` - 普通用户
- 可以使用所有功能
- 受配额限制
- `admin` - 管理员
- 所有user权限
- 可以访问运营后台
- 可以查看所有用户数据
### 数据隔离
**BR-P002: 用户数据隔离**
- 规则:用户只能访问自己创建的数据
- 验证所有查询必须包含userId过滤
- 实现:
```sql
WHERE user_id = currentUser.id
```
**BR-P003: 项目权限验证**
- 规则:修改/删除项目前,验证所有权
- 实现:
```typescript
const project = await prisma.project.findFirst({
where: { id: projectId, userId: currentUser.id }
})
if (!project) throw new UnauthorizedError()
```
### 管理员权限
**BR-P004: 用户管理权限**
- 规则只有admin可以查看用户列表
- 规则只有admin可以修改用户状态
- 规则admin不能删除用户只能暂停
**BR-P005: 对话查看权限**
- 规则只有admin可以查看用户对话
- 规则:必须记录审计日志
- 规则:敏感信息需要脱敏(如邮箱、手机号)
---
## 配额限制规则
### 知识库配额
**BR-Q001: 知识库数量配额**
- 默认配额3个知识库
- 记录字段:`users.kb_quota`, `users.kb_used`
- 更新时机:创建/删除知识库时
**BR-Q002: 文档数量配额**
- 默认配额50个文档/知识库
- 检查时机:上传文档前
- 实现:
```sql
SELECT COUNT(*) FROM documents
WHERE kb_id = ? AND status != 'failed'
```
### 存储配额(预留)
**BR-Q003: 存储空间配额**
- 默认配额1GB/用户
- 当前版本:不强制限制
- 未来:可以根据实际需求启用
### API限流
**BR-Q004: API请求频率限制**
- 普通用户100次/分钟
- 管理员500次/分钟
- 实现使用Redis + 滑动窗口算法
**BR-Q005: 登录接口限流**
- 规则5次/分钟
- 规则IP和账户双重限制
- 超过后临时封禁15分钟
---
## 业务流程
### 完整的对话流程
```
1. 用户选择智能体
2. 创建对话(可选关联项目)
3. 用户发送消息(可选引用知识库)
4. 后端组装上下文:
- 项目背景(如有)
- 智能体System Prompt
- 历史对话最近10轮
- 知识库检索结果(如有)
- 当前用户问题
5. 调用LLM流式输出
6. 保存消息到数据库
7. 用户可选:固定消息到项目背景
```
### 知识库文档处理流程
```
1. 用户上传文档
2. 验证:
- 文件格式
- 文件大小
- 数量限制
3. 上传到对象存储OSS
4. 创建document记录status: uploading
5. 异步提交到Dify
- 调用Dify上传API
- 获取dify_document_id
6. 轮询处理状态每2秒
- processing → 更新progress
- completed → 更新status, segments_count, tokens_count
- failed → 更新status, error_message
7. 更新知识库统计:
- file_count
- total_size_bytes
```
---
## 数据一致性规则
### 统计字段更新
**BR-D001: 项目对话数统计**
- 字段:`projects.conversation_count`
- 更新时机:创建/删除对话时
- 实现:使用事务保证一致性
**BR-D002: 知识库文档数统计**
- 字段:`knowledge_bases.file_count`
- 更新时机文档状态变为completed或删除时
- 计算只统计status='completed'的文档
**BR-D003: 用户知识库数统计**
- 字段:`users.kb_used`
- 更新时机:创建/删除知识库时
- 验证创建前检查是否超过quota
### 级联删除
**BR-D004: 用户删除级联**
- 规则:删除用户时,级联删除所有关联数据
- 包括projects, conversations, messages, knowledge_bases, documents
- 实现数据库ON DELETE CASCADE
**BR-D005: 项目删除级联**
- 规则:删除项目时,级联删除所有对话和消息
- 实现数据库ON DELETE CASCADE
**BR-D006: 知识库删除级联**
- 规则:删除知识库时:
1. 级联删除数据库中的documents
2. 调用Dify API删除dataset
3. 删除对象存储中的文件
---
## 异常处理规则
### 错误类型
**BR-E001: 业务错误**
- 验证失败HTTP 422
- 资源不存在HTTP 404
- 权限不足HTTP 403
- 配额超限HTTP 403
**BR-E002: 系统错误**
- 数据库错误HTTP 500
- 网络错误HTTP 503
- Dify服务错误HTTP 503
### 错误响应
**BR-E003: 统一错误格式**
```json
{
"success": false,
"error": {
"code": "QUOTA_EXCEEDED",
"message": "知识库数量已达上限",
"details": {
"resource": "knowledge_base",
"quota": 3,
"used": 3
}
},
"timestamp": "2025-10-10T10:00:00.000Z"
}
```
---
## 安全规则
### 密码安全
**BR-S001: 密码加密**
- 算法bcrypt
- 成本因子12
- 规则:永不存储明文密码
**BR-S002: Token安全**
- 规则Token包含用户ID和角色
- 规则Token签名使用JWT_SECRET
- 规则过期Token自动失效
### SQL注入防护
**BR-S003: 使用ORM**
- 规则所有数据库操作使用Prisma ORM
- 规则禁止拼接SQL语句
- 规则:使用参数化查询
### XSS防护
**BR-S004: 输入转义**
- 规则:所有用户输入在显示前转义
- 规则使用React默认转义
- 规则禁止使用dangerouslySetInnerHTML除非必要
---
## 性能优化规则
### 缓存规则
**BR-O001: 智能体列表缓存**
- 规则智能体列表缓存1小时
- 实现Redis缓存
- 失效修改agents.yaml后需清除缓存
**BR-O002: 用户信息缓存**
- 规则用户信息缓存5分钟
- Key`user:{userId}`
- 失效:用户信息更新时
### 查询优化
**BR-O003: 分页查询**
- 规则:列表查询必须分页
- 默认20条/页
- 最大100条/页
**BR-O004: 索引使用**
- 规则:频繁查询的字段必须建立索引
- 包括email, userId, projectId, conversationId
- 定期:检查慢查询日志
---
## 数据保留规则
### 数据归档
**BR-R001: 对话归档(未来)**
- 规则超过6个月的对话自动归档
- 归档表conversations_archive
- 查询:默认不查询归档数据
**BR-R002: 日志清理**
- 规则admin_logs保留3个月
- 执行:定时任务每月清理
---
## 业务规则变更流程
1. **提出变更**:填写变更申请
2. **影响评估**:评估对系统的影响
3. **Review**技术团队Review
4. **更新文档**:同步更新本文档
5. **实施变更**:修改代码
6. **测试验证**:测试新规则
7. **上线发布**:发布到生产
---
**文档维护:** 业务规则变更需同步更新
**Review频率** 每个里程碑Review一次
**最后更新:** 2025-10-10
**维护者:** 产品经理 + 技术负责人

File diff suppressed because it is too large Load Diff

86
docs/README.md Normal file
View File

@@ -0,0 +1,86 @@
# AI科研助手 - 文档中心
## 📚 文档导航
### 00-项目概述
- [产品需求文档(PRD)](./00-项目概述/产品需求文档(PRD).md) ⭐
- [技术架构总览](./00-项目概述/技术架构总览.md) ⭐
- [设计文档完成总结](./00-项目概述/设计文档完成总结.md)
### 01-设计文档 ⭐ 核心
- [数据库设计文档](./01-设计文档/数据库设计文档.md) ⭐
- [API设计规范](./01-设计文档/API设计规范.md) ⭐
- [用户端原型图](./01-设计文档/用户端原型图.html) 🎨
- [系统架构设计](./01-设计文档/系统架构设计.md)
- [接口设计详细说明](./01-设计文档/接口设计/)
### 02-开发规范 ⭐ 核心
- [代码规范](./02-开发规范/代码规范.md) ⭐
- [Git提交规范](./02-开发规范/Git提交规范.md)
- [命名规范](./02-开发规范/命名规范.md)
- [目录结构规范](./02-开发规范/目录结构规范.md)
### 03-业务规则 ⭐ 核心
- [核心业务规则总览](./03-业务规则/核心业务规则总览.md) ⭐
- [智能体管理规则](./03-业务规则/智能体管理规则.md)
- [项目管理规则](./03-业务规则/项目管理规则.md)
- [知识库管理规则](./03-业务规则/知识库管理规则.md)
### 04-开发计划 ⭐ 核心
- [开发里程碑](./04-开发计划/开发里程碑.md) ⭐
- [详细开发计划](./04-开发计划/)
### 05-配置文档
- [智能体配置说明](./05-配置文档/智能体配置说明.md)
- [模型配置说明](./05-配置文档/模型配置说明.md)
- [Prompt编写指南](./05-配置文档/Prompt编写指南.md)
### 06-测试文档
- [测试计划](./06-测试文档/测试计划.md)
- [测试用例](./06-测试文档/测试用例.md)
### 07-部署文档
- [本地开发环境配置](./07-部署文档/本地开发环境配置.md)
- [测试环境部署](./07-部署文档/测试环境部署.md)
- [生产环境部署](./07-部署文档/生产环境部署.md)
### 08-参考资料
- [Dify接入指南](./08-参考资料/Dify接入指南.md)
- [LobeChat组件参考](./08-参考资料/LobeChat组件参考.md)
- [对话系统实现方案](./08-参考资料/对话系统实现方案.md)
---
## 🎯 文档使用指南
### 新人入门顺序
1. 阅读 `00-项目概述/产品需求文档(PRD).md` - 了解产品全貌
2. 查看 `01-设计文档/用户端原型图.html` - 体验产品交互
3. 阅读 `00-项目概述/技术架构总览.md` ⭐ - 快速了解技术方案
4. 阅读 `01-设计文档/数据库设计文档.md` - 理解数据结构
5. 阅读 `01-设计文档/API设计规范.md` - 掌握接口定义
6. 阅读 `02-开发规范/代码规范.md` - 遵守开发规范
7. 阅读 `03-业务规则/核心业务规则总览.md` - 理解业务逻辑
8. 阅读 `04-开发计划/开发里程碑.md` - 了解开发计划
9. 开始开发
### 开发前必读
- ⭐ 产品需求文档(PRD) - 了解产品需求
- ⭐ 技术架构总览 - 了解技术方案
- ⭐ 数据库设计文档 - 理解数据结构
- ⭐ API设计规范 - 掌握接口定义
- ⭐ 代码规范 - 遵守开发规范
- ⭐ 业务规则总览 - 理解业务逻辑
### 文档维护原则
1. 所有设计变更必须先更新文档
2. 每个功能模块开发前先完善对应业务规则文档
3. API变更需要同步更新接口文档
4. 保持文档与代码同步
---
**文档版本:** v1.0
**创建日期:** 2025-10-10
**维护者:** 开发团队