# 自动投放双服务部署

## 1. Jenkins 构建

构建统一完整镜像，不再构建 `tencent-realtime-control` 独立镜像：

```bash
set -e

REGISTRY="registry.cn-hangzhou.aliyuncs.com/stuuudy"
IMAGE="${REGISTRY}/ad-put-agent"

cd "${WORKSPACE}"
docker build \
  -f Dockerfile.auto_put_ad_mini \
  -t "${IMAGE}:latest" \
  -t "${IMAGE}:${BUILD_TIMESTAMP}" \
  .
docker push "${IMAGE}:latest"
docker push "${IMAGE}:${BUILD_TIMESTAMP}"

ssh -i /root/.ssh/piaoquan.pem root@8.222.142.37 \
  "sh /home/sh/deploy.sh ${BUILD_TIMESTAMP}"
```

## 2. ECS 文件

将仓库中的 `docker-compose.yml` 放到 `/home/server/docker-compose.yml`。
当前部署兼容 ECS 已安装的 Docker Compose `2.27.0`。

两个服务共用现有的 `/home/server/.env`：

```bash
chmod 600 /home/server/.env
docker compose version
```

原有数据库、ODPS、腾讯、飞书、OSS 和 AI 环境变量全部保留。

首次部署增加：

```dotenv
RTC_START_HOUR=12
RTC_STOP_HOUR=21
RTC_POLL_SECONDS=600
RTC_HIGH_CPM=250
RTC_LOW_CPM=190
RTC_BID_UP_RATIO=1.25
RTC_MAX_PARTITION_LAG_HOURS=2
RTC_APPLY_ENABLED=0
RTC_COMMAND_ENABLED=1
RTC_COMMAND_CHAT_ID=oc_xxx
RTC_COMMAND_ALLOWED_OPEN_IDS=ou_xxx
RTC_COMMAND_CONFIRM_TTL_MINUTES=10

DAILY_CREATION_ENABLED=0
DAILY_CREATION_HOUR=10
DAILY_CREATION_MINUTE=30
DAILY_REVIEW_ENABLED=0
DAILY_REVIEW_INTERVAL_HOURS=2
DAILY_RUN_ON_STARTUP=0
DAILY_REVIEW_RUN_ON_STARTUP=0
DAILY_SYNC_DELIVERY_TEMPLATE=0

DAILY_ROI_ENABLED=0
DAILY_ROI_HOUR=11
DAILY_ROI_MINUTE=0
DAILY_ROI_RUN_ON_STARTUP=0
ROI_APPLY_ENABLED=0
ROI_APPROVAL_TTL_MINUTES=120
ROI_SCALE_RATIO=1.10
ROI_SCALE_COOLDOWN_DAYS=3
ROI_MAX_BASE_RATIO=2.00
ROI_FEISHU_CHAT_ID=oc_xxx
```

多个操作人使用英文逗号分隔 `RTC_COMMAND_ALLOWED_OPEN_IDS`。
代码默认 `RTC_APPLY_ENABLED=0`，日级创建、审核扫描、ROI 和模板同步也默认关闭；
缺少显式生产配置时不会创建广告或修改腾讯。

## 3. ECS 部署脚本

`/home/sh/deploy.sh` 使用：

```bash
#!/bin/bash
set -e

echo "Qingqu@2019" |
  docker login \
    --username=stuuudys \
    registry.cn-hangzhou.aliyuncs.com \
    --password-stdin

VERSION=${1:-latest}
cd /home/server

VERSION=${VERSION} docker compose --env-file /dev/null pull
VERSION=${VERSION} docker compose --env-file /dev/null down --remove-orphans
VERSION=${VERSION} docker compose --env-file /dev/null up -d
VERSION=${VERSION} docker compose --env-file /dev/null ps
```

继续沿用部署脚本中的 `source /home/server/.env`，Compose 同时通过标准
`env_file` 将同一个文件传给两个容器，环境变量文件和加载入口不变。

## 4. 首次 dry-run

保持 `RTC_APPLY_ENABLED=0` 和两个 `DAILY_*_ENABLED=0`：

