chore: project initialization - Day 4 environment setup
This commit is contained in:
359
docs/00-项目概述/产品需求文档(PRD).md
Normal file
359
docs/00-项目概述/产品需求文档(PRD).md
Normal 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
|
||||
|
||||
419
docs/00-项目概述/技术架构总览.md
Normal file
419
docs/00-项目概述/技术架构总览.md
Normal file
@@ -0,0 +1,419 @@
|
||||
# AI科研助手 - 技术架构总览
|
||||
|
||||
> **最后更新:2025-10-10**
|
||||
> **方案版本:v2.0(最终优化版)**
|
||||
> **文档说明:** 本文档是技术架构的快速参考指南,适合新人快速了解整体技术方案
|
||||
|
||||
---
|
||||
|
||||
## 📊 核心数据一览
|
||||
|
||||
| 指标 | 数值 |
|
||||
|------|------|
|
||||
| **开发周期** | 2.5个月(10周) |
|
||||
| **开发成本** | ¥49,300 |
|
||||
| **技术难度** | ⭐⭐⭐(中等) |
|
||||
| **月度成本** | ¥16,520(1000用户) |
|
||||
| **团队规模** | 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 API(DeepSeek-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(构建工具)
|
||||
- TailwindCSS(UI框架)
|
||||
- Zustand(状态管理)
|
||||
- LobeChat组件(聊天UI)⭐
|
||||
- react-markdown(Markdown渲染)
|
||||
```
|
||||
|
||||
### 后端
|
||||
```
|
||||
- Node.js + Fastify + TypeScript
|
||||
- Prisma(ORM)
|
||||
- PostgreSQL(数据库)
|
||||
- Redis(缓存)
|
||||
```
|
||||
|
||||
### 第三方服务
|
||||
```
|
||||
- Dify(RAG知识库)⭐
|
||||
- DeepSeek-V3(主力LLM)⭐
|
||||
- Qwen3(备用LLM)⭐
|
||||
- 阿里云OSS(对象存储)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 核心优势
|
||||
|
||||
### 1. 开发效率高
|
||||
- ✅ 复用LobeChat聊天UI:节省9.5天
|
||||
- ✅ 使用Dify RAG:节省40天
|
||||
- ✅ 配置化智能体:节省5天
|
||||
- ✅ 总节省:**约54.5天**
|
||||
|
||||
### 2. 成本可控
|
||||
- ✅ 开发成本:¥49,300(vs 纯手写¥80k+)
|
||||
- ✅ 月度LLM成本:¥180(vs GPT-4 ¥2,500)
|
||||
- ✅ 知识库存储:¥140/月(适度规模)
|
||||
|
||||
### 3. 技术风险低
|
||||
- ✅ Dify:50k+ Stars,生产级稳定
|
||||
- ✅ LobeChat:40k+ 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,520(1000用户)
|
||||
✅ **技术难度:** ⭐⭐⭐(中等)
|
||||
✅ **风险等级:** 低(使用成熟组件和服务)
|
||||
|
||||
**立即可以开始!** 🚀
|
||||
|
||||
---
|
||||
|
||||
**文档版本:v2.0**
|
||||
**最后更新:2025-10-10**
|
||||
**文档位置:** `docs/00-项目概述/技术架构总览.md`
|
||||
**作者:** AI技术顾问
|
||||
|
||||
297
docs/00-项目概述/设计文档完成总结.md
Normal file
297
docs/00-项目概述/设计文档完成总结.md
Normal 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)
|
||||
- 里程碑3:12个智能体 + 知识库(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技术顾问 + 项目团队
|
||||
|
||||
1022
docs/01-设计文档/API设计规范.md
Normal file
1022
docs/01-设计文档/API设计规范.md
Normal file
File diff suppressed because it is too large
Load Diff
770
docs/01-设计文档/数据库设计文档.md
Normal file
770
docs/01-设计文档/数据库设计文档.md
Normal 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) | 用户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 '管理员操作日志表,用于审计';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 索引设计
|
||||
|
||||
### 主要索引
|
||||
|
||||
| 表名 | 索引字段 | 类型 | 用途 |
|
||||
|------|---------|------|------|
|
||||
| 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一次
|
||||
|
||||
537
docs/01-设计文档/用户端原型图.html
Normal file
537
docs/01-设计文档/用户端原型图.html
Normal 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>
|
||||
|
||||
814
docs/02-开发规范/代码规范.md
Normal file
814
docs/02-开发规范/代码规范.md
Normal 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 exports(index.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
|
||||
**维护者:** 技术负责人
|
||||
|
||||
592
docs/03-业务规则/核心业务规则总览.md
Normal file
592
docs/03-业务规则/核心业务规则总览.md
Normal 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 Token:7天
|
||||
- Refresh Token:30天
|
||||
- 过期后需要重新登录
|
||||
|
||||
---
|
||||
|
||||
## 项目管理规则
|
||||
|
||||
### 创建规则
|
||||
|
||||
**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 K:5个结果
|
||||
- 相似度阈值: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
|
||||
**维护者:** 产品经理 + 技术负责人
|
||||
|
||||
1151
docs/04-开发计划/开发里程碑.md
Normal file
1151
docs/04-开发计划/开发里程碑.md
Normal file
File diff suppressed because it is too large
Load Diff
86
docs/README.md
Normal file
86
docs/README.md
Normal 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
|
||||
**维护者:** 开发团队
|
||||
|
||||
Reference in New Issue
Block a user