# 蓝绿部署计划

## 域名与环境映射

```
域名                 环境        后端端口        数据库
────────────────────────────────────────────────────
dev.xmclassmate.top  开发/测试   :8080          milkydata_dev
xmclassmate.top      生产        :4433 / :4434  milkydata
```

> **原则**：两个域名永远指向各自的环境，不互换。生产采用蓝绿部署实现零停机。

## 当前问题

单实例部署时存在停机时间：

```
停止服务 → 上传新代码 → 重启服务 → 跑迁移
           ↑
     3-5 秒停机
```

## 开发环境（dev.xmclassmate.top）

保持单实例，无需蓝绿：

```
dev.xmclassmate.top
       │
  OpenResty → :8080（单实例）
       │
  milkydata_dev
```

开发环境不需要蓝绿的原因：

| 原因 | 说明 |
|------|------|
| 停机时间可接受 | 仅开发者使用，重启 3-5 秒无影响 |
| 简化运维 | 少维护一套 systemd service |
| 快速迭代 | 直接 `deploy.sh development` 部署 |

## 生产环境（xmclassmate.top）— 蓝绿部署

### 架构

```
xmclassmate.top
       │
  OpenResty proxy
    /          \
蓝色 :4433    绿色 :4434
(active)      (idle)
    \          /
  milkydata（共享）
```

任何时候只有一个实例接收流量，另一个运行旧版本待命，切换零停机。

### 组件

**systemd service：**

```
/etc/systemd/system/
├── rust-backend-blue.service    ← :4433
└── rust-backend-green.service   ← :4434
```

两个 service 的 `DATABASE_URL` 都指向 `milkydata`，仅端口不同。

**OpenResty proxy 配置：**

```
# /www/sites/xmclassmate.top/proxy/root.conf
location ^~ / {
    proxy_pass http://127.0.0.1:4433;  # deploy.sh 切换此端口
}
```

切换时 `sed` 替换端口，`nginx -s reload`。

### 部署流程

```
初始：蓝色(:4433)=active  绿色(:4434)=idle

步骤 1：部署到空闲环境（绿色）
  ① cargo build --release
  ② rsync 二进制 → 绿色目录
  ③ systemctl restart rust-backend-green.service
  ④ 运行数据库迁移

步骤 2：验证绿色环境
  curl http://127.0.0.1:4434/health
  run_tests

步骤 3：切换流量
  sed 修改 proxy_pass → 127.0.0.1:4434
  nginx -s reload

步骤 4：验证
  curl https://xmclassmate.top/health

结果：绿色(:4434)=active  蓝色(:4433)=idle
      下次部署到蓝色
```

### 切换脚本

```bash
./scripts/switch-env.sh --env prod --switch green
# 或切回蓝色
./scripts/switch-env.sh --env prod --switch blue
```

### 回滚

```bash
./scripts/switch-env.sh --env prod --switch blue  # 即时恢复旧版本
```
旧环境代码未变，无需重新部署。

## 数据库

| 环境 | 数据库 | 说明 |
|------|--------|------|
| 开发 | `milkydata_dev` | 单实例直连 |
| 生产 | `milkydata` | 蓝绿实例共享，名称不变 |

> 生产库 `milkydata`，开发库 `milkydata_dev`，两者独立。

## 限制与注意事项

### 1. 部署只能部署到非活动端口

每次部署必须部署到**当前空闲的环境**，绝不能直接部署到正在接收流量的端口。

```
正确流程：
  Green(:4434) 活动中 → 部署到 Blue(:4433) → 测试 → 切到 Blue(:4433)
  Blue(:4433)  活动中 → 部署到 Green(:4434) → 测试 → 切到 Green(:4434)

错误：
  Green(:4434) 活动中 → 直接部署到 Green(:4434)  ← ❌ 会产生停机
```

**原因**：部署过程涉及服务重启（~3-5 秒），如果直接部署到活动中环境，
会导致在此期间的用户请求失败。

### 2. 代码只能通过 deploy.sh 部署

禁止手动 `rsync` + `systemctl restart` 部署。必须使用统一脚本：

```bash
# 正确
./deploy.sh production --target blue --yes

# 错误
cargo build --release && rsync ... systemctl restart  ← ❌
```

**原因**：
- 自动备份旧版本，支持回滚
- 自动运行数据库迁移
- 自动验证服务健康
- 自动切换代理（零停机）
- 自动记录部署日志
- 跳过手动操作的遗漏风险

### 3. 部署后先验证再切换

`deploy.sh --skip-tests` 跳过了部署后的自动测试。建议仅在开发/快速迭代时使用，
生产环境应让测试跑完再切换。

### 4. 数据库兼容性（迁移注意事项）

蓝绿共享数据库，迁移需向前兼容：

| 迁移类型 | 兼容 | 说明 |
|---------|------|------|
| `CREATE TABLE` | ✅ | 新旧代码均可运行 |
| `ADD COLUMN` | ✅ | 需 `DEFAULT` 或允许 `NULL` |
| `RENAME COLUMN` | ❌ | 旧代码查询旧列名会失败 |
| `DROP COLUMN` | ❌ | 旧代码查询被删列会失败 |

不兼容的迁移需要三段式部署。

## 实施优先级

| 阶段 | 内容 | 工作量 |
|------|------|--------|
| P1 | 创建 blue + green 两个 systemd service | 小 |
| P2 | 编写 `deploy-blue-green.sh` + `switch-env.sh` | 中 |
| P3 | 实现 OpenResty proxy 配置切换 | 中 |
| P4 | 改造 `deploy.sh` 支持 `--blue-green` | 中 |
| P5 | 不兼容迁移的三段式部署文档 | 小 |
| P6 | 回滚文档 | 小 |

## 生产上线待办

| # | 步骤 | 说明 | 状态 |
|---|------|------|------|
| 1 | 数据迁移 | 已完成 | ✅ |
| 2 | 准备生产 service 文件（填入实际密钥） | 待完成 | ⬜ |
| 3 | 部署新代码到 `:4433` | 待完成 | ⬜ |
| 4 | `env.ts` 设 `CURRENT_ENV = 'production'` | 已完成 | ✅ |
| 5 | 微信开发者工具上传小程序 | 待完成 | ⬜ |
| 6 | 1Panel 确认 `proxy_pass` 指向 `:4433` | 已完成 | ✅ |

## 实施状态

蓝绿部署已实施并运行中：

| 组件 | 状态 |
|------|------|
| Blue service (:4433) | ✅ 运行中，ConfigID `71ba8887` |
| Green service (:4434) | ✅ 运行中，活动中 |
| `deploy.sh --target blue\|green` | ✅ 已支持 |
| `switch-env.sh --switch blue\|green` | ✅ 自动切换 |
| OpenResty proxy | ✅ 指向活动中环境 |

### 下次部署流程

```bash
# 1. 查看当前活动中环境
./scripts/switch-env.sh --status

# 2. 部署到空闲环境（假设 green 活动中 → 部署到 blue）
./deploy.sh production --target blue --yes

# 3. 部署完成后自动切换流量到 blue
# deploy.sh 自动调用 switch-env.sh --switch blue
```
