签入版本

This commit is contained in:
2026-09-29 10:14:04 +08:00
parent 3e5fc01124
commit d6b552103d
1664 changed files with 298944 additions and 0 deletions
@@ -0,0 +1,423 @@
# 11.4 后端部署
:::tip 概述
本节详细说明后端服务的部署配置,包括 Nginx、Apache、PHP 生产优化、定时任务配置,以及多数据库部署注意事项。
:::
## Nginx 配置
```nginx
# =============================================================================
# Nginx 站点配置 — ThinkPHP 6 后端 API
# =============================================================================
# 用途:生产环境 Nginx 反向代理,将 HTTP 请求转发给 PHP-FPM 处理
# 配置路径:/etc/nginx/sites-available/rxthinkcmf
# 启用方式:ln -s /etc/nginx/sites-available/rxthinkcmf /etc/nginx/sites-enabled/
# =============================================================================
server {
# 监听 80 端口(HTTP),生产环境建议改为 443(HTTPS)
listen 80;
# 绑定域名(替换为实际域名)
server_name api.example.com;
# 网站根目录指向 ThinkPHP 入口目录
# 注意:必须指向 public/ 子目录,而非项目根目录
root /www/api/public;
index index.php;
# -----------------------------------------------------------------
# URL 重写(ThinkPHP 必需)
# -----------------------------------------------------------------
# 将所有非真实文件请求转发到 index.php,由 ThinkPHP 路由解析
# 例:GET /api/user/page → /index.php?s=/api/user/page
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=$1 last;
}
}
# -----------------------------------------------------------------
# PHP-FPM 处理
# -----------------------------------------------------------------
# 所有 .php 请求通过 FastCGI 协议转发给 PHP-FPM 进程
# 127.0.0.1:9000 是 PHP-FPM 默认监听地址
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000; # PHP-FPM 监听地址
fastcgi_index index.php; # 默认索引文件
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params; # 引入标准 FastCGI 参数
# 超时设置(秒),防止长请求(如 Excel 导出)被 Nginx 提前断开
fastcgi_read_timeout 300; # PHP 脚本最长执行时间
fastcgi_send_timeout 300; # Nginx 向 PHP-FPM 发送请求的超时
}
# -----------------------------------------------------------------
# 安全规则
# -----------------------------------------------------------------
# 禁止访问隐藏文件(如 .env、.git、.htaccess)
location ~ /\. {
deny all;
}
# 禁止上传目录执行 PHP(防止恶意文件上传后被当作脚本执行)
location ~ /uploads/.*\.php$ {
deny all;
}
# -----------------------------------------------------------------
# 静态资源缓存
# -----------------------------------------------------------------
# 常见静态文件类型设置 30 天浏览器缓存,减少重复请求
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# -----------------------------------------------------------------
# Gzip 压缩
# -----------------------------------------------------------------
# 对文本类响应启用压缩,减少传输体积(可节省 60%+ 带宽)
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1024; # 小于 1KB 的响应不压缩(压缩收益低)
}
```
### CDN / 负载均衡部署
当项目部署在 CDN、云防护或负载均衡后面时,PHP 获取到的 `REMOTE_ADDR` 是代理服务器的 IP(通常为 `127.0.0.1`),而非真实客户端 IP。
框架内置 `ProxyMiddleware` 全局中间件,会自动从 `X-Forwarded-For` / `X-Real-IP` 请求头中提取真实客户端 IP。只需在 `.env` 的 `[API]` 段配置受信代理 IP:
```ini
[API]
# 受信代理 IP(逗号分隔,支持 CIDR 网段)
# 腾讯云 CDN 常用 IP 段(完整列表请参考云服务商文档)
PROXY_IPS = 127.0.0.1,::1,101.226.0.0/15,112.65.0.0/16,140.207.0.0/16,180.101.0.0/16,182.254.0.0/16,223.166.0.0/16
```
:::warning 重要
- 未配置 `PROXY_IPS` 时,默认信任 `127.0.0.1` 和 `::1`
- **不要**在 Nginx 的 `fastcgi_param` 中手动覆盖 `REMOTE_ADDR`、`HTTP_X_REAL_IP`、`HTTP_X_FORWARDED_FOR`,否则会丢失 CDN 传递的真实 IP
- 配置后需清除运行时缓存:`rm -rf runtime/*`
:::
## Apache 配置
项目 `public/.htaccess` 已配置好 URL 重写,确保启用 `mod_rewrite`:
```bash
# 启用 Apache 重写模块(ThinkPHP 路由依赖此模块)
sudo a2enmod rewrite
# 编辑站点配置文件
sudo nano /etc/apache2/sites-available/000-default.conf
```
```apache
# =============================================================================
# Apache 虚拟主机配置 — ThinkPHP 6 后端 API
# =============================================================================
# 配置路径:/etc/apache2/sites-available/000-default.conf
# 生效命令:sudo systemctl restart apache2
# =============================================================================
<VirtualHost *:80>
# 绑定域名(替换为实际域名)
ServerName api.example.com
# 网站根目录指向 ThinkPHP 入口目录(必须是 public/)
DocumentRoot /www/api/public
# 允许 .htaccess 覆盖配置(ThinkPHP URL 重写依赖此设置)
<Directory /www/api/public>
AllowOverride All # 允许 .htaccess 覆盖所有配置
Require all granted # 允许所有 IP 访问
</Directory>
# 安全:禁止上传目录执行 PHP
<Directory /www/api/public/uploads>
<FilesMatch "\.php$">
Require all denied # 拒绝所有 .php 文件的访问
</FilesMatch>
</Directory>
</VirtualHost>
```
## PHP 生产环境优化
### php.ini 推荐配置
```ini
; =============================================================================
; php.ini 生产环境推荐配置
; =============================================================================
; 配置路径:/etc/php/8.2/fpm/php.ini(Nginx + PHP-FPM 场景)
; 生效命令:sudo systemctl restart php8.2-fpm
; =============================================================================
; -----------------------------------------------------------------------------
; 错误处理(生产环境必须关闭错误显示)
; -----------------------------------------------------------------------------
display_errors = Off ; 不向浏览器显示错误(防止 SQL/路径泄露)
error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED ; 记录除通知和弃用外的所有错误
; -----------------------------------------------------------------------------
; OPcache 加速(生产环境必须启用)
; -----------------------------------------------------------------------------
opcache.enable = 1 ; 启用 OPcache 字节码缓存
opcache.memory_consumption = 256 ; OPcache 共享内存大小(MB),按项目规模调整
opcache.interned_strings_buffer = 16 ; 驻留字符串内存(MB)
opcache.max_accelerated_files = 10000 ; 最大缓存文件数(需大于项目 PHP 文件总数)
opcache.validate_timestamps = 0 ; 不检查文件修改时间(性能最优,更新代码后需重启 PHP-FPM)
opcache.save_comments = 1 ; 保留注释(ThinkPHP 注解依赖此配置)
```
:::warning OPcache 注意事项
`opcache.validate_timestamps = 0` 表示不检查文件修改时间,性能最优但更新代码后需要手动清除缓存:`php opcache:clear` 或重启 PHP-FPM。开发环境建议设为 `1`。
:::
```ini
; -----------------------------------------------------------------------------
; 内存与超时
; -----------------------------------------------------------------------------
memory_limit = 256M ; 单脚本最大内存(Excel 导出建议 256M+)
max_execution_time = 60 ; 单脚本最大执行时间(秒)
max_input_time = 60 ; 解析请求数据的最大时间(秒)
; -----------------------------------------------------------------------------
; 文件上传
; -----------------------------------------------------------------------------
upload_max_filesize = 50M ; 单文件最大上传大小
post_max_size = 50M ; POST 请求最大大小(必须 >= upload_max_filesize)
max_file_uploads = 20 ; 单次请求最大上传文件数
; -----------------------------------------------------------------------------
; 时区与会话
; -----------------------------------------------------------------------------
date.timezone = Asia/Shanghai ; 时区(与 .env 中 APP.DEFAULT_TIMEZONE 一致)
session.gc_maxlifetime = 7200 ; 会话过期时间(秒),默认 2 小时
```
### PHP-FPM 配置
```ini
; =============================================================================
; PHP-FPM 进程池配置
; =============================================================================
; 配置路径:/etc/php/8.2/fpm/pool.d/www.conf
; 生效命令:sudo systemctl restart php8.2-fpm
; =============================================================================
[www]
; 运行用户(Nginx 也需使用同一用户,否则权限冲突)
user = www-data
group = www-data
; -----------------------------------------------------------------------------
; 进程管理方式
; -----------------------------------------------------------------------------
; dynamic = 动态管理(推荐生产环境),根据负载自动调整进程数
; static = 固定进程数(高并发场景)
pm = dynamic
pm.max_children = 50 ; 最大子进程数(根据服务器内存调整,每个约 30-50MB)
pm.start_servers = 5 ; 启动时的进程数
pm.min_spare_servers = 5 ; 最小空闲进程数(低于此值会自动创建)
pm.max_spare_servers = 35 ; 最大空闲进程数(高于此值会自动销毁)
pm.max_requests = 500 ; 单进程最大请求数(达到后重启,防内存泄漏)
; -----------------------------------------------------------------------------
; 慢请求日志(排查性能问题)
; -----------------------------------------------------------------------------
slowlog = /var/log/php-fpm-slow.log ; 慢请求日志路径
request_slowlog_timeout = 5s ; 超过 5 秒的请求记录为慢请求
```
## 定时任务配置
### Linux Crontab
```bash
# 编辑当前用户的 crontab 定时任务
crontab -e
# 添加定时任务:每分钟执行一次任务调度器
# 格式:分 时 日 月 周 命令
# * * * * * = 每分钟
# cd /www/api → 切换到项目目录(ThinkPHP 需要工作目录定位配置文件)
# /usr/bin/php → PHP 绝对路径(crontab 环境变量不完整,必须用绝对路径)
# think job:run → 执行 ThinkPHP 命令行任务调度
# >> /dev/null 2>&1 → 丢弃输出(有日志系统记录,不需要控制台输出)
* * * * * cd /www/api && /usr/bin/php think job:run >> /dev/null 2>&1
# 验证 crontab 是否配置成功
crontab -l
```
### Windows 计划任务
```powershell
# =============================================================================
# Windows 计划任务 — 每分钟执行一次任务调度器
# =============================================================================
# /create → 创建新任务
# /tn → 任务名称(在任务计划程序中显示的名称)
# /tr → 要执行的命令
# cmd /c → 执行后关闭 cmd 窗口
# cd /d → 切换驱动器和目录(/d 支持跨盘符)
# /sc minute → 调度频率为"分钟"
# /mo 1 → 每 1 分钟执行一次
# /f → 强制覆盖同名任务
# =============================================================================
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\xampp\htdocs\v3\thinkphp6 && C:\xampp\php\php.exe think job:run" /sc minute /mo 1 /f
# 或通过图形界面:Win+R → 输入 taskschd.msc → 回车
```
### Daemon 守护进程(Supervisor)
```ini
; =============================================================================
; Supervisor 进程守护配置
; =============================================================================
; 配置路径:/etc/supervisor/conf.d/rxthinkcmf-job.conf
; 生效命令:sudo supervisorctl reread && sudo supervisorctl update
; 优势:进程崩溃自动重启,比 crontab 更适合常驻进程
; =============================================================================
[program:rxthinkcmf-job]
command = php /www/api/think job:daemon ; 守护进程模式运行任务调度
directory = /www/api ; 工作目录
autostart = true ; Supervisor 启动时自动启动此进程
autorestart = true ; 进程退出后自动重启
user = www-data ; 运行用户
stdout_logfile = /var/log/rxthinkcmf-job.log ; 标准输出日志路径
```
```bash
# 重新读取 Supervisor 配置文件(发现新增的 conf 文件)
sudo supervisorctl reread
# 更新进程状态(应用新配置)
sudo supervisorctl update
# 启动 rxthinkcmf-job 进程
sudo supervisorctl start rxthinkcmf-job
# 查看进程状态
sudo supervisorctl status rxthinkcmf-job
```
## 多数据库部署注意事项
### MySQL
```bash
# 确保 MySQL 服务已启动
sudo systemctl start mysql
# 创建生产数据库
# CHARACTER SET utf8mb4 → 支持 emoji 等四字节字符
# COLLATE utf8mb4_general_ci → 不区分大小写的排序规则
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
# 导入初始化数据(项目自带的 SQL 文件)
mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
```
### PostgreSQL
```bash
# 确保 PostgreSQL 服务已启动
sudo systemctl start postgresql
# 安装 PHP 的 PostgreSQL 扩展(如果尚未安装)
sudo apt install php8.2-pgsql
# 重启 PHP-FPM 使扩展生效
sudo systemctl restart php8.2-fpm
# 创建数据库(-u postgres 以 postgres 用户执行)
sudo -u postgres createdb rxthinkcmf.thinkphp6.elevue
```
### SQLite
```bash
# 创建数据库文件存放目录
mkdir -p /www/api/runtime
# 创建空的 SQLite 数据库文件(touch 创建空文件即可)
touch /www/api/runtime/database.sqlite
# 设置文件权限(664 = owner/group 可读写,others 只读)
chmod 664 /www/api/runtime/database.sqlite
```
```ini
; .env 配置(切换到 SQLite 数据库)
[DATABASE]
TYPE = sqlite ; 数据库类型改为 sqlite
DATABASE = /www/api/runtime/database.sqlite ; SQLite 文件路径
```
### 数据库连接池
高并发场景建议配置持久连接:
```ini
; php.ini — 使用 Unix Socket 连接 MySQL(比 TCP 更快)
; 前提:PHP-FPM 和 MySQL 在同一台服务器上
pdo_mysql.default_socket = /var/run/mysqld/mysqld.sock
```
## 目录权限
```bash
# -----------------------------------------------------------------------------
# 项目目录(owner 可读写执行,group/others 可读和执行)
# -----------------------------------------------------------------------------
chmod -R 755 /www/api/ # 目录 755,文件 644
chown -R www-data:www-data /www/api/ # 设置 owner 为 Web 服务器用户
# -----------------------------------------------------------------------------
# 运行时目录(必须可写 — ThinkPHP 缓存、日志、编译模板)
# -----------------------------------------------------------------------------
chmod -R 777 /www/api/runtime/ # 777 确保 PHP-FPM 进程可写入
chown -R www-data:www-data /www/api/runtime/
# -----------------------------------------------------------------------------
# 上传目录(必须可写 — 用户上传文件存储位置)
# -----------------------------------------------------------------------------
mkdir -p /opt/data/uploads # 创建上传目录(如不存在)
chmod -R 755 /opt/data/uploads # 目录可写
chown -R www-data:www-data /opt/data/uploads # 设置 owner
```
## SSL 证书(HTTPS)
```nginx
# =============================================================================
# HTTPS 配置(生产环境必须启用)
# =============================================================================
# HTTPS 站点配置
server {
listen 443 ssl; # 监听 443 端口,启用 SSL
server_name api.example.com;
# SSL 证书文件路径(替换为实际证书路径)
ssl_certificate /etc/ssl/certs/api.example.com.pem; # 证书文件(含证书链)
ssl_certificate_key /etc/ssl/private/api.example.com.key; # 私钥文件
# ... 其他配置(location、fastcgi 等)同上方 Nginx 配置 ...
}
# HTTP 自动跳转 HTTPS(301 永久重定向)
server {
listen 80; # 监听 80 端口(HTTP)
server_name api.example.com;
return 301 https://$server_name$request_uri; # 301 跳转到 HTTPS
}
```
@@ -0,0 +1,246 @@
# 11.9 备份与恢复
:::danger 生产环境必须备份
数据库和上传文件必须定期备份,建议每天自动备份。备份文件应存储在独立于服务器的安全位置。
:::
## 数据库备份
### 手动备份
```bash
# -----------------------------------------------------------------------------
# MySQL 全量备份(结构 + 数据)
# -----------------------------------------------------------------------------
# -u root → 数据库用户名
# -proot → 数据库密码(生产环境建议用 -p 后不跟密码,交互式输入)
# rxthinkcmf... → 要备份的数据库名
# > backup_*.sql → 输出到文件,$(date +%Y%m%d) 自动生成日期后缀
mysqldump -u root -proot rxthinkcmf.thinkphp6.elevue > backup_$(date +%Y%m%d).sql
# -----------------------------------------------------------------------------
# 压缩备份(推荐 — 可节省 70%+ 磁盘空间)
# -----------------------------------------------------------------------------
# | gzip → 将 mysqldump 输出通过管道传给 gzip 压缩
mysqldump -u root -proot rxthinkcmf.thinkphp6.elevue | gzip > backup_$(date +%Y%m%d).sql.gz
# -----------------------------------------------------------------------------
# 仅备份表结构(不含数据 — 用于对比表结构变更)
# -----------------------------------------------------------------------------
# --no-data → 只导出 CREATE TABLE 语句,不导出数据
mysqldump -u root -proot --no-data rxthinkcmf.thinkphp6.elevue > schema_$(date +%Y%m%d).sql
# -----------------------------------------------------------------------------
# 仅备份数据(不含表结构 — 用于数据迁移)
# -----------------------------------------------------------------------------
# --no-create-info → 只导出 INSERT 语句,不导出 CREATE TABLE
mysqldump -u root -proot --no-create-info rxthinkcmf.thinkphp6.elevue > data_$(date +%Y%m%d).sql
```
### 自动备份脚本
```bash
#!/bin/bash
# =============================================================================
# backup_db.sh — 数据库自动备份脚本
# =============================================================================
# 用途:自动备份 MySQL 数据库,压缩存储,自动清理过期备份
# 用法:crontab 定时调用,或手动执行 bash backup_db.sh
# =============================================================================
# -----------------------------------------------------------------------------
# 配置参数(按实际环境修改)
# -----------------------------------------------------------------------------
BACKUP_DIR="/backup/mysql" # 备份文件存放目录
DB_NAME="rxthinkcmf.thinkphp6.elevue" # 要备份的数据库名
DB_USER="root" # 数据库用户名
DB_PASS="root" # 数据库密码
DATE=$(date +%Y%m%d_%H%M%S) # 当前时间戳(年月日_时分秒)
KEEP_DAYS=30 # 保留最近 N 天的备份
# -----------------------------------------------------------------------------
# 创建备份目录(-p 参数:目录不存在时自动创建)
# -----------------------------------------------------------------------------
mkdir -p $BACKUP_DIR
# -----------------------------------------------------------------------------
# 执行备份(mysqldump 输出通过管道传给 gzip 压缩)
# -----------------------------------------------------------------------------
mysqldump -u $DB_USER -p$DB_PASS $DB_NAME | gzip > $BACKUP_DIR/backup_$DATE.sql.gz
# -----------------------------------------------------------------------------
# 检查备份结果($? 为上一条命令的退出码,0=成功)
# -----------------------------------------------------------------------------
if [ $? -eq 0 ]; then
echo "[$(date)] 备份成功: backup_$DATE.sql.gz"
else
echo "[$(date)] 备份失败!"
exit 1 # 非零退出码表示失败,便于监控系统捕获
fi
# -----------------------------------------------------------------------------
# 清理过期备份
# -----------------------------------------------------------------------------
# find 参数说明:
# -name "backup_*.sql.gz" → 匹配备份文件名格式
# -mtime +$KEEP_DAYS → 修改时间超过 N 天的文件
# -delete → 删除匹配的文件
find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +$KEEP_DAYS -delete
echo "[$(date)] 清理完成,保留最近 $KEEP_DAYS 天的备份"
```
### 配置定时备份
```bash
# 编辑当前用户的 crontab 定时任务
crontab -e
# -----------------------------------------------------------------------------
# 定时备份规则
# -----------------------------------------------------------------------------
# 格式:分 时 日 月 周 命令
# 0 3 * * * → 每天凌晨 3:00 执行
# 0 4 * * 0 → 每周日凌晨 4:00 执行(0=周日)
# >> /var/log/backup.log 2>&1 → 追加日志(2>&1 将错误也写入日志)
# -----------------------------------------------------------------------------
0 3 * * * /www/scripts/backup_db.sh >> /var/log/backup.log 2>&1
0 4 * * 0 /www/scripts/backup_db_full.sh >> /var/log/backup.log 2>&1
```
## 上传文件备份
```bash
# -----------------------------------------------------------------------------
# 备份上传目录为压缩包
# -----------------------------------------------------------------------------
# tar 参数说明:
# -c → 创建新归档
# -z → 用 gzip 压缩
# -f → 指定输出文件名
# $(date +%Y%m%d) → 自动生成日期后缀
tar -czf /backup/uploads_$(date +%Y%m%d).tar.gz /opt/data/uploads/
# -----------------------------------------------------------------------------
# 上传文件自动备份脚本
# -----------------------------------------------------------------------------
#!/bin/bash
BACKUP_DIR="/backup/uploads" # 备份存放目录
DATE=$(date +%Y%m%d) # 日期后缀
mkdir -p $BACKUP_DIR # 创建目录(如不存在)
tar -czf $BACKUP_DIR/uploads_$DATE.tar.gz /opt/data/uploads/ # 压缩打包
find $BACKUP_DIR -name "uploads_*.tar.gz" -mtime +30 -delete # 清理 30 天前的备份
```
## 恢复
### 恢复数据库
```bash
# -----------------------------------------------------------------------------
# 从 SQL 文件恢复(全量覆盖)
# -----------------------------------------------------------------------------
# < backup_*.sql → 将文件内容作为 mysql 命令的输入
mysql -u root -proot rxthinkcmf.thinkphp6.elevue < backup_20260920.sql
# -----------------------------------------------------------------------------
# 从压缩文件恢复(gunzip 解压后传给 mysql)
# -----------------------------------------------------------------------------
# gunzip < file.sql.gz → 解压并输出到标准输出
# | mysql → 通过管道传给 mysql 命令执行
gunzip < backup_20260920.sql.gz | mysql -u root -proot rxthinkcmf.thinkphp6.elevue
# -----------------------------------------------------------------------------
# 恢复到新数据库(安全起见 — 先验证再切换)
# -----------------------------------------------------------------------------
# 1. 创建临时验证数据库
mysql -u root -proot -e "CREATE DATABASE `rxthinkcmf.thinkphp6.elevue_restore`;"
# 2. 恢复备份到临时库
mysql -u root -proot rxthinkcmf.thinkphp6.elevue_restore < backup_20260920.sql
# 3. 验证数据无误后,重命名数据库(需停机操作)
```
### 恢复上传文件
```bash
# -----------------------------------------------------------------------------
# 恢复上传目录到原始位置
# -----------------------------------------------------------------------------
# tar 参数说明:
# -x → 解压归档
# -z → 解压 gzip
# -f → 指定归档文件
# -C → 解压到指定目录(/ 表示按归档内的绝对路径还原)
tar -xzf /backup/uploads_20260920.tar.gz -C /
# -----------------------------------------------------------------------------
# 仅恢复特定子目录(如只恢复文章附件)
# -----------------------------------------------------------------------------
# 在归档文件名后追加要解压的路径前缀
tar -xzf /backup/uploads_20260920.tar.gz -C / www/uploads/article/
```
## 备份验证
:::warning 必须定期验证备份
备份文件必须定期恢复验证,确保备份可用。建议每月进行一次恢复测试。
:::
```bash
#!/bin/bash
# =============================================================================
# verify_backup.sh — 备份验证脚本
# =============================================================================
# 用途:将备份恢复到临时数据库,验证数据完整性后清理
# 用法:bash verify_backup.sh backup_20260920.sql.gz
# =============================================================================
BACKUP_FILE=$1 # 第一个参数:备份文件路径
VERIFY_DB="rxthinkcmf.thinkphp6.elevue_verify" # 临时验证数据库名
# -----------------------------------------------------------------------------
# 创建验证数据库(先删除旧的,再创建新的)
# -----------------------------------------------------------------------------
# DROP DATABASE IF EXISTS → 如果存在则删除(避免同名冲突)
mysql -u root -p -e "DROP DATABASE IF EXISTS $VERIFY_DB; CREATE DATABASE $VERIFY_DB;"
# -----------------------------------------------------------------------------
# 恢复备份到验证数据库
# -----------------------------------------------------------------------------
gunzip < $BACKUP_FILE | mysql -u root -p $VERIFY_DB
# -----------------------------------------------------------------------------
# 验证数据完整性
# -----------------------------------------------------------------------------
# -N → 不输出表头(只输出数据值)
# information_schema.tables → MySQL 系统表,存储所有表的元信息
TABLE_COUNT=$(mysql -u root -p -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$VERIFY_DB';")
echo "恢复的表数量: $TABLE_COUNT"
# 检查用户表数据量(验证核心数据是否完整)
USER_COUNT=$(mysql -u root -p -N -e "SELECT COUNT(*) FROM $VERIFY_DB.think_user;")
echo "用户数量: $USER_COUNT"
# -----------------------------------------------------------------------------
# 清理验证数据库(验证完毕后删除临时库)
# -----------------------------------------------------------------------------
mysql -u root -p -e "DROP DATABASE $VERIFY_DB;"
echo "备份验证完成"
```
## 备份存储建议
| 存储方式 | 优点 | 缺点 |
|----------|------|------|
| 本地磁盘 | 速度快 | 服务器故障可能丢失 |
| NAS / 网络存储 | 独立于服务器 | 需要网络 |
| 云存储(OSS/S3) | 高可靠、异地 | 需要付费 |
| 异地服务器 | 灾难恢复 | 需要维护 |
:::tip 最佳实践
- 本地保留最近 7 天的备份
- 异地保留最近 30 天的备份
- 每月进行一次恢复测试
- 备份文件加密存储(如含敏感数据)
:::
@@ -0,0 +1,214 @@
# 11.1 部署 Checklist
:::tip 本章概要
从服务器准备到应用上线的完整部署操作清单,适用于生产环境首次部署和后续版本更新。
:::
## 首次部署 Checklist
### 第 1 阶段:服务器准备
- [ ] 服务器操作系统:CentOS 7+ / Ubuntu 20.04+ / Debian 11+
- [ ] 防火墙开放端口:80(HTTP)、443(HTTPS)、22(SSH)
- [ ] 创建部署用户(非 root)
- [ ] 配置 SSH 密钥登录
- [ ] 配置时区:`timedatectl set-timezone Asia/Shanghai`
### 第 2 阶段:环境安装
**PHP 环境**:
- [ ] 安装 PHP 8.2+
- [ ] 安装必需扩展:`pdo_mysql`、`mbstring`、`json`、`openssl`、`fileinfo`、`gd`
- [ ] 安装可选扩展:`redis`(缓存驱动)
- [ ] 安装 Composer 2.x
- [ ] 配置 PHP-FPM(如使用 Nginx)
**数据库**:
- [ ] 安装 MySQL 8.0+(或 PostgreSQL / SQL Server / Oracle / SQLite)
- [ ] 创建数据库:`CREATE DATABASE \`rxthinkcmf.thinkphp6.elevue\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`
- [ ] 创建数据库用户并授权
- [ ] 导入 SQL 脚本:`mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql`
**Redis(可选)**:
- [ ] 安装 Redis 6.0+
- [ ] 配置密码
- [ ] 测试连接:`redis-cli ping`
**Node.js(前端构建)**:
- [ ] 安装 Node.js 22+
- [ ] 安装 pnpm:`npm install -g pnpm`
### 第 3 阶段:应用部署
**后端**:
- [ ] 上传项目代码到服务器
- [ ] 安装 Composer 依赖:`composer install --no-dev --optimize-autoloader`
- [ ] 配置 `.env` 文件(数据库、JWT 密钥、文件上传等)
- [ ] 生成 JWT 密钥:`php -r "echo bin2hex(random_bytes(32));"`
- [ ] 设置目录权限:`chmod -R 755 runtime/`、`chmod -R 755 public/uploads/`
- [ ] 测试启动:`php think run`
- [ ] 配置定时任务:`crontab -e` → `* * * * * cd /www/api && php think job:run`
**前端**:
- [ ] 安装前端依赖:`cd ui && pnpm install`
- [ ] 构建前端:`pnpm build`
- [ ] 上传构建产物(`ui/dist/` 目录)到服务器
### 第 4 阶段:Nginx 配置
```nginx
# =============================================================================
# 后端 API 站点配置
# =============================================================================
server {
listen 80; # 监听 80 端口
server_name api.example.com; # 绑定域名
root /www/api/public; # ThinkPHP 入口目录
index index.php; # 默认索引文件
# URL 重写(ThinkPHP 路由必需)
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=$1 last; # 非真实文件请求转发到 index.php
}
}
# PHP-FPM 转发
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000; # PHP-FPM 监听地址
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# 安全:禁止上传目录执行 PHP
location ~ /uploads/.*\.php$ { deny all; }
}
# =============================================================================
# 前端 SPA 站点配置
# =============================================================================
server {
listen 80; # 监听 80 端口
server_name admin.example.com; # 绑定域名
root /www/admin/dist; # 前端构建产物目录
index index.html; # 默认索引文件
# Vue Router History 模式(所有非文件请求回退到 index.html)
location / {
try_files $uri $uri/ /index.html;
}
# API 反向代理到后端
location /api/ {
proxy_pass http://127.0.0.1:8000; # 后端服务地址
proxy_set_header Host $host; # 传递原始 Host
proxy_set_header X-Real-IP $remote_addr; # 传递客户端 IP
}
}
```
### 第 5 阶段:安全加固
- [ ] `.env` 中 `APP_DEBUG = false`
- [ ] `.env` 中 `SHOW_ERROR_MSG = false`
- [ ] `.env` 中 `JWT.SECRET` 已修改为强随机密钥
- [ ] `.env` 中 `CORS.ALLOW_ORIGIN` 已限制为实际域名
- [ ] 启用 HTTPS(SSL 证书)
- [ ] 上传目录禁止执行 PHP
- [ ] 数据库不允许外网访问
- [ ] Redis 已设置密码
### 第 6 阶段:验证
- [ ] 访问前端页面正常
- [ ] 登录功能正常(admin / root)
- [ ] 各模块 CRUD 功能正常
- [ ] 文件上传功能正常
- [ ] 定时任务正常执行
- [ ] 操作日志正常记录
## 版本更新 Checklist
### 更新后端
```bash
# -----------------------------------------------------------------------------
# 第 1 步:备份(更新前必须备份)
# -----------------------------------------------------------------------------
# 备份项目代码(cp -r 递归复制整个目录)
cp -r /www/api /www/api_backup_$(date +%Y%m%d)
# 备份数据库(mysqldump 导出 SQL,$(date +%Y%m%d) 自动生成日期后缀)
mysqldump -u root -proot rxthinkcmf.thinkphp6.elevue > /backup/db_$(date +%Y%m%d).sql
# -----------------------------------------------------------------------------
# 第 2 步:更新代码
# -----------------------------------------------------------------------------
cd /www/api # 进入项目目录
composer install --no-dev --optimize-autoloader # 安装依赖(排除开发依赖,优化自动加载)
# -----------------------------------------------------------------------------
# 第 3 步:更新数据库(如有迁移脚本)
# -----------------------------------------------------------------------------
mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/migration.sql
# -----------------------------------------------------------------------------
# 第 4 步:清除缓存
# -----------------------------------------------------------------------------
php think clear # 清除 ThinkPHP 运行时缓存(模板、路由、配置等)
# -----------------------------------------------------------------------------
# 第 5 步:重启 PHP-FPM(使 OPcache 失效,加载新代码)
# -----------------------------------------------------------------------------
sudo systemctl restart php8.2-fpm
```
### 更新前端
```bash
# -----------------------------------------------------------------------------
# 第 1 步:构建前端
# -----------------------------------------------------------------------------
cd ui && pnpm install && pnpm build # 安装依赖并执行生产构建
# -----------------------------------------------------------------------------
# 第 2 步:部署构建产物
# -----------------------------------------------------------------------------
cp -r dist/* /www/admin/dist/ # 将构建产物复制到 Nginx 站点目录
# -----------------------------------------------------------------------------
# 第 3 步:重载 Nginx(不断开现有连接)
# -----------------------------------------------------------------------------
sudo nginx -s reload
```
## 回滚方案
```bash
# -----------------------------------------------------------------------------
# 后端回滚(恢复代码 + 数据库)
# -----------------------------------------------------------------------------
# 恢复项目代码
cp -r /www/api_backup_20260920/* /www/api/
# 恢复数据库
mysql -u root -proot rxthinkcmf.thinkphp6.elevue < /backup/db_20260920.sql
# 重启 PHP-FPM(清除 OPcache,加载旧代码)
sudo systemctl restart php8.2-fpm
# -----------------------------------------------------------------------------
# 前端回滚(恢复构建产物)
# -----------------------------------------------------------------------------
cp -r /www/admin_backup_20260920/dist/* /www/admin/dist/
# 重载 Nginx(应用旧的前端文件)
sudo nginx -s reload
```
@@ -0,0 +1,354 @@
# 11.11 CI/CD 流水线
:::tip 概述
本节介绍项目的持续集成/持续部署(CI/CD)方案,包括 GitHub Actions 和 GitLab CI 的配置示例,以及自动化测试、构建和部署流程。
:::
## 流水线架构
```text
代码提交 → 代码检查 → 单元测试 → 构建打包 → 部署上线
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
Git Push ESLint PHPUnit Docker 生产环境
PHPStan Vitest Image
```
## GitHub Actions
### 后端 CI 配置
```yaml
# =============================================================================
# .github/workflows/backend-ci.yml — 后端持续集成
# =============================================================================
# 触发条件:push 到 main/develop 分支,或向 main 发起 PR
# 执行环境:Ubuntu 最新版 + MySQL 8.0 服务容器
# 流程:代码检查 → 静态分析 → 单元测试 → 覆盖率上传
# =============================================================================
name: Backend CI
on:
push:
branches: [main, develop] # push 到这些分支时触发
pull_request:
branches: [main] # 向 main 发起 PR 时触发
jobs:
test:
runs-on: ubuntu-latest # 使用 GitHub 托管的最新 Ubuntu 运行器
# -------------------------------------------------------------------------
# 服务容器(GitHub Actions 自动启动,与运行器共享网络)
# -------------------------------------------------------------------------
services:
mysql:
image: mysql:8.0 # MySQL 8.0 官方镜像
env:
MYSQL_ROOT_PASSWORD: root # 测试环境密码(仅用于 CI)
MYSQL_DATABASE: test_db # 自动创建的测试数据库
ports:
- 3306:3306 # 暴露端口供测试连接
# 健康检查:确保 MySQL 就绪后再执行后续步骤
options: >-
--health-cmd="mysqladmin ping" # 用 mysqladmin ping 检测
--health-interval=10s # 每 10 秒检查一次
--health-timeout=5s # 超时时间
--health-retries=3 # 重试 3 次后判定失败
steps:
# 拉取代码(checkout@v4 是 GitHub 官方 Action)
- uses: actions/checkout@v4
# 安装 PHP 8.2 及所需扩展
- name: Setup PHP
uses: shivammathur/setup-php@v2 # 第三方 Action,自动安装 PHP
with:
php-version: '8.2' # PHP 版本
extensions: pdo_mysql, gd, redis # 安装的扩展
coverage: xdebug # 代码覆盖率工具
# 安装 Composer 依赖(--no-progress 隐藏进度条,--prefer-dist 下载压缩包更快)
- name: Install Dependencies
run: composer install --no-progress --prefer-dist
# 准备运行环境
- name: Prepare Environment
run: |
cp .env.example .env # 复制环境配置模板
php think db:migrate # 执行数据库迁移
# 代码风格检查(PSR-12 标准)
- name: Code Style Check
run: vendor/bin/phpcs --standard=PSR12 app/
# 静态分析(level=5 为严格模式,可发现潜在 bug)
- name: Static Analysis
run: vendor/bin/phpstan analyse app/ --level=5
# 运行单元测试(--coverage-text 输出文本格式覆盖率报告)
- name: Run Tests
run: php think test --coverage-text
# 上传覆盖率报告到 Codecov(免费的覆盖率追踪平台)
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml # 覆盖率报告文件
```
### 前端 CI 配置
```yaml
# =============================================================================
# .github/workflows/frontend-ci.yml — 前端持续集成
# =============================================================================
# 触发条件:仅当 ui/ 目录下的文件变更时触发(避免后端改动触发前端 CI)
# 流程:Lint → 类型检查 → 单元测试 → 构建
# =============================================================================
name: Frontend CI
on:
push:
branches: [main, develop]
paths:
- 'ui/**' # 仅 ui/ 目录变更时触发
pull_request:
branches: [main]
paths:
- 'ui/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 安装 Node.js 18 并配置 pnpm 缓存
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18' # Node.js 版本
cache: 'pnpm' # 启用 pnpm 缓存(加速依赖安装)
cache-dependency-path: ui/pnpm-lock.yaml # 缓存依赖的 lockfile 路径
# 全局安装 pnpm 包管理器
- name: Install pnpm
run: npm install -g pnpm
# 安装前端依赖(--frozen-lockfile 确保版本与 lockfile 一致)
- name: Install Dependencies
working-directory: ui # 在 ui/ 目录下执行
run: pnpm install --frozen-lockfile
# ESLint 代码风格检查
- name: Lint
working-directory: ui
run: pnpm lint:eslint
# TypeScript 类型检查(发现类型错误)
- name: Type Check
working-directory: ui
run: pnpm type:check
# 运行单元测试(Vitest)
- name: Run Tests
working-directory: ui
run: pnpm test
# 生产环境构建(验证构建是否成功)
- name: Build
working-directory: ui
run: pnpm build
```
### 自动部署配置
```yaml
# =============================================================================
# .github/workflows/deploy.yml — 自动部署
# =============================================================================
# 触发条件:push 到 main 分支,或打 v* 标签
# 流程:构建 Docker 镜像 → 推送到镜像仓库 → SSH 部署到服务器
# =============================================================================
name: Deploy
on:
push:
branches: [main]
tags:
- 'v*' # 匹配 v1.0.0、v2.1.3 等标签
jobs:
deploy:
runs-on: ubuntu-latest
needs: [test] # 依赖 test job 通过后才执行
if: github.ref == 'refs/heads/main' # 仅 main 分支触发部署
steps:
- uses: actions/checkout@v4
# 构建 Docker 镜像(用 git commit SHA 作为版本标签)
- name: Build Docker Image
run: |
docker build -t myapp:${{ github.sha }} . # 用 commit SHA 标记
docker tag myapp:${{ github.sha }} myapp:latest # 同时打 latest 标签
# 推送镜像到 Docker Hub(secrets 在 GitHub 仓库设置中配置)
- name: Push to Registry
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push myapp:${{ github.sha }} # 推送版本标签
docker push myapp:latest # 推送 latest 标签
# 通过 SSH 连接服务器执行部署命令
- name: Deploy to Server
uses: appleboy/ssh-action@v1 # 第三方 SSH Action
with:
host: ${{ secrets.SERVER_HOST }} # 服务器地址
username: ${{ secrets.SERVER_USER }} # SSH 用户名
key: ${{ secrets.SSH_KEY }} # SSH 私钥
script: |
cd /opt/myapp # 进入项目目录
docker-compose pull # 拉取最新镜像
docker-compose up -d # 重建并启动容器
php think db:migrate # 执行数据库迁移
```
## GitLab CI
```yaml
# =============================================================================
# .gitlab-ci.yml — GitLab CI/CD 配置
# =============================================================================
# 阶段:test → build → deploy
# 缓存:vendor/ 和 node_modules/ 按分支缓存,加速后续构建
# =============================================================================
stages:
- test # 测试阶段
- build # 构建阶段
- deploy # 部署阶段
# 全局变量(所有 job 共享)
variables:
MYSQL_ROOT_PASSWORD: root # 测试数据库密码
MYSQL_DATABASE: test_db # 测试数据库名
# -----------------------------------------------------------------------------
# 后端测试
# -----------------------------------------------------------------------------
backend:test:
stage: test
image: php:8.2-cli # PHP 8.2 CLI 镜像
services:
- mysql:8.0 # MySQL 服务容器
script:
# 安装系统依赖(PHP 扩展编译需要)
- apt-get update && apt-get install -y libpq-dev libzip-dev
# 安装 PHP 扩展
- docker-php-ext-install pdo_mysql zip
# 安装 Composer 依赖
- composer install --no-progress
# 准备环境并执行测试
- cp .env.example .env
- php think db:migrate # 数据库迁移
- php think test # 运行测试
cache:
key: ${CI_COMMIT_REF_SLUG} # 按分支名缓存
paths:
- vendor/ # 缓存 Composer 依赖
# -----------------------------------------------------------------------------
# 前端测试
# -----------------------------------------------------------------------------
frontend:test:
stage: test
image: node:18 # Node.js 18 镜像
script:
- cd ui
- npm install -g pnpm # 全局安装 pnpm
- pnpm install --frozen-lockfile # 安装依赖
- pnpm lint:eslint # 代码风格检查
- pnpm test # 单元测试
- pnpm build # 生产构建
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- ui/node_modules/ # 缓存前端依赖
# -----------------------------------------------------------------------------
# 生产部署(仅 main 分支)
# -----------------------------------------------------------------------------
deploy:production:
stage: deploy
only:
- main # 仅 main 分支触发
script:
- docker build -t myapp:$CI_COMMIT_SHA . # 构建镜像
- docker push myapp:$CI_COMMIT_SHA # 推送到仓库
# SSH 到服务器执行部署
- ssh $SERVER_USER@$SERVER_HOST "cd /opt/myapp && docker-compose pull && docker-compose up -d"
environment:
name: production
url: https://api.example.com
```
## 部署策略
### 蓝绿部署
```text
当前版本(蓝) → 新版本(绿)
│ │
▼ ▼
Nginx 切换流量 ← 验证通过
```
### 滚动更新
```bash
# Docker Swarm 滚动更新
# --update-parallelism 1 → 每次只更新 1 个容器实例
# --update-delay 30s → 每批更新间隔 30 秒(观察健康状态)
# --image myapp:v2.0 → 新版本镜像
# myapp_web → 服务名称
docker service update \
--update-parallelism 1 \
--update-delay 30s \
--image myapp:v2.0 \
myapp_web
```
## 通知集成
### 部署通知(钉钉/飞书)
```yaml
# GitHub Actions 部署成功通知(发送到钉钉机器人)
- name: Notify on Success
if: success() # 仅在部署成功时执行
run: |
# curl 发送 POST 请求到钉钉 Webhook
# -H → 设置请求头为 JSON 格式
# -d → 发送 JSON 消息体(Markdown 格式)
curl -X POST "${{ secrets.DINGTALK_WEBHOOK }}" \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title": "部署成功",
"text": "## ✅ 部署成功\n- **项目**: myapp\n- **分支**: ${{ github.ref_name }}\n- **提交**: ${{ github.sha }}\n- **时间**: $(date)"
}
}'
```
## 相关文档
- [Docker 生产部署](/zh/deploy/docker.html)
- [后端部署](/zh/deploy/backend.html)
- [前端部署](/zh/deploy/frontend.html)
@@ -0,0 +1,86 @@
# 11.3 数据库迁移
:::tip CLI 迁移工具
除了 SQL 脚本导入,项目还提供了 `php think db:migrate` 命令行工具,支持跨数据库自动迁移(MySQL → PostgreSQL 等),自动迁移索引、外键、字段注释、表注释、默认值和排序规则(collation),数据迁移基于主键稳定排序并支持事务回滚,详见 [7.6 数据库迁移工具](/zh/tools/db-migrate.html)。
:::
## SQL 脚本目录
```text
document/
├── mysql/rxthinkcmf.thinkphp6.elevue.sql # MySQL 版本(约 78 万行)
├── postgresql/rxthinkcmf.thinkphp6.elevue.sql # PostgreSQL 版本
├── sqlserver/rxthinkcmf.thinkphp6.elevue.sql # SQL Server 版本
├── oracle/rxthinkcmf.thinkphp6.elevue.sql # Oracle 版本
└── sqlite/rxthinkcmf.thinkphp6.elevue.sql # SQLite 版本
```
各数据库脚本已针对对应的 SQL 语法做了适配,可直接导入。
## 各数据库导入命令
### MySQL
```bash
# -----------------------------------------------------------------------------
# MySQL 导入(mysql 命行工具)
# -----------------------------------------------------------------------------
# -u root -proot → 用户名和密码
# rxthinkcmf... → 目标数据库名
# < file.sql → 将 SQL 文件作为输入执行
mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
```
### PostgreSQL
```bash
# -----------------------------------------------------------------------------
# PostgreSQL 导入(psql 命令行工具)
# -----------------------------------------------------------------------------
# -U postgres → 连接用户
# -d ... → 目标数据库
# -f file.sql → 执行 SQL 文件
psql -U postgres -d rxthinkcmf.thinkphp6.elevue -f document/postgresql/rxthinkcmf.thinkphp6.elevue.sql
```
### SQL Server
```bash
# -----------------------------------------------------------------------------
# SQL Server 导入(sqlcmd 命令行工具)
# -----------------------------------------------------------------------------
# -S localhost → 服务器地址
# -U sa → 用户名(sa 为管理员)
# -P root → 密码
# -d ... → 目标数据库
# -i file.sql → 执行 SQL 文件
sqlcmd -S localhost -U sa -P root -d rxthinkcmf.thinkphp6.elevue -i document/sqlserver/rxthinkcmf.thinkphp6.elevue.sql
```
### Oracle
```bash
# -----------------------------------------------------------------------------
# Oracle 导入(sqlplus 命令行工具)
# -----------------------------------------------------------------------------
# root/root@localhost:1521/XE → 用户/密码@主机:端口/服务名
# @file.sql → 执行 SQL 文件
sqlplus root/root@localhost:1521/XE @document/oracle/rxthinkcmf.thinkphp6.elevue.sql
```
### SQLite
```bash
# -----------------------------------------------------------------------------
# SQLite 导入(sqlite3 命令行工具)
# -----------------------------------------------------------------------------
# 数据库文件路径 → SQLite 数据库存储在单个文件中
# < file.sql → 将 SQL 文件作为输入执行
sqlite3 /www/api/runtime/database.sqlite < document/sqlite/rxthinkcmf.thinkphp6.elevue.sql
```
## 注意事项
- 每种数据库的主脚本已做语法适配,无需手动转换
- 导入前请先创建数据库并配置好用户权限
- 详见 [2.4 数据库初始化](/zh/quickstart/database-init.html)
@@ -0,0 +1,568 @@
# 11.6 Docker 生产部署
:::tip 概述
本节提供基于 Docker Compose 的生产环境部署方案,包含 **Nginx 反向代理**、**PHP-FPM 应用**、**MySQL 数据库**和 **Redis 缓存**四个服务的容器编排配置。所有脚本均带详细注释,适合快速搭建生产环境,也便于 CI/CD 集成。
:::
## 架构说明
```
浏览器 → 宿主机:8000 → [Nginx 容器:80] → [PHP-FPM 容器:9000] → ThinkPHP 应用
↕
[MySQL 容器:3306] + [Redis 容器:6379]
```
- **Nginx**:监听 80 端口,处理 HTTP 请求、URL 重写、静态资源缓存
- **PHP-FPM**:监听 9000 端口,执行 PHP 代码
- **MySQL**:数据库服务,数据持久化到 Docker 命名卷
- **Redis**:缓存服务,数据持久化到 Docker 命名卷
## 前置要求
| 依赖 | 版本要求 | 说明 |
|------|----------|------|
| Docker | >= 20.10 | 容器运行时 |
| Docker Compose | >= 2.0 | 容器编排工具 |
| 磁盘空间 | >= 5GB | 镜像 + 数据卷 |
## 文件结构
```
thinkphp6/
├── docker/
│ ├── Dockerfile # PHP-FPM 应用镜像定义
│ ├── nginx.conf # Nginx 站点配置
│ └── .env.docker # Docker 环境变量(密码等敏感信息)
├── docker-compose.yml # 容器编排配置(放在项目根目录)
├── .env # 应用配置(ThinkPHP 读取)
└── ...
```
## Dockerfile
```dockerfile
# =============================================================================
# Dockerfile — PHP-FPM 应用镜像
# =============================================================================
# 基础镜像:PHP 8.2 + FPM(FastCGI 进程管理器)
# 构建命令:docker-compose build app
# 运行方式:由 Nginx 容器通过 FastCGI 协议调用本容器的 9000 端口
# =============================================================================
FROM php:8.2-fpm
# -----------------------------------------------------------------------------
# 1. 安装系统依赖
# -----------------------------------------------------------------------------
# PHP 扩展编译需要的 C 库和工具,安装后清理 apt 缓存减小镜像体积
RUN apt-get update && apt-get install -y --no-install-recommends \
libpng-dev \ # GD 扩展 — PNG 支持
libjpeg-dev \ # GD 扩展 — JPEG 支持
libfreetype6-dev \ # GD 扩展 — 字体渲染(验证码依赖)
libzip-dev \ # zip 扩展依赖
zip \ # Composer 安装依赖包时可能用到
unzip \ # Composer 解压依赖包
&& rm -rf /var/lib/apt/lists/* # 清理 apt 缓存,减小镜像体积
# -----------------------------------------------------------------------------
# 2. 安装 PHP 扩展
# -----------------------------------------------------------------------------
# 项目必需扩展(根据 composer.json 和 config/ 推导):
# pdo_mysql — MySQL 数据库驱动(config/database.php 使用)
# mbstring — 多字节字符串处理(ThinkPHP 框架依赖)
# gd — 图片处理(验证码生成、图片缩放)
# bcmath — 高精度数学运算(phpspreadsheet 依赖)
# zip — ZIP 压缩(phpspreadsheet 读写 xlsx 依赖)
# redis — Redis 缓存驱动(config/cache.php 使用)
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
pdo_mysql \
mbstring \
gd \
bcmath \
zip \
&& pecl install redis \
&& docker-php-ext-enable redis
# -----------------------------------------------------------------------------
# 3. 安装 Composer
# -----------------------------------------------------------------------------
# 从官方镜像复制 composer 二进制文件,避免重复安装
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# -----------------------------------------------------------------------------
# 4. 配置 PHP 生产环境参数
# -----------------------------------------------------------------------------
# 使用 php.ini-production 作为基础,追加项目所需配置
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# 生产环境追加配置:内存、超时、上传、时区、OPcache 等
RUN echo '\n\
; ====== 生产环境追加配置(Docker 自动生成) ====== \n\
\n\
; 关闭错误显示,防止敏感信息泄露到前端\ndisplay_errors = Off\n\
error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED\n\
\n\
; 内存与超时\n\
memory_limit = 256M\n\
max_execution_time = 60\n\
max_input_time = 60\n\
\n\
; 文件上传(与 .env 中 FILE.MAX_SIZE 配合)\n\
upload_max_filesize = 50M\n\
post_max_size = 50M\n\
max_file_uploads = 20\n\
\n\
; 时区(与 .env 中 APP.DEFAULT_TIMEZONE 一致)\n\
date.timezone = Asia/Shanghai\n\
\n\
; OPcache 加速(生产环境必须开启)\n\
opcache.enable = 1\n\
opcache.memory_consumption = 256\n\
opcache.interned_strings_buffer = 16\n\
opcache.max_accelerated_files = 10000\n\
opcache.validate_timestamps = 0\n\
opcache.save_comments = 1\n\
' >> "$PHP_INI_DIR/php.ini"
# -----------------------------------------------------------------------------
# 5. 设置工作目录
# -----------------------------------------------------------------------------
WORKDIR /var/www/html
# -----------------------------------------------------------------------------
# 6. 复制项目文件并安装依赖
# -----------------------------------------------------------------------------
# 先复制 composer 文件,利用 Docker 层缓存 ——
# 只要 composer.json / composer.lock 没变就不重新安装依赖
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-interaction
# 再复制全部项目文件(会覆盖上面的 composer 文件,但内容一致无影响)
COPY . .
# -----------------------------------------------------------------------------
# 7. 设置目录权限
# -----------------------------------------------------------------------------
# runtime — ThinkPHP 运行时缓存、日志(必须可写)
# uploads — 用户上传文件目录(必须可写)
RUN mkdir -p runtime uploads \
&& chown -R www-data:www-data runtime uploads \
&& chmod -R 775 runtime uploads
# -----------------------------------------------------------------------------
# 8. 暴露端口并启动
# -----------------------------------------------------------------------------
# 9000 是 PHP-FPM 的 FastCGI 监听端口,Nginx 通过此端口转发请求
EXPOSE 9000
CMD ["php-fpm"]
```
## Nginx 配置
```nginx
# =============================================================================
# Nginx 配置 — ThinkPHP 6 反向代理
# =============================================================================
# 用途:Docker 容器内 Nginx 服务,将 HTTP 请求转发给 PHP-FPM 处理
# 挂载方式:docker-compose 中 volumes 映射到 /etc/nginx/conf.d/default.conf
# =============================================================================
server {
# 监听容器内 80 端口(宿主机通过 docker-compose ports 映射到 8000)
listen 80;
server_name localhost;
# 网站根目录指向 ThinkPHP 入口目录
# 容器内代码挂载在 /var/www/html,入口文件在 public/index.php
root /var/www/html/public;
index index.php;
# -----------------------------------------------------------------
# URL 重写(ThinkPHP 必需)
# -----------------------------------------------------------------
# 将所有非真实文件请求转发到 index.php,由 ThinkPHP 路由解析
# 例:/api/user/page → /index.php?s=/api/user/page
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=$1 last;
}
}
# -----------------------------------------------------------------
# PHP-FPM 处理
# -----------------------------------------------------------------
# 所有 .php 请求通过 FastCGI 协议转发给 PHP-FPM 容器
# "app" 是 docker-compose 中 PHP-FPM 服务的名称,Docker 内部 DNS 自动解析
location ~ \.php$ {
fastcgi_pass app:9000; # 转发到 PHP-FPM 容器的 9000 端口
fastcgi_index index.php; # 默认索引文件
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params; # 引入标准 FastCGI 参数
# 超时设置(秒),防止长请求被 Nginx 提前断开
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
}
# -----------------------------------------------------------------
# 安全规则
# -----------------------------------------------------------------
# 禁止访问隐藏文件(如 .env、.git、.htaccess)
location ~ /\. {
deny all;
}
# 禁止上传目录执行 PHP(防止恶意文件上传后被执行)
location ~ /uploads/.*\.php$ {
deny all;
}
# -----------------------------------------------------------------
# 静态资源缓存
# -----------------------------------------------------------------
# 常见静态文件类型设置 30 天浏览器缓存,减少重复请求
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# -----------------------------------------------------------------
# Gzip 压缩
# -----------------------------------------------------------------
# 对文本类响应启用压缩,减少传输体积
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1024; # 小于 1KB 的响应不压缩(压缩收益低)
gzip_comp_level 5; # 压缩级别 1-9,5 为性能与压缩率的平衡点
}
```
## docker-compose.yml
:::tip 与实际文件的关系
项目根目录已内置 `docker-compose.yml`,支持两种模式:
- **快速体验**:`docker-compose up -d`(使用默认密码 root123 / redis123)
- **生产部署**:`docker-compose --env-file docker/.env.docker up -d --build`(使用生产密码)
下方为生产推荐配置参考,实际文件通过 `${VAR:-默认值}` 语法兼容两种场景。
:::
```yaml
# =============================================================================
# Docker Compose 生产部署配置
# =============================================================================
# 服务架构:Nginx → PHP-FPM → MySQL + Redis
# 启动命令:docker-compose --env-file docker/.env.docker up -d
# 停止命令:docker-compose down
# =============================================================================
version: '3.8'
services:
# ===========================================================================
# Nginx 反向代理服务
# ===========================================================================
# 职责:接收外部 HTTP 请求,转发 .php 到 PHP-FPM,直接返回静态资源
# 端口:容器内 80 → 宿主机 8000
# ===========================================================================
nginx:
image: nginx:1.25-alpine # 官方 Nginx 镜像(Alpine 版本,体积小)
container_name: rxthinkcmf-nginx # 容器名称,便于 docker logs / docker exec
ports:
- "8000:80" # 宿主机 8000 → 容器 80
volumes:
# 挂载 Nginx 站点配置(覆盖默认配置)
- ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro
# 挂载项目代码(与 PHP-FPM 容器共享同一份代码)
- ./:/var/www/html:ro # :ro 只读,Nginx 不需要写入代码
depends_on:
- app # 确保 PHP-FPM 先启动
networks:
- rxthinkcmf-net # 加入内部网络
restart: unless-stopped # 异常退出自动重启
# ===========================================================================
# PHP-FPM 应用服务
# ===========================================================================
# 职责:执行 ThinkPHP PHP 代码,处理业务逻辑
# 端口:9000(FastCGI 协议,仅 Nginx 内部访问)
# ===========================================================================
app:
build:
context: . # 构建上下文为项目根目录
dockerfile: docker/Dockerfile # Dockerfile 路径(相对于 context)
container_name: rxthinkcmf-app
volumes:
# 挂载项目代码(开发/更新时无需重建镜像)
- ./:/var/www/html
# 上传文件独立持久化卷(容器重建不丢失用户上传)
- uploads_data:/opt/data/uploads
environment:
- APP_DEBUG=${APP_DEBUG:-false} # 调试模式,默认关闭
depends_on:
- mysql # 确保数据库先启动
- redis # 确保缓存先启动
networks:
- rxthinkcmf-net
restart: unless-stopped
# ===========================================================================
# MySQL 数据库服务
# ===========================================================================
# 职责:存储业务数据(用户、菜单、字典、文章等)
# 端口:3306(可选暴露,用于外部工具连接调试)
# ===========================================================================
mysql:
image: mysql:8.0 # MySQL 8.0 官方镜像
container_name: rxthinkcmf-mysql
# ports: # 生产环境建议注释掉,不暴露到宿主机
# - "3306:3306"
environment:
# root 用户密码(必须 8 位以上,包含大小写字母和数字)
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
# 启动时自动创建的数据库
MYSQL_DATABASE: ${MYSQL_DATABASE:-rxthinkcmf.thinkphp6.elevue}
# 字符集(支持 emoji 等四字节字符)
MYSQL_CHARSET: utf8mb4
# 排序规则
MYSQL_COLLATION: utf8mb4_general_ci
volumes:
# 数据持久化(容器重建不丢数据)
- mysql_data:/var/lib/mysql
# 初始化 SQL(首次启动时自动执行,仅在数据卷为空时生效)
- ./document/mysql/rxthinkcmf.thinkphp6.elevue.sql:/docker-entrypoint-initdb.d/init.sql:ro
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_general_ci
- --default-authentication-plugin=mysql_native_password
networks:
- rxthinkcmf-net
restart: unless-stopped
# ===========================================================================
# Redis 缓存服务
# ===========================================================================
# 职责:会话缓存、数据缓存(config/cache.php 配置的驱动)
# 端口:6379(可选暴露,用于外部工具连接调试)
# ===========================================================================
redis:
image: redis:7-alpine # Redis 7 Alpine 版本(体积小)
container_name: rxthinkcmf-redis
# ports: # 生产环境建议注释掉,不暴露到宿主机
# - "6379:6379"
command: redis-server --requirepass ${REDIS_PASSWORD} # 设置访问密码
volumes:
# 数据持久化(RDB 快照文件)
- redis_data:/data
networks:
- rxthinkcmf-net
restart: unless-stopped
# =============================================================================
# 命名卷(Named Volumes)
# =============================================================================
# Docker 管理的持久化存储,容器删除后数据保留
# 删除所有数据:docker-compose down -v(-v 参数同时删除命名卷)
# =============================================================================
volumes:
mysql_data: # MySQL 数据文件
redis_data: # Redis RDB 快照文件
uploads_data: # 用户上传文件
# =============================================================================
# 内部网络
# =============================================================================
# 所有服务通过此网络互通,容器间使用服务名(如 "mysql")作为主机名
# 外部无法直接访问此网络,必须通过 Nginx 的 ports 映射进入
# =============================================================================
networks:
rxthinkcmf-net:
driver: bridge
```
## 环境变量配置
创建 `docker/.env.docker` 文件:
```ini
# Docker 环境变量(密码等敏感信息,切勿提交 Git)
MYSQL_ROOT_PASSWORD=Your_Strong_Password_2024
MYSQL_DATABASE=rxthinkcmf.thinkphp6.elevue
REDIS_PASSWORD=Your_Redis_Password_2024
APP_DEBUG=false
```
:::warning 安全提示
- `docker/.env.docker` 包含生产密码,**必须加入 `.gitignore`**
- 密码至少 16 位,包含大小写字母和数字
- JWT 密钥在应用 `.env` 文件中配置,不在此文件中
:::
## 应用 .env 配置
容器启动前,确保项目根目录 `.env` 文件中以下配置正确:
```ini
APP_DEBUG = false
[APP]
SHOW_ERROR_MSG = false
[DATABASE]
; 连接地址(Docker 内部使用服务名 "mysql")
HOSTNAME = mysql
; 密码(与 .env.docker 中 MYSQL_ROOT_PASSWORD 一致)
PASSWORD = Your_Strong_Password_2024
[CACHE]
; 连接地址(Docker 内部使用服务名 "redis")
REDIS_HOST = redis
; 密码(与 .env.docker 中 REDIS_PASSWORD 一致)
REDIS_PASSWORD = Your_Redis_Password_2024
[FILE]
; 上传目录(容器内路径,与 docker-compose uploads_data 卷对应)
UPLOAD_DIR = /opt/data/uploads
```
## 启动部署
```bash
# 1. 配置环境变量
# 复制模板并修改密码
cp docker/.env.docker docker/.env.docker.local
vi docker/.env.docker.local # 修改密码
# 2. 配置应用 .env
cp .env.example .env
vi .env # 修改数据库、Redis 连接信息
# 3. 构建并启动所有服务
docker-compose --env-file docker/.env.docker.local up -d --build
# 4. 查看运行状态
docker-compose ps
# 5. 查看应用日志(确认无报错)
docker-compose logs -f app
```
## 常用命令
```bash
# --------------------------------------------------------------------------
# 生命周期管理
# --------------------------------------------------------------------------
# 启动所有服务(后台运行)
docker-compose up -d
# 停止所有服务(保留数据卷)
docker-compose down
# 停止并删除所有数据卷(⚠️ 清空数据库和上传文件)
docker-compose down -v
# 重建镜像并启动(Dockerfile 或 composer.json 变更后)
docker-compose up -d --build
# 重启单个服务
docker-compose restart app
# --------------------------------------------------------------------------
# 日志排查
# --------------------------------------------------------------------------
# 查看所有服务日志
docker-compose logs -f
# 查看单个服务日志
docker-compose logs -f app
docker-compose logs -f nginx
docker-compose logs -f mysql
# --------------------------------------------------------------------------
# 进入容器调试
# --------------------------------------------------------------------------
# 进入 PHP-FPM 容器
docker-compose exec app bash
# 进入 MySQL 容器
docker-compose exec mysql bash
# 在容器内执行 SQL
docker-compose exec mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.thinkphp6.elevue
# --------------------------------------------------------------------------
# 数据库操作
# --------------------------------------------------------------------------
# 导入 SQL 文件
docker-compose exec -T mysql mysql -uroot -pYour_Strong_Password_2024 rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
# 导出数据库备份
docker-compose exec mysql mysqldump -uroot -pYour_Strong_Password_2024 rxthinkcmf.thinkphp6.elevue > backup_$(date +%Y%m%d).sql
# --------------------------------------------------------------------------
# ThinkPHP 命令
# --------------------------------------------------------------------------
# 数据库迁移
docker-compose exec app php think db:migrate
# 清除运行时缓存
docker-compose exec app php think clear
# 查看路由列表
docker-compose exec app php think route:list
```
## 生产环境建议
| 配置项 | 建议值 | 说明 |
|--------|--------|------|
| APP_DEBUG | false | 关闭调试模式,防止 SQL/路径泄露 |
| MYSQL_ROOT_PASSWORD | 强密码(>=16位) | 包含大小写字母和数字 |
| REDIS_PASSWORD | 强密码 | 与数据库密码不同 |
| JWT.SECRET | 随机字符串(>=32字节) | 在应用 .env 中配置 |
| FILE.UPLOAD_DIR | /opt/data/uploads | 使用命名卷持久化 |
| 数据卷 | 持久化挂载 | 避免容器重启丢失数据 |
| Nginx SSL | 443 + 证书 | 生产环境必须启用 HTTPS |
## SSL 配置(HTTPS)
生产环境建议在 Nginx 前增加 SSL 终止,修改 `nginx.conf`:
```nginx
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
# ... 其余配置不变 ...
}
# HTTP 跳转 HTTPS
server {
listen 80;
server_name api.example.com;
return 301 https://$server_name$request_uri;
}
```
docker-compose.yml 中增加证书挂载:
```yaml
nginx:
volumes:
- ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./ssl/cert.pem:/etc/nginx/ssl/cert.pem:ro
- ./ssl/key.pem:/etc/nginx/ssl/key.pem:ro
```
## 相关文档
- [后端部署](/zh/deploy/backend.html) — Nginx/Apache 详细配置
- [前端部署](/zh/deploy/frontend.html) — Vue3 前端部署
- [数据库迁移](/zh/deploy/db-migrate.html) — 多数据库迁移工具
- [环境变量参考](/zh/appendix/config-reference.html) — 完整配置项说明
@@ -0,0 +1,79 @@
# 11.2 环境规划
:::tip 概述
本节提供不同规模项目的服务器配置建议和环境规划方案。
:::
## 推荐配置
| 组件 | 版本 | 说明 |
|------|------|------|
| PHP | 8.2+ | 必需 |
| MySQL | 8.0+ | 主数据库(也支持 PostgreSQL、SQL Server、Oracle、SQLite) |
| Redis | 6.0+ | 缓存(可选,推荐) |
| Nginx | 1.18+ | Web 服务器 |
| Node.js | 22+ | 前端构建 |
| Composer | 2.x | PHP 依赖 |
| pnpm | 12+ | 前端依赖 |
## 服务器配置建议
| 规模 | CPU | 内存 | 磁盘 | 说明 |
|------|-----|------|------|------|
| 小型 | 2核 | 4GB | 50GB | 单机部署,日活 < 1000 |
| 中型 | 4核 | 8GB | 100GB | 单机部署,日活 1000~10000 |
| 大型 | 8核+ | 16GB+ | 200GB+ | 分离部署,日活 > 10000 |
## 部署架构
### 小型项目(单机)
```text
┌─────────────────────────────┐
│ 单台服务器 │
│ │
│ Nginx + PHP-FPM + MySQL │
│ + Redis(可选) │
│ + 前端静态文件 │
└─────────────────────────────┘
```
### 中型项目(单机优化)
```text
┌─────────────────────────────┐
│ 单台服务器 │
│ │
│ Nginx(前端 + API 代理) │
│ PHP-FPM(多进程) │
│ MySQL(主库) │
│ Redis(缓存) │
│ Supervisor(进程管理) │
└─────────────────────────────┘
```
### 大型项目(分离部署)
```text
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Nginx │ │ Nginx │ │ Nginx │
│ 前端CDN │ │ API-1 │ │ API-2 │
└──────────┘ └──────────┘ └──────────┘
│ │
└──────┬───────┘
│
┌──────┴──────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ MySQL │ │ Redis │
│ 主从 │ │ 集群 │
└──────────┘ └──────────┘
```
## 环境变量规划
| 环境 | APP_DEBUG | CACHE.DRIVER | JWT.SECRET | CORS.ALLOW_ORIGIN |
|------|-----------|-------------|------------|-------------------|
| 开发 | true | file | 开发密钥 | * |
| 测试 | true | file | 测试密钥 | * |
| 生产 | false | redis | 强随机密钥 | 指定域名 |
@@ -0,0 +1,131 @@
# 11.5 前端部署
:::tip 概述
本节说明前端项目的构建与部署流程,包括构建命令、Nginx 配置、Gzip 压缩和部署步骤。适用于将前端静态资源部署到 Nginx 服务器。
:::
## 前置要求
| 依赖 | 版本要求 | 说明 |
|------|----------|------|
| Node.js | >= 18.0 | JavaScript 运行时 |
| pnpm | >= 8.0 | 包管理工具 |
| Nginx | >= 1.20 | Web 服务器 |
## 构建
```bash
# 进入前端项目目录
cd ui
# 安装项目依赖(从 pnpm-lock.yaml 精确安装,确保版本一致)
pnpm install
# 执行生产环境构建(Vite 会压缩代码、Tree-shaking、分包)
pnpm build
```
构建产物在 `ui/dist/` 目录。
### 环境变量配置
构建前可修改环境变量:
```bash
# .env.production — 生产环境构建配置
VITE_GLOB_API_URL=/api # API 基础路径(Nginx 代理到后端)
VITE_GLOB_API_URL_PREFIX= # API 路径前缀(一般留空)
```
## Nginx 配置
```nginx
# =============================================================================
# Nginx 站点配置 — Vue3 前端 SPA
# =============================================================================
# 用途:托管前端静态资源,API 请求反向代理到后端
# 配置路径:/etc/nginx/sites-available/rxthinkcmf-admin
# =============================================================================
server {
# 监听 80 端口(生产环境建议改为 443 + SSL)
listen 80;
# 绑定域名(替换为实际域名)
server_name admin.example.com;
# 前端构建产物目录(pnpm build 输出)
root /www/admin/dist;
index index.html;
# -----------------------------------------------------------------
# Vue Router History 模式
# -----------------------------------------------------------------
# SPA 路由在浏览器端处理,Nginx 需要将所有非文件请求回退到 index.html
# 例:访问 /user/list → 实际由 Vue Router 在浏览器端渲染
# try_files 依次尝试:$uri(真实文件)→ $uri/(目录)→ /index.html(回退)
location / {
try_files $uri $uri/ /index.html;
}
# -----------------------------------------------------------------
# 静态资源缓存
# -----------------------------------------------------------------
# Vite 构建的文件名包含 content hash(如 app.a1b2c3.js),
# 文件内容变化 → 文件名变化 → 浏览器自动请求新文件
# 因此可以放心设置长期缓存(30 天)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# -----------------------------------------------------------------
# API 反向代理
# -----------------------------------------------------------------
# 将 /api/ 开头的请求转发到后端 ThinkPHP 服务
# proxy_pass 末尾的 / 表示替换匹配的路径前缀
location /api/ {
proxy_pass http://127.0.0.1:8000; # 后端服务地址
proxy_set_header Host $host; # 传递原始 Host 头
proxy_set_header X-Real-IP $remote_addr; # 传递客户端真实 IP
}
}
```
## 部署步骤
```bash
# -----------------------------------------------------------------------------
# 第 1 步:构建前端项目
# -----------------------------------------------------------------------------
cd ui && pnpm install && pnpm build # 安装依赖并执行生产构建
# -----------------------------------------------------------------------------
# 第 2 步:将构建产物复制到 Nginx 站点目录
# -----------------------------------------------------------------------------
# -r → 递归复制目录
# dist/* → 构建产物(HTML、JS、CSS、图片等)
cp -r dist/* /www/admin/dist/
# -----------------------------------------------------------------------------
# 第 3 步:重载 Nginx 配置(不中断服务)
# -----------------------------------------------------------------------------
# reload → 优雅重载(不断开现有连接)
# 与 restart 的区别:restart 会断开所有连接,reload 不会
nginx -s reload
```
## 常见问题
| 问题 | 原因 | 解决方案 |
|------|------|----------|
| 刷新 404 | Nginx 未配置 try_files | 添加 `try_files $uri $uri/ /index.html` |
| API 404 | 代理配置错误 | 检查 `proxy_pass` 地址 |
| 静态资源 404 | 路径错误 | 检查 `root` 路径是否正确 |
| 白屏 | 构建失败或路径错误 | 检查构建日志和 Nginx 错误日志 |
| 样式错乱 | 字体文件加载失败 | 检查字体文件路径和 CORS 配置 |
## 相关文档
- [后端部署](/zh/deploy/backend.html)
- [Docker 生产部署](/zh/deploy/docker.html)
- [环境与构建配置](/zh/frontend/env-config.html)
@@ -0,0 +1,159 @@
# 11.7 性能调优
:::tip 概述
本节从 PHP、缓存、数据库、前端四个维度提供性能调优建议。
:::
## PHP 层
### OPcache(生产环境必须)
```ini
; =============================================================================
; php.ini — OPcache 字节码缓存配置
; =============================================================================
; OPcache 将 PHP 脚本编译为字节码并缓存在内存中,避免每次请求重复编译
; 启用后可提升 50%-80% 的 PHP 性能
; =============================================================================
opcache.enable = 1 ; 启用 OPcache(0=关闭,1=启用)
opcache.memory_consumption = 256 ; OPcache 共享内存大小(MB),按项目规模调整
opcache.interned_strings_buffer = 16 ; 驻留字符串内存(MB),减少重复字符串存储
opcache.max_accelerated_files = 10000 ; 最大缓存文件数(需大于项目 PHP 文件总数)
opcache.validate_timestamps = 0 ; 不检查文件修改时间(性能最优,更新代码后需重启 PHP-FPM)
opcache.save_comments = 1 ; 保留注释(ThinkPHP 注解依赖此配置)
```
:::warning OPcache 注意事项
`validate_timestamps = 0` 时,更新代码后必须重启 PHP-FPM 或手动清除 OPcache,否则不会生效。
:::
### PHP-FPM 进程池
```ini
; =============================================================================
; PHP-FPM 进程池配置
; =============================================================================
; 配置路径:/etc/php/8.2/fpm/pool.d/www.conf
; 根据服务器内存和并发量调整参数
; =============================================================================
pm = dynamic ; 动态进程管理(推荐)
pm.max_children = 50 ; 最大子进程数(每个约 30-50MB,根据内存调整)
pm.start_servers = 5 ; 启动时的进程数
pm.min_spare_servers = 5 ; 最小空闲进程(低于此值自动创建)
pm.max_spare_servers = 35 ; 最大空闲进程(高于此值自动销毁)
pm.max_requests = 500 ; 单进程最大请求数(达到后重启,防内存泄漏)
```
### Composer 优化
```bash
# 生产环境安装依赖
# --no-dev → 排除开发依赖(symfony/var-dumper、think-trace 等)
# --optimize-autoloader → 优化自动加载器(生成 classmap,减少文件查找)
composer install --no-dev --optimize-autoloader
```
## 缓存层
### 使用 Redis
```ini
; .env — Redis 缓存配置
[CACHE]
DRIVER = redis ; 缓存驱动改为 redis(默认为 file)
REDIS_HOST = 127.0.0.1 ; Redis 服务器地址
REDIS_PORT = 6379 ; Redis 端口
REDIS_PASSWORD = your_redis_password ; Redis 密码
```
:::tip 缓存效果
DictService 和 ParamService 采用两级缓存(请求级 + 持久缓存),使用 Redis 后:
- 字典查询:首次查库 + 写入 Redis,后续直接读缓存(1小时过期)
- 参数查询:同上
- 减少 80%+ 的数据库查询
:::
## 数据库层
### 索引优化
```sql
-- =============================================================================
-- 为常用查询字段添加索引(提升查询速度)
-- =============================================================================
-- 索引原理:类似书的目录,数据库通过索引快速定位数据行,避免全表扫描
-- 注意:索引会占用额外存储空间,且降低写入速度,只在高频查询字段上创建
-- =============================================================================
-- 用户表:用户名登录查询、租户隔离查询
ALTER TABLE think_user ADD INDEX idx_username (username);
ALTER TABLE think_user ADD INDEX idx_tenant_id (tenant_id);
-- 文章表:状态筛选、分类筛选
ALTER TABLE think_article ADD INDEX idx_status (status);
ALTER TABLE think_article ADD INDEX idx_category_id (category_id);
-- 操作日志表:按时间范围查询(最频繁的查询模式)
ALTER TABLE think_operation_log ADD INDEX idx_create_time (create_time);
```
### 慢查询日志
```ini
; MySQL my.cnf — 慢查询日志配置
; 配置路径:/etc/mysql/my.cnf 或 /etc/my.cnf
; 生效命令:sudo systemctl restart mysql
slow_query_log = 1 ; 启用慢查询日志
slow_query_log_file = /var/log/mysql/slow.log ; 日志文件路径
long_query_time = 2 ; 超过 2 秒的查询记录为慢查询
```
### 连接池
高并发场景建议配置持久连接:
```ini
; php.ini — 使用 Unix Socket 连接 MySQL(比 TCP 更快)
; 前提:PHP-FPM 和 MySQL 在同一台服务器上
pdo_mysql.default_socket = /var/run/mysqld/mysqld.sock
```
## 前端层
### Gzip 压缩
```nginx
# Nginx Gzip 压缩配置(添加到 http 或 server 块)
gzip on; ; 启用 Gzip 压缩
gzip_types text/plain text/css application/json application/javascript text/xml; ; 需要压缩的 MIME 类型
gzip_min_length 1024; ; 小于 1KB 的响应不压缩(压缩收益低)
```
### 静态资源缓存
```nginx
# 静态资源浏览器缓存配置(添加到 server 块)
# Vite 构建的文件名包含 content hash,可放心设置长期缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 30d; ; 缓存 30 天
add_header Cache-Control "public, immutable"; ; 公共缓存,内容不变
}
```
### CDN 加速
将静态资源(JS/CSS/图片)部署到 CDN,减轻服务器压力。
## 性能检查清单
| 检查项 | 生产环境 | 开发环境 |
|--------|----------|----------|
| OPcache | 启用 | 可选 |
| Redis 缓存 | 推荐 | 可选 |
| Gzip 压缩 | 启用 | 可选 |
| 慢查询日志 | 启用 | 启用 |
| Composer 优化 | `--no-dev --optimize` | 默认 |
| 静态资源缓存 | 30天 | 不缓存 |
@@ -0,0 +1,217 @@
# 11.8 安全加固清单
:::danger 上线前必检
以下安全配置在生产环境上线前必须完成,否则存在严重安全风险。
:::
## 必须执行
| 项目 | 操作 | 检查命令 |
|------|------|----------|
| JWT 密钥 | `.env` 中 `JWT.SECRET` 修改为强随机字符串 | `grep JWT.SECRET .env` |
| 调试模式 | `.env` 中 `APP_DEBUG = false` | `grep APP_DEBUG .env` |
| 错误信息 | `.env` 中 `SHOW_ERROR_MSG = false` | `grep SHOW_ERROR_MSG .env` |
| CORS | `.env` 中 `CORS.ALLOW_ORIGIN` 限制为实际域名 | `grep CORS .env` |
| 数据库密码 | 修改默认 root 密码 | `mysql -u root -p` |
| Redis 密码 | 设置 requirepass | `redis-cli CONFIG GET requirepass` |
| HTTPS | 启用 SSL 证书 | `curl -I https://your-domain.com` |
## 生成强密钥
```bash
# -----------------------------------------------------------------------------
# 生成 JWT 签名密钥(32 字节 = 64 个十六进制字符)
# -----------------------------------------------------------------------------
# random_bytes(32) → 生成 32 字节的密码学安全随机数
# bin2hex() → 转为十六进制字符串(便于复制粘贴到 .env)
php -r "echo bin2hex(random_bytes(32));"
# -----------------------------------------------------------------------------
# 生成 MySQL 密码(16 字节 = 32 个十六进制字符)
# -----------------------------------------------------------------------------
php -r "echo bin2hex(random_bytes(16));"
# -----------------------------------------------------------------------------
# 生成 Redis 密码(16 字节 = 32 个十六进制字符)
# -----------------------------------------------------------------------------
php -r "echo bin2hex(random_bytes(16));"
```
## Nginx 安全配置
```nginx
# =============================================================================
# Nginx 安全加固配置(添加到 server 块内)
# =============================================================================
# 禁止访问隐藏文件(.env、.git、.htaccess 等)
location ~ /\. {
deny all;
}
# 禁止上传目录执行 PHP(防止 Webshell 攻击)
location ~ /uploads/.*\.php$ {
deny all;
}
# -----------------------------------------------------------------------------
# 安全响应头
# -----------------------------------------------------------------------------
# X-Frame-Options → 禁止被嵌入 iframe(防点击劫持)
# X-Content-Type-Options → 禁止浏览器 MIME 类型嗅探
# X-XSS-Protection → 启用浏览器 XSS 过滤
# Referrer-Policy → 控制 Referer 头信息泄露
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 隐藏 Nginx 版本号(防止攻击者针对性利用已知漏洞)
server_tokens off;
```
## PHP 安全配置
```ini
; =============================================================================
; php.ini 生产环境安全配置
; =============================================================================
; 配置路径:/etc/php/8.2/fpm/php.ini
; 生效命令:sudo systemctl restart php8.2-fpm
; =============================================================================
expose_php = Off ; 隐藏 PHP 版本号(防止针对性攻击)
display_errors = Off ; 不向浏览器显示错误(防止 SQL/路径泄露)
log_errors = On ; 记录错误到日志文件(便于排查)
allow_url_fopen = Off ; 禁止 fopen 打开远程 URL(防 SSRF)
disable_functions = exec,passthru,shell_exec,system,proc_open ; 禁用危险系统函数
```
## 文件权限
```bash
# -----------------------------------------------------------------------------
# 项目目录(owner 可读写执行,group/others 可读和执行)
# -----------------------------------------------------------------------------
chmod -R 755 /www/api/ # 目录 755,文件自动 644
chown -R www-data:www-data /www/api/ # owner 设为 Web 服务器用户
# -----------------------------------------------------------------------------
# 运行时目录(必须可写 — ThinkPHP 缓存、日志、编译模板)
# -----------------------------------------------------------------------------
chmod -R 777 /www/api/runtime/ # 777 确保 PHP-FPM 可写入
# -----------------------------------------------------------------------------
# 上传目录(必须可写 — 用户上传文件存储位置)
# -----------------------------------------------------------------------------
chmod -R 755 /opt/data/uploads/ # 目录可写
chown -R www-data:www-data /opt/data/uploads/ # owner 设为 Web 服务器用户
# -----------------------------------------------------------------------------
# .env 文件(仅 owner 可读 — 包含数据库密码等敏感信息)
# -----------------------------------------------------------------------------
# 600 = owner 可读写,group/others 无权限
chmod 600 /www/api/.env
```
## 数据库安全
```sql
-- =============================================================================
-- MySQL 安全加固 SQL
-- =============================================================================
-- 在 MySQL 命令行中执行:mysql -u root -p
-- =============================================================================
-- 修改 root 密码(替换 strong_password_here 为实际强密码)
ALTER USER 'root'@'localhost' IDENTIFIED BY 'strong_password_here';
-- 创建应用专用数据库用户(生产环境不应用 root 连接应用)
-- rxthinkcmf_app → 应用专用用户名
-- app_password → 应用专用密码
CREATE USER 'rxthinkcmf_app'@'localhost' IDENTIFIED BY 'app_password';
-- 授权应用用户只能访问项目数据库(最小权限原则)
-- rxthinkcmf.thinkphp6.elevue.* → 该项目数据库的所有表
GRANT ALL PRIVILEGES ON `rxthinkcmf.thinkphp6.elevue`.* TO 'rxthinkcmf_app'@'localhost';
FLUSH PRIVILEGES; -- 刷新权限使配置生效
-- 禁止远程 root 登录(只允许本地连接)
-- Host NOT IN (...) → 删除非本地的 root 账户
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
FLUSH PRIVILEGES;
```
## Redis 安全
```ini
# =============================================================================
# redis.conf 安全配置
# =============================================================================
# 配置路径:/etc/redis/redis.conf
# 生效命令:sudo systemctl restart redis
# =============================================================================
requirepass your_strong_redis_password ; 设置访问密码(必须设置)
bind 127.0.0.1 ; 只监听本地(禁止外部直接访问)
protected-mode yes ; 启用保护模式(无密码时拒绝外部连接)
# 禁用危险命令(防止误操作清空数据或修改配置)
# 将命令重命名为空字符串 = 禁用该命令
rename-command FLUSHALL "" ; 禁用 FLUSHALL(清空所有数据库)
rename-command FLUSHDB "" ; 禁用 FLUSHDB(清空当前数据库)
rename-command CONFIG "" ; 禁用 CONFIG(防止运行时修改配置)
```
## 安全检查脚本
```bash
#!/bin/bash
# =============================================================================
# check_security.sh — 生产环境安全检查脚本
# =============================================================================
# 用途:自动检查 .env 中的安全配置项是否符合生产要求
# 用法:cd /www/api && bash check_security.sh
# =============================================================================
echo "=== 安全检查 ==="
# -----------------------------------------------------------------------------
# 检查 APP_DEBUG 是否关闭
# -----------------------------------------------------------------------------
# grep "APP_DEBUG" .env → 从 .env 文件中提取 APP_DEBUG 行
# grep -v "#" → 排除注释行
# cut -d= -f2 → 以 = 为分隔符取第 2 部分(值)
# tr -d ' ' → 去除空格
debug=$(grep "APP_DEBUG" .env | grep -v "#" | cut -d= -f2 | tr -d ' ')
if [ "$debug" = "true" ]; then
echo "❌ APP_DEBUG 未关闭(生产环境必须为 false)"
else
echo "✓ APP_DEBUG 已关闭"
fi
# -----------------------------------------------------------------------------
# 检查 JWT 密钥长度
# -----------------------------------------------------------------------------
# sed -n '/\[JWT\]/,/^\[/p' → 提取 .env 中 [JWT] 到下一个 [...] 之间的内容
# grep "^SECRET" → 取 SECRET 行
# ${#secret} → 获取字符串长度
secret=$(sed -n '/\[JWT\]/,/^\[/p' .env | grep "^SECRET" | cut -d= -f2 | tr -d ' ')
if [ ${#secret} -lt 32 ]; then
echo "❌ JWT 密钥太短(建议32字节以上,当前 ${#secret} 字符)"
else
echo "✓ JWT 密钥长度足够(${#secret} 字符)"
fi
# -----------------------------------------------------------------------------
# 检查 CORS 是否允许所有来源
# -----------------------------------------------------------------------------
cors=$(sed -n '/\[CORS\]/,/^\[/p' .env | grep "^ALLOW_ORIGIN" | cut -d= -f2 | tr -d ' ')
if [ "$cors" = "*" ]; then
echo "⚠ CORS 允许所有来源(生产环境建议限制为实际域名)"
else
echo "✓ CORS 已限制来源"
fi
echo "=== 检查完成 ==="
```
@@ -0,0 +1,419 @@
# 11.10 故障排查手册
:::tip 排查思路
遇到问题时,按以下顺序排查:
1. 查看 PHP 错误日志(`/var/log/php_errors.log`)
2. 查看 Nginx 错误日志(`/var/log/nginx/error.log`)
3. 查看浏览器控制台(F12)
4. 检查 `.env` 配置是否正确
:::
## 服务器问题
### 502 Bad Gateway
PHP-FPM 未启动或配置错误:
```bash
# -----------------------------------------------------------------------------
# 检查 PHP-FPM 服务状态
# -----------------------------------------------------------------------------
# status → 显示服务是否正在运行、主进程 PID、内存占用等
sudo systemctl status php8.2-fpm
# -----------------------------------------------------------------------------
# 重启 PHP-FPM(最常见的修复方式)
# -----------------------------------------------------------------------------
# restart → 停止后重新启动,所有正在处理的请求会被中断
sudo systemctl restart php8.2-fpm
# -----------------------------------------------------------------------------
# 实时查看 PHP-FPM 错误日志(Ctrl+C 退出)
# -----------------------------------------------------------------------------
# -f → follow 模式,持续输出新增日志
sudo tail -f /var/log/php8.2-fpm.log
```
### 500 Internal Server Error
```bash
# -----------------------------------------------------------------------------
# 查看 PHP 错误日志(定位 PHP 脚本执行错误)
# -----------------------------------------------------------------------------
# tail -f → 实时输出新增日志行
sudo tail -f /var/log/php_errors.log
# -----------------------------------------------------------------------------
# 查看 Nginx 错误日志(定位 Nginx 层错误)
# -----------------------------------------------------------------------------
sudo tail -f /var/log/nginx/error.log
# -----------------------------------------------------------------------------
# 检查运行时目录权限(500 错误常见原因)
# -----------------------------------------------------------------------------
# -la → 显示详细信息(包括隐藏文件)
ls -la /www/api/runtime/
```
### 404 Not Found
Nginx URL 重写配置错误:
```nginx
# 检查 try_files 或 rewrite 规则
# $uri → 当前请求的文件路径
# $uri/ → 当前请求的目录路径
# /index.php?s=$uri&$args → 回退到 ThinkPHP 入口,传递原始路径和参数
location / {
try_files $uri $uri/ /index.php?s=$uri&$args;
}
```
## 数据库问题
### MySQL 连接失败
```bash
# -----------------------------------------------------------------------------
# 检查 MySQL 服务状态
# -----------------------------------------------------------------------------
sudo systemctl status mysql
# -----------------------------------------------------------------------------
# 测试数据库连接(SELECT 1 是最简单的查询,验证连接是否正常)
# -----------------------------------------------------------------------------
mysql -u root -p -e "SELECT 1"
# -----------------------------------------------------------------------------
# 检查用户权限(查看所有用户及其允许的连接来源)
# -----------------------------------------------------------------------------
# user → 用户名,host → 允许连接的主机(% 表示任意)
mysql -u root -p -e "SELECT user, host FROM mysql.user;"
# -----------------------------------------------------------------------------
# 允许远程连接(生产环境慎用,建议通过 SSH 隧道连接)
# -----------------------------------------------------------------------------
# GRANT ALL → 授权所有权限
# ON `数据库`.* → 指定数据库的所有表
# TO 'root'@'%' → 允许从任意主机连接的 root 用户
# IDENTIFIED BY → 设置密码
mysql -u root -proot -e "GRANT ALL ON \`rxthinkcmf.thinkphp6.elevue\`.* TO 'root'@'%' IDENTIFIED BY 'root';"
mysql -u root -p -e "FLUSH PRIVILEGES;" # 刷新权限使配置生效
```
### PostgreSQL 连接失败
```bash
# -----------------------------------------------------------------------------
# 检查 PostgreSQL 服务状态
# -----------------------------------------------------------------------------
sudo systemctl status postgresql
# -----------------------------------------------------------------------------
# 测试连接(-U 指定用户,-d 指定数据库,-c 执行 SQL)
# -----------------------------------------------------------------------------
psql -U postgres -d rxthinkcmf.thinkphp6.elevue -c "SELECT 1;"
# -----------------------------------------------------------------------------
# 检查客户端认证配置(pg_hba.conf 控制哪些 IP 可以连接)
# -----------------------------------------------------------------------------
sudo nano /etc/postgresql/*/main/pg_hba.conf
# 添加以下行允许所有 IP 通过密码连接:
# host all all 0.0.0.0/0 md5
# 重启使配置生效
sudo systemctl restart postgresql
```
### SQLite 连接失败
```bash
# -----------------------------------------------------------------------------
# 检查 SQLite 数据库文件是否存在
# -----------------------------------------------------------------------------
# -la → 显示文件大小、权限、修改时间
ls -la /www/api/runtime/database.sqlite
# -----------------------------------------------------------------------------
# 修复文件权限(确保 Web 服务器用户可读写)
# -----------------------------------------------------------------------------
# 664 → owner/group 可读写,others 只读
chmod 664 /www/api/runtime/database.sqlite
chown www-data:www-data /www/api/runtime/database.sqlite
```
### SQL Server 连接失败
```bash
# -----------------------------------------------------------------------------
# 检查 SQL Server 服务状态
# -----------------------------------------------------------------------------
sudo systemctl status mssql-server
# -----------------------------------------------------------------------------
# 测试连接(-S 服务器,-U 用户,-P 密码,-Q 执行查询)
# -----------------------------------------------------------------------------
sqlcmd -S localhost -U sa -P root -Q "SELECT 1"
# -----------------------------------------------------------------------------
# 检查 PHP 的 SQL Server 扩展是否安装
# -----------------------------------------------------------------------------
php -m | grep pdo_sqlsrv # 查找 pdo_sqlsrv 扩展
```
### 数据库表不存在
```bash
# -----------------------------------------------------------------------------
# 重新导入初始化 SQL(按数据库类型选择对应命令)
# -----------------------------------------------------------------------------
# MySQL:通过 mysql 命令导入 SQL 文件
mysql -u root -proot rxthinkcmf.thinkphp6.elevue < document/mysql/rxthinkcmf.thinkphp6.elevue.sql
# PostgreSQL:通过 psql 命令导入 SQL 文件
# -U 指定用户,-d 指定数据库,-f 指定 SQL 文件
psql -U postgres -d rxthinkcmf.thinkphp6.elevue -f document/postgresql/rxthinkcmf.thinkphp6.elevue.sql
# SQLite:通过 sqlite3 命令导入 SQL 文件
sqlite3 /www/api/runtime/database.sqlite < document/sqlite/rxthinkcmf.thinkphp6.elevue.sql
```
## PHP 问题
### 缺少扩展
```bash
# -----------------------------------------------------------------------------
# 查看已安装的 PHP 扩展列表
# -----------------------------------------------------------------------------
php -m # 列出所有已加载的扩展
# -----------------------------------------------------------------------------
# 安装缺失的扩展(按数据库类型选择)
# -----------------------------------------------------------------------------
sudo apt install php8.2-mysql # MySQL 驱动(pdo_mysql)
# sudo apt install php8.2-pgsql # PostgreSQL 驱动(取消注释以安装)
# sudo apt install php8.2-sqlite3 # SQLite 驱动(取消注释以安装)
# -----------------------------------------------------------------------------
# 重启 PHP-FPM 使新扩展生效
# -----------------------------------------------------------------------------
sudo systemctl restart php8.2-fpm
```
### 内存不足
```ini
; php.ini — 增加单脚本最大内存限制
; 默认 128M,Excel 导出等大内存操作建议设为 256M 或更高
memory_limit = 256M
```
### 执行超时
```ini
; php.ini — 增加单脚本最大执行时间
; 默认 30 秒,长时间操作(如数据迁移)建议设为 300 秒
max_execution_time = 300
```
## 权限问题
```bash
# -----------------------------------------------------------------------------
# 修复项目目录权限(owner 为 Web 服务器用户)
# -----------------------------------------------------------------------------
# chown → 修改文件所有者(-R 递归)
# chmod → 修改文件权限(755 = owner 全权限,group/others 可读可执行)
sudo chown -R www-data:www-data /www/api/
sudo chmod -R 755 /www/api/
# -----------------------------------------------------------------------------
# 修复运行时目录(必须可写 — ThinkPHP 缓存、日志、编译模板)
# -----------------------------------------------------------------------------
# 777 = 所有人可读写执行(仅限运行时目录,不要用于项目根目录)
sudo chmod -R 777 /www/api/runtime/
# -----------------------------------------------------------------------------
# 修复上传目录(必须可写 — 用户上传文件存储位置)
# -----------------------------------------------------------------------------
sudo chmod -R 755 /opt/data/uploads/
sudo chown -R www-data:www-data /opt/data/uploads/
```
## 业务问题
### 401 未授权
```bash
# -----------------------------------------------------------------------------
# 检查 Token 是否正确携带
# -----------------------------------------------------------------------------
# -H "Authorization: Bearer <token>" → 在请求头中携带 JWT Token
# <token> 替换为实际的 access_token 值
curl -H "Authorization: Bearer <token>" http://localhost:8000/api/index/getUser
# -----------------------------------------------------------------------------
# 检查 JWT 密钥配置
# -----------------------------------------------------------------------------
# 确认 .env 中的 JWT.SECRET 与后端代码一致
grep JWT.SECRET .env
```
### 403 禁止访问
```bash
# -----------------------------------------------------------------------------
# 检查用户角色分配(用户是否分配了角色)
# -----------------------------------------------------------------------------
# think_user_role 表存储用户与角色的关联关系
mysql -u root -proot rxthinkcmf.thinkphp6.elevue -e "SELECT * FROM think_user_role WHERE user_id=1;"
# -----------------------------------------------------------------------------
# 检查角色权限(角色是否分配了菜单权限)
# -----------------------------------------------------------------------------
# think_role_menu 表存储角色与菜单的关联关系
mysql -u root -proot rxthinkcmf.thinkphp6.elevue -e "SELECT * FROM think_role_menu WHERE role_id=1;"
```
### 文件上传失败
```bash
# -----------------------------------------------------------------------------
# 检查上传目录权限和空间
# -----------------------------------------------------------------------------
# -la → 显示详细信息(权限、大小、修改时间)
ls -la /opt/data/uploads/
# -----------------------------------------------------------------------------
# 检查 PHP 上传限制配置
# -----------------------------------------------------------------------------
# php -i → 输出 PHP 配置信息
# grep → 过滤出上传相关的配置项
php -i | grep upload_max_filesize # 单文件最大上传大小
php -i | grep post_max_size # POST 请求最大大小
```
### Token 频繁过期
```bash
# -----------------------------------------------------------------------------
# 检查 JWT Token 过期时间配置
# -----------------------------------------------------------------------------
# grep → 从 .env 文件中提取 ACCESS_TTL 配置值
grep JWT.ACCESS_TTL .env
# 默认 7200 秒(2 小时),可在 .env 中调整
# 如果需要更长的登录状态,增大此值或使用 refresh_token 机制
```
### 日志中 IP 显示 127.0.0.1
部署在 CDN / 负载均衡 / Nginx 反向代理后面时,日志中的 IP 可能显示为 `127.0.0.1`。
```bash
# -----------------------------------------------------------------------------
# 第 1 步:检查 .env 中是否配置了 PROXY_IPS
# -----------------------------------------------------------------------------
grep PROXY_IPS .env
# 如果为空或未配置,添加受信代理 IP(以腾讯云 CDN 为例):
# PROXY_IPS = 127.0.0.1,::1,101.226.0.0/15,180.101.0.0/16,182.254.0.0/16
# -----------------------------------------------------------------------------
# 第 2 步:检查 Nginx 是否错误覆盖了代理头
# -----------------------------------------------------------------------------
# 如果 Nginx 配置中有以下行,需要删除:
# fastcgi_param REMOTE_ADDR $remote_addr;
# fastcgi_param HTTP_X_REAL_IP $remote_addr;
# fastcgi_param HTTP_X_FORWARDED_FOR $remote_addr;
# 这些会覆盖 CDN 传递的真实 IP 头
grep -r "fastcgi_param.*REMOTE_ADDR\|fastcgi_param.*X_REAL_IP\|fastcgi_param.*X_FORWARDED" /etc/nginx/
# -----------------------------------------------------------------------------
# 第 3 步:清除缓存并重载
# -----------------------------------------------------------------------------
rm -rf runtime/*
nginx -s reload
```
## 定时任务问题
### 任务不执行
```bash
# -----------------------------------------------------------------------------
# 查看当前用户的 crontab 配置(确认任务是否已添加)
# -----------------------------------------------------------------------------
# -l → 列出当前 crontab 中的所有任务
crontab -l
# -----------------------------------------------------------------------------
# 检查 cron 服务是否运行
# -----------------------------------------------------------------------------
sudo systemctl status cron
# -----------------------------------------------------------------------------
# 手动执行任务(验证命令是否能正常运行)
# -----------------------------------------------------------------------------
# cd /www/api → 切换到项目目录(crontab 环境变量不完整,需手动 cd)
cd /www/api && php think job:run
# -----------------------------------------------------------------------------
# 查看 cron 执行日志(排查任务是否被触发)
# -----------------------------------------------------------------------------
# grep CRON → 过滤包含 CRON 的日志行
sudo tail -f /var/log/syslog | grep CRON
```
### 任务重复执行
```bash
# -----------------------------------------------------------------------------
# 检查是否有多个 job:run 进程同时运行
# -----------------------------------------------------------------------------
# ps aux → 列出所有进程
# grep job:run → 过滤出 job:run 相关进程
ps aux | grep job:run
# -----------------------------------------------------------------------------
# 检查 Daemon 守护进程
# -----------------------------------------------------------------------------
ps aux | grep job:daemon
```
## 前端问题
### API 请求 404
```bash
# -----------------------------------------------------------------------------
# 测试后端路由是否正常(绕过前端,直接请求 API)
# -----------------------------------------------------------------------------
# -H → 设置请求头(携带 JWT Token)
curl http://localhost:8000/api/example/page -H "Authorization: Bearer <token>"
# -----------------------------------------------------------------------------
# 检查 Nginx 配置语法是否正确
# -----------------------------------------------------------------------------
# -t → test 模式,检查配置文件语法(不实际重载)
sudo nginx -t
```
### 白屏
打开浏览器 F12 控制台查看错误,常见原因:
1. JS 加载失败
2. 路由配置错误
3. Token 过期
### 构建失败
```bash
# -----------------------------------------------------------------------------
# 清理并重新安装依赖(修复依赖冲突或缓存损坏)
# -----------------------------------------------------------------------------
cd ui
rm -rf node_modules pnpm-lock.yaml # 删除依赖目录和锁文件
pnpm install # 重新安装所有依赖
pnpm build # 重新构建
```