# Linux 容器化部署方案（V2 - 合一容器 + MySQL）

> **文档版本**: v2.0
> **创建日期**: 2026-07-27
> **文档状态**: 初稿待评审
> **适用版本**: platform-auto-test v1.0.0
> **目标服务器**: 192.168.5.60 / Ubuntu 26.04 LTS
> **部署目录**: `/data/third_party/plat-auto-test/`
> **负责人**: czj

---

## 目录

1. [架构设计](#一架构设计)
2. [目录规划](#二目录规划)
3. [部署文件清单](#三部署文件清单)
4. [Dockerfile（前后端合一）](#四dockerfile前后端合一)
5. [Docker Compose 编排](#五docker-compose-编排)
6. [MySQL 配置](#六mysql-配置)
7. [Nginx 配置（可选）](#七nginx-配置可选)
8. [环境变量](#八环境变量)
9. [部署步骤](#九部署步骤)
10. [更新流程](#十更新流程)
11. [MySQL 迁移改造](#十一mysql-迁移改造)
12. [验收标准](#十二验收标准)
13. [运维指南](#十三运维指南)
14. [常见问题](#十四常见问题)

---

## 一、架构设计

### 1.1 架构对比

| 维度 | V1（原方案） | V2（本方案） |
|------|-------------|-------------|
| 容器数量 | 3 个（前端 Nginx + 后端 + SQLite） | **2 个（合一应用 + MySQL）** |
| 前端运行方式 | 独立 Nginx 容器 | **应用容器内 serve 静态文件** |
| 数据库 | SQLite（文件） | **MySQL 8.0（独立容器）** |
| 前端更新 | 需重启前端容器 | **热替换，无需重启** |
| 后端更新 | 需重启后端容器 | **重启应用容器（秒级）** |
| 部署目录 | `~/ubains-module-test/`（git 目录） | **`/data/third_party/plat-auto-test/`**（独立目录） |
| 与其他服务隔离 | 共用 git 目录 | **完全独立目录，无冲突** |

### 1.2 整体架构图

```
                    用户浏览器
                        │
                        ▼
        ┌───────────────────────────────────┐
        │   服务器 192.168.5.60 (Ubuntu)      │
        │                                     │
        │   ┌─────────────────────────────┐  │
        │   │  Docker Compose 网络          │  │
        │   │  (plat-auto-test-net)         │  │
        │   │                               │  │
        │   │  ┌─────────────┐  ┌────────┐ │  │
        │   │  │  app 容器   │  │ mysql  │ │  │
        │   │  │  (合一)     │──│ 容器   │ │  │
        │   │  │             │  │        │ │  │
        │   │  │ :8001 API   │  │ :3306  │ │  │
        │   │  │ :80   前端  │  │        │ │  │
        │   │  │             │  │        │ │  │
        │   │  │ ┌─────────┐ │  │ ┌────┐ │ │  │
        │   │  │ │uvicorn  │ │  │ │MySQL│ │ │  │
        │   │  │ │(API)    │ │  │ │ 8.0 │ │ │  │
        │   │  │ ├─────────┤ │  │ └────┘ │ │  │
        │   │  │ │静态文件 │ │  │        │ │  │
        │   │  │ │serve    │ │  │        │ │  │
        │   │  │ ├─────────┤ │  │        │ │  │
        │   │  │ │Playwright│ │  │        │ │  │
        │   │  │ │Chromium │ │  │        │ │  │
        │   │  │ └─────────┘ │  │        │ │  │
        │   │  └──────┬──────┘  └────┬───┘ │  │
        │   │         │              │      │  │
        │   │    Volume 挂载     Volume 挂载 │  │
        │   │         │              │      │  │
        │   └─────────┼──────────────┼──────┘  │
        │             │              │          │
        │   ┌─────────▼──────────────▼──────┐  │
        │   │  /data/third_party/            │  │
        │   │      plat-auto-test/           │  │
        │   │  ├── backend/      (源码)      │  │
        │   │  ├── frontend/dist/ (构建产物)  │  │
        │   │  ├── data/         (截图/报告)  │  │
        │   │  └── mysql/data/   (DB 数据)    │  │
        │   └────────────────────────────────┘  │
        │                                       │
        │   另一维护平台 (8088) ← 独立，不受影响  │
        └───────────────────────────────────────┘
```

### 1.3 容器职责划分

| 容器 | 职责 | 端口 | 镜像 |
|------|------|------|------|
| **app** | 运行 FastAPI（API + 前端静态文件 + Playwright） | 80 | 自构建（python:3.10-slim） |
| **mysql** | 运行 MySQL 8.0 数据库 | 3306（仅容器内） | mysql:8.0 |

### 1.4 为什么这样设计

**① 前后端合一容器**
- 前端是 Vue 构建后的静态文件（`dist/`），FastAPI 内置 `StaticFiles` 即可提供静态服务
- 无需独立 Nginx 容器，减少资源占用和管理复杂度
- 前端 API 调用走 `/api/*` 相对路径，同源访问，无跨域问题

**② MySQL 独立容器**
- 数据库是核心数据资产，独立运行便于备份、迁移、升级
- MySQL 8.0 支持原生 JSON 字段（项目大量使用 JSON 列）
- 数据持久化通过 volume 挂载，容器重建不丢数据
- 项目 ORM 用 SQLAlchemy 2.0，跨数据库兼容，迁移成本低

**③ 代码 volume 挂载**
- 后端代码、前端 `dist/` 都通过 volume 挂载到容器
- 更新前端：替换 `dist/` 文件即可，**无需重启容器**（FastAPI 静态服务实时读取文件系统）
- 更新后端：替换代码后重启容器，**秒级生效，无需重建镜像**
- 镜像只包含 Python 环境 + Playwright + 依赖，不打包业务代码

---

## 二、目录规划

### 2.1 服务器目录结构

```
/data/third_party/plat-auto-test/          ← 项目根目录（独立，无 git）
├── deploy/                                ← 部署配置
│   ├── docker-compose.yml                 ← 服务编排
│   ├── Dockerfile                         ← 应用镜像（合一）
│   ├── init.sql                           ← MySQL 初始化脚本
│   ├── nginx.conf                         ← Nginx 配置（可选，见第七节）
│   ├── .env                               ← 环境变量（敏感信息）
│   └── deploy.sh                          ← 一键部署脚本
├── backend/                               ← 后端源码（volume 挂载到 /app）
│   ├── app/
│   ├── scripts/
│   ├── requirements.txt
│   └── ...
├── frontend/
│   └── dist/                              ← 前端构建产物（volume 挂载）
│       ├── index.html
│       ├── assets/
│       └── ...
├── data/                                  ← 应用数据（volume 挂载）
│   ├── screenshots/                       ← 执行截图
│   └── reports/                           ← 测试报告
├── mysql/
│   └── data/                              ← MySQL 数据持久化
└── logs/                                  ← 日志（volume 挂载）
```

### 2.2 目录用途与权限

| 目录 | 用途 | 挂载到容器 | 权限 |
|------|------|-----------|------|
| `deploy/` | 部署配置文件 | 不挂载 | root |
| `backend/` | 后端源码 | `/app` | ubains |
| `frontend/dist/` | 前端构建产物 | `/app/frontend/dist` | ubains |
| `data/` | 截图、报告 | `/app/data` | ubains（1000:1000） |
| `mysql/data/` | MySQL 数据 | `/var/lib/mysql` | mysql（999:999） |
| `logs/` | 应用日志 | `/app/logs` | ubains |

---

## 三、部署文件清单

| 文件 | 说明 | 状态 |
|------|------|------|
| `deploy/Dockerfile` | 合一应用镜像 | 🆕 新建 |
| `deploy/docker-compose.yml` | 服务编排（app + mysql） | 🆕 重写 |
| `deploy/init.sql` | MySQL 数据库初始化 | 🆕 新建 |
| `deploy/.env` | 环境变量 | 🆕 新建 |
| `deploy/deploy.sh` | 一键部署脚本 | 🔄 改写 |
| `backend/app/main.py` | 挂载前端静态文件 | 🔄 改造 |
| `backend/app/config.py` | MySQL 连接配置 | 🔄 改造 |
| `backend/requirements.txt` | 新增 aiomysql 依赖 | 🔄 改造 |

---

## 四、Dockerfile（前后端合一）

**文件**: `deploy/Dockerfile`

```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

# 注意：以下内容通过 volume 挂载，不 COPY
# - backend/ 源码 → /app
# - frontend/dist/ → /app/frontend/dist
# - data/ → /app/data
# - logs/ → /app/logs

EXPOSE 80

# 启动 uvicorn，监听 80 端口（同时提供 API + 静态文件）
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
```

**关键点**：
- 镜像只装环境（Python + Playwright + 依赖），**不打包业务代码**
- 业务代码、前端 dist、数据、日志全部 volume 挂载
- uvicorn 监听 80 端口，FastAPI 同时提供 API 和前端静态服务
- 首次构建后，**后续更新代码无需重建镜像**

---

## 五、Docker Compose 编排

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

```yaml
version: "3.8"

services:
  # ==================== MySQL 数据库 ====================
  mysql:
    image: mysql:8.0
    container_name: plat-auto-test-mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-Ubains@13579}
      MYSQL_DATABASE: ${MYSQL_DATABASE:-plat_auto_test}
      MYSQL_USER: ${MYSQL_USER:-platapp}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD:-PlatApp@2026}
      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"   # 映射到宿主机 3307，避免与本地 MySQL 冲突
    networks:
      - plat-auto-test-net
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-Ubains@13579}"]
      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:-platapp}:${MYSQL_PASSWORD:-PlatApp@2026}@mysql:3306/${MYSQL_DATABASE:-plat_auto_test}?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
```

**关键设计**：
- `mysql` 容器先启动并通过健康检查后，`app` 才启动（`depends_on` + `condition: service_healthy`）
- `app` 的 `DATABASE_URL` 用容器服务名 `mysql` 作为主机名（Docker 内部 DNS）
- MySQL 宿主机映射 `3307` 端口（避免与服务器本地 3306 冲突，方便外部调试）
- `build.context` 指向 `backend/`，这样 `requirements.txt` 路径正确

---

## 六、MySQL 配置

### 6.1 初始化脚本

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

```sql
-- 平台自动化测试系统 - MySQL 初始化脚本
-- 注意：MYSQL_DATABASE 环境变量会自动创建数据库，此脚本用于补充设置

-- 确保字符集正确
ALTER DATABASE plat_auto_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 授予 platapp 用户完整权限（MYSQL_USER 已自动创建并授予当前库权限，此处确认）
GRANT ALL PRIVILEGES ON plat_auto_test.* TO 'platapp'@'%';
FLUSH PRIVILEGES;

-- 时区设置（避免 SQLAlchemy 时间字段时区问题）
SET GLOBAL time_zone = '+08:00';
SET SESSION time_zone = '+08:00';
```

### 6.2 表结构创建

**无需手动建表**。应用首次启动时，`main.py` 的 `lifespan` 会调用 `init_db()` → `Base.metadata.create_all()`，根据 SQLAlchemy 模型自动在 MySQL 中创建所有表：

| 表名 | 说明 |
|------|------|
| `modules` | 测试模块 |
| `test_cases` | 测试用例 |
| `executions` | 执行记录 |
| `case_results` | 用例结果 |
| `case_dependencies` | 用例依赖 |
| `security_configs` | 安全测试配置 |
| `vulnerability_results` | 漏洞结果 |

### 6.3 MySQL 字段兼容性分析

| 字段类型 | SQLite | MySQL 8.0 | 兼容性 |
|---------|--------|-----------|--------|
| `String(32)` 主键（UUID hex） | TEXT | VARCHAR(32) | ✅ 兼容 |
| `String(200)` | TEXT | VARCHAR(200) | ✅ 兼容 |
| `Text` | TEXT | TEXT | ✅ 兼容 |
| `JSON` | JSON | JSON | ✅ 兼容（MySQL 8.0+ 原生支持） |
| `Integer` | INTEGER | INT | ✅ 兼容 |
| `Float` | REAL | FLOAT | ✅ 兼容 |
| `Boolean` | BOOLEAN | TINYINT(1) | ✅ 兼容 |
| `DateTime` | DATETIME | DATETIME | ✅ 兼容 |

**唯一需要适配的代码**（已自动处理）：
- `cleanup_service.py:350` 的 `VACUUM`（SQLite 专用）→ 已有 `if "sqlite" not in` 保护，MySQL 自动跳过 ✅
- `database.py:32` 的 `check_same_thread`（SQLite 专用）→ 已有条件判断 ✅

---

## 七、Nginx 配置（可选）

> **说明**：本方案默认用 FastAPI 直接 serve 静态文件，**无需 Nginx**。
> 如果未来需要 HTTPS、负载均衡、或更高的静态文件性能，可再加 Nginx 反向代理。

如果需要，**文件**: `deploy/nginx.conf`

```nginx
server {
    listen 80;
    server_name _;

    # 前端静态文件（如需 Nginx 托管）
    location / {
        root /app/frontend/dist;
        try_files $uri $uri/ /index.html;
    }

    # API 反向代理
    location /api/ {
        proxy_pass http://app:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 300s;
    }

    # WebSocket 代理
    location /api/executions/ws/ {
        proxy_pass http://app:80;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400s;
    }
}
```

---

## 八、环境变量

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

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

# ==================== 应用配置 ====================
# DATABASE_URL 由 docker-compose 根据 MYSQL_* 变量自动拼接，无需手动设置
PLAYWRIGHT_HEADLESS=true
PLAYWRIGHT_TIMEOUT=30000
LOG_LEVEL=INFO
```

> ⚠️ **安全提示**：生产环境请修改 `MYSQL_ROOT_PASSWORD` 和 `MYSQL_PASSWORD` 为强密码。
> `.env` 文件不要提交到 git。

---

## 九、部署步骤

### Phase 1: 服务器准备（首次）

```bash
# SSH 到服务器
ssh ubains@192.168.5.60

# 停掉旧的 V1 容器（如存在）
cd ~/ubains-module-test/deploy 2>/dev/null && docker compose down 2>/dev/null

# 创建项目目录
sudo mkdir -p /data/third_party/plat-auto-test/{deploy,backend,frontend/dist,data/screenshots,data/reports,mysql/data,logs}

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

### Phase 2: 上传部署配置

从本地（Windows）上传 `deploy/` 目录下所有文件到服务器：

```powershell
# 本地执行
scp -r deploy/* ubains@192.168.5.60:/data/third_party/plat-auto-test/deploy/
```

### Phase 3: 上传后端源码

```powershell
# 本地：上传 backend/ 到服务器（排除 pycache）
scp -r backend/app backend/scripts backend/requirements.txt ubains@192.168.5.60:/data/third_party/plat-auto-test/backend/
```

### Phase 4: 构建并上传前端

```powershell
# 本地：构建前端
cd frontend
npm run build

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

### Phase 5: 构建镜像并启动

```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 6: 验证

```bash
# 健康检查
curl http://localhost/health
# 期望: {"status":"healthy",...}

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

# 数据库连接验证（查看表是否自动创建）
docker exec plat-auto-test-mysql mysql -uplatapp -pPlatApp@2026 plat_auto_test -e "SHOW TABLES;"
```

### Phase 7: 数据迁移（从本地 SQLite）

由于从 SQLite 迁移到 MySQL，需要导出本地数据并导入：

```bash
# 本地：从 SQLite 导出数据为 SQL
# （后续编写迁移脚本，见第十一节）
```

---

## 十、更新流程

> **核心优势**：镜像构建后，更新代码无需重建镜像，秒级生效。

### 10.1 更新前端（无需重启服务）

```powershell
# 本地改完前端代码后，构建
cd frontend
npm run build

# 上传到服务器（直接覆盖 dist/）
scp -r dist/* ubains@192.168.5.60:/data/third_party/plat-auto-test/frontend/dist/
```

✅ **前端无需重启**：FastAPI 静态文件服务实时读取文件系统，浏览器刷新即可看到新版本。

### 10.2 更新后端（需重启服务，秒级）

```powershell
# 本地改完后端代码后，上传
scp -r backend/app ubains@192.168.5.60:/data/third_party/plat-auto-test/backend/
```

```bash
# 服务器：重启应用容器（约 5-10 秒）
ssh ubains@192.168.5.60
cd /data/third_party/plat-auto-test/deploy
docker compose restart app
```

### 10.3 更新数据库配置（需重启 MySQL）

```bash
# 修改 .env 后重启 MySQL
nano .env
docker compose restart mysql app
```

### 10.4 更新依赖（需重建镜像）

仅当 `requirements.txt` 改变时才需要：

```bash
cd /data/third_party/plat-auto-test/deploy
docker compose build app
docker compose up -d app
```

---

## 十一、MySQL 迁移改造

### 11.1 后端代码改造清单

| 文件 | 改造内容 | 说明 |
|------|---------|------|
| `backend/app/main.py` | 新增挂载前端静态文件 | FastAPI serve SPA |
| `backend/app/config.py` | DATABASE_URL 默认改 MySQL（或保持环境变量） | 兼容 SQLite/MySQL |
| `backend/requirements.txt` | 新增 `aiomysql`、`cryptography` | MySQL 异步驱动 |
| `backend/app/database.py` | 无需改动 | 已做条件判断 |

### 11.2 main.py 改造（挂载前端静态文件）

在 `main.py` 末尾添加：

```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")

    # SPA 路由：所有非 /api 路径回退到 index.html
    @app.get("/{full_path:path}")
    async def serve_spa(full_path: str):
        """前端 SPA 入口，所有非 API 路径返回 index.html"""
        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"))
```

### 11.3 requirements.txt 改造

```diff
  fastapi==0.110.0
  uvicorn[standard]==0.27.0
  sqlalchemy==2.0.25
  aiosqlite==0.19.0      # 保留，开发环境仍用 SQLite
+ aiomysql==0.2.0        # 新增，MySQL 异步驱动
+ cryptography==42.0.0   # 新增，MySQL 认证需要
  pydantic==2.5.3
  python-multipart==0.0.6
  playwright==1.40.0
  websockets==12.0
  requests==2.31.0
```

### 11.4 数据迁移脚本

从本地 SQLite 导出到 MySQL，编写脚本 `backend/scripts/migrate_sqlite_to_mysql.py`：

```python
#!/usr/bin/env python
"""
SQLite → MySQL 数据迁移脚本
将本地 backend/data/test_platform.db 的数据迁移到 MySQL
"""
# 详见后续实现
```

---

## 十二、验收标准

### 12.1 功能验收

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

### 12.2 性能验收

| 验收项 | 标准 |
|--------|------|
| 应用容器启动时间 | < 15 秒（等待 MySQL 健康检查） |
| API 响应时间 | 健康检查 < 100ms |
| 后端重启时间 | < 10 秒 |
| 前端首屏加载 | < 3 秒 |

---

## 十三、运维指南

### 13.1 常用命令

```bash
cd /data/third_party/plat-auto-test/deploy

# 查看状态
docker compose ps

# 查看日志
docker compose logs -f app         # 应用日志
docker compose logs -f mysql       # 数据库日志

# 重启服务
docker compose restart app         # 重启应用（更新后端后用）
docker compose restart mysql       # 重启数据库

# 停止/启动
docker compose down                # 停止所有服务
docker compose up -d               # 启动所有服务

# 进入容器
docker exec -it plat-auto-test-app bash
docker exec -it plat-auto-test-mysql mysql -uplatapp -pPlatApp@2026 plat_auto_test
```

### 13.2 数据备份

```bash
# MySQL 备份
docker exec plat-auto-test-mysql mysqldump -uroot -pUbains@13579 \
    plat_auto_test > /data/third_party/plat-auto-test/backup_$(date +%Y%m%d).sql

# 定时备份（crontab）
# 每天凌晨 3 点备份
0 3 * * * docker exec plat-auto-test-mysql mysqldump -uroot -pUbains@13579 plat_auto_test > /data/third_party/plat-auto-test/backup/backup_$(date +\%Y\%m\%d).sql 2>/dev/null
```

### 13.3 数据恢复

```bash
# 从备份恢复
docker exec -i plat-auto-test-mysql mysql -uroot -pUbains@13579 plat_auto_test < backup_20260727.sql
```

### 13.4 截图/报告清理

```bash
# 清理 30 天前的截图和报告
find /data/third_party/plat-auto-test/data/screenshots -type f -mtime +30 -delete
find /data/third_party/plat-auto-test/data/reports -type f -mtime +30 -delete
```

---

## 十四、常见问题

### 14.1 应用连不上 MySQL

**现象**：app 容器日志报 `Can't connect to MySQL server`

**排查**：
```bash
# 1. 检查 MySQL 容器状态
docker compose ps mysql

# 2. 检查 MySQL 健康状态
docker exec plat-auto-test-mysql mysqladmin ping -uroot -pUbains@13579

# 3. 检查网络
docker exec plat-auto-test-app ping mysql

# 4. 检查 DATABASE_URL
docker exec plat-auto-test-app env | grep DATABASE_URL
```

### 14.2 表未自动创建

**现象**：API 报错表不存在

**原因**：MySQL 启动慢，app 连接时数据库未就绪

**解决**：
```bash
# 手动重启 app（MySQL 就绪后）
docker compose restart app
```

### 14.3 Playwright 在容器中执行失败

**现象**：执行用例报 `Missing libraries`

**解决**：确认 Dockerfile 中系统依赖完整（已包含所有 Playwright 依赖库）

### 14.4 前端更新不生效

**现象**：替换 dist/ 后浏览器还是旧版本

**解决**：
- 浏览器强制刷新（Ctrl+F5）清除缓存
- 或在浏览器开发者工具禁用缓存

### 14.5 MySQL 端口冲突

**现象**：MySQL 容器启动失败，提示 3306 被占用

**解决**：修改 `docker-compose.yml` 中 `ports: "3307:3306"` 为其他端口

### 14.6 WebSocket 连接失败

**现象**：执行页面进度不实时更新

**原因**：如使用 Nginx，需配置 WebSocket 升级头（见第七节）

---

## 附录

### A. 部署架构演进路线

| 阶段 | 架构 | 状态 |
|------|------|------|
| V1 | 前端 Nginx + 后端 + SQLite（代码 COPY 进镜像） | ✅ 已部署 |
| V2 | 合一应用 + MySQL（代码 volume 挂载） | 📋 本方案 |
| V3（远期） | 应用 + MySQL + Redis 缓存 + HTTPS + 监控 | 🔮 规划中 |

### B. 相关文档

| 文档 | 路径 |
|------|------|
| V1 部署方案 | `Docs/部署方案/Linux服务器部署方案.md` |
| V1 执行计划 | `Docs/部署方案/Linux容器化部署_执行计划.md` |
| 容器化优化 PRD | `Docs/PRD/需求文档/部署运维/_PRD_需求文档_容器化部署Volume挂载优化.md` |
| 容器化优化计划 | `Docs/PRD/需求文档/部署运维/_执行计划_容器化部署Volume挂载优化.md` |
| UI 自动化交接 | `HANDOFF_UI自动化.md` |
| 项目指南 | `CLAUDE.md` |

### C. 服务器信息

| 项目 | 值 |
|------|-----|
| 服务器 IP | 192.168.5.60 |
| SSH 用户 | ubains |
| 操作系统 | Ubuntu 26.04 LTS |
| 已有服务 | 8088 端口（另一维护平台） |
| 新部署目录 | `/data/third_party/plat-auto-test/` |
| 前端地址 | http://192.168.5.60 |
| API 文档 | http://192.168.5.60/docs |
| MySQL 外部访问 | 192.168.5.60:3307 |

---

*本文档由 Claude Code 编写，建议根据实际服务器环境调整参数。部署前请评审确认架构方案。*
