feat: 问题数据迁移到数据库;学员详情页URL导航改造;侧边栏统一

- 问题从文件系统迁移到数据库 problems 表
- 移除 PROBLEMS_DIR 配置和文件读取逻辑
- student.html 完整重写:编辑/添加/删除问题,生成方案进度显示
- 学员详情页支持独立URL访问 (/student/<id>)
- 统一侧边栏到 base.html
- 更新文档:DEPLOYMENT_SOP, MODELS, STRUCTURE, FRONTEND_ARCH
- 部署到生产环境 v1.2.0
This commit is contained in:
hmo
2026-04-23 06:35:32 +08:00
parent fd593bddf4
commit 18351212e8
18 changed files with 857 additions and 488 deletions
+60 -44
View File
@@ -1,7 +1,7 @@
# 钢琴练习方案系统 - 部署 SOP
> 版本:v1.1
> 日期:2026-04-21
> 版本:v1.2
> 日期:2026-04-23
> 核心原则:**不删除,只备份后新增/替换**
---
@@ -46,11 +46,12 @@
| 类型 | 源 | 容器内 | 说明 |
|------|-----|--------|------|
| Bind Mount | `/opt/piano-plan/个性化方案` | `/app/个性化方案` | 问题文件(15个md |
| Volume | `piano-plan-data` | `/app/data` | SQLite 数据库 |
| Volume | `piano-plan-output` | `/app/output` | PDF 输出 |
| Bind Mount | `/opt/piano-plan/config` | `/app/config` | API 配置 |
> ⚠️ **已移除**`/opt/piano-plan/个性化方案` 挂载点(问题文件已迁移到数据库 `problems` 表)
---
## 三、部署步骤
@@ -81,22 +82,18 @@ docker save piano-plan:latest -o piano-plan.tar
scp -i ~/.ssh/id_rsa piano-plan.tar root@47.106.65.108:/opt/piano-plan/
```
### 3.3 服务器部署(使用脚本!)
### 3.3 服务器部署
```bash
# 7. SSH 到服务器
ssh -i ~/.ssh/id_rsa root@47.106.65.108
# 8. 使用自动化部署脚本(会自动完成所有步骤并验证)
bash /path/to/deploy.sh /opt/piano-plan/piano-plan.tar
# 8. 创建带时间戳的备份目录
mkdir -p /opt/piano-plan/backups/backup_$(date +%Y%m%d)
# 或者手动部署(不推荐):
# 8a. 确认当前容器挂载配置
docker inspect piano-plan --format '{{json .Mounts}}'
# 9. 创建备份
mkdir -p /opt/piano-plan/backups
docker cp piano-plan:/app/data/piano_plans.db /opt/piano-plan/backups/piano_plans.db.bak
# 9. 备份当前数据库和配置
docker cp piano-plan:/app/data/piano_plans.db /opt/piano-plan/backups/backup_$(date +%Y%m%d)/
cp /opt/piano-plan/config/api_config.json /opt/piano-plan/backups/backup_$(date +%Y%m%d)/
# 10. 停止旧容器
docker stop piano-plan
@@ -105,38 +102,52 @@ docker rm piano-plan
# 11. 加载新镜像
docker load -i /opt/piano-plan/piano-plan.tar
# 12. 启动新容器(挂载配置必须完全正确!)
# 12. 启动新容器(无个性化方案挂载!)
docker run -d \
--name piano-plan \
-p 5001:5001 \
--restart unless-stopped \
-e FLASK_ENV=production \
-v /opt/piano-plan/个性化方案:/app/个性化方案 \
-v piano-plan-data:/app/data \
-v piano-plan-output:/app/output \
-v /opt/piano-plan/config:/app/config \
piano-plan:latest
```
### 3.4 验证
### 3.4 数据同步(特殊情况下从开发环境覆盖生产数据库)
> ⚠️ **警告**:这是**特殊处理**,仅在开发环境和生产环境数据结构需要统一时执行。正常部署不应覆盖生产数据库。
```bash
# 13. 检查容器状态
# 13. 停止容器
docker stop piano-plan
# 14. 上传开发环境数据库到服务器(在本地执行)
scp -i ~/.ssh/id_rsa data/piano_plans.db root@47.106.65.108:/opt/piano-plan/backups/
# 15. 覆盖生产数据库
docker cp /opt/piano-plan/backups/piano_plans.db piano-plan:/app/data/piano_plans.db
# 16. 重启容器
docker start piano-plan
```
### 3.5 验证
```bash
# 17. 检查容器状态
docker ps --filter name=piano-plan
# 14. 检查日志
# 18. 检查日志
docker logs piano-plan --tail 20
# 15. 验证服务
# 19. 验证服务
curl -I http://localhost:5001/
# 16. 验证问题文件(应该看到15个md文件)
docker exec piano-plan ls /app/个性化方案/
# 20. 验证数据库表
docker exec piano-plan ls /app/data/
# 17. 验证数据库(应该看到 templates 表)
docker exec piano-plan python -c "import sqlite3; conn=sqlite3.connect('/app/data/piano_plans.db'); print([r[0] for r in conn.execute('SELECT name FROM sqlite_master WHERE type=\"table\"')])"
# 18. 验证 API 配置
# 21. 验证 API 配置
docker exec piano-plan cat /app/config/api_config.json
```
@@ -152,7 +163,7 @@ docker exec piano-plan cat /app/config/api_config.json
| 学员数据 | piano-plan-data:/app/data | students, student_problems 表 |
| 班级数据 | piano-plan-data:/app/data | classes 表 |
| 练习方案 | piano-plan-data:/app/data | practice_plans 表 |
| 问题文件 | /opt/piano-plan/个性化方案 | 15个md文件 |
| 问题数据 | piano-plan-data:/app/data | problems 表(已从文件迁移到数据库) |
### 4.2 新增/更新的数据
@@ -221,32 +232,40 @@ ssh -i ~/.ssh/id_rsa root@47.106.65.108 "docker cp /tmp/update_templates.py pian
## 五、回滚流程
### 5.1 快速回滚(推荐)
### 5.1 从备份恢复数据库
```bash
# 停止当前容器
# 停止容器
docker stop piano-plan
# 从备份目录恢复(替换日期)
docker cp /opt/piano-plan/backups/backup_20260423/piano_plans.db piano-plan:/app/data/piano_plans.db
# 重启容器
docker start piano-plan
```
### 5.2 完整回滚(恢复旧镜像+数据库)
```bash
# 停止并删除当前容器
docker stop piano-plan
docker rm piano-plan
# 使用旧镜像重新启动(如果镜像还在)
# 使用旧镜像重新启动(如果镜像还在本地
docker run -d \
--name piano-plan \
-p 5001:5001 \
--restart unless-stopped \
-e FLASK_ENV=production \
-v /opt/piano-plan/个性化方案:/app/个性化方案 \
-v piano-plan-data:/app/data \
-v piano-plan-output:/app/output \
-v /opt/piano-plan/config:/app/config \
piano-plan:latest
```
### 5.2 从备份恢复
```bash
# 恢复数据库
# 从备份恢复数据库
docker stop piano-plan
cp /opt/piano-plan/backups/piano_plans.db.bak /var/lib/docker/volumes/piano-plan-data/_data/piano_plans.db
docker cp /opt/piano-plan/backups/backup_YYYYMMDD/piano_plans.db piano-plan:/app/data/piano_plans.db
docker start piano-plan
```
@@ -275,9 +294,10 @@ docker start piano-plan
| 源 | 容器内路径 | 说明 |
|-----|------------|------|
| /opt/piano-plan/个性化方案 | /app/个性化方案 | 问题文件 |
| /opt/piano-plan/config | /app/config | API 配置 |
> ⚠️ **已移除**`/opt/piano-plan/个性化方案` 挂载(问题文件已迁移到数据库)
---
## 七、API 配置说明
@@ -300,9 +320,6 @@ docker start piano-plan
## 八、常见问题
### Q: 部署后问题文件看不到?
A: 检查挂载 `/opt/piano-plan/个性化方案:/app/个性化方案` 是否正确
### Q: 数据库是空的?
A: 检查 volume `piano-plan-data` 是否被错误覆盖,尝试从备份恢复
@@ -335,8 +352,7 @@ location /api/generate-plan {
```
[ ] 容器状态:running
[ ] 服务响应:HTTP 200/302
[ ] 问题文件数量:15个 md 文件
[ ] 数据库记录:users, students, classes, student_problems, practice_plans 完整
[ ] 数据库记录:users, students, classes, student_problems, practice_plans, problems 完整
[ ] templates 表存在且包含 AI提示词模板、报告导出模板
[ ] API 配置:provider, model, api_key 正确
[ ] 功能验证:能生成练习方案
@@ -344,5 +360,5 @@ location /api/generate-plan {
---
> **最后更新**2026-04-21
> **更新原因**更新部署流程,添加数据保护规范,明确挂载点配置;添加 SSE 问题排查
> **最后更新**2026-04-23
> **更新原因**v1.2 部署更新;移除个性化方案挂载(问题已迁移到数据库);更新备份和回滚流程
+18 -7
View File
@@ -79,15 +79,15 @@ piano-plan/
### 发布流程
1. **开发完成** → 本地测试通过
2. **构建镜像**`docker build -t piano-plan:v1.2.0 .`
2. **构建镜像**`docker build -t piano-plan:latest .`
3. **打包部署文件** → 创建 `releases/v1.2.0/` 目录,放入:
- `piano-plan-v1.2.0.tar.gz` - Docker镜像
- `piano-nginx.conf` - Nginx配置(从服务器获取最新)
- `docker-compose.yml` - 部署编排
4. **上传** → 传到服务器 load 镜像
5. **部署** → docker-compose up -d
- `piano-plan.tar` - Docker镜像
4. **上传** → scp 到服务器 `/opt/piano-plan/`
5. **部署** → 按照 DEPLOYMENT_SOP.md 执行
6. **清理** → 本地 tar 包可删除(git已管理版本)
> ⚠️ Nginx 配置在服务器上:`/srv/nginx/conf/conf.d/piano.yoin.fun.conf`
### 版本化部署包命名
```
@@ -143,4 +143,15 @@ deploy: v1.2.0 生产环境部署
---
*最后更新:2026-04-21*
## 版本历史
| 版本 | 日期 | 说明 |
|------|------|------|
| V1.0 | 2026-04-17 | 初始版本:学员管理、问题记录、方案生成 |
| V1.1 | 2026-04-17 | 添加用户登录认证系统 |
| V1.2 | 2026-04-18 | 添加用户管理、角色权限、班级管理 |
| V1.2.0 | 2026-04-23 | 问题迁移到数据库;URL导航改造;侧边栏统一 |
---
*最后更新:2026-04-23*
+20 -2
View File
@@ -11,6 +11,9 @@
```
base.html (基础模板)
├── index.html (学员管理)
├── home.html (默认首页)
├── student.html (学员详情)
├── plan_edit.html (方案编辑)
├── settings.html (问题配置)
├── classes.html (班级管理)
├── users.html (用户管理)
@@ -218,8 +221,11 @@ base.html 已包含修改密码弹窗 HTML 和 JS。各页面不需要重复定
```
app/templates/
├── base.html # 基础模板(核心)
├── base.html # 基础模板(核心,统一侧边栏
├── index.html # 学员管理
├── home.html # 默认首页(统计信息)
├── student.html # 学员详情(URL导航)
├── plan_edit.html # 方案编辑(URL导航)
├── settings.html # 问题配置
├── classes.html # 班级管理
├── users.html # 用户管理
@@ -232,8 +238,20 @@ app/templates/
> 注意:`login.html`、`setup.html`、`wechat_card.html` 是独立页面,不继承 base.html。
## 7. 更新日志
## 7. URL 导航模式
系统支持两种导航模式:
| 模式 | 说明 | 示例 |
|------|------|------|
| SPA 模式 | 点击学员卡片弹窗查看详情 | 原 index.html 模式 |
| URL 模式 | 通过 URL 直接访问 | `/student/<id>`, `/plan/<id>/edit` |
推荐使用 URL 模式,便于分享和书签。
## 8. 更新日志
| 日期 | 版本 | 变更内容 |
|------|------|----------|
| 2026-04-21 | v1.0 | 初始文档,定义 base.html 模板继承模式 |
| 2026-04-23 | v1.1 | 添加 URL 导航模式说明;新增 home.html, student.html, plan_edit.html |
+22 -6
View File
@@ -10,6 +10,22 @@
## 数据表
### 0. Problem (问题定义)
系统预定义的15种常见钢琴学习问题。
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Integer | 主键,自增 |
| code | String(50) | 问题编号,如 "05_掌关节支撑差" |
| name | String(100) | 问题名称,如 "掌关节支撑差" |
| category | String(20) | 分类:技术类/认知类/节奏类/习惯类/综合类 |
| created_at | DateTime | 创建时间 |
> ⚠️ 问题数据已从文件系统迁移到数据库。student_problems 表通过 `problem_id` 外键关联到此表。
---
### 1. User (用户)
系统用户,用于登录认证和权限管理。
@@ -58,12 +74,13 @@
|------|------|------|
| id | Integer | 主键,自增 |
| student_id | Integer | 外键,关联 Student |
| problem_id | String(50) | 问题编号,如 "01_手小" |
| problem_name | String(100) | 问题名称,如 "手小" |
| problem_id | Integer | 外键,关联 Problem.id |
| severity | String(10) | 严重程度:轻微/中等/严重 |
| level | String(20) | 级别:启蒙/入门/进阶/熟练/精通 |
| created_at | DateTime | 创建时间 |
> ⚠️ `problem_id` 现为数字外键,关联 `Problem.id`。通过 `student_problem.problem` 关系获取问题名称。
---
### 4. Class (班级)
@@ -139,8 +156,7 @@
├──────────────────┤ │
│ id │ │
│ student_id ─────┘ │
│ problem_id │
│ problem_name │
│ problem_id │◄─┼──► Problem
│ severity │
│ level │
│ created_at │
@@ -173,8 +189,8 @@ print(user.role) # "admin" or "user"
```python
student = Student.query.get(1)
for problem in student.problems:
print(problem.problem_name, problem.severity, problem.level)
for sp in student.problems:
print(sp.problem.name, sp.problem.code, sp.severity, sp.level)
```
### 查询班级及其学员
+9 -10
View File
@@ -28,8 +28,11 @@
│ │ └── pdf_generator.py # PDF生成器
│ │
│ └── templates/ # 前端模板
│ ├── base.html # 基础模板(所有页面继承)
│ ├── base.html # 基础模板(所有页面继承,统一侧边栏
│ ├── index.html # 学员管理页面(继承base)
│ ├── home.html # 默认首页(显示统计信息)
│ ├── student.html # 学员详情页(URL导航)
│ ├── plan_edit.html # 方案编辑页(URL导航)
│ ├── settings.html # 问题配置页面(继承base)
│ ├── login.html # 登录页面(独立)
│ ├── setup.html # 初始设置页面(独立)
@@ -47,12 +50,6 @@
├── config/ # 配置目录(运行时创建)
│ └── api_config.json # API配置文件
├── 个性化方案/ # 练习方案内容
│ └── 针对性练习(拆分为单独文件)/
│ ├── 01_手小.md
│ ├── 02_识谱慢.md
│ └── ...
├── run.py # 应用入口
├── run.bat # 启动脚本
├── requirements.txt # Python依赖
@@ -96,8 +93,9 @@ def create_app():
数据库模型定义:
- `User` - 用户(登录认证、权限管理)
- `Student` - 学员
- `StudentProblem` - 问题记录
- `Class` - 班级(新增
- `StudentProblem` - 问题记录(关联 Problem 表)
- `Problem` - 问题定义(15种预定义问题,已从文件迁移到数据库
- `Class` - 班级
- `PracticePlan` - 练习方案
---
@@ -276,4 +274,5 @@ generate_pdf(plan_id, student_name, content, output_dir)
|------|------|------|
| V1.0 | 2026-04-17 | 初始版本:学员管理、问题记录、方案生成 |
| V1.1 | 2026-04-17 | 添加用户登录认证系统 |
| V1.2 | 2026-04-18 | 添加用户管理、角色权限、班级管理 |
| V1.2 | 2026-04-18 | 添加用户管理、角色权限、班级管理 |
| V1.2.0 | 2026-04-23 | 问题迁移到数据库;URL导航改造;侧边栏统一 |