# 执行计划：V2 部署架构升级 — 前后端合一容器 + MySQL

> **文档版本**: v2.0
> **编写日期**: 2026-07-28
> **最后更新**: 2026-08-17
> **当前分支**: `platform-auto-test`
> **目标服务器**: 192.168.5.60 / Ubuntu 26.04 LTS / 用户 ubains（V2 已完成）；192.168.5.69 / KylinOS V10（离线部署包验证已完成）
> **部署目录**: `/data/third_party/plat-auto-test/`
> **关联 PRD**: `_PRD_需求文档_V2部署架构升级_合一容器MySQL.md` v1.0
> **预计工时**: 1.5 天
> **状态**: ✅ V2 部署（5.60）+ 离线部署包（5.69）全部完成并验证通过

---

## 〇、执行状态总览

| 阶段 | 状态 | 说明 |
|------|------|------|
| Phase 1: 本地代码改造 | ✅ 完成 | Dockerfile / docker-compose / main.py / requirements / models |
| Phase 2: 数据迁移脚本 | ✅ 完成 | `migrate_sqlite_to_mysql.py`（含断点续传 + 外键处理） |
| Phase 3: 服务器部署实施 | ✅ 完成 | 5.60 双容器 running/healthy |
| Phase 4: 数据迁移与验证 | ✅ 完成 | 283 用例 + 28 模块 + 636 执行记录全部迁移 |
| **扩展：离线部署包（5.69）** | ✅ 完成 | v4 加固，快照恢复后端到端一次通过 |

> 详细踩坑（MySQL 密码 `@`、ID 字段超长、外键约束、aiomysql 兼容）与运维命令见 `HANDOFF_V2部署升级.md`。

---

## 一、执行概述

### 1.1 任务背景

当前系统以 Docker 容器化部署在 `192.168.5.60`，架构为 3 容器（前端 Nginx + 后端 + SQLite），存在以下问题：

1. **前端独立容器冗余** — 纯静态文件用 Nginx 容器 serve，资源浪费
2. **代码更新需重建镜像** — 每次修改代码必须 `docker compose build`（3-5 分钟）
3. **前端更新需重启容器** — 无法热更新
4. **SQLite 并发瓶颈** — 批量执行时 `database is locked`
5. **部署目录与另一服务共用** — 存在分支冲突风险

### 1.2 执行目标

| 目标 | 描述 | 验收标准 |
|------|------|---------|
| 前后端合一容器 | FastAPI 直接 serve 前端静态文件，去掉独立 Nginx 容器 | 访问 `http://192.168.5.60` 正常显示前端 |
| 前端热更新 | 前端构建产物通过 volume 挂载，替换文件立即生效 | 替换 dist/ 后刷新浏览器即生效，无需重启 |
| 后端 volume 挂载 | 后端代码通过 volume 挂载，改代码重启容器（秒级）生效 | 修改代码后 `docker compose restart app` 生效 |
| MySQL 替代 SQLite | 数据库升级为 MySQL 8.0，独立容器 | API 正常读写，批量执行无 `database is locked` |
| 数据迁移 | 283 个用例数据完整迁移到 MySQL | 查询返回 283 个用例 |
| 独立部署目录 | 迁移到 `/data/third_party/plat-auto-test/` | 与另一服务完全隔离，8088 不受影响 |

### 1.3 预计工期

| 阶段 | 内容 | 预计时间 |
|------|------|---------|
| Phase 1 | 本地代码改造（Dockerfile + main.py + requirements.txt） | 0.5 天 |
| Phase 2 | 数据迁移脚本编写与测试 | 0.5 天 |
| Phase 3 | 服务器部署实施（目录创建、文件上传、镜像构建） | 0.5 天 |
| Phase 4 | 数据迁移与验证 | 0.5 天 |
| **总计** | | **2 天** |

---

## 二、任务分解与实施计划

### Phase 1: 本地代码改造（0.5 天）

#### 1.1 新建合一 Dockerfile

**文件**: `deploy/Dockerfile`（新建）

**目标**：镜像只装 Python 环境 + Playwright + pip 依赖，不打包业务代码，代码通过 volume 挂载。

