airpredict/空气质量预测/源码/docs/superpowers/specs/2026-07-09-second-independe...

11 KiB
Raw Blame History

第二套独立部署 —— 设计文档

日期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-svrrestart_time 保持为 0。

2. 原系统现状2026-07-09 实地探测)

后端代码 C:\API\iapip-svrgit HarveyQi0425/iapip-svr,分支 deploy1rev 44e1aab
后端进程 PM2 app iapip-svrcluster 模式,dist/index.jsonlineuptime 自 2025-08-18
后端端口 6060
PM2_HOME C:\Users\Administrator\.pm2(默认)
数据库 mysql://root:harvey0425@localhost:3306/iapips
MySQL 5.7.44C:\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 RewriteARRsystem.webServer/proxyenabled="true")。 端口 6061 与 8081 均空闲。C 盘剩余约 71 GB。

3. 关键代码事实

这些事实决定了新系统需要改动哪些文件,均已核对源码:

  1. 后端运行时不加载 dotenvsrc/index.ts:24 只读 process.env.IAPIP_SVR_PORT,实际值由 PM2 从 ecosystem.config.jsenv 注入。.env 仅被 package.json 中的 dotenv -e .env -- npx prisma ... 用于 prisma CLI 命令。 → 新系统只需改 ecosystem.config.js.env 两个文件。

  2. 前端调用的是同源相对路径 /api/*(如 src/services/api/user.ts:16),依赖 IIS 在同一 origin 上把 /api 反代到本机后端端口。 → 两套系统必须各有独立 origin否则 /api 冲突。新系统用独立端口 8081 实现。

  3. 前端 publicPath/iapip-web/(管理端 /iapip-ms/)。新站点是独立 origin 且应用路径结构与原站相同。 → 前端构建产物无需任何配置改动,直接 build、直接拷贝。 .umirc.tsproxy.target: http://localhost:6060 只作用于 pnpm dev,不影响 pnpm build 产物。)

  4. 后端上传目录为 path.join(__dirname, '../../'),即代码目录的父目录。新系统代码放在 C:\API2\iapip-svr,上传自然落到 C:\API2\,与原系统的 C:\API\ 天然隔离。

  5. 新代码的 Prisma schema 比原系统数据库多三张表:ForumThreadForumReplyForumThreadLike。 → 从原库复制数据后,必须对新库补跑 prisma db push

  6. prisma/seed.ts 使用固定 idU000000 / A000000)且不清表,不可重复执行。新系统数据来自原库副本,因此不跑 seed

4. 隔离方案

采用独立 PM2_HOME 方案。

理由:这套部署里唯一带全局副作用的工具是 PM2 —— pm2 restart allpm2 killpm2 resurrectpm2 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 站点 iapips80/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\fontsC:\iapips\fts 只读复制C:\iapips2\ 下(各应用目录内亦有同名子目录,一并照抄)

注记:本地 pnpm install 需要 pnpm-workspace.yamlallowBuilds 放行 core-js / core-js-pure / es5-ext / esbuild,否则 pnpm v11 会以 ERR_PNPM_IGNORED_BUILDS 退出。已知的 umi tailwindcss 插件补丁只影响 pnpm dev 的 watch 模式,与 pnpm build 无关。

5.2 后端(传源码,服务器上编译)

@prisma/clientesbuild 含平台相关的原生二进制,本地 node_modules 不可直接拷贝。

  1. 传输 src/prisma/fonts/package.jsontsconfig.jsonopft.xlsxC:\API2\iapip-svr\
  2. 服务器上执行 npm install(走项目 .npmrc 的 npmmirror 源,偶发 ECONNRESET需重试
  3. npx prisma generate
  4. npm run buildtsc 输出 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.jsname: "iapip-svr2"envDATABASE_URL 指向 iapips2IAPIP_SVR_PORT: 6061JWT_SECRET_KEY 为新值。

传输方式优先 scp;若服务器 sshd 未启用 sftp 子系统,退回 tar | ssh 管道。

5.3 数据库复制

全程只读原库:

  1. mysqldump --single-transaction iapips > C:\API2\iapips_backup.sql --single-transaction 对 InnoDB 表不加锁,不影响线上读写)
  2. CREATE DATABASE iapips2 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 然后导入该 dump
  3. iapips2 执行 prisma db push,补上 ForumThread / ForumReply / ForumThreadLike 三张表及列差异

第 3 步是唯一有数据风险的操作。若 db push 判定某些变更会丢数据而要求 --accept-data-loss必须先查看它打算执行的变更并交由用户确认,不得直接加该 flag。该步骤的连接串指向 iapips2,原库 iapips 不出现在任何写操作中。

不执行 prisma/seed.ts(见第 3 节第 6 条)。

5.4 IIS 配置

  1. 新建应用池 iapips2
  2. 新建站点 iapips2,物理路径 C:\iapips2,绑定 http/*:8081:,应用池 iapips2
  3. 站点下新建应用 /iapip-webC:\iapips2\iapip-web)与 /iapip-msC:\iapips2\iapip-ms
  4. 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、应用池 iapipsDefault 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. 验收

按顺序执行,每步必须通过:

  1. 服务器本机 curl http://localhost:6061/api/test/ping 返回 OK
  2. C:\API2\pm2.bat list 只显示 iapip-svr2;不带 PM2_HOME 的 pm2 list 只显示 iapip-svr —— 证明两个守护进程互不可见
  3. 浏览器访问 http://47.95.40.218:8081/iapip-web/,用原系统账号登录,完成一次预测,确认 /source 页的柱状图与贡献饼图正常渲染
  4. 浏览器访问 http://47.95.40.218:8081/iapip-ms/,管理端可登录
  5. 原系统体检https://indoorhealthair.com/iapip-web/ 正常访问;pm2 listiapip-svr 状态 onlinerestart_time 仍为 0

8. 回滚

新系统的全部资源都在独立命名空间内,回滚即删除:

  1. C:\API2\pm2.bat delete iapip-svr2C:\API2\pm2.bat kill
  2. 删除 IIS 站点 iapips2 与应用池 iapips2
  3. DROP DATABASE iapips2;
  4. 删除 C:\API2\C:\iapips2\
  5. 移除防火墙 8081 规则

原系统全程零改动,无需回滚。

9. 风险清单

风险 缓解
pm2 全局命令误伤原系统 独立 PM2_HOME新系统 pm2 一律走 C:\API2\pm2.bat
prisma db push 丢数据 仅作用于 iapips2;需 --accept-data-loss 时先呈报 diff 给用户确认
mysqldump 影响线上性能 --single-transactionInnoDB 不加锁;建议低峰期执行
npmmirror 源不稳定 npm install 失败重试 23 次(缓存累积)
8081 未放行导致外部不可达 部署前先请用户在阿里云安全组放行;本机 curl 可先行验证后端
误操作写到 C:\API / C:\iapips / iapips 所有路径与库名带 2 后缀;执行前逐条核对目标路径