Files
TakeoutSaaS.AdminApi/Document/05_部署运维.md
2025-12-01 13:26:05 +08:00

1011 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 外卖SaaS系统 - 部署运维
## 1. 环境要求
### 1.1 开发环境
- **.NET SDK**10.0 或更高版本
- **IDE**Visual Studio 2022 / JetBrains Rider / VS Code
- **数据库**PostgreSQL 16+
- **缓存**Redis 7.0+
- **消息队列**RabbitMQ 3.12+
- **Git**:版本控制
- **Docker Desktop**:容器化开发(可选)
### 1.2 生产环境
- **操作系统**Linux (Ubuntu 22.04 LTS / CentOS 8+)
- **运行时**.NET Runtime 10.0
- **Web服务器**Nginx 1.24+
- **数据库**PostgreSQL 16+ (主从复制)
- **缓存**Redis 7.0+ (哨兵模式)
- **消息队列**RabbitMQ 3.12+ (集群模式)
- **对象存储**MinIO / 阿里云OSS / 腾讯云COS
- **监控**Prometheus + Grafana
- **日志**ELK Stack (Elasticsearch + Logstash + Kibana)
### 1.3 硬件要求(生产环境)
- **应用服务器**4核8GB内存最低推荐8核16GB
- **数据库服务器**8核16GB内存SSD存储
- **Redis服务器**4核8GB内存
- **负载均衡器**2核4GB内存
## 2. 本地开发环境搭建
### 2.1 安装.NET SDK
```bash
# Windows
# 从官网下载安装https://dotnet.microsoft.com/download
# Linux (Ubuntu)
wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y dotnet-sdk-10.0
# 验证安装
dotnet --version
```
### 2.2 安装PostgreSQL
```bash
# Ubuntu
sudo apt-get update
sudo apt-get install -y postgresql-16 postgresql-contrib-16
# 启动服务
sudo systemctl start postgresql
sudo systemctl enable postgresql
# 创建数据库
sudo -u postgres psql
CREATE DATABASE takeout_saas;
CREATE USER takeout_user WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE takeout_saas TO takeout_user;
\q
```
### 2.3 安装Redis
```bash
# Ubuntu
sudo apt-get install -y redis-server
# 启动服务
sudo systemctl start redis-server
sudo systemctl enable redis-server
# 测试连接
redis-cli ping
```
### 2.4 安装RabbitMQ
```bash
# Ubuntu
sudo apt-get install -y rabbitmq-server
# 启动服务
sudo systemctl start rabbitmq-server
sudo systemctl enable rabbitmq-server
# 启用管理插件
sudo rabbitmq-plugins enable rabbitmq_management
# 创建用户
sudo rabbitmqctl add_user admin password
sudo rabbitmqctl set_user_tags admin administrator
sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
# 访问管理界面http://localhost:15672
```
### 2.5 使用Docker Compose推荐
```yaml
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:16
container_name: takeout_postgres
environment:
POSTGRES_DB: takeout_saas
POSTGRES_USER: takeout_user
POSTGRES_PASSWORD: your_password
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
container_name: takeout_redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
rabbitmq:
image: rabbitmq:3.12-management
container_name: takeout_rabbitmq
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: password
ports:
- "5672:5672"
- "15672:15672"
volumes:
- rabbitmq_data:/var/lib/rabbitmq
minio:
image: minio/minio:latest
container_name: takeout_minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: admin
MINIO_ROOT_PASSWORD: password123
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio_data:/data
volumes:
postgres_data:
redis_data:
rabbitmq_data:
minio_data:
```
```bash
# 启动所有服务
docker-compose up -d
# 查看服务状态
docker-compose ps
# 停止服务
docker-compose down
```
### 2.6 配置项目
```bash
# 克隆项目
git clone https://github.com/your-org/takeout-saas.git
cd takeout-saas
# 还原依赖
dotnet restore
# 配置appsettings.Development.json
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=takeout_saas;Username=takeout_user;Password=your_password"
},
"Redis": {
"Configuration": "localhost:6379"
},
"RabbitMQ": {
"Host": "localhost",
"Port": 5672,
"Username": "admin",
"Password": "password"
}
}
# 执行数据库迁移
cd src/TakeoutSaaS.Api
dotnet ef database update
# 运行项目
dotnet run
```
### 2.7 EF Core 迁移基线
现已内置 `dotnet-ef` 本地工具与设计时 DbContext 工厂,可直接在命令行生成/更新数据库。运行前可通过环境变量 `TAKEOUTSAAS_APP_CONNECTION``TAKEOUTSAAS_IDENTITY_CONNECTION` 覆盖默认连接串(默认指向本地 PostgreSQL
```powershell
# 业务主库TakeoutAppDbContext含租户/商户/门店/商品/订单等)
dotnet tool run dotnet-ef database update `
--project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
--startup-project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
--context TakeoutSaaS.Infrastructure.App.Persistence.TakeoutAppDbContext
# 身份库IdentityDbContext
dotnet tool run dotnet-ef database update `
--project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
--startup-project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
--context TakeoutSaaS.Infrastructure.Identity.Persistence.IdentityDbContext
# 业务/字典库DictionaryDbContext归属 AppDatabase
dotnet tool run dotnet-ef database update `
--project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
--startup-project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
--context TakeoutSaaS.Infrastructure.Dictionary.Persistence.DictionaryDbContext
```
> Hangfire 使用 Scheduler.ConnectionString 指向的数据库,首次启动服务会自动建表;只需提前创建空数据库并授予账号权限。
## 3. Docker部署
### 3.1 创建Dockerfile
```dockerfile
# Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["src/TakeoutSaaS.Api/TakeoutSaaS.Api.csproj", "src/TakeoutSaaS.Api/"]
COPY ["src/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj", "src/TakeoutSaaS.Application/"]
COPY ["src/TakeoutSaaS.Domain/TakeoutSaaS.Domain.csproj", "src/TakeoutSaaS.Domain/"]
COPY ["src/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj", "src/TakeoutSaaS.Infrastructure/"]
COPY ["src/TakeoutSaaS.Shared/TakeoutSaaS.Shared.csproj", "src/TakeoutSaaS.Shared/"]
RUN dotnet restore "src/TakeoutSaaS.Api/TakeoutSaaS.Api.csproj"
COPY . .
WORKDIR "/src/src/TakeoutSaaS.Api"
RUN dotnet build "TakeoutSaaS.Api.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "TakeoutSaaS.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "TakeoutSaaS.Api.dll"]
```
### 3.2 构建镜像
```bash
# 构建镜像
docker build -t takeout-saas-api:latest .
# 查看镜像
docker images | grep takeout-saas
# 运行容器
docker run -d \
--name takeout-api \
-p 8080:80 \
-e ASPNETCORE_ENVIRONMENT=Production \
-e ConnectionStrings__DefaultConnection="Host=postgres;Port=5432;Database=takeout_saas;Username=takeout_user;Password=your_password" \
takeout-saas-api:latest
```
### 3.3 生产环境Docker Compose
```yaml
# docker-compose.prod.yml
version: '3.8'
services:
api:
image: takeout-saas-api:latest
container_name: takeout_api
restart: always
environment:
ASPNETCORE_ENVIRONMENT: Production
ConnectionStrings__DefaultConnection: "Host=postgres;Port=5432;Database=takeout_saas;Username=takeout_user;Password=${DB_PASSWORD}"
Redis__Configuration: "redis:6379"
RabbitMQ__Host: "rabbitmq"
ports:
- "8080:80"
depends_on:
- postgres
- redis
- rabbitmq
networks:
- takeout_network
nginx:
image: nginx:latest
container_name: takeout_nginx
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/ssl:/etc/nginx/ssl
depends_on:
- api
networks:
- takeout_network
networks:
takeout_network:
driver: bridge
```
## 4. Nginx配置
### 4.1 基础配置
```nginx
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml font/truetype font/opentype
application/vnd.ms-fontobject image/svg+xml;
# 限流配置
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
include /etc/nginx/conf.d/*.conf;
}
```
### 4.2 API服务配置
```nginx
# /etc/nginx/conf.d/api.conf
upstream api_backend {
least_conn;
server api1:80 weight=1 max_fails=3 fail_timeout=30s;
server api2:80 weight=1 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 80;
server_name api.example.com;
# 重定向到HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
# SSL证书配置
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全头
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 Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 客户端请求体大小限制
client_max_body_size 10M;
# API接口
location /api/ {
# 限流
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 10;
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲设置
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
}
# WebSocket
location /ws {
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# WebSocket超时
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
# 健康检查
location /health {
proxy_pass http://api_backend;
access_log off;
}
# 静态文件缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://api_backend;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
```
## 5. 数据库部署
### 5.1 PostgreSQL主从复制
```bash
# 主库配置 (postgresql.conf)
listen_addresses = '*'
wal_level = replica
max_wal_senders = 10
wal_keep_size = 64MB
hot_standby = on
# 主库配置 (pg_hba.conf)
host replication replicator 192.168.1.0/24 md5
# 创建复制用户
CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'repl_password';
# 从库配置
# 1. 停止从库
sudo systemctl stop postgresql
# 2. 清空从库数据目录
rm -rf /var/lib/postgresql/16/main/*
# 3. 从主库复制数据
pg_basebackup -h master_ip -D /var/lib/postgresql/16/main -U replicator -P -v -R -X stream -C -S replica1
# 4. 启动从库
sudo systemctl start postgresql
# 5. 验证复制状态
# 主库执行
SELECT * FROM pg_stat_replication;
```
### 5.2 数据库备份脚本
```bash
#!/bin/bash
# backup_db.sh
BACKUP_DIR="/backup/postgres"
DATE=$(date +%Y%m%d_%H%M%S)
DB_NAME="takeout_saas"
DB_USER="takeout_user"
RETENTION_DAYS=30
# 创建备份目录
mkdir -p $BACKUP_DIR
# 全量备份
pg_dump -h localhost -U $DB_USER -d $DB_NAME -F c -f $BACKUP_DIR/full_$DATE.dump
# 压缩备份
gzip $BACKUP_DIR/full_$DATE.dump
# 删除过期备份
find $BACKUP_DIR -name "full_*.dump.gz" -mtime +$RETENTION_DAYS -delete
# 上传到对象存储(可选)
# aws s3 cp $BACKUP_DIR/full_$DATE.dump.gz s3://your-bucket/backups/
echo "Backup completed: full_$DATE.dump.gz"
```
### 5.3 定时备份Crontab
```bash
# 编辑crontab
crontab -e
# 每天凌晨2点执行备份
0 2 * * * /path/to/backup_db.sh >> /var/log/backup.log 2>&1
```
## TODO基础设施部署脚本
- [ ] PostgreSQL 主从:整理主库/从库初始化脚本、basebackup 步骤与故障切换手册。
- [ ] Redis 哨兵/集群:补充 redis.conf/sentinel.conf 模板以及一主两从搭建命令。
- [ ] RabbitMQ编写单节点到镜像队列的安装脚本记录 VHost、用户、权限、监控等操作。
- [ ] 腾讯云 COS整理桶创建、ACL、CDN 绑定与密钥轮换流程,并提供 coscmd/SDK 示例。
- [ ] Hangfire 存储:确认 PostgreSQL Schema 初始化脚本,补充定期备份、清理、监控的 SOP。
## 6. Redis部署
### 6.1 Redis哨兵模式
```bash
# redis.conf (主节点)
bind 0.0.0.0
port 6379
requirepass your_password
masterauth your_password
# sentinel.conf
port 26379
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel auth-pass mymaster your_password
sentinel down-after-milliseconds mymaster 5000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 10000
```
### 6.2 Redis持久化配置
```bash
# redis.conf
# RDB持久化
save 900 1
save 300 10
save 60 10000
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
# AOF持久化
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
```
## 7. CI/CD配置
### 7.1 GitHub Actions
```yaml
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '10.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
- name: Publish
run: dotnet publish src/TakeoutSaaS.Api/TakeoutSaaS.Api.csproj -c Release -o ./publish
- name: Build Docker image
run: |
docker build -t takeout-saas-api:${{ github.sha }} .
docker tag takeout-saas-api:${{ github.sha }} takeout-saas-api:latest
- name: Push to Registry
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push takeout-saas-api:${{ github.sha }}
docker push takeout-saas-api:latest
- name: Deploy to Server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/takeout-saas
docker-compose pull
docker-compose up -d
docker system prune -f
```
### 7.2 GitLab CI
```yaml
# .gitlab-ci.yml
stages:
- build
- test
- deploy
variables:
DOCKER_IMAGE: registry.example.com/takeout-saas-api
build:
stage: build
image: mcr.microsoft.com/dotnet/sdk:10.0
script:
- dotnet restore
- dotnet build --configuration Release
artifacts:
paths:
- src/*/bin/Release/
test:
stage: test
image: mcr.microsoft.com/dotnet/sdk:10.0
script:
- dotnet test --configuration Release
deploy:
stage: deploy
image: docker:latest
services:
- docker:dind
script:
- docker build -t $DOCKER_IMAGE:$CI_COMMIT_SHA .
- docker tag $DOCKER_IMAGE:$CI_COMMIT_SHA $DOCKER_IMAGE:latest
- docker push $DOCKER_IMAGE:$CI_COMMIT_SHA
- docker push $DOCKER_IMAGE:latest
only:
- main
```
## 8. 监控告警
### 8.1 Prometheus配置
```yaml
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'takeout-api'
static_configs:
- targets: ['api:80']
metrics_path: '/metrics'
- job_name: 'postgres'
static_configs:
- targets: ['postgres-exporter:9187']
- job_name: 'redis'
static_configs:
- targets: ['redis-exporter:9121']
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']
```
### 8.2 应用监控指标
```csharp
// Program.cs - 添加Prometheus监控
builder.Services.AddPrometheusMetrics();
app.UseMetricServer(); // /metrics端点
app.UseHttpMetrics(); // HTTP请求指标
// 自定义指标
public class MetricsService
{
private static readonly Counter OrderCreatedCounter = Metrics
.CreateCounter("orders_created_total", "Total orders created");
private static readonly Histogram OrderProcessingDuration = Metrics
.CreateHistogram("order_processing_duration_seconds", "Order processing duration");
public void RecordOrderCreated()
{
OrderCreatedCounter.Inc();
}
public IDisposable MeasureOrderProcessing()
{
return OrderProcessingDuration.NewTimer();
}
}
```
### 8.3 Grafana仪表板
```json
{
"dashboard": {
"title": "外卖SaaS系统监控",
"panels": [
{
"title": "API请求速率",
"targets": [
{
"expr": "rate(http_requests_total[5m])"
}
]
},
{
"title": "订单创建数",
"targets": [
{
"expr": "increase(orders_created_total[1h])"
}
]
},
{
"title": "数据库连接数",
"targets": [
{
"expr": "pg_stat_activity_count"
}
]
}
]
}
}
```
### 8.4 告警规则
```yaml
# alert.rules.yml
groups:
- name: takeout_alerts
interval: 30s
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "高错误率告警"
description: "API错误率超过5%"
- alert: DatabaseDown
expr: up{job="postgres"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "数据库宕机"
description: "PostgreSQL数据库不可用"
- alert: HighMemoryUsage
expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes > 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "内存使用率过高"
description: "内存使用率超过90%"
```
## 9. 日志管理
### 9.1 Serilog配置
```json
{
"Serilog": {
"Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Elasticsearch"],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "logs/log-.txt",
"rollingInterval": "Day",
"retainedFileCountLimit": 30
}
},
{
"Name": "Elasticsearch",
"Args": {
"nodeUris": "http://elasticsearch:9200",
"indexFormat": "takeout-logs-{0:yyyy.MM.dd}",
"autoRegisterTemplate": true
}
}
],
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"]
}
}
```
### 9.2 ELK Stack部署
```yaml
# docker-compose.elk.yml
version: '3.8'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
volumes:
- es_data:/usr/share/elasticsearch/data
logstash:
image: docker.elastic.co/logstash/logstash:8.11.0
volumes:
- ./logstash/pipeline:/usr/share/logstash/pipeline
ports:
- "5044:5044"
depends_on:
- elasticsearch
kibana:
image: docker.elastic.co/kibana/kibana:8.11.0
ports:
- "5601:5601"
environment:
ELASTICSEARCH_HOSTS: http://elasticsearch:9200
depends_on:
- elasticsearch
volumes:
es_data:
```
## 10. 安全加固
### 10.1 防火墙配置
```bash
# UFW防火墙
sudo ufw enable
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw deny 5432/tcp # 禁止外部访问数据库
sudo ufw deny 6379/tcp # 禁止外部访问Redis
```
### 10.2 SSL证书Let's Encrypt
```bash
# 安装Certbot
sudo apt-get install certbot python3-certbot-nginx
# 获取证书
sudo certbot --nginx -d api.example.com
# 自动续期
sudo certbot renew --dry-run
# 添加定时任务
0 3 * * * certbot renew --quiet
```
### 10.3 应用安全配置
```csharp
// Program.cs
builder.Services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromDays(365);
options.IncludeSubDomains = true;
options.Preload = true;
});
builder.Services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status308PermanentRedirect;
options.HttpsPort = 443;
});
// 添加安全头
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
context.Response.Headers.Add("Referrer-Policy", "no-referrer");
await next();
});
```
## 11. 性能优化
### 11.1 数据库连接池
```json
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=takeout_saas;Username=user;Password=pass;Pooling=true;MinPoolSize=5;MaxPoolSize=100;ConnectionLifetime=300"
}
}
```
### 11.2 Redis连接池
```csharp
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = configuration["Redis:Configuration"];
options.InstanceName = "TakeoutSaaS:";
});
```
### 11.3 响应压缩
```csharp
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<BrotliCompressionProvider>();
});
```
## 12. 故障恢复
### 12.1 数据库恢复
```bash
# 从备份恢复
pg_restore -h localhost -U takeout_user -d takeout_saas -v /backup/full_20240101.dump
# PITR恢复到指定时间点
# 1. 停止数据库
sudo systemctl stop postgresql
# 2. 恢复基础备份
rm -rf /var/lib/postgresql/16/main/*
tar -xzf /backup/base_backup.tar.gz -C /var/lib/postgresql/16/main/
# 3. 配置recovery.conf
restore_command = 'cp /backup/wal_archive/%f %p'
recovery_target_time = '2024-01-01 12:00:00'
# 4. 启动数据库
sudo systemctl start postgresql
```
### 12.2 应用回滚
```bash
# Docker回滚到上一个版本
docker-compose down
docker-compose up -d --force-recreate --no-deps api
# 或使用特定版本
docker pull takeout-saas-api:previous-version
docker-compose up -d
```