feat: upgrade Anthropic API config and add comprehensive docs
- 升级 Anthropic API 配置支持自定义 base URL - 更新模型版本到 claude-sonnet-4-6 - 添加 QuestionResponse.user_id 字段 - 完善 docker-compose.yml 添加 backend 和 celery 服务 - 新增项目文档(API、部署、架构等) - 添加测试脚本和配置文件 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
---
|
||||
name: code-reviewer
|
||||
description: 每个 Stage 完成后的自动代码审查
|
||||
tools: Read, Grep, Glob, Bash
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
# Code Reviewer Agent
|
||||
|
||||
审查刚完成的 Stage 代码,关注以下方面:
|
||||
|
||||
## 🔴 安全性(Critical)
|
||||
|
||||
1. **SQL注入**: 检查是否存在原始字符串拼接构建 SQL 查询
|
||||
2. **密钥泄露**: 检查是否有硬编码的 API key、密码等敏感信息
|
||||
3. **路径遍历**: 检查文件操作是否验证路径安全性
|
||||
4. **越权访问**: 检查是否正确验证用户权限
|
||||
|
||||
## 🔴 一致性(Critical)
|
||||
|
||||
1. **API响应格式**: 所有 API 是否统一返回 `{"code": 0, "data": {}, "message": ""}`
|
||||
2. **错误处理**: 错误响应是否也遵循统一格式
|
||||
3. **命名规范**: Python 使用 snake_case,Dart 使用 camelCase
|
||||
|
||||
## 🔴 异步正确性(Critical)
|
||||
|
||||
1. **async/await**: 检查是否在 async 函数中调用同步阻塞操作
|
||||
2. **Celery+async**: Celery task 内是否正确使用 `asyncio.run()` 包装 async 代码
|
||||
3. **数据库操作**: 是否全部使用 async ORM
|
||||
|
||||
## 🟡 存储安全(Important)
|
||||
|
||||
1. **图片存储**: 数据库是否存储 object_key 而非裸 URL
|
||||
2. **presigned URL**: API 返回时是否生成 presigned URL
|
||||
3. **URL 有效期**: presigned URL 是否设置了合理的过期时间
|
||||
|
||||
## 🟡 错误处理(Important)
|
||||
|
||||
1. **全局异常**: 异常是否被 middleware 正确捕获
|
||||
2. **错误日志**: 错误是否被正确记录
|
||||
3. **用户友好**: 错误信息是否对用户友好
|
||||
|
||||
## 🟡 日志规范(Important)
|
||||
|
||||
1. **structlog**: 是否使用 structlog 记录日志
|
||||
2. **request_id**: 每条日志是否包含 request_id
|
||||
3. **日志级别**: 日志级别是否合理(DEBUG/INFO/WARNING/ERROR)
|
||||
|
||||
## 🟢 测试覆盖(Nice to have)
|
||||
|
||||
1. **单元测试**: 关键路径是否有测试覆盖
|
||||
2. **测试质量**: 测试是否真正验证了功能
|
||||
3. **Mock 使用**: 外部依赖是否正确 mock
|
||||
|
||||
## 输出格式
|
||||
|
||||
按严重等级列出问题和修复建议:
|
||||
|
||||
```
|
||||
🔴 Critical Issues:
|
||||
- [文件:行号] 问题描述
|
||||
修复建议: ...
|
||||
|
||||
🟡 Important Issues:
|
||||
- [文件:行号] 问题描述
|
||||
修复建议: ...
|
||||
|
||||
🟢 Suggestions:
|
||||
- [文件:行号] 建议
|
||||
改进方案: ...
|
||||
|
||||
✅ Summary:
|
||||
- X critical issues found
|
||||
- Y important issues found
|
||||
- Z suggestions
|
||||
```
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
name: run-all-checks
|
||||
description: 运行全部检查确认项目可构建
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# Run All Checks Skill
|
||||
|
||||
执行以下检查确认项目可构建:
|
||||
|
||||
## 检查清单
|
||||
|
||||
### 1. Python 代码检查
|
||||
```bash
|
||||
cd backend && ruff check .
|
||||
```
|
||||
|
||||
### 2. Python 测试
|
||||
```bash
|
||||
cd backend && pytest -x -v
|
||||
```
|
||||
|
||||
### 3. Flutter 代码分析(如果 Flutter 项目已创建)
|
||||
```bash
|
||||
cd app && flutter analyze
|
||||
```
|
||||
|
||||
### 4. Flutter 测试(如果有测试)
|
||||
```bash
|
||||
cd app && flutter test
|
||||
```
|
||||
|
||||
### 5. Docker Compose 配置验证
|
||||
```bash
|
||||
docker compose config --quiet
|
||||
```
|
||||
|
||||
## 输出格式
|
||||
|
||||
每一步结果打印,失败的用 ❌ 标记,全通过输出:
|
||||
|
||||
```
|
||||
✅ Python lint check: PASSED
|
||||
✅ Python tests: PASSED
|
||||
✅ Flutter analyze: PASSED
|
||||
✅ Flutter tests: PASSED
|
||||
✅ Docker compose config: PASSED
|
||||
|
||||
🎉 All checks passed!
|
||||
```
|
||||
|
||||
如果有失败:
|
||||
|
||||
```
|
||||
❌ Python lint check: FAILED
|
||||
Error: ...
|
||||
|
||||
✅ Python tests: PASSED
|
||||
⚠️ Flutter analyze: SKIPPED (project not found)
|
||||
✅ Docker compose config: PASSED
|
||||
|
||||
❌ Some checks failed. Please fix the issues above.
|
||||
```
|
||||
+3
-2
@@ -18,8 +18,9 @@ MINIO_USE_SSL=false
|
||||
MINIO_PRESIGN_EXPIRY=3600
|
||||
|
||||
# ── AI 服务 (可选, 为空则使用 mock) ──
|
||||
ANTHROPIC_API_KEY=
|
||||
ANTHROPIC_MODEL=claude-sonnet-4-20250514
|
||||
ANTHROPIC_AUTH_TOKEN=
|
||||
ANTHROPIC_BASE_URL=https://api.anthropic.com
|
||||
ANTHROPIC_MODEL=claude-sonnet-4-6
|
||||
ANTHROPIC_TEMPERATURE=0.1
|
||||
ANTHROPIC_MAX_TOKENS_RECOGNIZE=2048
|
||||
ANTHROPIC_MAX_TOKENS_ANALYZE=4096
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
# 自动修复系统和组织仓库更新完成
|
||||
|
||||
## ✅ 完成的工作
|
||||
|
||||
### 1. 更新部署仓库为组织仓库
|
||||
|
||||
**变更**:
|
||||
- 从: `BrunoGao/ljwx-deploy`
|
||||
- 到: `BrunoGaoSZ/ljwx-deploy`
|
||||
|
||||
**更新的文件**:
|
||||
- `.github/workflows/ci.yml` - CI/CD workflow
|
||||
- `.github/workflows/README.md` - 架构文档
|
||||
- `.github/workflows/SECRETS_SETUP_GUIDE.md` - Secrets 设置指南
|
||||
- `.github/workflows/QUICKSTART.md` - 快速开始
|
||||
- `.github/workflows/SETUP.md` - 设置指南
|
||||
- `.github/workflows/INDEX.md` - 文档索引
|
||||
|
||||
**更新的 Secret**:
|
||||
```bash
|
||||
DEPLOY_REPO=BrunoGaoSZ/ljwx-deploy
|
||||
```
|
||||
|
||||
### 2. 创建自动修复系统
|
||||
|
||||
**新增文件**:
|
||||
- `.github/workflows/auto-fix.yml` - 自动修复 workflow
|
||||
- `.github/workflows/AUTO_FIX.md` - 完整文档
|
||||
|
||||
**功能特性**:
|
||||
|
||||
#### ✅ 自动错误检测
|
||||
- **Missing Python Module** - 缺少 Python 模块
|
||||
- **Import Error** - 导入错误
|
||||
- **Database Error** - 数据库配置错误
|
||||
- **Test Failure** - 测试失败
|
||||
- **Type Check Error** - 类型检查错误
|
||||
|
||||
#### ✅ 自动化流程
|
||||
```
|
||||
CI 失败 → 下载日志 → 分析错误 → 创建 Issue → 通知团队
|
||||
```
|
||||
|
||||
#### ✅ Issue 自动创建
|
||||
当 CI 失败时,系统会自动创建包含以下信息的 issue:
|
||||
- 失败的分支和提交
|
||||
- 错误类型分析
|
||||
- 相关日志链接
|
||||
- 修复建议
|
||||
- 快速链接
|
||||
|
||||
---
|
||||
|
||||
## 🚀 如何使用
|
||||
|
||||
### 查看自动创建的 Issue
|
||||
|
||||
```bash
|
||||
# 列出所有 CI 失败的 issue
|
||||
gh issue list --label "ci-failure"
|
||||
|
||||
# 查看特定 issue
|
||||
gh issue view <issue-number>
|
||||
```
|
||||
|
||||
### 监控自动修复运行
|
||||
|
||||
```bash
|
||||
# 查看 Auto Fix workflow 运行历史
|
||||
gh run list --workflow=auto-fix.yml --limit 10
|
||||
|
||||
# 查看特定运行的日志
|
||||
gh run view <run-id> --log
|
||||
```
|
||||
|
||||
### 手动触发分析
|
||||
|
||||
```bash
|
||||
# 重新运行 Auto Fix workflow
|
||||
gh workflow run auto-fix.yml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 当前 CI/CD 状态
|
||||
|
||||
### 最新运行
|
||||
|
||||
```bash
|
||||
# 查看最新的 CI 运行
|
||||
gh run list --limit 5
|
||||
|
||||
# 实时监控
|
||||
gh run watch
|
||||
```
|
||||
|
||||
### 预期流程
|
||||
|
||||
1. **Test Stage** (约 20 分钟)
|
||||
- ✅ Linter 检查
|
||||
- ✅ Type checker (optional)
|
||||
- ✅ Migrations
|
||||
- ✅ Tests with coverage
|
||||
|
||||
2. **Build Stage** (约 30 分钟)
|
||||
- ✅ Docker 镜像构建
|
||||
- ✅ 推送到 Harbor
|
||||
|
||||
3. **Deploy Stage** (约 10 分钟)
|
||||
- ✅ 更新 ljwx-deploy 仓库
|
||||
- ✅ 创建 PR
|
||||
|
||||
4. **Auto Fix** (如果失败)
|
||||
- ✅ 分析错误
|
||||
- ✅ 创建 Issue
|
||||
|
||||
---
|
||||
|
||||
## 🔧 配置说明
|
||||
|
||||
### GitHub Secrets
|
||||
|
||||
确保以下 secrets 已正确配置:
|
||||
|
||||
```bash
|
||||
# 验证 secrets
|
||||
gh secret list
|
||||
|
||||
# 应该看到:
|
||||
# HARBOR_USERNAME - Harbor 用户名
|
||||
# HARBOR_PASSWORD - Harbor 密码
|
||||
# DEPLOY_REPO_TOKEN - GitHub PAT (访问 ljwx-deploy)
|
||||
# DEPLOY_REPO - BrunoGaoSZ/ljwx-deploy
|
||||
```
|
||||
|
||||
### 权限要求
|
||||
|
||||
Auto Fix workflow 需要以下权限:
|
||||
- `contents: write` - 创建分支和提交
|
||||
- `pull-requests: write` - 创建 PR
|
||||
- `issues: write` - 创建 Issue
|
||||
|
||||
这些权限已在 `.github/workflows/auto-fix.yml` 中配置。
|
||||
|
||||
---
|
||||
|
||||
## 📚 文档
|
||||
|
||||
### 核心文档
|
||||
- [CI/CD 架构](../.github/workflows/README.md)
|
||||
- [自动修复系统](../.github/workflows/AUTO_FIX.md)
|
||||
- [快速开始](../.github/workflows/QUICKSTART.md)
|
||||
- [Secrets 设置](../.github/workflows/SECRETS_SETUP_GUIDE.md)
|
||||
|
||||
### 快速链接
|
||||
- **GitHub Actions**: https://github.com/BrunoGao/ljwx-shiti/actions
|
||||
- **部署仓库**: https://github.com/BrunoGaoSZ/ljwx-deploy
|
||||
- **Harbor**: https://harbor.omniverseai.net/harbor/projects/ljwx
|
||||
|
||||
---
|
||||
|
||||
## 🎯 下一步
|
||||
|
||||
### 1. 等待 CI 完成
|
||||
|
||||
```bash
|
||||
# 实时监控
|
||||
gh run watch
|
||||
|
||||
# 或在浏览器查看
|
||||
open https://github.com/BrunoGao/ljwx-shiti/actions
|
||||
```
|
||||
|
||||
### 2. 验证部署仓库访问
|
||||
|
||||
```bash
|
||||
# 测试 token 权限
|
||||
curl -H "Authorization: token YOUR_TOKEN" \
|
||||
https://api.github.com/repos/BrunoGaoSZ/ljwx-deploy
|
||||
|
||||
# 应该返回仓库信息
|
||||
```
|
||||
|
||||
### 3. 检查自动修复
|
||||
|
||||
如果 CI 失败,检查是否自动创建了 issue:
|
||||
|
||||
```bash
|
||||
gh issue list --label "ci-failure"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 故障排查
|
||||
|
||||
### CI 仍然失败
|
||||
|
||||
1. 查看最新的运行日志:
|
||||
```bash
|
||||
gh run view --log
|
||||
```
|
||||
|
||||
2. 检查是否创建了 auto-fix issue:
|
||||
```bash
|
||||
gh issue list --label "ci-failure"
|
||||
```
|
||||
|
||||
3. 查看具体错误并手动修复
|
||||
|
||||
### Auto Fix 未触发
|
||||
|
||||
1. 检查 workflow 权限
|
||||
2. 查看 Auto Fix 运行历史:
|
||||
```bash
|
||||
gh run list --workflow=auto-fix.yml
|
||||
```
|
||||
|
||||
### 部署仓库访问失败
|
||||
|
||||
1. 验证 DEPLOY_REPO secret:
|
||||
```bash
|
||||
gh secret list | grep DEPLOY_REPO
|
||||
```
|
||||
|
||||
2. 测试 token 权限:
|
||||
```bash
|
||||
curl -H "Authorization: token YOUR_TOKEN" \
|
||||
https://api.github.com/repos/BrunoGaoSZ/ljwx-deploy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 未来改进
|
||||
|
||||
### 自动修复增强
|
||||
- [ ] 集成 Claude API 进行智能分析
|
||||
- [ ] 自动创建修复 PR
|
||||
- [ ] 支持更多错误类型
|
||||
- [ ] 添加 Slack/Discord 通知
|
||||
|
||||
### 监控和分析
|
||||
- [ ] 错误趋势分析
|
||||
- [ ] CI 性能监控
|
||||
- [ ] 成功率统计
|
||||
- [ ] 自动回滚机制
|
||||
|
||||
---
|
||||
|
||||
**创建时间**: 2026-02-27
|
||||
**最后更新**: 2026-02-27
|
||||
|
||||
**相关提交**:
|
||||
- `840b586` - feat: add auto-fix workflow and update deploy repo to org
|
||||
- `2c40a0b` - fix: use asyncpg driver for alembic migrations in CI
|
||||
- `ca9f9e4` - fix: add psycopg2-binary for alembic migrations
|
||||
- `e5526ce` - ci: add GitHub Actions CI/CD pipeline
|
||||
@@ -0,0 +1,247 @@
|
||||
# WrongBook - 智能错题本
|
||||
|
||||
基于 AI 的智能错题管理系统,支持拍照识别、自动分析、智能复习。
|
||||
|
||||
## 📚 文档导航
|
||||
|
||||
- [CLAUDE.md](./CLAUDE.md) - Agent 指令和项目规范
|
||||
- [构建指令](./docs/BUILD_INSTRUCTIONS.md) - 完整的 10 阶段构建指南
|
||||
- [系统架构](./docs/architecture.md) - 技术架构和设计文档
|
||||
- [风险验证](./docs/RISK_VERIFICATION.md) - 风险点及验证清单
|
||||
- [API 文档](./docs/API.md) - API 接口文档
|
||||
- [部署指南](./docs/DEPLOYMENT.md) - 部署配置和运维指南
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
ljwx-shiti/
|
||||
├── backend/ # Python FastAPI 后端
|
||||
├── app/ # Flutter 客户端
|
||||
├── miniprogram/ # uni-app 微信小程序
|
||||
├── infra/ # 基础设施配置
|
||||
└── docs/ # 文档
|
||||
```
|
||||
|
||||
## 技术栈
|
||||
|
||||
### 后端
|
||||
- Python 3.11
|
||||
- FastAPI
|
||||
- SQLAlchemy 2.0 (async)
|
||||
- PostgreSQL 16
|
||||
- Redis 7
|
||||
- Celery
|
||||
- Claude Sonnet API (vision)
|
||||
- PaddleOCR (可选)
|
||||
|
||||
### 客户端
|
||||
- Flutter 3.22+
|
||||
- Dart 3.4+
|
||||
- Riverpod
|
||||
- Dio
|
||||
|
||||
### 小程序
|
||||
- uni-app
|
||||
- Vue 3
|
||||
- TypeScript
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 环境要求
|
||||
|
||||
- Docker & Docker Compose
|
||||
- Python 3.11+
|
||||
- Flutter 3.22+ (可选,用于客户端开发)
|
||||
- Node.js 18+ (可选,用于小程序开发)
|
||||
|
||||
### 启动服务
|
||||
|
||||
1. 克隆仓库
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd ljwx-shiti
|
||||
```
|
||||
|
||||
2. 配置环境变量
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# 编辑 .env 文件,填入必要的配置
|
||||
```
|
||||
|
||||
3. 启动所有服务
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
4. 运行数据库迁移
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
5. 访问服务
|
||||
|
||||
- 后端 API: http://localhost:8000
|
||||
- API 文档: http://localhost:8000/docs
|
||||
- MinIO 控制台: http://localhost:49001
|
||||
|
||||
### 开发模式
|
||||
|
||||
#### 后端开发
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
|
||||
# 安装依赖
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 运行测试
|
||||
pytest -x -v
|
||||
|
||||
# 启动开发服务器
|
||||
uvicorn app.main:app --reload
|
||||
|
||||
# 代码检查
|
||||
ruff check .
|
||||
```
|
||||
|
||||
#### Flutter 客户端开发
|
||||
|
||||
```bash
|
||||
cd app
|
||||
|
||||
# 安装依赖
|
||||
flutter pub get
|
||||
|
||||
# 代码生成
|
||||
flutter pub run build_runner build --delete-conflicting-outputs
|
||||
|
||||
# 运行
|
||||
flutter run
|
||||
|
||||
# 代码检查
|
||||
flutter analyze
|
||||
```
|
||||
|
||||
#### 微信小程序开发
|
||||
|
||||
```bash
|
||||
cd miniprogram
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 运行开发环境
|
||||
npm run dev:mp-weixin
|
||||
```
|
||||
|
||||
## 核心功能
|
||||
|
||||
1. **拍照录题**
|
||||
- 支持拍照或相册选择
|
||||
- 自动去除手写笔迹
|
||||
- AI 识别题目内容
|
||||
|
||||
2. **智能分析**
|
||||
- Claude Sonnet 视觉分析
|
||||
- 自动提取知识点
|
||||
- 难度评估
|
||||
|
||||
3. **题目管理**
|
||||
- 分类浏览
|
||||
- 全文搜索
|
||||
- 标签管理
|
||||
|
||||
4. **智能复习**
|
||||
- 复习记录
|
||||
- 个人笔记
|
||||
- 学习统计
|
||||
|
||||
## API 文档
|
||||
|
||||
启动服务后访问 http://localhost:8000/docs 查看完整 API 文档。
|
||||
|
||||
### 主要端点
|
||||
|
||||
- `POST /api/v1/auth/login` - 用户登录
|
||||
- `POST /api/v1/auth/register` - 用户注册
|
||||
- `POST /api/v1/questions/` - 创建题目
|
||||
- `GET /api/v1/questions/` - 获取题目列表
|
||||
- `GET /api/v1/questions/{id}` - 获取题目详情
|
||||
- `PUT /api/v1/questions/{id}` - 更新题目
|
||||
- `DELETE /api/v1/questions/{id}` - 删除题目
|
||||
- `GET /api/v1/questions/search` - 搜索题目
|
||||
|
||||
## 部署
|
||||
|
||||
### 生产环境部署
|
||||
|
||||
1. 更新环境变量
|
||||
|
||||
```bash
|
||||
# 设置生产环境配置
|
||||
export ENV=production
|
||||
export SECRET_KEY=<your-secret-key>
|
||||
export DATABASE_URL=<your-database-url>
|
||||
```
|
||||
|
||||
2. 构建并启动
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
3. 配置反向代理 (Nginx)
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
# 后端测试
|
||||
cd backend
|
||||
pytest -x -v
|
||||
|
||||
# 覆盖率报告
|
||||
pytest --cov=app --cov-report=html
|
||||
```
|
||||
|
||||
## 代码规范
|
||||
|
||||
- Python: 使用 ruff 格式化,必须写 type hints
|
||||
- Dart: 遵循 flutter_lints,优先使用 const 构造函数
|
||||
- 所有 API 返回统一格式: `{"code": 0, "data": {}, "message": ""}`
|
||||
- 数据库操作一律使用 async
|
||||
- 所有日志使用 structlog,每条日志带 request_id
|
||||
|
||||
## 贡献指南
|
||||
|
||||
1. Fork 项目
|
||||
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
|
||||
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
||||
5. 开启 Pull Request
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
|
||||
## 联系方式
|
||||
|
||||
- 项目主页: <repository-url>
|
||||
- 问题反馈: <repository-url>/issues
|
||||
@@ -0,0 +1,58 @@
|
||||
# WrongBook Flutter App
|
||||
|
||||
智能错题本 Flutter 客户端
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Flutter 3.22+
|
||||
- Dart 3.4+
|
||||
|
||||
## 安装依赖
|
||||
|
||||
```bash
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
## 代码生成
|
||||
|
||||
```bash
|
||||
flutter pub run build_runner build --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
## 运行
|
||||
|
||||
```bash
|
||||
flutter run
|
||||
```
|
||||
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
# Android
|
||||
flutter build apk --release
|
||||
|
||||
# iOS
|
||||
flutter build ios --release
|
||||
```
|
||||
|
||||
## 代码检查
|
||||
|
||||
```bash
|
||||
flutter analyze
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
lib/
|
||||
├── main.dart # 应用入口
|
||||
├── app.dart # 应用配置和路由
|
||||
├── core/ # 核心功能
|
||||
│ ├── models/ # 数据模型
|
||||
│ └── services/ # 服务层
|
||||
└── features/ # 功能模块
|
||||
├── auth/ # 认证
|
||||
├── home/ # 首页
|
||||
├── question/ # 题目管理
|
||||
└── capture/ # 拍照录题
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
rules:
|
||||
- prefer_const_constructors
|
||||
- prefer_const_literals_to_create_immutables
|
||||
- prefer_final_fields
|
||||
- avoid_print
|
||||
- avoid_unnecessary_containers
|
||||
- sized_box_for_whitespace
|
||||
- use_key_in_widget_constructors
|
||||
@@ -0,0 +1,52 @@
|
||||
name: wrongbook
|
||||
description: 智能错题本 Flutter 客户端
|
||||
publish_to: 'none'
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: '>=3.4.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
# 状态管理
|
||||
flutter_riverpod: ^2.5.1
|
||||
riverpod_annotation: ^2.3.5
|
||||
|
||||
# 网络
|
||||
dio: ^5.4.3+1
|
||||
|
||||
# 本地存储
|
||||
shared_preferences: ^2.2.3
|
||||
|
||||
# 数学公式渲染
|
||||
flutter_math_fork: ^0.7.2
|
||||
|
||||
# 图片选择和处理
|
||||
image_picker: ^1.0.8
|
||||
|
||||
# WebSocket
|
||||
web_socket_channel: ^2.4.5
|
||||
|
||||
# JSON 序列化
|
||||
json_annotation: ^4.9.0
|
||||
|
||||
# 路由
|
||||
go_router: ^14.1.4
|
||||
|
||||
# UI 组件
|
||||
cupertino_icons: ^1.0.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.0
|
||||
|
||||
# 代码生成
|
||||
build_runner: ^2.4.9
|
||||
riverpod_generator: ^2.4.0
|
||||
json_serializable: ^6.8.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
@@ -0,0 +1,101 @@
|
||||
# Claude API 配置总结
|
||||
|
||||
## ✅ 测试结果
|
||||
|
||||
Claude API 已成功配置并通过测试!
|
||||
|
||||
```
|
||||
============================================================
|
||||
Claude API 连接测试
|
||||
============================================================
|
||||
|
||||
1. 检查配置:
|
||||
ANTHROPIC_AUTH_TOKEN: 已设置
|
||||
ANTHROPIC_BASE_URL: https://apiproxy.aicodeditor.com/api
|
||||
ANTHROPIC_MODEL: claude-sonnet-4-6
|
||||
|
||||
2. 创建 AI 客户端...
|
||||
客户端可用: True
|
||||
|
||||
3. 测试 API 调用...
|
||||
请求 URL: https://apiproxy.aicodeditor.com/api/v1/messages
|
||||
使用模型: claude-sonnet-4-6
|
||||
|
||||
✅ API 调用成功!
|
||||
响应文本: Hello from WrongBook!...
|
||||
输入 tokens: 28
|
||||
输出 tokens: 6
|
||||
延迟: 2431ms
|
||||
|
||||
============================================================
|
||||
✅ 测试通过: Claude API 可以正常使用
|
||||
============================================================
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 当前配置(.env 文件)
|
||||
|
||||
```bash
|
||||
ANTHROPIC_AUTH_TOKEN=sk_1d0e119b46617712413a82bb732d4f78e3b255eae4d2fa3d1c96aa96a9099635
|
||||
ANTHROPIC_BASE_URL=https://apiproxy.aicodeditor.com/api
|
||||
ANTHROPIC_MODEL=claude-sonnet-4-6
|
||||
```
|
||||
|
||||
### 配置文件位置
|
||||
|
||||
- **环境变量文件**: `/ljwx-shiti/.env` (项目根目录)
|
||||
- **配置模块**: `backend/app/config.py`
|
||||
- **AI 客户端**: `backend/app/services/ai_client.py`
|
||||
|
||||
### 关键修改
|
||||
|
||||
1. **环境变量名称更新**:
|
||||
- `ANTHROPIC_API_KEY` → `ANTHROPIC_AUTH_TOKEN`
|
||||
- 新增 `ANTHROPIC_BASE_URL` 支持
|
||||
|
||||
2. **配置文件路径修复**:
|
||||
- 更新 `config.py` 使其能正确读取项目根目录的 `.env` 文件
|
||||
- 使用 `Path(__file__).parent.parent.parent` 定位项目根目录
|
||||
|
||||
3. **模型名称**:
|
||||
- 默认模型: `claude-sonnet-4-6`
|
||||
- 支持其他模型: `claude-3-5-sonnet-20241022`, `claude-3-opus-20240229` 等
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 在代码中使用
|
||||
|
||||
```python
|
||||
from app.services.ai_client import get_ai_client
|
||||
|
||||
# 获取客户端(自动选择真实或 Mock)
|
||||
client = get_ai_client()
|
||||
|
||||
# 检查是否可用
|
||||
if client.is_available:
|
||||
result = await client.call_vision(
|
||||
system_prompt="你是一个助手",
|
||||
user_content=[{"type": "text", "text": "你好"}],
|
||||
max_tokens=100,
|
||||
)
|
||||
print(result['text'])
|
||||
```
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python test_claude_api.py
|
||||
```
|
||||
|
||||
## 降级策略
|
||||
|
||||
如果 `ANTHROPIC_AUTH_TOKEN` 未设置或为空,系统会自动使用 `MockAiClient`,返回预设的测试数据,不会影响开发和测试流程。
|
||||
|
||||
## 下一步
|
||||
|
||||
Claude API 已就绪,可以开始实现:
|
||||
- Layer 3: Sonnet 识别(题目文本识别)
|
||||
- Layer 4: 解析(题目结构化分析)
|
||||
- AI 调用可追溯性(ai_invocation_logs 表)
|
||||
@@ -0,0 +1,143 @@
|
||||
# Claude API 测试指南
|
||||
|
||||
## 配置环境变量
|
||||
|
||||
在运行测试之前,需要设置以下环境变量:
|
||||
|
||||
```bash
|
||||
# 必需: Claude API 认证令牌
|
||||
export ANTHROPIC_AUTH_TOKEN='your-api-key-here'
|
||||
|
||||
# 可选: API 基础 URL (默认为官方 API)
|
||||
export ANTHROPIC_BASE_URL='https://api.anthropic.com'
|
||||
|
||||
# 可选: 模型名称 (推荐使用以下之一)
|
||||
export ANTHROPIC_MODEL='claude-sonnet-4-6' # Sonnet 4.6 (最新,推荐)
|
||||
# export ANTHROPIC_MODEL='claude-3-5-sonnet-20241022' # Sonnet 3.5
|
||||
# export ANTHROPIC_MODEL='claude-3-opus-20240229' # Opus 3
|
||||
# export ANTHROPIC_MODEL='claude-3-sonnet-20240229' # Sonnet 3
|
||||
# export ANTHROPIC_MODEL='claude-3-haiku-20240307' # Haiku 3
|
||||
```
|
||||
|
||||
**重要提示:模型名称格式**
|
||||
|
||||
Claude API 支持以下模型名称格式:
|
||||
- `claude-sonnet-4-6` - Claude Sonnet 4.6 (最新版本,推荐)
|
||||
- `claude-3-5-sonnet-20241022` - Claude 3.5 Sonnet
|
||||
- `claude-3-opus-20240229` - Claude 3 Opus (最强大)
|
||||
- `claude-3-sonnet-20240229` - Claude 3 Sonnet
|
||||
- `claude-3-haiku-20240307` - Claude 3 Haiku (最快)
|
||||
|
||||
如果你使用的是代理服务(如 `https://apiproxy.aicodeditor.com/api`),请确认代理服务支持的模型名称格式。
|
||||
|
||||
## 运行测试
|
||||
|
||||
### 方法 1: 直接运行测试脚本
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python test_claude_api.py
|
||||
```
|
||||
|
||||
### 方法 2: 使用 .env 文件
|
||||
|
||||
1. 复制 `.env.example` 到 `.env`:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. 编辑 `.env` 文件,设置你的 API key:
|
||||
```bash
|
||||
ANTHROPIC_AUTH_TOKEN=your-api-key-here
|
||||
ANTHROPIC_BASE_URL=https://api.anthropic.com
|
||||
ANTHROPIC_MODEL=claude-sonnet-4-6
|
||||
```
|
||||
|
||||
3. 运行测试:
|
||||
```bash
|
||||
cd backend
|
||||
python test_claude_api.py
|
||||
```
|
||||
|
||||
## 测试输出示例
|
||||
|
||||
### 成功的输出:
|
||||
```
|
||||
============================================================
|
||||
Claude API 连接测试
|
||||
============================================================
|
||||
|
||||
1. 检查配置:
|
||||
ANTHROPIC_AUTH_TOKEN: 已设置
|
||||
ANTHROPIC_BASE_URL: https://api.anthropic.com
|
||||
ANTHROPIC_MODEL: claude-sonnet-4-6
|
||||
|
||||
2. 创建 AI 客户端...
|
||||
客户端可用: True
|
||||
|
||||
3. 测试 API 调用...
|
||||
✅ API 调用成功!
|
||||
响应文本: Hello from WrongBook!
|
||||
输入 tokens: 25
|
||||
输出 tokens: 12
|
||||
延迟: 1234ms
|
||||
|
||||
============================================================
|
||||
✅ 测试通过: Claude API 可以正常使用
|
||||
============================================================
|
||||
```
|
||||
|
||||
### 失败的输出:
|
||||
```
|
||||
============================================================
|
||||
Claude API 连接测试
|
||||
============================================================
|
||||
|
||||
1. 检查配置:
|
||||
ANTHROPIC_AUTH_TOKEN: 未设置
|
||||
ANTHROPIC_BASE_URL: https://api.anthropic.com
|
||||
ANTHROPIC_MODEL: claude-sonnet-4-20250514
|
||||
|
||||
❌ 错误: ANTHROPIC_AUTH_TOKEN 未设置
|
||||
|
||||
请设置环境变量:
|
||||
export ANTHROPIC_AUTH_TOKEN='your-api-key'
|
||||
export ANTHROPIC_BASE_URL='https://api.anthropic.com'
|
||||
|
||||
============================================================
|
||||
❌ 测试失败: Claude API 无法连接
|
||||
============================================================
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1. 认证失败
|
||||
如果看到 `401 Unauthorized` 错误,请检查:
|
||||
- API key 是否正确
|
||||
- API key 是否有效且未过期
|
||||
|
||||
### 2. 网络连接失败
|
||||
如果看到连接超时或网络错误:
|
||||
- 检查网络连接
|
||||
- 检查 `ANTHROPIC_BASE_URL` 是否正确
|
||||
- 如果使用代理,确保代理配置正确
|
||||
|
||||
### 3. 模型不存在
|
||||
如果看到模型不存在的错误:
|
||||
- 检查 `ANTHROPIC_MODEL` 配置是否正确
|
||||
- 确认你的 API key 有权限访问该模型
|
||||
|
||||
## 代码集成
|
||||
|
||||
测试通过后,你的应用将自动使用配置的 Claude API。如果 `ANTHROPIC_AUTH_TOKEN` 未设置,系统会自动降级到 Mock 模式用于开发和测试。
|
||||
|
||||
```python
|
||||
from app.services.ai_client import get_ai_client
|
||||
|
||||
# 获取客户端(自动��择真实或 Mock)
|
||||
client = get_ai_client()
|
||||
|
||||
# 检查是否可用
|
||||
if client.is_available:
|
||||
result = await client.call_vision(...)
|
||||
```
|
||||
@@ -0,0 +1,262 @@
|
||||
# 自动化测试套件实施总结
|
||||
|
||||
## 已完成的工作
|
||||
|
||||
### 1. 测试基础设施 ✅
|
||||
|
||||
**更新的文件**:
|
||||
- `backend/pyproject.toml` - 添加测试依赖(pytest-cov, factory-boy, respx)
|
||||
- `backend/tests/conftest.py` - 完善测试配置和 fixtures
|
||||
|
||||
**新增功能**:
|
||||
- 真实 PostgreSQL 数据库测试支持
|
||||
- AI API Mock 支持(respx)
|
||||
- 测试数据工厂支持(factory-boy)
|
||||
- 测试图片生成支持
|
||||
|
||||
### 2. 测试工厂 ✅
|
||||
|
||||
**创建的文件**:
|
||||
- `backend/tests/factories.py` - 用户和错题数据工厂
|
||||
|
||||
**功能**:
|
||||
- `UserFactory` - 创建测试用户
|
||||
- `WrongQuestionFactory` - 创建测试错题
|
||||
- 支持批量创建和异步操作
|
||||
|
||||
### 3. 单元测试示例 ✅
|
||||
|
||||
**创建的文件**:
|
||||
- `backend/tests/unit/test_error_recaller_unit.py` - 错题召回评分算法测试
|
||||
|
||||
**测试覆盖**:
|
||||
- 高分题块识别(叉号标记)
|
||||
- 低分题块识别(干净题块)
|
||||
- 边界情况处理
|
||||
- 扣分文本影响
|
||||
- 订正标记影响
|
||||
- 参数化测试(红色区域比例)
|
||||
|
||||
### 4. 集成测试示例 ✅
|
||||
|
||||
**创建的文件**:
|
||||
- `backend/tests/integration/test_questions_search_api.py` - 错题搜索 API 测试
|
||||
|
||||
**测试覆盖**:
|
||||
- 按学科和难度搜索
|
||||
- 按错误类型搜索
|
||||
- 全文搜索
|
||||
- 分页功能
|
||||
- 权限控制
|
||||
- 数据隔离
|
||||
|
||||
### 5. 端到端测试示例 ✅
|
||||
|
||||
**创建的文件**:
|
||||
- `backend/tests/e2e/test_full_pipeline.py` - 完整流水线测试
|
||||
|
||||
**测试覆盖**:
|
||||
- 完整的上传到入库流程
|
||||
- 低置信度处理
|
||||
- 错误处理
|
||||
- 多图片上传
|
||||
|
||||
### 6. CI/CD 配置 ✅
|
||||
|
||||
**创建的文件**:
|
||||
- `.github/workflows/backend-tests.yml` - GitHub Actions 配置
|
||||
|
||||
**功能**:
|
||||
- 自动运行测试
|
||||
- 代码质量检查
|
||||
- 覆盖率报告
|
||||
- Codecov 集成
|
||||
|
||||
### 7. 文档 ✅
|
||||
|
||||
**创建的文件**:
|
||||
- `backend/docs/TESTING.md` - 测试文档
|
||||
- `backend/docs/QUALITY_ASSURANCE.md` - 质量保障文档
|
||||
|
||||
**内容**:
|
||||
- 测试架构说明
|
||||
- 快速开始指南
|
||||
- 编写测试指南
|
||||
- 最佳实践
|
||||
- 故障排查
|
||||
- 质量门禁
|
||||
- AI 服务质量追踪
|
||||
- 分阶段交付计划
|
||||
|
||||
### 8. 测试脚本 ✅
|
||||
|
||||
**创建的文件**:
|
||||
- `backend/run_tests.sh` - 测试运行脚本
|
||||
|
||||
**功能**:
|
||||
- 代码检查
|
||||
- 类型检查
|
||||
- 运行测试
|
||||
- 覆盖率报告
|
||||
- 彩色输出
|
||||
|
||||
## 测试架构
|
||||
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # 测试配置和 fixtures
|
||||
├── factories.py # 测试数据工厂
|
||||
��── unit/ # 单元测试 (60%)
|
||||
│ └── test_error_recaller_unit.py
|
||||
├── integration/ # 集成测试 (30%)
|
||||
│ └── test_questions_search_api.py
|
||||
└── e2e/ # 端到端测试 (10%)
|
||||
└── test_full_pipeline.py
|
||||
```
|
||||
|
||||
## 使用指南
|
||||
|
||||
### 安装依赖
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
# 运行所有测试
|
||||
pytest
|
||||
|
||||
# 运行特定类型的测试
|
||||
./run_tests.sh --unit # 单元测试
|
||||
./run_tests.sh --integration # 集成测试
|
||||
./run_tests.sh --e2e # 端到端测试
|
||||
|
||||
# 运行带覆盖率的测试
|
||||
./run_tests.sh --coverage
|
||||
|
||||
# 详细输出
|
||||
./run_tests.sh -v
|
||||
```
|
||||
|
||||
### 查看覆盖率
|
||||
|
||||
```bash
|
||||
pytest --cov=app --cov-report=html
|
||||
open htmlcov/index.html
|
||||
```
|
||||
|
||||
## 质量门禁
|
||||
|
||||
每次 PR 合并前必须通过:
|
||||
|
||||
1. ✅ `ruff check` - 零 warning
|
||||
2. ✅ `pytest --cov-fail-under=70` - 覆盖率 ≥ 70%
|
||||
3. ✅ 所有测试通过
|
||||
4. ✅ Code Review
|
||||
|
||||
## 关键特性
|
||||
|
||||
### 1. 真实数据库测试
|
||||
|
||||
使用真实的 PostgreSQL 数据库,支持:
|
||||
- ARRAY 类型
|
||||
- GIN 索引
|
||||
- tsvector 全文检索
|
||||
- 事务回滚隔离
|
||||
|
||||
### 2. AI API Mock
|
||||
|
||||
使用 respx 拦截 HTTP 请求:
|
||||
- 不产生实际费用
|
||||
- 可控的响应
|
||||
- 支持不同场景
|
||||
|
||||
### 3. 测试数据工厂
|
||||
|
||||
使用 factory-boy 创建测试数据:
|
||||
- 自动生成合理的测试数据
|
||||
- 支持批量创建
|
||||
- 支持异步操作
|
||||
|
||||
### 4. 完整的测试覆盖
|
||||
|
||||
- 单元测试:纯逻辑函数
|
||||
- 集成测试:API + 数据库
|
||||
- 端到端测试:完整流程
|
||||
|
||||
## 下一步行动
|
||||
|
||||
### 立即执行
|
||||
|
||||
1. **安装测试依赖**
|
||||
```bash
|
||||
cd backend
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
2. **运行测试验证**
|
||||
```bash
|
||||
./run_tests.sh
|
||||
```
|
||||
|
||||
3. **查看覆盖率**
|
||||
```bash
|
||||
./run_tests.sh --coverage
|
||||
open htmlcov/index.html
|
||||
```
|
||||
|
||||
### 后续工作
|
||||
|
||||
4. **补充现有代码的测试**
|
||||
- 为已有的 API 添加集成测试
|
||||
- 为业务逻辑添加单元测试
|
||||
- 提高覆盖率到 70%+
|
||||
|
||||
5. **实现核心 Pipeline**
|
||||
- Stage 1: 题块切分
|
||||
- Stage 2: 错题召回
|
||||
- Stage 3: Sonnet 识别
|
||||
- Stage 4: 解析
|
||||
- 每个 Stage 都要有对应的测试
|
||||
|
||||
6. **设置 CI/CD**
|
||||
- 推送代码到 GitHub
|
||||
- 验证 GitHub Actions 运行
|
||||
- 配置 Codecov
|
||||
|
||||
7. **准备测试数据集**
|
||||
- 收集 50 张真实试卷照片
|
||||
- 建立验收测试集
|
||||
- 记录基准指标
|
||||
|
||||
## 测试覆盖率目标
|
||||
|
||||
- **整体**: ≥ 70%
|
||||
- **核心模块**: ≥ 90%
|
||||
- `app/services/pipeline/`
|
||||
- `app/services/review/`
|
||||
- `app/api/`
|
||||
|
||||
## 参考文档
|
||||
|
||||
- [测试文档](docs/TESTING.md)
|
||||
- [质量保障文档](docs/QUALITY_ASSURANCE.md)
|
||||
- [pytest 文档](https://docs.pytest.org/)
|
||||
- [factory-boy 文档](https://factoryboy.readthedocs.io/)
|
||||
- [respx 文档](https://lundberg.github.io/respx/)
|
||||
|
||||
## 总结
|
||||
|
||||
自动化测试套件已经完整搭建,包括:
|
||||
- ✅ 三层测试架构(单元/集成/端到端)
|
||||
- ✅ 完善的测试基础设施
|
||||
- ✅ AI API Mock 方案
|
||||
- ✅ 测试数据工厂
|
||||
- ✅ CI/CD 配置
|
||||
- ✅ 详细的文档
|
||||
- ✅ 便捷的测试脚本
|
||||
|
||||
现在可以开始编写业务代码,并为每个功能添加相应的测试。
|
||||
+12
-3
@@ -1,7 +1,12 @@
|
||||
"""配置管理模块"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
# 项目根目录(backend 的父目录)
|
||||
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""应用配置"""
|
||||
@@ -24,8 +29,9 @@ class Settings(BaseSettings):
|
||||
MINIO_PRESIGN_EXPIRY: int = 3600 # presigned URL 有效期(秒)
|
||||
|
||||
# AI (可选)
|
||||
ANTHROPIC_API_KEY: str = "" # 为空=不可用, 测试用mock
|
||||
ANTHROPIC_MODEL: str = "claude-sonnet-4-20250514"
|
||||
ANTHROPIC_AUTH_TOKEN: str = "" # 为空=不可用, 测试用mock
|
||||
ANTHROPIC_BASE_URL: str = "https://api.anthropic.com"
|
||||
ANTHROPIC_MODEL: str = "claude-sonnet-4-6" # 或 claude-3-5-sonnet-20241022
|
||||
ANTHROPIC_TEMPERATURE: float = 0.1
|
||||
ANTHROPIC_MAX_TOKENS_RECOGNIZE: int = 2048
|
||||
ANTHROPIC_MAX_TOKENS_ANALYZE: int = 4096
|
||||
@@ -47,7 +53,10 @@ class Settings(BaseSettings):
|
||||
APP_ENV: str = "development"
|
||||
LOG_LEVEL: str = "INFO"
|
||||
|
||||
model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
|
||||
model_config = {
|
||||
"env_file": str(PROJECT_ROOT / ".env"),
|
||||
"env_file_encoding": "utf-8",
|
||||
}
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
@@ -30,10 +30,11 @@ class QuestionResponse(BaseModel):
|
||||
"""错题响应"""
|
||||
|
||||
id: int
|
||||
user_id: int
|
||||
subject_id: int
|
||||
question_text: str | None
|
||||
question_image_url: str | None
|
||||
clean_image_url: str | None
|
||||
question_image_url: str | None = None
|
||||
clean_image_url: str | None = None
|
||||
question_type: str | None
|
||||
correct_answer: str | None
|
||||
my_answer: str | None
|
||||
|
||||
@@ -18,10 +18,11 @@ class AiClient:
|
||||
"""AI 客户端"""
|
||||
|
||||
def __init__(self):
|
||||
self._api_key = settings.ANTHROPIC_API_KEY
|
||||
self._auth_token = settings.ANTHROPIC_AUTH_TOKEN
|
||||
self._base_url = settings.ANTHROPIC_BASE_URL
|
||||
self._model = settings.ANTHROPIC_MODEL
|
||||
self._http = httpx.AsyncClient(timeout=90.0)
|
||||
self._available = bool(self._api_key)
|
||||
self._available = bool(self._auth_token)
|
||||
|
||||
@property
|
||||
def is_available(self) -> bool:
|
||||
@@ -43,9 +44,9 @@ class AiClient:
|
||||
# 调用 Anthropic API
|
||||
try:
|
||||
response = await self._http.post(
|
||||
"https://api.anthropic.com/v1/messages",
|
||||
f"{self._base_url}/v1/messages",
|
||||
headers={
|
||||
"x-api-key": self._api_key,
|
||||
"x-api-key": self._auth_token,
|
||||
"anthropic-version": "2023-06-01",
|
||||
"content-type": "application/json",
|
||||
},
|
||||
@@ -107,6 +108,6 @@ class MockAiClient(AiClient):
|
||||
|
||||
def get_ai_client() -> AiClient:
|
||||
"""获取 AI 客户端(依赖注入)"""
|
||||
if settings.ANTHROPIC_API_KEY:
|
||||
if settings.ANTHROPIC_AUTH_TOKEN:
|
||||
return AiClient()
|
||||
return MockAiClient()
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
# 项目质量与交付保障体系
|
||||
|
||||
## 代码质量门禁
|
||||
|
||||
每次 PR 合并前必须通过以下四道关卡:
|
||||
|
||||
### 1. 静态代码分析
|
||||
|
||||
```bash
|
||||
# Ruff 检查(零 warning)
|
||||
ruff check .
|
||||
|
||||
# 自动修复
|
||||
ruff check --fix .
|
||||
```
|
||||
|
||||
**要求**: 零 warning,所有代码符合 PEP 8 规范
|
||||
|
||||
### 2. 测试覆盖率
|
||||
|
||||
```bash
|
||||
# 运行测试并检查覆盖率
|
||||
pytest --cov=app --cov-report=term --cov-fail-under=70
|
||||
```
|
||||
|
||||
**要求**:
|
||||
- 整体覆盖率 ≥ 70%
|
||||
- 核心业务模块 ≥ 90%
|
||||
- `app/services/pipeline/`
|
||||
- `app/services/review/`
|
||||
- `app/api/`
|
||||
|
||||
### 3. 类型检查(可选)
|
||||
|
||||
```bash
|
||||
# MyPy 类型检查
|
||||
mypy app --strict
|
||||
```
|
||||
|
||||
**要求**: 关键模块通过严格类型检查
|
||||
|
||||
### 4. Code Review
|
||||
|
||||
**检查清单**:
|
||||
- [ ] API 是否使用统一响应格式
|
||||
- [ ] 异常是否包含 request_id
|
||||
- [ ] AI 调用是否记录到 ai_invocation_logs
|
||||
- [ ] 数据库查询是否有适当的索引
|
||||
- [ ] 是否有适当的错误处理
|
||||
- [ ] 是否有必要的日志记录
|
||||
- [ ] 是否有安全漏洞(SQL 注入、XSS 等)
|
||||
- [ ] 是否遵循项目约定和最佳实践
|
||||
|
||||
## AI 服务质量追踪
|
||||
|
||||
### AI 调用追溯
|
||||
|
||||
每次调用 Claude API 都必须写入 `ai_invocation_logs` 表:
|
||||
|
||||
```python
|
||||
# 记录 AI 调用
|
||||
await log_ai_invocation(
|
||||
session=session,
|
||||
model=settings.ANTHROPIC_MODEL,
|
||||
prompt_version="v1.0",
|
||||
input_hash=compute_hash(input_data),
|
||||
output_hash=compute_hash(output_data),
|
||||
confidence=0.92,
|
||||
latency_ms=1234,
|
||||
input_tokens=1000,
|
||||
output_tokens=200,
|
||||
estimated_cost=0.015,
|
||||
)
|
||||
```
|
||||
|
||||
### 质量监控指标
|
||||
|
||||
通过 AI 调用日志回答三个关键问题:
|
||||
|
||||
#### 1. 识别准确率是否在持续提升?
|
||||
|
||||
```sql
|
||||
-- 按周统计平均置信度趋势
|
||||
SELECT
|
||||
DATE_TRUNC('week', created_at) as week,
|
||||
AVG(confidence) as avg_confidence,
|
||||
COUNT(*) as call_count
|
||||
FROM ai_invocation_logs
|
||||
WHERE operation_type = 'recognition'
|
||||
GROUP BY week
|
||||
ORDER BY week DESC;
|
||||
```
|
||||
|
||||
#### 2. 哪些类型的题目识别效果差?
|
||||
|
||||
```sql
|
||||
-- 按错误类型统计低置信度比例
|
||||
SELECT
|
||||
error_type,
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN confidence < 0.7 THEN 1 ELSE 0 END) as low_confidence_count,
|
||||
AVG(confidence) as avg_confidence
|
||||
FROM wrong_questions
|
||||
GROUP BY error_type
|
||||
ORDER BY avg_confidence ASC;
|
||||
```
|
||||
|
||||
#### 3. Prompt 迭代是否带来改善?
|
||||
|
||||
```sql
|
||||
-- 按 Prompt 版本对比准确率
|
||||
SELECT
|
||||
prompt_version,
|
||||
AVG(confidence) as avg_confidence,
|
||||
COUNT(*) as call_count,
|
||||
AVG(latency_ms) as avg_latency
|
||||
FROM ai_invocation_logs
|
||||
WHERE operation_type = 'recognition'
|
||||
GROUP BY prompt_version
|
||||
ORDER BY prompt_version DESC;
|
||||
```
|
||||
|
||||
### 置信度阈值运营策略
|
||||
|
||||
**初期(保守)**:
|
||||
- 自动通过阈值: 0.90
|
||||
- 人工审核阈值: 0.70 - 0.90
|
||||
- 自动拒绝阈值: < 0.70
|
||||
|
||||
**优化后(平衡)**:
|
||||
- 收集两周数据
|
||||
- 如果人工确认通过率 > 95%
|
||||
- 逐步下调至 0.85
|
||||
|
||||
**监控指标**:
|
||||
- 人工确认通过率
|
||||
- 用户修改率
|
||||
- 平均处理时间
|
||||
|
||||
## 分阶段交付里程碑
|
||||
|
||||
### Phase 1 - MVP(第 1-6 周)
|
||||
|
||||
#### 交付物
|
||||
- ✅ 后端全部 API
|
||||
- ✅ Flutter App(Android + iOS)
|
||||
- ✅ uni-app 微信小程序
|
||||
|
||||
#### 验收标准
|
||||
|
||||
**功能验收**:
|
||||
1. 上传一张真实数学试卷照片
|
||||
2. 系统在 30 秒内完成识别、去痕、解析并入库
|
||||
3. 错题列表支持按学科、难度、错因筛选
|
||||
4. 解析内容包含完整步骤和错因分析
|
||||
5. LaTeX 公式正确渲染
|
||||
|
||||
**质量验收**:
|
||||
- 测试覆盖率 ≥ 70%
|
||||
- 核心模块覆盖率 ≥ 90%
|
||||
- 所有 API 响应时间 P95 < 500ms(不含 AI 调用)
|
||||
|
||||
#### 验收测试集
|
||||
|
||||
准备 50 张真实数学试卷照片,覆盖:
|
||||
- 题型:选择题、填空题、计算题、证明题
|
||||
- 布局:单栏、双栏
|
||||
- 条件:不同光照、角度、清晰度
|
||||
|
||||
**目标指标**:
|
||||
- 题块召回率 ≥ 95%
|
||||
- 错题命中率 ≥ 90%
|
||||
- 题面文字准确率 ≥ 80%
|
||||
- 解析可用率 ≥ 85%
|
||||
- 无需人工干预比例 ≥ 70%
|
||||
|
||||
### Phase 2 - 增强(第 7-10 周)
|
||||
|
||||
#### 交付物
|
||||
- 智能复习推送
|
||||
- 统计分析面板
|
||||
- 打印导出功能
|
||||
|
||||
#### 验收标准
|
||||
|
||||
**复习功能**:
|
||||
- SM-2 算法按预期推送复习题目
|
||||
- 连续答对的题目间隔递增
|
||||
- 答错的题目次日重现
|
||||
|
||||
**统计功能**:
|
||||
- 错误类型分布图
|
||||
- 知识点薄弱热力图
|
||||
- 掌握度趋势曲线
|
||||
|
||||
## 监控与告警
|
||||
|
||||
### 生产环境监控
|
||||
|
||||
使用 Prometheus + Grafana 监控以下指标:
|
||||
|
||||
#### 1. API 性能
|
||||
|
||||
```yaml
|
||||
# 响应时间
|
||||
api_response_time_p95 < 500ms # 不含 AI 调用
|
||||
api_response_time_p99 < 1000ms
|
||||
|
||||
# 错误率
|
||||
api_error_rate < 1%
|
||||
```
|
||||
|
||||
#### 2. 任务队列
|
||||
|
||||
```yaml
|
||||
# Celery 队列深度
|
||||
celery_queue_depth < 100 # 超出触发告警
|
||||
|
||||
# 任务处理时间
|
||||
celery_task_duration_p95 < 30s
|
||||
```
|
||||
|
||||
#### 3. AI 服务
|
||||
|
||||
```yaml
|
||||
# Claude API 错误率
|
||||
claude_api_error_rate < 2% # 超出自动降级
|
||||
|
||||
# AI 调用延迟
|
||||
claude_api_latency_p95 < 5000ms
|
||||
```
|
||||
|
||||
#### 4. 存储
|
||||
|
||||
```yaml
|
||||
# MinIO 存储使用率
|
||||
minio_storage_usage < 80%
|
||||
|
||||
# 数据库连接池
|
||||
db_connection_pool_usage < 80%
|
||||
```
|
||||
|
||||
### 告警策略
|
||||
|
||||
**P0 - 紧急**:
|
||||
- API 完全不可用
|
||||
- 数据库连接失败
|
||||
- 存储空间耗尽
|
||||
|
||||
**P1 - 高优先级**:
|
||||
- API 错误率 > 5%
|
||||
- 任务队列积压 > 500
|
||||
- AI 服务错误率 > 10%
|
||||
|
||||
**P2 - 中优先级**:
|
||||
- API 响应时间 P95 > 1s
|
||||
- 任务队列积压 > 100
|
||||
- 存储使用率 > 80%
|
||||
|
||||
### 日志追踪
|
||||
|
||||
使用 structlog,每条日志包含 request_id:
|
||||
|
||||
```python
|
||||
import structlog
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
# 在请求中间件中设置 request_id
|
||||
logger.info(
|
||||
"api_request",
|
||||
request_id=request_id,
|
||||
method=request.method,
|
||||
path=request.url.path,
|
||||
)
|
||||
|
||||
# 在业务代码中使用
|
||||
logger.info(
|
||||
"question_created",
|
||||
request_id=request_id,
|
||||
question_id=question.id,
|
||||
user_id=user.id,
|
||||
)
|
||||
```
|
||||
|
||||
### 错误追踪
|
||||
|
||||
使用 Sentry 收集错误:
|
||||
|
||||
```python
|
||||
import sentry_sdk
|
||||
|
||||
sentry_sdk.init(
|
||||
dsn="your-sentry-dsn",
|
||||
environment=settings.APP_ENV,
|
||||
traces_sample_rate=0.1,
|
||||
)
|
||||
|
||||
# 自动捕获异常
|
||||
# 手动记录
|
||||
sentry_sdk.capture_message("Something went wrong")
|
||||
```
|
||||
|
||||
## 渐进式发布策略
|
||||
|
||||
### 1. 内部灰度(第 1 周)
|
||||
|
||||
**范围**: 团队 + 10 名种子用户
|
||||
|
||||
**目标**:
|
||||
- 验证核心功能可用
|
||||
- 收集初步反馈
|
||||
- 修复高优问题
|
||||
|
||||
**监控指标**:
|
||||
- 崩溃率
|
||||
- 核心功能完成率
|
||||
- 用户反馈
|
||||
|
||||
### 2. 小范围公测(第 2-3 周)
|
||||
|
||||
**范围**: 100 名用户
|
||||
|
||||
**目标**:
|
||||
- 验证系统稳定性
|
||||
- 优化用户体验
|
||||
- 调整 AI Prompt
|
||||
|
||||
**重点关注**:
|
||||
1. 用户是否在"人工确认"页面频繁修改识别结果
|
||||
- 说明 Prompt 需要优化
|
||||
2. 单次试卷处理的平均耗时是否超过 30 秒
|
||||
- 可能需要并行优化
|
||||
3. 用户次日留存率
|
||||
- 衡量解析质量是否真正有价值
|
||||
|
||||
### 3. 全量发布(第 4 周+)
|
||||
|
||||
**条件**:
|
||||
- 崩溃率 < 0.1%
|
||||
- 核心功能完成率 > 90%
|
||||
- 用户满意度 > 4.0/5.0
|
||||
|
||||
## 质量检查清单
|
||||
|
||||
### 代码提交前
|
||||
|
||||
- [ ] 运行 `ruff check` 无 warning
|
||||
- [ ] 运行 `pytest` 所有测试通过
|
||||
- [ ] 新功能有对应的测试
|
||||
- [ ] 更新了相关文档
|
||||
- [ ] Commit message 清晰描述改动
|
||||
|
||||
### PR 创建前
|
||||
|
||||
- [ ] 代码已经过自我审查
|
||||
- [ ] 删除了调试代码和注释
|
||||
- [ ] 更新了 CHANGELOG
|
||||
- [ ] 添加了必要的注释
|
||||
- [ ] 检查了安全问题
|
||||
|
||||
### 发布前
|
||||
|
||||
- [ ] 所有测试通过
|
||||
- [ ] 覆盖率达标
|
||||
- [ ] 性能测试通过
|
||||
- [ ] 安全扫描通过
|
||||
- [ ] 文档已更新
|
||||
- [ ] 发布说明已准备
|
||||
|
||||
## 总结行动清单
|
||||
|
||||
### 当前最高优先级
|
||||
|
||||
1. **验证 Claude API**
|
||||
```bash
|
||||
cd backend
|
||||
python test_claude_api.py
|
||||
```
|
||||
|
||||
2. **启动测试基础设施**
|
||||
```bash
|
||||
docker compose up -d postgres redis
|
||||
pytest -v
|
||||
```
|
||||
|
||||
3. **准备测试数据集**
|
||||
- 收集 5 张真实数学试卷照片
|
||||
- 手动调用 Pipeline Stage 3(Sonnet 识别)
|
||||
- 观察输出质量
|
||||
- 调整 Prompt 和置信度阈值
|
||||
|
||||
### 下一步
|
||||
|
||||
4. **实现核心 Pipeline**
|
||||
- Stage 1: 题块切分
|
||||
- Stage 2: 错题召回
|
||||
- Stage 3: Sonnet 识别
|
||||
- Stage 4: 解析
|
||||
|
||||
5. **编写测试**
|
||||
- 每个 Stage 的单元测试
|
||||
- Pipeline 集成测试
|
||||
- 端到端测试
|
||||
|
||||
6. **设置 CI/CD**
|
||||
- 配置 GitHub Actions
|
||||
- 设置覆盖率报告
|
||||
- 配置自动部署
|
||||
|
||||
完成这些步骤后,项目的核心技术风险基本消除,可以进入正式开发节奏。
|
||||
@@ -0,0 +1,384 @@
|
||||
# 自动化测试套件文档
|
||||
|
||||
## 测试架构概览
|
||||
|
||||
本项目采用三层测试金字塔架构:
|
||||
|
||||
```
|
||||
/\
|
||||
/ \ 端到端测试 (10%)
|
||||
/____\
|
||||
/ \ 集成测试 (30%)
|
||||
/________\
|
||||
/ \ 单元测试 (60%)
|
||||
/__________ \
|
||||
```
|
||||
|
||||
### 测试分层
|
||||
|
||||
1. **单元测试 (60%)** - 测试纯逻辑函数
|
||||
- 错题召回评分算法
|
||||
- SM-2 复习调度算法
|
||||
- 图像预处理参数校验
|
||||
- 数据模型验证
|
||||
|
||||
2. **集成测试 (30%)** - 测试组件交互
|
||||
- API 端点与数据库交互
|
||||
- Celery 任务链
|
||||
- WebSocket 消息推送
|
||||
- 外部服务集成(Mock)
|
||||
|
||||
3. **端到端测试 (10%)** - 测试完整流程
|
||||
- 图片上传到错题入库
|
||||
- 用户注册到复习推送
|
||||
- 完整的业务场景
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 安装测试依赖
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
### 运行所有测试
|
||||
|
||||
```bash
|
||||
pytest
|
||||
```
|
||||
|
||||
### 运行特定类型的测试
|
||||
|
||||
```bash
|
||||
# 只运行单元测试
|
||||
pytest tests/unit/
|
||||
|
||||
# 只运行集成测试
|
||||
pytest tests/integration/
|
||||
|
||||
# 只运行端到端测试
|
||||
pytest tests/e2e/
|
||||
```
|
||||
|
||||
### 运行��覆盖率的测试
|
||||
|
||||
```bash
|
||||
pytest --cov=app --cov-report=html --cov-report=term
|
||||
```
|
||||
|
||||
查看覆盖率报告:
|
||||
```bash
|
||||
open htmlcov/index.html
|
||||
```
|
||||
|
||||
### 运行特定测试文件
|
||||
|
||||
```bash
|
||||
pytest tests/unit/test_error_recaller_unit.py -v
|
||||
```
|
||||
|
||||
### 运行特定测试用例
|
||||
|
||||
```bash
|
||||
pytest tests/unit/test_error_recaller_unit.py::TestErrorRecaller::test_high_score_with_cross_mark -v
|
||||
```
|
||||
|
||||
## 测试基础设施
|
||||
|
||||
### 数据库测试
|
||||
|
||||
测试使用真实的 PostgreSQL 数据库(通过 testcontainers 或独立测试数据库):
|
||||
|
||||
```python
|
||||
# conftest.py 自动提供
|
||||
@pytest_asyncio.fixture
|
||||
async def db_session(test_engine, setup_database):
|
||||
"""每个测试用事务包裹并回滚"""
|
||||
async with async_session() as session:
|
||||
async with session.begin():
|
||||
yield session
|
||||
await session.rollback() # 测试后回滚,互不干扰
|
||||
```
|
||||
|
||||
### AI 服务 Mock
|
||||
|
||||
所有 Claude API 调用都通过 `respx` 库 Mock,不会产生实际费用:
|
||||
|
||||
```python
|
||||
@pytest.fixture
|
||||
def mock_claude_api(mock_claude_recognition_response, mock_claude_analysis_response):
|
||||
"""Mock Claude API 调用"""
|
||||
with respx.mock(assert_all_called=False) as respx_mock:
|
||||
respx_mock.post("https://api.anthropic.com/v1/messages").mock(
|
||||
side_effect=route_claude_calls
|
||||
)
|
||||
yield respx_mock
|
||||
```
|
||||
|
||||
### 测试数据工厂
|
||||
|
||||
使用 `factory-boy` 创建测试数据:
|
||||
|
||||
```python
|
||||
from tests.factories import UserFactory, WrongQuestionFactory
|
||||
|
||||
# 创建单个用户
|
||||
user = await UserFactory.create(session=db_session, phone="13800138000")
|
||||
|
||||
# 批量创建错题
|
||||
questions = await WrongQuestionFactory.create_batch(
|
||||
10, session=db_session, user_id=user.id, difficulty=4
|
||||
)
|
||||
```
|
||||
|
||||
## 编写测试
|
||||
|
||||
### 单元测试示例
|
||||
|
||||
```python
|
||||
# tests/unit/test_my_module.py
|
||||
import pytest
|
||||
|
||||
class TestMyFunction:
|
||||
def test_basic_case(self):
|
||||
result = my_function(input_data)
|
||||
assert result == expected_output
|
||||
|
||||
@pytest.mark.parametrize("input,expected", [
|
||||
(1, 2),
|
||||
(2, 4),
|
||||
(3, 6),
|
||||
])
|
||||
def test_multiple_cases(self, input, expected):
|
||||
assert my_function(input) == expected
|
||||
```
|
||||
|
||||
### 集成测试示例
|
||||
|
||||
```python
|
||||
# tests/integration/test_my_api.py
|
||||
import pytest
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestMyAPI:
|
||||
async def test_create_resource(self, client, db_session):
|
||||
# 创建测试数据
|
||||
user = await UserFactory.create(session=db_session)
|
||||
|
||||
# 调用 API
|
||||
response = await client.post(
|
||||
"/api/v1/resources",
|
||||
json={"name": "test"},
|
||||
headers={"Authorization": f"Bearer {token}"}
|
||||
)
|
||||
|
||||
# 验证响应
|
||||
assert response.status_code == 200
|
||||
assert response.json()["data"]["name"] == "test"
|
||||
|
||||
# 验证数据库
|
||||
result = await db_session.execute(
|
||||
select(Resource).where(Resource.name == "test")
|
||||
)
|
||||
resource = result.scalar_one()
|
||||
assert resource is not None
|
||||
```
|
||||
|
||||
### 端到端测试示例
|
||||
|
||||
```python
|
||||
# tests/e2e/test_user_journey.py
|
||||
import pytest
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestUserJourney:
|
||||
async def test_complete_workflow(self, client, db_session, mock_claude_api):
|
||||
# 1. 用户注册
|
||||
# 2. 上传试卷
|
||||
# 3. 等待处理
|
||||
# 4. 查看错题
|
||||
# 5. 开始复习
|
||||
# 验证整个流程
|
||||
```
|
||||
|
||||
## 测试最佳实践
|
||||
|
||||
### 1. 测试命名
|
||||
|
||||
- 测试文件:`test_*.py`
|
||||
- 测试类:`Test*`
|
||||
- 测试方法:`test_*`
|
||||
- 描述性命名:`test_user_can_login_with_valid_credentials`
|
||||
|
||||
### 2. 测试隔离
|
||||
|
||||
- 每个测试独立运行
|
||||
- 使用事务回滚保证数据库清洁
|
||||
- 不依赖测试执行顺序
|
||||
|
||||
### 3. 测试数据
|
||||
|
||||
- 使用工厂创建测试数据
|
||||
- 避免硬编码 ID
|
||||
- 每个测试创建自己需要的数据
|
||||
|
||||
### 4. Mock 外部服务
|
||||
|
||||
- AI API 调用必须 Mock
|
||||
- 第三方服务必须 Mock
|
||||
- 使用 `respx` Mock HTTP 请求
|
||||
|
||||
### 5. 断言清晰
|
||||
|
||||
```python
|
||||
# ❌ 不好
|
||||
assert result
|
||||
|
||||
# ✅ 好
|
||||
assert result.score > 0.6, "有叉号的题块应该得高分"
|
||||
```
|
||||
|
||||
## CI/CD 集成
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
每次 push 和 PR 都会自动运行测试:
|
||||
|
||||
```yaml
|
||||
# .github/workflows/backend-tests.yml
|
||||
- name: Run tests with coverage
|
||||
run: |
|
||||
cd backend
|
||||
pytest --cov=app --cov-report=xml --cov-fail-under=70 -v
|
||||
```
|
||||
|
||||
### 质量门禁
|
||||
|
||||
PR 合并前必须通过:
|
||||
|
||||
1. ✅ `ruff check` 静态分析零 warning
|
||||
2. ✅ `pytest --cov-fail-under=70` 测试覆盖率 ≥ 70%
|
||||
3. ✅ 所有测试通过
|
||||
4. ✅ 至少一人 Code Review
|
||||
|
||||
## 测试覆盖率目标
|
||||
|
||||
- **整体覆盖率**: ≥ 70%
|
||||
- **核心业务模块**: ≥ 90%
|
||||
- `app/services/pipeline/`
|
||||
- `app/services/review/`
|
||||
- `app/api/`
|
||||
|
||||
### 查看覆盖率
|
||||
|
||||
```bash
|
||||
# 生成覆盖率报告
|
||||
pytest --cov=app --cov-report=html
|
||||
|
||||
# 查看报告
|
||||
open htmlcov/index.html
|
||||
```
|
||||
|
||||
### 覆盖率不足的处理
|
||||
|
||||
```bash
|
||||
# 查看未覆盖的代码
|
||||
pytest --cov=app --cov-report=term-missing
|
||||
```
|
||||
|
||||
## 性能测试
|
||||
|
||||
### 基准测试
|
||||
|
||||
```bash
|
||||
# 运行性能基准测试
|
||||
pytest tests/benchmarks/ --benchmark-only
|
||||
```
|
||||
|
||||
### 压力测试
|
||||
|
||||
```bash
|
||||
# 使用 locust 进行压力测试
|
||||
cd tests/load
|
||||
locust -f locustfile.py
|
||||
```
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 测试失败
|
||||
|
||||
```bash
|
||||
# 显示详细输出
|
||||
pytest -vv
|
||||
|
||||
# 显示 print 输出
|
||||
pytest -s
|
||||
|
||||
# 在第一个失败处停止
|
||||
pytest -x
|
||||
|
||||
# 显示最慢的 10 个测试
|
||||
pytest --durations=10
|
||||
```
|
||||
|
||||
### 数据库问题
|
||||
|
||||
```bash
|
||||
# 检查测试数据库连接
|
||||
psql -h localhost -p 5433 -U wrongbook -d wrongbook_test
|
||||
|
||||
# 重置测试数据库
|
||||
cd backend
|
||||
alembic downgrade base
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
### Mock 问题
|
||||
|
||||
```bash
|
||||
# 查看 HTTP 请求
|
||||
pytest -v --log-cli-level=DEBUG
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 测试运行很慢怎么办?
|
||||
|
||||
A:
|
||||
1. 使用 `pytest-xdist` 并行运行:`pytest -n auto`
|
||||
2. 只运行修改相关的测试
|
||||
3. 使用 `--lf` 只运行上次失败的测试
|
||||
|
||||
### Q: 如何跳过某些测试?
|
||||
|
||||
A:
|
||||
```python
|
||||
@pytest.mark.skip(reason="暂时跳过")
|
||||
def test_something():
|
||||
pass
|
||||
|
||||
@pytest.mark.skipif(condition, reason="条件不满足")
|
||||
def test_something():
|
||||
pass
|
||||
```
|
||||
|
||||
### Q: 如何标记慢速测试?
|
||||
|
||||
A:
|
||||
```python
|
||||
@pytest.mark.slow
|
||||
def test_slow_operation():
|
||||
pass
|
||||
|
||||
# 运行时排除慢速测试
|
||||
pytest -m "not slow"
|
||||
```
|
||||
|
||||
## 参考资源
|
||||
|
||||
- [pytest 文档](https://docs.pytest.org/)
|
||||
- [pytest-asyncio 文档](https://pytest-asyncio.readthedocs.io/)
|
||||
- [factory-boy 文档](https://factoryboy.readthedocs.io/)
|
||||
- [respx 文档](https://lundberg.github.io/respx/)
|
||||
- [testcontainers 文档](https://testcontainers-python.readthedocs.io/)
|
||||
Executable
+139
@@ -0,0 +1,139 @@
|
||||
#!/bin/bash
|
||||
# 测试运行脚本
|
||||
|
||||
set -e
|
||||
|
||||
echo "================================"
|
||||
echo "WrongBook 测试套件"
|
||||
echo "================================"
|
||||
echo ""
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 检查是否在 backend 目录
|
||||
if [ ! -f "pyproject.toml" ]; then
|
||||
echo -e "${RED}错误: 请在 backend 目录下运行此脚本${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 解析参数
|
||||
TEST_TYPE="all"
|
||||
COVERAGE=false
|
||||
VERBOSE=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--unit)
|
||||
TEST_TYPE="unit"
|
||||
shift
|
||||
;;
|
||||
--integration)
|
||||
TEST_TYPE="integration"
|
||||
shift
|
||||
;;
|
||||
--e2e)
|
||||
TEST_TYPE="e2e"
|
||||
shift
|
||||
;;
|
||||
--coverage)
|
||||
COVERAGE=true
|
||||
shift
|
||||
;;
|
||||
-v|--verbose)
|
||||
VERBOSE=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $1"
|
||||
echo "用法: $0 [--unit|--integration|--e2e] [--coverage] [-v|--verbose]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 1. 代码检查
|
||||
echo -e "${YELLOW}[1/4] 运行代码检查...${NC}"
|
||||
if ruff check .; then
|
||||
echo -e "${GREEN}✓ 代码检查通过${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ 代码检查失败${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 2. 类型检查(可选)
|
||||
echo -e "${YELLOW}[2/4] 运行类型检查...${NC}"
|
||||
if command -v mypy &> /dev/null; then
|
||||
if mypy app --ignore-missing-imports; then
|
||||
echo -e "${GREEN}✓ 类型检查通过${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ 类型检查有警告(非阻塞)${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠ mypy 未安装,跳过类型检查${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 3. 运行测试
|
||||
echo -e "${YELLOW}[3/4] 运行测试...${NC}"
|
||||
|
||||
# 构建 pytest 命令
|
||||
PYTEST_CMD="pytest"
|
||||
|
||||
# 选择测试类型
|
||||
case $TEST_TYPE in
|
||||
unit)
|
||||
PYTEST_CMD="$PYTEST_CMD tests/unit/"
|
||||
echo "测试类型: 单元测试"
|
||||
;;
|
||||
integration)
|
||||
PYTEST_CMD="$PYTEST_CMD tests/integration/"
|
||||
echo "测试类型: 集成测试"
|
||||
;;
|
||||
e2e)
|
||||
PYTEST_CMD="$PYTEST_CMD tests/e2e/"
|
||||
echo "测试类型: 端到端测试"
|
||||
;;
|
||||
all)
|
||||
echo "测试类型: 全部测试"
|
||||
;;
|
||||
esac
|
||||
|
||||
# 添加覆盖率
|
||||
if [ "$COVERAGE" = true ]; then
|
||||
PYTEST_CMD="$PYTEST_CMD --cov=app --cov-report=term --cov-report=html"
|
||||
echo "覆盖率: 启用"
|
||||
fi
|
||||
|
||||
# 添加详细输出
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
PYTEST_CMD="$PYTEST_CMD -vv"
|
||||
else
|
||||
PYTEST_CMD="$PYTEST_CMD -v"
|
||||
fi
|
||||
|
||||
# 执行测试
|
||||
if eval $PYTEST_CMD; then
|
||||
echo -e "${GREEN}✓ 测试通过${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ 测试失败${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 4. 覆盖率检查
|
||||
if [ "$COVERAGE" = true ]; then
|
||||
echo -e "${YELLOW}[4/4] 检查覆盖率...${NC}"
|
||||
echo "覆盖率报告已生成: htmlcov/index.html"
|
||||
echo "查看报告: open htmlcov/index.html"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# 总结
|
||||
echo "================================"
|
||||
echo -e "${GREEN}✓ 所有检查通过!${NC}"
|
||||
echo "================================"
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""测试 Claude API 连接"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到 Python 路径
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from app.config import settings
|
||||
from app.services.ai_client import AiClient, AiServiceUnavailableError
|
||||
|
||||
|
||||
async def test_claude_api():
|
||||
"""测试 Claude API 连接"""
|
||||
print("=" * 60)
|
||||
print("Claude API 连接测试")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# 检查配置
|
||||
print("1. 检查配置:")
|
||||
print(f" ANTHROPIC_AUTH_TOKEN: {'已设置' if settings.ANTHROPIC_AUTH_TOKEN else '未设置'}")
|
||||
print(f" ANTHROPIC_BASE_URL: {settings.ANTHROPIC_BASE_URL}")
|
||||
print(f" ANTHROPIC_MODEL: {settings.ANTHROPIC_MODEL}")
|
||||
print()
|
||||
|
||||
if not settings.ANTHROPIC_AUTH_TOKEN:
|
||||
print("❌ 错误: ANTHROPIC_AUTH_TOKEN 未设置")
|
||||
print()
|
||||
print("请设置环境变量:")
|
||||
print(" export ANTHROPIC_AUTH_TOKEN='your-api-key'")
|
||||
print(" export ANTHROPIC_BASE_URL='https://api.anthropic.com'")
|
||||
print()
|
||||
return False
|
||||
|
||||
# 创建客户端
|
||||
print("2. 创建 AI 客户端...")
|
||||
client = AiClient()
|
||||
print(f" 客户端���用: {client.is_available}")
|
||||
print()
|
||||
|
||||
# 测试简单调用
|
||||
print("3. 测试 API 调用...")
|
||||
print(f" 请求 URL: {settings.ANTHROPIC_BASE_URL}/v1/messages")
|
||||
print(f" 使用模型: {settings.ANTHROPIC_MODEL}")
|
||||
print()
|
||||
|
||||
try:
|
||||
result = await client.call_vision(
|
||||
system_prompt="你是一个测试助手。",
|
||||
user_content=[
|
||||
{
|
||||
"type": "text",
|
||||
"text": "请回复: Hello from WrongBook!",
|
||||
}
|
||||
],
|
||||
max_tokens=100,
|
||||
temperature=0.1,
|
||||
)
|
||||
|
||||
print(" ✅ API 调用成功!")
|
||||
print(f" 响应文本: {result['text'][:100]}...")
|
||||
print(f" 输入 tokens: {result['input_tokens']}")
|
||||
print(f" 输出 tokens: {result['output_tokens']}")
|
||||
print(f" 延迟: {result['latency_ms']}ms")
|
||||
print()
|
||||
|
||||
await client.close()
|
||||
return True
|
||||
|
||||
except AiServiceUnavailableError as e:
|
||||
print(f" ❌ API 调用失败: {e}")
|
||||
print()
|
||||
print(" 可能的原因:")
|
||||
print(" 1. 模型名称不正确")
|
||||
print(" 2. API key 无效或过期")
|
||||
print(" 3. 代理服务配置问题")
|
||||
print()
|
||||
print(" 常见的 Claude 模型名称:")
|
||||
print(" - claude-sonnet-4-6 (最新)")
|
||||
print(" - claude-3-5-sonnet-20241022")
|
||||
print(" - claude-3-opus-20240229")
|
||||
print(" - claude-3-sonnet-20240229")
|
||||
print(" - claude-3-haiku-20240307")
|
||||
print()
|
||||
print(" 请检查你的 ANTHROPIC_MODEL 环境变量")
|
||||
print()
|
||||
await client.close()
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" ❌ 未知错误: {e}")
|
||||
print()
|
||||
await client.close()
|
||||
return False
|
||||
|
||||
|
||||
async def main():
|
||||
"""主函数"""
|
||||
success = await test_claude_api()
|
||||
|
||||
print("=" * 60)
|
||||
if success:
|
||||
print("✅ 测试通过: Claude API 可以正常使用")
|
||||
else:
|
||||
print("❌ 测试失败: Claude API 无法连接")
|
||||
print("=" * 60)
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,4 +1,46 @@
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: backend/Dockerfile
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
DATABASE_URL: postgresql+asyncpg://wrongbook:wrongbook@postgres:5432/wrongbook
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
MINIO_ENDPOINT: minio:9000
|
||||
MINIO_ACCESS_KEY: minioadmin
|
||||
MINIO_SECRET_KEY: minioadmin
|
||||
MINIO_BUCKET: wrongbook
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||
|
||||
celery:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: backend/Dockerfile
|
||||
environment:
|
||||
DATABASE_URL: postgresql+asyncpg://wrongbook:wrongbook@postgres:5432/wrongbook
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
MINIO_ENDPOINT: minio:9000
|
||||
MINIO_ACCESS_KEY: minioadmin
|
||||
MINIO_SECRET_KEY: minioadmin
|
||||
MINIO_BUCKET: wrongbook
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
command: celery -A app.tasks.celery_app worker --loglevel=info
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
ports:
|
||||
@@ -52,3 +94,4 @@ services:
|
||||
volumes:
|
||||
pgdata:
|
||||
miniodata:
|
||||
|
||||
|
||||
+343
@@ -0,0 +1,343 @@
|
||||
# WrongBook API 文档
|
||||
|
||||
## 基础信息
|
||||
|
||||
- Base URL: `http://localhost:8000/api/v1`
|
||||
- 认证方式: JWT Bearer Token
|
||||
- 响应格式: JSON
|
||||
|
||||
## 统一响应格式
|
||||
|
||||
所有 API 响应遵循统一格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {},
|
||||
"message": "success"
|
||||
}
|
||||
```
|
||||
|
||||
错误响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 错误码,
|
||||
"data": null,
|
||||
"message": "错误描述"
|
||||
}
|
||||
```
|
||||
|
||||
## 错误码
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 0 | 成功 |
|
||||
| 1001 | 参数错误 |
|
||||
| 1002 | 认证失败 |
|
||||
| 1003 | 权限不足 |
|
||||
| 1004 | 资源不存在 |
|
||||
| 1005 | 资源已存在 |
|
||||
| 2001 | 数据库错误 |
|
||||
| 2002 | 外部服务错误 |
|
||||
| 9999 | 未知错误 |
|
||||
|
||||
## 认证
|
||||
|
||||
### 注册
|
||||
|
||||
```http
|
||||
POST /auth/register
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "user123",
|
||||
"password": "password123",
|
||||
"nickname": "昵称"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"access_token": "eyJ...",
|
||||
"refresh_token": "eyJ...",
|
||||
"user": {
|
||||
"id": "uuid",
|
||||
"username": "user123",
|
||||
"nickname": "昵称",
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
},
|
||||
"message": "success"
|
||||
}
|
||||
```
|
||||
|
||||
### 登录
|
||||
|
||||
```http
|
||||
POST /auth/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "user123",
|
||||
"password": "password123"
|
||||
}
|
||||
```
|
||||
|
||||
响应格式同注册。
|
||||
|
||||
### 刷新 Token
|
||||
|
||||
```http
|
||||
POST /auth/refresh
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"refresh_token": "eyJ..."
|
||||
}
|
||||
```
|
||||
|
||||
## 题目管理
|
||||
|
||||
### 创建题目
|
||||
|
||||
```http
|
||||
POST /questions/
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
image: <file>
|
||||
subject: 数学
|
||||
difficulty: 中等
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"subject": "数学",
|
||||
"difficulty": "中等",
|
||||
"original_image_url": "https://...",
|
||||
"status": "processing",
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
},
|
||||
"message": "success"
|
||||
}
|
||||
```
|
||||
|
||||
### 获取题目列表
|
||||
|
||||
```http
|
||||
GET /questions/?page=1&page_size=20&subject=数学&difficulty=中等
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"subject": "数学",
|
||||
"difficulty": "中等",
|
||||
"knowledge_points": "函数,导数",
|
||||
"review_count": 3,
|
||||
"last_reviewed_at": "2024-01-01T00:00:00Z",
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
],
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
},
|
||||
"message": "success"
|
||||
}
|
||||
```
|
||||
|
||||
### 获取题目详情
|
||||
|
||||
```http
|
||||
GET /questions/{id}
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"subject": "数学",
|
||||
"difficulty": "中等",
|
||||
"knowledge_points": "函数,导数",
|
||||
"original_image_url": "https://...",
|
||||
"clean_image_url": "https://...",
|
||||
"recognized_text": "题目文本...",
|
||||
"ai_analysis": "这是一道关于...",
|
||||
"user_notes": "我的笔记...",
|
||||
"review_count": 3,
|
||||
"last_reviewed_at": "2024-01-01T00:00:00Z",
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-01T00:00:00Z"
|
||||
},
|
||||
"message": "success"
|
||||
}
|
||||
```
|
||||
|
||||
### 更新题目
|
||||
|
||||
```http
|
||||
PUT /questions/{id}
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"subject": "物理",
|
||||
"difficulty": "困难",
|
||||
"user_notes": "更新的笔记"
|
||||
}
|
||||
```
|
||||
|
||||
### 删除题目
|
||||
|
||||
```http
|
||||
DELETE /questions/{id}
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
### 搜索题目
|
||||
|
||||
```http
|
||||
GET /questions/search?q=导数&page=1&page_size=20
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
响应格式同获取题目列表。
|
||||
|
||||
### 记录复习
|
||||
|
||||
```http
|
||||
POST /questions/{id}/review
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"review_count": 4,
|
||||
"last_reviewed_at": "2024-01-01T00:00:00Z"
|
||||
},
|
||||
"message": "success"
|
||||
}
|
||||
```
|
||||
|
||||
## WebSocket
|
||||
|
||||
### 连接
|
||||
|
||||
```
|
||||
ws://localhost:8000/ws?token=<jwt_token>
|
||||
```
|
||||
|
||||
### 消息格式
|
||||
|
||||
客户端发送:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ping"
|
||||
}
|
||||
```
|
||||
|
||||
服务端响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "pong",
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
题目处理进度推送:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "question_progress",
|
||||
"data": {
|
||||
"question_id": "uuid",
|
||||
"status": "processing",
|
||||
"progress": 50,
|
||||
"message": "正在识别题目..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
题目处理完成:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "question_completed",
|
||||
"data": {
|
||||
"question_id": "uuid",
|
||||
"status": "completed"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 限流
|
||||
|
||||
- 认证端点: 5 次/分钟
|
||||
- 题目创建: 10 次/分钟
|
||||
- 其他端点: 60 次/分钟
|
||||
|
||||
## 分页
|
||||
|
||||
所有列表接口支持分页参数:
|
||||
|
||||
- `page`: 页码,从 1 开始
|
||||
- `page_size`: 每页数量,默认 20,最大 100
|
||||
|
||||
## 排序
|
||||
|
||||
部分接口支持排序参数:
|
||||
|
||||
- `sort_by`: 排序字段
|
||||
- `order`: `asc` 或 `desc`
|
||||
|
||||
示例:
|
||||
|
||||
```http
|
||||
GET /questions/?sort_by=created_at&order=desc
|
||||
```
|
||||
|
||||
## 过滤
|
||||
|
||||
支持多种过滤参数:
|
||||
|
||||
- `subject`: 科目
|
||||
- `difficulty`: 难度
|
||||
- `knowledge_points`: 知识点(逗号分隔)
|
||||
- `created_after`: 创建时间起始
|
||||
- `created_before`: 创建时间结束
|
||||
|
||||
示例:
|
||||
|
||||
```http
|
||||
GET /questions/?subject=数学&difficulty=中等&created_after=2024-01-01
|
||||
```
|
||||
@@ -0,0 +1,545 @@
|
||||
# 智能错题本项目 - 完整构建指令(10阶段)
|
||||
|
||||
## 执行说明
|
||||
|
||||
这是一份完整的、可自动执行的项目构建指令。严格按照以下 10 个阶段从零构建整个项目。
|
||||
|
||||
## 全局执行规则(贯穿所有阶段)
|
||||
|
||||
1. 每个阶段完成后: 运行验证命令 → git add -A → git commit → 输出 "✅ Stage N/10 完成" → 立即进入下一阶段
|
||||
2. 遇到错误: 自行诊断修复, 修复后继续, 不要停下来问我
|
||||
3. 遇到依赖安装失败: 降级版本或标记为可选, 确保构建不中断
|
||||
4. 所有代码必须符合 CLAUDE.md 中的 IMPORTANT 约束
|
||||
5. 每个 Python 文件写完立即 ruff check --fix
|
||||
6. 每个 Dart 文件写完立即 dart format
|
||||
7. 最终目标: 10个阶段全部完成, docker compose up -d 能启动完整服务
|
||||
|
||||
---
|
||||
|
||||
## STAGE 1/10: 项目脚手架 + 基础配置
|
||||
|
||||
### 目标
|
||||
创建项目基础目录结构、配置文件和核心模块。
|
||||
|
||||
### 目录结构
|
||||
```
|
||||
wrongbook/
|
||||
├── CLAUDE.md (已存在)
|
||||
├── .gitignore (Python + Flutter + Node + Docker + .env)
|
||||
├── .env.example (环境变量模板)
|
||||
├── .env (从 .env.example 复制)
|
||||
├── docker-compose.yml
|
||||
├── backend/
|
||||
│ ├── pyproject.toml
|
||||
│ ├── alembic.ini
|
||||
│ ├── alembic/
|
||||
│ │ ├── env.py
|
||||
│ │ └── versions/ (空目录)
|
||||
│ ├── app/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── main.py
|
||||
│ │ ├── config.py
|
||||
│ │ ├── database.py
|
||||
│ │ ├── redis_client.py
|
||||
│ │ ├── storage.py
|
||||
│ │ ├── middleware.py (统一响应+request_id+全局异常)
|
||||
│ │ ├── dependencies.py (通用依赖注入)
|
||||
│ │ ├── logger.py (structlog 配置)
|
||||
│ │ ├── models/
|
||||
│ │ │ ├── __init__.py
|
||||
│ │ │ └── base.py
|
||||
│ │ ├── schemas/
|
||||
│ │ │ ├── __init__.py
|
||||
│ │ │ └── common.py (ApiResponse 统一响应模型)
|
||||
│ │ ├── api/
|
||||
│ │ │ ├── __init__.py
|
||||
│ │ │ └── router.py
|
||||
│ │ ├── services/
|
||||
│ │ │ └── __init__.py
|
||||
│ │ ├── prompts/
|
||||
│ │ │ └── __init__.py
|
||||
│ │ └── utils/
|
||||
│ │ └── __init__.py
|
||||
│ └── tests/
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py
|
||||
│ └── fixtures/ (测试用图片等)
|
||||
├── app/
|
||||
│ └── .gitkeep
|
||||
├── miniprogram/
|
||||
│ └── .gitkeep
|
||||
├── infra/
|
||||
│ ├── init.sql
|
||||
│ └── .gitkeep
|
||||
└── docs/
|
||||
└── architecture.md
|
||||
```
|
||||
|
||||
### docker-compose.yml 要求
|
||||
|
||||
```yaml
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
ports: ["5432:5432"]
|
||||
environment:
|
||||
POSTGRES_DB: wrongbook
|
||||
POSTGRES_USER: wrongbook
|
||||
POSTGRES_PASSWORD: wrongbook
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
- ./infra/init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U wrongbook"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
postgres-test:
|
||||
image: postgres:16-alpine
|
||||
ports: ["5433:5432"]
|
||||
environment:
|
||||
POSTGRES_DB: wrongbook_test
|
||||
POSTGRES_USER: wrongbook
|
||||
POSTGRES_PASSWORD: wrongbook
|
||||
tmpfs: /var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports: ["6379:6379"]
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
command: server /data --console-address ":9001"
|
||||
ports: ["9000:9000", "9001:9001"]
|
||||
environment:
|
||||
MINIO_ROOT_USER: minioadmin
|
||||
MINIO_ROOT_PASSWORD: minioadmin
|
||||
volumes:
|
||||
- miniodata:/data
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
miniodata:
|
||||
```
|
||||
|
||||
### infra/init.sql
|
||||
|
||||
```sql
|
||||
-- 启用扩展
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- 模糊搜索
|
||||
CREATE EXTENSION IF NOT EXISTS "ltree"; -- 树形路径
|
||||
```
|
||||
|
||||
### pyproject.toml 核心要求
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "wrongbook-backend"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.11,<3.13"
|
||||
dependencies = [
|
||||
"fastapi>=0.115.0",
|
||||
"uvicorn[standard]>=0.30.0",
|
||||
"sqlalchemy[asyncio]>=2.0.30",
|
||||
"asyncpg>=0.29.0",
|
||||
"alembic>=1.13.0",
|
||||
"redis>=5.0.0",
|
||||
"celery[redis]>=5.4.0",
|
||||
"httpx>=0.27.0",
|
||||
"pydantic>=2.7.0",
|
||||
"pydantic-settings>=2.3.0",
|
||||
"python-multipart>=0.0.9",
|
||||
"python-jose[cryptography]>=3.3.0",
|
||||
"Pillow>=10.3.0",
|
||||
"opencv-python-headless>=4.9.0",
|
||||
"numpy>=1.26.0",
|
||||
"structlog>=24.1.0",
|
||||
"minio>=7.2.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
paddle = [
|
||||
"paddlepaddle>=2.6.0",
|
||||
"paddleocr>=2.7.0",
|
||||
]
|
||||
dev = [
|
||||
"pytest>=8.2.0",
|
||||
"pytest-asyncio>=0.23.0",
|
||||
"httpx>=0.27.0",
|
||||
"ruff>=0.4.0",
|
||||
"testcontainers[postgres]>=4.4.0",
|
||||
]
|
||||
```
|
||||
|
||||
### 验证命令
|
||||
|
||||
```bash
|
||||
docker compose up -d postgres postgres-test redis minio
|
||||
cd backend
|
||||
pip install -e ".[dev]"
|
||||
ruff check .
|
||||
python -c "from app.main import app; print('FastAPI app OK')"
|
||||
python -c "from app.config import settings; print(f'DB: {settings.DATABASE_URL}')"
|
||||
git init && git add -A && git commit -m "Stage 1: project scaffold with unified middleware and test DB"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## STAGE 2/10: 数据库模型 + 迁移
|
||||
|
||||
### 目标
|
||||
创建所有 ORM 模型和数据库迁移。
|
||||
|
||||
### 模型清单
|
||||
|
||||
1. **base.py** — Base 类 + 通用 Mixin
|
||||
2. **user.py** — User
|
||||
3. **subject.py** — Subject
|
||||
4. **knowledge_tag.py** — KnowledgeTag (树形结构)
|
||||
5. **exam_paper.py** — ExamPaper
|
||||
6. **wrong_question.py** — WrongQuestion (核心表)
|
||||
7. **review_log.py** — ReviewLog
|
||||
8. **question_book.py** — QuestionBook + BookQuestion
|
||||
9. **ai_invocation_log.py** — AiInvocationLog (AI 可追溯)
|
||||
|
||||
### 关键索引
|
||||
|
||||
WrongQuestion 表索引:
|
||||
- Index('ix_wq_user_subject', user_id, subject_id)
|
||||
- Index('ix_wq_knowledge', knowledge_tags, postgresql_using='gin')
|
||||
- Index('ix_wq_custom_tags', custom_tags, postgresql_using='gin')
|
||||
- Index('ix_wq_review', user_id, next_review_at)
|
||||
- Index('ix_wq_created', user_id, text('created_at DESC'))
|
||||
|
||||
### 验证命令
|
||||
|
||||
```bash
|
||||
cd backend && alembic revision --autogenerate -m "initial_models"
|
||||
cd backend && alembic upgrade head
|
||||
cd backend && python -c "from app.models import *; print('All models OK')"
|
||||
cd backend && ruff check .
|
||||
git add -A && git commit -m "Stage 2: database models with AI traceability"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## STAGE 3/10: 用户认证 + 统一响应 + 基础API
|
||||
|
||||
### 目标
|
||||
实现 JWT 认证、统一响应格式、基础 API 端点。
|
||||
|
||||
### 核心文件
|
||||
|
||||
1. **schemas/common.py** — ApiResponse 统一响应模型
|
||||
2. **auth.py** — JWT 认证
|
||||
3. **api/auth.py** — 登录/刷新端点
|
||||
4. **api/users.py** — 用户信息端点
|
||||
5. **api/subjects.py** — 学科列表端点
|
||||
6. **seed.py** — 种子数据
|
||||
|
||||
### API 端点
|
||||
|
||||
- POST /api/v1/auth/login
|
||||
- POST /api/v1/auth/refresh
|
||||
- GET /api/v1/users/me
|
||||
- PUT /api/v1/users/me
|
||||
- GET /api/v1/subjects
|
||||
|
||||
### 验证命令
|
||||
|
||||
```bash
|
||||
cd backend && ruff check .
|
||||
cd backend && pytest tests/test_auth.py -v
|
||||
git add -A && git commit -m "Stage 3: auth, unified response, base API"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## STAGE 4/10: Layer 1 题块切分 + Layer 2 错题召回
|
||||
|
||||
### 目标
|
||||
实现图片预处理、题块切分(规则+PaddleOCR可选)、错题召回评分。
|
||||
|
||||
### 核心服务
|
||||
|
||||
1. **image_preprocessor.py** — 图片预处理(纯 OpenCV)
|
||||
2. **block_cutter.py** — 题块切分器(规则降级)
|
||||
3. **error_recaller.py** — 错题召回评分
|
||||
|
||||
### 关键特性
|
||||
|
||||
- PaddleOCR 为可选依赖
|
||||
- 必须有纯规则降级路径
|
||||
- 红色标记检测(HSV)
|
||||
- 评分信号:✗ 符号、红色面积、负分标注等
|
||||
|
||||
### 验证命令
|
||||
|
||||
```bash
|
||||
cd backend && ruff check .
|
||||
cd backend && pytest tests/test_block_cutter.py tests/test_error_recaller.py -v
|
||||
git add -A && git commit -m "Stage 4: block cutter (with paddle optional) and error recall"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## STAGE 5/10: Layer 3 Sonnet 识别 + Layer 4 解析 + AI 可追溯
|
||||
|
||||
### 目标
|
||||
实现 Claude Sonnet API 调用、题目识别、解析、AI 调用追溯。
|
||||
|
||||
### 核心服务
|
||||
|
||||
1. **ai_client.py** — AI 调用抽象层(可 mock)
|
||||
2. **prompts/recognition_prompt.py** — 识别 prompt
|
||||
3. **prompts/analysis_prompt.py** — 解析 prompt
|
||||
4. **prompts/verification_prompt.py** — 校对 prompt
|
||||
5. **sonnet_recognizer.py** — Sonnet 识别器
|
||||
6. **sonnet_analyzer.py** — Sonnet 解析器
|
||||
7. **handwriting_cleaner.py** — 手写清除
|
||||
8. **ai_trace.py** — AI 调用追溯
|
||||
|
||||
### 关键特性
|
||||
|
||||
- 所有 AI 调用可 mock
|
||||
- 双图输入(原图+增强图)
|
||||
- 两段式调用(校对+解析)
|
||||
- AI 调用记录到 ai_invocation_logs 表
|
||||
|
||||
### 验证命令
|
||||
|
||||
```bash
|
||||
cd backend && ruff check .
|
||||
cd backend && pytest tests/ -v
|
||||
git add -A && git commit -m "Stage 5: Sonnet recognition/analysis with AI traceability"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## STAGE 6/10: 错题管理 API (CRUD + 搜索 + 复习)
|
||||
|
||||
### 目标
|
||||
实现错题的完整 CRUD、高级搜索、复习功能。
|
||||
|
||||
### API 端点
|
||||
|
||||
**papers.py:**
|
||||
- POST /api/v1/papers/upload
|
||||
- GET /api/v1/papers/{id}
|
||||
- GET /api/v1/papers/{id}/recall
|
||||
- POST /api/v1/papers/{id}/confirm
|
||||
|
||||
**questions.py:**
|
||||
- POST /api/v1/questions/search
|
||||
- GET /api/v1/questions/{id}
|
||||
- PUT /api/v1/questions/{id}
|
||||
- PUT /api/v1/questions/{id}/star
|
||||
- PUT /api/v1/questions/{id}/archive
|
||||
- DELETE /api/v1/questions/{id}
|
||||
- GET /api/v1/questions/stats
|
||||
|
||||
**books.py:**
|
||||
- CRUD + 添加/移除错题
|
||||
|
||||
**review.py:**
|
||||
- GET /api/v1/review/today
|
||||
- POST /api/v1/review/{question_id}
|
||||
|
||||
### 关键特性
|
||||
|
||||
- PostgreSQL 全文检索(tsvector)
|
||||
- ARRAY overlap 查询
|
||||
- SM-2 改进版复习算法
|
||||
- presigned URL 返回
|
||||
|
||||
### 验证命令
|
||||
|
||||
```bash
|
||||
cd backend && ruff check .
|
||||
cd backend && pytest tests/ -v
|
||||
git add -A && git commit -m "Stage 6: question CRUD, search, review API"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## STAGE 7/10: Celery 异步任务 + WebSocket 通知
|
||||
|
||||
### 目标
|
||||
实现 Celery 异步任务处理、WebSocket 实时通知(JWT 鉴权)。
|
||||
|
||||
### 核心文件
|
||||
|
||||
1. **celery_app.py** — Celery 配置
|
||||
2. **tasks.py** — 异步任务
|
||||
- process_paper_stage1(切题+召回)
|
||||
- process_paper_stage2(识别+解析+入库)
|
||||
3. **websocket.py** — WebSocket 端点(JWT 鉴权)
|
||||
|
||||
### 关键特性
|
||||
|
||||
- Celery task 内正确调用 async 代码(asyncio.run())
|
||||
- WebSocket 使用 JWT query param 鉴权
|
||||
- Redis pubsub 通知机制
|
||||
|
||||
### 验证命令
|
||||
|
||||
```bash
|
||||
cd backend && ruff check .
|
||||
cd backend && pytest tests/ -v
|
||||
git add -A && git commit -m "Stage 7: Celery async-safe tasks + JWT WebSocket"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## STAGE 8/10: Flutter App
|
||||
|
||||
### 目标
|
||||
创建 Flutter 客户端骨架和核心页面。
|
||||
|
||||
### 目录结构
|
||||
|
||||
```
|
||||
app/lib/
|
||||
├── main.dart
|
||||
├── app.dart
|
||||
├── core/
|
||||
│ ├── config/
|
||||
│ ├── network/
|
||||
│ ├── platform/
|
||||
│ ├── storage/
|
||||
│ └── theme/
|
||||
├── features/
|
||||
│ ├── auth/
|
||||
│ ├── scan/
|
||||
│ ├── questions/
|
||||
│ ├── review/
|
||||
│ ├── books/
|
||||
│ ├── stats/
|
||||
│ └── home/
|
||||
└── widgets/
|
||||
```
|
||||
|
||||
### 关键特性
|
||||
|
||||
- 只声明 android + ios 平台
|
||||
- HarmonyOS 适配作为占位
|
||||
- PlatformService 抽象层
|
||||
- LaTeX 渲染(flutter_math_fork)
|
||||
- WebSocket 实时通知
|
||||
|
||||
### 验证命令
|
||||
|
||||
```bash
|
||||
cd app && flutter pub get
|
||||
cd app && flutter analyze
|
||||
cd app && flutter test
|
||||
git add -A && git commit -m "Stage 8: Flutter app skeleton with core pages"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## STAGE 9/10: uni-app 微信小程序
|
||||
|
||||
### 目标
|
||||
创建 uni-app 微信小程序轻量版。
|
||||
|
||||
### 目录结构
|
||||
|
||||
```
|
||||
miniprogram/src/
|
||||
├── pages/
|
||||
│ ├── index/
|
||||
│ ├── login/
|
||||
│ ├── questions/
|
||||
│ └── scan/
|
||||
├── utils/
|
||||
├── store/
|
||||
├── App.vue
|
||||
├── main.ts
|
||||
├── pages.json
|
||||
└── manifest.json
|
||||
```
|
||||
|
||||
### 关键特性
|
||||
|
||||
- Vue 3 + TypeScript
|
||||
- 微信登录
|
||||
- 图片上传
|
||||
- 简化版错题列表和详情
|
||||
|
||||
### 后端新增接口
|
||||
|
||||
- POST /api/v1/auth/wx-login
|
||||
- GET /api/v1/questions/{id}/analysis-image
|
||||
|
||||
### 验证命令
|
||||
|
||||
```bash
|
||||
cd miniprogram && npm install
|
||||
cd miniprogram && npx tsc --noEmit || echo "TS check done"
|
||||
cd backend && ruff check .
|
||||
cd backend && pytest tests/ -v
|
||||
git add -A && git commit -m "Stage 9: uni-app mini program lite version"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## STAGE 10/10: Docker 部署 + 集成测试 + 文档
|
||||
|
||||
### 目标
|
||||
完成 Docker 部署配置、集成测试、完整文档。
|
||||
|
||||
### 核心文件
|
||||
|
||||
1. **backend/Dockerfile**
|
||||
2. **backend/Dockerfile.worker**
|
||||
3. **docker-compose.yml**(更新)
|
||||
4. **tests/test_integration.py**
|
||||
5. **docs/api-reference.md**
|
||||
6. **README.md**
|
||||
7. **docs/architecture.md**
|
||||
|
||||
### 验证命令
|
||||
|
||||
```bash
|
||||
cd backend && ruff check .
|
||||
cd backend && pytest tests/ -v
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
curl -s http://localhost:8000/docs | head -5
|
||||
docker compose down
|
||||
git add -A && git commit -m "Stage 10: Docker deployment, integration tests, docs"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完成标志
|
||||
|
||||
全部 10 个阶段完成后,输出:
|
||||
|
||||
**🎉 智能错题本项目 v2 全部 10 阶段构建完成!**
|
||||
|
||||
并输出:
|
||||
1. 最终的完整目录树
|
||||
2. 全部 API endpoint 汇总
|
||||
3. 已知的 TODO/限制列表
|
||||
|
||||
---
|
||||
|
||||
## 风险验证矩阵
|
||||
|
||||
| 原风险 | 修订措施 | 验证方法 |
|
||||
|--------|----------|----------|
|
||||
| Python+PaddleOCR | Python 3.11 + paddle 可选extra | pip install -e "." 不含paddle也能成功 |
|
||||
| 测试DB | docker-compose postgres-test:5433 | pytest 连接5433端口运行真实PG测试 |
|
||||
| Celery+async | sync_wrapper + asyncio.new_event_loop | test_tasks.py 验证 task 能正确调用async |
|
||||
| WS鉴权 | /ws?token=xxx + JWT校验 | test_websocket.py: 无token→拒绝 |
|
||||
| ES形同虚设 | 删除ES, 用PG tsvector | keyword搜索测试用tsvector验证 |
|
||||
| Flutter鸿蒙 | 鸿蒙占位不阻断 | flutter analyze 在 android+ios 通过 |
|
||||
| AI可追溯 | ai_invocation_logs 表 | 每次AI调用写入log |
|
||||
| 全局异常 | middleware + exception handlers | 所有错误返回统一格式 |
|
||||
| 存储安全 | object_key + presigned URL | API返回presigned URL而非裸地址 |
|
||||
@@ -0,0 +1,447 @@
|
||||
# 部署指南
|
||||
|
||||
## 开发环境部署
|
||||
|
||||
### 前置要求
|
||||
|
||||
- Docker 20.10+
|
||||
- Docker Compose 2.0+
|
||||
- Python 3.11+
|
||||
- PostgreSQL 16 (可选,可使用 Docker)
|
||||
- Redis 7 (可选,可使用 Docker)
|
||||
|
||||
### 步骤
|
||||
|
||||
1. 克隆仓库
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd ljwx-shiti
|
||||
```
|
||||
|
||||
2. 配置环境变量
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
编辑 `.env` 文件:
|
||||
|
||||
```env
|
||||
# 数据库
|
||||
DATABASE_URL=postgresql+asyncpg://wrongbook:wrongbook@localhost:45432/wrongbook
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://localhost:46379/0
|
||||
|
||||
# MinIO
|
||||
MINIO_ENDPOINT=localhost:49000
|
||||
MINIO_ACCESS_KEY=minioadmin
|
||||
MINIO_SECRET_KEY=minioadmin
|
||||
MINIO_BUCKET=wrongbook
|
||||
|
||||
# JWT
|
||||
SECRET_KEY=your-secret-key-here
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||
REFRESH_TOKEN_EXPIRE_DAYS=7
|
||||
|
||||
# Claude API (可选)
|
||||
ANTHROPIC_API_KEY=your-api-key-here
|
||||
|
||||
# RemoveHandwriting API (可选)
|
||||
REMOVE_HANDWRITING_API_KEY=your-api-key-here
|
||||
```
|
||||
|
||||
3. 启动基础服务
|
||||
|
||||
```bash
|
||||
docker-compose up -d postgres redis minio
|
||||
```
|
||||
|
||||
4. 运行数据库迁移
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
pip install -r requirements.txt
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
5. 启动后端服务
|
||||
|
||||
```bash
|
||||
# 开发模式
|
||||
uvicorn app.main:app --reload
|
||||
|
||||
# 或使用 Docker
|
||||
docker-compose up -d backend celery
|
||||
```
|
||||
|
||||
6. 访问服务
|
||||
|
||||
- API: http://localhost:8000
|
||||
- API 文档: http://localhost:8000/docs
|
||||
- MinIO 控制台: http://localhost:49001
|
||||
|
||||
## 生产环境部署
|
||||
|
||||
### 使用 Docker Compose
|
||||
|
||||
1. 准备生产环境配置
|
||||
|
||||
```bash
|
||||
cp .env.example .env.production
|
||||
```
|
||||
|
||||
编辑 `.env.production`,设置生产环境配置:
|
||||
|
||||
```env
|
||||
ENV=production
|
||||
SECRET_KEY=<strong-random-key>
|
||||
DATABASE_URL=postgresql+asyncpg://wrongbook:<password>@postgres:5432/wrongbook
|
||||
# ... 其他配置
|
||||
```
|
||||
|
||||
2. 创建生产环境 docker-compose 文件
|
||||
|
||||
```yaml
|
||||
# docker-compose.prod.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: backend/Dockerfile
|
||||
ports:
|
||||
- "8000:8000"
|
||||
env_file:
|
||||
- .env.production
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
restart: always
|
||||
|
||||
celery:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: backend/Dockerfile
|
||||
env_file:
|
||||
- .env.production
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
command: celery -A app.tasks.celery_app worker --loglevel=info
|
||||
restart: always
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env_file:
|
||||
- .env.production
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
restart: always
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: always
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
command: server /data --console-address ":9001"
|
||||
env_file:
|
||||
- .env.production
|
||||
volumes:
|
||||
- miniodata:/data
|
||||
restart: always
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./infra/nginx.conf:/etc/nginx/nginx.conf
|
||||
- ./infra/ssl:/etc/nginx/ssl
|
||||
depends_on:
|
||||
- backend
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
miniodata:
|
||||
```
|
||||
|
||||
3. 启动服务
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
### Nginx 配置
|
||||
|
||||
创建 `infra/nginx.conf`:
|
||||
|
||||
```nginx
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
upstream backend {
|
||||
server backend:8000;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
|
||||
# 重定向到 HTTPS
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name your-domain.com;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/cert.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
||||
|
||||
client_max_body_size 10M;
|
||||
|
||||
location / {
|
||||
proxy_pass http://backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 数据库备份
|
||||
|
||||
1. 手动备份
|
||||
|
||||
```bash
|
||||
docker exec -t postgres pg_dump -U wrongbook wrongbook > backup.sql
|
||||
```
|
||||
|
||||
2. 自动备份脚本
|
||||
|
||||
创建 `infra/backup.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
BACKUP_DIR="/backups"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="$BACKUP_DIR/wrongbook_$DATE.sql"
|
||||
|
||||
docker exec -t postgres pg_dump -U wrongbook wrongbook > $BACKUP_FILE
|
||||
|
||||
# 压缩备份
|
||||
gzip $BACKUP_FILE
|
||||
|
||||
# 删除 7 天前的备份
|
||||
find $BACKUP_DIR -name "wrongbook_*.sql.gz" -mtime +7 -delete
|
||||
|
||||
echo "Backup completed: $BACKUP_FILE.gz"
|
||||
```
|
||||
|
||||
3. 设置定时任务
|
||||
|
||||
```bash
|
||||
# 每天凌晨 2 点备份
|
||||
0 2 * * * /path/to/infra/backup.sh
|
||||
```
|
||||
|
||||
### 监控
|
||||
|
||||
#### 健康检查
|
||||
|
||||
```bash
|
||||
# 检查后端服务
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# 检查数据库
|
||||
docker exec postgres pg_isready -U wrongbook
|
||||
|
||||
# 检查 Redis
|
||||
docker exec redis redis-cli ping
|
||||
```
|
||||
|
||||
#### 日志查看
|
||||
|
||||
```bash
|
||||
# 查看后端日志
|
||||
docker-compose logs -f backend
|
||||
|
||||
# 查看 Celery 日志
|
||||
docker-compose logs -f celery
|
||||
|
||||
# 查看所有服务日志
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
### 性能优化
|
||||
|
||||
1. **数据库连接池**
|
||||
|
||||
在 `.env` 中配置:
|
||||
|
||||
```env
|
||||
DB_POOL_SIZE=20
|
||||
DB_MAX_OVERFLOW=10
|
||||
```
|
||||
|
||||
2. **Redis 缓存**
|
||||
|
||||
启用查询缓存:
|
||||
|
||||
```env
|
||||
ENABLE_CACHE=true
|
||||
CACHE_TTL=300
|
||||
```
|
||||
|
||||
3. **Celery 并发**
|
||||
|
||||
```bash
|
||||
celery -A app.tasks.celery_app worker --concurrency=4
|
||||
```
|
||||
|
||||
### 安全建议
|
||||
|
||||
1. 使用强密码和密钥
|
||||
2. 启用 HTTPS
|
||||
3. 配置防火墙规则
|
||||
4. 定期更新依赖
|
||||
5. 启用日志审计
|
||||
6. 限制 API 访问频率
|
||||
7. 定期备份数据
|
||||
|
||||
### 故障排查
|
||||
|
||||
#### 服务无法启动
|
||||
|
||||
```bash
|
||||
# 查看日志
|
||||
docker-compose logs backend
|
||||
|
||||
# 检查端口占用
|
||||
netstat -tulpn | grep 8000
|
||||
|
||||
# 重启服务
|
||||
docker-compose restart backend
|
||||
```
|
||||
|
||||
#### 数据库连接失败
|
||||
|
||||
```bash
|
||||
# 检查数据库状态
|
||||
docker-compose ps postgres
|
||||
|
||||
# 测试连接
|
||||
docker exec -it postgres psql -U wrongbook -d wrongbook
|
||||
```
|
||||
|
||||
#### Celery 任务不执行
|
||||
|
||||
```bash
|
||||
# 检查 Celery 状态
|
||||
docker-compose logs celery
|
||||
|
||||
# 检查 Redis 连接
|
||||
docker exec redis redis-cli ping
|
||||
|
||||
# 重启 Celery
|
||||
docker-compose restart celery
|
||||
```
|
||||
|
||||
### 扩展部署
|
||||
|
||||
#### 水平扩展
|
||||
|
||||
```yaml
|
||||
# docker-compose.scale.yml
|
||||
services:
|
||||
backend:
|
||||
deploy:
|
||||
replicas: 3
|
||||
|
||||
celery:
|
||||
deploy:
|
||||
replicas: 2
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.prod.yml -f docker-compose.scale.yml up -d
|
||||
```
|
||||
|
||||
#### 负载均衡
|
||||
|
||||
使用 Nginx 配置多个后端实例:
|
||||
|
||||
```nginx
|
||||
upstream backend {
|
||||
server backend1:8000;
|
||||
server backend2:8000;
|
||||
server backend3:8000;
|
||||
}
|
||||
```
|
||||
|
||||
## 更新部署
|
||||
|
||||
1. 拉取最新代码
|
||||
|
||||
```bash
|
||||
git pull origin main
|
||||
```
|
||||
|
||||
2. 重新构建镜像
|
||||
|
||||
```bash
|
||||
docker-compose build
|
||||
```
|
||||
|
||||
3. 运行数据库迁移
|
||||
|
||||
```bash
|
||||
docker-compose run backend alembic upgrade head
|
||||
```
|
||||
|
||||
4. 重启服务
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 回滚
|
||||
|
||||
1. 恢复代码
|
||||
|
||||
```bash
|
||||
git checkout <previous-commit>
|
||||
```
|
||||
|
||||
2. 回滚数据库
|
||||
|
||||
```bash
|
||||
docker-compose run backend alembic downgrade -1
|
||||
```
|
||||
|
||||
3. 重启服务
|
||||
|
||||
```bash
|
||||
docker-compose restart
|
||||
```
|
||||
@@ -0,0 +1,291 @@
|
||||
# 智能错题本 - 快速参考
|
||||
|
||||
## 🚀 快速启动
|
||||
|
||||
```bash
|
||||
# 1. 启动基础服务
|
||||
docker compose up -d
|
||||
|
||||
# 2. 安装后端依赖
|
||||
cd backend && pip install -e ".[dev]"
|
||||
|
||||
# 3. 运行数据库迁移
|
||||
cd backend && alembic upgrade head
|
||||
|
||||
# 4. 启动后端服务
|
||||
cd backend && uvicorn app.main:app --reload
|
||||
|
||||
# 5. 启动 Celery Worker(另一个终端)
|
||||
cd backend && celery -A app.celery_app:celery_app worker --loglevel=info
|
||||
```
|
||||
|
||||
## 📋 常用命令
|
||||
|
||||
### 后端
|
||||
|
||||
```bash
|
||||
# 代码检查
|
||||
cd backend && ruff check .
|
||||
|
||||
# 运行测试
|
||||
cd backend && pytest -v
|
||||
|
||||
# 创建迁移
|
||||
cd backend && alembic revision --autogenerate -m "description"
|
||||
|
||||
# 应用迁移
|
||||
cd backend && alembic upgrade head
|
||||
|
||||
# 回滚迁移
|
||||
cd backend && alembic downgrade -1
|
||||
```
|
||||
|
||||
### Flutter
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
cd app && flutter pub get
|
||||
|
||||
# 代码生成
|
||||
cd app && flutter pub run build_runner build --delete-conflicting-outputs
|
||||
|
||||
# 代码分析
|
||||
cd app && flutter analyze
|
||||
|
||||
# 运行测试
|
||||
cd app && flutter test
|
||||
|
||||
# 运行应用
|
||||
cd app && flutter run
|
||||
|
||||
# 构建 APK
|
||||
cd app && flutter build apk --release
|
||||
```
|
||||
|
||||
### 小程序
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
cd miniprogram && npm install
|
||||
|
||||
# 开发模式
|
||||
cd miniprogram && npm run dev:mp-weixin
|
||||
|
||||
# 构建生产版本
|
||||
cd miniprogram && npm run build:mp-weixin
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# 启动所有服务
|
||||
docker compose up -d
|
||||
|
||||
# 查看日志
|
||||
docker compose logs -f
|
||||
|
||||
# 停止所有服务
|
||||
docker compose down
|
||||
|
||||
# 重启服务
|
||||
docker compose restart <service_name>
|
||||
|
||||
# 构建镜像
|
||||
docker compose build
|
||||
|
||||
# 清理数据卷
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
## 🔧 环境变量
|
||||
|
||||
关键环境变量(.env 文件):
|
||||
|
||||
```bash
|
||||
# 数据库
|
||||
DATABASE_URL=postgresql+asyncpg://wrongbook:wrongbook@localhost:5432/wrongbook
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
|
||||
# MinIO
|
||||
MINIO_ENDPOINT=localhost:9000
|
||||
MINIO_ACCESS_KEY=minioadmin
|
||||
MINIO_SECRET_KEY=minioadmin
|
||||
|
||||
# AI(可选)
|
||||
ANTHROPIC_API_KEY=
|
||||
ANTHROPIC_MODEL=claude-sonnet-4-20250514
|
||||
|
||||
# JWT
|
||||
JWT_SECRET=your-secret-key-change-in-production
|
||||
JWT_EXPIRE_MINUTES=10080
|
||||
|
||||
# PaddleOCR(可选)
|
||||
ENABLE_PADDLE_OCR=false
|
||||
```
|
||||
|
||||
## 📊 服务端口
|
||||
|
||||
| 服务 | 端口 | 说明 |
|
||||
|------|------|------|
|
||||
| FastAPI | 8000 | 后端 API |
|
||||
| PostgreSQL | 5432 | 主数据库 |
|
||||
| PostgreSQL Test | 5433 | 测试数据库 |
|
||||
| Redis | 6379 | 缓存和队列 |
|
||||
| MinIO | 9000 | 对象存储 API |
|
||||
| MinIO Console | 9001 | MinIO 管理控制台 |
|
||||
|
||||
## 🔗 访问地址
|
||||
|
||||
- API 文档: http://localhost:8000/docs
|
||||
- MinIO 控制台: http://localhost:9001
|
||||
- 用户名: minioadmin
|
||||
- 密码: minioadmin
|
||||
|
||||
## 📝 API 端点速查
|
||||
|
||||
### 认证
|
||||
- `POST /api/v1/auth/login` - 登录
|
||||
- `POST /api/v1/auth/refresh` - 刷新 token
|
||||
|
||||
### 用户
|
||||
- `GET /api/v1/users/me` - 获取当前用户信息
|
||||
- `PUT /api/v1/users/me` - 更新用户信息
|
||||
|
||||
### 试卷
|
||||
- `POST /api/v1/papers/upload` - 上传试卷
|
||||
- `GET /api/v1/papers/{id}` - 获取试卷状态
|
||||
- `GET /api/v1/papers/{id}/recall` - 获取召回结果
|
||||
- `POST /api/v1/papers/{id}/confirm` - 确认错题
|
||||
|
||||
### 错题
|
||||
- `POST /api/v1/questions/search` - 搜索错题
|
||||
- `GET /api/v1/questions/{id}` - 获取错题详情
|
||||
- `PUT /api/v1/questions/{id}` - 更新错题
|
||||
- `DELETE /api/v1/questions/{id}` - 删除错题
|
||||
- `GET /api/v1/questions/stats` - 获取统计信息
|
||||
|
||||
### 复习
|
||||
- `GET /api/v1/review/today` - 今日待复习
|
||||
- `POST /api/v1/review/{question_id}` - 提交复习结果
|
||||
|
||||
### WebSocket
|
||||
- `ws://localhost:8000/ws?token=<jwt_token>` - 实时通知
|
||||
|
||||
## 🧪 测试
|
||||
|
||||
```bash
|
||||
# 运行所有测试
|
||||
cd backend && pytest -v
|
||||
|
||||
# 运行特定测试文件
|
||||
cd backend && pytest tests/test_auth.py -v
|
||||
|
||||
# 运行特定测试函数
|
||||
cd backend && pytest tests/test_auth.py::test_login_success -v
|
||||
|
||||
# 查看测试覆盖率
|
||||
cd backend && pytest --cov=app --cov-report=html
|
||||
|
||||
# 运行集成测试
|
||||
cd backend && pytest tests/test_integration.py -v
|
||||
```
|
||||
|
||||
## 🐛 故障排查
|
||||
|
||||
### 数据库连接失败
|
||||
|
||||
```bash
|
||||
# 检查数据库状态
|
||||
docker compose ps postgres
|
||||
|
||||
# 查看数据库日志
|
||||
docker compose logs postgres
|
||||
|
||||
# 测试连接
|
||||
psql -h localhost -p 5432 -U wrongbook -d wrongbook
|
||||
```
|
||||
|
||||
### Celery 任务不执行
|
||||
|
||||
```bash
|
||||
# 检查 Celery 状态
|
||||
docker compose logs celery
|
||||
|
||||
# 检查 Redis 连接
|
||||
docker exec -it <redis_container> redis-cli ping
|
||||
|
||||
# 重启 Celery
|
||||
docker compose restart celery
|
||||
```
|
||||
|
||||
### MinIO 连接失败
|
||||
|
||||
```bash
|
||||
# 检查 MinIO 状态
|
||||
docker compose ps minio
|
||||
|
||||
# 访问 MinIO 控制台
|
||||
open http://localhost:9001
|
||||
|
||||
# 检查 bucket 是否存在
|
||||
mc ls minio/wrongbook
|
||||
```
|
||||
|
||||
## 📦 依赖版本
|
||||
|
||||
### 后端
|
||||
- Python: 3.11
|
||||
- FastAPI: >=0.115.0
|
||||
- SQLAlchemy: >=2.0.30
|
||||
- PostgreSQL: 16
|
||||
- Redis: 7
|
||||
|
||||
### 客户端
|
||||
- Flutter: 3.22+
|
||||
- Dart: 3.4+
|
||||
|
||||
### 小程序
|
||||
- Node.js: 18+
|
||||
- uni-app: 3.0+
|
||||
- Vue: 3.4+
|
||||
|
||||
## 🔐 安全注意事项
|
||||
|
||||
1. **生产环境必须修改**:
|
||||
- JWT_SECRET
|
||||
- 数据库密码
|
||||
- MinIO 密钥
|
||||
|
||||
2. **HTTPS**:
|
||||
- 生产环境必须启用 HTTPS
|
||||
- 配置 SSL 证书
|
||||
|
||||
3. **API Key**:
|
||||
- 不要将 ANTHROPIC_API_KEY 提交到代码仓库
|
||||
- 使用环境变量或密钥管理服务
|
||||
|
||||
4. **CORS**:
|
||||
- 配置允许的域名
|
||||
- 不要使用 `*` 通配符
|
||||
|
||||
## 📚 更多文档
|
||||
|
||||
- [完整构建指令](./docs/BUILD_INSTRUCTIONS.md)
|
||||
- [系统架构](./docs/architecture.md)
|
||||
- [风险验证清单](./docs/RISK_VERIFICATION.md)
|
||||
- [API 文档](./docs/API.md)
|
||||
- [部署指南](./docs/DEPLOYMENT.md)
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
1. Fork 项目
|
||||
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
|
||||
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
||||
5. 开启 Pull Request
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
MIT License
|
||||
@@ -0,0 +1,355 @@
|
||||
# 项目风险验证清单
|
||||
|
||||
本文档列出了项目中已识别的风险点及其修订措施和验证方法。
|
||||
|
||||
## 🔴 Critical 风险(已修复)
|
||||
|
||||
### 1. Python + PaddleOCR 依赖冲突
|
||||
|
||||
**原风险**: Python 3.12+ 与 PaddleOCR 存在兼容性问题
|
||||
|
||||
**修订措施**:
|
||||
- 使用 Python 3.11
|
||||
- PaddleOCR 作为可选依赖(`[paddle]` extra)
|
||||
- 提供纯规则降级路径
|
||||
|
||||
**验证方法**:
|
||||
```bash
|
||||
# 不安装 paddle 也能成功
|
||||
pip install -e "."
|
||||
|
||||
# ENABLE_PADDLE_OCR=false 时降级到规则切分
|
||||
ENABLE_PADDLE_OCR=false pytest tests/test_block_cutter.py -v
|
||||
```
|
||||
|
||||
**验证标准**: ✅ 不安装 PaddleOCR 时,系统仍能正常运行
|
||||
|
||||
---
|
||||
|
||||
### 2. 测试数据库配置不明确
|
||||
|
||||
**原风险**: conftest.py 配置含糊,测试可能污染开发数据库
|
||||
|
||||
**修订措施**:
|
||||
- docker-compose 中独立的 postgres-test(端口 5433)
|
||||
- 使用 tmpfs,测试后自动清理
|
||||
- 每个测试用事务包裹并 rollback
|
||||
|
||||
**验证方法**:
|
||||
```bash
|
||||
# 启动测试数据库
|
||||
docker compose up -d postgres-test
|
||||
|
||||
# 运行测试
|
||||
cd backend && pytest -v
|
||||
|
||||
# 验证测试数据库独立
|
||||
psql -h localhost -p 5433 -U wrongbook -d wrongbook_test -c "SELECT count(*) FROM wrong_questions;"
|
||||
```
|
||||
|
||||
**验证标准**: ✅ 测试运行在独立数据库,不影响开发数据
|
||||
|
||||
---
|
||||
|
||||
### 3. Celery + async 代码混用
|
||||
|
||||
**原风险**: Celery task 内直接调用 async 代码会导致 event loop 错误
|
||||
|
||||
**修订措施**:
|
||||
- 使用 `asyncio.run()` 包装 async 代码
|
||||
- 在 Celery prefork 模式下创建新的 event loop
|
||||
|
||||
**验证方法**:
|
||||
```bash
|
||||
# 运行 Celery 任务测试
|
||||
cd backend && pytest tests/test_tasks.py -v
|
||||
|
||||
# 验证不会出现 event loop 重入错误
|
||||
```
|
||||
|
||||
**验证标准**: ✅ Celery task 能正确调用 async 代码,无 event loop 错误
|
||||
|
||||
---
|
||||
|
||||
### 4. WebSocket 鉴权不安全
|
||||
|
||||
**原风险**: `/ws/{user_id}` 路径暴露用户 ID,无鉴权
|
||||
|
||||
**修订措施**:
|
||||
- 改为 `/ws?token=<jwt_token>`
|
||||
- JWT query parameter 鉴权
|
||||
- 鉴权失败返回 close(4001)
|
||||
|
||||
**验证方法**:
|
||||
```bash
|
||||
# 运行 WebSocket 测试
|
||||
cd backend && pytest tests/test_websocket.py -v
|
||||
|
||||
# 测试场景:
|
||||
# 1. 无 token → 连接被拒
|
||||
# 2. 错误 token → 连接被拒(code=4001)
|
||||
# 3. 正确 token → 连接成功
|
||||
```
|
||||
|
||||
**验证标准**: ✅ 无有效 JWT 无法建立 WebSocket 连接
|
||||
|
||||
---
|
||||
|
||||
### 5. Elasticsearch 形同虚设
|
||||
|
||||
**原风险**: docker-compose 中有 ES 但未使用,增加复杂度
|
||||
|
||||
**修订措施**:
|
||||
- 删除 Elasticsearch
|
||||
- 使用 PostgreSQL tsvector 全文检索
|
||||
- 减少一个故障点
|
||||
|
||||
**验证方法**:
|
||||
```bash
|
||||
# 验证 docker-compose 中无 ES 服务
|
||||
docker compose config | grep -i elasticsearch
|
||||
# 应该无输出
|
||||
|
||||
# 验证全文检索使用 tsvector
|
||||
cd backend && pytest tests/test_questions_api.py::test_search_by_keyword -v
|
||||
```
|
||||
|
||||
**验证标准**: ✅ 全文检索功能正常,无 ES 依赖
|
||||
|
||||
---
|
||||
|
||||
### 6. Flutter 鸿蒙适配阻断构建
|
||||
|
||||
**原风险**: 要求三端(Android/iOS/HarmonyOS)同时跑通
|
||||
|
||||
**修订措施**:
|
||||
- 只声明 android + ios 平台
|
||||
- 鸿蒙作为占位接口,不阻断构建
|
||||
- PlatformService 抽象层,鸿蒙方法抛 UnimplementedError
|
||||
|
||||
**验证方法**:
|
||||
```bash
|
||||
# Flutter 分析应该通过
|
||||
cd app && flutter analyze
|
||||
|
||||
# 构建 Android 应该成功
|
||||
cd app && flutter build apk --debug
|
||||
```
|
||||
|
||||
**验证标准**: ✅ flutter analyze 在 android+ios 平台通过
|
||||
|
||||
---
|
||||
|
||||
## 🟡 Important 风险(已修复)
|
||||
|
||||
### 7. AI 调用不可追溯
|
||||
|
||||
**原风险**: 无法追溯 AI 调用历史,难以调试和优化
|
||||
|
||||
**修订措施**:
|
||||
- 新增 `ai_invocation_logs` 表
|
||||
- 记录:model_name, prompt_version, input_hash, output_json
|
||||
- 每次 AI 调用自动记录
|
||||
|
||||
**验证方法**:
|
||||
```bash
|
||||
# 运行 AI 调用测试
|
||||
cd backend && pytest tests/test_sonnet_recognizer.py -v
|
||||
|
||||
# 验证日志记录
|
||||
psql -h localhost -p 5432 -U wrongbook -d wrongbook -c "SELECT count(*) FROM ai_invocation_logs;"
|
||||
```
|
||||
|
||||
**验证标准**: ✅ 每次 AI 调用都有日志记录
|
||||
|
||||
---
|
||||
|
||||
### 8. 全局异常处理缺失
|
||||
|
||||
**原风险**: 异常未统一处理,返回格式不一致
|
||||
|
||||
**修订措施**:
|
||||
- middleware.py 中实现 RequestIdMiddleware
|
||||
- register_exception_handlers 统一处理异常
|
||||
- 所有错误返回统一格式:`{"code": xxx, "data": null, "message": "xxx"}`
|
||||
- structlog 记录所有异常,带 request_id
|
||||
|
||||
**验证方法**:
|
||||
```bash
|
||||
# 运行异常处理测试
|
||||
cd backend && pytest tests/test_middleware.py -v
|
||||
|
||||
# 验证所有错误返回统一格式
|
||||
```
|
||||
|
||||
**验证标准**: ✅ 所有错误返回统一格式,日志可通过 request_id 串联
|
||||
|
||||
---
|
||||
|
||||
### 9. 图片存储不安全
|
||||
|
||||
**原风险**: 数据库存储裸 URL,可能泄露或失效
|
||||
|
||||
**修订措施**:
|
||||
- 数据库只存 object_key
|
||||
- API 返回时生成 presigned URL
|
||||
- URL 有效期可配置(默认 1 小时)
|
||||
|
||||
**验证方法**:
|
||||
```bash
|
||||
# 运行存储测试
|
||||
cd backend && pytest tests/test_storage.py -v
|
||||
|
||||
# 验证 API 返回 presigned URL
|
||||
curl -H "Authorization: Bearer <token>" http://localhost:8000/api/v1/questions/1
|
||||
# 响应中的图片字段应该是 presigned URL
|
||||
```
|
||||
|
||||
**验证标准**: ✅ API 返回 presigned URL,URL 有时效性
|
||||
|
||||
---
|
||||
|
||||
### 10. AI 模型名硬编码
|
||||
|
||||
**原风险**: 模型名硬编码在代码中,难以切换
|
||||
|
||||
**修订措施**:
|
||||
- 所有 AI 配置放在 .env 中
|
||||
- ANTHROPIC_MODEL, ANTHROPIC_TEMPERATURE 等可配置
|
||||
- prompt 模板放在 backend/app/prompts/ 目录
|
||||
|
||||
**验证方法**:
|
||||
```bash
|
||||
# 验证配置可读取
|
||||
cd backend && python -c "from app.config import settings; print(settings.ANTHROPIC_MODEL)"
|
||||
|
||||
# 验证 prompt 模板独立文件
|
||||
ls backend/app/prompts/
|
||||
```
|
||||
|
||||
**验证标准**: ✅ AI 配置可通过环境变量修改
|
||||
|
||||
---
|
||||
|
||||
## 验证矩阵总览
|
||||
|
||||
| 风险项 | 严重程度 | 状态 | 验证命令 |
|
||||
|--------|----------|------|----------|
|
||||
| Python+PaddleOCR | 🔴 Critical | ✅ 已修复 | `pip install -e "."` |
|
||||
| 测试DB | 🔴 Critical | ✅ 已修复 | `pytest -v` |
|
||||
| Celery+async | 🔴 Critical | ✅ 已修复 | `pytest tests/test_tasks.py -v` |
|
||||
| WS鉴权 | 🔴 Critical | ✅ 已修复 | `pytest tests/test_websocket.py -v` |
|
||||
| ES形同虚设 | 🔴 Critical | ✅ 已修复 | `docker compose config` |
|
||||
| Flutter鸿蒙 | 🔴 Critical | ✅ 已修复 | `flutter analyze` |
|
||||
| AI可追溯 | 🟡 Important | ✅ 已修复 | `pytest tests/test_sonnet_recognizer.py -v` |
|
||||
| 全局异常 | 🟡 Important | ✅ 已修复 | `pytest tests/test_middleware.py -v` |
|
||||
| 存储安全 | 🟡 Important | ✅ 已修复 | `pytest tests/test_storage.py -v` |
|
||||
| 模型名硬编码 | 🟡 Important | ✅ 已修复 | `python -c "from app.config import settings; print(settings.ANTHROPIC_MODEL)"` |
|
||||
|
||||
---
|
||||
|
||||
## 完整验证流程
|
||||
|
||||
### 1. 环境准备
|
||||
|
||||
```bash
|
||||
# 启动基础服务
|
||||
docker compose up -d postgres postgres-test redis minio
|
||||
|
||||
# 安装依赖(不含 PaddleOCR)
|
||||
cd backend && pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
### 2. 运行所有测试
|
||||
|
||||
```bash
|
||||
# Python 代码检查
|
||||
cd backend && ruff check .
|
||||
|
||||
# Python 测试
|
||||
cd backend && pytest -v
|
||||
|
||||
# Flutter 分析(如果已创建)
|
||||
cd app && flutter analyze
|
||||
|
||||
# Docker Compose 配置验证
|
||||
docker compose config --quiet
|
||||
```
|
||||
|
||||
### 3. 集成测试
|
||||
|
||||
```bash
|
||||
# 构建并启动所有服务
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
|
||||
# 等待服务启动
|
||||
sleep 10
|
||||
|
||||
# 验证 API 可访问
|
||||
curl -s http://localhost:8000/docs | head -5
|
||||
|
||||
# 运行集成测试
|
||||
cd backend && pytest tests/test_integration.py -v
|
||||
|
||||
# 清理
|
||||
docker compose down
|
||||
```
|
||||
|
||||
### 4. 验证标准
|
||||
|
||||
所有测试通过,输出:
|
||||
|
||||
```
|
||||
✅ Python lint check: PASSED
|
||||
✅ Python tests: PASSED (XX tests)
|
||||
✅ Flutter analyze: PASSED
|
||||
✅ Docker compose config: PASSED
|
||||
✅ Integration tests: PASSED
|
||||
|
||||
🎉 All checks passed!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 持续验证
|
||||
|
||||
### CI/CD 集成(未来)
|
||||
|
||||
```yaml
|
||||
# .github/workflows/test.yml
|
||||
name: Test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Start services
|
||||
run: docker compose up -d
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd backend
|
||||
pip install -e ".[dev]"
|
||||
ruff check .
|
||||
pytest -v
|
||||
```
|
||||
|
||||
### 定期审查
|
||||
|
||||
- **每周**: 运行完整测试套件
|
||||
- **每月**: 审查 AI 调用日志,优化 prompt
|
||||
- **每季度**: 更新依赖版本,修复安全漏洞
|
||||
|
||||
---
|
||||
|
||||
## 问题反馈
|
||||
|
||||
如果发现任何风险点未被覆盖,请:
|
||||
|
||||
1. 在 GitHub Issues 中提交
|
||||
2. 标注严重程度(🔴 Critical / 🟡 Important / 🟢 Nice to have)
|
||||
3. 提供复现步骤
|
||||
4. 建议修复方案
|
||||
@@ -0,0 +1,314 @@
|
||||
# 智能错题本系统架构文档
|
||||
|
||||
## 系统概述
|
||||
|
||||
智能错题本是一个基于 AI 的智能错题管理系统,支持拍照识别、自动分析、智能复习。
|
||||
|
||||
## 技术架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 客户端层 │
|
||||
├─────────────────┬─────────────────┬─────────────────────────────┤
|
||||
│ Flutter App │ uni-app 小程序 │ Web (未来) │
|
||||
│ (Android/iOS) │ (微信) │ │
|
||||
└────────┬────────┴────────┬────────┴──────────────────────────────┘
|
||||
│ │
|
||||
│ HTTPS/WSS │
|
||||
▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ API Gateway / Nginx │
|
||||
└────────┬────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ FastAPI 后端服务 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Auth │ │ Questions│ │ Review │ │ Books │ │
|
||||
│ │ API │ │ API │ │ API │ │ API │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ WebSocket 实时通知 │ │
|
||||
│ └───────────────────────────────────────────��──────────┘ │
|
||||
└────────┬────────────────────────────────────────────────────────┘
|
||||
│
|
||||
├─────────────┬─────────────┬─────────────┬──────────────┐
|
||||
▼ ▼ ▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ PostgreSQL │ │ Redis │ │ MinIO │ │ Celery │ │ Claude │
|
||||
│ 数据库 │ │ 缓存 │ │ 对象存储 │ │ 任务队列│ │ API │
|
||||
└──────────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
## 核心模块
|
||||
|
||||
### 1. 认证模块(Auth)
|
||||
|
||||
- **功能**: 用户注册、登录、JWT 令牌管理
|
||||
- **技术**: JWT + Redis(token 黑名单)
|
||||
- **端点**:
|
||||
- POST /api/v1/auth/login
|
||||
- POST /api/v1/auth/refresh
|
||||
- POST /api/v1/auth/wx-login(小程序)
|
||||
|
||||
### 2. 试卷处理模块(Papers)
|
||||
|
||||
- **功能**: 试卷上传、题块切分、错题召回
|
||||
- **技术**: OpenCV + PaddleOCR(可选)
|
||||
- **流程**:
|
||||
1. 用户上传试卷图片
|
||||
2. Stage 1: 图片预处理 → 题块切分 → 错题召回
|
||||
3. 用户确认错题
|
||||
4. Stage 2: AI 识别 → 手写清除 → AI 解析 → 入库
|
||||
|
||||
### 3. 错题管理模块(Questions)
|
||||
|
||||
- **功能**: 错题 CRUD、搜索、统计
|
||||
- **技术**: PostgreSQL tsvector 全文检索
|
||||
- **端点**:
|
||||
- POST /api/v1/questions/search
|
||||
- GET /api/v1/questions/{id}
|
||||
- PUT /api/v1/questions/{id}
|
||||
- DELETE /api/v1/questions/{id}
|
||||
- GET /api/v1/questions/stats
|
||||
|
||||
### 4. 复习模块(Review)
|
||||
|
||||
- **功能**: 智能复习提醒、复习记录
|
||||
- **技术**: SM-2 改进算法
|
||||
- **端点**:
|
||||
- GET /api/v1/review/today
|
||||
- POST /api/v1/review/{question_id}
|
||||
|
||||
### 5. 错题集模块(Books)
|
||||
|
||||
- **功能**: 错题集管理、题目分组
|
||||
- **端点**:
|
||||
- POST /api/v1/books
|
||||
- GET /api/v1/books
|
||||
- POST /api/v1/books/{id}/questions
|
||||
|
||||
## 数据模型
|
||||
|
||||
### 核心表
|
||||
|
||||
1. **users** - 用户表
|
||||
2. **subjects** - 学科表
|
||||
3. **knowledge_tags** - 知识点标签(树形结构)
|
||||
4. **exam_papers** - 试卷表
|
||||
5. **wrong_questions** - 错题表(核心)
|
||||
6. **review_logs** - 复习记录表
|
||||
7. **question_books** - 错题集表
|
||||
8. **book_questions** - 错题集-题目关联表
|
||||
9. **ai_invocation_logs** - AI 调用日志(可追溯)
|
||||
|
||||
### 关键索引
|
||||
|
||||
```sql
|
||||
-- 错题表索引
|
||||
CREATE INDEX ix_wq_user_subject ON wrong_questions(user_id, subject_id);
|
||||
CREATE INDEX ix_wq_knowledge ON wrong_questions USING gin(knowledge_tags);
|
||||
CREATE INDEX ix_wq_custom_tags ON wrong_questions USING gin(custom_tags);
|
||||
CREATE INDEX ix_wq_review ON wrong_questions(user_id, next_review_at);
|
||||
CREATE INDEX ix_wq_created ON wrong_questions(user_id, created_at DESC);
|
||||
```
|
||||
|
||||
## AI Pipeline
|
||||
|
||||
### 四层处理流程
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Layer 1: 题块切分 │
|
||||
│ - 图片预处理(去阴影、增强对比度) │
|
||||
│ - 题块切分(规则 / PaddleOCR) │
|
||||
│ - 题号识别 │
|
||||
└────────┬────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Layer 2: 错题召回 │
|
||||
│ - 红色标记检测(HSV) │
|
||||
│ - ✗ 符号检测(霍夫线交叉) │
|
||||
│ - 评分 → confirmed / suspected / likely_correct │
|
||||
└────────┬────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Layer 3: Sonnet 识别 │
|
||||
│ - 双图输入(原图 + 增强图) │
|
||||
│ - Claude Sonnet Vision API │
|
||||
│ - 提取:题面、题型、选项、学生答案、正确答案 │
|
||||
│ - 置信度评估 → auto_pass / needs_review │
|
||||
└────────┬────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Layer 4: Sonnet 解析 │
|
||||
│ - 两段式调用: │
|
||||
│ 1. 题面校对(如果置信度低) │
|
||||
│ 2. 完整解析(解题步骤、错因分析、知识点) │
|
||||
│ - 自检验证(代回验证、量纲检查) │
|
||||
│ - 手写清除(local / API) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 异步任务
|
||||
|
||||
### Celery 任务
|
||||
|
||||
1. **process_paper_stage1**
|
||||
- 切题 + 召回(不调大模型)
|
||||
- 结果写入 Redis
|
||||
- 通知 WebSocket
|
||||
|
||||
2. **process_paper_stage2**
|
||||
- 识别 + 解析 + 入库(调大模型)
|
||||
- 并行处理多道题
|
||||
- 通知 WebSocket
|
||||
|
||||
### WebSocket 通知
|
||||
|
||||
- **连接**: `ws://host/ws?token=<jwt_token>`
|
||||
- **鉴权**: JWT query parameter
|
||||
- **消息类型**:
|
||||
- `stage1_complete`: Stage 1 完成
|
||||
- `stage2_complete`: Stage 2 完成
|
||||
- `stage2_failed`: Stage 2 失败
|
||||
|
||||
## 存储策略
|
||||
|
||||
### 图片存储
|
||||
|
||||
- **存储**: MinIO 对象存储
|
||||
- **数据库**: 只存 object_key(不存 URL)
|
||||
- **访问**: 生成 presigned URL(有效期 1 小时)
|
||||
- **安全**: URL 带签名,过期自动失效
|
||||
|
||||
### 缓存策略
|
||||
|
||||
- **Redis 用途**:
|
||||
- JWT token 黑名单
|
||||
- Stage 1 召回结果(TTL 1 小时)
|
||||
- WebSocket pubsub 通知
|
||||
- Celery broker
|
||||
|
||||
## 安全设计
|
||||
|
||||
### 认证与授权
|
||||
|
||||
- **JWT**: 访问令牌(30 分钟)+ 刷新令牌(7 天)
|
||||
- **权限**: 用户只能访问自己的数据
|
||||
- **WebSocket**: JWT query parameter 鉴权
|
||||
|
||||
### 数据安全
|
||||
|
||||
- **密码**: bcrypt 哈希
|
||||
- **敏感信息**: 环境变量配置,不硬编码
|
||||
- **SQL 注入**: 使用 ORM,参数化查询
|
||||
- **XSS**: 前端输入验证和转义
|
||||
|
||||
### API 安全
|
||||
|
||||
- **HTTPS**: 生产环境强制 HTTPS
|
||||
- **CORS**: 配置允许的域名
|
||||
- **限流**: 防止 API 滥用
|
||||
- **日志**: 记录所有 API 调用(含 request_id)
|
||||
|
||||
## 可观测性
|
||||
|
||||
### 日志
|
||||
|
||||
- **工具**: structlog
|
||||
- **格式**: JSON(生产)/ Console(开发)
|
||||
- **字段**: request_id, user_id, timestamp, level, message
|
||||
- **存储**: 文件 / ELK(未来)
|
||||
|
||||
### 监控
|
||||
|
||||
- **健康检查**: /health 端点
|
||||
- **指标**: 响应时间、错误率、队列长度
|
||||
- **告警**: 关键错误自动告警(未来)
|
||||
|
||||
### AI 可追溯
|
||||
|
||||
- **ai_invocation_logs 表**:
|
||||
- 记录每次 AI 调用
|
||||
- 包含:model_name, prompt_version, input_hash, output_json
|
||||
- 用于:审计、调试、优化
|
||||
|
||||
## 扩展性设计
|
||||
|
||||
### 水平扩展
|
||||
|
||||
- **API 服务**: 无状态,可多实例部署
|
||||
- **Celery Worker**: 可增加 worker 数量
|
||||
- **数据库**: 读写分离、分片(未来)
|
||||
|
||||
### 功能扩展
|
||||
|
||||
- **PaddleOCR**: 可选依赖,按需启用
|
||||
- **HarmonyOS**: 占位接口,待 Flutter-ohos 稳定
|
||||
- **Elasticsearch**: 可替换 PostgreSQL 全文检索
|
||||
- **自建 Inpainting**: 可替换 OpenCV 手写清除
|
||||
|
||||
## 部署架构
|
||||
|
||||
### 开发环境
|
||||
|
||||
```
|
||||
docker-compose.yml:
|
||||
- postgres (5432)
|
||||
- postgres-test (5433)
|
||||
- redis (6379)
|
||||
- minio (9000, 9001)
|
||||
|
||||
本地运行:
|
||||
- FastAPI (8000)
|
||||
- Celery Worker
|
||||
```
|
||||
|
||||
### 生产环境
|
||||
|
||||
```
|
||||
docker-compose.prod.yml:
|
||||
- nginx (80, 443)
|
||||
- api (多实例)
|
||||
- worker (多实例)
|
||||
- postgres (主从)
|
||||
- redis (哨兵)
|
||||
- minio (集群)
|
||||
```
|
||||
|
||||
## 技术选型理由
|
||||
|
||||
| 技术 | 理由 |
|
||||
|------|------|
|
||||
| FastAPI | 高性能、异步支持、自动文档 |
|
||||
| SQLAlchemy 2.0 | 成熟的 ORM、异步支持 |
|
||||
| PostgreSQL | 强大的全文检索、JSON 支持、ARRAY 类型 |
|
||||
| Redis | 高性能缓存、pubsub 支持 |
|
||||
| Celery | 成熟的任务队列、支持多种 broker |
|
||||
| MinIO | S3 兼容、自托管、高性能 |
|
||||
| Flutter | 跨平台、高性能、丰富的组件 |
|
||||
| uni-app | 快速开发小程序、跨平台 |
|
||||
| Claude Sonnet | 强大的视觉理解、高质量输出 |
|
||||
|
||||
## 已知限制与 TODO
|
||||
|
||||
### 当前限制
|
||||
|
||||
1. **HarmonyOS**: 占位接口,待 Flutter-ohos 稳定
|
||||
2. **PaddleOCR**: 可选依赖,默认使用规则切分
|
||||
3. **手写清除**: 使用 OpenCV,效果有限
|
||||
4. **全文检索**: 使用 PostgreSQL tsvector,大规模数据可能需要 ES
|
||||
|
||||
### 未来计划
|
||||
|
||||
1. **自建 Inpainting 模型**: 提升手写清除效果
|
||||
2. **Elasticsearch 集成**: 提升搜索性能
|
||||
3. **CI/CD**: GitHub Actions 自动化部署
|
||||
4. **监控告警**: Prometheus + Grafana
|
||||
5. **数据分析**: 学习行为分析、推荐系统
|
||||
@@ -0,0 +1,38 @@
|
||||
# WrongBook 微信小程序
|
||||
|
||||
智能错题本微信小程序轻量版
|
||||
|
||||
## 技术栈
|
||||
|
||||
- uni-app
|
||||
- Vue 3
|
||||
- TypeScript
|
||||
|
||||
## 开发
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 运行开发环境
|
||||
npm run dev:mp-weixin
|
||||
|
||||
# 构建生产版本
|
||||
npm run build:mp-weixin
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
pages/
|
||||
├── index/ # 首页
|
||||
├── login/ # 登录
|
||||
├── questions/ # 题目相关
|
||||
│ ├── list.vue # 题目列表
|
||||
│ └── detail.vue # 题目详情
|
||||
└── capture/ # 拍照录题
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
在 `manifest.json` 中配置小程序 appid。
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "wrongbook-miniprogram",
|
||||
"appid": "",
|
||||
"description": "智能错题本微信小程序",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
"mp-weixin": {
|
||||
"appid": "",
|
||||
"setting": {
|
||||
"urlCheck": false,
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true
|
||||
},
|
||||
"usingComponents": true
|
||||
},
|
||||
"uniStatistics": {
|
||||
"enable": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "wrongbook-miniprogram",
|
||||
"version": "1.0.0",
|
||||
"description": "智能错题本微信小程序",
|
||||
"scripts": {
|
||||
"dev:mp-weixin": "uni -p mp-weixin",
|
||||
"build:mp-weixin": "uni build -p mp-weixin"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "^3.0.0",
|
||||
"@dcloudio/uni-mp-weixin": "^3.0.0",
|
||||
"vue": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "^3.4.0",
|
||||
"@dcloudio/uni-cli-shared": "^3.0.0",
|
||||
"@dcloudio/vite-plugin-uni": "^3.0.0",
|
||||
"typescript": "^5.3.0",
|
||||
"vite": "^5.0.0",
|
||||
"vue-tsc": "^1.8.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "智能错题本"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/questions/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "错题列表"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/questions/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "题目详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/capture/capture",
|
||||
"style": {
|
||||
"navigationBarTitleText": "拍照录题"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "智能错题本",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#3cc51f",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "static/home.png",
|
||||
"selectedIconPath": "static/home-active.png",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/questions/list",
|
||||
"iconPath": "static/list.png",
|
||||
"selectedIconPath": "static/list-active.png",
|
||||
"text": "错题"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="preview" v-if="imagePath">
|
||||
<image :src="imagePath" mode="aspectFit" class="image" />
|
||||
</view>
|
||||
<view class="placeholder" v-else>
|
||||
<text>请选择或拍摄图片</text>
|
||||
</view>
|
||||
|
||||
<view class="actions">
|
||||
<button class="btn" @tap="chooseImage('camera')">拍照</button>
|
||||
<button class="btn" @tap="chooseImage('album')">相册</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="!imagePath || isProcessing"
|
||||
@tap="uploadImage"
|
||||
>
|
||||
{{ isProcessing ? '处理中...' : '确认' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const imagePath = ref('');
|
||||
const isProcessing = ref(false);
|
||||
|
||||
const chooseImage = (sourceType: 'camera' | 'album') => {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sourceType: [sourceType],
|
||||
success: (res) => {
|
||||
imagePath.value = res.tempFilePaths[0];
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const uploadImage = () => {
|
||||
if (!imagePath.value) return;
|
||||
|
||||
isProcessing.value = true;
|
||||
|
||||
// TODO: 实现图片上传逻辑
|
||||
setTimeout(() => {
|
||||
isProcessing.value = false;
|
||||
uni.showToast({
|
||||
title: '题目已添加',
|
||||
icon: 'success',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}, 2000);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.preview,
|
||||
.placeholder {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #3cc51f;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<text class="title">智能错题本</text>
|
||||
</view>
|
||||
<view class="features">
|
||||
<view class="feature-item" @tap="navigateTo('/pages/capture/capture')">
|
||||
<view class="feature-icon">📷</view>
|
||||
<text class="feature-text">拍照录题</text>
|
||||
</view>
|
||||
<view class="feature-item" @tap="navigateTo('/pages/questions/list')">
|
||||
<view class="feature-icon">📝</view>
|
||||
<text class="feature-text">错题列表</text>
|
||||
</view>
|
||||
<view class="feature-item">
|
||||
<view class="feature-icon">🔍</view>
|
||||
<text class="feature-text">搜索题目</text>
|
||||
</view>
|
||||
<view class="feature-item">
|
||||
<view class="feature-icon">📊</view>
|
||||
<text class="feature-text">学习统计</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const navigateTo = (url: string) => {
|
||||
uni.navigateTo({ url });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.feature-text {
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="login-box">
|
||||
<text class="title">登录</text>
|
||||
<input
|
||||
v-model="username"
|
||||
class="input"
|
||||
placeholder="用户名"
|
||||
type="text"
|
||||
/>
|
||||
<input
|
||||
v-model="password"
|
||||
class="input"
|
||||
placeholder="密码"
|
||||
type="password"
|
||||
/>
|
||||
<button class="btn-login" @tap="handleLogin">登录</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const username = ref('');
|
||||
const password = ref('');
|
||||
|
||||
const handleLogin = () => {
|
||||
if (!username.value || !password.value) {
|
||||
uni.showToast({
|
||||
title: '请输入用户名和密码',
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: 实现登录逻辑
|
||||
uni.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index',
|
||||
});
|
||||
}, 1500);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.login-box {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
display: block;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
width: 100%;
|
||||
background: #3cc51f;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="section">
|
||||
<text class="section-title">题目信息</text>
|
||||
<view class="image-box">
|
||||
<text>题目图片</text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text class="info-item">科目: 数学</text>
|
||||
<text class="info-item">难度: 中等</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<text class="section-title">AI 分析</text>
|
||||
<text class="content">这是一道关于...的题目</text>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<text class="section-title">我的笔记</text>
|
||||
<textarea
|
||||
v-model="notes"
|
||||
class="textarea"
|
||||
placeholder="添加笔记..."
|
||||
/>
|
||||
<button class="btn-save" @tap="saveNotes">保存笔记</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const notes = ref('');
|
||||
|
||||
const saveNotes = () => {
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success',
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.image-box {
|
||||
height: 200px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
width: 100%;
|
||||
background: #3cc51f;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="list">
|
||||
<view
|
||||
v-for="item in questions"
|
||||
:key="item.id"
|
||||
class="list-item"
|
||||
@tap="navigateToDetail(item.id)"
|
||||
>
|
||||
<view class="item-content">
|
||||
<text class="item-title">{{ item.title }}</text>
|
||||
<text class="item-meta">{{ item.subject }} · {{ item.difficulty }}</text>
|
||||
</view>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
interface Question {
|
||||
id: string;
|
||||
title: string;
|
||||
subject: string;
|
||||
difficulty: string;
|
||||
}
|
||||
|
||||
const questions = ref<Question[]>([
|
||||
{ id: '1', title: '题目 1', subject: '数学', difficulty: '中等' },
|
||||
{ id: '2', title: '题目 2', subject: '物理', difficulty: '困难' },
|
||||
{ id: '3', title: '题目 3', subject: '化学', difficulty: '简单' },
|
||||
]);
|
||||
|
||||
const navigateToDetail = (id: string) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/questions/detail?id=${id}`,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.item-meta {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.item-arrow {
|
||||
font-size: 24px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
Executable
+176
@@ -0,0 +1,176 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "🔧 Harbor 配置设置"
|
||||
echo "==============================================="
|
||||
echo ""
|
||||
|
||||
# 选择 Harbor 服务器
|
||||
echo "选择 Harbor 服务器:"
|
||||
echo "1) 美国 (harbor.omniverseai.net) - 默认"
|
||||
echo "2) 欧洲 (harbor-eu.omniverseai.net)"
|
||||
echo "3) 自定义"
|
||||
echo ""
|
||||
read -p "请选择 [1-3]: " choice
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
HARBOR_HOST="harbor.omniverseai.net"
|
||||
echo "✓ 选择: 美国服务器"
|
||||
;;
|
||||
2)
|
||||
HARBOR_HOST="harbor-eu.omniverseai.net"
|
||||
echo "✓ 选择: 欧洲服务器"
|
||||
;;
|
||||
3)
|
||||
read -p "请输入 Harbor 地址 (例如: harbor.example.com): " HARBOR_HOST
|
||||
echo "✓ 使用自定义服务器: $HARBOR_HOST"
|
||||
;;
|
||||
*)
|
||||
echo "⚠️ 无效选择,使用默认值: harbor.omniverseai.net"
|
||||
HARBOR_HOST="harbor.omniverseai.net"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
|
||||
# 项目名称
|
||||
read -p "Harbor 项目名称 [ljwx]: " HARBOR_PROJECT
|
||||
HARBOR_PROJECT=${HARBOR_PROJECT:-ljwx}
|
||||
echo "✓ 项目名称: $HARBOR_PROJECT"
|
||||
|
||||
echo ""
|
||||
|
||||
# 镜像名称
|
||||
read -p "镜像名称 [ljwx-shiti]: " IMAGE_NAME
|
||||
IMAGE_NAME=${IMAGE_NAME:-ljwx-shiti}
|
||||
echo "✓ 镜像名称: $IMAGE_NAME"
|
||||
|
||||
echo ""
|
||||
|
||||
# 用户名
|
||||
read -p "Harbor 用户名 [admin]: " HARBOR_USERNAME
|
||||
HARBOR_USERNAME=${HARBOR_USERNAME:-admin}
|
||||
echo "✓ 用户名: $HARBOR_USERNAME"
|
||||
|
||||
echo ""
|
||||
|
||||
# 密码
|
||||
read -sp "Harbor 密码: " HARBOR_PASSWORD
|
||||
echo ""
|
||||
echo "✓ 密码已输入"
|
||||
|
||||
echo ""
|
||||
echo "==============================================="
|
||||
echo "📝 设置 GitHub Secrets..."
|
||||
echo ""
|
||||
|
||||
# 检查 gh CLI
|
||||
if ! command -v gh &> /dev/null; then
|
||||
echo "❌ 错误: 未找到 gh CLI"
|
||||
echo "请先安装 GitHub CLI: https://cli.github.com/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查认证
|
||||
if ! gh auth status &> /dev/null; then
|
||||
echo "❌ 错误: GitHub CLI 未认证"
|
||||
echo "请先运行: gh auth login"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 设置 secrets
|
||||
echo "设置 HARBOR_HOST..."
|
||||
echo "$HARBOR_HOST" | gh secret set HARBOR_HOST
|
||||
|
||||
echo "设置 HARBOR_PROJECT..."
|
||||
echo "$HARBOR_PROJECT" | gh secret set HARBOR_PROJECT
|
||||
|
||||
echo "设置 IMAGE_NAME..."
|
||||
echo "$IMAGE_NAME" | gh secret set IMAGE_NAME
|
||||
|
||||
echo "设置 HARBOR_USERNAME..."
|
||||
echo "$HARBOR_USERNAME" | gh secret set HARBOR_USERNAME
|
||||
|
||||
echo "设置 HARBOR_PASSWORD..."
|
||||
echo "$HARBOR_PASSWORD" | gh secret set HARBOR_PASSWORD
|
||||
|
||||
echo ""
|
||||
echo "==============================================="
|
||||
echo "✅ Harbor 配置完成!"
|
||||
echo ""
|
||||
echo "配置信息:"
|
||||
echo " Harbor 地址: $HARBOR_HOST"
|
||||
echo " 项目名称: $HARBOR_PROJECT"
|
||||
echo " 镜像名称: $IMAGE_NAME"
|
||||
echo " 用户名: $HARBOR_USERNAME"
|
||||
echo ""
|
||||
echo "完整镜像地址:"
|
||||
echo " $HARBOR_HOST/$HARBOR_PROJECT/$IMAGE_NAME:latest"
|
||||
echo ""
|
||||
echo "==============================================="
|
||||
echo "🧪 验证配置..."
|
||||
echo ""
|
||||
|
||||
# 验证 secrets
|
||||
echo "检查 GitHub Secrets..."
|
||||
if gh secret list | grep -q "HARBOR_HOST"; then
|
||||
echo "✓ HARBOR_HOST 已设置"
|
||||
else
|
||||
echo "✗ HARBOR_HOST 未设置"
|
||||
fi
|
||||
|
||||
if gh secret list | grep -q "HARBOR_PROJECT"; then
|
||||
echo "✓ HARBOR_PROJECT 已设置"
|
||||
else
|
||||
echo "✗ HARBOR_PROJECT 未设置"
|
||||
fi
|
||||
|
||||
if gh secret list | grep -q "IMAGE_NAME"; then
|
||||
echo "✓ IMAGE_NAME 已设置"
|
||||
else
|
||||
echo "✗ IMAGE_NAME 未设置"
|
||||
fi
|
||||
|
||||
if gh secret list | grep -q "HARBOR_USERNAME"; then
|
||||
echo "✓ HARBOR_USERNAME 已设置"
|
||||
else
|
||||
echo "✗ HARBOR_USERNAME 未设置"
|
||||
fi
|
||||
|
||||
if gh secret list | grep -q "HARBOR_PASSWORD"; then
|
||||
echo "✓ HARBOR_PASSWORD 已设置"
|
||||
else
|
||||
echo "✗ HARBOR_PASSWORD 未设置"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "==============================================="
|
||||
echo "🔍 测试 Harbor 连接..."
|
||||
echo ""
|
||||
|
||||
# 测试 Docker 登录
|
||||
if docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" -p "$HARBOR_PASSWORD" &> /dev/null; then
|
||||
echo "✅ Harbor 登录成功!"
|
||||
echo ""
|
||||
echo "你可以开始推送镜像了:"
|
||||
echo " docker build -t $HARBOR_HOST/$HARBOR_PROJECT/$IMAGE_NAME:test ."
|
||||
echo " docker push $HARBOR_HOST/$HARBOR_PROJECT/$IMAGE_NAME:test"
|
||||
else
|
||||
echo "⚠️ Harbor 登录失败"
|
||||
echo "请检查:"
|
||||
echo " 1. Harbor 地址是否正确"
|
||||
echo " 2. 用户名和密码是否正确"
|
||||
echo " 3. 网络连接是否正常"
|
||||
echo ""
|
||||
echo "手动测试登录:"
|
||||
echo " docker login $HARBOR_HOST"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "==============================================="
|
||||
echo "📚 相关文档:"
|
||||
echo " - Harbor 配置: .github/workflows/HARBOR_CONFIG.md"
|
||||
echo " - CI/CD 文档: .github/workflows/README.md"
|
||||
echo ""
|
||||
echo "完成!🎉"
|
||||
Reference in New Issue
Block a user