```dockerfile
FROM python:3.10-slim

WORKDIR /app

# 安装 Playwright 系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    wget gnupg curl \
    libnss3 libnspr4 libatk1.0-0t64 libatk-bridge2.0-0t64 \
    libcups2t64 libdrm2 libdbus-1-3 libxkbcommon0 \
    libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \
    libgbm1 libpango-1.0-0 libcairo2 libasound2t64 \
    && rm -rf /var/lib/apt/lists/*

# 安装 Python 依赖（国内镜像源）
COPY requirements.txt .
RUN pip install --no-cache-dir \
    -i https://pypi.tuna.tsinghua.edu.cn/simple \
    --trusted-host pypi.tuna.tsinghua.edu.cn \
    --retries 5 --timeout 120 -r requirements.txt

# 安装 Playwright Chromium
RUN playwright install chromium

# 创建必要目录
RUN mkdir -p /app/data/screenshots /app/data/reports \
    /app/frontend/dist /app/logs

EXPOSE 80

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
```

**检查要点**：
- [ ] WORKDIR 为 `/app`，与 volume 挂载路径一致
- [ ] 使用国内镜像源加速
- [ ] 未包含 `COPY backend/ .` — 代码从 volume 挂载
- [ ] 端口改为 80（合一容器监听 80 端口）

#### 1.2 改造 requirements.txt

**文件**: `backend/requirements.txt`

**变更**：新增 aiomysql 和 cryptography 依赖

```diff
  aiosqlite==0.19.0
+ aiomysql==0.2.0
+ cryptography==42.0.0
```

**检查要点**：
- [ ] aiomysql 版本兼容 SQLAlchemy 2.0
- [ ] cryptography 用于 MySQL 认证
- [ ] 保留 aiosqlite（本地开发仍用 SQLite）

#### 1.3 改造 main.py（前端静态文件服务）

**文件**: `backend/app/main.py`

**变更**：在文件末尾添加前端静态文件挂载和 SPA 路由回退

```python
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pathlib import Path

# ==================== 前端静态文件服务 ====================
FRONTEND_DIST = Path(__file__).parent.parent / "frontend" / "dist"

if FRONTEND_DIST.exists():
    app.mount("/assets", StaticFiles(directory=FRONTEND_DIST / "assets"), name="assets")

    @app.get("/{full_path:path}")
    async def serve_spa(full_path: str):
        file_path = FRONTEND_DIST / full_path
        if file_path.exists() and file_path.is_file():
            return FileResponse(str(file_path))
        return FileResponse(str(FRONTEND_DIST / "index.html"))
```

**检查要点**：
- [ ] 仅在 `frontend/dist/` 存在时挂载（本地开发时不干扰）
- [ ] SPA 路由回退正确（非 `/api` 路径返回 `index.html`）
- [ ] 不影响现有 API 路由（`/api/*` 路由优先匹配）
- [ ] 路径引用正确：`parent.parent` 从 `app/` 到项目根

#### 1.4 重写 docker-compose.yml

**文件**: `deploy/docker-compose.yml`

**变更**：从 3 服务改为 2 服务（app + mysql），全部使用 volume 挂载

```yaml
version: "3.8"

services:
  mysql:
    image: mysql:8.0
    container_name: plat-auto-test-mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE:-plat_auto_test}
      MYSQL_USER: ${MYSQL_USER:-platapp}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      TZ: Asia/Shanghai
    volumes:
      - /data/third_party/plat-auto-test/mysql/data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    ports:
      - "3307:3306"
    networks:
      - plat-auto-test-net
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --default-authentication-plugin=mysql_native_password
      - --max_connections=200

  app:
    build:
      context: ../backend
      dockerfile: ../deploy/Dockerfile
    container_name: plat-auto-test-app
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
    environment:
      - DATABASE_URL=mysql+aiomysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql:3306/${MYSQL_DATABASE}?charset=utf8mb4
      - PLAYWRIGHT_HEADLESS=true
      - PLAYWRIGHT_TIMEOUT=30000
      - LOG_LEVEL=INFO
      - TZ=Asia/Shanghai
    volumes:
      - /data/third_party/plat-auto-test/backend:/app
      - /data/third_party/plat-auto-test/frontend/dist:/app/frontend/dist
      - /data/third_party/plat-auto-test/data:/app/data
      - /data/third_party/plat-auto-test/logs:/app/logs
    ports:
      - "80:80"
    networks:
      - plat-auto-test-net
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

networks:
  plat-auto-test-net:
    driver: bridge
```

**检查要点**：
- [ ] `build.context` 指向 `../backend`（确保 requirements.txt 路径正确）
- [ ] `build.dockerfile` 指向 `../deploy/Dockerfile`
- [ ] MySQL 宿主机端口用 3307，避免冲突
- [ ] app 依赖 mysql 健康检查
- [ ] 4 个 volume 挂载路径正确
- [ ] DATABASE_URL 使用容器服务名 `mysql` 作为主机名

