docs: 第二套独立部署设计文档
在阿里云服务器上部署与现有生产系统完全隔离的第二套 IAPIP。 独立 PM2_HOME 隔离进程,独立库 iapips2,IIS 新站点绑 8081。 原系统零改动。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011RYsNhCmPk2V1hv2jmHFcV
This commit is contained in:
parent
5a6945a062
commit
e4ae01f5f5
|
|
@ -0,0 +1,209 @@
|
|||
# 第二套独立部署 —— 设计文档
|
||||
|
||||
日期: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. 关键代码事实
|
||||
|
||||
这些事实决定了新系统需要改动哪些文件,均已核对源码:
|
||||
|
||||
1. 后端**运行时不加载 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` 两个文件。
|
||||
|
||||
2. 前端调用的是**同源相对路径** `/api/*`(如 `src/services/api/user.ts:16`),依赖 IIS 在同一 origin 上把 `/api` 反代到本机后端端口。
|
||||
→ 两套系统必须各有独立 origin,否则 `/api` 冲突。新系统用独立端口 8081 实现。
|
||||
|
||||
3. 前端 `publicPath` 为 `/iapip-web/`(管理端 `/iapip-ms/`)。新站点是独立 origin 且应用路径结构与原站相同。
|
||||
→ **前端构建产物无需任何配置改动**,直接 build、直接拷贝。
|
||||
(`.umirc.ts` 里 `proxy.target: http://localhost:6060` 只作用于 `pnpm dev`,不影响 `pnpm build` 产物。)
|
||||
|
||||
4. 后端上传目录为 `path.join(__dirname, '../../')`,即代码目录的父目录。新系统代码放在 `C:\API2\iapip-svr`,上传自然落到 `C:\API2\`,与原系统的 `C:\API\` 天然隔离。
|
||||
|
||||
5. 新代码的 Prisma schema 比原系统数据库多三张表:`ForumThread`、`ForumReply`、`ForumThreadLike`。
|
||||
→ 从原库复制数据后,必须对新库补跑 `prisma db push`。
|
||||
|
||||
6. `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` 不可直接拷贝。
|
||||
|
||||
1. 传输 `src/`、`prisma/`、`fonts/`、`package.json`、`tsconfig.json`、`opft.xlsx` 至 `C:\API2\iapip-svr\`
|
||||
2. 服务器上执行 `npm install`(走项目 `.npmrc` 的 npmmirror 源,偶发 ECONNRESET,需重试)
|
||||
3. `npx prisma generate`
|
||||
4. `npm 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 数据库复制
|
||||
|
||||
全程只读原库:
|
||||
|
||||
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-web`(`C:\iapips2\iapip-web`)与 `/iapip-ms`(`C:\iapips2\iapip-ms`)
|
||||
4. `C:\iapips2\web.config` 照抄原站结构,仅把反代目标端口改为 6061:
|
||||
|
||||
```xml
|
||||
<?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. 验收
|
||||
|
||||
按顺序执行,每步必须通过:
|
||||
|
||||
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 list` 中 `iapip-svr` 状态 `online` 且 `restart_time` 仍为 0
|
||||
|
||||
## 8. 回滚
|
||||
|
||||
新系统的全部资源都在独立命名空间内,回滚即删除:
|
||||
|
||||
1. `C:\API2\pm2.bat delete iapip-svr2` 并 `C:\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-transaction`,InnoDB 不加锁;建议低峰期执行 |
|
||||
| npmmirror 源不稳定 | `npm install` 失败重试 2–3 次(缓存累积) |
|
||||
| 8081 未放行导致外部不可达 | 部署前先请用户在阿里云安全组放行;本机 curl 可先行验证后端 |
|
||||
| 误操作写到 `C:\API` / `C:\iapips` / `iapips` 库 | 所有路径与库名带 `2` 后缀;执行前逐条核对目标路径 |
|
||||
Loading…
Reference in New Issue