归档状态:暂停草稿。 当前为此前暂停的在线打包部署草稿,基于bc3267ef旧基线;尚未同步c4070071及最新屏端和Nginx修复,不作为新版现场可执行方案。当前可部署版本请使用离线打包物中的已验收制品和总手册。
食安客户新服务器现场打包与两套系统部署手册
适用:客户新服务器为银河麒麟 V10,达梦已由厂家安装,可访问公网;业务代码采用 standard_wxb。目标是在客户新机上制作完整部署制品,再部署两套食安;以后在该机重复完成版本更新,不依赖临时服务器。
本手册另行新增,原《食安迁移部署总手册》和原制品保留。首次可继续用原制品上线;本手册用于建立现场打包能力,也包含两套系统首次安装、数据与文件迁移、Nginx、更新和回退。配套文件为 foodsafe-onsite-build-tools.tar.gz,必须与本手册一起保存。
先说明验证范围:配套工具经过源码核对、语法及隔离组装验证;本次没有登录客户新机,也没有在客户新机完成全量构建或业务验收。现场必须通过第 11~13、23 步后才能正式使用。后续源码变化可能要求研发更新发布适配工具;工具遇到不匹配会停止,不会自动猜测修改。
01|先看懂执行方式
01.1 分清账号和机器
| 文中标记 | 在哪里执行 | 做什么 |
|---|---|---|
| 管理电脑 | 你的 Mac / Windows 电脑 | 阅读、上传源码或文件、用浏览器验收 |
| 新机 root | SSH 登录客户新麒麟服务器 | 装软件、创建账号、安装制品、配置 Nginx |
| 新机 foodsafe-build | 客户新机专用打包用户 | 拉取源码、安装开发依赖、build、生成归档 |
| 新机 foodsafe | 客户新机专用应用用户 | 由 PM2 运行两套已编译后端 |
| 旧机管理员 | 原食安服务器 | 备份两个库、图片与配置,正式切换时停写 |
| 数据库管理员 | 厂家或客户 DBA | 达梦账号、模式、迁库、备份、对账 |
foodsafe 和 foodsafe-build 是 Linux 用户,与达梦数据库用户是两回事。应用用户和打包用户分开,避免打包时操作到运行目录。数据库的两套用户也必须分开。
01.2 每个命令块怎么执行
- 先看当前步骤的机器和账号,确认后再复制命令。
- 命令统一在 Bash 下执行。SSH 进入后输入
bash即可。 - 不复制代码块外面的说明;
#开头是解释,可以一起复制。 - 文中的
CHANGE_ME、中文示例 IP、/实际路径必须先改成真实值。 - 一个代码块执行完,检查成功标准,再进行下一块。报错就停在本步。
sudo -iu foodsafe-build表示切换用户;exit表示返回上一层,不是停止业务系统。vi 文件:按i编辑;完成后按 Esc,输入:wq回车;放弃本次编辑用:q!。- 数据库口令只填私有
.env或交互输入;不要放进命令行、截图或公开文档。 - 本文给出重启、覆盖程序和迁库操作,实际执行要安排客户确认的维护窗口。写手册不等于这些操作已经执行。
01.3 两种使用路线
- **新机还未部署:**按 02~24 顺序操作;第 14~17 包含首次迁库和文件准备。
- **新机已有两套系统:**先完成 02~13 的打包环境与制品验证;以后走第 25~28 的更新流程,不重复首次安装、不重迁整库。
打包时不会启动后端、不会连接达梦、不会写 Nginx,也不会替你执行数据库升级。后端“启动成功”与“数据库数据正确”要分别验收。
02|填写两套系统登记表
执行位置:管理电脑记录,现场逐项核对。
| 项目 | 第一套 | 第二套 |
|---|---|---|
| 原 MySQL8 库 | foodsafe | foodsafe2 |
| 达梦登录用户 / 同名模式(建议名) | FOODSAFE_APP | FOODSAFE2_APP |
| 达梦表空间(建议名) | FOODSAFE_TBS | FOODSAFE2_TBS |
| 应用目录 | /data/apps/foodsafe1 | /data/apps/foodsafe2 |
| PM2 进程 | foodsafe-1 | foodsafe-2 |
| 后端端口 | 3300 | 3301 |
| 学校端端口 | 4201 | 4202 |
| 监管端端口 | 4301 | 4302 |
| 大屏端口 | 5209 | 5210 |
| 移动端端口 | 4211 | 4212 |
| 本套旧图暂存目录 | /data/migration/foodsafe1 | /data/migration/foodsafe2 |
| 本套部署配置 | /data/config/foodsafe/deploy1.env | /data/config/foodsafe/deploy2.env |
另外登记:新旧 IP/域名、SSH 端口、达梦版本/实例地址/端口、Nginx 安装与 include 路径、两个旧项目目录、图片真实路径、两份后端环境配置、设备回调地址、MQTT broker 和认证/Topic、视频服务、OSS、前厅联动、备份位置。
厂家一个达梦实例可以承载两个独立业务用户/模式,共用同一个数据库监听端口;不要求为食安另装两个数据库实例。上述命名以厂家实际分配为准,不能因为名字不同就重新建库。
**成功标准:**所有端口、账号和目录能一一对应;没有不知道属于哪套的旧库或文件。
03|检查新服务器和磁盘
执行位置:新机 root。
03.1 检查系统、架构和时间
bash
cat /etc/os-release
uname -m
getconf GNU_LIBC_VERSION
free -h
timedatectl
本配套工具限定 Linux x86_64、Node20.19.5;Node 运行条件需要 glibc≥2.28。达梦是厂家已装好的服务,不重装。若显示 aarch64、其他架构或 glibc 不满足,不套用本工具,交研发准备匹配版本。核对时间为北京时间,校时按客户现有 NTP 管理方式进行。
03.2 检查空间和占用
findmnt -T /data
findmnt -T /workspace
df -h /data /workspace
df -i /data /workspace
ss -lntp
应用、打包临时目录和在线图片优先放 /data;大的归档、备份暂存可以放 /workspace,不要放进达梦数据文件目录。历史报告中 /workspace IO 明显偏低,未复测前不把它作为高频构建目录。
规划 /data 至少能同时容纳源码、完整开发依赖、构建结果、新旧制品及业务文件;先预留约 20GB 打包余量,这只是初始预算,不含图片、数据库和增长空间。首次打包后用 du -sh /data/build/jobs/版本号 实测。大体量图片按实际容量另算,并保留磁盘余量。
**成功标准:**架构匹配,系统时间正确,端口无冲突,磁盘和 inode 都充足。**失败处理:**不在客户现场升级 glibc,也不删除达梦文件腾空间。
04|准备目录、两个 Linux 账号和基础命令
执行位置:新机 root。
04.1 创建不存在的专用账号
id foodsafe || useradd -m -r -d /var/lib/foodsafe -s /bin/bash foodsafe
id foodsafe-build || useradd -m -r -d /var/lib/foodsafe-build -s /bin/bash foodsafe-build
getent passwd foodsafe foodsafe-build
如果账号已存在,以 getent 返回的家目录为准,不修改已有账号归属,不重新创建。
04.2 创建分类目录
install -d -m 755 /data/apps /data/build /data/releases /opt/foodsafe-build-tools
install -d -m 750 -o foodsafe-build -g "$(id -gn foodsafe-build)" \
/data/build/food_safety /data/build/jobs /data/releases/food_safety
install -d -m 700 /data/config/foodsafe /data/backups/foodsafe \
/data/migration/foodsafe1 /data/migration/foodsafe2
**现在不要创建 /data/apps/foodsafe1 和 /data/apps/foodsafe2。**首次安装脚本要求目标不存在,防止覆盖数据。
04.3 补基础命令
先检查:
command -v git curl tar gzip python3 rsync make gcc g++
python3 --version
麒麟仓库可用且缺少上述软件时,按实际包管理器选择一条,不重复执行两条:
# 新机使用 dnf 时
sudo dnf install -y git curl tar gzip python3 rsync make gcc gcc-c++
# 新机只有 yum 时
sudo yum install -y git curl tar gzip python3 rsync make gcc gcc-c++
配套 Python 脚本只用标准库,使用 Python3.6 或以上。不要求安装 pip 库。系统软件仓库受客户管理,包缺失时请运维补充同版麒麟仓库或安装包,不混用其他发行版源,不执行全系统升级。
**成功标准:**两个账号存在;打包账号能写三个打包目录;基础命令都有路径。
05|安装 nvm、Node20、pnpm 和 PM2
执行位置:先新机 root 切到 foodsafe-build。已有固定版本可直接核验,不重复覆盖。
05.1 在打包用户下安装 nvm
sudo -iu foodsafe-build
bash
export NVM_DIR="$HOME/.nvm"
if [ ! -e "$NVM_DIR" ]; then
git clone --branch v0.40.3 --depth 1 https://github.com/nvm-sh/nvm.git "$NVM_DIR"
fi
. "$NVM_DIR/nvm.sh"
nvm --version
预期 0.40.3。如果 GitHub 不通,即使普通网页可打开也不能继续安装;通过管理电脑下载可信版本,离线传入,不粘贴来源不明的一键脚本。现有 nvm 不是该版本也不覆盖,先由运维核对能否加载固定 Node。
05.2 安装本流程固定的软件版本
nvm install v20.19.5
nvm use v20.19.5
nvm alias default v20.19.5
npm install -g pnpm@10.21.0 pm2@6.0.13
node -v
pnpm -v
node -p "require(process.env.NVM_DIR+'/versions/node/v20.19.5/lib/node_modules/pm2/package.json').version"
应依次看到 v20.19.5、10.21.0、6.0.13。这是复现原制品的固定组合,不是要求所有软件永远停留在该版本;未来升级版本应由研发重新验证打包工具、运行依赖和业务后再统一调整。
05.3 把运行时独立放进 /opt
先 exit 返回 root;如果进入了额外 Bash,可能需要再 exit 一次。用 whoami 确认是 root。
whoami
BUILD_HOME=$(getent passwd foodsafe-build | cut -d: -f6)
RUNTIME_SOURCE="$BUILD_HOME/.nvm/versions/node/v20.19.5"
test -x "$RUNTIME_SOURCE/bin/node"
下面的复制只在目标目录尚不存在时执行;已经存在先核验,不覆盖。
if [ -e /opt/foodsafe-node-v20.19.5 ] || [ -L /opt/foodsafe-node-v20.19.5 ]; then
echo "目标已存在,停止复制,先核对现有版本"
else
cp -a "$RUNTIME_SOURCE" /opt/foodsafe-node-v20.19.5 && \
chown -R root:root /opt/foodsafe-node-v20.19.5 && \
chmod -R a+rX /opt/foodsafe-node-v20.19.5
fi
建立稳定路径,只在 /opt/foodsafe-node 不存在时执行:
if [ -e /opt/foodsafe-node ] || [ -L /opt/foodsafe-node ]; then
echo "稳定路径已存在,停止创建,先核对目标"
else
ln -s /opt/foodsafe-node-v20.19.5 /opt/foodsafe-node
fi
如果已有该路径,检查:
readlink -f /opt/foodsafe-node
/opt/foodsafe-node/bin/node -v
不要用 ln -sfn 覆盖已有运行环境。其他安装方式也可以,但后面的 Node、PM2 绝对路径必须统一修改并验证。
05.4 核验两个账号都能使用
runuser -u foodsafe-build -- /opt/foodsafe-node/bin/node -v
runuser -u foodsafe -- /opt/foodsafe-node/bin/node -v
runuser -u foodsafe-build -- env PATH=/opt/foodsafe-node/bin:/usr/bin:/bin \
/opt/foodsafe-node/bin/pnpm -v
nvm 管理文件和下载缓存位于打包账号家目录,通常在系统盘;实际应用运行时复制在 /opt;打包目录与应用在 /data,不是全部软件都安装到 /data。之后不要删除 /opt/foodsafe-node-v20.19.5。
**成功标准:**两个账号均返回相同 Node20;pnpm 版本正确;应用用户无需访问 root 私有目录。
06|核验公网能否用于下载和代码更新
执行位置:新机 foodsafe-build。
sudo -iu foodsafe-build
export PATH=/opt/foodsafe-node/bin:/usr/local/bin:/usr/bin:/bin
curl -I --connect-timeout 10 --max-time 20 https://registry.npmjs.org/
curl -I --connect-timeout 10 --max-time 20 https://nodejs.org/
git ls-remote https://codeup.aliyun.com/60069db88deaa14d9e02b875/food_safety HEAD
- HTTP 返回可解释的成功或重定向响应;不是 DNS、证书或超时错误。
- 代码仓库可能要求登录,使用客户批准的个人或只读部署凭据。不要把 Token 拼进 URL。
- 若代码仓库无法访问,可以用第 09 步的源码离线传入方案;依赖仓库仍需可达,或提前准备完整依赖缓存。
- 不用
strict-ssl=false、跳过证书校验来掩盖网络问题。
**成功标准:**Git 与依赖仓库分别可用。普通网页能打开不能代替这两项检查。
07|准备 Nginx 和 Mosquitto
执行位置:新机 root。先 exit 回 root。
07.1 检查软件是否已安装
command -v nginx mosquitto mosquitto_passwd
nginx -v
systemctl list-unit-files | grep -Ei 'nginx|mosquitto'
ss -lntp | grep -E ':1883|:9001|:4201|:4202|:3300|:3301'
如果缺少且麒麟仓库提供,使用实际包管理器安装:
# dnf 示例;只有 yum 时换成 yum
sudo dnf install -y nginx mosquitto
如果提示没有包,不继续随机添加外部仓库。由运维提供同版麒麟、同架构且依赖完整的包。应用制品不包含 Nginx/Mosquitto 本体。
07.2 确认 Nginx 实际路径与 include
command -v nginx
readlink -f "$(command -v nginx)"
nginx -V 2>&1
nginx -T 2>&1 | less
按 q 退出 less。从输出核对:主配置路径,以及 http 块里面加载站点的 include。常见路径:
- 系统包:程序
/usr/sbin/nginx,站点/etc/nginx/conf.d。 - 宝塔:程序
/www/server/nginx/sbin/nginx,站点/www/server/panel/vhost/nginx。
把实际值填进第 18 步。不是看目录存在就认为它已被 include。不要覆盖整个 nginx.conf;同机还有前厅时,只新增本套站点。
若全新系统包 Nginx 尚未运行:
nginx -t
systemctl enable --now nginx
systemctl is-active nginx
宝塔按实际管理机制启动,不强行套 systemctl 服务名。
07.3 MQTT 分清“装软件”和“业务连到哪”
- 安装 Mosquitto 不代表设备会自动使用本机。
- 先登记旧系统实际使用的 broker、认证、Topic 和设备配置;厂家要求继续用原 broker 时,先沿用并测试连通,不擅自切到新机。
- 只有方案明确要新建本机 broker,才按下面模板配置。已有 broker 服务不覆盖、不另起一个抢 1883。
**仅全新、独立 Mosquitto 的示例:**先 mosquitto -h 核对配置路径;以下按系统包 /etc/mosquitto、服务用户 mosquitto 举例。
id mosquitto
install -d -m 750 -o root -g mosquitto /etc/mosquitto/foodsafe
mosquitto_passwd -c /etc/mosquitto/foodsafe/passwd foodsafe1
mosquitto_passwd /etc/mosquitto/foodsafe/passwd foodsafe2
chown root:mosquitto /etc/mosquitto/foodsafe/passwd
chmod 640 /etc/mosquitto/foodsafe/passwd
上面两次会交互输入密码;-c 只用于首次创建,不要第二次也加,避免覆盖第一套账户。
vi /etc/mosquitto/foodsafe/mosquitto.conf
示例内容:
listener 1883 127.0.0.1
allow_anonymous false
password_file /etc/mosquitto/foodsafe/passwd
这只允许本机应用访问。设备需要跨机器连入时,由网络管理员按设备网段调整监听和防火墙,并结合真实 Topic 配 ACL;两个密码用户本身不等于 Topic 已隔离。需要 WebSocket 9001 时还需确认该构建支持 WebSocket,不能把 TCP1883 直接当浏览器 ws 使用。
此示例配置不会自动被系统服务读取。以下仅用于全新专用broker,将系统服务明确指向本文件。先确认程序路径和已有单元,记录原配置:
command -v mosquitto
systemctl cat mosquitto
systemctl edit mosquitto
在编辑器写入下面覆盖内容。示例程序路径是/usr/sbin/mosquitto,若command -v不同须替换:
[Service]
Type=simple
ExecStart=
ExecStart=/usr/sbin/mosquitto -c /etc/mosquitto/foodsafe/mosquitto.conf
User=mosquitto
Group=mosquitto
保存退出后(编辑器如果是nano,Ctrl+O、回车保存,Ctrl+X退出):
systemctl daemon-reload
systemctl cat mosquitto
# 核对ExecStart已指向专用配置,且端口未被其他broker占用
systemctl enable --now mosquitto
systemctl status mosquitto --no-pager
ss -lntp | grep ':1883'
此最小broker示例未启用持久化和Topic ACL,不能把它当作设备交付验收完成。需要跨重启保留持久会话/排队消息时由设备协议方案补齐持久化目录及权限,并实测断网重连;不需要本机broker的部署可跳过全部新建配置,只核对实际外部broker。
已有服务在维护窗口使用 systemctl restart mosquitto 才会重读修改;共享 broker 必须评估其他设备影响。消息发布订阅测试使用厂家约定的测试 Topic,避免把测试消息发给真实设备。
**成功标准:**Nginx 路径/include 清楚;实际 broker 方案清楚;软件安装与业务配置分别确认。
08|上传并解压本手册配套打包工具
执行位置:管理电脑上传,新机 root 解压。
08.1 上传两个文件
把本次提供的以下文件上传到新机 /workspace/foodsafe-packages/:
foodsafe-onsite-build-tools.tar.gzfoodsafe-onsite-build-tools.tar.gz.sha256
目录不存在时先由新机 root 创建:
install -d -m 700 /workspace/foodsafe-packages
Mac 示例(把 IP 和本地文件路径换成实际值,SSH 非22端口时给 scp 加 -P 实际端口):
scp /本地路径/foodsafe-onsite-build-tools.tar.gz \
/本地路径/foodsafe-onsite-build-tools.tar.gz.sha256 \
root@客户新机IP:/workspace/foodsafe-packages/
Windows 可以用 WinSCP,上传到同一目录;不要上传成文本模式破坏压缩包。
08.2 校验和解压
新机 root:
cd /workspace/foodsafe-packages
sha256sum -c foodsafe-onsite-build-tools.tar.gz.sha256
tar -tzf foodsafe-onsite-build-tools.tar.gz | head -20
应看到 OK,且包内顶层为 foodsafe-onsite-build-tools/。只在目标子目录不存在时解压:
if [ -e /opt/foodsafe-build-tools/foodsafe-onsite-build-tools ]; then
echo "工具已存在,停止解压,先核对版本"
else
tar -xzf foodsafe-onsite-build-tools.tar.gz -C /opt/foodsafe-build-tools && \
chmod -R a+rX /opt/foodsafe-build-tools/foodsafe-onsite-build-tools && \
ls /opt/foodsafe-build-tools/foodsafe-onsite-build-tools/tools
fi
应有 build-release.sh、assemble.py、apply-release-overlay.py、locks/ 和 template/。
工具目录只读使用,打包输出不会写在里面。以后研发更新工具时解压到新的版本目录,核对后切换,保留旧版,不直接覆盖。
09|取得源码,锁定 standard_wxb 的发布提交
执行位置:新机 foodsafe-build。
09.1 首次从 Git 获取
sudo -iu foodsafe-build
export PATH=/opt/foodsafe-node/bin:/usr/local/bin:/usr/bin:/bin
cd /data/build/food_safety
若当前目录为空,执行:
git clone --branch standard_wxb --single-branch \
https://codeup.aliyun.com/60069db88deaa14d9e02b875/food_safety .
已有仓库则不 clone,先检查:
git status --short --branch
git remote -v
git branch --show-current
只有工作区干净、分支为 standard_wxb、远端正确,才更新:
git pull --ff-only
git log -5 --oneline
git rev-parse HEAD
有本地改动或无法快进就停止,请研发处理,不能用 reset、clean、强制覆盖。有人持续提交不代表每个 HEAD 都可发布,让研发给出已测试的 40位完整提交号。
09.2 Git 不能访问时,传完整 Git bundle
在研发电脑的正确食安仓库执行,先确认本地分支已包含计划发布提交:
git status --short --branch
git bundle create foodsafe-standard-wxb.bundle standard_wxb
将 bundle 上传至新机打包用户可读的位置。新机的 /data/build/food_safety 必须为空,再以 foodsafe-build 执行:
cd /data/build/food_safety
git clone --branch standard_wxb /实际传入目录/foodsafe-standard-wxb.bundle .
git log -5 --oneline
该仓库 origin 指向 bundle,以后不能当作可联网的远端更新;继续由研发提供更新 bundle 或由运维核对后改为批准的 Git 地址。普通“源码压缩包”没有提交历史,不能直接交给本配套工具;可让研发提供 bundle,而不是在客户机器随便 init 后冒充正式版本。
09.3 核对发布适配
现有基线为 bc3267ef4c97fcfc38c1add26a2a9e1d94263275。原制品还包括:本地二维码、移动/大屏运行时地址、HOST监听、本地播放器与 logo 等适配,单纯拉原分支再 build 不一定包含这些。
配套工具先在独立副本应用精确匹配适配。后续研发已把适配合入源码、改动相关文件或增加运行依赖时,可能匹配失败;**此时让研发同步更新打包工具,不是你删除报错或重复打补丁。**没有把本次暂停的旧配置核查结果当作已修复代码。
**成功标准:**源码干净、分支与远端正确,研发已明确本次提交号、变更内容、数据库升级需求和验收结果。
10|在独立目录执行打包
执行位置:新机 foodsafe-build。打包不需要填写数据库口令。
10.1 设置本次版本号和提交号
export PATH=/opt/foodsafe-node/bin:/usr/local/bin:/usr/bin:/bin
export RELEASE_COMMIT=bc3267ef4c97fcfc38c1add26a2a9e1d94263275
export RELEASE_VERSION=20261020-bc3267ef-r1
上面是已知基线例子。以后把提交号换为研发确认值,版本号也换新,例如 20261025-实际短提交号-r1。版本号只用字母、数字、点、横线、下划线,不含空格。
10.2 限制负载并启动
nice -n 10 bash \
/opt/foodsafe-build-tools/foodsafe-onsite-build-tools/tools/build-release.sh \
/data/build/food_safety "$RELEASE_COMMIT" "$RELEASE_VERSION"
尽量选业务低峰时段。nice 只降低 CPU 调度优先级,不限制磁盘 IO、内存或数据库影响;即使有192核,也不在繁忙时段随意打包。系统资源不足时改用同架构构建机,不强行停数据库腾资源。
10.3 工具实际做了什么
- 检查 Linux x86_64、Node/pnpm 固定版本、源码干净和提交号归属。
- 创建
/data/build/jobs/本次版本号/,不会使用运行目录。 - 用
git archive只导出已提交源码到src/,不复制旧项目的未跟踪.env、图片或依赖。 - 拒绝源码中真正的
.env环境文件,防止把客户配置编进前端。 - 应用发布适配;在专用
extra/用提供的 npm 锁文件安装qrcode1.5.4。 - 在
src/执行pnpm install --frozen-lockfile --ignore-scripts,安装完整构建依赖而非仅生产依赖。 - 顺序构建
web-school、web-regulator、screen、mobile-device四端,再执行 gateway 的 TypeScript build。 - 收集四端 dist、后端完整嵌套 dist、libs 和完整 node_modules 依赖树,包括
dmdb。 - 把临时 qrcode 外部链接替换为真实依赖;检查符号链接不能越出制品或失效。
- 在拷出的制品内部实际加载 dmdb/qrcode/NestJS 等模块,生成版本和 SHA256 记录。
- 输出新的部署包,不自动部署、不启动任何后端、不运行 SQL。
--ignore-scripts 是本工具固定策略,避免任意依赖安装脚本在客户机自动执行。以后增加需要 postinstall/native 编译的依赖时,研发必须更新并审核工具;不要现场全量放开脚本。冻结锁文件安装失败时不删除锁文件,也不改用 pnpm update。
10.4 查看结果和日志
ls -lh /data/releases/food_safety/
tail -n 60 "/data/build/jobs/$RELEASE_VERSION/logs/build.log"
成功应出现:
foodsafe-本次版本号-kylin-x64/
foodsafe-本次版本号-kylin-x64.tar.gz
foodsafe-本次版本号-kylin-x64.tar.gz.sha256
看到 打包完成(尚未部署) 才表示构建链完成。出现 tsc 错误,即使某些 .js 已生成,也不准继续发布;请研发修复后用新提交、新版本号重新构建。root 的 pnpm build 只是提示语,不能代替五个项目分别构建。
**失败处理:**保留本次 jobs 目录和日志,换新版本号重试;不要删除运行目录,不要用旧 dist 拼成“成功”。
11|检查制品完整性和达梦驱动
执行位置:新机 foodsafe-build。接第 10 步的同一窗口;换窗口先重设变量。
export PATH=/opt/foodsafe-node/bin:/usr/local/bin:/usr/bin:/bin
export KIT="/data/releases/food_safety/foodsafe-$RELEASE_VERSION-kylin-x64"
cd /data/releases/food_safety
sha256sum -c "foodsafe-$RELEASE_VERSION-kylin-x64.tar.gz.sha256"
cd "$KIT"
sha256sum -c PAYLOAD.sha256 > /dev/null
cat RELEASE.json
预期两个校验均通过。确认 source_commit 是本次指定版本;不是只看压缩包日期。
11.1 驱动到底怎么安装
当前服务 package.json 声明 dmdb: 1.0.52452。**打包目录执行 pnpm install 时安装驱动,组装时把它及依赖带进制品。**应用目录首次安装和以后更新不再执行 pnpm install;不需要安装 PHP 的达梦扩展、Java JDBC 或复制别的应用驱动。
单独验证制品里的驱动:
cd "$KIT/payload"
node - <<'JS'
const path = require('path');
const { createRequire } = require('module');
const req = createRequire(path.resolve('services/gateway-service/package.json'));
for (const name of ['dmdb', 'qrcode', 'dotenv', '@nestjs/core', 'mqtt']) {
req(name);
console.log(name + ' OK');
}
console.log('dmdb version:', req('dmdb/package.json').version);
JS
应逐项 OK 且 dmdb 版本符合本次版本清单。这里仅验证模块可加载,不连接数据库。
11.2 制品里应该有什么、不该有什么
payload/apps/web-school/dist/
payload/apps/web-regulator/dist/
payload/apps/screen/dist/
payload/apps/mobile-device/dist/
payload/services/gateway-service/dist/
payload/services/gateway-service/node_modules/
payload/services/gateway-service/package.json
payload/node_modules/ 包括 .pnpm 和完整链接目标
payload/libs/
scripts/ config/ 安装、检查、配置和首次启用工具
RELEASE.json PAYLOAD.sha256 SYMLINKS.json provenance/
不包含真实客户数据库、历史业务图片、实际 .env、证书私钥或许可证。为了 Node 模块正常运行会有依赖包源码、声明文件和少量共享 libs;不是完整业务源码仓库,也不是加密程序。
不得只复制后端 main.js;此项目入口是 services/gateway-service/dist/services/gateway-service/src/main.js,还需要同一 dist 树下其他模块。用 tar/scp/rsync 保留隐藏文件和符号链接,不通过 Windows 解压后挑文件上传。
**成功标准:**SHA、提交号、四端产物和驱动检查全部通过。
12|在隔离环境验收新制品
执行位置:新机 root、DBA、测试人员。此步在首次上线和每次版本更新前执行。
12.1 准备隔离测试数据
- DBA 从备份准备可丢弃的达梦测试模式,不能直接连接客户生产模式做新版本试运行。
- 确认测试账号只访问该测试模式,无跨生产模式权限;测试数据使用客户批准范围。
- 复制一组脱敏测试图片到独立目录,不能指向在线系统可写目录。
- 测试不接真实设备 broker、不填生产设备凭据、不共享真实队列,不启用回调/定时任务。
- 仅设
AUDIT_ENABLED=false不能保证应用完全不写库,登录、守卫和业务路径仍可能写入;隔离模式必须真正隔离。
12.2 创建测试部署参数
以下以“第一套配置、但使用测试端口和目录”为例;INSTANCE=1 所以进程名仍是 foodsafe-1,不要执行 activate.sh,只手工使用专用测试进程名启动。
export KIT=/data/releases/food_safety/foodsafe-本次版本号-kylin-x64
cp "$KIT/config/deploy.env.example" /data/config/foodsafe/test.env
vi /data/config/foodsafe/test.env
填入:
INSTANCE=1
APP_ROOT=/data/test-foodsafe/本次版本号
APP_USER=foodsafe
PUBLIC_HOST=127.0.0.1
PUBLIC_SCHEME=http
LISTEN_HOST=127.0.0.1
SCHOOL_PORT=14201
REGULATOR_PORT=14301
SCREEN_PORT=15209
MOBILE_PORT=14211
GATEWAY_PORT=13300
NODE_BIN=/opt/foodsafe-node/bin/node
PM2_BIN=/opt/foodsafe-node/bin/pm2
NGINX_BIN=/usr/sbin/nginx
NGINX_VHOST_DIR=/etc/nginx/conf.d
INTEGRATION_UPSTREAM=
TLS_CERT_PATH=
TLS_KEY_PATH=
路径和端口必须空闲。测试只启动后端,以上四个前端端口不安装进现有 Nginx。
bash "$KIT/scripts/install.sh" /data/config/foodsafe/test.env
vi /data/test-foodsafe/本次版本号/services/gateway-service/.env
填测试 DM_HOST/PORT/USER/PASSWORD、测试 JWT;确认 PORT=13300、HOST=127.0.0.1,任务开关均0。测试不能直接照抄生产 .env。
12.3 校验并启动专用测试后端
bash "$KIT/scripts/check.sh" /data/config/foodsafe/test.env
APP_HOME=$(getent passwd foodsafe | cut -d: -f6)
TEST_ROOT=/data/test-foodsafe/本次版本号
cd "$TEST_ROOT"
runuser -u foodsafe -- env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/bin:/bin \
/opt/foodsafe-node/bin/pm2 start \
services/gateway-service/dist/services/gateway-service/src/main.js \
--name foodsafe-build-test --cwd "$TEST_ROOT" --interpreter /opt/foodsafe-node/bin/node \
--output "$TEST_ROOT/logs/test-out.log" --error "$TEST_ROOT/logs/test-error.log"
curl --fail --max-time 10 http://127.0.0.1:13300/health
先检查该用户 PM2 中没有同名 foodsafe-build-test,若已存在先确认归属,不覆盖。测试期间不要 pm2 save,避免把临时测试进程保存为开机业务。
12.4 前端和业务验收
要验收登录、二维码、视频和上传,使用下面的独立测试Nginx。它只监听本机测试端口,不修改现有Nginx主配置、不reload生产Nginx。先按20.4的说明修正测试目录内的integration.config.json(生产端口换为本次测试端口,不能连接真实设备)。
# 新机root,TEST_ROOT仍为本次独立测试目录
export TEST_ROOT=/data/test-foodsafe/本次版本号
export TEST_NGINX=/usr/sbin/nginx
export TEST_MIME=/etc/nginx/mime.types
# 上面两项从07步的实际安装核对;宝塔mime通常在其conf目录
# 下面用户须为真实存在的Nginx worker用户,如nginx或www
export TEST_WORKER=nginx
id "$TEST_WORKER"
test -f "$TEST_MIME"
ss -lntp | grep -E ':14201|:14301|:15209|:14211'
最后一条应无输出,表示没有监听冲突。若存在测试残留先核实,不直接结束未知进程。再生成独立主配置:
python3 - <<'PYCONF'
import os,pathlib,re
r=pathlib.Path(os.environ['TEST_ROOT']);worker=os.environ['TEST_WORKER'];mime=os.environ['TEST_MIME']
if not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_-]*',worker):raise SystemExit('worker用户非法')
if any(c in str(r)+mime for c in [' ', ';', '{', '}', '\n']):raise SystemExit('路径非法')
(r/'deploy/nginx-test.conf').write_text(f"""user {worker};
worker_processes 1;
pid {r}/deploy/nginx-test.pid;
error_log {r}/logs/nginx-test-error.log;
events {{ worker_connections 256; }}
http {{
include {mime};
default_type application/octet-stream;
access_log {r}/logs/nginx-test-access.log;
include {r}/deploy/nginx.conf;
}}
""")
PYCONF
"$TEST_NGINX" -p "$TEST_ROOT/deploy/" -c "$TEST_ROOT/deploy/nginx-test.conf" -t
语法通过才启动独立测试Nginx:
"$TEST_NGINX" -p "$TEST_ROOT/deploy/" -c "$TEST_ROOT/deploy/nginx-test.conf"
curl --fail --max-time 10 http://127.0.0.1:14201/api/health
curl --fail --max-time 10 http://127.0.0.1:14201/system/device-iot
在管理电脑另开终端建立SSH隧道(非22端口时加-p 实际SSH端口),保持窗口打开:
ssh -N -L 14201:127.0.0.1:14201 -L 14301:127.0.0.1:14301 \
-L 15209:127.0.0.1:15209 -L 14211:127.0.0.1:14211 root@客户新机IP
电脑浏览器打开 http://127.0.0.1:14201,监管14301、大屏15209、移动14211。此时runtime-config中的127.0.0.1通过隧道访问,不对外开放测试端口。电脑端口已占用时先处理本机冲突,不任意改其中一处数字。
执行第23步清单和研发本次用例,只使用测试数据/隔离服务。测试后新机root只停止独立测试Nginx,必须带本次专用配置参数:
"$TEST_NGINX" -p "$TEST_ROOT/deploy/" -c "$TEST_ROOT/deploy/nginx-test.conf" -s quit
管理电脑隧道窗口按Ctrl+C退出。不能省略专用配置后对生产Nginx执行quit。
12.5 测试结束清理临时进程
只删除本次明确创建的测试进程;不使用 pm2 kill、delete all:
APP_HOME=$(getent passwd foodsafe | cut -d: -f6)
runuser -u foodsafe -- env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/bin:/bin \
/opt/foodsafe-node/bin/pm2 delete foodsafe-build-test
如果此前已经配置开机恢复,确认正式进程列表没变;保留本次测试日志。测试模式是否删除由 DBA 统一处理。
13|保存本次制品,明确是否涉及数据库升级
执行位置:实施人员与研发、DBA。
- 记录版本号、40位源码提交号、包SHA、打包与测试结果。
- 同一包用于两套系统;两套的数据库、地址和私有配置在部署时区分,不为第二套重新 build。
- 向研发确认本次是否有数据库结构变更。
- 首次 MySQL→达梦迁移走14~17;已迁达梦后的程序更新不再重新迁整库,按研发提供的达梦增量SQL单独审核执行。
sql-review/zywxb-mysql8-candidate.sql是历史 MySQL8 升级候选,不是达梦脚本,更不是每个版本都要重复执行。- 制品、SHA和手册另存管理电脑或独立备份存储,不能只在客户同一台机器留一份。
**成功标准:**知道将要发布哪个版本、SQL是否要执行、哪些业务已验收;不知道就不开始正式切换。
14|首次迁移:备份旧机两个库、配置和图片
执行位置:旧机管理员。仅首次 MySQL→达梦迁移使用;已上线后更新程序跳过14~17。
14.1 先确认原程序实际读取的配置
- 记录旧机两个后端的进程名、工作目录和启动命令。
- 找到每套有效的
services/gateway-service/.env,如旧版从根目录.env补读,也保存根目录那份。 - 确认 MySQL 实际库名和连接地址,不能只按目录名推断。
- 记录旧 Nginx 站点、图片路径、AI盒子文件路径,以及程序保存的完整图片URL。
- 配置备份只保存在权限受限位置,不上传进 Git 或公开页面。
14.2 分别导出两个 MySQL8 库
在旧机执行,--host 改真实数据库地址,使用有备份权限的用户。示例数据库用户 backup_user 要替换;-p 后不写密码,回车后交互输入。
umask 077
BACKUP_TIME=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="/实际备份磁盘/foodsafe-$BACKUP_TIME"
mkdir -p "$BACKUP_DIR"
mysqldump --host=实际MySQL地址 --port=3306 --user=backup_user -p \
--single-transaction --quick --routines --triggers --events \
--hex-blob --set-gtid-purged=OFF --no-tablespaces foodsafe \
> "$BACKUP_DIR/foodsafe.sql"
检查上一条命令退出码,再导第二套:
echo $?
# 上一条必须是0;非0时不要把残缺sql当备份
mysqldump --host=实际MySQL地址 --port=3306 --user=backup_user -p \
--single-transaction --quick --routines --triggers --events \
--hex-blob --set-gtid-purged=OFF --no-tablespaces foodsafe2 \
> "$BACKUP_DIR/foodsafe2.sql"
用 DBA 认可的 MySQL8 客户端;若出现未知选项或权限错误,修正客户端/权限后重新导出,不把报错输出作为SQL备份。--single-transaction 对非事务表不能保证一致性,正式最终备份仍须停写并禁止并发DDL。
ls -lh "$BACKUP_DIR"/*.sql
sha256sum "$BACKUP_DIR"/*.sql > "$BACKUP_DIR/databases.sha256"
14.3 单独备份文件与配置
下面示例源目录须换成旧机真实路径,第一套和第二套分别做:
mkdir -p "$BACKUP_DIR/foodsafe1/files" "$BACKUP_DIR/foodsafe1/private"
rsync -a /第一套旧项目真实路径/data/uploads/ "$BACKUP_DIR/foodsafe1/files/uploads/"
rsync -a /第一套旧项目真实路径/aiboxfile/ "$BACKUP_DIR/foodsafe1/files/aiboxfile/"
cp -a /第一套旧项目真实路径/services/gateway-service/.env "$BACKUP_DIR/foodsafe1/private/gateway.env"
旧版如果图片在根目录 uploads/ 或 services/gateway-service/uploads/,先按数据库真实URL、代码配置核对归属,再整理到统一的 files/uploads/ 暂存结构。**两个来源有同名不同内容文件时不直接合并覆盖,先核对数据库引用。**没有某类文件时登记“不使用”,跳过该命令,不创建空目录冒充备份。
mkdir -p "$BACKUP_DIR/foodsafe2/files" "$BACKUP_DIR/foodsafe2/private"
rsync -a /第二套旧项目真实路径/data/uploads/ "$BACKUP_DIR/foodsafe2/files/uploads/"
rsync -a /第二套旧项目真实路径/aiboxfile/ "$BACKUP_DIR/foodsafe2/files/aiboxfile/"
cp -a /第二套旧项目真实路径/services/gateway-service/.env "$BACKUP_DIR/foodsafe2/private/gateway.env"
另备份旧Nginx站点、证书和有效根 .env,不要只备份后端模板。备份后先做恢复验证,原始备份保留不修改。
15|首次迁移:在隔离 MySQL8 副本审核并升级
执行位置:独立迁移环境,DBA/研发。不能直接在旧生产库试脚本。
- 准备与源库兼容的临时 MySQL8,使用与生产不同的地址和专用迁移账号。
- 建立
foodsafe_migrate和foodsafe2_migrate,字符集/排序规则先与源库核对。 - 导入备份,核对无失败、表数、关键表数量与备份来源一致。
mysql --host=隔离MySQL地址 --user=迁移账号 -p foodsafe_migrate \
< /实际备份路径/foodsafe.sql
mysql --host=隔离MySQL地址 --user=迁移账号 -p foodsafe2_migrate \
< /实际备份路径/foodsafe2.sql
- 获取计划发布版本的
db/upgrade/zywxb.sql。这份历史脚本可能针对foodsafe2,两套来源结构可能不同,要分别比较。 - 研发审核脚本里的
USE、库名前缀、DROP/DELETE/TRUNCATE、重复列/索引、默认值和历史数据修改,改为隔离副本目标库;审核副本另存,不改原始备份。 - 在已经恢复的隔离库执行审核后的脚本。不能只做全局库名替换就认定安全;不能重复执行已经执行过的增量。
- 核对升级后的结构与本次代码要求一致,抽查关键业务和历史数据。
- 保存审核后脚本、执行日志、升级前后数量和版本号;演练通过后再准备正式最终迁移。
**成功标准:**两套隔离副本可重复恢复、升级成功、数据对账正确。**失败处理:**在隔离环境从原始备份恢复重演,不在旧生产库边试边改。
16|首次迁移:由厂家确认达梦账号,用 DTS 迁移
执行位置:DBA、达梦迁移客户端。新机达梦已经安装,不重装。
16.1 请厂家确认以下内容
- 达梦服务实际版本、实例地址/端口、字符集、大小写相关设置及可用授权。
- 两个独立业务用户及默认同名模式、对应表空间、所需权限和备份策略。
- 两套数据库数据文件和日志所在路径,不占用应用构建目录。
- 应用用各自业务用户,不使用 SYSDBA;不要给两套业务用户不必要的跨模式权限。
表空间物理文件路径由厂家根据现有实例确定。本手册不编造现场文件路径,也不要求新手套用未知CREATE USER/表空间命令。
16.2 DTS 逐套操作
- 在能同时连通隔离 MySQL8 和客户达梦的管理电脑打开达梦 DTS;Linux 图形客户端一般在安装目录
tool/下。 - 新建迁移工程,源选择 MySQL,目标选择达梦。
- 源填第一套已升级的
foodsafe_migrate;目标登录第一套达梦用户、选第一套模式。 - 测试两个连接。DTS 如缺 MySQL JDBC 驱动,使用与工具版本匹配的驱动,不能拿 Node 的 dmdb 代替。
- 选择需要的表、数据、约束、索引等,审核字段映射、长文本/二进制、时间、空值、自增/标识列、默认值和关键字。
- 先在演练目标执行,检查错误日志,不忽略失败表或无效对象。
- 对第二套
foodsafe2_migrate重复,目标改为第二套模式,不能仍用第一套用户。 - 保存工程与迁移日志。正式迁移时使用最终停写备份形成的副本,不能把演练旧数据当最终数据。
16.3 对账和应用探针
DBA至少对照两边:表数、每表行数、关键主键范围、最新业务时间;抽查人员/权限、设备、晨检/留样、库存和图片路径。自增/标识下一值须验证,避免新记录主键冲突。
迁移对账完成后,才允许本手册 check.sh 用业务账号做 SELECT 探针。探针通过不代表业务验收全部完成。
禁止把 mysqldump 导出的 MySQL SQL直接交达梦执行;也不把它当纯文本全局替换后直接上线。
17|首次迁移:把旧文件和私有配置传到新机暂存
执行位置:旧机管理员上传,新机 root核对。
- 把第一套
files/uploads/内容放到/data/migration/foodsafe1/data/uploads/。 - 把第一套
files/aiboxfile/放到/data/migration/foodsafe1/aiboxfile/。 - 第二套放对应
foodsafe2暂存目录。 - 私有配置放
/data/config/foodsafe/legacy1.env、legacy2.env,权限600,只供逐项合并。 - 不把旧项目整目录覆盖进新的运行目录;源码、node_modules、dist和私有.env不能这样混装。
- 传输后用 rsync 内容比较或备份SHA确认,抽查图片路径。
root检查:
ls -ld /data/migration/foodsafe1/data/uploads /data/migration/foodsafe2/data/uploads
chmod 600 /data/config/foodsafe/legacy1.env /data/config/foodsafe/legacy2.env
如果图存 OSS 或旧完整URL,单复制文件不能改地址。保持原存储服务时验证访问权限/网络;改本地存储时,必须先导出对象并由研发审核字段与路径转换。不要在全库盲目替换域名。
18|为两套正式系统分别填写部署参数
执行位置:新机 root。这一步不启动服务。
18.1 指定已通过验收的制品
export KIT=/data/releases/food_safety/foodsafe-本次版本号-kylin-x64
test -f "$KIT/RELEASE.json"
cp "$KIT/config/deploy.env.example" /data/config/foodsafe/deploy1.env
cp "$KIT/config/deploy.env.example" /data/config/foodsafe/deploy2.env
chmod 600 /data/config/foodsafe/deploy1.env /data/config/foodsafe/deploy2.env
只有初次创建配置才使用 cp。已有配置先备份和比较,不覆盖。
18.2 第一套 deploy1.env 完整示例
vi /data/config/foodsafe/deploy1.env
INSTANCE=1
APP_ROOT=/data/apps/foodsafe1
APP_USER=foodsafe
PUBLIC_HOST=CHANGE_ME
PUBLIC_SCHEME=http
LISTEN_HOST=0.0.0.0
SCHOOL_PORT=4201
REGULATOR_PORT=4301
SCREEN_PORT=5209
MOBILE_PORT=4211
GATEWAY_PORT=3300
NODE_BIN=/opt/foodsafe-node/bin/node
PM2_BIN=/opt/foodsafe-node/bin/pm2
NGINX_BIN=/usr/sbin/nginx
NGINX_VHOST_DIR=/etc/nginx/conf.d
INTEGRATION_UPSTREAM=
TLS_CERT_PATH=
TLS_KEY_PATH=
PUBLIC_HOST 换为客户终端能访问的新IP/域名,只填主机名,不带协议、路径和端口。Nginx两个路径换为07步核实值。需要HTTPS时填客户有效证书和私钥路径,域名要被证书覆盖。
18.3 第二套 deploy2.env 完整示例
vi /data/config/foodsafe/deploy2.env
INSTANCE=2
APP_ROOT=/data/apps/foodsafe2
APP_USER=foodsafe
PUBLIC_HOST=CHANGE_ME
PUBLIC_SCHEME=http
LISTEN_HOST=0.0.0.0
SCHOOL_PORT=4202
REGULATOR_PORT=4302
SCREEN_PORT=5210
MOBILE_PORT=4212
GATEWAY_PORT=3301
NODE_BIN=/opt/foodsafe-node/bin/node
PM2_BIN=/opt/foodsafe-node/bin/pm2
NGINX_BIN=/usr/sbin/nginx
NGINX_VHOST_DIR=/etc/nginx/conf.d
INTEGRATION_UPSTREAM=
TLS_CERT_PATH=
TLS_KEY_PATH=
独立 MEGO 集成服务确实存在才填写 INTEGRATION_UPSTREAM=http://实际服务IP:端口。没有服务就留空,对应接口会返回503,相关功能不算完成。该服务不是主食安后端,不能凭空填3300凑数。
**成功标准:**两份端口各不相同,目录/实例号正确;不含数据库密码。
19|首次安装两套程序
执行位置:新机 root。APP_ROOT 必须不存在。已有部署走25步,不执行本步覆盖。
bash "$KIT/scripts/install.sh" /data/config/foodsafe/deploy1.env
bash "$KIT/scripts/install.sh" /data/config/foodsafe/deploy2.env
每条成功后才继续下一条。脚本生成但不启用:
/data/apps/foodsafe1/services/gateway-service/.env
/data/apps/foodsafe1/deploy/ecosystem.config.cjs
/data/apps/foodsafe1/deploy/nginx.conf
/data/apps/foodsafe1/apps/*/dist/runtime-config.js
第二套为同样目录结构,前缀改 foodsafe2。
检查:
cp -a "$KIT/RELEASE.json" /data/apps/foodsafe1/deploy/RELEASE.json
cp -a "$KIT/RELEASE.json" /data/apps/foodsafe2/deploy/RELEASE.json
ls -l /data/apps/foodsafe1/deploy /data/apps/foodsafe2/deploy
ls -l /data/apps/foodsafe1/services/gateway-service/dist/services/gateway-service/src/main.js
**失败处理:**目标已存在先查是否运行中的系统或上次部分安装,不删除目录绕过保护。Node路径不通就回05步修正;驱动缺失回11步查制品完整性,不在运行目录临时安装随机版本。
20|逐套合并后端和前端配置
执行位置:新机 root;旧私有.env与新模板逐项比较。
20.1 第一套后端最小配置
vi /data/apps/foodsafe1/services/gateway-service/.env
NODE_ENV=production
HOST=127.0.0.1
PORT=3300
DB_KIND=dm
DM_HOST=客户达梦地址
DM_PORT=厂家实际端口
DM_USER=FOODSAFE_APP
DM_PASSWORD="本套真实口令"
JWT_SECRET="客户原有效密钥或批准的新密钥"
DB_AUTO_MIGRATE=0
DB_SYNC_COMMENTS=0
TZ=Asia/Shanghai
DEMO_SEED=false
DEMO_AI_JOB_ENABLED=0
AIBOX_SCHEDULER_ENABLED=0
DEWO_CALLBACK_SUBSCRIBE_ENABLED=0
YLT_TOKEN_REFRESH_ENABLED=0
AUDIT_ENABLED=true
USE_REDIS_CACHE=false
OSS_SWITCH=false
BRIGHT_KITCHEN_MODE=2
FOODSAFE_MOBILE_BASE_URL=http://客户地址:4211
ZHCT_BASE_URL=
BRIGHT_KITCHEN_MODE=2 只适用于客户继续使用原来的本地明厨模式;现场方案不同按旧有效值和研发确认调整,不机械填2。其他示例开关也不能代替客户业务选择。
20.2 第二套的差异
编辑 /data/apps/foodsafe2/services/gateway-service/.env,至少改为:
PORT=3301
DM_USER=FOODSAFE2_APP
DM_PASSWORD="第二套真实口令"
FOODSAFE_MOBILE_BASE_URL=http://客户地址:4212
其余逐项核对,不能复制第一套整份配置后忘了改用户、密钥、设备参数。当前程序没有 DM_SCHEMA 环境变量;实际登录用户/默认模式由厂家配置确定。
20.3 MQTT、视频、OSS等不能漏
下面是“核对清单”,不是要求全填,也不是模板已覆盖全部业务:
| 配置组 | 要核对什么 |
|---|---|
| 得沃 MQTT | DEWO_MQTT_URIS、DEWO_MQTT_USERNAME、DEWO_MQTT_PASSWORD;还要核对库中每台设备的连接和认证配置,设备配置可能优先于全局值 |
| 设备和回调 | 设备厂商/类型、学校食堂绑定、签名密钥、回调地址、Topic;旧新后端不能同时消费同一真实消息 |
| 明厨亮灶 | 前后端模式一致,AIbox/TrustIVS/YLT等实际服务地址及账号凭据、推拉流和播放器网络 |
| YLT | 源码中使用的小写 ylt_* 名称按原名核对,不能自行改为大写 |
| OSS / 图片 | 实际存储模式、桶/区域/域名/凭据、历史完整URL;不需要云存储时不要照搬云凭据,OSS_SWITCH=false 本身不能证明所有业务上传路径都本地化 |
| 前厅联动 | ZHCT_BASE_URL 与该版本使用的联动账号、密码、权限范围;新IP/协议正确 |
| MEGO | 独立集成服务、EQUIPMENT_CODE、实际设备配置;不要保留示例设备编号 |
| Redis / 审计 | 客户是否实际依赖、连接参数和权限;不把示例 false 直接作为关闭客户业务的决定 |
| 上传和公网地址 | 文件大小、后端限制、Nginx 50m限制是否匹配;设备需访问的图片地址是否包含真实端口 |
| 定时任务 | 正式切换前核对真实任务是否要启用;演示AI任务保持关闭 |
MQTT格式例子,只填真实客户配置:
DEWO_MQTT_URIS='tcp://实际Broker地址:1883'
DEWO_MQTT_USERNAME=实际账号
DEWO_MQTT_PASSWORD='实际口令'
不要使用聊天里旧环境的IP和密码作为默认值。新后端是否订阅/下发由实际业务与设备配置共同决定,填三行不代表设备闭环已验收。
布尔变量按代码约定填值。任务开关常用 0/1,审计/Redis常用 true/false;不要把所有变量统一写false。特别是设备签名旧逻辑可能把非空字符串 false 也视为启用,沿用厂家/研发确认的有效写法。
20.4 前端 integration.config.json
安装脚本会生成每套 runtime-config.js,但不会自动完成所有前端集成配置。检查:
cat /data/apps/foodsafe1/apps/web-school/dist/integration.config.json
vi /data/apps/foodsafe1/apps/web-school/dist/integration.config.json
依据实际业务填写,例:
{
"SCHOOL_INTEGRATION_BASE": "/integration",
"MEGO_CANDIDATES": "http://实际可访问的集成服务地址:实际端口",
"EQUIPMENT_CODE": "本套真实设备编号",
"BRIGHT_KITCHEN_MODE": 2,
"OSS_SWITCH": false
}
这是说明字段的例子:没有 MEGO 时可删除 MEGO_CANDIDATES、EQUIPMENT_CODE,按项目方案禁用或不使用相应功能;有MEGO时填真实值并验收,不能留 localhost:4001/4003 或 DEMO-EC-0001。浏览器里的 localhost 指用户自己的电脑。
JSON不支持注释,修改后验证:
python3 -m json.tool /data/apps/foodsafe1/apps/web-school/dist/integration.config.json > /dev/null
第二套单独编辑并验证,同步核对 runtime-config.js 的移动端为4212、大屏为5210。
使用无痕窗口验证,或在客户批准后清除该站点旧集成配置缓存;localStorage可覆盖部分参数。不要在不了解影响时清理浏览器所有站点的数据。前端有些配置在启动早期捕获,必须通过浏览器网络请求确认真实目标,而不是只看配置文件。
20.5 权限和配置优先级
chown foodsafe:"$(id -gn foodsafe)" /data/apps/foodsafe1/services/gateway-service/.env \
/data/apps/foodsafe2/services/gateway-service/.env
chmod 600 /data/apps/foodsafe1/services/gateway-service/.env \
/data/apps/foodsafe2/services/gateway-service/.env
私有 .env 是 dotenv文件,不要 source;密码含 #、空格时用引号。公开 deploy.env 则是受控 Shell 参数文件,按本手册内容填写,不能粘贴陌生脚本进去。
后端优先读 services/gateway-service/.env,进程环境变量仍有更高优先级。根 .env 不应再放一份互相冲突的配置;PM2更新时也要检查旧进程环境覆盖。
**成功标准:**两套数据库、设备、存储、前端地址各自正确,所有启用的业务都有配置,禁用项有明确记录。
21|把旧图片和附件放入两套安装目录
执行位置:新机 root;先确认19步已安装。
21.1 第一套
test -f /data/apps/foodsafe1/deploy/ecosystem.config.cjs
mkdir -p /data/apps/foodsafe1/data/uploads /data/apps/foodsafe1/aiboxfile
rsync -a /data/migration/foodsafe1/data/uploads/ /data/apps/foodsafe1/data/uploads/
rsync -a /data/migration/foodsafe1/aiboxfile/ /data/apps/foodsafe1/aiboxfile/
chown -R foodsafe:"$(id -gn foodsafe)" /data/apps/foodsafe1/data /data/apps/foodsafe1/aiboxfile
rsync -rcn --itemize-changes /data/migration/foodsafe1/data/uploads/ /data/apps/foodsafe1/data/uploads/
rsync -rcn --itemize-changes /data/migration/foodsafe1/aiboxfile/ /data/apps/foodsafe1/aiboxfile/
21.2 第二套
test -f /data/apps/foodsafe2/deploy/ecosystem.config.cjs
mkdir -p /data/apps/foodsafe2/data/uploads /data/apps/foodsafe2/aiboxfile
rsync -a /data/migration/foodsafe2/data/uploads/ /data/apps/foodsafe2/data/uploads/
rsync -a /data/migration/foodsafe2/aiboxfile/ /data/apps/foodsafe2/aiboxfile/
chown -R foodsafe:"$(id -gn foodsafe)" /data/apps/foodsafe2/data /data/apps/foodsafe2/aiboxfile
rsync -rcn --itemize-changes /data/migration/foodsafe2/data/uploads/ /data/apps/foodsafe2/data/uploads/
rsync -rcn --itemize-changes /data/migration/foodsafe2/aiboxfile/ /data/apps/foodsafe2/aiboxfile/
rsync -rcn 为只读内容比较,正常没有文件差异输出。末尾 / 表示复制目录里的内容,避免形成 uploads/uploads/。不要加 --delete;确实没有AI文件的系统跳过对应两条同步/比较并记录。
常见对应:
| 数据库/页面地址 | 第一套实际文件 |
|---|---|
| /files/view/2026/10/20/a.jpg | /data/apps/foodsafe1/data/uploads/2026/10/20/a.jpg |
| /api/files/view/2026/10/20/a.jpg | 同上,Nginx去掉/api后转后端 |
| /static/盒子目录/a.txt | 通常为/data/apps/foodsafe1/aiboxfile/盒子目录/a.txt,以代码和旧机实际为准 |
apps/*/dist/static/ 里的logo等是程序资源,旧业务照片不要随意塞进去。数据库保存的是旧完整URL时另按17步处理。
22|启用第一套,再启用第二套 Nginx 和 PM2
执行位置:新机 root。首次启用专用脚本不用于已运行实例的更新。
22.1 第一套先检查后启动
export KIT=/data/releases/food_safety/foodsafe-本次版本号-kylin-x64
bash "$KIT/scripts/check.sh" /data/config/foodsafe/deploy1.env
bash "$KIT/scripts/activate.sh" /data/config/foodsafe/deploy1.env
只有check通过再执行activate。脚本会:
- 校验本套配置、驱动、四端dist,并用真实业务账号做达梦只读探针。
- 检查本套五个端口没有占用、PM2没有同名进程。
- 把本套
deploy/nginx.conf安装为实际 include目录下的foodsafe-release-1.conf。 - 执行
nginx -t;语法正确才启动后端。 - 使用 foodsafe账号启动
foodsafe-1,cwd是第一套项目根,运行编译后的嵌套入口。 - 等待健康检查成功,再 reload Nginx并保存PM2列表。
22.2 检查第一套,再启动第二套
curl --fail --max-time 10 http://127.0.0.1:4201/api/health
curl --fail --max-time 10 http://127.0.0.1:4201/system/device-iot
bash "$KIT/scripts/check.sh" /data/config/foodsafe/deploy2.env
bash "$KIT/scripts/activate.sh" /data/config/foodsafe/deploy2.env
curl --fail --max-time 10 http://127.0.0.1:4202/api/health
深链应返回前端页面;API应返回接口数据,不能是SPA首页。/health成功只是基本响应,还要23步。
22.3 Nginx配置到底在哪、怎么查看
生成位置:/data/apps/foodsafe1/deploy/nginx.conf
启用位置:实际NGINX_VHOST_DIR/foodsafe-release-1.conf
第二套: /data/apps/foodsafe2/deploy/nginx.conf
实际NGINX_VHOST_DIR/foodsafe-release-2.conf
less /data/apps/foodsafe1/deploy/nginx.conf
# 实际Nginx不是下面路径时换成登记值
/usr/sbin/nginx -t
/usr/sbin/nginx -T 2>&1 | less
无需另手写一份站点配置。生成文件本身包含四个完整 server,分别服务学校、监管、大屏和移动端,还包含upstream、WebSocket代理、文件转发和SPA深链规则。只展示核心路由帮助排障:
# 节选,实际以本套生成的完整文件为准,不要用这段替换完整配置
location ^~ /api/ { proxy_pass http://foodsafe_release_1/; }
location ^~ /websocket/ { proxy_pass http://foodsafe_release_1; }
location / { try_files $uri $uri/ /index.html; }
/api/ 尾斜杠对应去前缀;不要随便增删。不能把整个 /system/ 都代理后端,/system/device-iot 等是前端页面。生成器对确实属于后端的少量system接口做精确匹配。
本地文件代理、设备图片绝对地址还要确认原始端口是否保留;端口错时核查后端该版本的 PUBLIC_BASE_URL/设备地址配置,以及Nginx转发的Host,不直接全局替换代理头后上线。
22.4 失败如何处理
- 配置/端口/同名进程冲突:脚本会停止,先查旧服务归属,不 kill 不明进程。
- Nginx语法失败:不会重载旧站点;修正本套生成配置或安装路径。
- 后端启动或健康失败:撤回本次新增进程和站点;查看本套logs。
- reload失败:脚本尝试撤回新增站点和进程;核对服务日志。
- 仅
pm2 save失败:服务可能已经运行,只是未持久化;修复权限再save,不能重复执行首次activate。
后台监听默认127.0.0.1,由Nginx转发。只有设备确需直连3300/3301时,才按网络方案改HOST并限制来源网段。开放八个前端端口只对客户需要的终端网段;不要因为客户可出公网就把业务端口对全互联网放开。
23|两套系统逐项验收,不能只看首页
执行位置:管理电脑浏览器、实施人员、DBA。正式接设备前先限制访问和订阅。
23.1 确认访问入口
| 入口 | 第一套 | 第二套 |
|---|---|---|
| 学校 | http://客户地址:4201 | http://客户地址:4202 |
| 监管 | http://客户地址:4301 | http://客户地址:4302 |
| 大屏 | http://客户地址:5209 | http://客户地址:5210 |
| 移动 | http://客户地址:4211 | http://客户地址:4212 |
客户端打不开但本机curl正常时,检查实际IP、监听、防火墙、网络ACL与HTTPS证书,不重新build碰运气。
23.2 每套都检查并登记
- 登录真实客户测试账号,学校/食堂/角色权限正确。
- 查出迁移前已知的人员、设备、晨检、留样、库存记录,与DBA对账结果一致。
- 抽查健康证、晨检、留样旧图及真实AI盒子文件;响应应为正确图片/文件,不是HTTP200的HTML首页。
- 经客户批准在测试范围做一次新增、修改和上传,刷新后仍存在,数据和图片都落本套。
- 验证日期筛选、报表、导出内容和北京时间。
- 访问
/system/device-iot、/bk-local/channels后刷新,不能404,也不能返回后端错误JSON。 - 第一套二维码进入4211,第二套进入4212;大屏分别5209/5210。扫描打开验证,不只看二维码显示。
- 通过浏览器F12的Network看真实请求目标,避免浏览器localhost、开发服务器或另一套端口。
- 验证前厅联动、视频、MQTT认证、回调、设备下发与回执、任务去重;未接设备的项目标“未验收”,不能自动算通过。
- 第一套查看/新增的数据不能出现在第二套不该出现的位置;使用不同学校和独有记录证明未串模式。
- PM2只有两套正式进程;试运行时不同时启用旧机和新机真实任务。
- 研发给出的本次变更用例全部通过,记录未通过项和处理结果。
前端确认是dist:页面源代码和网络不出现 /@vite/client、/src/main.ts 开发入口,加载的是带hash的静态资源。数据库确认是达梦:业务配置DB_KIND=dm、实际业务用户探针和DBA真实查询对应,不仅凭页面有数据判断。
**成功标准:**上述验收按两套分别留记录,所有计划上线的业务通过;没有把“系统能打开”当“业务全对”。
24|开机恢复、正式切换和最终备份
执行位置:新机 root、旧机管理员和DBA;按客户窗口。
24.1 PM2注册开机恢复
先保存正式进程列表,确认没有临时测试进程:
APP_HOME=$(getent passwd foodsafe | cut -d: -f6)
runuser -u foodsafe -- env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/bin:/bin /opt/foodsafe-node/bin/pm2 list
runuser -u foodsafe -- env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/bin:/bin /opt/foodsafe-node/bin/pm2 save
只在还没有正确服务时注册,已有服务先核对,不覆盖其他业务:
env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
/opt/foodsafe-node/bin/pm2 startup systemd -u foodsafe --hp "$APP_HOME"
systemctl cat pm2-foodsafe
systemctl enable pm2-foodsafe
核对Node、PM2、HOME、PM2_HOME是本手册路径。pm2 save不等于已通过重启恢复测试。安排演练窗口由运维验证systemd从保存列表恢复,并检查两套接口和文件。整机重启会同时影响达梦、前厅等同机业务,须统一安排,不直接执行reboot。
24.2 正式切换顺序
- 演练全部通过后,通知客户维护窗口,记录旧机最后业务时间。
- 关闭旧业务入口并停止旧两套后端、定时任务、回调消费和自动拉起机制,等在途请求结束。
- 确认旧库不再增长,做最终两库备份和文件增量备份。
- 用最终备份重建隔离MySQL升级副本、完成最终DTS迁移;不要覆盖已产生新业务的达梦模式。
- 最终文件增量同步,核对新库、新图同一截止点。
- 两套新机业务核对通过后,切DNS/IP、客户端与设备地址,按方案只在新机启用真实任务/订阅。
- 再做两套登录、数据、图片、设备和报表验收;记录切换时刻及是否已有新写入。
- DBA执行新达梦备份并确认恢复能力;文件和配置也备份至独立存储。
- 旧机保持冻结用于回退观察,不立即删除、不再作为正常写入端。
24.3 回旧服务器的限制
若新机尚无新业务,可按预案切回旧入口并只恢复旧机任务;新机任务先停。若新机已写入新业务,不能直接切回旧库,否则会丢新记录;先由DBA处理数据追平/回迁和设备消息截止点。
25|以后代码变更:重新制作新制品
执行位置:新机 foodsafe-build。原系统继续运行。
25.1 获取研发批准的新版本
sudo -iu foodsafe-build
export PATH=/opt/foodsafe-node/bin:/usr/local/bin:/usr/bin:/bin
cd /data/build/food_safety
git status --short --branch
git branch --show-current
确认干净、standard_wxb,才执行:
git pull --ff-only
git log -5 --oneline
由研发确认40位提交号、相关SQL和配置变更。若源码已合入发布适配或依赖结构有变,先更新本手册配套工具至研发验证版,再打包,不删工具里的保护判断。
25.2 新版本号重新打包
export RELEASE_COMMIT=CHANGE_ME_研发提供的40位提交号
export RELEASE_VERSION=20261025-CHANGE_ME-r1
nice -n 10 bash \
/opt/foodsafe-build-tools/foodsafe-onsite-build-tools/tools/build-release.sh \
/data/build/food_safety "$RELEASE_COMMIT" "$RELEASE_VERSION"
命令中的占位内容必须先替换。走第11~13步校验/隔离验收。新版本失败不会自动更新在线程序。更新纯前端或后端也建议仍产出同一版本完整制品,避免两套系统的模块来源混杂。
通常不需要重装nvm/Node/PM2/Nginx/Mosquitto,不重迁MySQL,不复制整份旧源码覆盖应用,不重新安装运行目录依赖。
26|更新第一套:先备份,再替换程序
执行位置:新机 root;客户维护窗口。这里只更新第一套,通过后才做第二套。
26.1 确认新包、旧运行目录和备份位置
export NEW_KIT=/data/releases/food_safety/foodsafe-新版本号-kylin-x64
export APP_ROOT=/data/apps/foodsafe1
export INSTANCE=1
export DEPLOY_ENV=/data/config/foodsafe/deploy1.env
export BACKUP_DIR="/data/backups/foodsafe/$(date +%Y%m%d-%H%M%S)-instance1"
test -f "$NEW_KIT/RELEASE.json"
test -f "$APP_ROOT/deploy/ecosystem.config.cjs"
cat "$NEW_KIT/RELEASE.json"
26.2 备份程序、配置和站点
install -d -m 700 "$BACKUP_DIR"
rsync -a "$APP_ROOT/" "$BACKUP_DIR/app/" \
--exclude='/data/' --exclude='/aiboxfile/' --exclude='/logs/'
cp -a "$DEPLOY_ENV" "$BACKUP_DIR/deploy.env"
上述备份不含体量大的业务数据和日志。业务文件由既定备份方案保护;本次SQL或功能会改写图片/附件时,应另做一致性文件快照/备份。没有恢复能力不要上线。
加载本套公开部署参数并备份真实站点:
set -a
source "$DEPLOY_ENV"
set +a
cp -a "$NGINX_VHOST_DIR/foodsafe-release-$INSTANCE.conf" "$BACKUP_DIR/nginx.conf"
.env已经随app备份在权限700目录,不输出内容。DBA在本窗口做达梦备份,确认数据库增量脚本和回退策略;无SQL变更也应有可用近期备份。
26.3 先停止本套写入
- 确认本套客户用户已退出或入口处于维护状态。
- 暂停/缓冲本套设备写入,避免更新中请求丢失;不能影响第二套设备。
- 停止本套进程:
APP_HOME=$(getent passwd foodsafe | cut -d: -f6)
runuser -u foodsafe -- env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/bin:/bin /opt/foodsafe-node/bin/pm2 stop foodsafe-1
若本次数据库变化会影响第二套或其他应用,不能只停第一套就执行;由研发/DBA统一发布顺序。
26.4 替换程序,保留本套数据和私有配置
新payload不含真实.env,但替换node_modules必须删除旧残留依赖,所以限定在明确的程序子目录同步。先核对变量:
printf '新制品=%s\n目标=%s\n' "$NEW_KIT" "$APP_ROOT"
test "$APP_ROOT" = /data/apps/foodsafe1
(cd "$NEW_KIT" && sha256sum -c PAYLOAD.sha256 > /dev/null)
确认后逐项执行;这些 --delete 只限下列程序目录,绝不能扩大到APP_ROOT、data、aiboxfile或备份目录:
rsync -a --delete "$NEW_KIT/payload/node_modules/" "$APP_ROOT/node_modules/"
rsync -a --delete "$NEW_KIT/payload/services/gateway-service/node_modules/" \
"$APP_ROOT/services/gateway-service/node_modules/"
rsync -a --delete "$NEW_KIT/payload/services/gateway-service/dist/" \
"$APP_ROOT/services/gateway-service/dist/"
rsync -a --delete "$NEW_KIT/payload/libs/" "$APP_ROOT/libs/"
cp -a "$NEW_KIT/payload/services/gateway-service/package.json" \
"$APP_ROOT/services/gateway-service/package.json"
for app in web-school web-regulator screen mobile-device; do
rsync -a --delete --exclude='/runtime-config.js' --exclude='/integration.config.json' \
"$NEW_KIT/payload/apps/$app/dist/" "$APP_ROOT/apps/$app/dist/"
done
这里主动保留运行时地址和现有 integration.config.json;若新版本这两个文件的字段或语义变化,按研发说明手工合并,不能永远照搬旧文件。
此流程假设仍是本文既定payload结构。以后新增工作区运行依赖、服务或变更入口时,研发必须更新工具和本节替换清单,不能继续靠旧清单漏拷。
26.5 合并新配置、执行审核SQL
- 比较新版本发布说明和私有.env,新增参数逐项合并,保留本套DM/JWT/设备/存储配置。
- 需要SQL时,由DBA执行与新版本匹配、经过演练的达梦增量SQL,保存回执。不能重新执行MySQL的zywxb.sql。
- 用新工具重新生成本套PM2/Nginx/运行地址配置:
set -a
source "$DEPLOY_ENV"
set +a
python3 "$NEW_KIT/scripts/configure.py"
bash "$NEW_KIT/scripts/check.sh" "$DEPLOY_ENV"
生成器不会覆盖已存在的私有.env,但会更新runtime-config.js和deploy目录的配置。确认integration.config.json仍是本套有效值。
26.6 更新站点、启动并验收
install -m 644 "$APP_ROOT/deploy/nginx.conf" \
"$NGINX_VHOST_DIR/foodsafe-release-$INSTANCE.conf"
"$NGINX_BIN" -t
**上一条不通过立即停下,恢复备份站点,禁止reload。**通过后,以同一用户/同一PM2_HOME重建本套进程,避免旧进程环境残留;不删除其他进程:
runuser -u foodsafe -- env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/bin:/bin /opt/foodsafe-node/bin/pm2 delete foodsafe-1
cd "$APP_ROOT"
runuser -u foodsafe -- env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/bin:/bin \
/opt/foodsafe-node/bin/pm2 start deploy/ecosystem.config.cjs
确认后端响应,再重载Nginx:
curl --fail --max-time 10 http://127.0.0.1:3300/health
"$NGINX_BIN" -s reload
curl --fail --max-time 10 http://127.0.0.1:4201/api/health
启动可能需要数秒,首次curl失败时先查进程和日志,再等待重试,不把失败命令直接跳过。执行23步验收和本次变更用例,通过后保存进程并恢复本套入口/设备:
runuser -u foodsafe -- env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/bin:/bin /opt/foodsafe-node/bin/pm2 save
cp -a "$NEW_KIT/RELEASE.json" "$APP_ROOT/deploy/RELEASE.json"
**成功标准:**第一套实际运行新版本、关键业务通过、第二套仍正常;再继续27步。
27|更新第二套:同一制品,独立备份和执行
执行位置:新机root;第一套验收通过后执行。使用与第一套相同的NEW_KIT,不重新打包。下面已经写成第二套命令,不需要你临时修改第一套数字。
27.1 确认新包、旧运行目录和备份位置
export NEW_KIT=/data/releases/food_safety/foodsafe-新版本号-kylin-x64
export APP_ROOT=/data/apps/foodsafe2
export INSTANCE=2
export DEPLOY_ENV=/data/config/foodsafe/deploy2.env
export BACKUP_DIR="/data/backups/foodsafe/$(date +%Y%m%d-%H%M%S)-instance2"
test -f "$NEW_KIT/RELEASE.json"
test -f "$APP_ROOT/deploy/ecosystem.config.cjs"
cat "$NEW_KIT/RELEASE.json"
27.2 备份程序、配置和站点
install -d -m 700 "$BACKUP_DIR"
rsync -a "$APP_ROOT/" "$BACKUP_DIR/app/" \
--exclude='/data/' --exclude='/aiboxfile/' --exclude='/logs/'
cp -a "$DEPLOY_ENV" "$BACKUP_DIR/deploy.env"
上述备份不含体量大的业务数据和日志。业务文件由既定备份方案保护;本次SQL或功能会改写图片/附件时,应另做一致性文件快照/备份。没有恢复能力不要上线。
加载本套公开部署参数并备份真实站点:
set -a
source "$DEPLOY_ENV"
set +a
cp -a "$NGINX_VHOST_DIR/foodsafe-release-$INSTANCE.conf" "$BACKUP_DIR/nginx.conf"
.env已经随app备份在权限700目录,不输出内容。DBA在本窗口做达梦备份,确认数据库增量脚本和回退策略;无SQL变更也应有可用近期备份。
27.3 先停止本套写入
- 确认本套客户用户已退出或入口处于维护状态。
- 暂停/缓冲本套设备写入,避免更新中请求丢失;不能影响第二套设备。
- 停止本套进程:
APP_HOME=$(getent passwd foodsafe | cut -d: -f6)
runuser -u foodsafe -- env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/bin:/bin /opt/foodsafe-node/bin/pm2 stop foodsafe-2
若本次数据库变化会影响第二套或其他应用,不能只停第二套就执行;由研发/DBA统一发布顺序。
27.4 替换程序,保留本套数据和私有配置
新payload不含真实.env,但替换node_modules必须删除旧残留依赖,所以限定在明确的程序子目录同步。先核对变量:
printf '新制品=%s\n目标=%s\n' "$NEW_KIT" "$APP_ROOT"
test "$APP_ROOT" = /data/apps/foodsafe2
(cd "$NEW_KIT" && sha256sum -c PAYLOAD.sha256 > /dev/null)
确认后逐项执行;这些 --delete 只限下列程序目录,绝不能扩大到APP_ROOT、data、aiboxfile或备份目录:
rsync -a --delete "$NEW_KIT/payload/node_modules/" "$APP_ROOT/node_modules/"
rsync -a --delete "$NEW_KIT/payload/services/gateway-service/node_modules/" \
"$APP_ROOT/services/gateway-service/node_modules/"
rsync -a --delete "$NEW_KIT/payload/services/gateway-service/dist/" \
"$APP_ROOT/services/gateway-service/dist/"
rsync -a --delete "$NEW_KIT/payload/libs/" "$APP_ROOT/libs/"
cp -a "$NEW_KIT/payload/services/gateway-service/package.json" \
"$APP_ROOT/services/gateway-service/package.json"
for app in web-school web-regulator screen mobile-device; do
rsync -a --delete --exclude='/runtime-config.js' --exclude='/integration.config.json' \
"$NEW_KIT/payload/apps/$app/dist/" "$APP_ROOT/apps/$app/dist/"
done
这里主动保留运行时地址和现有 integration.config.json;若新版本这两个文件的字段或语义变化,按研发说明手工合并,不能永远照搬旧文件。
此流程假设仍是本文既定payload结构。以后新增工作区运行依赖、服务或变更入口时,研发必须更新工具和本节替换清单,不能继续靠旧清单漏拷。
27.5 合并新配置、执行审核SQL
- 比较新版本发布说明和私有.env,新增参数逐项合并,保留本套DM/JWT/设备/存储配置。
- 需要SQL时,由DBA执行与新版本匹配、经过演练的达梦增量SQL,保存回执。不能重新执行MySQL的zywxb.sql。
- 用新工具重新生成本套PM2/Nginx/运行地址配置:
set -a
source "$DEPLOY_ENV"
set +a
python3 "$NEW_KIT/scripts/configure.py"
bash "$NEW_KIT/scripts/check.sh" "$DEPLOY_ENV"
生成器不会覆盖已存在的私有.env,但会更新runtime-config.js和deploy目录的配置。确认integration.config.json仍是本套有效值。
27.6 更新站点、启动并验收
install -m 644 "$APP_ROOT/deploy/nginx.conf" \
"$NGINX_VHOST_DIR/foodsafe-release-$INSTANCE.conf"
"$NGINX_BIN" -t
**上一条不通过立即停下,恢复备份站点,禁止reload。**通过后,以同一用户/同一PM2_HOME重建本套进程,避免旧进程环境残留;不删除其他进程:
runuser -u foodsafe -- env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/bin:/bin /opt/foodsafe-node/bin/pm2 delete foodsafe-2
cd "$APP_ROOT"
runuser -u foodsafe -- env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/bin:/bin \
/opt/foodsafe-node/bin/pm2 start deploy/ecosystem.config.cjs
确认后端响应,再重载Nginx:
curl --fail --max-time 10 http://127.0.0.1:3301/health
"$NGINX_BIN" -s reload
curl --fail --max-time 10 http://127.0.0.1:4202/api/health
启动可能需要数秒,首次curl失败时先查进程和日志,再等待重试,不把失败命令直接跳过。执行23步验收和本次变更用例,通过后保存进程并恢复本套入口/设备:
runuser -u foodsafe -- env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/bin:/bin /opt/foodsafe-node/bin/pm2 save
cp -a "$NEW_KIT/RELEASE.json" "$APP_ROOT/deploy/RELEASE.json"
**成功标准:**第二套实际运行新版本、关键业务通过、第一套仍正常;完成两套交叉验收并归档。
28|程序更新失败如何回退
执行位置:新机 root、DBA;先停止失败实例,暂停本套写入。
28.1 先判断数据库能不能配旧程序
- 没有结构/数据变更、旧程序仍兼容:可以只回退程序和配置。
- 有不兼容SQL或新版本已产生旧版不认识的数据:先由DBA/研发确认恢复与追平方案,不只恢复代码。
- 不能直接把数据库恢复到早期时间点而丢弃切换后的业务;必须记录和处理新增数据。
28.2 第一套程序回退示例
先填写本次真实备份路径,不能用“最近一个”猜测:
export APP_ROOT=/data/apps/foodsafe1
export BACKUP_DIR=/data/backups/foodsafe/本次真实备份目录-instance1
export DEPLOY_ENV=/data/config/foodsafe/deploy1.env
APP_HOME=$(getent passwd foodsafe | cut -d: -f6)
test -f "$BACKUP_DIR/app/deploy/ecosystem.config.cjs"
runuser -u foodsafe -- env HOME="$APP_HOME" PM2_HOME="$APP_HOME/.pm2" \
PATH=/opt/foodsafe-node/bin:/usr/bin:/bin /opt/foodsafe-node/bin/pm2 stop foodsafe-1
逐个恢复程序目录,不覆盖业务数据目录:
for part in node_modules libs; do
rsync -a --delete "$BACKUP_DIR/app/$part/" "$APP_ROOT/$part/"
done
for part in dist node_modules; do
rsync -a --delete "$BACKUP_DIR/app/services/gateway-service/$part/" \
"$APP_ROOT/services/gateway-service/$part/"
done
cp -a "$BACKUP_DIR/app/services/gateway-service/package.json" \
"$APP_ROOT/services/gateway-service/package.json"
cp -a "$BACKUP_DIR/app/services/gateway-service/.env" \
"$APP_ROOT/services/gateway-service/.env"
for app in web-school web-regulator screen mobile-device; do
rsync -a --delete "$BACKUP_DIR/app/apps/$app/dist/" "$APP_ROOT/apps/$app/dist/"
done
rsync -a --delete "$BACKUP_DIR/app/deploy/" "$APP_ROOT/deploy/"
cp -a "$BACKUP_DIR/deploy.env" "$DEPLOY_ENV"
set -a
source "$DEPLOY_ENV"
set +a
cp -a "$BACKUP_DIR/nginx.conf" "$NGINX_VHOST_DIR/foodsafe-release-$INSTANCE.conf"
"$NGINX_BIN" -t
语法通过且DBA确认数据库适配旧程序后,按26.6同一方式delete/start 本套PM2、检查3300健康、reload Nginx、检查4201,并重新验收/save。
第二套单独选择它自己的备份、配置、PM2名称和端口;不要把第一套备份恢复给第二套。失败日志和失败制品保留给研发,不先删掉证据。
29|以后每次更新,照这张短清单核对
- 研发给:已测试提交号、改动说明、配置变更、达梦SQL及回退要求。
- 打包用户:拉标准分支,干净工作区,固定提交号,新版本号打包。
- 校验:包SHA、payload、驱动、版本清单。
- 隔离验收:测试模式、测试端口、测试图片;不消费真实设备。
- 维护窗口:数据库备份、程序和私有配置备份、文件恢复能力确认。
- 第一套:停写→停进程→替换程序→合并配置/审核SQL→检查→启动→验收→保存。
- 第二套:单独备份,重复同样流程;两套不串库不串图不串设备。
- 归档:新旧制品、SHA、发布记录、验收记录、备份位置和恢复方法。
- 定期清理:由实施人员核对后清理确定无用的打包中间目录;至少保留当前和上一版可恢复制品。不要删除应用目录、数据库文件或正在用的Node运行时。
30|常见报错:先查什么
| 现象 | 先检查 | 不要怎么做 |
|---|---|---|
| nvm找不到 | 当前用户、NVM_DIR、是否加载nvm.sh;打包可用固定/opt Node PATH | 反复在root下重装 |
| node/pnpm找不到 | export PATH=/opt/foodsafe-node/bin:/usr/local/bin:/usr/bin:/bin |
在运行目录临时装另一个Node |
| frozen-lockfile失败 | 研发是否同步提交package.json和锁文件、pnpm版本是否固定 | 删锁文件或pnpm update |
| 发布适配断言失败 | 该提交是否已合入或改过相关源码,交研发更新工具 | 删除assert、强行重复打补丁 |
| tsc/vite报错 | 本次日志、提交号、依赖、实际失败文件 | 使用报错后残留的dist发布 |
| esbuild/native依赖缺失 | 本机架构、下载是否完整、是否新增需安装脚本的依赖 | 在客户机无条件开启所有依赖脚本 |
| Cannot find module dmdb/qrcode | 完整node_modules、.pnpm和链接、制品校验 | 只拷main.js或随意安装最新版 |
| 达梦连不上 | 实例地址/端口、厂家用户/口令、网络权限、当前实际配置 | 把DB_KIND改回mysql凑启动 |
| 页面有数据但疑似不是达梦 | 实际进程环境、DB_KIND、DM用户、探针和DBA查询 | 仅凭页面判断数据库类型 |
| Nginx502 | PM2状态、后端端口、cwd、日志、权限 | 用另一个root后端抢端口 |
| 刷新页面404/JSON | SPA规则、是否把整个system代理给后端 | 把所有路径都代理 |
| 旧图404 | 数据库URL、data/uploads路径、cwd、读权限、OSS来源 | 把所有图平铺到dist/static |
| 视频/MEGO请求localhost | integration.config.json、runtime-config.js、localStorage、真实服务 | 重新build后不看网络请求 |
| MQTT没反应 | 全局与设备级连接配置、认证/Topic、任务开关、设备归属 | 用另一个系统broker密码试 |
| 第二套看到第一套数据 | deploy2参数、私有DM用户、PM2环境、真实请求端口 | 重导整个数据库掩盖问题 |
| 重启后系统不起来 | pm2-foodsafe、HOME/PM2_HOME、dump、Node路径、达梦/Nginx启动 | 同时起root和foodsafe两套守护 |
日志查看示例(新机root;只读):
tail -n 100 /data/apps/foodsafe1/logs/error*.log
tail -n 100 /data/apps/foodsafe2/logs/error*.log
journalctl -u pm2-foodsafe -n 80 --no-pager
journalctl -u mosquitto -n 80 --no-pager
日志可能含客户数据和凭据,不直接复制全量到公开文档;提供给研发前先脱敏。
31|现场记录与参考资料
每次记录:执行人、时间、源码提交、工具版本、包SHA、两套DM用户标签、SQL文件与执行结果、备份路径、上线与回退时间、八个入口验收和设备测试结果。口令不写入记录表。
本手册依据 standard_wxb@bc3267ef4c97fcfc38c1add26a2a9e1d94263275 的package.json、锁文件、五个build脚本,以及原制品发布适配、安装/检查/配置/启用脚本整理。后续业务代码变更不由手册自动保证正确,需研发测试和现场验收闭环。
官方参考:
- pnpm 10 安装命令:冻结锁文件与安装脚本选项。
- nvm 官方安装资料:账号范围和加载方式。
- 达梦 Node.js 接口:应用驱动与连接;以厂家实际版本及应用固定依赖为准。
- 达梦上手与运维入口:DTS/客户端等资料,和应用Node驱动区分。