feat: 初始化 PicAnalysis 项目
完整的前后端图片分析应用,包含: - 后端:Express + Prisma + SQLite,101个单元测试全部通过 - 前端:React + TypeScript + Vite,47个单元测试,89.73%覆盖率 - E2E测试:Playwright 测试套件 - MCP集成:Playwright MCP配置完成并测试通过 功能模块: - 用户认证(JWT) - 文档管理(CRUD) - 待办管理(三态工作流) - 图片管理(上传、截图、OCR) 测试覆盖: - 后端单元测试:101/101 ✅ - 前端单元测试:47/47 ✅ - E2E测试:通过 ✅ - MCP Playwright测试:通过 ✅ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
328
.project/brainstorm.md
Normal file
328
.project/brainstorm.md
Normal file
@@ -0,0 +1,328 @@
|
||||
# 头脑风暴记录 - 图片OCR与智能文档管理系统
|
||||
|
||||
## 会议时间
|
||||
2026-02-21
|
||||
|
||||
## 参与人员
|
||||
- 产品负责人
|
||||
- 技术架构师
|
||||
- AI/ML专家
|
||||
|
||||
## 头脑风暴主题
|
||||
|
||||
### 1. 用户旅程和交互流程
|
||||
|
||||
#### 1.1 典型使用场景
|
||||
**场景A: 会议记录快速转化**
|
||||
```
|
||||
用户截取线上会议截图 → 上传系统 → OCR识别 → AI提取要点 → 生成待办事项 → 分配截止日期
|
||||
```
|
||||
|
||||
**场景B: 收集资料归档**
|
||||
```
|
||||
用户看到有用的文章/图表 → 截图保存 → OCR提取文字 → AI自动打标签 → 归档到知识库
|
||||
```
|
||||
|
||||
**场景C: 待办事项管理**
|
||||
```
|
||||
截图聊天记录中的任务 → OCR识别 → 一键转化为待办 → 设置提醒 → 后续跟踪
|
||||
```
|
||||
|
||||
#### 1.2 交互优化点
|
||||
- 支持快捷键触发截图(全局快捷键)
|
||||
- OCR完成后自动进入编辑模式
|
||||
- AI分析结果可一键应用或手动调整
|
||||
- 待办事项支持拖拽排序
|
||||
- 标签支持快捷输入(按回车添加)
|
||||
|
||||
### 2. 边界情况和异常处理
|
||||
|
||||
#### 2.1 OCR相关
|
||||
| 场景 | 处理方案 |
|
||||
|------|----------|
|
||||
| 图片模糊/质量差 | 提示用户重新上传,提供图片预处理选项 |
|
||||
| OCR无文字结果 | 友好提示"未检测到文字",允许手动输入 |
|
||||
| OCR部分识别失败 | 标注不确定区域,允许用户修正 |
|
||||
| 多语言混合图片 | 自动检测语言或让用户指定 |
|
||||
|
||||
#### 2.2 AI相关
|
||||
| 场景 | 处理方案 |
|
||||
|------|----------|
|
||||
| AI API调用失败 | 降级到模板匹配或允许手动输入 |
|
||||
| API配额用尽 | 提示用户并阻止新请求,提供配额管理 |
|
||||
| 响应超时 | 设置合理超时时间,提供重试选项 |
|
||||
| 标签/分类不合理 | 允许用户反馈,改进prompt |
|
||||
|
||||
#### 2.3 文件相关
|
||||
| 场景 | 处理方案 |
|
||||
|------|----------|
|
||||
| 文件过大 | 前端预校验,超出限制友好提示 |
|
||||
| 不支持的格式 | 明确告知支持的格式列表 |
|
||||
| 网络中断上传失败 | 支持断点续传或重试机制 |
|
||||
|
||||
### 3. 数据模型优化建议
|
||||
|
||||
#### 3.1 增加的实体
|
||||
- **Folder**: 文件夹,用于组织文档和待办
|
||||
- **Reminder**: 提醒记录,支持定时提醒
|
||||
- **ActivityLog**: 操作日志,审计追踪
|
||||
- **Template**: 模板,预设待办/文档格式
|
||||
|
||||
#### 3.2 关系优化
|
||||
- 支持待办事项之间的依赖关系
|
||||
- 文档可以关联多个待办事项
|
||||
- 标签支持层级结构
|
||||
|
||||
### 4. 安全性和权限控制
|
||||
|
||||
#### 4.1 多租户隔离
|
||||
- 每个用户的数据完全隔离
|
||||
- 用户间不能查看彼此数据
|
||||
- 未来可扩展为团队共享模式
|
||||
|
||||
#### 4.2 API密钥管理
|
||||
- 用户个人的AI API密钥加密存储
|
||||
- 支持平台提供密钥(配额管理)
|
||||
- 密钥使用量统计和限流
|
||||
|
||||
#### 4.3 数据安全
|
||||
- 定期自动备份
|
||||
- 敏感操作二次确认
|
||||
- 敏感信息脱敏显示
|
||||
|
||||
### 5. 性能优化点
|
||||
|
||||
#### 5.1 前端优化
|
||||
- 图片上传前压缩(减少带宽)
|
||||
- 虚拟列表(大量文档/待办场景)
|
||||
- 图片懒加载
|
||||
- Service Worker缓存
|
||||
|
||||
#### 5.2 后端优化
|
||||
- OCR任务队列化(避免阻塞)
|
||||
- AI分析异步处理
|
||||
- 数据库查询优化(索引、分页)
|
||||
- 图片CDN加速(可选)
|
||||
|
||||
#### 5.3 OCR优化
|
||||
- 图片预处理(去噪、锐化、旋转校正)
|
||||
- 文字区域检测(ROI裁剪)
|
||||
- 缓存OCR结果(避免重复识别)
|
||||
|
||||
### 6. 可扩展性考虑
|
||||
|
||||
#### 6.1 插件化架构
|
||||
- OCR提供商插件化
|
||||
- AI提供商插件化
|
||||
- 存储后端插件化(本地/OSS/S3)
|
||||
|
||||
#### 6.2 Webhook支持
|
||||
- OCR完成通知
|
||||
- AI分析完成通知
|
||||
- 待办事项到期提醒
|
||||
|
||||
#### 6.3 API开放
|
||||
- RESTful API完整文档
|
||||
- Web API密钥管理
|
||||
- SDK提供(Python/JavaScript)
|
||||
|
||||
### 7. AI Prompt工程
|
||||
|
||||
#### 7.1 智能标签生成Prompt(支持动态创建)
|
||||
```
|
||||
你是一个智能文档助手。请分析以下文本,生成3-5个最相关的标签。
|
||||
|
||||
要求:
|
||||
1. 标签应简洁明了(2-4个字)
|
||||
2. 优先提取:主题、领域、关键实体
|
||||
3. 如果现有标签库中没有合适的,可以创建新标签
|
||||
4. 新标签应该具有普遍性和复用性
|
||||
5. 以JSON数组格式返回
|
||||
|
||||
用户现有标签:
|
||||
{existing_tags}
|
||||
|
||||
文本内容:
|
||||
{content}
|
||||
|
||||
返回格式:
|
||||
{
|
||||
"tags": ["标签1", "标签2", "标签3"],
|
||||
"new_tags": ["新标签1"], // 如果创建了新标签
|
||||
"confidence": 0.95
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.2 智能分类建议Prompt(支持动态创建分类)
|
||||
```
|
||||
你是一个智能文档助手。请分析以下文本,判断其最合适的分类。
|
||||
|
||||
要求:
|
||||
1. 首先从现有分类中选择最匹配的
|
||||
2. 如果现有分类都不合适,创建一个新分类
|
||||
3. 新分类名称应该简洁、清晰、有概括性
|
||||
4. 推荐一个合适的图标emoji
|
||||
|
||||
现有分类:
|
||||
{existing_categories}
|
||||
|
||||
文本内容:
|
||||
{content}
|
||||
|
||||
返回格式:
|
||||
{
|
||||
"category": "分类名称", // 已有或新建
|
||||
"is_new": false, // 是否新建
|
||||
"suggested_icon": "📄", // 推荐图标
|
||||
"confidence": 0.88,
|
||||
"reason": "分类理由"
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.3 文档类型智能检测Prompt
|
||||
```
|
||||
你是一个智能文档类型识别助手。请分析以下文本,判断文档类型。
|
||||
|
||||
常见文档类型:
|
||||
- 会议记录:包含会议、讨论、纪要等关键词
|
||||
- 待办事项:包含任务、计划、TODO等
|
||||
- 学习笔记:包含笔记、重点、总结等
|
||||
- 发票/票据:包含金额、日期、发票等
|
||||
- 合同/协议:包含条款、签署、协议等
|
||||
- 资料文章:一般性文章、资料
|
||||
- 代码/技术:包含代码、技术文档
|
||||
- 其他:无法明确分类
|
||||
|
||||
文本内容:
|
||||
{content}
|
||||
|
||||
返回格式:
|
||||
{
|
||||
"type": "meeting_notes", // 类型标识
|
||||
"type_name": "会议记录", // 类型显示名
|
||||
"confidence": 0.92
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.4 待办提取Prompt(增强版)
|
||||
```
|
||||
你是一个智能待办助手。请从以下文本中提取待办事项。
|
||||
|
||||
要求:
|
||||
1. 识别所有行动项和任务
|
||||
2. 提取优先级(根据紧急程度关键词:紧急、重要、尽快等)
|
||||
3. 如果有明确时间,提取截止日期
|
||||
4. 每个待办事项简洁明确
|
||||
5. 如果文本本身就是要办事项列表,直接提取
|
||||
|
||||
文本内容:
|
||||
{content}
|
||||
|
||||
返回JSON格式:
|
||||
{
|
||||
"todos": [
|
||||
{
|
||||
"title": "完成项目报告",
|
||||
"description": "需要在周五前提交给经理",
|
||||
"priority": "high",
|
||||
"due_date": "2024-01-12", // 如果有明确日期
|
||||
"suggested_tags": ["工作", "报告"]
|
||||
}
|
||||
],
|
||||
"is_todo_list": true // 是否本身就是待办列表
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.5 图片质量评估Prompt
|
||||
```
|
||||
你是一个图片质量评估助手。请评估以下OCR结果的质量。
|
||||
|
||||
OCR结果:
|
||||
{ocr_result}
|
||||
置信度:{confidence}
|
||||
|
||||
请评估:
|
||||
1. 文本是否完整可读
|
||||
2. 是否有明显错误或乱码
|
||||
3. 是否有重要信息缺失
|
||||
|
||||
返回格式:
|
||||
{
|
||||
"quality": "good", // good/acceptable/poor
|
||||
"confidence_score": 0.75, // 综合质量分数
|
||||
"issues": [], // 发现的问题列表
|
||||
"suggestion": "continue" // continue/manual/retry
|
||||
}
|
||||
```
|
||||
|
||||
### 8. 创意功能想法
|
||||
|
||||
#### 8.1 智能推荐
|
||||
- 基于历史行为推荐标签
|
||||
- 相似文档推荐
|
||||
- 智能归档建议
|
||||
- **动态分类建议**:根据内容自动创建新分类
|
||||
- **标签自动补全**:输入时智能推荐相关标签
|
||||
|
||||
#### 8.2 数据可视化
|
||||
- 标签云展示(大小随使用频率变化)
|
||||
- 待办完成率图表
|
||||
- 文档创建趋势
|
||||
- **三种状态待办统计**:未完成/已完成/已确认占比
|
||||
|
||||
#### 8.3 快捷操作
|
||||
- 拖拽截图到浏览器自动上传
|
||||
- 邮件转发创建待办
|
||||
- 微信/钉钉机器人集成
|
||||
- **全局快捷键截图**:系统级快捷键触发截图上传
|
||||
|
||||
#### 8.4 AI增强
|
||||
- 自动提取文档关键信息(日期、人名、金额)
|
||||
- 智能总结长文档
|
||||
- 多文档合并分析
|
||||
- **AI创建新类型**:根据内容自动创建新的文档/待办类型
|
||||
- **智能分类图标**:为新分类自动匹配合适的emoji图标
|
||||
|
||||
#### 8.5 待处理图片优化
|
||||
- **图片自动增强**:模糊检测、自动锐化、降噪
|
||||
- **批量处理**:一键重试所有待处理图片
|
||||
- **智能裁剪**:自动检测并裁剪到文字区域
|
||||
- **OCR提示**:对于模糊图片给出改善建议
|
||||
|
||||
#### 8.6 三状态待办工作流
|
||||
- **自动化流转**: overdue自动提醒 → completed自动归档
|
||||
- **批量确认**:一键确认所有已完成待办
|
||||
- **定时清理**:已确认超过N天的自动归档
|
||||
- **完成感奖励**:完成任务时的动效反馈
|
||||
|
||||
---
|
||||
|
||||
## 决策记录
|
||||
|
||||
| 决策点 | 选择方案 | 理由 | 时间 |
|
||||
|--------|----------|------|------|
|
||||
| 前端框架 | React | 生态丰富,Ant Design组件库成熟 | 2026-02-21 |
|
||||
| 后端框架 | Express | 成熟稳定,中间件丰富 | 2026-02-21 |
|
||||
| 数据库 | SQLite开发/PG生产 | 开发简单,生产可扩展 | 2026-02-21 |
|
||||
| OCR方案 | 可配置(本地+云端) | 灵活性最高,适应不同场景 | 2026-02-21 |
|
||||
| AI集成 | 抽象层设计 | 便于扩展新提供商 | 2026-02-21 |
|
||||
|
||||
---
|
||||
|
||||
## 待讨论问题
|
||||
|
||||
1. [ ] 是否需要支持批量导入历史图片?
|
||||
2. [ ] 待办事项是否需要支持子任务?
|
||||
3. [ ] 是否需要文档版本历史?
|
||||
4. [ ] 是否需要支持全文搜索(高亮)?
|
||||
5. [ ] 是否需要导出功能(PDF/Word)?
|
||||
|
||||
---
|
||||
|
||||
## 下一步行动
|
||||
|
||||
- [x] 完成需求文档
|
||||
- [x] 完成头脑风暴记录
|
||||
- [ ] 技术选型确认
|
||||
- [ ] 架构设计文档
|
||||
- [ ] 详细开发计划
|
||||
388
.project/decisions.md
Normal file
388
.project/decisions.md
Normal file
@@ -0,0 +1,388 @@
|
||||
# 技术决策记录 (ADR)
|
||||
|
||||
## 项目信息
|
||||
- **项目名称**: 图片OCR与智能文档管理系统
|
||||
- **记录时间**: 2026-02-21
|
||||
- **记录人**: 架构师
|
||||
|
||||
---
|
||||
|
||||
## ADR-001: 前端框架选择
|
||||
|
||||
### 状态
|
||||
已采纳 ✅
|
||||
|
||||
### 背景
|
||||
需要选择一个前端框架来构建Web UI,要求:组件丰富、开发效率高、适合中小型项目。
|
||||
|
||||
### 决策
|
||||
使用 **React 18** 作为前端框架
|
||||
|
||||
### 理由
|
||||
| 优势 | 说明 |
|
||||
|------|------|
|
||||
| 生态成熟 | 组件库、工具链丰富 |
|
||||
| Ant Design | 企业级UI组件库,减少开发量 |
|
||||
| 开发者熟悉 | 团队React经验丰富 |
|
||||
| 社区支持 | 问题解决成本低 |
|
||||
|
||||
### 考虑过的方案
|
||||
- **Vue 3**: 也很优秀,但Ant Design React更适合本项目
|
||||
- **Svelte**: 生态相对较小,不如React成熟
|
||||
|
||||
### 影响
|
||||
- 技术栈统一为React生态
|
||||
- 使用Vite作为构建工具
|
||||
- 使用Zustand/React Query管理状态
|
||||
|
||||
---
|
||||
|
||||
## ADR-002: 后端框架选择
|
||||
|
||||
### 状态
|
||||
已采纳 ✅
|
||||
|
||||
### 背景
|
||||
需要选择Node.js后端框架,要求:轻量、灵活、中间件丰富。
|
||||
|
||||
### 决策
|
||||
使用 **Express.js** 作为后端框架
|
||||
|
||||
### 理由
|
||||
| 优势 | 说明 |
|
||||
|------|------|
|
||||
| 成熟稳定 | 生产环境验证充分 |
|
||||
| 中间件丰富 | 认证、日志、CORS等开箱即用 |
|
||||
| 灵活性高 | 不强制架构,便于定制 |
|
||||
| 学习成本低 | 团队熟悉度高 |
|
||||
|
||||
### 考虑过的方案
|
||||
- **Fastify**: 性能更好,但生态不如Express
|
||||
- **Koa**: 更现代,但中间件模式不同,迁移成本
|
||||
|
||||
### 影响
|
||||
- 使用Express Router组织API
|
||||
- 使用JWT认证
|
||||
- 使用Prisma ORM
|
||||
|
||||
---
|
||||
|
||||
## ADR-003: 数据库选择
|
||||
|
||||
### 状态
|
||||
已采纳 ✅
|
||||
|
||||
### 背景
|
||||
项目规模为个人/小团队,需要平衡开发效率和可扩展性。
|
||||
|
||||
### 决策
|
||||
开发环境使用 **SQLite**,生产环境可切换到 **PostgreSQL**
|
||||
|
||||
### 理由
|
||||
| SQLite优势 | PostgreSQL优势 |
|
||||
|------------|----------------|
|
||||
| 零配置部署 | 支持更高并发 |
|
||||
| 开发便利 | 全文搜索更好 |
|
||||
| 备份简单 | JSON类型支持 |
|
||||
| 适合原型 | 生产级可靠性 |
|
||||
|
||||
### 考虑过的方案
|
||||
- **MySQL**: 与PostgreSQL类似,但JSON支持较弱
|
||||
- **MongoDB**: 不需要文档数据库的灵活性
|
||||
- **纯文件存储**: 不支持复杂查询
|
||||
|
||||
### 影响
|
||||
- 使用Prisma ORM,便于切换数据库
|
||||
- 开发阶段使用SQLite简化流程
|
||||
- 生产环境通过环境变量切换
|
||||
|
||||
---
|
||||
|
||||
## ADR-004: OCR方案架构
|
||||
|
||||
### 状态
|
||||
已采纳 ✅
|
||||
|
||||
### 背景
|
||||
用户对OCR有不同需求(隐私、成本、速度),需要灵活支持。
|
||||
|
||||
### 决策
|
||||
采用 **可配置的OCR插件架构**,支持本地和云端两种模式
|
||||
|
||||
### 理由
|
||||
| 方案 | 优势 | 劣势 |
|
||||
|------|------|------|
|
||||
| 本地OCR | 隐私好、无持续成本 | 需要GPU、速度慢 |
|
||||
| 云端API | 准确率高、快速 | 持续费用、隐私顾虑 |
|
||||
|
||||
### 实现方案
|
||||
```
|
||||
OCRProvider (Interface)
|
||||
├── LocalOCREngine (PaddleOCR)
|
||||
├── BaiduOCREngine
|
||||
├── TencentOCREngine
|
||||
└── AliyunOCREngine
|
||||
```
|
||||
|
||||
### 影响
|
||||
- 增加开发复杂度
|
||||
- 需要统一的错误处理
|
||||
- 用户可自由切换
|
||||
|
||||
---
|
||||
|
||||
## ADR-005: AI提供商集成方式
|
||||
|
||||
### 状态
|
||||
已采纳 ✅
|
||||
|
||||
### 背景
|
||||
需要支持GLM、MiniMax、DeepSeek等多个AI提供商。
|
||||
|
||||
### 决策
|
||||
使用 **统一的AI抽象层**,通过配置切换提供商
|
||||
|
||||
### 理由
|
||||
- 避免代码与特定提供商耦合
|
||||
- 便于添加新的AI服务
|
||||
- 统一的错误处理和重试逻辑
|
||||
- 统一的prompt管理
|
||||
|
||||
### 接口设计
|
||||
```typescript
|
||||
interface AIProvider {
|
||||
name: string;
|
||||
analyze(content: string, options?: AIOptions): Promise<AIResult>;
|
||||
suggestTags(content: string): Promise<string[]>;
|
||||
suggestCategory(content: string, categories: string[]): Promise<string>;
|
||||
extractTodos(content: string): Promise<TodoItem[]>;
|
||||
}
|
||||
```
|
||||
|
||||
### 影响
|
||||
- 增加抽象层开发成本
|
||||
- 长期维护成本降低
|
||||
- 便于A/B测试不同模型
|
||||
|
||||
---
|
||||
|
||||
## ADR-006: 前端状态管理方案
|
||||
|
||||
### 状态
|
||||
已采纳 ✅
|
||||
|
||||
### 背景
|
||||
React项目需要状态管理,处理用户认证、文档列表、待办等状态。
|
||||
|
||||
### 决策
|
||||
组合使用 **Zustand** (全局状态) 和 **React Query** (服务器状态)
|
||||
|
||||
### 理由
|
||||
| Zustand优势 | React Query优势 |
|
||||
|-------------|----------------|
|
||||
| 轻量简洁 | 自动缓存/重新验证 |
|
||||
| 无需Provider | 乐观更新 |
|
||||
| TypeScript友好 | 请求去重 |
|
||||
| 易于调试 | 后台数据同步 |
|
||||
|
||||
### 责任划分
|
||||
- **Zustand**: UI状态(模态框、侧边栏、用户偏好)
|
||||
- **React Query**: 服务器数据(文档、待办、分类)
|
||||
|
||||
### 影响
|
||||
- 减少样板代码
|
||||
- 自动处理加载和错误状态
|
||||
- 更好的用户体验
|
||||
|
||||
---
|
||||
|
||||
## ADR-007: 文件存储方案
|
||||
|
||||
### 状态
|
||||
已采纳 ✅
|
||||
|
||||
### 背景
|
||||
需要存储用户上传的图片文件,考虑成本、性能、扩展性。
|
||||
|
||||
### 决策
|
||||
使用 **本地文件系统** 存储,支持未来扩展到OSS
|
||||
|
||||
### 理由
|
||||
| 方案 | 适用场景 |
|
||||
|------|----------|
|
||||
| 本地存储 | 小规模、成本优先 |
|
||||
| 阿里云OSS | 大规模、CDN加速 |
|
||||
| AWS S3 | 国际化场景 |
|
||||
|
||||
### 实现策略
|
||||
1. 基础版本使用本地存储
|
||||
2. 抽象存储接口便于切换
|
||||
3. 支持环境变量配置
|
||||
|
||||
### 目录结构
|
||||
```
|
||||
uploads/
|
||||
├── images/
|
||||
│ ├── {user_id}/
|
||||
│ │ ├── {year}/{month}/
|
||||
│ │ │ └── {uuid}.{ext}
|
||||
```
|
||||
|
||||
### 影响
|
||||
- Docker部署需要volume挂载
|
||||
- 备份策略需要考虑文件
|
||||
|
||||
---
|
||||
|
||||
## ADR-008: 认证方案
|
||||
|
||||
### 状态
|
||||
已采纳 ✅
|
||||
|
||||
### 背景
|
||||
多用户系统需要安全的身份认证机制。
|
||||
|
||||
### 决策
|
||||
使用 **JWT (JSON Web Token)** 进行无状态认证
|
||||
|
||||
### 理由
|
||||
| JWT优势 | 说明 |
|
||||
|---------|------|
|
||||
| 无状态 | 服务端不存储session |
|
||||
| 跨域友好 | 适合前后端分离 |
|
||||
| 性能好 | 无需数据库查询session |
|
||||
| 标准化 | 生态工具完善 |
|
||||
|
||||
### 实现细节
|
||||
- Access Token有效期: 24小时
|
||||
- Refresh Token: 可选(未来扩展)
|
||||
- 存储方式: httpOnly Cookie或localStorage
|
||||
- 密码加密: bcrypt
|
||||
|
||||
### 安全措施
|
||||
- HTTPS传输
|
||||
- Token签名验证
|
||||
- 密码强度要求
|
||||
- 登录失败限制
|
||||
|
||||
---
|
||||
|
||||
## ADR-009: Docker部署策略
|
||||
|
||||
### 状态
|
||||
已采纳 ✅
|
||||
|
||||
### 背景
|
||||
需要支持一键部署,简化用户使用门槛。
|
||||
|
||||
### 决策
|
||||
使用 **Docker Compose** 编排多容器部署
|
||||
|
||||
### 容器划分
|
||||
| 容器 | 职责 |
|
||||
|------|------|
|
||||
| frontend | React静态文件服务 |
|
||||
| backend | Express API服务 |
|
||||
| ocr-service | 本地OCR服务(可选) |
|
||||
| nginx | 反向代理 |
|
||||
|
||||
### 理由
|
||||
- 简化部署流程
|
||||
- 环境一致性
|
||||
- 易于扩展和升级
|
||||
- 支持本地OCR服务隔离
|
||||
|
||||
### 影响
|
||||
- 需要编写详细的部署文档
|
||||
- 需要提供环境变量配置模板
|
||||
|
||||
---
|
||||
|
||||
## ADR-010: 异步任务处理
|
||||
|
||||
### 状态
|
||||
已采纳 ✅
|
||||
|
||||
### 背景
|
||||
OCR和AI分析都是耗时操作,不应阻塞用户请求。
|
||||
|
||||
### 决策
|
||||
使用 **内存队列 + 轮询** 的方式处理异步任务
|
||||
|
||||
### 理由
|
||||
| 方案 | 优势 | 劣势 |
|
||||
|------|------|------|
|
||||
| 内存队列 | 简单、无需额外服务 | 重启丢失 |
|
||||
| Redis队列 | 持久化、分布式 | 额外依赖 |
|
||||
| 消息队列 | 企业级 | 过于复杂 |
|
||||
|
||||
### 任务流程
|
||||
```
|
||||
1. 用户上传图片
|
||||
2. 返回taskId
|
||||
3. 后台异步处理
|
||||
4. 前端轮询状态
|
||||
5. 完成后获取结果
|
||||
```
|
||||
|
||||
### 影响
|
||||
- 前端需要实现轮询逻辑
|
||||
- 任务状态需要持久化
|
||||
- 考虑添加WebSocket优化(未来)
|
||||
|
||||
---
|
||||
|
||||
## 待决策项
|
||||
|
||||
| 编号 | 主题 | 计划决策时间 |
|
||||
|------|------|--------------|
|
||||
| ADR-011 | 日志方案 | Sprint 2 |
|
||||
| ADR-012 | 监控告警 | Sprint 4 |
|
||||
| ADR-013 | 备份策略 | Sprint 5 |
|
||||
| ADR-014 | 前端路由模式 | Sprint 1 |
|
||||
|
||||
---
|
||||
|
||||
## 决策模板
|
||||
|
||||
```markdown
|
||||
## ADR-XXX: 决策标题
|
||||
|
||||
### 状态
|
||||
[提议中/已采纳/已废弃/已替代]
|
||||
|
||||
### 背景
|
||||
[描述驱动这个决策的上下文]
|
||||
|
||||
### 决策
|
||||
[我们做了什么决定]
|
||||
|
||||
### 理由
|
||||
[为什么做出这个决定]
|
||||
|
||||
### 考虑过的方案
|
||||
[我们考虑过哪些替代方案]
|
||||
|
||||
### 影响
|
||||
[这个决策会产生什么影响]
|
||||
|
||||
### 相关决策
|
||||
[关联的其他ADR]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 变更历史
|
||||
|
||||
| 日期 | ADR编号 | 变更类型 | 说明 |
|
||||
|------|---------|----------|------|
|
||||
| 2026-02-21 | ADR-001 | 新增 | 前端框架选择 |
|
||||
| 2026-02-21 | ADR-002 | 新增 | 后端框架选择 |
|
||||
| 2026-02-21 | ADR-003 | 新增 | 数据库选择 |
|
||||
| 2026-02-21 | ADR-004 | 新增 | OCR方案架构 |
|
||||
| 2026-02-21 | ADR-005 | 新增 | AI提供商集成 |
|
||||
| 2026-02-21 | ADR-006 | 新增 | 状态管理方案 |
|
||||
| 2026-02-21 | ADR-007 | 新增 | 文件存储方案 |
|
||||
| 2026-02-21 | ADR-008 | 新增 | 讴证方案 |
|
||||
| 2026-02-21 | ADR-009 | 新增 | Docker部署 |
|
||||
| 2026-02-21 | ADR-010 | 新增 | 异步任务处理 |
|
||||
1065
.project/development-plan.md
Normal file
1065
.project/development-plan.md
Normal file
File diff suppressed because it is too large
Load Diff
787
.project/requirements.md
Normal file
787
.project/requirements.md
Normal file
@@ -0,0 +1,787 @@
|
||||
# 图片OCR与智能文档管理系统 - 需求文档
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
### 1.1 项目背景
|
||||
在数字化办公场景中,用户经常需要从截图中提取文字内容(如截图保存的待办事项、会议记录、资料等),然后手动整理成文档或待办任务。本项目旨在通过OCR和AI技术,自动化这一流程,提升工作效率。
|
||||
|
||||
### 1.2 项目目标
|
||||
- 实现图片到文本的自动识别与转换
|
||||
- 利用AI智能分析,自动为文档打标签和分类
|
||||
- 支持将识别结果一键转化为待办事项或归档文档
|
||||
- 提供友好的Web界面,支持多用户协作
|
||||
|
||||
### 1.3 成功标准
|
||||
- OCR识别准确率达到90%以上(清晰印刷体)
|
||||
- AI标签分类准确率达到85%以上
|
||||
- 端到端流程(截图→待办)操作步骤不超过5步
|
||||
- 系统响应时间 < 2秒(不含OCR处理时间)
|
||||
- 支持Docker一键部署
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能需求
|
||||
|
||||
### 2.1 核心功能 (Must Have - P0)
|
||||
|
||||
#### 2.1.1 用户认证与授权
|
||||
- **描述**: 支持多用户注册登录,数据隔离
|
||||
- **优先级**: P0
|
||||
- **验收标准**:
|
||||
- [ ] 支持邮箱/用户名注册登录
|
||||
- [ ] 支持密码加密存储(bcrypt)
|
||||
- [ ] 用户只能查看和操作自己的数据
|
||||
- [ ] 提供登出功能
|
||||
- [ ] JWT Token认证机制
|
||||
|
||||
#### 2.1.2 图片采集
|
||||
- **描述**: 支持系统截图和本地图片上传两种方式
|
||||
- **优先级**: P0
|
||||
- **验收标准**:
|
||||
- [ ] 支持点击调用系统截图(需浏览器权限)
|
||||
- [ ] 支持拖拽上传图片
|
||||
- [ ] 支持点击选择文件上传
|
||||
- [ ] 支持常见图片格式(JPG、PNG、WEBP)
|
||||
- [ ] 图片预览功能
|
||||
- [ ] 单个图片大小限制 < 10MB
|
||||
|
||||
#### 2.1.3 OCR文字识别与智能处理
|
||||
- **描述**: 将图片中的文字转换为可编辑文本,建立图片-文档关联,支持OCR失败时的降级处理
|
||||
- **优先级**: P0
|
||||
- **验收标准**:
|
||||
- [ ] 支持中文、英文识别
|
||||
- [ ] OCR结果可编辑
|
||||
- [ ] 建立图片与识别结果的永久关联
|
||||
- [ ] 显示OCR置信度/处理状态
|
||||
- [ ] 支持重新OCR识别
|
||||
- [ ] **OCR失败处理**:
|
||||
- [ ] 当OCR置信度低于阈值(如30%)时,不自动生成文档
|
||||
- [ ] 图片保存到"待处理"列表,用户可查看所有失败/待处理的图片
|
||||
- [ ] 用户可从待处理列表手动创建文档(输入文字内容)
|
||||
- [ ] 提供图片预处理选项(旋转、裁剪、亮度调整)后重试
|
||||
- [ ] 显示明确的失败原因和建议
|
||||
- [ ] **模糊图片处理**:
|
||||
- [ ] 自动检测图片质量(模糊度、分辨率)
|
||||
- [ ] 低质量图片发出警告,但仍可继续处理
|
||||
- [ ] 提供图片增强选项
|
||||
|
||||
#### 2.1.4 文档管理
|
||||
- **描述**: 对OCR结果进行CRUD操作
|
||||
- **优先级**: P0
|
||||
- **验收标准**:
|
||||
- [ ] 创建文档(从OCR结果)
|
||||
- [ ] 编辑文档内容
|
||||
- [ ] 删除文档
|
||||
- [ ] 文档列表展示
|
||||
- [ ] 文档搜索(按标题、内容)
|
||||
- [ ] 文档详情查看
|
||||
|
||||
#### 2.1.5 待办事项管理
|
||||
- **描述**: 将文档转化为待办事项并管理,支持三种状态列表
|
||||
- **优先级**: P0
|
||||
- **验收标准**:
|
||||
- [ ] 从文档创建待办事项
|
||||
- [ ] 设置优先级(高/中/低)
|
||||
- [ ] 设置截止日期
|
||||
- [ ] **三种状态列表**:
|
||||
- [ ] **未完成列表**: 新创建、进行中的待办
|
||||
- [ ] **已完成列表**: 用户标记完成的待办
|
||||
- [ ] **已确认列表**: 完成后经过用户确认归档的待办
|
||||
- [ ] 状态流转:未完成 → 已完成 → 已确认
|
||||
- [ ] 支持批量操作(批量完成、批量确认)
|
||||
- [ ] 待办列表按状态/优先级/截止日期排序
|
||||
- [ ] 待办归类(支持分类文件夹)
|
||||
- [ ] 已确认列表支持归档和导出
|
||||
|
||||
#### 2.1.6 AI智能分析
|
||||
- **描述**: 对OCR结果进行AI分析,自动打标签和分类,支持动态类型和标签扩展
|
||||
- **优先级**: P0
|
||||
- **验收标准**:
|
||||
- [ ] 支持GLM(智谱AI)接口
|
||||
- [ ] 支持MiniMax接口
|
||||
- [ ] 支持DeepSeek接口
|
||||
- [ ] **智能标签生成**:
|
||||
- [ ] 自动生成3-5个标签
|
||||
- [ ] AI可根据内容创建新标签(非预定义标签)
|
||||
- [ ] 新标签自动添加到用户标签库
|
||||
- [ ] 标签使用频率统计,常用标签优先展示
|
||||
- [ ] **智能分类与类型**:
|
||||
- [ ] AI可自动识别文档/待办类型
|
||||
- [ ] 支持AI创建新分类(如"会议记录"、"发票"、"学习笔记"等)
|
||||
- [ ] 新分类自动添加到用户分类体系
|
||||
- [ ] 分类图标和颜色自动生成(可手动修改)
|
||||
- [ ] **动态展示优化**:
|
||||
- [ ] 根据用户保存的内容,自动调整标签/分类展示顺序
|
||||
- [ ] 常用组合(标签+分类)智能推荐
|
||||
- [ ] 相似内容自动归集建议
|
||||
- [ ] 标签和分类可手动修改
|
||||
- [ ] AI分析失败时降级处理
|
||||
|
||||
### 2.2 重要功能 (Should Have - P1)
|
||||
|
||||
#### 2.2.1 标签与分类系统
|
||||
- **描述**: 完善的标签分类管理体系
|
||||
- **优先级**: P1
|
||||
- **验收标准**:
|
||||
- [ ] 创建自定义分类
|
||||
- [ ] 创建自定义标签
|
||||
- [ ] 标签颜色自定义
|
||||
- [ ] 按标签/分类筛选
|
||||
- [ ] 标签统计展示
|
||||
|
||||
#### 2.2.2 配置管理
|
||||
- **描述**: 可配置的服务提供商设置
|
||||
- **优先级**: P1
|
||||
- **验收标准**:
|
||||
- [ ] OCR提供商配置(本地/云端)
|
||||
- [ ] AI提供商配置(API Key等)
|
||||
- [ ] 模型参数配置(温度、top_p等)
|
||||
- [ ] 配置测试功能
|
||||
- [ ] 配置导入/导出
|
||||
|
||||
#### 2.2.3 批量操作
|
||||
- **描述**: 提高批量处理效率
|
||||
- **优先级**: P1
|
||||
- **验收标准**:
|
||||
- [ ] 批量上传图片
|
||||
- [ ] 批量OCR识别
|
||||
- [ ] 批量AI分析
|
||||
- [ ] 批量删除
|
||||
- [ ] 批量打标签
|
||||
|
||||
### 2.3 可选功能 (Could Have - P2)
|
||||
|
||||
#### 2.3.1 数据导出
|
||||
- **描述**: 支持将数据导出为各种格式
|
||||
- **优先级**: P2
|
||||
- **功能**:
|
||||
- 导出为Markdown
|
||||
- 导出为PDF
|
||||
- 导出为JSON
|
||||
|
||||
#### 2.3.2 数据统计
|
||||
- **描述**: 展示使用统计
|
||||
- **优先级**: P2
|
||||
- **功能**:
|
||||
- OCR次数统计
|
||||
- 文档数量趋势
|
||||
- 待办完成率
|
||||
|
||||
#### 2.3.3 模板系统
|
||||
- **描述**: 预设文档/待办模板
|
||||
- **优先级**: P2
|
||||
- **功能**:
|
||||
- 创建模板
|
||||
- 应用模板
|
||||
- 模板市场
|
||||
|
||||
---
|
||||
|
||||
## 3. 非功能需求
|
||||
|
||||
### 3.1 性能要求
|
||||
- 页面首屏加载时间: < 2秒
|
||||
- API响应时间: < 500ms(不含OCR处理)
|
||||
- OCR处理时间: < 5秒(单张常规图片)
|
||||
- 并发用户: 5-10人同时使用
|
||||
- 数据容量: 单用户最多1000个文档
|
||||
|
||||
### 3.2 安全要求
|
||||
- 密码使用bcrypt加密
|
||||
- JWT Token有效期24小时
|
||||
- API Key加密存储
|
||||
- 文件上传类型验证
|
||||
- SQL注入防护
|
||||
- XSS防护
|
||||
- CORS配置
|
||||
- HTTPS部署支持
|
||||
|
||||
### 3.3 可用性要求
|
||||
- 系统可用性: 99%
|
||||
- 故障恢复时间: < 1小时
|
||||
- 数据备份频率: 每日自动备份
|
||||
|
||||
### 3.4 可维护性要求
|
||||
- 代码结构清晰,模块化
|
||||
- 完善的日志系统
|
||||
- Docker容器化部署
|
||||
- 环境变量配置
|
||||
- API文档完整
|
||||
|
||||
---
|
||||
|
||||
## 4. 技术栈
|
||||
|
||||
### 4.1 前端技术栈
|
||||
- **框架**: React 18
|
||||
- **构建工具**: Vite
|
||||
- **UI组件库**: Ant Design 5
|
||||
- **状态管理**: Zustand / React Query
|
||||
- **路由**: React Router v6
|
||||
- **HTTP客户端**: Axios
|
||||
- **截图功能**: html2canvas 或 MediaDevices API
|
||||
- **拖拽上传**: react-dropzone
|
||||
|
||||
### 4.2 后端技术栈
|
||||
- **运行时**: Node.js 18+
|
||||
- **框架**: Express.js / Fastify
|
||||
- **ORM**: Prisma
|
||||
- **数据库**: SQLite(开发) / PostgreSQL(生产)
|
||||
- **认证**: JWT
|
||||
- **文件存储**: 本地存储 / 可选OSS
|
||||
|
||||
### 4.3 OCR方案
|
||||
- **本地**: PaddleOCR (Python微服务) / Tesseract.js
|
||||
- **云端API**:
|
||||
- 百度OCR
|
||||
- 腾讯云OCR
|
||||
- 阿里云OCR
|
||||
|
||||
### 4.4 AI提供商
|
||||
- **智谱AI (GLM)**: GLM-4 / GLM-4-Flash
|
||||
- **MiniMax**: MiniMax-Pro
|
||||
- **DeepSeek**: DeepSeek-Chat / DeepSeek-Coder
|
||||
|
||||
### 4.5 部署方案
|
||||
- **容器化**: Docker + Docker Compose
|
||||
- **反向代理**: Nginx
|
||||
- **进程管理**: PM2 (开发环境)
|
||||
|
||||
---
|
||||
|
||||
## 5. 数据模型
|
||||
|
||||
### 5.1 实体关系图
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USER ||--o{ DOCUMENT : owns
|
||||
USER ||--o{ TODO : owns
|
||||
USER ||--o{ CATEGORY : owns
|
||||
USER ||--o{ TAG : owns
|
||||
USER ||--o{ IMAGE : owns
|
||||
|
||||
DOCUMENT ||--o| IMAGE : has
|
||||
DOCUMENT ||--o{ DOCUMENT_TAG : has
|
||||
DOCUMENT }|--|| CATEGORY : belongs_to
|
||||
|
||||
TODO }|--o| DOCUMENT : derived_from
|
||||
TODO }|--|| CATEGORY : belongs_to
|
||||
TODO ||--o{ TODO_TAG : has
|
||||
|
||||
TAG ||--o{ DOCUMENT_TAG : associated
|
||||
TAG ||--o{ TODO_TAG : associated
|
||||
|
||||
DOCUMENT }|--|| AI_ANALYSIS : has
|
||||
|
||||
NOTE: IMAGE.document_id 可为空,支持待处理图片独立存在
|
||||
```
|
||||
|
||||
### 5.2 核心实体
|
||||
|
||||
#### User (用户)
|
||||
| 字段 | 类型 | 说明 | 约束 |
|
||||
|------|------|------|------|
|
||||
| id | UUID | 主键 | PK |
|
||||
| username | String | 用户名 | UNIQUE, NOT NULL |
|
||||
| email | String | 邮箱 | UNIQUE |
|
||||
| password_hash | String | 密码哈希 | NOT NULL |
|
||||
| created_at | DateTime | 创建时间 | |
|
||||
| updated_at | DateTime | 更新时间 | |
|
||||
|
||||
#### Document (文档)
|
||||
| 字段 | 类型 | 说明 | 约束 |
|
||||
|------|------|------|------|
|
||||
| id | UUID | 主键 | PK |
|
||||
| user_id | UUID | 所属用户 | FK, NOT NULL |
|
||||
| title | String | 标题 | |
|
||||
| content | Text | OCR内容/编辑后内容 | NOT NULL |
|
||||
| category_id | UUID | 所属分类 | FK |
|
||||
| created_at | DateTime | 创建时间 | |
|
||||
| updated_at | DateTime | 更新时间 | |
|
||||
|
||||
#### Image (图片)
|
||||
| 字段 | 类型 | 说明 | 约束 |
|
||||
|------|------|------|------|
|
||||
| id | UUID | 主键 | PK |
|
||||
| user_id | UUID | 所属用户 | FK, NOT NULL |
|
||||
| document_id | UUID | 关联文档 | FK (可为空) |
|
||||
| file_path | String | 存储路径 | NOT NULL |
|
||||
| file_size | Integer | 文件大小 | |
|
||||
| mime_type | String | MIME类型 | |
|
||||
| ocr_result | Text | OCR原始结果 | |
|
||||
| ocr_confidence | Float | 置信度 | |
|
||||
| processing_status | Enum | 处理状态 | pending/processing/success/failed |
|
||||
| quality_score | Float | 图片质量分数 | |
|
||||
| error_message | Text | 失败原因 | |
|
||||
| created_at | DateTime | 创建时间 | |
|
||||
| updated_at | DateTime | 更新时间 | |
|
||||
|
||||
#### Todo (待办事项)
|
||||
| 字段 | 类型 | 说明 | 约束 |
|
||||
|------|------|------|------|
|
||||
| id | UUID | 主键 | PK |
|
||||
| user_id | UUID | 所属用户 | FK, NOT NULL |
|
||||
| document_id | UUID | 来源文档 | FK |
|
||||
| title | String | 标题 | NOT NULL |
|
||||
| description | Text | 描述 | |
|
||||
| priority | Enum | 优先级 | high/medium/low |
|
||||
| status | Enum | 状态 | pending(未完成)/completed(已完成)/confirmed(已确认) |
|
||||
| due_date | DateTime | 截止日期 | |
|
||||
| category_id | UUID | 所属分类 | FK |
|
||||
| completed_at | DateTime | 完成时间 | |
|
||||
| confirmed_at | DateTime | 确认时间 | |
|
||||
| created_at | DateTime | 创建时间 | |
|
||||
| updated_at | DateTime | 更新时间 | |
|
||||
|
||||
#### Category (分类)
|
||||
| 字段 | 类型 | 说明 | 约束 |
|
||||
|------|------|------|------|
|
||||
| id | UUID | 主键 | PK |
|
||||
| user_id | UUID | 所属用户 | FK, NOT NULL |
|
||||
| name | String | 分类名 | NOT NULL |
|
||||
| type | Enum | 类型 | document/todo |
|
||||
| color | String | 颜色 | |
|
||||
| icon | String | 图标 | |
|
||||
| parent_id | UUID | 父分类 | FK |
|
||||
| sort_order | Integer | 排序 | |
|
||||
| usage_count | Integer | 使用次数 | 默认0 |
|
||||
| is_ai_created | Boolean | AI创建 | 默认false |
|
||||
| created_at | DateTime | 创建时间 | |
|
||||
|
||||
#### Tag (标签)
|
||||
| 字段 | 类型 | 说明 | 约束 |
|
||||
|------|------|------|------|
|
||||
| id | UUID | 主键 | PK |
|
||||
| user_id | UUID | 所属用户 | FK, NOT NULL |
|
||||
| name | String | 标签名 | NOT NULL |
|
||||
| color | String | 颜色 | |
|
||||
| usage_count | Integer | 使用次数 | 默认0 |
|
||||
| is_ai_created | Boolean | AI创建 | 默认false |
|
||||
| created_at | DateTime | 创建时间 | |
|
||||
|
||||
#### AIAnalysis (AI分析结果)
|
||||
| 字段 | 类型 | 说明 | 约束 |
|
||||
|------|------|------|------|
|
||||
| id | UUID | 主键 | PK |
|
||||
| document_id | UUID | 关联文档 | FK, NOT NULL |
|
||||
| provider | String | AI提供商 | |
|
||||
| model | String | 模型名 | |
|
||||
| suggested_tags | JSON | 推荐标签 | |
|
||||
| suggested_category | String | 推荐分类 | |
|
||||
| summary | Text | 摘要 | |
|
||||
| raw_response | JSON | 原始响应 | |
|
||||
| created_at | DateTime | 创建时间 | |
|
||||
|
||||
#### Config (配置)
|
||||
| 字段 | 类型 | 说明 | 约束 |
|
||||
|------|------|------|------|
|
||||
| id | UUID | 主键 | PK |
|
||||
| user_id | UUID | 所属用户 | FK, NOT NULL |
|
||||
| key | String | 配置键 | NOT NULL |
|
||||
| value | JSON | 配置值 | |
|
||||
| created_at | DateTime | 创建时间 | |
|
||||
| updated_at | DateTime | 更新时间 | |
|
||||
|
||||
---
|
||||
|
||||
### 5.3 待处理图片列表
|
||||
|
||||
#### 概念说明
|
||||
当OCR失败或置信度过低时,图片不会被删除,而是保存到"待处理图片列表"中,用户可以:
|
||||
1. 查看所有待处理的图片
|
||||
2. 手动输入文字创建文档
|
||||
3. 调整图片后重新OCR
|
||||
4. 删除无用的图片
|
||||
|
||||
#### 查询逻辑
|
||||
```sql
|
||||
-- 待处理图片列表
|
||||
SELECT * FROM images
|
||||
WHERE user_id = ? AND (document_id IS NULL OR processing_status = 'failed')
|
||||
ORDER BY created_at DESC
|
||||
```
|
||||
|
||||
#### 状态流转
|
||||
```
|
||||
图片上传 → OCR处理 → 成功(创建文档) / 失败(进入待处理列表)
|
||||
待处理列表 → 手动创建文档 / 删除 / 重新OCR
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 用户界面
|
||||
|
||||
### 6.1 页面结构
|
||||
|
||||
| 页面 | 路由 | 权限 | 描述 |
|
||||
|------|------|------|------|
|
||||
| 登录/注册 | `/auth` | 公开 | 用户登录和注册 |
|
||||
| 工作台 | `/` | 需登录 | 主页面,包含快速操作和统计 |
|
||||
| 文档列表 | `/documents` | 需登录 | 文档管理页面 |
|
||||
| 文档详情 | `/documents/:id` | 需登录 | 文档编辑/查看 |
|
||||
| **待办列表** | `/todos` | 需登录 | **待办事项管理(三种状态)** |
|
||||
| **待处理图片** | `/pending-images` | 需登录 | **OCR失败的待处理图片列表** |
|
||||
| 设置 | `/settings` | 需登录 | 系统配置 |
|
||||
| 标签管理 | `/tags` | 需登录 | 标签和分类管理 |
|
||||
| 统计 | `/stats` | 需登录 | 数据统计 |
|
||||
|
||||
### 6.2 核心交互流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[用户登录] --> B[工作台]
|
||||
B --> C{选择操作}
|
||||
|
||||
C -->|截图/上传| D[图片上传]
|
||||
D --> E[OCR处理]
|
||||
E --> F{OCR结果}
|
||||
|
||||
F -->|成功且置信度高| G[显示OCR结果]
|
||||
F -->|失败/置信度低| H[保存到待处理列表]
|
||||
|
||||
G --> I{满意结果?}
|
||||
I -->|是| J[保存文档]
|
||||
I -->|需编辑| K[编辑OCR结果]
|
||||
K --> J
|
||||
|
||||
H --> L[待处理图片页]
|
||||
L --> M{处理方式}
|
||||
M -->|手动输入| N[创建文档]
|
||||
M -->|图片增强+重试| D
|
||||
M -->|删除| O[移除图片]
|
||||
|
||||
J --> P[AI智能分析]
|
||||
P --> Q{AI分析}
|
||||
Q -->|自动创建| R[新标签/新分类]
|
||||
Q -->|推荐已有| S[现有标签/分类]
|
||||
|
||||
R --> T[用户确认/修改]
|
||||
S --> T
|
||||
|
||||
T --> U{文档用途}
|
||||
U -->|待办事项| V[创建待办]
|
||||
U -->|存档| W[归档文档]
|
||||
|
||||
V --> X[设置优先级/截止日期]
|
||||
X --> Y[保存到未完成列表]
|
||||
|
||||
Y --> Z{状态流转}
|
||||
Z -->|完成| AA[移动到已完成列表]
|
||||
Z -->|确认| AB[移动到已确认列表]
|
||||
|
||||
W --> AC[选择分类]
|
||||
AC --> AD[保存完成]
|
||||
```
|
||||
|
||||
### 6.3 界面原型要点
|
||||
|
||||
#### 工作台首页
|
||||
- 顶部:快速截图按钮(突出显示)
|
||||
- 中部:最近文档 + 待办事项
|
||||
- 底部:快捷操作入口
|
||||
|
||||
#### 文档编辑页
|
||||
- 左侧:图片预览 + OCR原始结果
|
||||
- 右侧:可编辑文本区域
|
||||
- 底部:AI分析按钮 + 标签选择 + 操作按钮
|
||||
|
||||
#### 待办管理页(三种状态列表)
|
||||
- **顶部**: Tab切换(未完成 / 已完成 / 已确认)
|
||||
- **筛选器**: 优先级、分类、标签、截止日期
|
||||
- **未完成列表**:
|
||||
- 待办卡片显示:标题、描述、优先级标签、截止日期
|
||||
- 操作:编辑、标记完成、删除
|
||||
- 支持拖拽排序
|
||||
- **已完成列表**:
|
||||
- 已完成的待办,显示完成时间
|
||||
- 操作:撤销(回到未完成)、确认归档、删除
|
||||
- 批量确认操作
|
||||
- **已确认列表**:
|
||||
- 归档的待办,只读查看
|
||||
- 支持导出、批量删除
|
||||
- 显示确认时间
|
||||
|
||||
#### 待处理图片页
|
||||
- **顶部**: 统计信息(待处理数量、本周新增)
|
||||
- **图片网格**: 显示所有待处理图片
|
||||
- **图片卡片操作**:
|
||||
- 预览图片
|
||||
- 手动创建文档(打开编辑对话框)
|
||||
- 图片增强(旋转、裁剪、亮度)后重新OCR
|
||||
- 删除图片
|
||||
- **批量操作**: 全选后批量删除
|
||||
|
||||
#### 文档详情页
|
||||
- **左侧**: 图片预览 + OCR原始结果
|
||||
- **右侧**: 可编辑文本区域
|
||||
- **底部/侧边**:
|
||||
- AI分析按钮
|
||||
- 动态标签展示(常用标签优先)
|
||||
- 动态分类展示(AI推荐分类置顶)
|
||||
- 转为待办按钮
|
||||
|
||||
---
|
||||
|
||||
## 7. API设计
|
||||
|
||||
### 7.1 认证相关
|
||||
```
|
||||
POST /api/auth/register # 用户注册
|
||||
POST /api/auth/login # 用户登录
|
||||
POST /api/auth/logout # 用户登出
|
||||
GET /api/auth/me # 获取当前用户信息
|
||||
```
|
||||
|
||||
### 7.2 文档相关
|
||||
```
|
||||
GET /api/documents # 获取文档列表
|
||||
POST /api/documents # 创建文档
|
||||
GET /api/documents/:id # 获取文档详情
|
||||
PUT /api/documents/:id # 更新文档
|
||||
DELETE /api/documents/:id # 删除文档
|
||||
GET /api/documents/:id/image # 获取关联图片
|
||||
```
|
||||
|
||||
### 7.3 OCR相关
|
||||
```
|
||||
POST /api/ocr/upload # 上传图片并OCR
|
||||
POST /api/ocr/analyze # 对已有图片重新OCR
|
||||
GET /api/ocr/status/:taskId # 查询OCR任务状态
|
||||
POST /api/ocr/enhance # 图片增强后重新OCR
|
||||
GET /api/ocr/pending # 获取待处理图片列表
|
||||
DELETE /api/ocr/pending/:id # 删除待处理图片
|
||||
POST /api/ocr/pending/:id/manual-create # 手动创建文档
|
||||
```
|
||||
|
||||
### 7.4 待办相关
|
||||
```
|
||||
GET /api/todos # 获取待办列表(支持状态筛选)
|
||||
POST /api/todos # 创建待办
|
||||
GET /api/todos/:id # 获取待办详情
|
||||
PUT /api/todos/:id # 更新待办
|
||||
DELETE /api/todos/:id # 删除待办
|
||||
PATCH /api/todos/:id/complete # 标记完成
|
||||
PATCH /api/todos/:id/confirm # 标记确认
|
||||
PATCH /api/todos/:id/reopen # 撤销到未完成
|
||||
POST /api/todos/batch-complete # 批量完成
|
||||
POST /api/todos/batch-confirm # 批量确认
|
||||
```
|
||||
|
||||
### 7.5 AI分析相关
|
||||
```
|
||||
POST /api/ai/analyze # AI分析文档(标签+分类)
|
||||
POST /api/ai/suggest-tags # 获取标签建议(可创建新标签)
|
||||
POST /api/ai/suggest-category # 获取分类建议(可创建新分类)
|
||||
POST /api/ai/detect-type # AI检测文档类型
|
||||
GET /api/ai/smart-suggestions # 获取智能推荐(基于历史)
|
||||
```
|
||||
|
||||
### 7.6 分类与标签
|
||||
```
|
||||
GET /api/categories # 获取分类列表
|
||||
POST /api/categories # 创建分类
|
||||
PUT /api/categories/:id # 更新分类
|
||||
DELETE /api/categories/:id # 删除分类
|
||||
|
||||
GET /api/tags # 获取标签列表
|
||||
POST /api/tags # 创建标签
|
||||
PUT /api/tags/:id # 更新标签
|
||||
DELETE /api/tags/:id # 删除标签
|
||||
```
|
||||
|
||||
### 7.7 配置相关
|
||||
```
|
||||
GET /api/config # 获取配置
|
||||
PUT /api/config # 更新配置
|
||||
POST /api/config/test # 测试配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 开发计划
|
||||
|
||||
### 8.1 里程碑
|
||||
|
||||
| 里程碑 | 预计完成 | 交付物 |
|
||||
|--------|----------|--------|
|
||||
| M1: 基础框架搭建 | 第1周 | 项目脚手架、数据库设计、基础API |
|
||||
| M2: 核心功能开发 | 第2-3周 | OCR识别、文档CRUD、用户认证 |
|
||||
| M3: AI集成 | 第4周 | AI分析功能、标签分类 |
|
||||
| M4: 待办管理 | 第5周 | 待办CRUD、优先级截止日期 |
|
||||
| M5: 完善与优化 | 第6周 | UI优化、测试、文档 |
|
||||
|
||||
### 8.2 任务分解
|
||||
|
||||
#### Sprint 1: 基础架构
|
||||
- 搭建React + Vite项目
|
||||
- 搭建Express后端项目
|
||||
- 设计数据库Schema (Prisma)
|
||||
- 实现JWT认证
|
||||
- Docker配置文件编写
|
||||
- **估算**: 3-5天
|
||||
|
||||
#### Sprint 2: 图片与OCR
|
||||
- 实现图片上传功能
|
||||
- 集成本地OCR (PaddleOCR)
|
||||
- 集成云端OCR API
|
||||
- 建立图片-文档关联
|
||||
- OCR结果编辑功能
|
||||
- **估算**: 5-7天
|
||||
- **依赖**: Sprint 1
|
||||
|
||||
#### Sprint 3: 文档管理
|
||||
- 文档CRUD API
|
||||
- 文档列表UI
|
||||
- 文档详情/编辑页
|
||||
- 搜索功能
|
||||
- **估算**: 3-4天
|
||||
- **依赖**: Sprint 2
|
||||
|
||||
#### Sprint 4: AI集成
|
||||
- AI提供商抽象层设计
|
||||
- 集成GLM API
|
||||
- 集成MiniMax API
|
||||
- 集成DeepSeek API
|
||||
- 标签分类生成逻辑
|
||||
- **估算**: 5-7天
|
||||
- **依赖**: Sprint 3
|
||||
|
||||
#### Sprint 5: 待办管理
|
||||
- 待办数据模型
|
||||
- 待办CRUD API和UI
|
||||
- 优先级和截止日期
|
||||
- 状态管理
|
||||
- 待办分类
|
||||
- **估算**: 4-5天
|
||||
- **依赖**: Sprint 3
|
||||
|
||||
#### Sprint 6: 配置与优化
|
||||
- 配置管理页面
|
||||
- OCR/AI提供商配置
|
||||
- UI/UX优化
|
||||
- 性能优化
|
||||
- 错误处理完善
|
||||
- **估算**: 3-4天
|
||||
|
||||
---
|
||||
|
||||
## 9. 风险评估
|
||||
|
||||
| 风险 | 可能性 | 影响 | 缓解措施 |
|
||||
|------|--------|------|----------|
|
||||
| OCR准确率不达标 | 中 | 高 | 同时支持多个OCR提供商,允许用户选择 |
|
||||
| AI API成本过高 | 中 | 中 | 提供本地模型选项,优化prompt减少token |
|
||||
| 浏览器截图权限限制 | 高 | 中 | 提供本地文件上传作为替代方案 |
|
||||
| 本地OCR性能问题 | 中 | 中 | 使用GPU加速,或默认使用云端API |
|
||||
| 多用户数据隔离问题 | 低 | 高 | 严格的中间件验证,充分的测试 |
|
||||
| AI提供商API变更 | 中 | 中 | 抽象层设计,便于切换提供商 |
|
||||
|
||||
---
|
||||
|
||||
## 10. Docker部署方案
|
||||
|
||||
### 10.1 服务架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Nginx (80/443) │
|
||||
├─────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────────┐ │
|
||||
│ │ React │ │ Express API │ │
|
||||
│ │ Frontend │ │ Backend │ │
|
||||
│ └─────────────┘ └─────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────┐ ┌──────▼──────┐ │
|
||||
│ │ PaddleOCR │ │ Database │ │
|
||||
│ │ (Optional) │ │ (SQLite/ │ │
|
||||
│ │ │ │ PG) │ │
|
||||
│ └─────────────┘ └─────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 10.2 Docker Compose 配置
|
||||
|
||||
```yaml
|
||||
services:
|
||||
frontend:
|
||||
build: ./frontend
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
backend:
|
||||
build: ./backend
|
||||
ports:
|
||||
- "4000:4000"
|
||||
environment:
|
||||
- DATABASE_URL=file:./dev.db
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
volumes:
|
||||
- ./uploads:/app/uploads
|
||||
- ./data:/app/data
|
||||
|
||||
ocr-service:
|
||||
build: ./ocr-service
|
||||
ports:
|
||||
- "5000:5000"
|
||||
profiles:
|
||||
- local-ocr
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
depends_on:
|
||||
- frontend
|
||||
- backend
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 后续扩展方向
|
||||
|
||||
1. **移动端适配**: 响应式设计或PWA
|
||||
2. **协作功能**: 分享文档、多人协作编辑
|
||||
3. **语音输入**: 支持语音转文字后处理
|
||||
4. **智能提醒**: 基于待办的智能提醒
|
||||
5. **知识图谱**: 构建文档间的关联关系
|
||||
6. **版本控制**: 文档修改历史和版本回溯
|
||||
7. **插件系统**: 支持自定义扩展
|
||||
8. **API开放**: 提供开放API供第三方集成
|
||||
|
||||
---
|
||||
|
||||
## 12. 附录
|
||||
|
||||
### 12.1 参考资料
|
||||
- [PaddleOCR文档](https://github.com/PaddlePaddle/PaddleOCR)
|
||||
- [智谱AI开放平台](https://open.bigmodel.cn/)
|
||||
- [MiniMax API文档](https://www.minimaxi.com/)
|
||||
- [DeepSeek API文档](https://platform.deepseek.com/)
|
||||
- [Ant Design React](https://ant.design/)
|
||||
|
||||
### 12.2 术语表
|
||||
- **OCR**: Optical Character Recognition,光学字符识别
|
||||
- **GLM**: General Language Model,智谱AI的大语言模型
|
||||
- **JWT**: JSON Web Token,用于身份验证的令牌
|
||||
- **CRUD**: Create, Read, Update, Delete,增删改查
|
||||
- **Docker**: 容器化部署技术
|
||||
|
||||
---
|
||||
|
||||
## 需求确认
|
||||
|
||||
我已经整理了完整的需求文档。请确认:
|
||||
|
||||
1. **功能完整性** - 是否有遗漏的功能?
|
||||
2. **优先级** - P0/P1/P2 的划分是否合理?
|
||||
3. **可行性** - 技术方案和时间估算是否可行?
|
||||
4. **其他** - 还有其他需要补充的吗?
|
||||
|
||||
如果确认无误,请回复 **"确认"**,我将进入开发规划阶段。
|
||||
如果需要修改,请告诉我具体需要调整的地方。
|
||||
949
.project/sprints/sprint-1.md
Normal file
949
.project/sprints/sprint-1.md
Normal file
@@ -0,0 +1,949 @@
|
||||
# Sprint 1: 基础架构 - 详细计划
|
||||
|
||||
**时间**: Days 1-5 (2026-02-21 ~ 2026-02-26)
|
||||
**目标**: 搭建项目基础架构,实现用户认证系统
|
||||
**状态**: 🔄 进行中
|
||||
|
||||
---
|
||||
|
||||
## Sprint 目标
|
||||
|
||||
### 主要目标
|
||||
- ✅ 初始化前后端项目结构
|
||||
- ✅ 设计并创建数据库Schema
|
||||
- ✅ 实现用户认证系统(注册、登录、JWT)
|
||||
- ✅ 搭建基础API框架
|
||||
- ✅ 配置Docker环境
|
||||
|
||||
### 验收标准
|
||||
- [ ] 所有测试通过(单元+集成)
|
||||
- [ ] 代码覆盖率 ≥ 80%
|
||||
- [ ] 可以通过API完成注册登录
|
||||
- [ ] Docker一键启动成功
|
||||
|
||||
---
|
||||
|
||||
## 任务列表
|
||||
|
||||
### Task 1.1: 项目初始化 (0.5天)
|
||||
|
||||
**负责人**: -
|
||||
**优先级**: P0
|
||||
**依赖**: 无
|
||||
|
||||
#### 子任务
|
||||
- [ ] 创建前端项目 (React + Vite + TypeScript)
|
||||
- [ ] 创建后端项目 (Express + TypeScript)
|
||||
- [ ] 配置Prisma ORM
|
||||
- [ ] 配置测试框架 (Jest + Vitest)
|
||||
- [ ] 配置ESLint + Prettier
|
||||
- [ ] 创建.gitignore
|
||||
|
||||
#### 测试任务
|
||||
```typescript
|
||||
// tests/config/build.config.test.ts
|
||||
describe('Build Configuration', () => {
|
||||
it('should have valid package.json', () => {
|
||||
// 验证依赖、脚本等
|
||||
});
|
||||
|
||||
it('should compile TypeScript without errors', () => {
|
||||
// 验证TypeScript配置
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### Ralph 问题
|
||||
- **开始前**: 我是否需要所有这些依赖?
|
||||
- **实现中**: 配置是否最小化?
|
||||
- **完成后**: 项目结构是否清晰?
|
||||
|
||||
#### 验收标准
|
||||
- [ ] `npm install` 成功
|
||||
- [ ] `npm run build` 成功
|
||||
- [ ] `npm run test` 运行成功
|
||||
- [ ] 目录结构符合规范
|
||||
|
||||
---
|
||||
|
||||
### Task 1.2: 数据库Schema设计 (1天)
|
||||
|
||||
**负责人**: -
|
||||
**优先级**: P0
|
||||
**依赖**: Task 1.1
|
||||
|
||||
#### 子任务
|
||||
- [ ] 定义Prisma Schema
|
||||
- [ ] User模型
|
||||
- [ ] Document模型
|
||||
- [ ] Image模型
|
||||
- [ ] Todo模型
|
||||
- [ ] Category模型
|
||||
- [ ] Tag模型
|
||||
- [ ] AIAnalysis模型
|
||||
- [ ] Config模型
|
||||
- [ ] 定义实体关系
|
||||
- [ ] 添加索引
|
||||
- [ ] 创建Migration
|
||||
- [ ] 创建Seed脚本
|
||||
|
||||
#### Prisma Schema (草案)
|
||||
```prisma
|
||||
// prisma/schema.prisma
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
username String @unique
|
||||
email String? @unique
|
||||
password_hash String
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
documents Document[]
|
||||
todos Todo[]
|
||||
categories Category[]
|
||||
tags Tag[]
|
||||
images Image[]
|
||||
configs Config[]
|
||||
}
|
||||
|
||||
model Document {
|
||||
id String @id @default(uuid())
|
||||
user_id String
|
||||
title String?
|
||||
content String
|
||||
category_id String?
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
user User @relation(fields: [user_id], references: [id])
|
||||
category Category? @relation(fields: [category_id], references: [id])
|
||||
images Image[]
|
||||
aiAnalysis AIAnalysis?
|
||||
|
||||
@@index([user_id])
|
||||
@@index([category_id])
|
||||
}
|
||||
|
||||
model Image {
|
||||
id String @id @default(uuid())
|
||||
user_id String
|
||||
document_id String?
|
||||
file_path String
|
||||
file_size Int
|
||||
mime_type String
|
||||
ocr_result String?
|
||||
ocr_confidence Float?
|
||||
processing_status String @default("pending") // pending/processing/success/failed
|
||||
quality_score Float?
|
||||
error_message String?
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
user User @relation(fields: [user_id], references: [id])
|
||||
document Document? @relation(fields: [document_id], references: [id])
|
||||
|
||||
@@index([user_id])
|
||||
@@index([processing_status])
|
||||
}
|
||||
|
||||
model Todo {
|
||||
id String @id @default(uuid())
|
||||
user_id String
|
||||
document_id String?
|
||||
title String
|
||||
description String?
|
||||
priority String @default("medium") // high/medium/low
|
||||
status String @default("pending") // pending/completed/confirmed
|
||||
due_date DateTime?
|
||||
category_id String?
|
||||
completed_at DateTime?
|
||||
confirmed_at DateTime?
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
user User @relation(fields: [user_id], references: [id])
|
||||
document Document? @relation(fields: [document_id], references: [id])
|
||||
category Category? @relation(fields: [category_id], references: [id])
|
||||
|
||||
@@index([user_id])
|
||||
@@index([status])
|
||||
@@index([category_id])
|
||||
}
|
||||
|
||||
model Category {
|
||||
id String @id @default(uuid())
|
||||
user_id String
|
||||
name String
|
||||
type String // document/todo
|
||||
color String?
|
||||
icon String?
|
||||
parent_id String?
|
||||
sort_order Int @default(0)
|
||||
usage_count Int @default(0)
|
||||
is_ai_created Boolean @default(false)
|
||||
created_at DateTime @default(now())
|
||||
|
||||
user User @relation(fields: [user_id], references: [id])
|
||||
parent Category? @relation("CategoryToCategory", fields: [parent_id], references: [id])
|
||||
children Category[] @relation("CategoryToCategory")
|
||||
documents Document[]
|
||||
todos Todo[]
|
||||
|
||||
@@index([user_id])
|
||||
@@index([type])
|
||||
}
|
||||
|
||||
model Tag {
|
||||
id String @id @default(uuid())
|
||||
user_id String
|
||||
name String
|
||||
color String?
|
||||
usage_count Int @default(0)
|
||||
is_ai_created Boolean @default(false)
|
||||
created_at DateTime @default(now())
|
||||
|
||||
user User @relation(fields: [user_id], references: [id])
|
||||
|
||||
@@unique([user_id, name])
|
||||
@@index([user_id])
|
||||
}
|
||||
|
||||
model AIAnalysis {
|
||||
id String @id @default(uuid())
|
||||
document_id String @unique
|
||||
provider String
|
||||
model String
|
||||
suggested_tags String // JSON
|
||||
suggested_category String?
|
||||
summary String?
|
||||
raw_response String // JSON
|
||||
created_at DateTime @default(now())
|
||||
|
||||
document Document @relation(fields: [document_id], references: [id])
|
||||
}
|
||||
|
||||
model Config {
|
||||
id String @id @default(uuid())
|
||||
user_id String
|
||||
key String
|
||||
value String // JSON
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
user User @relation(fields: [user_id], references: [id])
|
||||
|
||||
@@unique([user_id, key])
|
||||
@@index([user_id])
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试任务
|
||||
```typescript
|
||||
// tests/database/schema.test.ts
|
||||
describe('Database Schema', () => {
|
||||
beforeAll(async () => {
|
||||
await prisma.$executeRawUnsafe('DELETE FROM User');
|
||||
});
|
||||
|
||||
describe('User Model', () => {
|
||||
it('should create user with valid data', async () => {
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
password_hash: 'hash123'
|
||||
}
|
||||
});
|
||||
|
||||
expect(user).toHaveProperty('id');
|
||||
expect(user.username).toBe('testuser');
|
||||
});
|
||||
|
||||
it('should enforce unique username', async () => {
|
||||
await prisma.user.create({
|
||||
data: { username: 'duplicate', email: 'a@test.com', password_hash: 'hash' }
|
||||
});
|
||||
|
||||
await expect(
|
||||
prisma.user.create({
|
||||
data: { username: 'duplicate', email: 'b@test.com', password_hash: 'hash' }
|
||||
})
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should hash password (application layer)', async () => {
|
||||
// This tests the PasswordService, not Prisma directly
|
||||
const hash = await PasswordService.hash('password123');
|
||||
expect(hash).not.toBe('password123');
|
||||
expect(hash.length).toBe(60);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Image Model', () => {
|
||||
it('should allow image without document', async () => {
|
||||
const image = await prisma.image.create({
|
||||
data: {
|
||||
user_id: userId,
|
||||
file_path: '/path/to/image.png',
|
||||
file_size: 1024,
|
||||
mime_type: 'image/png',
|
||||
document_id: null
|
||||
}
|
||||
});
|
||||
|
||||
expect(image.document_id).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Todo Status', () => {
|
||||
it('should support three states', async () => {
|
||||
const statuses = ['pending', 'completed', 'confirmed'];
|
||||
for (const status of statuses) {
|
||||
const todo = await prisma.todo.create({
|
||||
data: {
|
||||
user_id: userId,
|
||||
title: `Test ${status}`,
|
||||
status: status as any
|
||||
}
|
||||
});
|
||||
expect(todo.status).toBe(status);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### Ralph 问题
|
||||
- **开始前**: 数据模型是否完整?
|
||||
- **实现中**: 关系设计是否正确?
|
||||
- **完成后**: 索引是否足够?
|
||||
|
||||
#### 验收标准
|
||||
- [ ] Migration成功执行
|
||||
- [ ] Seed脚本运行成功
|
||||
- [ ] 所有测试通过
|
||||
- [ ] 数据可以正确CRUD
|
||||
|
||||
---
|
||||
|
||||
### Task 1.3: 用户认证系统 (1.5天)
|
||||
|
||||
**负责人**: -
|
||||
**优先级**: P0
|
||||
**依赖**: Task 1.2
|
||||
|
||||
#### 子任务
|
||||
- [ ] PasswordService (bcrypt)
|
||||
- [ ] AuthService (JWT)
|
||||
- [ ] UserService (CRUD)
|
||||
- [ ] AuthController
|
||||
- [ ] 认证中间件
|
||||
- [ ] 注册API
|
||||
- [ ] 登录API
|
||||
- [ ] 登出API
|
||||
- [ ] 获取当前用户API
|
||||
|
||||
#### 测试任务
|
||||
|
||||
**密码服务测试**
|
||||
```typescript
|
||||
// tests/services/password.service.test.ts
|
||||
describe('PasswordService', () => {
|
||||
describe('hash', () => {
|
||||
it('should hash password with bcrypt', async () => {
|
||||
const plainPassword = 'MySecurePassword123!';
|
||||
const hash = await PasswordService.hash(plainPassword);
|
||||
|
||||
expect(hash).toBeDefined();
|
||||
expect(hash).not.toBe(plainPassword);
|
||||
expect(hash.length).toBe(60); // bcrypt hash length
|
||||
});
|
||||
|
||||
it('should generate different hashes for same password', async () => {
|
||||
const password = 'test123';
|
||||
const hash1 = await PasswordService.hash(password);
|
||||
const hash2 = await PasswordService.hash(password);
|
||||
|
||||
expect(hash1).not.toBe(hash2); // salt is different
|
||||
});
|
||||
|
||||
it('should handle empty string', async () => {
|
||||
const hash = await PasswordService.hash('');
|
||||
expect(hash).toBeDefined();
|
||||
expect(hash.length).toBe(60);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verify', () => {
|
||||
it('should verify correct password', async () => {
|
||||
const password = 'test123';
|
||||
const hash = await PasswordService.hash(password);
|
||||
const isValid = await PasswordService.verify(password, hash);
|
||||
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject wrong password', async () => {
|
||||
const hash = await PasswordService.hash('test123');
|
||||
const isValid = await PasswordService.verify('wrong', hash);
|
||||
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject invalid hash format', async () => {
|
||||
await expect(
|
||||
PasswordService.verify('test', 'invalid-hash')
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**JWT服务测试**
|
||||
```typescript
|
||||
// tests/services/auth.service.test.ts
|
||||
describe('AuthService', () => {
|
||||
describe('generateToken', () => {
|
||||
it('should generate valid JWT token', () => {
|
||||
const payload = { user_id: 'user-123' };
|
||||
const token = AuthService.generateToken(payload);
|
||||
|
||||
expect(token).toBeDefined();
|
||||
expect(typeof token).toBe('string');
|
||||
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any;
|
||||
expect(decoded.user_id).toBe('user-123');
|
||||
});
|
||||
|
||||
it('should set appropriate expiration', () => {
|
||||
const token = AuthService.generateToken({ user_id: 'test' });
|
||||
const decoded = jwt.decode(token) as any;
|
||||
|
||||
// Verify expiration is set (24 hours)
|
||||
const exp = decoded.exp;
|
||||
const iat = decoded.iat;
|
||||
expect(exp - iat).toBe(24 * 60 * 60); // 24 hours in seconds
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyToken', () => {
|
||||
it('should verify valid token', () => {
|
||||
const payload = { user_id: 'user-123' };
|
||||
const token = AuthService.generateToken(payload);
|
||||
const decoded = AuthService.verifyToken(token);
|
||||
|
||||
expect(decoded.user_id).toBe('user-123');
|
||||
});
|
||||
|
||||
it('should reject expired token', () => {
|
||||
const expiredToken = jwt.sign(
|
||||
{ user_id: 'test' },
|
||||
process.env.JWT_SECRET!,
|
||||
{ expiresIn: '0s' }
|
||||
);
|
||||
|
||||
// Wait a moment for token to expire
|
||||
setTimeout(() => {
|
||||
expect(() => AuthService.verifyToken(expiredToken))
|
||||
.toThrow();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('should reject malformed token', () => {
|
||||
expect(() => AuthService.verifyToken('not-a-token'))
|
||||
.toThrow();
|
||||
});
|
||||
|
||||
it('should reject token with wrong secret', () => {
|
||||
const token = jwt.sign({ user_id: 'test' }, 'wrong-secret');
|
||||
expect(() => AuthService.verifyToken(token))
|
||||
.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**认证API集成测试**
|
||||
```typescript
|
||||
// tests/integration/auth.api.test.ts
|
||||
describe('Auth API', () => {
|
||||
describe('POST /api/auth/register', () => {
|
||||
it('should register new user', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/auth/register')
|
||||
.send({
|
||||
username: 'newuser',
|
||||
email: 'new@test.com',
|
||||
password: 'password123'
|
||||
});
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.data).toHaveProperty('token');
|
||||
expect(response.body.data.user).toHaveProperty('id');
|
||||
expect(response.body.data.user.username).toBe('newuser');
|
||||
expect(response.body.data.user).not.toHaveProperty('password_hash');
|
||||
});
|
||||
|
||||
it('should reject duplicate username', async () => {
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
username: 'existing',
|
||||
email: 'existing@test.com',
|
||||
password_hash: 'hash'
|
||||
}
|
||||
});
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/auth/register')
|
||||
.send({
|
||||
username: 'existing',
|
||||
email: 'another@test.com',
|
||||
password: 'password123'
|
||||
});
|
||||
|
||||
expect(response.status).toBe(409);
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toContain('用户名已存在');
|
||||
});
|
||||
|
||||
it('should reject weak password', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/auth/register')
|
||||
.send({
|
||||
username: 'test',
|
||||
email: 'test@test.com',
|
||||
password: '123' // too short
|
||||
});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body.success).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject missing fields', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/auth/register')
|
||||
.send({
|
||||
username: 'test'
|
||||
// missing email and password
|
||||
});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/auth/login', () => {
|
||||
beforeEach(async () => {
|
||||
const hash = await PasswordService.hash('password123');
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
username: 'loginuser',
|
||||
email: 'login@test.com',
|
||||
password_hash: hash
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should login with correct credentials', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/auth/login')
|
||||
.send({
|
||||
username: 'loginuser',
|
||||
password: 'password123'
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.data).toHaveProperty('token');
|
||||
});
|
||||
|
||||
it('should reject wrong password', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/auth/login')
|
||||
.send({
|
||||
username: 'loginuser',
|
||||
password: 'wrongpassword'
|
||||
});
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
expect(response.body.success).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject non-existent user', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/auth/login')
|
||||
.send({
|
||||
username: 'nonexistent',
|
||||
password: 'password123'
|
||||
});
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/auth/me', () => {
|
||||
it('should return current user with valid token', async () => {
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username: 'meuser',
|
||||
email: 'me@test.com',
|
||||
password_hash: 'hash'
|
||||
}
|
||||
});
|
||||
|
||||
const token = AuthService.generateToken({ user_id: user.id });
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/auth/me')
|
||||
.set('Authorization', `Bearer ${token}`);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.data.id).toBe(user.id);
|
||||
expect(response.body.data.username).toBe('meuser');
|
||||
});
|
||||
|
||||
it('should reject request without token', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/auth/me');
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
});
|
||||
|
||||
it('should reject request with invalid token', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/auth/me')
|
||||
.set('Authorization', 'Bearer invalid-token');
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Data Isolation', () => {
|
||||
it('should not allow user to access other user data', async () => {
|
||||
const user1 = await createTestUser('user1');
|
||||
const user2 = await createTestUser('user2');
|
||||
|
||||
// Create document for user1
|
||||
await prisma.document.create({
|
||||
data: {
|
||||
user_id: user1.id,
|
||||
content: 'User 1 document'
|
||||
}
|
||||
});
|
||||
|
||||
// Try to access with user2 token
|
||||
const token = AuthService.generateToken({ user_id: user2.id });
|
||||
const response = await request(app)
|
||||
.get('/api/documents')
|
||||
.set('Authorization', `Bearer ${token}`);
|
||||
|
||||
// Should only return user2's documents (empty)
|
||||
expect(response.body.data).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### Ralph 问题
|
||||
- **开始前**: 安全性考虑是否充分?
|
||||
- **实现中**: Token过期是否正确处理?
|
||||
- **完成后**: 数据隔离是否验证?
|
||||
|
||||
#### 验收标准
|
||||
- [ ] 密码使用bcrypt加密
|
||||
- [ ] JWT生成和验证正确
|
||||
- [ ] 所有API测试通过
|
||||
- [ ] 数据隔离验证通过
|
||||
- [ ] 代码覆盖率 ≥ 85%
|
||||
|
||||
---
|
||||
|
||||
### Task 1.4: 基础API框架 (1天)
|
||||
|
||||
**负责人**: -
|
||||
**优先级**: P0
|
||||
**依赖**: Task 1.3
|
||||
|
||||
#### 子任务
|
||||
- [ ] 统一响应格式中间件
|
||||
- [ ] 错误处理中间件
|
||||
- [ ] 请求验证中间件
|
||||
- [ ] CORS配置
|
||||
- [ ] 日志中间件
|
||||
- [ ] 请求日志
|
||||
|
||||
#### 测试任务
|
||||
```typescript
|
||||
// tests/middleware/response-format.test.ts
|
||||
describe('Response Format Middleware', () => {
|
||||
it('should format success response', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/test-success');
|
||||
|
||||
expect(response.body).toEqual({
|
||||
success: true,
|
||||
data: { message: 'test' }
|
||||
});
|
||||
});
|
||||
|
||||
it('should format error response', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/test-error');
|
||||
|
||||
expect(response.body).toEqual({
|
||||
success: false,
|
||||
error: expect.any(String)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handler Middleware', () => {
|
||||
it('should handle 404 errors', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/non-existent');
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.body.success).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle validation errors', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/auth/register')
|
||||
.send({ username: '' }); // invalid
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CORS Middleware', () => {
|
||||
it('should set CORS headers', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/test')
|
||||
.set('Origin', 'http://localhost:3000');
|
||||
|
||||
expect(response.headers['access-control-allow-origin']).toBeDefined();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### Ralph 问题
|
||||
- **开始前**: API设计是否RESTful?
|
||||
- **实现中**: 错误处理是否统一?
|
||||
- **完成后**: 响应格式是否一致?
|
||||
|
||||
#### 验收标准
|
||||
- [ ] 统一响应格式
|
||||
- [ ] 错误正确处理
|
||||
- [ ] CORS正确配置
|
||||
- [ ] 日志正常输出
|
||||
|
||||
---
|
||||
|
||||
### Task 1.5: Docker配置 (0.5天)
|
||||
|
||||
**负责人**: -
|
||||
**优先级**: P1
|
||||
**依赖**: Task 1.4
|
||||
|
||||
#### 子任务
|
||||
- [ ] 后端Dockerfile
|
||||
- [ ] 前端Dockerfile
|
||||
- [ ] docker-compose.yml
|
||||
- [ ] .dockerignore
|
||||
- [ ] 启动脚本
|
||||
|
||||
#### Dockerfile示例
|
||||
|
||||
**后端 Dockerfile**
|
||||
```dockerfile
|
||||
# backend/Dockerfile
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
|
||||
# Copy source
|
||||
COPY . .
|
||||
|
||||
# Generate Prisma Client
|
||||
RUN npx prisma generate
|
||||
|
||||
# Expose port
|
||||
EXPOSE 4000
|
||||
|
||||
# Start server
|
||||
CMD ["npm", "start"]
|
||||
```
|
||||
|
||||
**前端 Dockerfile**
|
||||
```dockerfile
|
||||
# frontend/Dockerfile
|
||||
FROM node:18-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
**docker-compose.yml**
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
backend:
|
||||
build: ./backend
|
||||
ports:
|
||||
- "4000:4000"
|
||||
environment:
|
||||
- DATABASE_URL=file:./dev.db
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
- NODE_ENV=production
|
||||
volumes:
|
||||
- ./backend/uploads:/app/uploads
|
||||
- ./backend/data:/app/data
|
||||
|
||||
frontend:
|
||||
build: ./frontend
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
- backend
|
||||
```
|
||||
|
||||
#### 测试任务
|
||||
```bash
|
||||
# 测试Docker构建
|
||||
docker-compose build
|
||||
|
||||
# 测试启动
|
||||
docker-compose up -d
|
||||
|
||||
# 验证服务
|
||||
curl http://localhost:4000/api/health
|
||||
curl http://localhost/
|
||||
```
|
||||
|
||||
#### Ralph 问题
|
||||
- **开始前**: 需要多少容器?
|
||||
- **实现中**: 数据卷是否持久化?
|
||||
- **完成后**: 是否可以一键启动?
|
||||
|
||||
#### 验收标准
|
||||
- [ ] Docker构建成功
|
||||
- [ ] docker-compose启动成功
|
||||
- [ ] 服务正常访问
|
||||
- [ ] 数据持久化
|
||||
|
||||
---
|
||||
|
||||
## Sprint 回顾
|
||||
|
||||
### 完成情况
|
||||
- [ ] Task 1.1: 项目初始化
|
||||
- [ ] Task 1.2: 数据库Schema设计
|
||||
- [ ] Task 1.3: 用户认证系统
|
||||
- [ ] Task 1.4: 基础API框架
|
||||
- [ ] Task 1.5: Docker配置
|
||||
|
||||
### 测试覆盖率
|
||||
| 模块 | 目标 | 实际 | 状态 |
|
||||
|------|------|------|------|
|
||||
| 密码服务 | 90% | - | - |
|
||||
| JWT服务 | 90% | - | - |
|
||||
| 认证API | 85% | - | - |
|
||||
| 中间件 | 80% | - | - |
|
||||
| 数据库 | 80% | - | - |
|
||||
|
||||
### 风险与问题
|
||||
| 问题 | 影响 | 状态 | 解决方案 |
|
||||
|------|------|------|----------|
|
||||
| - | - | - | - |
|
||||
|
||||
### 下一步
|
||||
- Sprint 2: 图片与OCR功能
|
||||
|
||||
---
|
||||
|
||||
## 附录
|
||||
|
||||
### 目录结构
|
||||
```
|
||||
picAnalysis/
|
||||
├── backend/
|
||||
│ ├── prisma/
|
||||
│ │ ├── schema.prisma
|
||||
│ │ └── migrations/
|
||||
│ ├── src/
|
||||
│ │ ├── controllers/
|
||||
│ │ ├── services/
|
||||
│ │ ├── middleware/
|
||||
│ │ ├── routes/
|
||||
│ │ ├── utils/
|
||||
│ │ └── index.ts
|
||||
│ ├── tests/
|
||||
│ │ ├── unit/
|
||||
│ │ └── integration/
|
||||
│ ├── package.json
|
||||
│ ├── tsconfig.json
|
||||
│ └── Dockerfile
|
||||
├── frontend/
|
||||
│ ├── src/
|
||||
│ │ ├── components/
|
||||
│ │ ├── pages/
|
||||
│ │ ├── services/
|
||||
│ │ ├── hooks/
|
||||
│ │ ├── utils/
|
||||
│ │ └── main.tsx
|
||||
│ ├── tests/
|
||||
│ ├── package.json
|
||||
│ ├── vite.config.ts
|
||||
│ └── Dockerfile
|
||||
├── .project/
|
||||
│ ├── requirements.md
|
||||
│ ├── development-plan.md
|
||||
│ └── sprints/
|
||||
├── docker-compose.yml
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### 环境变量模板
|
||||
```env
|
||||
# .env.example
|
||||
DATABASE_URL="file:./dev.db"
|
||||
JWT_SECRET="your-secret-key-here"
|
||||
NODE_ENV="development"
|
||||
PORT=4000
|
||||
|
||||
# OCR
|
||||
OCR_PROVIDER="local" # local/baidu/tencent
|
||||
|
||||
# AI
|
||||
AI_PROVIDER="glm" # glm/minimax/deepseek
|
||||
AI_API_KEY=""
|
||||
AI_API_URL=""
|
||||
```
|
||||
565
.project/test-strategy.md
Normal file
565
.project/test-strategy.md
Normal file
@@ -0,0 +1,565 @@
|
||||
# 测试策略 - 图片OCR与智能文档管理系统
|
||||
|
||||
## 项目信息
|
||||
- **项目名称**: 图片OCR与智能文档管理系统
|
||||
- **测试策略版本**: 1.0
|
||||
- **创建日期**: 2026-02-21
|
||||
- **测试方法论**: TDD (测试驱动开发)
|
||||
|
||||
---
|
||||
|
||||
## 1. 测试金字塔
|
||||
|
||||
```
|
||||
/\
|
||||
/ \
|
||||
/ E2E \ <-- 10% 关键用户旅程
|
||||
/------\
|
||||
/ \
|
||||
/集成测试 \ <-- 30% API和组件交互
|
||||
/----------\
|
||||
/ \
|
||||
/ 单元测试 \ <-- 60% 函数和类级测试
|
||||
/--------------\
|
||||
```
|
||||
|
||||
### 测试分布
|
||||
|
||||
| 测试类型 | 占比 | 数量(预估) | 运行时间 | 覆盖目标 |
|
||||
|---------|------|-----------|---------|----------|
|
||||
| 单元测试 | 60% | ~200+ | <30秒 | 80%+ |
|
||||
| 集成测试 | 30% | ~80+ | <2分钟 | 70%+ |
|
||||
| E2E测试 | 10% | ~15+ | <5分钟 | 关键路径 |
|
||||
|
||||
---
|
||||
|
||||
## 2. E2E 测试清单
|
||||
|
||||
### 2.1 用户认证流程
|
||||
|
||||
#### E2E-AUTH-001: 用户注册登录
|
||||
- **优先级**: P0
|
||||
- **描述**: 新用户注册并登录系统
|
||||
- **步骤**:
|
||||
1. 打开注册页面 `/auth`
|
||||
- 预期: 显示注册表单(用户名、邮箱、密码)
|
||||
2. 输入有效信息并提交
|
||||
- 预期: 注册成功,自动登录
|
||||
3. 验证跳转到工作台 `/`
|
||||
- 预期: 显示工作台首页
|
||||
4. 刷新页面
|
||||
- 预期: 仍保持登录状态
|
||||
|
||||
#### E2E-AUTH-002: 登录失败处理
|
||||
- **优先级**: P0
|
||||
- **描述**: 输入错误凭证时显示错误信息
|
||||
- **步骤**:
|
||||
1. 打开登录页面
|
||||
2. 输入错误的用户名/密码
|
||||
3. 点击登录
|
||||
- 预期: 显示"用户名或密码错误"提示
|
||||
- 预期: 不跳转页面
|
||||
|
||||
#### E2E-AUTH-003: 登出功能
|
||||
- **优先级**: P0
|
||||
- **描述**: 用户登出后无法访问受保护页面
|
||||
- **步骤**:
|
||||
1. 登录后点击登出
|
||||
2. 尝试访问 `/documents`
|
||||
- 预期: 重定向到登录页
|
||||
|
||||
### 2.2 图片上传与OCR流程
|
||||
|
||||
#### E2E-OCR-001: 成功OCR识别
|
||||
- **优先级**: P0
|
||||
- **描述**: 上传清晰图片并成功识别
|
||||
- **步骤**:
|
||||
1. 登录系统
|
||||
2. 点击"上传图片"按钮
|
||||
3. 选择测试图片(清晰文字图片)
|
||||
4. 等待OCR处理
|
||||
- 预期: 显示处理中状态
|
||||
- 预期: 5秒内完成
|
||||
5. OCR完成后显示结果
|
||||
- 预期: 显示识别的文字
|
||||
- 预期: 置信度 > 80%
|
||||
6. 保存为文档
|
||||
- 预期: 文档列表中显示新文档
|
||||
|
||||
#### E2E-OCR-002: OCR失败处理
|
||||
- **优先级**: P0
|
||||
- **描述**: 模糊图片OCR失败时进入待处理列表
|
||||
- **步骤**:
|
||||
1. 上传模糊测试图片
|
||||
2. 等待OCR处理
|
||||
3. 处理完成但置信度 < 30%
|
||||
- 预期: 不自动创建文档
|
||||
- 预期: 显示"识别失败,请手动处理"提示
|
||||
4. 进入待处理图片页 `/pending-images`
|
||||
- 预期: 显示失败的图片
|
||||
5. 点击"手动创建文档"
|
||||
6. 输入文字并保存
|
||||
- 预期: 成功创建文档
|
||||
|
||||
#### E2E-OCR-003: 图片增强重试
|
||||
- **优先级**: P1
|
||||
- **描述**: 对模糊图片增强后重新OCR
|
||||
- **步骤**:
|
||||
1. 在待处理列表选择图片
|
||||
2. 点击"图片增强"(旋转、裁剪)
|
||||
3. 点击"重新OCR"
|
||||
4. 等待处理
|
||||
- 预期: 使用增强后的图片重新识别
|
||||
|
||||
### 2.3 文档管理与AI分析流程
|
||||
|
||||
#### E2E-DOC-001: AI智能分析
|
||||
- **优先级**: P0
|
||||
- **描述**: 对文档进行AI分析,自动打标签和分类
|
||||
- **步骤**:
|
||||
1. 创建文档后点击"AI分析"按钮
|
||||
2. 等待AI处理
|
||||
- 预期: 显示分析中状态
|
||||
3. AI完成后显示结果
|
||||
- 预期: 显示3-5个推荐标签
|
||||
- 预期: 显示推荐分类
|
||||
- 预期: 标签和分类可手动修改
|
||||
4. 确认应用
|
||||
- 预期: 标签和分类保存到文档
|
||||
|
||||
#### E2E-DOC-002: AI创建新分类
|
||||
- **优先级**: P1
|
||||
- **描述**: AI识别新内容类型并创建新分类
|
||||
- **步骤**:
|
||||
1. 上传发票图片并OCR
|
||||
2. 点击AI分析
|
||||
3. AI识别为发票类型
|
||||
- 预期: 推荐新分类"发票"
|
||||
- 预期: 显示推荐图标 🧾
|
||||
4. 确认创建
|
||||
- 预期: "发票"分类添加到分类列表
|
||||
|
||||
### 2.4 待办事项管理流程
|
||||
|
||||
#### E2E-TODO-001: 创建待办事项
|
||||
- **优先级**: P0
|
||||
- **描述**: 从文档创建待办事项
|
||||
- **步骤**:
|
||||
1. 在文档详情页点击"转为待办"
|
||||
2. 设置优先级为"高"
|
||||
3. 设置截止日期为明天
|
||||
4. 保存
|
||||
- 预期: 待办出现在"未完成"列表
|
||||
- 预期: 显示高优先级标签
|
||||
- 预期: 显示截止日期
|
||||
|
||||
#### E2E-TODO-002: 三状态流转
|
||||
- **优先级**: P0
|
||||
- **描述**: 待办从未完成→已完成→已确认
|
||||
- **步骤**:
|
||||
1. 在"未完成"列表点击"完成"
|
||||
- 预期: 待办移到"已完成"列表
|
||||
2. 在"已完成"列表查看
|
||||
- 预期: 显示完成时间
|
||||
3. 点击"确认"
|
||||
- 预期: 待办移到"已确认"列表
|
||||
4. 在"已确认"列表查看
|
||||
- 预期: 显示确认时间
|
||||
- 预期: 只读状态
|
||||
|
||||
#### E2E-TODO-003: 批量操作
|
||||
- **优先级**: P1
|
||||
- **描述**: 批量完成和确认待办
|
||||
- **步骤**:
|
||||
1. 在"未完成"列表选择3个待办
|
||||
2. 点击"批量完成"
|
||||
- 预期: 3个待办都移到"已完成"列表
|
||||
3. 在"已完成"列表全选
|
||||
4. 点击"批量确认"
|
||||
- 预期: 所有待办移到"已确认"列表
|
||||
|
||||
### 2.5 完整用户旅程
|
||||
|
||||
#### E2E-JOURNEY-001: 截图→待办完整流程
|
||||
- **优先级**: P0
|
||||
- **描述**: 从截图到创建待办的完整流程
|
||||
- **步骤**:
|
||||
1. 用户登录
|
||||
2. 点击"系统截图"
|
||||
3. 截取包含待办事项的图片
|
||||
4. OCR识别成功
|
||||
5. 编辑OCR结果
|
||||
6. 保存为文档
|
||||
7. AI分析生成标签和分类
|
||||
8. 转为待办事项
|
||||
9. 设置优先级和截止日期
|
||||
10. 保存到未完成列表
|
||||
- 预期: 全流程不超过5个主要操作
|
||||
- 预期: 总耗时 < 2分钟
|
||||
|
||||
---
|
||||
|
||||
## 3. 集成测试清单
|
||||
|
||||
### 3.1 后端API集成
|
||||
|
||||
#### INT-API-001: 用户认证API
|
||||
- **测试**: 完整的注册登录流程
|
||||
- **涉及组件**: AuthController, UserService, JWTMiddleware, Database
|
||||
- **Mock**: 密码加密、邮件服务
|
||||
- **断言**:
|
||||
- 注册成功返回JWT token
|
||||
- 密码使用bcrypt加密存储
|
||||
- token验证中间件正确拦截
|
||||
- 过期token被拒绝
|
||||
|
||||
#### INT-API-002: OCR处理API
|
||||
- **测试**: 图片上传到OCR完成的异步流程
|
||||
- **涉及组件**: OCRController, OCRService, OCRProvider, ImageStorage
|
||||
- **Mock**: OCR provider响应
|
||||
- **断言**:
|
||||
- 图片正确存储
|
||||
- OCR任务异步处理
|
||||
- 轮询接口返回正确状态
|
||||
- 置信度低于阈值时不创建文档
|
||||
|
||||
#### INT-API-003: AI分析API
|
||||
- **测试**: AI分析调用的完整流程
|
||||
- **涉及组件**: AIController, AIService, AIProviders (GLM/MiniMax/DeepSeek)
|
||||
- **Mock**: AI provider API响应
|
||||
- **断言**:
|
||||
- 正确调用配置的AI provider
|
||||
- 返回的标签和分类格式正确
|
||||
- 新标签/新分类正确创建
|
||||
- API失败时降级处理
|
||||
|
||||
#### INT-API-004: 待办状态流转API
|
||||
- **测试**: 待办状态变更的API调用
|
||||
- **涉及组件**: TodoController, TodoService, Database
|
||||
- **断言**:
|
||||
- pending → completed 正确更新completed_at
|
||||
- completed → confirmed 正确更新confirmed_at
|
||||
- confirmed 不能回到其他状态
|
||||
- 批量操作事务性正确
|
||||
|
||||
### 3.2 数据库集成
|
||||
|
||||
#### INT-DB-001: 数据模型关系
|
||||
- **测试**: 验证实体关系正确性
|
||||
- **涉及组件**: Prisma ORM, Database
|
||||
- **断言**:
|
||||
- 用户级联删除正确
|
||||
- Image.document_id 可为NULL
|
||||
- Tag/Category usage_count 自动更新
|
||||
- 事务回滚正确
|
||||
|
||||
#### INT-DB-002: 查询性能
|
||||
- **测试**: 验证查询效率
|
||||
- **涉及组件**: Database, Indexes
|
||||
- **断言**:
|
||||
- 待处理图片查询 < 100ms
|
||||
- 带筛选的文档查询 < 200ms
|
||||
- 全文搜索 < 500ms
|
||||
|
||||
### 3.3 前端组件集成
|
||||
|
||||
#### INT-FE-001: 图片上传组件
|
||||
- **测试**: 拖拽上传、文件选择、预览
|
||||
- **涉及组件**: ImageUpload, OCRService, Toast
|
||||
- **Mock**: OCR API
|
||||
- **断言**:
|
||||
- 拖拽正确触发上传
|
||||
- 文件类型验证
|
||||
- 进度条正确显示
|
||||
- 错误正确提示
|
||||
|
||||
#### INT-FE-002: 三状态待办列表
|
||||
- **测试**: Tab切换和状态流转
|
||||
- **涉及组件**: TodoList, TodoCard, TodoAPI
|
||||
- **Mock**: Todo API
|
||||
- **断言**:
|
||||
- Tab切换正确筛选数据
|
||||
- 状态变更后列表正确更新
|
||||
- 批量操作正确选中
|
||||
- 撤销操作正确恢复状态
|
||||
|
||||
#### INT-FE-003: AI分析UI
|
||||
- **测试**: AI分析按钮和结果显示
|
||||
- **涉及组件**: AIAnalysisButton, TagSelector, CategorySelector
|
||||
- **Mock**: AI API
|
||||
- **断言**:
|
||||
- 加载状态正确显示
|
||||
- 新标签/新分类高亮显示
|
||||
- 确认后正确应用
|
||||
- 取消后不应用
|
||||
|
||||
---
|
||||
|
||||
## 4. 单元测试清单
|
||||
|
||||
### 4.1 后端单元测试
|
||||
|
||||
#### UNIT-AUTH-001: 密码加密
|
||||
- **测试**: `UserService.hashPassword()`
|
||||
- 输入: "plaintext123"
|
||||
- 预期输出: bcrypt hash (60字符)
|
||||
- 边界: 空字符串、超长密码、特殊字符
|
||||
|
||||
#### UNIT-AUTH-002: JWT生成和验证
|
||||
- **测试**: `AuthService.generateToken()`, `verifyToken()`
|
||||
- 输入: user_id = "uuid-123"
|
||||
- 预期输出: 有效JWT token
|
||||
- 边界: 过期token、伪造token
|
||||
|
||||
#### UNIT-OCR-001: 置信度阈值判断
|
||||
- **测试**: `OCRService.shouldCreateDocument()`
|
||||
- 输入: confidence = 0.31, 阈值 = 0.3
|
||||
- 预期输出: true
|
||||
- 边界: 0.3, 0.29, 1.0, 0.0
|
||||
|
||||
#### UNIT-OCR-002: 图片质量检测
|
||||
- **测试**: `ImageQualityAnalyzer.assess()`
|
||||
- 输入: 模糊图片base64
|
||||
- 预期输出: { quality: 'poor', score: < 0.5 }
|
||||
- 边界: 清晰图片、全黑图片、超大图片
|
||||
|
||||
#### UNIT-AI-001: 标签提取
|
||||
- **测试**: `AIService.extractTags()`
|
||||
- 输入: "会议记录:讨论项目进度..."
|
||||
- 预期输出: ["会议", "项目", "进度"]
|
||||
- 边界: 空文本、纯数字、混合语言
|
||||
|
||||
#### UNIT-AI-002: 分类推荐
|
||||
- **测试**: `AIService.suggestCategory()`
|
||||
- 输入: 文本内容 + 现有分类列表
|
||||
- 预期输出: 匹配的分类 或 新分类建议
|
||||
- 边界: 无现有分类、完全匹配、部分匹配
|
||||
|
||||
#### UNIT-TODO-001: 待办状态验证
|
||||
- **测试**: `TodoService.validateStatusTransition()`
|
||||
- 输入: "pending" → "completed"
|
||||
- 预期输出: true
|
||||
- 边界: "confirmed" → "pending" (应返回false)
|
||||
|
||||
#### UNIT-TODO-002: 待办排序
|
||||
- **测试**: `TodoService.sortTodos()`
|
||||
- 输入: 待办列表 + 排序规则
|
||||
- 预期输出: 按优先级和截止日期排序
|
||||
- 边界: 空列表、相同优先级、无截止日期
|
||||
|
||||
### 4.2 前端单元测试
|
||||
|
||||
#### UNIT-FE-001: 图片预览组件
|
||||
- **测试**: `ImagePreview`
|
||||
- 输入: imageUrl = "http://..."
|
||||
- 预期: 渲染img标签
|
||||
- 边界: 加载失败、超大图片
|
||||
|
||||
#### UNIT-FE-002: 标签选择器
|
||||
- **测试**: `TagSelector`
|
||||
- 输入: tags = [], 新标签输入
|
||||
- 预期: 调用onTagsChange
|
||||
- 边界: 重复标签、空标签
|
||||
|
||||
#### UNIT-FE-003: 待办卡片
|
||||
- **测试**: `TodoCard`
|
||||
- 输入: todo对象
|
||||
- 预期: 正确显示优先级颜色
|
||||
- 边界: 无截止日期、已完成状态
|
||||
|
||||
#### UNIT-FE-004: OCR结果编辑器
|
||||
- **测试**: `OCResultEditor`
|
||||
- 输入: initialText, onChangeText
|
||||
- 预期: 文本变更触发回调
|
||||
- 边界: 超长文本、特殊字符
|
||||
|
||||
### 4.3 工具函数测试
|
||||
|
||||
#### UNIT-UTIL-001: 文件大小格式化
|
||||
- **测试**: `formatFileSize()`
|
||||
- 输入: 1024 → "1 KB"
|
||||
- 输入: 1048576 → "1 MB"
|
||||
- 边界: 0, 负数
|
||||
|
||||
#### UNIT-UTIL-002: 日期格式化
|
||||
- **测试**: `formatDate()`
|
||||
- 输入: "2024-01-15" → "1月15日"
|
||||
- 边界: 无效日期、未来日期
|
||||
|
||||
#### UNIT-UTIL-003: 搜索高亮
|
||||
- **测试**: `highlightSearch()`
|
||||
- 输入: "hello world", "world"
|
||||
- 预期: "hello <mark>world</mark>"
|
||||
- 边界: 空搜索词、多个匹配
|
||||
|
||||
---
|
||||
|
||||
## 5. 测试矩阵
|
||||
|
||||
| 功能模块 | 单元测试 | 集成测试 | E2E测试 | 覆盖率目标 | 关键测试 |
|
||||
|---------|---------|---------|---------|-----------|----------|
|
||||
| 用户认证 | 15 | 4 | 3 | 85% | 登录/权限/数据隔离 |
|
||||
| 图片上传 | 12 | 3 | 2 | 80% | 拖拽/格式验证/大小限制 |
|
||||
| OCR处理 | 20 | 5 | 3 | 80% | 置信度/失败处理/重试 |
|
||||
| 文档管理 | 18 | 4 | 2 | 80% | CRUD/搜索/关联 |
|
||||
| AI分析 | 25 | 6 | 2 | 75% | 标签/分类/新分类创建 |
|
||||
| 待办管理 | 30 | 6 | 4 | 85% | 三状态/批量/流转 |
|
||||
| 标签分类 | 15 | 3 | 1 | 75% | 创建/关联/统计 |
|
||||
| 待处理图片 | 12 | 3 | 2 | 80% | 手动创建/增强/删除 |
|
||||
| 配置管理 | 10 | 2 | 0 | 70% | CRUD/测试配置 |
|
||||
| **总计** | **157** | **36** | **19** | **80%** | - |
|
||||
|
||||
---
|
||||
|
||||
## 6. 测试数据管理
|
||||
|
||||
### 6.1 测试图片
|
||||
|
||||
| 类型 | 文件名 | 用途 | 预期结果 |
|
||||
|------|--------|------|----------|
|
||||
| 清晰中文 | `clear-zh.png` | 基础OCR测试 | 置信度 > 90% |
|
||||
| 清晰英文 | `clear-en.png` | 英文OCR测试 | 置信度 > 90% |
|
||||
| 模糊图片 | `blur.png` | 失败处理测试 | 置信度 < 30% |
|
||||
| 混合语言 | `mixed.png` | 多语言测试 | 正确识别 |
|
||||
| 手写文字 | `handwriting.png` | 手写测试 | 置信度 50-70% |
|
||||
| 发票样本 | `invoice.png` | 类型识别测试 | 识别为发票 |
|
||||
| 会议记录 | `meeting.png` | 类型识别测试 | 识别为会议 |
|
||||
| 超大图片 | `large.png` | 大小限制测试 | 拒绝 (>10MB) |
|
||||
|
||||
### 6.2 测试用户
|
||||
|
||||
| 用户名 | 角色 | 用途 |
|
||||
|--------|------|------|
|
||||
| test_user | 普通用户 | 常规测试 |
|
||||
| admin_user | 管理员 | 管理功能测试 |
|
||||
| new_user | 新用户 | 注册流程测试 |
|
||||
|
||||
### 6.3 Mock数据
|
||||
|
||||
- **AI响应**: 预设的标签和分类推荐
|
||||
- **OCR响应**: 不同置信度的识别结果
|
||||
- **API响应**: 成功/失败/超时场景
|
||||
|
||||
---
|
||||
|
||||
## 7. 性能测试
|
||||
|
||||
### 7.1 响应时间
|
||||
|
||||
| 操作 | 目标 | 测试方法 |
|
||||
|------|------|----------|
|
||||
| 页面加载 | <2s | Lighthouse |
|
||||
| API响应 | <500ms | k6 |
|
||||
| OCR处理 | <5s | 计时测试 |
|
||||
| AI分析 | <10s | 计时测试 |
|
||||
|
||||
### 7.2 并发测试
|
||||
|
||||
- 5个用户同时上传图片
|
||||
- 10个并发API请求
|
||||
- 数据库连接池压力测试
|
||||
|
||||
---
|
||||
|
||||
## 8. 测试环境
|
||||
|
||||
### 8.1 本地开发
|
||||
```bash
|
||||
# 运行所有测试
|
||||
npm test
|
||||
|
||||
# 单元测试
|
||||
npm run test:unit
|
||||
|
||||
# 集成测试
|
||||
npm run test:integration
|
||||
|
||||
# E2E测试
|
||||
npm run test:e2e
|
||||
|
||||
# 覆盖率报告
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
### 8.2 CI/CD
|
||||
```yaml
|
||||
# .github/workflows/test.yml
|
||||
name: Test
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
test:
|
||||
steps:
|
||||
- run: npm run test:unit
|
||||
- run: npm run test:integration
|
||||
- run: npm run test:e2e
|
||||
- run: npm run test:coverage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 测试工具
|
||||
|
||||
### 9.1 后端
|
||||
- **框架**: Jest
|
||||
- **Mock**: jest.mock
|
||||
- **覆盖率**: istanbul
|
||||
- **API测试**: supertest
|
||||
|
||||
### 9.2 前端
|
||||
- **框架**: Vitest
|
||||
- **组件测试**: Testing Library
|
||||
- **Mock**: vi.mock
|
||||
- **E2E**: Playwright
|
||||
|
||||
### 9.3 工具
|
||||
- **覆盖率**: c8 / istanbul
|
||||
- **性能**: Lighthouse, k6
|
||||
- **Mock服务器**: MSW (Mock Service Worker)
|
||||
|
||||
---
|
||||
|
||||
## 10. 测试质量标准
|
||||
|
||||
### 10.1 代码覆盖率
|
||||
- **整体覆盖率**: ≥ 80%
|
||||
- **核心模块**: ≥ 85%
|
||||
- **工具函数**: ≥ 90%
|
||||
|
||||
### 10.2 测试质量
|
||||
- 所有测试独立且可重复
|
||||
- 测试命名清晰描述意图
|
||||
- 每个测试只有一个断言原因
|
||||
- 边界条件都有测试覆盖
|
||||
|
||||
### 10.3 CI/CD门禁
|
||||
- 所有测试必须通过
|
||||
- 覆盖率不能下降
|
||||
- Lint必须通过
|
||||
- 构建时间 < 5分钟
|
||||
|
||||
---
|
||||
|
||||
## 11. 风险和缓解
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| AI API不稳定 | 测试失败 | 使用Mock,减少真实调用 |
|
||||
| OCR耗时 | 测试慢 | 使用预设结果,跳过真实OCR |
|
||||
| 测试数据污染 | 测试失败 | 每次测试前清理数据库 |
|
||||
| E2E测试不稳定 | CI失败 | 增加重试机制,优化选择器 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 下一步
|
||||
|
||||
测试策略已制定完成。下一步:
|
||||
|
||||
1. **生成TDD开发计划** - 详细的Sprint计划
|
||||
2. **创建测试骨架** - 生成测试文件模板
|
||||
3. **开始TDD开发** - 红-绿-重构循环
|
||||
|
||||
```bash
|
||||
# 查看开发计划
|
||||
@skill tdd-planner
|
||||
生成开发计划
|
||||
```
|
||||
Reference in New Issue
Block a user