后端 (Laravel 10 + Sanctum): - RBAC 四层权限系统 (users→roles→permissions→menus) - 门店隔离中间件 (BelongsToStore Trait + StoreIsolation Middleware) - 操作日志中间件 (自动记录写操作) - 权限检查中间件 (CheckPermission) - 16张数据库表迁移 (系统基础+RBAC+字典+配置) - 11个 Eloquent Model - Auth API (登录/登出/用户信息) - 系统设置模块 CRUD (门店/部门/职务/用户/角色/菜单/权限/字典/日志) - 45条 RESTful API 路由 - InitSeeder 初始数据 (超管/角色/76权限/31菜单) 前端 (Vue 3 + Element Plus + Vite): - Axios 请求封装 + Token 注入 - Pinia 状态管理 (user + permission store) - 动态路由 (服务端菜单→前端路由自动生成) - 后台布局 (侧边栏+顶栏+主内容区) - 登录页 + 仪表盘首页 - 系统设置 7 个 CRUD 页面 技术方案文档 (7卷): - 技术总览/数据库设计/API规范/RBAC设计/模块详设/小程序设计/部署方案
15 KiB
15 KiB
宫中有喜 — 部署方案
一、部署架构
┌─────────────────────────────────────────┐
│ CDN (可选) │
│ 静态资源加速 │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ Nginx (反向代理) │
│ 80/443 → 静态资源 / API / WebSocket │
└────┬──────────────┬─────────────────────┘
│ │
▼ ▼
┌─────────┐ ┌──────────────┐
│ Vue SPA │ │ PHP-FPM │
│ (静态) │ │ Laravel App │
└─────────┘ └──────┬───────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌──────────┐ ┌────────┐ ┌─────────┐
│ MySQL 8 │ │ Redis │ │ Storage │
│ │ │ │ │ (文件) │
└──────────┘ └────────┘ └─────────┘
二、环境要求
| 组件 | 最低版本 | 推荐 |
|---|---|---|
| OS | Ubuntu 20.04 / CentOS 8 | Ubuntu 22.04 |
| PHP | 8.1 | 8.2 |
| MySQL | 8.0 | 8.0 |
| Redis | 6.0 | 7.0 |
| Nginx | 1.18 | 1.24 |
| Node.js | 16 | 18 LTS |
| Composer | 2.x | 2.x |
| Docker | 20.10 | 24.x |
| Docker Compose | 2.x | 2.x |
PHP 扩展
php-fpm php-mysql php-redis php-mbstring php-xml
php-curl php-gd php-zip php-bcmath php-intl
php-opcache php-fileinfo
三、Docker Compose 部署
3.1 docker-compose.yml
version: '3.8'
services:
# Nginx 反向代理
nginx:
image: nginx:1.24-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./docker/nginx/conf.d:/etc/nginx/conf.d
- ./docker/nginx/ssl:/etc/nginx/ssl
- ./frontend/dist:/var/www/frontend
- ./backend/public:/var/www/backend/public
- ./backend/storage/app/public:/var/www/backend/storage/app/public
depends_on:
- php
networks:
- app-network
restart: unless-stopped
# PHP-FPM
php:
build:
context: ./docker/php
dockerfile: Dockerfile
volumes:
- ./backend:/var/www/backend
depends_on:
- mysql
- redis
networks:
- app-network
restart: unless-stopped
# Laravel 队列 Worker
queue:
build:
context: ./docker/php
dockerfile: Dockerfile
command: php artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
volumes:
- ./backend:/var/www/backend
depends_on:
- mysql
- redis
networks:
- app-network
restart: unless-stopped
# Laravel 定时任务
scheduler:
build:
context: ./docker/php
dockerfile: Dockerfile
command: /bin/sh -c "while true; do php /var/www/backend/artisan schedule:run --verbose --no-interaction >> /dev/null 2>&1; sleep 60; done"
volumes:
- ./backend:/var/www/backend
depends_on:
- mysql
- redis
networks:
- app-network
restart: unless-stopped
# MySQL
mysql:
image: mysql:8.0
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_USER: ${DB_USERNAME}
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
- ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
- ./docker/mysql/init:/docker-entrypoint-initdb.d
networks:
- app-network
restart: unless-stopped
# Redis
redis:
image: redis:7-alpine
ports:
- "6379:6379"
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis-data:/data
networks:
- app-network
restart: unless-stopped
volumes:
mysql-data:
redis-data:
networks:
app-network:
driver: bridge
3.2 PHP Dockerfile
# docker/php/Dockerfile
FROM php:8.2-fpm-alpine
# 安装扩展
RUN apk add --no-cache \
libpng-dev libjpeg-turbo-dev freetype-dev \
libzip-dev icu-dev oniguruma-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install \
pdo_mysql mbstring xml curl gd zip bcmath intl opcache pcntl \
&& pecl install redis \
&& docker-php-ext-enable redis
# Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# PHP 配置
COPY php.ini /usr/local/etc/php/conf.d/custom.ini
WORKDIR /var/www/backend
# 权限
RUN chown -R www-data:www-data /var/www/backend
USER www-data
EXPOSE 9000
CMD ["php-fpm"]
3.3 PHP 配置
; docker/php/php.ini
upload_max_filesize = 20M
post_max_size = 25M
memory_limit = 256M
max_execution_time = 60
date.timezone = Asia/Shanghai
; OPcache
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
3.4 Nginx 配置
# docker/nginx/conf.d/default.conf
# 后台管理 SPA
server {
listen 80;
server_name admin.example.com;
root /var/www/frontend;
index index.html;
# SPA 路由
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# Gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1024;
}
# API 服务
server {
listen 80;
server_name api.example.com;
root /var/www/backend/public;
index index.php;
# Laravel 路由
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP-FPM
location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 60;
}
# 文件上传大小
client_max_body_size 25m;
# 静态资源
location /storage {
alias /var/www/backend/storage/app/public;
expires 30d;
}
# 禁止访问隐藏文件
location ~ /\. {
deny all;
}
}
3.5 MySQL 配置
# docker/mysql/my.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
default-time-zone = '+08:00'
# InnoDB
innodb_buffer_pool_size = 512M
innodb_log_file_size = 128M
innodb_flush_log_at_trx_commit = 2
# 连接
max_connections = 200
wait_timeout = 600
# 慢查询
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
# 二进制日志(用于备份恢复)
log-bin = mysql-bin
binlog_expire_logs_seconds = 604800
binlog_format = ROW
[client]
default-character-set = utf8mb4
四、环境变量 (.env)
# .env (生产环境示例)
APP_NAME="宫中有喜"
APP_ENV=production
APP_KEY=base64:xxxx
APP_DEBUG=false
APP_URL=https://api.example.com
# 数据库
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=gongzhongyouxi
DB_USERNAME=appuser
DB_PASSWORD=strong_password_here
DB_ROOT_PASSWORD=root_strong_password
# Redis
REDIS_HOST=redis
REDIS_PASSWORD=redis_password
REDIS_PORT=6379
# 缓存/队列/会话
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
# 文件存储
FILESYSTEM_DISK=local
# 或 OSS:
# FILESYSTEM_DISK=oss
# OSS_ACCESS_KEY_ID=xxx
# OSS_ACCESS_KEY_SECRET=xxx
# OSS_BUCKET=xxx
# OSS_ENDPOINT=xxx
# 微信小程序
WECHAT_MINI_APP_ID=wxXXXXXXXXXXX
WECHAT_MINI_SECRET=xxxxxxxxxx
# 微信支付
WECHAT_PAY_MCH_ID=xxxxxxxxxx
WECHAT_PAY_KEY=xxxxxxxxxx
WECHAT_PAY_CERT_PATH=/path/to/cert
WECHAT_PAY_KEY_PATH=/path/to/key
WECHAT_PAY_NOTIFY_URL=https://api.example.com/api/v1/client/pay/callback
# 短信
SMS_DRIVER=aliyun
SMS_KEY=xxx
SMS_SECRET=xxx
SMS_SIGN=宫中有喜
# 日志
LOG_CHANNEL=daily
LOG_LEVEL=warning
五、部署流程
5.1 首次部署
# 1. 克隆代码
git clone <repo> /opt/saas
cd /opt/saas
# 2. 配置环境变量
cp .env.example .env
vim .env # 填入生产配置
# 3. 构建前端
cd frontend
npm install
npm run build
cd ..
# 4. 启动容器
docker compose up -d
# 5. 初始化 Laravel
docker compose exec php composer install --no-dev --optimize-autoloader
docker compose exec php php artisan key:generate
docker compose exec php php artisan migrate --force
docker compose exec php php artisan db:seed --class=InitSeeder
docker compose exec php php artisan storage:link
docker compose exec php php artisan config:cache
docker compose exec php php artisan route:cache
docker compose exec php php artisan view:cache
# 6. 验证
curl https://api.example.com/api/v1/health
5.2 更新部署
# 1. 拉取代码
cd /opt/saas
git pull origin main
# 2. 更新后端依赖
docker compose exec php composer install --no-dev --optimize-autoloader
# 3. 数据库迁移
docker compose exec php php artisan migrate --force
# 4. 清除缓存
docker compose exec php php artisan config:cache
docker compose exec php php artisan route:cache
docker compose exec php php artisan view:cache
docker compose exec php php artisan queue:restart
# 5. 更新前端(如有变更)
cd frontend
npm install
npm run build
cd ..
# 6. 重载 Nginx
docker compose exec nginx nginx -s reload
5.3 零停机部署(可选)
# 使用 Laravel 维护模式
docker compose exec php php artisan down --retry=60
# 执行部署步骤...
docker compose exec php php artisan up
六、备份策略
6.1 数据库备份
#!/bin/bash
# scripts/backup-db.sh
BACKUP_DIR="/opt/backups/mysql"
DATE=$(date +%Y%m%d_%H%M)
KEEP_DAYS=30
mkdir -p $BACKUP_DIR
# 备份
docker compose exec -T mysql mysqldump \
-u root -p"${DB_ROOT_PASSWORD}" \
--single-transaction \
--routines --triggers \
${DB_DATABASE} | gzip > "${BACKUP_DIR}/db_${DATE}.sql.gz"
# 清理旧备份
find $BACKUP_DIR -name "*.sql.gz" -mtime +$KEEP_DAYS -delete
echo "[$(date)] Backup completed: db_${DATE}.sql.gz"
6.2 文件备份
#!/bin/bash
# scripts/backup-files.sh
BACKUP_DIR="/opt/backups/files"
DATE=$(date +%Y%m%d)
mkdir -p $BACKUP_DIR
# 备份上传文件
tar -czf "${BACKUP_DIR}/storage_${DATE}.tar.gz" \
-C /opt/saas/backend storage/app/public
# 清理 30 天前
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete
6.3 定时任务
# crontab -e
# 每天凌晨 3 点备份数据库
0 3 * * * /opt/saas/scripts/backup-db.sh >> /var/log/backup.log 2>&1
# 每周日凌晨 4 点备份文件
0 4 * * 0 /opt/saas/scripts/backup-files.sh >> /var/log/backup.log 2>&1
七、监控与日志
7.1 健康检查
// routes/api.php
Route::get('/health', function () {
try {
DB::connection()->getPdo();
Redis::ping();
return response()->json([
'status' => 'healthy',
'timestamp' => now()->toISOString(),
'services' => [
'database' => 'ok',
'redis' => 'ok',
'storage' => is_writable(storage_path()) ? 'ok' : 'error',
]
]);
} catch (\Exception $e) {
return response()->json(['status' => 'unhealthy', 'error' => $e->getMessage()], 500);
}
});
7.2 日志管理
// config/logging.php — 生产配置
'channels' => [
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'warning',
'days' => 14,
],
'business' => [
'driver' => 'daily',
'path' => storage_path('logs/business.log'),
'level' => 'info',
'days' => 30,
],
],
7.3 监控检查清单
| 监控项 | 方法 | 告警条件 |
|---|---|---|
| 服务存活 | /health 接口 | 连续 3 次失败 |
| CPU 使用率 | 系统监控 | > 80% 持续 5 分钟 |
| 内存使用率 | 系统监控 | > 85% |
| 磁盘使用率 | 系统监控 | > 80% |
| MySQL 连接数 | SHOW STATUS | > 150 |
| MySQL 慢查询 | 慢查询日志 | > 10 条/小时 |
| Redis 内存 | INFO memory | > 80% maxmemory |
| 队列积压 | queue:monitor | > 100 条待处理 |
| Laravel 日志 | 日志文件 | ERROR 级别 |
| 响应时间 | Nginx access_log | P95 > 2s |
| SSL 证书 | 定期检查 | 到期前 30 天 |
八、安全加固
8.1 服务器
# 防火墙 — 仅开放必要端口
ufw allow 22/tcp # SSH
ufw allow 80/tcp # HTTP
ufw allow 443/tcp # HTTPS
ufw enable
# SSH 加固
# /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
8.2 Docker 安全
- 不使用 root 运行容器(PHP Dockerfile 已配置 www-data)
- MySQL/Redis 端口不暴露到公网(仅限 Docker 内部网络)
- 定期更新基础镜像
- 使用
.dockerignore排除敏感文件
8.3 应用安全
| 措施 | 配置 |
|---|---|
| HTTPS 强制 | Nginx redirect 80→443 |
| CORS | 限定域名白名单 |
| CSRF | Sanctum SPA 模式自动处理 |
| Rate Limiting | Laravel throttle 中间件 |
| SQL 注入 | Eloquent 参数绑定 |
| XSS | HTMLPurifier + CSP Header |
| 文件上传 | 白名单 + 大小限制 + 病毒扫描 |
| 敏感数据 | .env 不入 Git,密钥加密存储 |
8.4 SSL 证书
# Let's Encrypt 免费证书
apt install certbot python3-certbot-nginx
certbot --nginx -d admin.example.com -d api.example.com
# 自动续期
certbot renew --dry-run
# crontab
0 0 1 */2 * certbot renew --quiet
九、性能优化
9.1 Laravel 优化
# 配置/路由/视图缓存
php artisan config:cache
php artisan route:cache
php artisan view:cache
# Composer 自动加载优化
composer install --optimize-autoloader --no-dev
9.2 MySQL 优化
- 索引覆盖常用查询(见 02-database-design.md 索引策略)
- 开启查询缓存(Redis 层面)
- 大表分页使用 cursor 分页
- 定期 ANALYZE TABLE
9.3 Redis 缓存策略
| 缓存键 | TTL | 场景 |
|---|---|---|
| user:{id} | 30min | 用户信息 |
| menu:{role_id} | 60min | 角色菜单 |
| room:board:{store_id}:{date} | 5min | 房态看板 |
| config:{key} | 60min | 系统配置 |
| dict:{type} | 24h | 数据字典 |
9.4 前端优化
- Vite 构建:代码分割 + Tree Shaking
- 路由懒加载:按模块动态 import
- 图片压缩:WebP 格式 + CDN
- Gzip:Nginx 开启 gzip 压缩
十、目录结构(Docker 相关)
saas/
├── docker/
│ ├── nginx/
│ │ ├── conf.d/
│ │ │ └── default.conf
│ │ └── ssl/ # SSL 证书
│ ├── php/
│ │ ├── Dockerfile
│ │ └── php.ini
│ └── mysql/
│ ├── my.cnf
│ └── init/ # 初始化 SQL
├── scripts/
│ ├── backup-db.sh
│ ├── backup-files.sh
│ └── deploy.sh
├── docker-compose.yml
├── .env
└── .env.example