P6 生产硬化阶段交付物(46 文件):
## 1. API Gateway 中间件链(services/api-gateway/internal/middleware/)
- circuit-breaker.go: gobreaker v2 熔断器(5s 窗口/50% 错误率/30s OPEN→HALF_OPEN)
- ratelimit.go: 令牌桶限流(sync.Map + cleanup goroutine,默认 100rps/20 burst)
- cors.go: CORS 中间件(CORS_ORIGINS 环境变量)
- recovery.go: panic 恢复 + uuid request_id
- security.go: 安全头 + 请求体 10MB 限制
- requestid.go: 请求 ID 注入
- health/health.go: /healthz + /readyz 健康检查
- main.go: 重写注册全部中间件链(Recovery→RequestID→CORS→Security→BodyLimit→RateLimit→CircuitBreaker→Auth)
## 2. 基础设施硬化(infra/)
- backup/backup-mysql.sh: MySQL 全量备份(mysqldump+gzip,按服务独立)
- backup/restore-mysql.sh: 恢复脚本
- backup/backup-cron.sh: cron 调度入口(5 服务批量备份)
- alertmanager/alertmanager.yml: 告警路由(webhook + 邮件示例)
- prometheus/rules.yml: 8 条告警规则(服务可用性/性能/资源 3 组)
- grafana/dashboards/microservices-overview.json: 4 panel 仪表盘
- grafana/provisioning/: 数据源和仪表盘 provisioning
- k8s/namespace.yaml: 4 命名空间(edu-system/services/monitoring/ingress)
- k8s/api-gateway-deployment.yaml: Deployment + Service 骨架
- chaos/experiments.yaml: 3 个 Litmus 混沌实验(pod-kill/network-latency/disk-fill)
- docker-compose.monitoring.yml: 监控栈 profile
- security/secrets.example.env: 8 项密钥占位符
- security/waf-rules.conf: ModSecurity WAF 规则骨架
## 3. 业务服务健康检查 + 优雅停机(5 个 NestJS 服务)
- services/{iam,core-edu,content,msg,classes}/src/shared/health/: /healthz + /readyz
- services/{iam,core-edu,content,msg,classes}/src/shared/lifecycle/: OnModuleInit + OnApplicationShutdown
## 4. Python 服务健康检查
- services/{ai,data-ana}/src/health/health.py: FastAPI APIRouter
## 5. 运维文档
- docs/architecture/runbooks/p6-hardening.md: P6 总览 Runbook(9 章节)
- docs/architecture/runbooks/incident-response.md: 事件响应手册(5 章节)
- docs/architecture/004-p6-addendum.md: 004 架构补记 P6 章节
- docs/troubleshooting/known-issues-p6-addendum.md: 15 条 P6 场景→技术映射
## 验收信号
- RPO ≤ 15min(MySQL 备份 + binlog PITR)
- RTO ≤ 30min(K8s 滚动更新 + DNS 切换)
- P99 ≤ 500ms(熔断 + 限流 + 缓存)
- 熔断器错误率 > 50% 触发 OPEN
- 限流 100rps/20 burst
- 备份保留 7 天
- 混沌实验每月 1 次
70 lines
1.8 KiB
Go
70 lines
1.8 KiB
Go
package middleware
|
||
|
||
import (
|
||
"net/http"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// corsMaxAge 预检缓存时长:12 小时
|
||
const corsMaxAge = 12 * 60 * 60
|
||
|
||
// CORS 返回跨域资源共享中间件。
|
||
// 允许来源从环境变量 CORS_ORIGINS 读取(逗号分隔,默认 *)。
|
||
// 允许方法:GET POST PUT DELETE OPTIONS PATCH
|
||
// 允许头:Authorization Content-Type X-Request-Id X-Trace-Id
|
||
// 暴露头:X-Request-Id X-Trace-Id
|
||
func CORS() gin.HandlerFunc {
|
||
allowed := parseCORSOrigins(os.Getenv("CORS_ORIGINS"))
|
||
|
||
return func(c *gin.Context) {
|
||
origin := c.GetHeader("Origin")
|
||
allowOrigin := ""
|
||
|
||
if len(allowed) == 0 {
|
||
// 未配置则默认允许所有来源
|
||
allowOrigin = "*"
|
||
} else if allowed[origin] {
|
||
allowOrigin = origin
|
||
}
|
||
|
||
if allowOrigin != "" {
|
||
h := c.Writer.Header()
|
||
h.Set("Access-Control-Allow-Origin", allowOrigin)
|
||
h.Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH")
|
||
h.Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Request-Id, X-Trace-Id")
|
||
h.Set("Access-Control-Expose-Headers", "X-Request-Id, X-Trace-Id")
|
||
h.Set("Access-Control-Max-Age", strconv.Itoa(corsMaxAge))
|
||
// 非通配来源需标注 Vary,便于缓存正确区分
|
||
if allowOrigin != "*" {
|
||
h.Add("Vary", "Origin")
|
||
}
|
||
}
|
||
|
||
// 预检请求直接返回 204
|
||
if c.Request.Method == http.MethodOptions {
|
||
c.AbortWithStatus(http.StatusNoContent)
|
||
return
|
||
}
|
||
c.Next()
|
||
}
|
||
}
|
||
|
||
// parseCORSOrigins 解析逗号分隔的来源列表为集合,空字符串返回空 map(表示通配 *)
|
||
func parseCORSOrigins(raw string) map[string]bool {
|
||
allowed := map[string]bool{}
|
||
if raw == "" {
|
||
return allowed
|
||
}
|
||
for _, o := range strings.Split(raw, ",") {
|
||
o = strings.TrimSpace(o)
|
||
if o != "" {
|
||
allowed[o] = true
|
||
}
|
||
}
|
||
return allowed
|
||
}
|