Files
x-server-mobile/docs/DEPLOYMENT.md
T
root 4ceb5eac7e chore: 重组项目文件结构,文档和部署文件归档
文件结构优化:
- docs/ 归档所有项目文档(PROJECT.md, PROJECT_ISSUES.md 等)
- deploy/ 归档所有部署相关文件(docker-compose.prod.yml, nginx 配置等)
- scripts/ 归档脚本文件(deploy.sh, rollback.sh)
- tests/ 归档测试文件(test_data.php)

更新引用路径:
- CLAUDE.md 更新文档路径引用
- 部署文档更新配置文件路径引用

清理文件:
- 移除非必要的测试文件(TestController, 测试脚本等)
- 移除重复/错误的目录

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 18:01:17 +08:00

15 KiB
Raw Blame History

X-Server Mobile 线上部署方案

线上环境

  • 面板: 1Panel (Docker 管理)
  • Web 服务器: OpenResty
  • 数据库: MySQL (容器)
  • 缓存: Redis (容器)
  • 域名: xsm.hnxinyouhe.com (待配置)

架构设计

                    ┌─────────────────────────────────────┐
                    │           OpenResty (80/443)        │
                    │   ┌─────────────────────────────┐   │
                    │   │     静态资源托管             │   │
                    │   │   /frontend/dist/*          │   │
                    │   │   SPA Router fallback       │   │
                    │   └─────────────────────────────┘   │
                    │   ┌─────────────────────────────┐   │
                    │   │     API 反向代理             │   │
                    │   │   /api/* → localhost:8787   │   │
                    │   └─────────────────────────────┘   │
                    │   ┌─────────────────────────────┐   │
                    │   │     飞书回调代理             │   │
                    │   │   /login/* → localhost:8787 │   │
                    │   └─────────────────────────────┘   │
                    └─────────────────────────────────────┘
                                    │
                    ┌───────────────┼───────────────┐
                    ▼               ▼               ▼
            ┌───────────┐   ┌───────────┐   ┌───────────┐
            │  后端     │   │  MySQL    │   │  Redis    │
            │  :8787    │   │  :3306    │   │  :6379    │
            │  Docker   │   │  1Panel   │   │  1Panel   │
            └───────────┘   └───────────┘   └───────────┘

部署步骤

第一步:准备线上目录结构

# 在服务器上创建部署目录
mkdir -p /opt/x-server-mobile
mkdir -p /opt/x-server-mobile/frontend
mkdir -p /opt/x-server-mobile/backend
mkdir -p /opt/x-server-mobile/logs
mkdir -p /opt/x-server-mobile/backup

第二步:前端部署

2.1 本地构建

# 在开发环境
cd /www/x-server-mobile/frontend

# 安装依赖(如果需要)
npm install

# 构建生产版本
npm run build

# 生成的文件在 frontend/dist/ 目录

2.2 上传静态文件

# 方式1:使用 scp 上传
scp -r frontend/dist/* root@your-server:/opt/x-server-mobile/frontend/

# 方式2:使用 rsync 同步
rsync -avz --delete frontend/dist/ root@your-server:/opt/x-server-mobile/frontend/

2.3 构建环境配置

创建 frontend/.env.production:

# API 地址(使用相对路径,由 OpenResty 代理)
VITE_API_BASE=/api/v1

# 或使用绝对地址(如果前后端分离部署)
# VITE_API_BASE=https://api.xsm.hnxinyouhe.com

第三步:后端部署

3.1 创建生产环境 Docker Compose

文件: /opt/x-server-mobile/deploy/docker-compose.prod.yml

services:
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    container_name: x-server-backend-prod
    restart: unless-stopped
    ports:
      - "8787:8787"
    environment:
      # 生产环境配置
      - APP_ENV=production
      - APP_DEBUG=false

      # 数据库配置(使用 1Panel 的 MySQL)
      - DB_HOST=1Panel-mysql-xxxx  # 替换为实际容器名
      - DB_PORT=3306
      - DB_DATABASE=xserver
      - DB_USERNAME=xserver        # 建议创建专用数据库用户
      - DB_PASSWORD=your_password  # 强密码

      # Redis 配置(使用 1Panel 的 Redis)
      - REDIS_HOST=1Panel-redis-xxxx  # 替换为实际容器名
      - REDIS_PORT=6379
      - REDIS_PASSWORD=your_redis_password

      # 飞书配置
      - FEISHU_APP_ID=cli_xxxxxxxxxxxxx
      - FEISHU_APP_SECRET=xxxxxxxxxxxxxxxxxxxx
      - FEISHU_ENCRYPT_KEY=xxxxxxxxxxxxxxxxxxxx
      - FEISHU_VERIFICATION_TOKEN=xxxxxxxxxxxxxxxxxxxx
      - FEISHU_TENANT_KEY=xxxxxxxxxxxxxxxxxxxx
    volumes:
      - ./backend:/app
      - ./logs/app:/app/runtime/logs
    networks:
      - 1panel-network
    healthcheck:
      test: ["CMD", "php", "-r", "exit(0);"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

networks:
  1panel-network:
    external: true

3.2 创建生产环境后端配置

后端 OPcache 配置 (deploy/php-prod.ini):

# 生产环境 OPcache 配置
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1

# 禁用错误显示
display_errors=0
display_startup_errors=0
log_errors=1
error_log=/app/runtime/logs/php_errors.log

3.3 更新 Dockerfile

FROM php:8.5-cli

# Install system dependencies
RUN apt-get update && apt-get install -y \
    unzip \
    git \
    libzip-dev \
    libpng-dev \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-install zip pdo pdo_mysql bcmath pcntl opcache

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer

# Set working directory
WORKDIR /app

# Copy composer files
COPY composer.json composer.lock* ./

# Install dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction

# Copy application files
COPY . .

# Copy PHP configuration
COPY php-prod.ini /usr/local/etc/php/conf.d/99-production.ini

# Create runtime directories
RUN mkdir -p /app/runtime/logs

# Expose port
EXPOSE 8787

# Start webman in foreground mode
CMD ["php", "start.php", "start"]

第四步:OpenResty 配置

4.1 站点配置

文件: /usr/local/openresty/nginx/conf/conf.d/x-server-mobile.conf

# X-Server Mobile 线上环境配置
server {
    listen 80;
    server_name xsm.hnxinyouhe.com;

    # 强制 HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name xsm.hnxinyouhe.com;

    # SSL 证书配置(使用 Let's Encrypt 或自签名)
    ssl_certificate /etc/nginx/ssl/xsm.hnxinyouhe.com.crt;
    ssl_certificate_key /etc/nginx/ssl/xsm.hnxinyouhe.com.key;

    # SSL 安全配置
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # 日志
    access_log /opt/x-server-mobile/logs/nginx_access.log;
    error_log /opt/x-server-mobile/logs/nginx_error.log;

    # Gzip 压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript
               application/json application/javascript application/xml+rss
               application/rss+xml font/truetype font/opentype
               application/vnd.ms-fontobject image/svg+xml;

    # 静态资源缓存
    location ~* \.(js|css|png|jpg|jpeg|gif|webp|svg|ico|woff|woff2|ttf|eot)$ {
        root /opt/x-server-mobile/frontend;
        expires 7d;
        add_header Cache-Control "public, immutable";
    }

    # 飞书回调(必须在 API 之前配置)
    location /login {
        proxy_pass http://127.0.0.1:8787;
        proxy_http_version 1.1;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /login/callback {
        proxy_pass http://127.0.0.1:8787;
        proxy_http_version 1.1;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /login/qrcode {
        proxy_pass http://127.0.0.1:8787;
        proxy_http_version 1.1;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # 飞书卡片回调
    location /feishu/card/callback {
        proxy_pass http://127.0.0.1:8787;
        proxy_http_version 1.1;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # API 代理
    location /api/ {
        proxy_pass http://127.0.0.1:8787;
        proxy_http_version 1.1;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;

        # 超时配置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # 前端 SPA(所有其他请求返回 index.html)
    location / {
        root /opt/x-server-mobile/frontend;
        try_files $uri $uri/ /index.html;
        add_header Cache-Control "no-cache, no-store, must-revalidate";
    }
}

4.2 配置说明

  1. 静态资源: 直接由 OpenResty 托管,7天缓存
  2. API 请求: 代理到后端 8787 端口
  3. 飞书回调: 特殊路径直接代理到后端
  4. SPA 路由: 所有其他请求返回 index.html

第五步:数据库准备

5.1 创建数据库和用户

-- 创建专用数据库用户
CREATE USER 'xserver'@'%' IDENTIFIED BY 'your_strong_password';

-- 授予权限
GRANT ALL PRIVILEGES ON xserver.* TO 'xserver'@'%';
FLUSH PRIVILEGES;

-- 如果数据库不存在,创建并导入结构
CREATE DATABASE IF NOT EXISTS xserver CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

5.2 数据库迁移

# 从开发环境导出数据
docker exec 1Panel-mysql-409s mysqldump -uroot -pmysql_xEsdPa xserver > xserver_backup.sql

# 导入到线上数据库
docker exec -i 1Panel-mysql-xxxx mysql -uxserver -pyour_password xserver < xserver_backup.sql

第六步:SSL 证书配置

6.1 使用 Let's Encrypt(推荐)

# 安装 certbot
apt-get install certbot

# 获取证书
certbot certonly --standalone -d xsm.hnxinyouhe.com

# 证书路径
# /etc/letsencrypt/live/xsm.hnxinyouhe.com/fullchain.pem
# /etc/letsencrypt/live/xsm.hnxinyouhe.com/privkey.pem

6.2 自动续期

# 添加 crontab
0 3 * * * certbot renew --quiet && nginx -s reload

第七步:部署流程

开发到生产部署

#!/bin/bash
# deploy.sh - 部署脚本

set -e

echo "========================================="
echo "  X-Server Mobile 生产部署脚本"
echo "========================================="

# 1. 备份当前版本
echo "📦 备份当前版本..."
ssh root@your-server "mkdir -p /opt/x-server-mobile/backup/\$(date +%Y%m%d_%H%M%S)"
ssh root@your-server "cp -r /opt/x-server-mobile/frontend /opt/x-server-mobile/backup/\$(date +%Y%m%d_%H%M%S)/"

# 2. 构建前端
echo "🔨 构建前端..."
cd frontend
npm install
npm run build

# 3. 上传前端
echo "📤 上传前端..."
rsync -avz --delete dist/ root@your-server:/opt/x-server-mobile/frontend/

# 4. 上传后端
echo "📤 上传后端..."
rsync -avz --exclude='vendor' --exclude='runtime' \
    ../backend/ root@your-server:/opt/x-server-mobile/backend/

# 5. 重启后端容器
echo "🔄 重启后端..."
ssh root@your-server "cd /opt/x-server-mobile && docker-compose -f docker-compose.prod.yml restart backend"

# 6. 重载 OpenResty
echo "🔄 重载 OpenResty..."
ssh root@your-server "openresty -s reload"

echo "✅ 部署完成!"

第八步:监控和日志

8.1 日志位置

# Nginx 访问日志
/opt/x-server-mobile/logs/nginx_access.log

# Nginx 错误日志
/opt/x-server-mobile/logs/nginx_error.log

# 后端应用日志
docker logs x-server-backend-prod -f

# 后端运行时日志
/opt/x-server-mobile/logs/app/

8.2 监控命令

# 检查后端容器状态
docker ps | grep x-server-backend-prod

# 检查后端健康
curl http://127.0.0.1:8787/

# 检查数据库连接
docker exec x-server-backend-prod php -r "var_dump(getenv('DB_HOST'));"

# 查看 PHP OPcache 状态
# 需要在后端添加 OPcache 状态页面

第九步:安全加固

9.1 防火墙配置

# 只开放必要端口
ufw allow 22    # SSH
ufw allow 80    # HTTP
ufw allow 443   # HTTPS
ufw enable

9.2 PHP 安全配置

# php-prod.ini
expose_php = Off
allow_url_fopen = Off
allow_url_include = Off
disable_functions = exec,passthru,shell_exec,system,proc_open,popen

9.3 环境变量保护

  • 不要将敏感信息提交到代码仓库
  • 使用 .env 文件或 Docker secrets 管理敏感信息
  • 定期轮换密码和密钥

第十步:回滚方案

10.1 快速回滚

#!/bin/bash
# rollback.sh - 回滚脚本

BACKUP_DATE=$1  # 格式: 20240227_150000

if [ -z "$BACKUP_DATE" ]; then
    echo "请指定备份日期,格式: 20240227_150000"
    exit 1
fi

echo "🔄 回滚到 $BACKUP_DATE ..."

# 恢复前端
cp -r /opt/x-server-mobile/backup/$BACKUP_DATE/frontend/* /opt/x-server-mobile/frontend/

# 恢复后端(可选)
# cd /opt/x-server-mobile/backend
# git checkout <previous-commit>
# docker-compose -f docker-compose.prod.yml restart backend

# 重载 OpenResty
openresty -s reload

echo "✅ 回滚完成"

检查清单

部署前检查:

  • 域名 DNS 已配置指向服务器 IP
  • SSL 证书已配置
  • MySQL 数据库已创建
  • Redis 可正常连接
  • 飞书应用回调地址已更新
  • 环境变量已正确配置
  • 防火墙规则已设置
  • 日志目录已创建

部署后验证:

  • 前端页面可正常访问
  • API 接口返回正常
  • 飞书登录功能正常
  • 审批流程功能正常
  • 日志输出正常
  • 容器健康检查通过

常见问题

Q1: 前端刷新 404

原因: OpenResty 配置没有正确处理 SPA 路由

解决: 确保 try_files $uri $uri/ /index.html; 配置正确

Q2: API 请求跨域

原因: 前端和后端域名不同

解决: 在后端添加 CORS 头,或使用 OpenResty 代理统一域名

Q3: 飞书回调失败

原因: 回调地址配置错误或 SSL 证书问题

解决:

  • 确认飞书后台配置的回调地址为 https://xsm.hnxinyouhe.com/login/callback
  • 确保服务器可公网访问
  • 检查 SSL 证书是否有效

Q4: 容器无法连接数据库

原因: 容器网络不在同一个网络

解决: 确保后端容器和 MySQL/Redis 容器在同一个 Docker 网络(如 1panel-network)


联系方式

如有问题,请联系开发团队。