#### 1.5 新建 MySQL 初始化脚本

**文件**: `deploy/init.sql`

```sql
ALTER DATABASE plat_auto_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON plat_auto_test.* TO 'platapp'@'%';
FLUSH PRIVILEGES;
SET GLOBAL time_zone = '+08:00';
SET SESSION time_zone = '+08:00';
```

#### 1.6 更新 .env.example

**文件**: `deploy/.env.example`

```bash
# MySQL 配置
MYSQL_ROOT_PASSWORD=Ubains@13579
MYSQL_DATABASE=plat_auto_test
MYSQL_USER=platapp
MYSQL_PASSWORD=PlatApp@2026

# 应用配置
PLAYWRIGHT_HEADLESS=true
PLAYWRIGHT_TIMEOUT=30000
LOG_LEVEL=INFO
```

#### 1.7 编写数据迁移脚本

**文件**: `backend/scripts/migrate_sqlite_to_mysql.py`

**功能**：从本地 SQLite 数据库读取数据，写入 MySQL

**表迁移顺序**：
1. `modules` — 先迁移模块（无外键依赖）
2. `test_cases` — 依赖 module.id
3. `executions` — 独立
4. `case_results` — 依赖 execution.id
5. `case_dependencies` — 依赖 test_case.id
6. `security_configs` — 独立
7. `vulnerability_results` — 依赖 security_configs.id

**迁移逻辑**：
```python
#!/usr/bin/env python
"""
SQLite → MySQL 数据迁移脚本
将本地 SQLite 数据迁移到远程 MySQL
"""

import asyncio
from sqlalchemy import create_engine, text
from sqlalchemy.ext.asyncio import create_async_engine

SQLITE_URL = "sqlite+aiosqlite:///./data/test_platform.db"
MYSQL_URL = "mysql+aiomysql://platapp:PlatApp@2026@192.168.5.60:3307/plat_auto_test?charset=utf8mb4"

TABLES_ORDER = [
    "modules",
    "test_cases",
    "executions",
    "case_results",
    "case_dependencies",
    "security_configs",
    "vulnerability_results",
]

async def migrate():
    # 1. 连接 SQLite 读取数据
    sqlite_engine = create_async_engine(SQLITE_URL)
    
    # 2. 连接 MySQL
    mysql_engine = create_async_engine(MYSQL_URL)
    
    # 3. 逐表迁移
    for table in TABLES_ORDER:
        # 读取 SQLite 数据
        async with sqlite_engine.connect() as conn:
            rows = await conn.execute(text(f"SELECT * FROM {table}"))
            columns = rows.keys()
            data = [dict(zip(columns, row)) for row in rows]
        
        # 写入 MySQL
        if data:
            async with mysql_engine.connect() as conn:
                for row in data:
                    keys = ", ".join(row.keys())
                    values = ", ".join([f":{k}" for k in row.keys()])
                    await conn.execute(
                        text(f"INSERT INTO {table} ({keys}) VALUES ({values})"),
                        row
                    )
                await conn.commit()
        
        print(f"✅ {table}: {len(data)} 行迁移完成")
    
    print("🎉 所有数据迁移完成！")

if __name__ == "__main__":
    asyncio.run(migrate())
```

**检查要点**：
- [ ] 迁移前确认 MySQL 表已创建（应用首次启动自动建表）
- [ ] 处理 UUID 主键兼容性（SQLite 和 MySQL 的 hex 字符串一致）
- [ ] 处理 JSON 字段序列化（SQLite 的 JSON 和 MySQL 的 JSON 兼容）
- [ ] 迁移完成后对比行数

#### 1.8 更新 deploy.sh

**文件**: `deploy/deploy.sh`

**变更**：适配新目录 `/data/third_party/plat-auto-test/` 和新架构（2 容器）

---

### Phase 2: 数据迁移脚本测试（0.5 天）

#### 2.1 本地测试

```bash
# 1. 本地启动 MySQL（Docker）
docker run --name mysql-test -e MYSQL_ROOT_PASSWORD=Ubains@13579 \
    -e MYSQL_DATABASE=plat_auto_test -e MYSQL_USER=platapp \
    -e MYSQL_PASSWORD=PlatApp@2026 -p 3307:3306 -d mysql:8.0 \
    --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci \
    --default-authentication-plugin=mysql_native_password

# 2. 安装依赖
pip install aiomysql cryptography

# 3. 创建表结构（启动应用或手动建表）
# 4. 运行迁移脚本
cd backend
python scripts/migrate_sqlite_to_mysql.py

# 5. 验证数据
mysql -h 127.0.0.1 -P 3307 -uplatapp -pPlatApp@2026 plat_auto_test -e "SELECT COUNT(*) FROM test_cases;"
# 期望: 283
```

