# 宫中有喜 — 部署方案 ## 一、部署架构 ``` ┌─────────────────────────────────────────┐ │ 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 ```yaml 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 ```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 配置 ```ini ; 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 配置 ```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 配置 ```ini # 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) ```bash # .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 首次部署 ```bash # 1. 克隆代码 git clone /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 更新部署 ```bash # 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 零停机部署(可选) ```bash # 使用 Laravel 维护模式 docker compose exec php php artisan down --retry=60 # 执行部署步骤... docker compose exec php php artisan up ``` --- ## 六、备份策略 ### 6.1 数据库备份 ```bash #!/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 文件备份 ```bash #!/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 定时任务 ```bash # 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 健康检查 ```php // 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 日志管理 ```php // 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 服务器 ```bash # 防火墙 — 仅开放必要端口 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 证书 ```bash # 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 优化 ```bash # 配置/路由/视图缓存 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 ```