```bash
cd /home/server
docker compose --env-file /dev/null config --services
docker compose --env-file /dev/null up -d
docker compose --env-file /dev/null ps
docker compose --env-file /dev/null logs --tail=200 ad_control_service
docker compose --env-file /dev/null logs --tail=200 ad_daily_service
```

控制服务启动时自动执行实时状态表增量迁移。也可以显式验证：

```bash
docker compose --env-file /dev/null run --rm \
  ad_control_service \
  python /app/examples/tencent_realtime_control/init_db.py
```

执行无腾讯写入的 CPM dry-run：

```bash
docker compose --env-file /dev/null run --rm \
  ad_control_service \
  python /app/examples/tencent_realtime_control/run_once.py \
  --at 2026-07-25T12:00:00 \
  --cpm-override 220 \
  --force-refresh
```

飞书验证：

1. 在配置群 @机器人发送 `查看暂停状态`。
2. 发送一个单账户暂停命令，确认机器人返回预览和 `cmd_xxx`。
3. 回复确认，服务必须提示 dry-run 禁止写腾讯。

## 5. 生产切换

1. 确认旧 `auto_put_ad_mini/server.py` 和旧实时容器不再运行。
2. 设置 `RTC_APPLY_ENABLED=1`，保持日级创建和 ROI 服务关闭。
3. 重新 `docker compose up -d`，观察一个完整实时周期。
4. 用单账户命令验证暂停、恢复、腾讯回读和飞书结果。
5. 设置 `DAILY_REVIEW_ENABLED=1`、`DAILY_SYNC_DELIVERY_TEMPLATE=1`。
6. 审核扫描稳定后设置 `DAILY_CREATION_ENABLED=1`。
7. 保持 `DAILY_RUN_ON_STARTUP=0`，防止部署时额外触发完整创建。

日级 ROI 单独分两阶段启用：

1. 设置 `DAILY_ROI_ENABLED=1`、`ROI_APPLY_ENABLED=0`，观察 11:00 的 ODPS 计算、数据库快照、只读飞书表和整批审批提示。
2. 使用 `确认 roi_<run_id>` 验证控制服务明确提示写操作关闭。
3. 核对阈值、账户可执行范围和动作数量后，再设置 `ROI_APPLY_ENABLED=1`。
4. 生产审批有效期默认 120 分钟；过期批次不能执行。拒绝整批使用 `拒绝 roi_<run_id>`。
5. 不允许 `DAILY_ROI_ENABLED=0`、`ROI_APPLY_ENABLED=1`，两个服务都会在启动阶段拒绝该配置。

首次启用前可手工运行一个“不发飞书、不写腾讯”的计算批次。该命令会读取 ODPS、
初始化/写入 ROI 数据库快照并在挂载目录生成本地 Excel：

```bash
docker compose run --rm ad_daily_service \
  python /app/examples/auto_put_ad_mini/run_daily_roi.py \
  --end-date 20260725
```

确认数据库快照和本地报告后，再增加 `--send-feishu` 验证只读在线表和整批命令。
此时必须保持 `ROI_APPLY_ENABLED=0`；回复确认只应得到“写操作关闭”的提示。

## 6. 验收与回滚

验收：

```bash
docker compose --env-file /dev/null ps
docker compose --env-file /dev/null logs --since=30m ad_control_service
docker compose --env-file /dev/null logs --since=30m ad_daily_service
```

应确认：

- 两个容器均为 healthy。
- 实时循环每 10 分钟运行且没有双实例锁冲突。
- 飞书只有一个 WebSocket 消费者。
- 10:30 只出现一次创建任务。
- 审核扫描每 2 小时最多一个实例。
- 11:00 只生成一个相同指标/策略版本的 ROI 批次，同日重跑不会重复发布。
- ROI 审批执行与实时 CPM 调控不并发写腾讯，基础出价调整后两边状态一致。

回滚时将 `VERSION` 改为上一个镜像版本并重新部署。数据库新增表和列可以保留，
旧代码不会读取这些字段，不需要执行破坏性回滚。
