13 KiB
蝶雅 dy-dashboard 云端部署文档
本文档为 云端服务器(gyl.dieya.net.cn) 的部署操作手册。 生成日期:2026-09-21 适用项目:
dy-dashboard⚠️ 本文档不涉及 11.79 生产环境。生产升级需单独明确指令,流程见生产环境部署指南.md。
一、目标环境(2026-09-21 实测)
| 项 | 值 |
|---|---|
| SSH | ubuntu@82.156.135.91(本机 id_rsa 免密) |
| 域名 | gyl.dieya.net.cn |
| 项目目录 | /opt/dy-dashboard |
| 是否 Git 仓库 | 否(打包部署结构,不要执行任何 git 命令) |
| Python | /opt/dy-dashboard/venv/bin/python3(3.12.3) |
| 服务 | 系统单元 dy-dashboard.service(当前 active,MainPID 1271894) |
| 端口 | Gunicorn 18888(4 worker) |
| 数据库 | 127.0.0.1:3306,库 dy_data,账号 dy_app / dy_data_2026 |
| 备份目录 | ~/dy-backups/<用途>-<时间戳>/(/opt/dy-dashboard-backups 属 root,不可写) |
| 磁盘 | 40G,当前 23G 已用 / 16G 可用(60%) |
| 表数量 | 52 张,985(本地) vs 986(云端)字段 |
二、授权与边界(先读)
- 仅在明确点名"云端"时执行本流程;生产 11.79 需单独指令。
- 部署前先确认本次改的是代码还是数据切片,以及判断成功的业务指标。
- 边界内不做的事:
- ❌ 不覆盖云端
config.py(内含云端专属VAULT_PATH) - ❌ 不删除云端独有列或表
- ❌ 不覆盖
static/uploads(云端用户上传图片优先保留) - ❌ 不动
/opt/dy-dashboard-backups、/opt/backups等备份目录 - ❌ 不在云端跑
git命令
- ❌ 不覆盖云端
- 若用户报的是"网页打不开/接口超时/502",走故障排查流程,不要当部署任务做。
三、部署前检查
# 1) 云端可达性 + 磁盘余量 + 服务状态
ssh ubuntu@82.156.135.91 'echo OK; df -h / | tail -1; systemctl is-active dy-dashboard.service; systemctl show -p MainPID --value dy-dashboard.service'
磁盘:余量不足时连 mkdir 都会报 No space left on device。
- 磁盘满会表现为「页面能打开、点登录没反应」——先看
df -h /,别往代码方向查。 - 只清明确属临时产物的东西:
sudo journalctl --vacuum-size=100M、/tmp里历次部署残留的*.tar.gz/*.sql/临时目录。 - 备份一律不动,删除备份必须经用户明确确认。
# 2) 本地差异清单(只读,务必先 dry-run)
cd ~/.openclaw/workspace/dy-dashboard
rsync -a --dry-run --out-format='%n' <排除项> ./ ubuntu@82.156.135.91:/opt/dy-dashboard/
完成标准:清单里只有本次要上线的功能文件。若混进 .git、*.bak、tmp_*,补齐排除项后再看一遍。
排除项(完整清单)
config.py .git/ venv logs backups tmp_cache static/uploads
*.sql *.tar.gz *.csv *.xlsx *.db *.bak*
tmp_csv/ tmp_inventory_*/ tmp_outbound*/ tmp_sync_*/ {CLOUD_DIR}/
*.pyc __pycache__/
deploy.sh cloud_deploy*.sh run_cloud_deploy*.py ssh_cloud.py upload_cloud.py
四、备份(写入前必做)
时间戳只生成一次,全程复用。
TS=$(date +%Y%m%d_%H%M%S)
ssh ubuntu@82.156.135.91 "TS=$TS bash -s" <<'OUTER'
set -e
BK="$HOME/dy-backups/deploy-$TS"
rm -rf "$BK" && mkdir -p "$BK"
cd /opt/dy-dashboard
# 代码备份
tar -czf "$BK/code-pre.tar.gz" \
--exclude=venv --exclude=__pycache__ --exclude='*.pyc' \
--exclude=logs --exclude=backups --exclude=tmp_cache \
--exclude=static/uploads .
# 数据库备份
mysqldump -udy_app -pdy_data_2026 --single-transaction --complete-insert \
--hex-blob --no-tablespaces --routines --triggers --events \
dy_data | gzip -9 > "$BK/dy_data_full.sql.gz"
# 自检
echo "代码包文件数: $(tar -tzf "$BK/code-pre.tar.gz" | wc -l)"
gzip -t "$BK/dy_data_full.sql.gz" && echo "数据库包 gzip OK"
ls -lh "$BK"
md5sum "$BK"/*
OUTER
--routines --triggers --events在受限账号上可能失败:检查返回码与文件是否非空,必要时去掉这些参数重试。
并发守卫:重跑备份前先 pgrep -af 'mysqldump|tar -czf',确认没有并发的同类进程,否则会留下多个半成品目录,分不清该用哪个。
完成标准:只有一个本次时间戳的目录,其中文件完整、gzip -t 通过、md5 已记录。
五、代码同步
方式 A(推荐)—— 定向差异同步
cd ~/.openclaw/workspace/dy-dashboard
rsync -a --dry-run --out-format='%n' \
--exclude='config.py' --exclude='.git/' --exclude='venv' --exclude='logs' \
--exclude='backups' --exclude='tmp_cache' --exclude='static/uploads' \
--exclude='*.sql' --exclude='*.tar.gz' --exclude='*.csv' --exclude='*.xlsx' \
--exclude='*.db' --exclude='*.bak*' --exclude='tmp_csv/' \
--exclude='tmp_inventory_*/' --exclude='tmp_outbound*/' --exclude='tmp_sync_*/' \
--exclude='{CLOUD_DIR}/' --exclude='*.pyc' --exclude='__pycache__/' \
--exclude='deploy.sh' --exclude='cloud_deploy*.sh' --exclude='run_cloud_deploy*.py' \
--exclude='ssh_cloud.py' --exclude='upload_cloud.py' \
./ ubuntu@82.156.135.91:/opt/dy-dashboard/
确认清单无误后,去掉 --dry-run 实跑。注意 rsync -a 不加 --delete。
方式 B —— 使用打包文件(谨慎)
本机桌面的 dy-dashboard-<TS>.tar.gz 是为"留档"打的全量包,含 tmp_csv、dy_data_v1.0.sql 等大体积非功能文件,不建议直接解压覆盖云端。若确要用:
# 云端解到临时目录 → 再用 rsync 从临时目录挑选功能文件同步
scp ~/桌面/dy-dashboard-<TS>.tar.gz ubuntu@82.156.135.91:/tmp/
ssh ubuntu@82.156.135.91 'mkdir -p /tmp/dy-stage && tar -xzf /tmp/dy-dashboard-<TS>.tar.gz -C /tmp/dy-stage'
# 随后按方式 A 的排除项,从 /tmp/dy-stage 同步到 /opt/dy-dashboard
整文件替换的红线
整文件替换前,必须先判断云端文件是否等于本地"本次改动前"的版本:
git show <本次提交>^:<路径> > /tmp/prev_version
ssh ubuntu@82.156.135.91 'cat /opt/dy-dashboard/<路径>' > /tmp/cloud_version
diff /tmp/prev_version /tmp/cloud_version
完全一致才可整文件替换;有差异说明云端另有本地未同步的内容,必须回到锚点替换,别把未授权的改动一起带上线。
六、数据库结构增量迁移
原则:只增量,不覆盖数据;绝不删除云端独有列或表。
Q="select table_name,column_name,column_type,is_nullable,ifnull(column_default,'~NULL~'),extra,column_comment,ifnull(character_set_name,''),ifnull(collation_name,'') from information_schema.columns where table_schema='dy_data' order by table_name,ordinal_position"
mysql -udy_app -pdy_data_2026 -N -B -e "$Q" > /tmp/local_cols.tsv
ssh ubuntu@82.156.135.91 "mysql -udy_app -pdy_data_2026 -N -B -e \"$Q\"" > /tmp/cloud_cols.tsv
diff /tmp/local_cols.tsv /tmp/cloud_cols.tsv
取数必须用
mysql -N -B -e,不要用mysqldump跑 select。
生成 DDL 规则
| 情况 | 动作 |
|---|---|
| 本地有、云端无 | ADD COLUMN |
| 两端都有但定义不同 | MODIFY(改 NOT NULL 前先确认该列 NULL 行数为 0) |
| 云端有、本地无 | 只列出并保留,不删除 |
列定义拼接顺序:type + CHARACTER SET + COLLATE + NOT NULL/NULL + DEFAULT(~NULL~ 表示无默认;NOT NULL 且原本无默认时给 DEFAULT '')+ extra + COMMENT。
应用后重新导出并 diff,期望只剩「云端独有列」;同时对比关键业务表行数,确认数据未变。
七、代码自检与缓存
ssh ubuntu@82.156.135.91 'cd /opt/dy-dashboard && \
venv/bin/python3 -m compileall -q app.py routes services && \
node --check static/js/app.js && \
gzip -9 -k -f static/js/app.js && gzip -t static/js/app.js.gz && \
find . -name __pycache__ -type d -exec rm -rf {} + 2>/dev/null; echo DONE'
⚠️ 改了
static/js/app.js必须重新生成app.js.gz。浏览器带 gzip 头请求时会优先拿到预压缩缓存,不重新生成 → 前端改动"看不到效果"。
八、服务重载
# 常规代码更新:平滑重载(只换 worker)
ssh ubuntu@82.156.135.91 'kill -HUP $(systemctl show -p MainPID --value dy-dashboard.service)'
改了单元文件本身(如给
ExecStart加--max-requests)时,HUP 不会应用新的 ExecStart,必须:sudo systemctl daemon-reload && sudo systemctl restart dy-dashboard
等 3–8 秒后确认:单元 active、日志出现新 worker 启动、无新错误、端口只有一组进程。
ssh ubuntu@82.156.135.91 'systemctl is-active dy-dashboard.service; journalctl -u dy-dashboard.service -n 30 --no-pager | tail -20; ss -ltnp | grep 18888'
九、验证(探针全绿才算完成)
# 1) 本机与公网探针
curl -s -o /dev/null -w 'local %{http_code}\n' http://127.0.0.1:18888/login
curl -sk -o /dev/null -w 'public %{http_code}\n' https://gyl.dieya.net.cn/login
云端本机探针需在云端执行:ssh ubuntu@82.156.135.91 'curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:18888/login'
# 2) 确认下发的 app.js 就是磁盘文件
curl -sk --compressed https://gyl.dieya.net.cn/static/js/app.js -o /tmp/served.js
ssh ubuntu@82.156.135.91 'cat /opt/dy-dashboard/static/js/app.js' > /tmp/disk.js
cmp /tmp/served.js /tmp/disk.js && echo "下发内容一致"
# 3) 数据类改动后:刷新缓存 + 复核取值分布与总数
ssh ubuntu@82.156.135.91 'cd /opt/dy-dashboard && venv/bin/python3 -c "from services.cache import refresh_all_caches; refresh_all_caches()"'
完成标准:探针全绿 + 下发内容与磁盘一致 + 数据与预期一致。
十、回滚
ssh ubuntu@82.156.135.91 "TS=$TS bash -s" <<'OUTER'
set -e
BK="$HOME/dy-backups/deploy-$TS"
cd /opt/dy-dashboard
# 1) 恢复代码(保留云端上传文件)
mv static/uploads /tmp/uploads-rollback && \
tar -xzf "$BK/code-pre.tar.gz" -C /opt/dy-dashboard && \
mkdir -p static/uploads && cp -a /tmp/uploads-rollback/. static/uploads/ && \
rm -rf /tmp/uploads-rollback
# 2) 恢复数据库(仅在数据被误改时执行,会覆盖当前数据!)
# gunzip -c "$BK/dy_data_full.sql.gz" | mysql -udy_app -pdy_data_2026 dy_data
# 3) 重载
kill -HUP $(systemctl show -p MainPID --value dy-dashboard.service)
echo ROLLBACK_DONE
OUTER
回滚后同样跑第九节的验证探针。
十一、本次待部署内容(2026-09-21 实测)
代码差异:15 项
| 文件 | 类型 |
|---|---|
run.py |
入口 |
update_data.py |
数据更新 |
backup.sh |
备份脚本 |
routes/ai.py |
路由 |
routes/inventory.py |
路由 |
routes/planning.py |
路由 |
routes/products.py |
路由 |
routes/purchase.py |
路由 |
services/ai_datasources.py |
服务 |
services/db.py |
服务 |
services/field_perms.py |
服务 |
static/js/app.js |
前端 |
static/js/app.js.gz |
前端预压缩 |
数据库结构差异:无需迁移
- 本地 985 列 vs 云端 986 列,表清单 52 张完全一致。
- 无需 ADD / MODIFY。
- 云端多出 1 列
finished_products.待提数量(int)——保留,不删除。 bom_packaging.updated_at、packaging_orders.计划下单时间列定义完全相同,仅列顺序有差异(无害);因此不要用不带ordinal_position的裸 diff 判断差异。
十二、记录同步
部署成功后:
- 主动告知结果与备份位置(云端路径)。
- 按
work-record-sync写训练记录与四处索引/日志。 - 部署类记录另需同步到云端:
rsync -a --exclude='.obsidian' <本地>/知识库/ ubuntu@82.156.135.91:/opt/dy-dashboard/vault/知识库/,不加--delete(云端独有笔记保留)。
十三、常见故障速查
| 现象 | 首查 | 说明 |
|---|---|---|
| 页面能开、点登录没反应 | df -h / |
磁盘满 → MySQL 无法提交 → 登录卡 60s |
| 改了前端但看不到效果 | app.js.gz |
未重新生成预压缩缓存 |
| 改了数据但页面显示旧值 | refresh_all_caches() |
手工 SQL 导入绕过导入脚本,不会自动刷缓存 |
| 服务起不来 | journalctl -u dy-dashboard |
看 Python 依赖是否缺失(必须用 venv) |
| 端口占用 | ss -ltnp | grep 18888 |
检查是否有孤儿 gunicorn 进程 |
No space left on device |
/tmp + journal |
清临时产物;备份需用户确认后才能删 |
十四、SSH 多行脚本写法约束(实测踩坑)
- 用
ssh <host> bash -s <<'OUTER'传入多行脚本,脚本内再嵌套<<'PY'。 不要把多行 Python 直接塞进单引号 SSH 参数——远端会把 stdin 当文件名,报/opt/dy-dashboard/-一类错误。 - 结束标记必须独占一行。写成
OUTER 2>&1 | tail -20会找不到结束符(远端报"立即文档被文件结束符分隔")且脚本只执行一半。 要截取输出就把整个调用包起来:{ ssh <host> bash -s <<'OUTER' ... OUTER } 2>&1 | tail -20 <<'OUTER'带引号时本地变量不展开。需要传变量走 ssh 命令本身:ssh <host> "TS=$TS bash -s" <<'OUTER' ... 脚本内用 $TS ... OUTER