生產環境部署
本指南說明如何將花店系統部署到生產環境。
部署架構
分離部署(推薦)
┌─────────────────┐
│ Nginx (443) │ SSL 終止、靜態檔案
└────────┬────────┘
│
┌────────┴────────┐
│ │
┌───────▼──────┐ ┌──────▼────────┐
│ SPA (靜態) │ │ Spring Boot │
│ CDN 加速 │ │ (8080) │
└──────────────┘ └───────┬────────┘
│
┌───────▼────────┐
│ PostgreSQL │
│ (RDS) │
└────────────────┘
前端建置
1. 環境變數
建立 .env.production:
# API 基礎 URL
VITE_API_BASE_URL=https://api.florist.leandev.io
# 停用 Mock API
VITE_ENABLE_MOCK_API=false
# 啟用生產優化
VITE_ENV=production
2. 建置
cd app-web
# 安裝依賴
npm ci
# 建置(自動使用 .env.production)
npm run build
# 輸出在 dist/
3. 部署到 Nginx
# 複製檔案到伺服器
scp -r dist/* deploy@prod:/var/www/florist/
# 或使用 rsync
rsync -avz --delete dist/ deploy@prod:/var/www/florist/
Nginx 配置:
# /etc/nginx/sites-available/florist
server {
listen 443 ssl http2;
server_name florist.leandev.io;
# SSL 憑證
ssl_certificate /etc/letsencrypt/live/florist.leandev.io/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/florist.leandev.io/privkey.pem;
# 安全 Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
root /var/www/florist;
index index.html;
# Gzip 壓縮
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# SPA 路由
location / {
try_files $uri $uri/ /index.html;
}
# 靜態資源快取
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# API 反向代理
location /api/ {
proxy_pass http://localhost:8080/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# HTTP 重導向到 HTTPS
server {
listen 80;
server_name florist.leandev.io;
return 301 https://$server_name$request_uri;
}
重新載入 Nginx:
sudo nginx -t
sudo systemctl reload nginx
後端建置
1. 環境變數
建立 application-prod.yml:
spring:
datasource:
url: ${DATABASE_URL}
username: ${DATABASE_USERNAME}
password: ${DATABASE_PASSWORD}
hikari:
maximum-pool-size: 20
minimum-idle: 5
jpa:
show-sql: false
hibernate:
ddl-auto: validate # 生產環境使用 Flyway
properties:
hibernate:
format_sql: false
flyway:
enabled: true
locations: classpath:db/migration
logging:
level:
root: INFO
io.leandev.appfuse.app: INFO
file:
name: /var/log/florist/application.log
management:
endpoints:
web:
exposure:
include: health,metrics,info
endpoint:
health:
show-details: when-authorized
2. 建置
cd app-server
# 執行測試
./gradlew test
# 建置 JAR
./gradlew clean build
# 輸出在 build/libs/app-server-0.0.1-SNAPSHOT.jar
3. 部署
# 複製 JAR 到伺服器
scp build/libs/app-server-0.0.1-SNAPSHOT.jar deploy@prod:/opt/florist/
# SSH 到伺服器
ssh deploy@prod
# 建立 systemd 服務
sudo nano /etc/systemd/system/florist.service
Systemd 服務配置:
[Unit]
Description=Florist Management System
After=network.target
[Service]
Type=simple
User=florist
WorkingDirectory=/opt/florist
Environment="SPRING_PROFILES_ACTIVE=prod"
Environment="DATABASE_URL=jdbc:postgresql://localhost:5432/florist"
Environment="DATABASE_USERNAME=florist"
Environment="DATABASE_PASSWORD=xxxxx"
ExecStart=/usr/bin/java -Xmx2g -Xms512m \
-jar /opt/florist/app-server-0.0.1-SNAPSHOT.jar
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
啟動服務:
sudo systemctl daemon-reload
sudo systemctl enable florist
sudo systemctl start florist
sudo systemctl status florist
資料庫設定
PostgreSQL(AWS RDS)
-
建立 RDS 實例:
- Engine: PostgreSQL 16
- Instance type: db.t3.medium
- Storage: 100GB SSD
- Multi-AZ: Yes(高可用性)
- Backup retention: 30 days
-
建立資料庫:
CREATE DATABASE florist;
CREATE USER florist WITH ENCRYPTED PASSWORD 'xxxxx';
GRANT ALL PRIVILEGES ON DATABASE florist TO florist;
- 執行 Flyway 遷移:
./gradlew flywayMigrate \
-Pflyway.url=jdbc:postgresql://rds-endpoint:5432/florist \
-Pflyway.user=florist \
-Pflyway.password=xxxxx
監控與日誌
應用程式監控
使用 Spring Boot Actuator:
# 健康檢查
curl https://api.florist.leandev.io/actuator/health
# 應用程式指標
curl https://api.florist.leandev.io/actuator/metrics
日誌管理
集中化日誌(使用 ELK Stack 或 CloudWatch):
# application-prod.yml
logging:
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: /var/log/florist/application.log
max-size: 100MB
max-history: 30
告警
設定監控告警:
- 健康檢查失敗 - 立即通知
- CPU 使用率 > 80% - 警告
- 記憶體使用率 > 85% - 警告
- 錯誤率 > 1% - 警告
安全性檢查清單
應用程式安全
- HTTPS 強制啟用
- 安全 Headers 配置(HSTS、CSP、X-Frame-Options)
- CORS 限制僅允許特定來源
- SQL Injection 防護(使用參數化查詢)
- XSS 防護(輸入驗證、輸出編碼)
- CSRF 防護
- 密碼加密(bcrypt)
- JWT Token 安全配置
基礎設施安全
- 防火牆規則(僅開放 443、80)
- SSH 密鑰認證
- 定期安全更新
- 資料庫備份
- 日誌審計
效能優化
前端優化
- Gzip 壓縮
- 靜態資源快取
- Code Splitting
- CDN 加速
- 圖片優化(WebP)
後端優化
- 資料庫連線池
- 快取(Redis)
- JPA 查詢優化
- 資料庫索引
- 非同步處理
備份策略
資料庫備份
# 每日自動備份
0 2 * * * pg_dump florist > /backup/florist-$(date +\%Y\%m\%d).sql
# 保留 30 天
find /backup -name "florist-*.sql" -mtime +30 -delete
應用程式備份
- JAR 檔案版本控制
- 配置檔案備份
- 日誌檔案備份
災難復原
復原計畫
- 資料庫復原:
psql florist < /backup/florist-20240120.sql
- 應用程式復原:
sudo systemctl restart florist
- 驗證:
curl https://api.florist.leandev.io/actuator/health