#### 2.2 验证要点

- [ ] 所有表数据完整迁移
- [ ] 主键无冲突
- [ ] 外键关系正确
- [ ] JSON 字段内容正确
- [ ] 迁移后本地 SQLite 可继续使用（本地开发）

---

### Phase 3: 服务器部署实施（0.5 天）

#### 3.1 停掉旧容器

```bash
ssh ubains@192.168.5.60

# 停掉 V1 容器
cd ~/ubains-module-test/deploy 2>/dev/null
docker compose down 2>/dev/null

# 确认容器已停止
docker compose ps
```

#### 3.2 创建独立目录

```bash
# 创建目录结构
sudo mkdir -p /data/third_party/plat-auto-test/deploy
sudo mkdir -p /data/third_party/plat-auto-test/backend
sudo mkdir -p /data/third_party/plat-auto-test/frontend/dist
sudo mkdir -p /data/third_party/plat-auto-test/data/screenshots
sudo mkdir -p /data/third_party/plat-auto-test/data/reports
sudo mkdir -p /data/third_party/plat-auto-test/mysql/data
sudo mkdir -p /data/third_party/plat-auto-test/logs

# 设置权限
sudo chown -R ubains:ubains /data/third_party/plat-auto-test
```

#### 3.3 上传部署配置

```powershell
# 本地执行 - 上传部署相关文件
scp deploy/Dockerfile ubains@192.168.5.60:/data/third_party/plat-auto-test/deploy/
scp deploy/docker-compose.yml ubains@192.168.5.60:/data/third_party/plat-auto-test/deploy/
scp deploy/init.sql ubains@192.168.5.60:/data/third_party/plat-auto-test/deploy/
scp deploy/.env.example ubains@192.168.5.60:/data/third_party/plat-auto-test/deploy/.env
scp deploy/deploy.sh ubains@192.168.5.60:/data/third_party/plat-auto-test/deploy/
```

#### 3.4 上传后端代码

```powershell
# 本地执行 - 上传后端源码（排除 pycache）
scp -r backend/app ubains@192.168.5.60:/data/third_party/plat-auto-test/backend/
scp -r backend/scripts ubains@192.168.5.60:/data/third_party/plat-auto-test/backend/
scp backend/requirements.txt ubains@192.168.5.60:/data/third_party/plat-auto-test/backend/
```

#### 3.5 构建前端并上传

```powershell
# 本地执行 - 构建前端
cd frontend
npm run build

# 上传构建产物
scp -r dist/* ubains@192.168.5.60:/data/third_party/plat-auto-test/frontend/dist/
```

#### 3.6 构建镜像并启动

```bash
# 服务器上执行
ssh ubains@192.168.5.60
cd /data/third_party/plat-auto-test/deploy

# 构建镜像（首次约 3-5 分钟）
docker compose build

# 启动服务
docker compose up -d

# 查看启动状态
docker compose ps
docker compose logs -f app    # 看应用日志，确认连上 MySQL
```

---

### Phase 4: 数据迁移与验证（0.5 天）

#### 4.1 确认 MySQL 表已创建

```bash
# 检查 MySQL 表是否自动创建
docker exec plat-auto-test-mysql mysql -uplatapp -pPlatApp@2026 \
    plat_auto_test -e "SHOW TABLES;"
```

#### 4.2 运行数据迁移

```bash
# 本地运行迁移脚本（连接服务器 MySQL）
cd backend
python scripts/migrate_sqlite_to_mysql.py
```

#### 4.3 验证数据完整性

```bash
# 验证用例数
curl -s "http://192.168.5.60/api/cases?limit=500" | python3 -c "
import sys,json
d=json.load(sys.stdin)
print(f'总用例数: {len(d.get(\"items\", d))}')"

# 验证模块数
curl -s "http://192.168.5.60/api/modules?case_type=ui" | python3 -c "
import sys,json
d=json.load(sys.stdin)
print(f'UI模块数: {len(d)}')"

# 验证前端可访问
curl -s -o /dev/null -w "%{http_code}" http://192.168.5.60/
# 期望: 200

# 验证 API 健康
curl -s http://192.168.5.60/health
# 期望: {"status":"healthy"}

# 验证已有服务不受影响
curl -s -o /dev/null -w "%{http_code}" http://192.168.5.60:8088/
# 期望: 200
```

