Files
piano-plan/docs/DEPLOYMENT_SOP.md
T
2026-04-24 10:54:06 +08:00

379 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 钢琴练习方案系统 - 部署 SOP
> 版本:v1.3
> 日期:2026-04-24
> 核心原则:**不删除,只备份后新增/替换**
---
## 一、部署原则(铁律)
| 操作 | 允许? | 说明 |
|------|--------|------|
| 删除容器 | ❌ 禁止 | 停止即可,容器配置是资产 |
| 删除 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\青年钢琴集体课\练习方案系统"
# 1. 构建镜像
docker build -t piano-plan:v1.3.0 .
# 2. 保存镜像
docker save piano-plan:v1.3.0 -o releases/v1.3.0/toRelease/program/piano-plan.tar
```
---
## 四、服务器部署步骤
### 4.1 SSH 到服务器
```bash
ssh -i ~/.ssh/id_rsa root@47.106.65.108
```
### 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.106.65.108:/opt/piano-plan/releases/v1.3.0/toRelease/
```
### 4.4 服务器执行部署
```bash
# SSH 再次连接
ssh -i ~/.ssh/id_rsa root@47.106.65.108
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.106.65.108` |
| 容器名 | 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 配置特定的代理设置,参考之前文档
---
## 十、检查清单(部署完成后必填)
```
[ ] 容器状态:running
[ ] 服务响应:HTTP 200/302
[ ] 数据库表完整:users, students, classes, student_problems, practice_plans, problems, goals, goal_relations, student_goals
[ ] 目标管理功能正常:创建目标、分配目标、评估目标
[ ] API 配置正确
[ ] 功能验证:能生成练习方案
```
---
## 十一、版本历史
| 版本 | 日期 | 变更 |
|------|------|------|
| 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-24
> **更新原因**:v1.3.1 代码更新;DRY 规范入撰;Fragment 复用方案;班级批量分配目标