跳至主要内容

生產環境部署

本指南說明如何將花店系統部署到生產環境。

部署架構

分離部署(推薦)

                ┌─────────────────┐
│ 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)

  1. 建立 RDS 實例

    • Engine: PostgreSQL 16
    • Instance type: db.t3.medium
    • Storage: 100GB SSD
    • Multi-AZ: Yes(高可用性)
    • Backup retention: 30 days
  2. 建立資料庫

CREATE DATABASE florist;
CREATE USER florist WITH ENCRYPTED PASSWORD 'xxxxx';
GRANT ALL PRIVILEGES ON DATABASE florist TO florist;
  1. 執行 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 檔案版本控制
  • 配置檔案備份
  • 日誌檔案備份

災難復原

復原計畫

  1. 資料庫復原
psql florist < /backup/florist-20240120.sql
  1. 應用程式復原
sudo systemctl restart florist
  1. 驗證
curl https://api.florist.leandev.io/actuator/health

下一步