11 KiB
第二套独立部署 —— 设计文档
日期:2026-07-09
目标服务器:阿里云 Windows Server 2012 R2,公网 IP 47.95.40.218
1. 背景与目标
服务器上已经运行着一套 IAPIP 生产系统(下称原系统),对外域名 https://indoorhealthair.com/,自 2025-08-18 起持续在线。
现在要在同一台服务器上部署第二套完全独立的系统(下称新系统),要求:
- 新系统与原系统互不影响,数据、进程、配置、静态文件全部隔离
- 绝对不能覆盖、修改或重启原系统的任何部分
- 新系统部署
feature/source-charts分支的最新代码(含用户端与管理端) - 新系统的数据库是原系统数据库的一份副本,此后各自演化
成功标准:新系统可从 http://47.95.40.218:8081/iapip-web/ 正常登录并完成一次预测;同时原系统 https://indoorhealthair.com/iapip-web/ 无任何异常,其 PM2 进程 iapip-svr 的 restart_time 保持为 0。
2. 原系统现状(2026-07-09 实地探测)
| 项 | 值 |
|---|---|
| 后端代码 | C:\API\iapip-svr(git HarveyQi0425/iapip-svr,分支 deploy1,rev 44e1aab) |
| 后端进程 | PM2 app iapip-svr,cluster 模式,dist/index.js,online,uptime 自 2025-08-18 |
| 后端端口 | 6060 |
| PM2_HOME | C:\Users\Administrator\.pm2(默认) |
| 数据库 | mysql://root:harvey0425@localhost:3306/iapips |
| MySQL | 5.7.44,C:\MySQL\mysql-5.7.44-winx64\bin |
| IIS 站点 | iapips,绑定 http/*:80:indoorhealthair.com + https:443,应用池 iapips |
| IIS 物理路径 | C:\iapips,下挂应用 /iapip-web、/iapip-ms |
/api 反代 |
C:\iapips\web.config 的 URL Rewrite 规则 → http://localhost:6060/api/{R:1} |
| 上传目录 | C:\API\(后端 path.join(__dirname, '../../') 的结果) |
| 开机自启 | 无。没有服务、计划任务或启动项;只有 pm2 save 留下的 dump.pm2 |
服务器已装:Node v18.18.0、npm 9.8.1、git 2.43、pm2 5.4.3、Python 3.12、chocolatey。
IIS 已装 URL Rewrite 与 ARR(system.webServer/proxy 的 enabled="true")。
端口 6061 与 8081 均空闲。C 盘剩余约 71 GB。
3. 关键代码事实
这些事实决定了新系统需要改动哪些文件,均已核对源码:
-
后端运行时不加载 dotenv。
src/index.ts:24只读process.env.IAPIP_SVR_PORT,实际值由 PM2 从ecosystem.config.js的env注入。.env仅被package.json中的dotenv -e .env -- npx prisma ...用于 prisma CLI 命令。 → 新系统只需改ecosystem.config.js与.env两个文件。 -
前端调用的是同源相对路径
/api/*(如src/services/api/user.ts:16),依赖 IIS 在同一 origin 上把/api反代到本机后端端口。 → 两套系统必须各有独立 origin,否则/api冲突。新系统用独立端口 8081 实现。 -
前端
publicPath为/iapip-web/(管理端/iapip-ms/)。新站点是独立 origin 且应用路径结构与原站相同。 → 前端构建产物无需任何配置改动,直接 build、直接拷贝。 (.umirc.ts里proxy.target: http://localhost:6060只作用于pnpm dev,不影响pnpm build产物。) -
后端上传目录为
path.join(__dirname, '../../'),即代码目录的父目录。新系统代码放在C:\API2\iapip-svr,上传自然落到C:\API2\,与原系统的C:\API\天然隔离。 -
新代码的 Prisma schema 比原系统数据库多三张表:
ForumThread、ForumReply、ForumThreadLike。 → 从原库复制数据后,必须对新库补跑prisma db push。 -
prisma/seed.ts使用固定 id(U000000/A000000)且不清表,不可重复执行。新系统数据来自原库副本,因此不跑 seed。
4. 隔离方案
采用独立 PM2_HOME 方案。
理由:这套部署里唯一带全局副作用的工具是 PM2 —— pm2 restart all、pm2 kill、pm2 resurrect、pm2 update 都作用于整个守护进程下的所有应用。若两套系统共用默认 PM2_HOME,隔离就只能靠"操作时小心"来维持。给新系统单独指定 PM2_HOME=C:\API2\.pm2 后,它会启动一个独立的 PM2 守护进程,两边的 pm2 list 互相不可见,任何 pm2 命令在物理上都够不到对方的进程。
IIS 与 MySQL 不需要额外隔离手段:IIS 的站点/应用池、MySQL 的库,本身就是按名字划分的强边界,应用层不存在跨库读写的可能。为此再装一个 MySQL 实例(监听 3307)代价高而收益低,不采用。
资源分配
| 资源 | 原系统(保持不动) | 新系统 |
|---|---|---|
| 后端代码 | C:\API\iapip-svr |
C:\API2\iapip-svr |
| 后端端口 | 6060 | 6061 |
| PM2 应用名 | iapip-svr |
iapip-svr2 |
| PM2_HOME | C:\Users\Administrator\.pm2 |
C:\API2\.pm2 |
| 数据库 | iapips |
iapips2(同一 MySQL 实例,3306) |
| JWT 密钥 | PROD-IAPIPS-JWT-SECRET-KEY |
新生成的随机值 |
| 前端静态文件 | C:\iapips\{iapip-web,iapip-ms} |
C:\iapips2\{iapip-web,iapip-ms} |
| IIS 站点 | iapips(80/443 + 域名) |
iapips2(绑定 http/*:8081:) |
| IIS 应用池 | iapips |
iapips2 |
| 上传目录 | C:\API\ |
C:\API2\ |
共享的仅三样:Windows 操作系统、MySQL 服务进程(库分离)、IIS 服务进程(站点与应用池分离)。
新系统的所有 pm2 操作通过封装脚本 C:\API2\pm2.bat 执行,该脚本先 set PM2_HOME=C:\API2\.pm2 再转发参数给 pm2。
5. 构建与部署流程
5.1 前端(本地构建)
服务器不安装 pnpm,也不接触前端工具链。
- 本地
源码/用户端/iapip-web执行pnpm build,产物dist/传至C:\iapips2\iapip-web\ - 本地
源码/管理端/iapip-ms执行pnpm build,产物dist/传至C:\iapips2\iapip-ms\ - 比照原站结构,把 PDF 中文字体目录从
C:\iapips\fonts、C:\iapips\fts只读复制到C:\iapips2\下(各应用目录内亦有同名子目录,一并照抄)
注记:本地 pnpm install 需要 pnpm-workspace.yaml 的 allowBuilds 放行 core-js / core-js-pure / es5-ext / esbuild,否则 pnpm v11 会以 ERR_PNPM_IGNORED_BUILDS 退出。已知的 umi tailwindcss 插件补丁只影响 pnpm dev 的 watch 模式,与 pnpm build 无关。
5.2 后端(传源码,服务器上编译)
@prisma/client 与 esbuild 含平台相关的原生二进制,本地 node_modules 不可直接拷贝。
- 传输
src/、prisma/、fonts/、package.json、tsconfig.json、opft.xlsx至C:\API2\iapip-svr\ - 服务器上执行
npm install(走项目.npmrc的 npmmirror 源,偶发 ECONNRESET,需重试) npx prisma generatenpm run build(tsc输出dist/)
新建两个文件:
JWT 密钥用 node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" 生成一个 64 位十六进制串,同时写入下面两个文件(两处必须一致)。
C:\API2\iapip-svr\.env(供 prisma CLI):
DATABASE_URL="mysql://root:harvey0425@localhost:3306/iapips2"
JWT_SECRET_KEY="<上一步生成的 64 位十六进制串>"
IAPIP_SVR_PORT=6061
NODE_ENV=production
C:\API2\iapip-svr\ecosystem.config.js:name: "iapip-svr2",env 中 DATABASE_URL 指向 iapips2、IAPIP_SVR_PORT: 6061、JWT_SECRET_KEY 为新值。
传输方式优先 scp;若服务器 sshd 未启用 sftp 子系统,退回 tar | ssh 管道。
5.3 数据库复制
全程只读原库:
mysqldump --single-transaction iapips > C:\API2\iapips_backup.sql(--single-transaction对 InnoDB 表不加锁,不影响线上读写)CREATE DATABASE iapips2 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;然后导入该 dump- 对
iapips2执行prisma db push,补上ForumThread/ForumReply/ForumThreadLike三张表及列差异
第 3 步是唯一有数据风险的操作。若 db push 判定某些变更会丢数据而要求 --accept-data-loss,必须先查看它打算执行的变更并交由用户确认,不得直接加该 flag。该步骤的连接串指向 iapips2,原库 iapips 不出现在任何写操作中。
不执行 prisma/seed.ts(见第 3 节第 6 条)。
5.4 IIS 配置
- 新建应用池
iapips2 - 新建站点
iapips2,物理路径C:\iapips2,绑定http/*:8081:,应用池iapips2 - 站点下新建应用
/iapip-web(C:\iapips2\iapip-web)与/iapip-ms(C:\iapips2\iapip-ms) C:\iapips2\web.config照抄原站结构,仅把反代目标端口改为 6061:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="api">
<match url="^api/(.*)$" />
<action type="Rewrite" url="http://localhost:6061/api/{R:1}" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
前端用的是 hash 路由,因此不需要 SPA 回退规则(原站也没有)。
所有 IIS 操作只新增对象,不修改站点 iapips、应用池 iapips 或 Default Web Site。
5.5 网络放行
- 服务器本机防火墙:新增入站规则放行 TCP 8081
- 阿里云安全组:需用户在控制台放行入方向 TCP 8081。此步骤无法由 Claude 代劳。
6. 启动
C:\API2\pm2.bat start ecosystem.config.js --env production
C:\API2\pm2.bat save
与原系统一致,新系统同样不配置开机自启(原系统也没有;引入自启机制会改变服务器现状,超出本次范围)。若日后需要,应为两个 PM2_HOME 分别配置,不可用一条 pm2 resurrect 覆盖。
7. 验收
按顺序执行,每步必须通过:
- 服务器本机
curl http://localhost:6061/api/test/ping返回OK C:\API2\pm2.bat list只显示iapip-svr2;不带 PM2_HOME 的pm2 list只显示iapip-svr—— 证明两个守护进程互不可见- 浏览器访问
http://47.95.40.218:8081/iapip-web/,用原系统账号登录,完成一次预测,确认/source页的柱状图与贡献饼图正常渲染 - 浏览器访问
http://47.95.40.218:8081/iapip-ms/,管理端可登录 - 原系统体检:
https://indoorhealthair.com/iapip-web/正常访问;pm2 list中iapip-svr状态online且restart_time仍为 0
8. 回滚
新系统的全部资源都在独立命名空间内,回滚即删除:
C:\API2\pm2.bat delete iapip-svr2并C:\API2\pm2.bat kill- 删除 IIS 站点
iapips2与应用池iapips2 DROP DATABASE iapips2;- 删除
C:\API2\与C:\iapips2\ - 移除防火墙 8081 规则
原系统全程零改动,无需回滚。
9. 风险清单
| 风险 | 缓解 |
|---|---|
| pm2 全局命令误伤原系统 | 独立 PM2_HOME;新系统 pm2 一律走 C:\API2\pm2.bat |
prisma db push 丢数据 |
仅作用于 iapips2;需 --accept-data-loss 时先呈报 diff 给用户确认 |
mysqldump 影响线上性能 |
--single-transaction,InnoDB 不加锁;建议低峰期执行 |
| npmmirror 源不稳定 | npm install 失败重试 2–3 次(缓存累积) |
| 8081 未放行导致外部不可达 | 部署前先请用户在阿里云安全组放行;本机 curl 可先行验证后端 |
误操作写到 C:\API / C:\iapips / iapips 库 |
所有路径与库名带 2 后缀;执行前逐条核对目标路径 |