4ceb5eac7e
文件结构优化: - 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>
599 lines
15 KiB
Markdown
599 lines
15 KiB
Markdown
# 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 │
|
||
└───────────┘ └───────────┘ └───────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 部署步骤
|
||
|
||
### 第一步:准备线上目录结构
|
||
|
||
```bash
|
||
# 在服务器上创建部署目录
|
||
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 本地构建
|
||
|
||
```bash
|
||
# 在开发环境
|
||
cd /www/x-server-mobile/frontend
|
||
|
||
# 安装依赖(如果需要)
|
||
npm install
|
||
|
||
# 构建生产版本
|
||
npm run build
|
||
|
||
# 生成的文件在 frontend/dist/ 目录
|
||
```
|
||
|
||
#### 2.2 上传静态文件
|
||
|
||
```bash
|
||
# 方式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`:
|
||
|
||
```env
|
||
# 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`
|
||
|
||
```yaml
|
||
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`):
|
||
|
||
```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
|
||
|
||
```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`
|
||
|
||
```nginx
|
||
# 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 创建数据库和用户
|
||
|
||
```sql
|
||
-- 创建专用数据库用户
|
||
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 数据库迁移
|
||
|
||
```bash
|
||
# 从开发环境导出数据
|
||
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(推荐)
|
||
|
||
```bash
|
||
# 安装 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 自动续期
|
||
|
||
```bash
|
||
# 添加 crontab
|
||
0 3 * * * certbot renew --quiet && nginx -s reload
|
||
```
|
||
|
||
---
|
||
|
||
### 第七步:部署流程
|
||
|
||
#### 开发到生产部署
|
||
|
||
```bash
|
||
#!/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 日志位置
|
||
|
||
```bash
|
||
# 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 监控命令
|
||
|
||
```bash
|
||
# 检查后端容器状态
|
||
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 防火墙配置
|
||
|
||
```bash
|
||
# 只开放必要端口
|
||
ufw allow 22 # SSH
|
||
ufw allow 80 # HTTP
|
||
ufw allow 443 # HTTPS
|
||
ufw enable
|
||
```
|
||
|
||
#### 9.2 PHP 安全配置
|
||
|
||
```ini
|
||
# 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 快速回滚
|
||
|
||
```bash
|
||
#!/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)
|
||
|
||
---
|
||
|
||
## 联系方式
|
||
|
||
如有问题,请联系开发团队。
|