dy-dashboard/云端部署指南.md
2026-10-28 11:14:17 +08:00

13 KiB
Raw Blame History

蝶雅 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(云端)字段

二、授权与边界(先读)

  1. 仅在明确点名"云端"时执行本流程;生产 11.79 需单独指令。
  2. 部署前先确认本次改的是代码还是数据切片,以及判断成功的业务指标。
  3. 边界内不做的事:
    • ❌ 不覆盖云端 config.py(内含云端专属 VAULT_PATH)
    • ❌ 不删除云端独有列或表
    • ❌ 不覆盖 static/uploads(云端用户上传图片优先保留)
    • ❌ 不动 /opt/dy-dashboard-backups、/opt/backups 等备份目录
    • ❌ 不在云端跑 git 命令
  4. 若用户报的是"网页打不开/接口超时/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 判断差异。

十二、记录同步

部署成功后:

  1. 主动告知结果与备份位置(云端路径)。
  2. 按 work-record-sync 写训练记录与四处索引/日志。
  3. 部署类记录另需同步到云端: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 多行脚本写法约束(实测踩坑)

  1. 用 ssh <host> bash -s <<'OUTER' 传入多行脚本,脚本内再嵌套 <<'PY'。 不要把多行 Python 直接塞进单引号 SSH 参数——远端会把 stdin 当文件名,报 /opt/dy-dashboard/- 一类错误。
  2. 结束标记必须独占一行。写成 OUTER 2>&1 | tail -20 会找不到结束符(远端报"立即文档被文件结束符分隔")且脚本只执行一半。 要截取输出就把整个调用包起来:
    { ssh <host> bash -s <<'OUTER'
    ...
    OUTER
    } 2>&1 | tail -20
    
  3. <<'OUTER' 带引号时本地变量不展开。需要传变量走 ssh 命令本身:
    ssh <host> "TS=$TS bash -s" <<'OUTER'
    ... 脚本内用 $TS ...
    OUTER