feat: rename system to 有音个性化教学系统; update docs
This commit is contained in:
@@ -164,8 +164,8 @@ piano-plan/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
> **版本**:v1.5.1
|
> **版本**:v1.5.6
|
||||||
> **创建时间**:2026-04-17
|
> **创建时间**:2026-04-17
|
||||||
> **最后更新**:2026-04-28
|
> **最后更新**:2026-04-30
|
||||||
>
|
>
|
||||||
> **重要更新**:v1.5.1 - PDF水印配置保存修复;{student_goals}占位符修复
|
> **重要更新**:v1.5.6 - Nginx限流(防爬虫);每日自动备份(30天)
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ def generate_pdf(plan_id, student_name, content, output_dir, rendered_report=Non
|
|||||||
pdf.add_table(table_data)
|
pdf.add_table(table_data)
|
||||||
else:
|
else:
|
||||||
# 使用结构化内容
|
# 使用结构化内容
|
||||||
pdf.add_title(f"钢琴练习方案 - {student_name}")
|
pdf.add_title(f"有音个性化教学 - {student_name}")
|
||||||
pdf.add_heading("学员信息")
|
pdf.add_heading("学员信息")
|
||||||
pdf.add_paragraph(f"学员姓名:{student_name}")
|
pdf.add_paragraph(f"学员姓名:{student_name}")
|
||||||
pdf.add_paragraph(f"每日练习时间:{content.get('practice_time', 'N/A')}")
|
pdf.add_paragraph(f"每日练习时间:{content.get('practice_time', 'N/A')}")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}API设置 - 钢琴练习方案系统{% endblock %}
|
{% block title %}API设置 - 有音个性化教学系统{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
<title>{% block title %}钢琴练习方案管理系统{% endblock %}</title>
|
<title>{% block title %}有音个性化教学系统{% endblock %}</title>
|
||||||
|
|
||||||
<!-- 公共CSS -->
|
<!-- 公共CSS -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
<nav class="mobile-nav-toggle navbar navbar-dark bg-dark d-flex d-md-none" style="position:fixed;top:0;left:0;right:0;z-index:1050;padding:10px;">
|
<nav class="mobile-nav-toggle navbar navbar-dark bg-dark d-flex d-md-none" style="position:fixed;top:0;left:0;right:0;z-index:1050;padding:10px;">
|
||||||
<div class="container-fluid d-flex justify-content-between align-items-center">
|
<div class="container-fluid d-flex justify-content-between align-items-center">
|
||||||
<div>
|
<div>
|
||||||
<span class="navbar-brand mb-0"><i class="bi bi-music-note-beamed"></i> 钢琴方案</span>
|
<a href="/" class="navbar-brand mb-0 text-white text-decoration-none"><i class="bi bi-music-note-beamed"></i> 有音个性化教学系统</a>
|
||||||
<small id="mobileUserDisplay" class="d-block text-white-50" style="font-size:10px;"></small>
|
<small id="mobileUserDisplay" class="d-block text-white-50" style="font-size:10px;"></small>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-outline-light btn-sm" onclick="toggleMobileNav()">
|
<button class="btn btn-outline-light btn-sm" onclick="toggleMobileNav()">
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
<!-- 侧边栏 -->
|
<!-- 侧边栏 -->
|
||||||
<div class="col-md-2 sidebar p-0 collapsed" id="sidebar">
|
<div class="col-md-2 sidebar p-0 collapsed" id="sidebar">
|
||||||
<div class="p-3 text-center border-bottom border-secondary d-none d-md-block">
|
<div class="p-3 text-center border-bottom border-secondary d-none d-md-block">
|
||||||
<h5><i class="bi bi-music-note-beamed"></i> 钢琴方案</h5>
|
<h5><a href="/" class="text-white text-decoration-none"><i class="bi bi-music-note-beamed"></i> 有音个性化教学系统</a></h5>
|
||||||
<small id="currentUserDisplay" class="text-light"></small>
|
<small id="currentUserDisplay" class="text-light"></small>
|
||||||
</div>
|
</div>
|
||||||
<nav class="nav flex-column">
|
<nav class="nav flex-column">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}班级管理 - 钢琴练习方案系统{% endblock %}
|
{% block title %}班级管理 - 有音个性化教学系统{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}首页 - 钢琴练习方案系统{% endblock %}
|
{% block title %}首页 - 有音个性化教学系统{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row g-4 mb-4">
|
<div class="row g-4 mb-4">
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body text-center py-5">
|
<div class="card-body text-center py-5">
|
||||||
<i class="bi bi-music-note-beamed text-muted" style="font-size: 48px;"></i>
|
<i class="bi bi-music-note-beamed text-muted" style="font-size: 48px;"></i>
|
||||||
<h4 class="mt-3 text-muted">欢迎使用钢琴练习方案管理系统</h4>
|
<h4 class="mt-3 text-muted">欢迎使用有音个性化教学系统</h4>
|
||||||
<p class="text-muted">从左侧菜单选择功能</p>
|
<p class="text-muted">从左侧菜单选择功能</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}钢琴练习方案管理系统{% endblock %}
|
{% block title %}有音个性化教学系统{% endblock %}
|
||||||
|
|
||||||
{% block page_css %}
|
{% block page_css %}
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>登录 - 钢琴练习方案系统</title>
|
<title>登录 - 有音个性化教学系统</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<div class="login-card">
|
<div class="login-card">
|
||||||
<div class="text-center mb-4">
|
<div class="text-center mb-4">
|
||||||
<i class="bi bi-music-note-beamed" style="font-size: 48px; color: #667eea;"></i>
|
<i class="bi bi-music-note-beamed" style="font-size: 48px; color: #667eea;"></i>
|
||||||
<h4 class="mt-3">钢琴练习方案系统</h4>
|
<h4 class="mt-3">有音个性化教学系统</h4>
|
||||||
<p class="text-muted">请登录继续</p>
|
<p class="text-muted">请登录继续</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}方案详情 - 钢琴练习方案系统{% endblock %}
|
{% block title %}方案详情 - 有音个性化教学系统{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}编辑方案 - 钢琴练习方案系统{% endblock %}
|
{% block title %}编辑方案 - 有音个性化教学系统{% endblock %}
|
||||||
|
|
||||||
{% block page_css %}
|
{% block page_css %}
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}方案管理 - 钢琴练习方案系统{% endblock %}
|
{% block title %}方案管理 - 有音个性化教学系统{% endblock %}
|
||||||
|
|
||||||
{% block page_css %}
|
{% block page_css %}
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}问题配置 - 钢琴练习方案系统{% endblock %}
|
{% block title %}问题配置 - 有音个性化教学系统{% endblock %}
|
||||||
|
|
||||||
{% block page_css %}
|
{% block page_css %}
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>初始设置 - 钢琴练习方案系统</title>
|
<title>初始设置 - 有音个性化教学系统</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}数据统计 - 钢琴练习方案系统{% endblock %}
|
{% block title %}数据统计 - 有音个性化教学系统{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="container-fluid py-4">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}{{ student.name }} - 学员详情 - 钢琴练习方案系统{% endblock %}
|
{% block title %}{{ student.name }} - 学员详情 - 有音个性化教学系统{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}模板管理 - 钢琴练习方案系统{% endblock %}
|
{% block title %}模板管理 - 有音个性化教学系统{% endblock %}
|
||||||
|
|
||||||
{% block page_css %}
|
{% block page_css %}
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}用户管理 - 钢琴练习方案系统{% endblock %}
|
{% block title %}用户管理 - 有音个性化教学系统{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
|||||||
+262
-4
@@ -1,11 +1,219 @@
|
|||||||
# 钢琴练习方案系统 - 部署 SOP
|
# 钢琴练习方案系统 - 部署 SOP
|
||||||
|
|
||||||
> 版本:v1.5.2
|
> 版本:v1.5.6
|
||||||
> 日期:2026-04-28
|
> 日期: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)
|
## 重要更新(v1.5.2)
|
||||||
|
|
||||||
### ✨ 导出预览功能
|
### ✨ 导出预览功能
|
||||||
@@ -447,6 +655,7 @@ A: 检查是否执行了 migrate_goals_v3.py 迁移脚本,该脚本创建 stud
|
|||||||
|
|
||||||
| 版本 | 日期 | 变更 |
|
| 版本 | 日期 | 变更 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
|
| v1.5.6 | 2026-04-30 | Nginx限流配置(防爬虫/恶意请求);每日自动备份(30天保留) |
|
||||||
| v1.5.5 | 2026-04-28 | 修复容器时区(TZ=Asia/Shanghai);学员列表"x个方案"可点击跳转最新方案详情 |
|
| v1.5.5 | 2026-04-28 | 修复容器时区(TZ=Asia/Shanghai);学员列表"x个方案"可点击跳转最新方案详情 |
|
||||||
| v1.5.4 | 2026-04-28 | PDF正文字体12pt、表格11pt |
|
| v1.5.4 | 2026-04-28 | PDF正文字体12pt、表格11pt |
|
||||||
| v1.5.3 | 2026-04-28 | PDF行间距修复(7mm→2mm) |
|
| v1.5.3 | 2026-04-28 | PDF行间距修复(7mm→2mm) |
|
||||||
@@ -467,5 +676,54 @@ A: 检查是否执行了 migrate_goals_v3.py 迁移脚本,该脚本创建 stud
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
> **最后更新**:2026-04-28
|
## 十二、Ops 运维脚本
|
||||||
> **更新原因**:v1.5.2 - 导出预览功能;目标换行修复;居中语法支持
|
|
||||||
|
### 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限流配置;每日自动备份
|
||||||
|
|||||||
Reference in New Issue
Block a user