Files
hmo 04db423416 Initial commit: skills library
- 70 skills with code and documentation
- Add .gitignore (ignore __pycache__, output/, temp/, venv/)
- Clean up test intermediates and caches
2026-04-26 19:27:40 +08:00

472 lines
13 KiB
Markdown
Raw Permalink 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.
---
name: piano-practice-sys
description: 钢琴练习方案生成系统的运维技能。用于系统的开发,维护、部署、数据备份恢复等操作。当用户提到钢琴方案系统的任何运维工作(查看数据、修改配置、备份恢复、部署上线等)时使用此技能。
---
# 钢琴练习方案生成系统 - 运维技能
> 版本:v1.3 | 更新日期:2026-04-26
> **核心原则:不删除,只备份后新增/替换**
> **脚本优先原则:脚本报错 → 修复脚本,而非绕过脚本**
## 系统信息
| 项目 | 值 |
|------|-----|
| **项目路径** | `D:\F\NewI\opencode\daily-workspace\projects\青年钢琴集体课\练习方案系统` |
| **本地启动** | 双击 `run.bat` |
| **本地访问** | http://127.0.0.1:5001 |
| **生产环境** | https://piano.yoin.fun |
| **生产服务器** | 47.115.32.206 (新) |
| **旧服务器** | 47.106.65.108 (仅保留 Gitea/FRP) |
| **数据库** | SQLite (`data/piano_plans.db`) |
| **备份目录** | `/opt/piano-plan/backups/` |
## 登录凭据
**必须先登录才能调用API**
```python
import requests
s = requests.Session()
s.post('https://piano.yoin.fun/api/login', json={
'username': 'hmo',
'password': 'Dm19000o1st!'
})
# 后续请求用这个session
```
---
## 部署原则(铁律)
| 操作 | 允许? | 说明 |
|------|--------|------|
| 删除容器 | ❌ 禁止 | 停止即可,容器配置是资产 |
| 删除 volume | ❌ 禁止 | 数据资产,不可恢复 |
| 删除 host 文件 | ❌ 禁止 | 先备份到 `/tmp/backup_YYYYMMDD/` |
| 覆盖文件 | ⚠️ 先备份 | 任何覆盖操作前必须先备份 |
| 停止容器 | ✅ 允许 | stop 是安全的 |
| 启动新容器 | ✅ 允许 | 配合正确的挂载配置 |
### 脚本优先原则(铁律)
> **当脚本执行失败时:修复脚本,而非绕过脚本。**
| 错误行为 | 正确行为 |
|---------|---------|
| 脚本报错 → `docker rm` 手动清理 | 脚本报错 → 查看日志 → 修复脚本问题 → 重跑脚本 |
| 挂载丢失 → 手动指定新挂载 | 挂载丢失 → 更新脚本的挂载配置 → 重跑脚本 |
| 镜像加载失败 → `docker rmi` 清理 | 镜像加载失败 → 检查错误 → 重跑脚本 |
| 容器启动失败 → `docker rm` 重来 | 容器启动失败 → 查看 `docker logs` → 修复配置 → 重跑脚本 |
---
## 生产环境部署
### 架构
```
用户 → https://piano.yoin.fun → Nginx (容器) → piano容器:5001
```
### 关键路径
| 类型 | 宿主机/源 | 容器内路径 | 说明 |
|------|-----------|------------|------|
| 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 配置 |
### 部署步骤(使用脚本!)
**重要:遵循 DEPLOYMENT_SOP.md 的规范!**
#### 3.1 本地构建
```powershell
# 1. 启动 Docker DesktopWindows
Start-Process "C:\Program Files\Docker\Docker\Docker Desktop.exe"
# 2. 等待 Docker 就绪
docker version # 看到 Server: Docker Desktop 即为就绪
# 3. 进入项目目录
cd "D:\F\NewI\opencode\daily-workspace\projects\青年钢琴集体课\练习方案系统"
# 4. 构建 Docker 镜像
docker build -t piano-plan:latest .
# 5. 保存镜像为 tar 文件
docker save piano-plan:latest -o piano-plan.tar
```
#### 3.2 上传到服务器
```powershell
# 6. 上传到服务器临时目录
scp -i ~/.ssh/id_rsa piano-plan.tar root@47.115.32.206:/opt/piano-plan/
```
#### 3.3 服务器部署(使用脚本!)
```bash
# 7. SSH 到服务器
ssh -i ~/.ssh/id_rsa root@47.115.32.206
# 8. 使用自动化部署脚本(会自动完成所有步骤并验证)
bash /opt/piano-plan/deploy.sh /opt/piano-plan/piano-plan.tar
```
**脚本会自动完成**
- 检查镜像文件
- 停止并删除旧容器
- 加载新镜像
- 备份数据库
- 启动新容器(正确的挂载配置)
- 验证部署
### 验证清单(部署完成后必填)
```
[ ] 容器状态:running
[ ] 服务响应:HTTP 200/302
[ ] 问题文件数量:15个 md 文件
[ ] 数据库记录:users, students, classes, student_problems, practice_plans 完整
[ ] templates 表存在且包含 AI提示词模板、报告导出模板
[ ] API 配置:provider, model, api_key 正确
[ ] 功能验证:能生成练习方案
```
---
## 证书管理
```bash
# 申请Let's Encrypt证书(停止nginx后执行)
certbot certonly --standalone -d piano.yoin.fun
# 证书位置
/etc/letsencrypt/live/piano.yoin.fun/
# 复制证书到nginx容器内
docker cp /etc/letsencrypt/live/piano.yoin.fun/cert.pem nginx_server:/etc/letsencrypt/live/piano.yoin.fun/
docker cp /etc/letsencrypt/live/piano.yoin.fun/chain.pem nginx_server:/etc/letsencrypt/live/piano.yoin.fun/
docker cp /etc/letsencrypt/live/piano.yoin.fun/fullchain.pem nginx_server:/etc/letsencrypt/live/piano.yoin.fun/
docker cp /etc/letsencrypt/live/piano.yoin.fun/privkey.pem nginx_server:/etc/letsencrypt/live/piano.yoin.fun/
```
### Nginx配置
```nginx
# /etc/nginx/conf.d/piano.yoin.fun.conf
server {
server_name piano.yoin.fun;
location / {
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;
}
# SSE (Server-Sent Events) 支持 - 生成方案进度需要
location /api/generate-plan {
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
# 检查容器状态
docker ps -a | grep piano
# 查看容器日志
docker logs piano-plan --tail 50
# 进入容器
docker exec -it piano-plan sh
# 检查挂载
docker inspect piano-plan --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{end}}'
# 检查问题文件
docker exec piano-plan ls /app/个性化方案/
# 重启nginx
docker exec nginx_server nginx -s reload
```
---
## 回滚流程
### 快速回滚(推荐)
```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
```
### 从备份恢复
```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 start piano-plan
```
---
## 数据保护规范
### 必须保护的数据(绝对不删除)
| 数据类型 | 存储位置 | 说明 |
|----------|----------|------|
| 用户数据 | 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 表 |
| 问题文件 | /opt/piano-plan/个性化方案 | 15个md文件 |
### 备份操作
```bash
# 备份数据库(volume
docker cp piano-plan:/app/data/piano_plans.db /opt/piano-plan/backups/piano_plans.db.$(date +%Y%m%d)
# 备份 API 配置
cp /opt/piano-plan/config/api_config.json /opt/piano-plan/backups/
# 列出所有备份
ls -la /opt/piano-plan/backups/
```
---
## 数据同步
### 生产环境数据导出
```python
# 在服务器容器内执行
import sqlite3
conn = sqlite3.connect('/app/data/piano_plans.db')
c = conn.cursor()
# 导出用户
for row in c.execute("SELECT * FROM users"):
print(row)
# 导出班级
for row in c.execute("SELECT * FROM classes"):
print(row)
# 导出学生
for row in c.execute("SELECT * FROM students"):
print(row)
conn.close()
```
### 本地数据导入生产
```python
# 创建同步脚本 sync_data.py
import sqlite3
users = [
('hyh', 'hash值', 'user', '时间戳'),
# ...
]
classes = [
('26春(1)', '', 1, '时间戳'),
# ...
]
students = [
('张三', '手机号', '微信昵称', '30分钟', '', 1, '时间戳'),
# ...
]
conn = sqlite3.connect('/app/data/piano_plans.db')
c = conn.cursor()
for u in users:
c.execute("INSERT INTO users (username, password_hash, role, created_at) VALUES (?, ?, ?, ?)", u)
for cls in classes:
c.execute("INSERT INTO classes (name, description, active, created_at) VALUES (?, ?, ?, ?)", cls)
for s in students:
c.execute("INSERT INTO students (name, phone, wechat_nickname, practice_time, notes, class_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)", s)
conn.commit()
conn.close()
```
上传到服务器执行:
```bash
docker cp sync_data.py piano-plan:/tmp/
docker exec piano-plan python /tmp/sync_data.py
```
---
## 从生产环境同步数据到本地开发
### 警告
- **生产环境数据只读**:不得修改生产环境数据
- **本地会被覆盖**:执行后本地数据会被生产数据完全替代
- **先备份本地**:每次同步前自动备份
### 同步步骤
#### 1. 同步数据库(生产→本地)
```powershell
# 1.1 备份本地数据库(手动)
Copy-Item "data\piano_plans.db" "data\piano_plans.db.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
# 1.2 从生产容器复制数据库到服务器
ssh -i ~/.ssh/id_rsa root@47.115.32.206 "docker cp piano-plan:/app/data/piano_plans.db /tmp/piano_plans_prod.db"
# 1.3 下载到本地
scp -i ~/.ssh/id_rsa root@47.115.32.206:/tmp/piano_plans_prod.db data\
# 1.4 覆盖本地数据库
Copy-Item "data\piano_plans_prod.db" "data\piano_plans.db" -Force
# 1.5 添加本地特有的字段(如果生产数据库没有)
# 本地开发环境可能有 template_id, is_typical 等生产没有的字段
```
```python
# add_local_cols.py - 添加本地特有字段
import sqlite3
conn = sqlite3.connect('data/piano_plans.db')
cursor = conn.cursor()
# 检查并添加 template_id
cursor.execute("PRAGMA table_info(practice_plans)")
columns = [col[1] for col in cursor.fetchall()]
if 'template_id' not in columns:
cursor.execute("ALTER TABLE practice_plans ADD COLUMN template_id INTEGER")
if 'is_typical' not in columns:
cursor.execute("ALTER TABLE practice_plans ADD COLUMN is_typical INTEGER DEFAULT 0")
conn.commit()
conn.close()
```
#### 2. 同步问题文件(生产→本地)
```powershell
# 2.1 确保本地目录存在
New-Item -ItemType Directory -Path "D:\F\NewI\opencode\daily-workspace\个性化方案\针对性练习(拆分为单独文件)" -Force
# 2.2 从服务器同步问题文件
scp -i ~/.ssh/id_rsa -r root@47.106.65.108:/opt/piano-plan/个性化方案/* "D:\F\NewI\opencode\daily-workspace\个性化方案\针对性练习(拆分为单独文件)\"
```
### 一键同步脚本
```python
# sync_prod_to_local.py
import sqlite3
import shutil
import os
from datetime import datetime
LOCAL_DB = 'data/piano_plans.db'
PROD_DB = 'data/piano_plans_prod.db'
def sync_from_production():
# 1. 备份本地
backup = f"{LOCAL_DB}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
shutil.copy2(LOCAL_DB, backup)
print(f"本地已备份: {backup}")
# 2. 覆盖本地数据库
shutil.copy2(PROD_DB, LOCAL_DB)
print("生产数据库已覆盖本地")
# 3. 添加本地特有字段
conn = sqlite3.connect(LOCAL_DB)
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(practice_plans)")
columns = [col[1] for col in cursor.fetchall()]
if 'template_id' not in columns:
cursor.execute("ALTER TABLE practice_plans ADD COLUMN template_id INTEGER")
print("添加 template_id 字段")
if 'is_typical' not in columns:
cursor.execute("ALTER TABLE practice_plans ADD COLUMN is_typical INTEGER DEFAULT 0")
print("添加 is_typical 字段")
conn.commit()
conn.close()
print("完成")
if __name__ == '__main__':
sync_from_production()
```
### 验证同步结果
```python
# verify_sync.py
import sqlite3
conn = sqlite3.connect('data/piano_plans.db')
cursor = conn.cursor()
for table in ['classes', 'students', 'student_problems', 'practice_plans']:
cursor.execute(f"SELECT COUNT(*) FROM {table}")
print(f" {table}: {cursor.fetchone()[0]}")
conn.close()
```
---
## 快速命令参考
| 操作 | 命令 |
|------|------|
| 生产环境SSH | `ssh -i ~/.ssh/id_rsa root@47.115.32.206` |
| 旧服务器SSH | `ssh -i ~/.ssh/id_rsa root@47.106.65.108` |
| 查看容器 | `docker ps -a | grep piano` |
| 查看日志 | `docker logs piano-plan --tail 50` |
| 重启容器 | `docker restart piano-plan` |
| 停止容器 | `docker stop piano-plan` |
| 查看证书 | `docker exec nginx_server ls /etc/letsencrypt/live/` |
| 测试HTTPS | `curl https://piano.yoin.fun` |
## 相关文档
- [完整API文档](../../projects/青年钢琴集体课/练习方案系统/docs/API.md)
- [项目README](../../projects/青年钢琴集体课/练习方案系统/README.md)
- [部署SOP(必读)](../../projects/青年钢琴集体课/练习方案系统/docs/DEPLOYMENT_SOP.md)