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:
2026-02-27 14:11:44 +08:00
co-authored by Claude Sonnet 4.6
parent efe7170c21
commit 841512af33
35 changed files with 5439 additions and 12 deletions
+76
View File
@@ -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
```
+63
View File
@@ -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
View File
@@ -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
+256
View File
@@ -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
+247
View File
@@ -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
+58
View File
@@ -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/ # 拍照录题
```
+11
View File
@@ -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
+52
View File
@@ -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
+101
View File
@@ -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 表)
+143
View File
@@ -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(...)
```
+262
View File
@@ -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
View File
@@ -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()
+3 -2
View File
@@ -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
+6 -5
View File
@@ -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()
+411
View File
@@ -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
- 设置覆盖率报告
- 配置自动部署
完成这些步骤后,项目的核心技术风险基本消除,可以进入正式开发节奏。
+384
View File
@@ -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/)
+139
View File
@@ -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 "================================"
+114
View File
@@ -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())
+43
View File
@@ -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
View File
@@ -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
```
+545
View File
@@ -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而非裸地址 |
+447
View File
@@ -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
```
+291
View File
@@ -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
+355
View File
@@ -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. 建议修复方案
+314
View File
@@ -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. **数据分析**: 学习行为分析、推荐系统
+38
View File
@@ -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。
+21
View File
@@ -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
}
}
+22
View File
@@ -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"
}
}
+60
View File
@@ -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": "错题"
}
]
}
}
+105
View File
@@ -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>
+70
View File
@@ -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>
+88
View File
@@ -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>
+102
View File
@@ -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>
+78
View File
@@ -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>
+176
View File
@@ -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 "完成!🎉"