# 外卖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 应用监控指标(OpenTelemetry + Prometheus Exporter) ```csharp // Program.cs - 指标与探针 builder.Services.AddHealthChecks(); builder.Services.AddOpenTelemetry() .WithMetrics(metrics => { metrics .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddRuntimeInstrumentation() .AddPrometheusExporter(); // /metrics }); var app = builder.Build(); app.MapHealthChecks("/healthz"); // 存活/就绪探针 app.MapPrometheusScrapingEndpoint(); // 默认 /metrics ``` 自定义业务指标(使用 `System.Diagnostics.Metrics`,由 Prometheus Exporter 暴露): ```csharp internal static class BusinessMetrics { private static readonly Meter Meter = new("TakeoutSaaS.App", "1.0.0"); public static readonly Counter OrdersCreated = Meter.CreateCounter("orders_created_total", "个", "订单创建计数"); public static readonly Histogram OrderProcessingSeconds = Meter.CreateHistogram("order_processing_duration_seconds", "s", "订单处理耗时"); } ``` Prometheus 抓取示例:见 `deploy/prometheus/prometheus.yml`,默认拉取 `/metrics`,告警规则见 `deploy/prometheus/alert.rules.yml`。 ### 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(); options.Providers.Add(); }); ``` ## 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 ``` ## 13. 网关 TakeoutSaaS.ApiGateway 部署 1. **部署拓扑** - Nginx 负责域名 `kjkj.qiyuesns.cn`(含后续 HTTPS 证书),并将所有流量反代到本机 `http://127.0.0.1:5000`。 - .NET 网关容器(TakeoutSaaS.ApiGateway)负责 YARP 路由、限流、日志与 OpenTelemetry 埋点,向下游 49.7.179.246 的 Admin/User/Mini API 转发。 2. **构建与运行** ```bash # 构建镜像 docker build -f src/Gateway/TakeoutSaaS.ApiGateway/Dockerfile -t takeoutsaas/apigateway:latest . # 启动容器(生产环境建议挂载独立配置) docker run -d --name takeout-gateway -p 5000:5000 ^ -e ASPNETCORE_ENVIRONMENT=Production ^ -v /opt/takeoutsaas/gateway/appsettings.Production.json:/app/appsettings.Production.json ^ takeoutsaas/apigateway:latest ``` - `appsettings.json` 默认将 `/api/admin|mini|user/**` 指向主应用服务器(7801/7701/7901 端口)。 - `appsettings.Development.json` 可在本地覆盖为 `localhost:5001/5002/5003`,无需改代码。 3. **Nginx 参考配置** ```nginx server { listen 80; server_name kjkj.qiyuesns.cn; location / { proxy_pass http://127.0.0.1:5000; 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; } } ``` - 后续启用 HTTPS 时,将 `listen 443 ssl` 与证书配置加入同一 `server`,其余保持不变。 4. **关键配置项说明** - `Gateway:RateLimiting`:按客户端 IP 固定窗口限流(默认 300 次/60 秒),可通过配置文件调整或关闭。 - `ReverseProxy`:集中声明路由规则(`/api/{service}/**`),后端地址变更时只需改配置即可。 - `OpenTelemetry`:默认开启 OTLP 导出,Collector 地址通过 `OpenTelemetry:OtlpEndpoint` 指定。 - `Serilog`:统一输出到控制台,日志采集器可以直接收集 Docker stdout。 5. **健康检查** - `GET /healthz`:基础健康,用于探活或监控告警。 - `GET /`:返回服务元信息,可作为简易诊断接口。