#### 4.4 验证前端热更新

```bash
# 1. 修改本地前端代码并构建
cd frontend
# 修改某个文件 ...
npm run build

# 2. 上传新构建产物
scp -r dist/* ubains@192.168.5.60:/data/third_party/plat-auto-test/frontend/dist/

# 3. 浏览器刷新 - 验证新版本生效（无需重启容器）
```

#### 4.5 验证后端更新

```bash
# 1. 修改本地后端代码
# 修改某个 .py 文件 ...

# 2. 上传修改的文件
scp -r backend/app ubains@192.168.5.60:/data/third_party/plat-auto-test/backend/

# 3. 重启容器（秒级）
ssh ubains@192.168.5.60
cd /data/third_party/plat-auto-test/deploy
docker compose restart app

# 4. 验证新代码生效
curl -s http://192.168.5.60/health
```

---

## 三、验收标准

### 3.1 功能验收

| 验收项 | 操作 | 期望结果 |
|--------|------|---------|
| 目录独立 | 检查 `/data/third_party/plat-auto-test/` | 文件结构完整，与 `/opt/troubleshoot/` 独立 |
| 容器运行 | `docker compose ps` | app + mysql 状态 running/healthy |
| API 健康 | `curl http://localhost/health` | 返回 `{"status":"healthy"}` |
| 前端访问 | 浏览器打开 `http://192.168.5.60` | 显示登录页或首页 |
| 数据库连接 | 应用日志无 MySQL 连接错误 | 表自动创建 |
| 数据完整 | 查询用例数 | 283 个用例 |
| 前端热更新 | 替换 dist/ 后刷新浏览器 | 新版本生效，无需重启容器 |
| 后端更新 | 重启 app 容器后调用 API | 新代码生效 |
| 已有服务 | 访问 8088 端口 | 另一维护平台正常 |
| 本地开发 | 本地 `uvicorn` 启动 | 使用 SQLite 正常运行 |

### 3.2 性能验收

| 验收项 | 标准 |
|--------|------|
| 容器启动时间 | 总部署时间 < 5 分钟 |
| 后端重启时间 | < 10 秒 |
| 前端热更新 | 替换文件后刷新即生效 |

### 3.3 数据迁移验收

| 表名 | 期望行数 | 是否完整 |
|------|---------|---------|
| modules | ~28 | ✅ |
| test_cases | 283 | ✅ |
| 其他表 | 完整迁移 | ✅ |

---

## 四、测试计划

### 4.1 本地测试

| 测试项 | 方法 | 预期 |
|--------|------|------|
| 本地 SQLite 模式 | `uvicorn app.main:app --port 8001` | 正常启动，使用本地数据库 |
| 前端挂载 | 访问 `http://localhost:8001` | 前端页面正常显示 |
| API 路由 | 访问 `http://localhost:8001/api/cases` | 返回用例列表 |
| 数据迁移脚本 | 运行迁移脚本 | 数据完整迁移 |

### 4.2 服务器测试

| 测试项 | 方法 | 预期 |
|--------|------|------|
| 服务启动 | `docker compose up -d` | 2 容器均正常运行 |
| 健康检查 | `curl http://localhost/health` | 200 OK |
| 前端访问 | 浏览器访问 `http://192.168.5.60` | 正常显示 |
| 用例查询 | `curl /api/cases?limit=10` | 返回数据 |
| 批量执行 | 执行 10 个用例 | 无 `database is locked` |

---

## 五、风险评估

| 风险 | 概率 | 影响 | 缓解措施 |
|------|------|------|---------|
| MySQL 连接失败 | 低 | 高 | 健康检查依赖确保启动顺序 |
| 数据迁移丢失 | 低 | 高 | 迁移前备份 SQLite，迁移后对比行数 |
| 容器内路径不一致 | 中 | 高 | 确保 WORKDIR 和 volume 路径严格匹配 |
| 旧容器端口冲突 | 低 | 中 | 先 `docker compose down` 再启动 |
| Dockerfile 构建失败 | 低 | 中 | 使用国内镜像源，已有成功经验 |
| 前端热更新不生效 | 低 | 低 | 浏览器缓存问题，强制刷新解决 |

---

## 六、实施记录

