# 钢琴练习方案系统 - 部署 SOP > 版本:v1.5.1 > 日期:2026-04-28 > 核心原则:**不删除,只备份后新增/替换** --- ## 重要更新(v1.5.1) ### ⚠️ 问题文件已迁移到数据库 **历史**:`/app/个性化方案/*.md`(15个问题文件) **现状**:所有问题数据已迁移到 `problems` 表,不再需要挂载问题文件目录。 **影响**: - 部署时不再检查问题文件数量 - 不再需要 `/opt/piano-plan/个性化方案` 挂载 - 验证清单中"问题文件数量"检查已废弃 ### ⚠️ Docker 构建需要代理 **本地代理端口**:`15000` 构建命令: ```powershell $env:HTTP_PROXY="http://127.0.0.1:15000" $env:HTTPS_PROXY="http://127.0.0.1:15000" docker build -t piano-plan:latest . ``` ### ⚠️ PDF 水印配置 PDF 水印文本在 API 设置页面(`/api-settings`)中配置,存储在 `api_config.json` 的 `watermark_text` 字段。 - 留空 = 不显示水印 - 填写文字 = 每页中央斜向显示该文字(56pt,25%透明度) ### ⚠️ 中文字体 Docker 镜像已内置 `fonts-wqy-microhei`,无需额外挂载。 --- ## 一、部署原则(铁律) | 操作 | 允许? | 说明 | |------|--------|------| | 删除容器 | ❌ 禁止 | 停止即可,容器配置是资产 | | 删除 volume | ❌ 禁止 | 数据资产,不可恢复 | | 删除 host 文件 | ❌ 禁止 | 先备份到 `/tmp/backup_YYYYMMDD/` | | 覆盖文件 | ⚠️ 先备份 | 任何覆盖操作前必须先备份 | | 停止容器 | ✅ 允许 | stop 是安全的 | | 启动新容器 | ✅ 允许 | 配合正确的挂载配置 | ### 脚本优先原则(铁律) > **使脚本执行失败时:修复脚本,而非绕过脚本。** ### 双备份先行原则(铁律 - 新增) > **在双备份(本地 `releases/v{version}/bk/` + 服务器 `/opt/piano-plan/releases/v{version}/bk/`)全部完成之前,禁止执行任何实质性的部署操作。** 以下操作在双备份完成前**严禁**执行: - ❌ 更新/修改生产数据库 schema - ❌ 停止旧 Docker 容器 - ❌ 启动新 Docker 容器 - ❌ 上传/覆盖生产环境数据 - ❌ 加载新镜像到服务器 正确流程: 1. ✅ 完成本地 `releases/v{version}/bk/` 备份 2. ✅ 完成服务器 `/opt/piano-plan/releases/v{version}/bk/` 备份 3. ✅ **验证两份备份均存在且完整** 4. ✅ **方可执行部署操作** | 错误行为 | 正确行为 | |---------|---------| | 脚本报错 → `docker rm` 手动清理 | 脚本报错 → 查看日志 → 修复脚本问题 → 重跑脚本 | | 挂载丢失 → 手动指定新挂载 | 挂载丢失 → 更新脚本的挂载配置 → 重跑脚本 | | 镜像加载失败 → `docker rmi` 清理 | 镜像加载失败 → 检查错误 → 重跑脚本 | | 容器启动失败 → `docker rm` 重来 | 容器启动失败 → 查看 `docker logs` → 修复配置 → 重跑脚本 | **一旦开始用脚本部署,就要坚持用到底,中途放弃脚本去做手动操作,等于打开了破坏系统的潘多拉魔盒。** --- ## 二、部署前检查清单 ``` [ ] 确认本地代码已验证通过 [ ] 确认无未提交的代码 [ ] 确认获得用户的明确同意(用户说"部署吧"或"可以部署") [ ] 确认需要保留的挂载点列表(见下方) [ ] 确认 Docker Desktop 已启动(Windows) ``` ### 必须保留的挂载点 | 类型 | 源 | 容器内 | 说明 | |------|-----|--------|------| | Volume | `piano-plan-data` | `/app/data` | SQLite 数据库 | | Volume | `piano-plan-output` | `/app/output` | PDF 输出 | | Bind Mount | `/opt/piano-plan/config` | `/app/config` | API 配置 | --- ## 三、本地发布包准备 ### 3.1 版本目录结构 每个版本发布前,在本地 `releases/` 目录下创建: ``` releases/ └── v1.3.0/ ├── bk/ # 上次生产环境备份(来自服务器) │ ├── piano_plans.db # 上次生产数据库 │ ├── docker/ # 上次版本的 docker 镜像 tar │ │ └── piano-plan.tar │ └── config/ # 上次版本的配置备份 │ └── api_config.json └── toRelease/ # 本次发布文件 ├── program/ # 本次发布的程序包 │ └── piano-plan.tar ├── schema.sql # 数据库 schema(仅结构,不含数据) ├── scripts/ # 发布脚本 │ └── deploy.sh └── config/ # 本次配置(如有变更) └── api_config.json ``` ### 3.2 生成 schema.sql(仅结构) ```powershell cd "D:\F\NewI\opencode\daily-workspace\projects\青年钢琴集体课\练习方案系统" # 进入版本目录 mkdir -p releases/v1.3.0/toRelease/scripts # 导出数据库结构(不含数据) docker exec piano-plan-for-export sqlite3 /app/data/piano_plans.db ".schema" > releases/v1.3.0/toRelease/schema.sql # 或使用 python python -c " import sqlite3 conn = sqlite3.connect('data/piano_plans.db') with open('releases/v1.3.0/toRelease/schema.sql', 'w', encoding='utf-8') as f: for line in conn.iterdump(): if 'CREATE' in line or 'CREATE' in line.upper(): f.write(line + '\n') " ``` ### 3.3 构建 Docker 镜像并打包 ```powershell cd "D:\F\NewI\opencode\daily-workspace\projects\青年钢琴集体课\练习方案系统" # 0. 配置代理(必须!) $env:HTTP_PROXY="http://127.0.0.1:15000" $env:HTTPS_PROXY="http://127.0.0.1:15000" # 1. 构建镜像 docker build -t piano-plan:v1.4.0 . # 2. 保存镜像 docker save piano-plan:v1.4.0 -o releases/v1.4.0/toRelease/program/piano-plan.tar ``` --- ## 四、服务器部署步骤 ### 4.1 SSH 到服务器 ```bash ssh -i ~/.ssh/id_rsa root@47.115.32.206 ``` ### 4.2 创建版本目录并备份上次生产环境 ```bash # 创建版本目录 mkdir -p /opt/piano-plan/releases/v1.3.0/bk mkdir -p /opt/piano-plan/releases/v1.3.0/toRelease # 备份当前生产环境到 bk/ # 1. 备份数据库 docker cp piano-plan:/app/data/piano_plans.db /opt/piano-plan/releases/v1.3.0/bk/piano_plans.db.$(date +%Y%m%d) # 2. 备份 docker 镜像(如果存在) docker save piano-plan:$(docker inspect piano-plan --format '{{.Config.Image}}' | sed 's/piano-plan://') -o /opt/piano-plan/releases/v1.3.0/bk/docker/piano-plan.tar 2>/dev/null || true # 3. 备份配置 cp -r /opt/piano-plan/config /opt/piano-plan/releases/v1.3.0/bk/config.bak.$(date +%Y%m%d) ``` ### 4.3 上传本次发布文件 ```bash # 在本地执行上传 exit # 先退出服务器 SSH # 上传发布包 scp -i ~/.ssh/id_rsa -r releases/v1.3.0/toRelease/* root@47.115.32.206:/opt/piano-plan/releases/v1.3.0/toRelease/ ``` ### 4.4 服务器执行部署 ```bash # SSH 再次连接 ssh -i ~/.ssh/id_rsa root@47.115.32.206 cd /opt/piano-plan/releases/v1.3.0 # 1. 停止旧容器 docker stop piano-plan docker rm piano-plan # 2. 加载新镜像 docker load -i toRelease/program/piano-plan.tar # 3. 执行数据库迁移(仅 schema,不覆盖数据) # 方法A:使用 sqlite3 直接执行 schema(如果有 ALTER TABLE 新增字段) docker cp toRelease/schema.sql piano-plan:/tmp/schema.sql docker exec piano-plan sqlite3 /app/data/piano_plans.db < /tmp/schema.sql # 方法B:如果 schema 变更较大,先备份再重建表结构(不丢失数据) # 具体看本次变更内容决定 # 4. 启动新容器 docker run -d \ --name piano-plan \ -p 5001:5001 \ --restart unless-stopped \ -e FLASK_ENV=production \ -v piano-plan-data:/app/data \ -v piano-plan-output:/app/output \ -v /opt/piano-plan/config:/app/config \ piano-plan:v1.3.0 ``` ### 4.5 验证 ```bash # 检查容器状态 docker ps --filter name=piano-plan # 检查日志 docker logs piano-plan --tail 30 # 验证数据库表结构 docker exec piano-plan sqlite3 /app/data/piano_plans.db ".tables" # 验证服务 curl -I http://localhost:5001/ ``` --- ## 五、数据保护规范 ### 5.1 必须保护的数据(绝对不删除) | 数据类型 | 存储位置 | 说明 | |----------|----------|------| | 用户数据 | piano-plan-data:/app/data | users 表 | | 学员数据 | piano-plan-data:/app/data | students, student_problems 表 | | 班级数据 | piano-plan-data:/app/data | classes 表 | | 练习方案 | piano-plan-data:/app/data | practice_plans 表 | | 目标数据 | piano-plan-data:/app/data | goals, goal_relations, student_goals 表 | | 问题数据 | piano-plan-data:/app/data | problems 表 | ### 5.2 本次发布 schema 变更 > 本次发布涉及新增目标管理模块,需要执行数据库迁移: > - 新增 `goals` 表 > - 新增 `goal_relations` 表 > - 新增 `student_goals` 表 > - 修改 `problems` 表:更新分类体系 > - 修改 `students` 表:无变更 ### 5.3 双备份保障 | 备份位置 | 内容 | 说明 | |----------|------|------| | `/opt/piano-plan/releases/v1.3.0/bk/` | 上次生产环境完整备份 | 版本回退用 | | `/opt/piano-plan/releases/v1.3.1/bk/` | 本次发布前备份 | 下次发布时自动创建 | --- ## 六、回滚流程 ### 6.1 回滚到上一版本 ```bash # 停止当前容器 docker stop piano-plan docker rm piano-plan # 使用 bk 中的旧镜像(如果有) docker load -i /opt/piano-plan/releases/v1.3.0/bk/docker/piano-plan.tar # 启动旧容器 docker run -d \ --name piano-plan \ -p 5001:5001 \ --restart unless-stopped \ -e FLASK_ENV=production \ -v piano-plan-data:/app/data \ -v piano-plan-output:/app/output \ -v /opt/piano-plan/config:/app/config \ piano-plan:旧版本标签 ``` ### 6.2 数据库紧急回滚 ```bash # 停止容器 docker stop piano-plan # 从 bk 恢复数据库 cp /opt/piano-plan/releases/v1.3.0/bk/piano_plans.db.上次的日期 piano-plan:/app/data/piano_plans.db # 重启 docker start piano-plan ``` --- ## 七、服务信息 | 项目 | 值 | |------|-----| | 生产地址 | https://piano.yoin.fun | | SSH | `ssh -i ~/.ssh/id_rsa root@47.115.32.206` | | 容器名 | piano-plan | | 端口 | 5001 | | 数据库位置 | piano-plan-data volume | | API配置位置 | /opt/piano-plan/config | | 发布包位置 | /opt/piano-plan/releases/ | ### Volume 列表 | Volume | 容器内路径 | 说明 | |--------|------------|------| | piano-plan-data | /app/data | SQLite 数据库 | | piano-plan-output | /app/output | PDF 导出 | ### Bind Mount 列表 | 源 | 容器内路径 | 说明 | |-----|------------|------| | /opt/piano-plan/config | /app/config | API 配置 | --- ## 八、API 配置说明 ### 8.1 支持的 Provider | Provider | Endpoint | 模型 | |----------|----------|------| | minimax | https://api.minimaxi.com/anthropic/v1 | MiniMax-M2.7-highspeed | | volcengine | https://ark.cn-beijing.volces.com/api/coding/v3 | doubao-seed-2.0-pro | | deepseek | https://api.deepseek.com | deepseek-chat | ### 8.2 API 配置存储 - 配置存储在 `/opt/piano-plan/config/api_config.json` - 每个 provider 的 key 存储在 `api_keys` 映射中 - 切换 provider 时自动使用对应的 key --- ## 九、常见问题 ### Q: 数据库 schema 迁移失败? A: 检查 sqlite3 版本兼容性,确保 .schema 导出的 SQL 兼容目标数据库版本 ### Q: 容器无法启动? A: 检查日志 `docker logs piano-plan`,常见原因:端口被占用、volume 权限问题 ### Q: 目标管理功能报错? A: 检查数据库是否成功执行了 schema 迁移,新增了 goals, goal_relations, student_goals 表 ### Q: SSE 不完整? A: nginx 需要为 SSE 配置特定的代理设置,参考之前文档 ### Q: v1.3.2 部署后评估功能不工作? A: 检查是否执行了 migrate_goals_v3.py 迁移脚本,该脚本创建 student_goal_evaluations 表 --- ## 十、检查清单(部署完成后必填) ``` [ ] 容器状态:running [ ] 服务响应:HTTP 200/302 [ ] 数据库表完整:users, students, classes, student_problems, practice_plans, templates, problems, goals, goal_relations, student_goals, student_goal_evaluations [ ] practice_plans 表有新字段:created_by, updated_by, updated_at, template_id, is_typical [ ] 目标管理功能正常:创建目标、分配目标、评估目标 [ ] 时间线正常显示阶段评估和最终评估 [ ] API 配置正确 [ ] 功能验证:能生成练习方案 [ ] 方案列表支持删除 [ ] 学员列表"暂无方案/问题"样式正常 [ ] PDF 水印功能正常(配置后导出可见) [ ] 数据统计页面正常显示 [ ] 导出PDF时 {student_goals} 正常显示学员目标 ``` --- ## 十一、版本历史 | 版本 | 日期 | 变更 | |------|------|------| | v1.5.1 | 2026-04-28 | PDF水印配置保存修复(3处漏改);{student_goals}占位符修复;移除目标导出时的"内容:"标签 | | v1.5.0 | 2026-04-27 | 数据统计页面(问题/级别分布可视化);PDF水印配置(可自定义文本);编辑页按钮吸底;侧边栏顺序调整;Linux中文字体路径修复 | | v1.4.0 | 2026-04-27 | 典型方案采纳;推荐方案列表;方案编辑/详情页导航优化(bfcache处理);审计字段完善(created_by/updated_by/updated_at);方案列表支持删除;学员列表"暂无方案/问题"样式统一 | | v1.3.6 | 2026-04-24 | 方案详情导航优化;典型方案开关移至方案详情;方案列表显示问题级别+严重程度 | | v1.3.5 | 2026-04-24 | 班级班主任字段;用户姓名name字段;班级/学员/方案增加"我的"筛选 | | v1.3.4 | 2026-04-24 | 方案编辑按钮;问题增量添加;teachers API公开 | | v1.3.3 | 2026-04-24 | 评估日期编辑;最终评估关联 StudentGoal 同步 | | v1.3.2 | 2026-04-24 | StudentGoal 新增 status 字段;新增 StudentGoalEvaluation 表 | | v1.3.1 | 2026-04-24 | DRY 规范;Fragment 复用方案;班级批量分配目标 | | v1.3 | 2026-04-24 | 目标管理模块:Goal/GoalRelation/StudentGoal;问题分类重构 | | v1.2 | 2026-04-23 | 问题迁移到数据库;移除个性化方案挂载 | | v1.1 | 2026-04-20 | 模板管理;API配置界面 | | v1.0 | 2026-04-17 | 初始版本 | --- > **最后更新**:2026-04-28 > **更新原因**:v1.5.1 补丁;PDF水印保存漏改;{student_goals}占位符修复;清理重复检查清单