730 lines
22 KiB
Markdown
730 lines
22 KiB
Markdown
# 钢琴练习方案系统 - 部署 SOP
|
||
|
||
> 版本:v1.5.6
|
||
> 日期:2026-04-30
|
||
> 核心原则:**不删除,只备份后新增/替换**
|
||
|
||
---
|
||
|
||
## 重要更新(v1.5.6)
|
||
|
||
### 🔒 安全防护:Nginx 限流配置
|
||
|
||
新增 Nginx 限流规则,防止爬虫和恶意请求:
|
||
|
||
**限流规则:**
|
||
|
||
| 路由 | 限流规则 | 说明 |
|
||
|------|----------|------|
|
||
| `/` (全站) | 30请求/秒, burst=50 | 正常用户够用 |
|
||
| `/api/generate-plan` | 10请求/秒, burst=20 | 重点防护(AI生成接口) |
|
||
|
||
**超过限制返回:** `429 Too Many Requests`
|
||
|
||
**配置文件:**
|
||
- 主配置:`/etc/nginx/nginx.conf`(限流zone定义)
|
||
- 站点配置:`/etc/nginx/conf.d/piano.yoin.fun.conf`(限流规则应用)
|
||
|
||
**完整的 nginx.conf 限流部分(http{} 块中添加):**
|
||
```nginx
|
||
# 限流配置 - 防止爬虫和恶意请求
|
||
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
|
||
limit_req_zone $binary_remote_addr zone=general_limit:10m rate=30r/s;
|
||
limit_req_log_level warn;
|
||
limit_req_status 429;
|
||
```
|
||
|
||
**完整的站点配置(piano.yoin.fun.conf):**
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name piano.yoin.fun;
|
||
return 301 https://$server_name$request_uri;
|
||
}
|
||
|
||
server {
|
||
server_name piano.yoin.fun;
|
||
location / {
|
||
limit_req zone=general_limit burst=50 nodelay;
|
||
proxy_pass http://172.17.0.1:5001;
|
||
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_buffering off;
|
||
proxy_cache off;
|
||
tcp_nodelay on;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Connection '';
|
||
}
|
||
location /api/generate-plan {
|
||
limit_req zone=api_limit burst=20 nodelay;
|
||
proxy_pass http://172.17.0.1:5001;
|
||
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_buffering off;
|
||
proxy_cache off;
|
||
tcp_nodelay on;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Connection '';
|
||
proxy_buffers 8 32k;
|
||
proxy_buffer_size 32k;
|
||
proxy_max_temp_file_size 1024m;
|
||
}
|
||
listen 443 ssl;
|
||
ssl_certificate /etc/letsencrypt/live/piano.yoin.fun/fullchain.pem;
|
||
ssl_certificate_key /etc/letsencrypt/live/piano.yoin.fun/privkey.pem;
|
||
}
|
||
```
|
||
|
||
**部署/修改方法:**
|
||
```bash
|
||
# 1. 备份当前配置
|
||
docker exec nginx_server cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
|
||
docker exec nginx_server cp /etc/nginx/conf.d/piano.yoin.fun.conf /etc/nginx/conf.d/piano.yoin.fun.conf.bak
|
||
|
||
# 2. 使用 sed 或直接编辑修改配置
|
||
# 主配置:在 http{} 块中插入限流zone定义
|
||
# 站点配置:在 location{} 块中添加 limit_req 指令
|
||
|
||
# 3. 测试配置
|
||
docker exec nginx_server nginx -t -c /etc/nginx/nginx.conf
|
||
|
||
# 4. 重载nginx
|
||
docker exec nginx_server nginx -s reload
|
||
```
|
||
|
||
**常用命令:**
|
||
```bash
|
||
# 查看限流配置
|
||
docker exec nginx_server cat /etc/nginx/conf.d/piano.yoin.fun.conf | grep limit_req
|
||
|
||
# 检查nginx状态
|
||
docker exec nginx_server nginx -t
|
||
|
||
# 重载配置
|
||
docker exec nginx_server nginx -s reload
|
||
|
||
# 查看nginx日志(限流日志)
|
||
docker exec nginx_server tail -50 /var/log/nginx/error.log
|
||
```
|
||
|
||
---
|
||
|
||
### 💾 数据备份:每日自动备份
|
||
|
||
新增服务器本地每日自动备份机制:
|
||
|
||
**备份配置:**
|
||
- 备份路径:`/opt/backups/piano-db/`
|
||
- 执行时间:每天凌晨 3:00
|
||
- 保留期限:30 天
|
||
- 备份方式:SQLite `.backup` 命令(保证一致性)
|
||
- 服务状态:crond 运行中
|
||
|
||
**备份脚本位置:** `/opt/backups/backup_piano_db.sh`(宿主机,不受容器重建影响)
|
||
|
||
**完整脚本内容:**
|
||
```bash
|
||
#!/bin/bash
|
||
# Piano Plan 数据库每日备份脚本
|
||
# 保留30天
|
||
# 自包含:部署后依然有效,不依赖容器内预存文件
|
||
|
||
BACKUP_DIR="/opt/backups/piano-db"
|
||
RETENTION_DAYS=30
|
||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||
BACKUP_FILE="piano_plans_${TIMESTAMP}.db"
|
||
SCRIPT_FILE="/tmp/backup_piano_$$.py"
|
||
|
||
mkdir -p "$BACKUP_DIR"
|
||
|
||
# 创建临时Python脚本(写在宿主机,然后复制到容器)
|
||
cat > "$SCRIPT_FILE" << 'PYEOF'
|
||
import sqlite3
|
||
conn = sqlite3.connect('/app/data/piano_plans.db')
|
||
backup = sqlite3.connect('/tmp/piano_backup.db')
|
||
conn.backup(backup)
|
||
backup.close()
|
||
conn.close()
|
||
PYEOF
|
||
|
||
# 复制到容器并执行
|
||
docker cp "$SCRIPT_FILE" piano-plan:/tmp/backup_piano.py
|
||
rm -f "$SCRIPT_FILE"
|
||
docker exec piano-plan python3 /tmp/backup_piano.py
|
||
|
||
# 复制备份到宿主机
|
||
docker cp piano-plan:/tmp/piano_backup.db "$BACKUP_DIR/$BACKUP_FILE"
|
||
|
||
# 清理容器内文件
|
||
docker exec piano-plan rm -f /tmp/backup_piano.py /tmp/piano_backup.db
|
||
|
||
# 验证
|
||
if [ -f "$BACKUP_DIR/$BACKUP_FILE" ]; then
|
||
SIZE=$(du -h "$BACKUP_DIR/$BACKUP_FILE" | cut -f1)
|
||
echo "[$(date)] Backup OK: $BACKUP_FILE ($SIZE)"
|
||
|
||
# 删除30天前的备份
|
||
find "$BACKUP_DIR" -name "piano_plans_*.db" -mtime +${RETENTION_DAYS} -delete
|
||
echo "[$(date)] Cleanup done (retained $RETENTION_DAYS days)"
|
||
else
|
||
echo "[$(date)] Backup FAILED"
|
||
exit 1
|
||
fi
|
||
|
||
ls -lh "$BACKUP_DIR"
|
||
```
|
||
|
||
**设置 cron 任务:**
|
||
```bash
|
||
# 添加到 crontab
|
||
(crontab -l 2>/dev/null; echo '0 3 * * * /opt/backups/backup_piano_db.sh >> /opt/backups/backup.log 2>&1') | crontab -
|
||
|
||
# 验证
|
||
crontab -l
|
||
```
|
||
|
||
**常用命令:**
|
||
```bash
|
||
# 查看备份
|
||
ls -lh /opt/backups/piano-db/
|
||
|
||
# 手动执行备份
|
||
/opt/backups/backup_piano_db.sh
|
||
|
||
# 查看 cron 配置
|
||
crontab -l
|
||
|
||
# 删除旧的 cron 任务(如果需要)
|
||
crontab -e # 编辑模式删除对应行
|
||
```
|
||
|
||
**备份恢复:**
|
||
```bash
|
||
# 停止容器
|
||
docker stop piano-plan
|
||
|
||
# 复制备份文件到容器
|
||
docker cp /opt/backups/piano-db/piano_plans_XXXXXXXX_XXXXXX.db piano-plan:/app/data/piano_plans.db
|
||
|
||
# 启动容器
|
||
docker start piano-plan
|
||
```
|
||
|
||
---
|
||
|
||
## 重要更新(v1.5.2)
|
||
|
||
### ✨ 导出预览功能
|
||
|
||
在方案详情页新增「预览」按钮,点击弹出模态框展示套用模板导出后的最终效果(所见即所得)。
|
||
|
||
**功能特点:**
|
||
- 预览内容与 PDF 导出效果一致(字体、标题层级、表格样式)
|
||
- 支持水印预览(如已配置)
|
||
- 内容超出时可滚动查看
|
||
- 支持 `<para alignment="center">` 居中语法(预览和 PDF 导出均支持)
|
||
|
||
**模板居中写法:**
|
||
```html
|
||
<para alignment="center">【{generated_by} 撰写于 {generated_at}】</para>
|
||
```
|
||
|
||
**技术实现:**
|
||
- 后端:`GET /api/plans/<id>/preview` 返回渲染后的 HTML
|
||
- 前端:Bootstrap 模态框 + CSS 镜像 PDF 样式
|
||
- 新增依赖:`markdown` Python 包(Markdown → HTML 转换)
|
||
|
||
---
|
||
|
||
## 重要更新(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 \
|
||
-e TZ=Asia/Shanghai \
|
||
-v piano-plan-data:/app/data \
|
||
-v piano-plan-output:/app/output \
|
||
-v /opt/piano-plan/config:/app/config \
|
||
piano-plan:v1.3.0
|
||
```
|
||
|
||
> ⚠️ **重要**:`TZ=Asia/Shanghai` 必须设置,否则容器内 Python `datetime.now()` 返回 UTC 时间,比服务器时间早 8 小时,导致数据库记录的时间比实际时间晚 8 小时。
|
||
|
||
### 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 \
|
||
-e TZ=Asia/Shanghai \
|
||
-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} 正常显示学员目标
|
||
[ ] 导出预览功能正常:预览按钮、模态框、水印显示
|
||
[ ] 模板支持 <para alignment="center"> 居中语法(预览和PDF均有效)
|
||
```
|
||
|
||
---
|
||
|
||
## 十一、版本历史
|
||
|
||
| 版本 | 日期 | 变更 |
|
||
|------|------|------|
|
||
| v1.5.6 | 2026-04-30 | Nginx限流配置(防爬虫/恶意请求);每日自动备份(30天保留) |
|
||
| v1.5.5 | 2026-04-28 | 修复容器时区(TZ=Asia/Shanghai);学员列表"x个方案"可点击跳转最新方案详情 |
|
||
| v1.5.4 | 2026-04-28 | PDF正文字体12pt、表格11pt |
|
||
| v1.5.3 | 2026-04-28 | PDF行间距修复(7mm→2mm) |
|
||
| v1.5.2 | 2026-04-28 | 导出预览功能(预览按钮+模态框+水印);目标内容换行修复;支持<para alignment="center">居中语法;隐藏MD下载按钮 |
|
||
| 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 | 初始版本 |
|
||
|
||
---
|
||
|
||
## 十二、Ops 运维脚本
|
||
|
||
### 12.1 备份脚本 (scripts/ops/backup_piano_db.sh)
|
||
|
||
**位置**:`scripts/ops/backup_piano_db.sh`(项目目录)
|
||
|
||
**上传到服务器**:
|
||
```bash
|
||
scp -i ~/.ssh/id_rsa scripts/ops/backup_piano_db.sh root@47.115.32.206:/opt/backups/backup_piano_db.sh
|
||
chmod +x /opt/backups/backup_piano_db.sh
|
||
```
|
||
|
||
**设置定时任务**:
|
||
```bash
|
||
ssh -i ~/.ssh/id_rsa root@47.115.32.206
|
||
(crontab -l 2>/dev/null; echo '0 3 * * * /opt/backups/backup_piano_db.sh >> /opt/backups/backup.log 2>&1') | crontab -
|
||
```
|
||
|
||
**验证设置**:
|
||
```bash
|
||
crontab -l
|
||
ls -lh /opt/backups/piano-db/
|
||
```
|
||
|
||
### 12.2 脚本特性
|
||
|
||
- **自包含**:不依赖容器内预存文件
|
||
- **幂等性**:可重复执行
|
||
- **验证**:备份后自动验证文件存在
|
||
- **清理**:自动删除 30 天前的备份
|
||
|
||
### 12.3 恢复备份
|
||
|
||
```bash
|
||
# 1. 停止容器
|
||
docker stop piano-plan
|
||
|
||
# 2. 列出可用备份
|
||
ls -lh /opt/backups/piano-db/
|
||
|
||
# 3. 复制备份到容器
|
||
docker cp /opt/backups/piano-db/piano_plans_XXXXXXXX_XXXXXX.db piano-plan:/app/data/piano_plans.db
|
||
|
||
# 4. 启动容器
|
||
docker start piano-plan
|
||
```
|
||
|
||
---
|
||
|
||
> **最后更新**:2026-04-30
|
||
> **更新原因**:v1.5.6 - Nginx限流配置;每日自动备份
|