| 日期 | 操作 | 负责人 | 备注 |
|------|------|--------|------|
| 2026-07-28 | 编写需求文档和计划执行文档 | czj | 待实施 |
| 2026-07-28 | 完成 Phase 1 代码改造 + Phase 2 迁移脚本 | czj | ✅ 本地完成 |
| 2026-07-28 | 完成 Phase 3 服务器部署 + Phase 4 数据迁移验证（5.60） | czj | ✅ 283 用例/28 模块/636 执行记录全部迁移 |
| 2026-08-17 | 制作离线部署包并部署验证（5.69，KylinOS V10） | czj | ✅ v3/v4 加固，快照恢复后端到端一次通过 |
| 2026-08-17 | 修复问题 11（基础数据导入误判） | czj | ✅ `2>/dev/null` → `2>&1` + `grep ERROR` |

---

## 七、后续工作

| 优先级 | 待办 | 说明 |
|--------|------|------|
| P1 | 全量用例执行验证（5.60 MySQL） | 批量执行 UI 用例，确认无 `database is locked` |
| P2 | 收敛部署包临时工具 | `upload_pkg.py` / `upload_backend.py` / `run_deploy.py` 移出交付目录或标注非交付组件 |
| P3 | 下次快照验证时实证 compose 插件自动安装 | 快照恢复后先 `ls /usr/libexec/docker/cli-plugins/` 确认缺失再跑 deploy.sh |
| P4 | HTTPS 配置 | 为前端配置 Let's Encrypt 证书 |
| P5 | 编写自动化更新脚本 | 简化后续前端/后端更新操作 |

> 端到端验证细节、问题 1~11 台账与运维命令见：
> - `HANDOFF_V2部署升级.md`（V2 部署 + 离线部署包验证）
> - `Docs/PRD/部署运维/部署包问题记录与修复计划.md`（问题台账）

---

## 八、附录

### 8.1 涉及改造的文件清单

| 文件 | 操作 | 说明 |
|------|------|------|
| `deploy/Dockerfile` | 🆕 新建 | 合一应用镜像 |
| `deploy/docker-compose.yml` | 🔄 重写 | 2 服务编排 |
| `deploy/init.sql` | 🆕 新建 | MySQL 初始化 |
| `deploy/.env.example` | 🔄 更新 | 新增 MySQL 配置项 |
| `deploy/deploy.sh` | 🔄 重写 | 适配新目录 |
| `backend/app/main.py` | 🔄 改造 | 新增 StaticFiles 挂载前端 |
| `backend/requirements.txt` | 🔄 改造 | 新增 aiomysql + cryptography |
| `backend/scripts/migrate_sqlite_to_mysql.py` | 🆕 新建 | 数据迁移脚本 |

### 8.2 服务器目录结构

```
/data/third_party/plat-auto-test/
├── deploy/
│   ├── docker-compose.yml
│   ├── Dockerfile
│   ├── init.sql
│   ├── .env
│   └── deploy.sh
├── backend/
│   ├── app/
│   ├── scripts/
│   └── requirements.txt
├── frontend/dist/
│   ├── index.html
│   └── assets/
├── data/
│   ├── screenshots/
│   └── reports/
├── mysql/data/
└── logs/
```

### 8.3 更新流程速查

| 场景 | 命令 |
|------|------|
| 更新前端 | `npm run build && scp -r dist/* ubains@5.60:/data/third_party/plat-auto-test/frontend/dist/` |
| 更新后端 | `scp -r backend/app ubains@5.60:.../backend/ && ssh ubains@5.60 'cd .../deploy && docker compose restart app'` |
| 更新依赖 | `docker compose build app && docker compose up -d app` |
| 查看日志 | `docker compose logs -f app` |
| 重启服务 | `docker compose restart app` |
| 备份数据库 | `docker exec plat-auto-test-mysql mysqldump -uroot -pXXX plat_auto_test > backup.sql` |

### 8.4 相关文档

| 文档 | 路径 |
|------|------|
| V2 部署升级 PRD | `Docs/PRD/需求文档/部署运维/_PRD_需求文档_V2部署架构升级_合一容器MySQL.md` |
| V1 部署方案 | `Docs/部署方案/Linux服务器部署方案.md` |
| V2 设计方案 | `Docs/部署方案/Linux容器化部署方案_v2.md` |
| 项目指南 | `CLAUDE.md` |
| UI 自动化交接 | `HANDOFF_UI自动化.md` |

---

*本文档由 Claude Code 生成，建议评审后按 Phase 1-4 顺序实施。*