feat(api-gateway): 实现 W1-W8 网关硬化与 P2-P5 路由扩展

依据 coord-final-decisions §3.8 W1-W8 裁决与
president-final-rulings §2.15/§2.16/§2.19 完整实现网关硬化:

- W1/W2: 错误码 GW_ 前缀 + ActionState 信封响应体
- W3: 全量替换为 log/slog 结构化日志
- W4: /readyz 并行 ping 9 下游 + 软失败规则
- W5: 7 个业务 Prometheus 指标 + /metrics 端点
- W6: tracer 资源属性补全(name/version/env/host)
- W7: DevMode=true && ENV=production panic 防护
- W8: 保持共享 downstream 熔断

P2 RS256 升级:接入 shared-go/jwks.Fetcher(TTL 5min)。
P2.7+P3-P5 路由扩展:student/parent/messages/dashboard。
文档同步:README/01/02/known-issues,arch.db 已更新。
质量校验:go vet + build + test 均通过。
This commit is contained in:
SpecialX
2026-07-10 18:15:48 +08:00
parent 9e767b4e95
commit 4307f6b73c
20 changed files with 797 additions and 382 deletions

View File

@@ -5,6 +5,7 @@ import (
"sync"
"time"
"github.com/edu-cloud/api-gateway/internal/observability"
"github.com/gin-gonic/gin"
)
@@ -22,9 +23,12 @@ type rateLimiter struct {
buckets sync.Map // map[string]*bucket
}
// RateLimit 返回基于令牌桶的限流中间件(内存版,不依赖 Redis
// RateLimit 返回基于令牌桶的限流中间件(内存版,P6 迁 Redis
// 每个客户端 IP 独立桶,按 rps每秒令牌数补充最大容量为 burst。
// 超限返回 429 + JSON {"error":"rate_limited","retry_after":60}。
// 超限返回 429 + ActionState 信封W1/W2 裁决):
//
// {"success":false,"error":{"code":"GW_RATE_LIMITED","message":"..."},"retry_after":60}
//
// 内部启动 cleanup goroutine每 5 分钟清理 10 分钟未访问的桶。
func RateLimit(rps float64, burst int) gin.HandlerFunc {
rl := &rateLimiter{rps: rps, burst: burst}
@@ -47,9 +51,14 @@ func RateLimit(rps float64, burst int) gin.HandlerFunc {
// 令牌不足:拒绝
if b.tokens < 1 {
b.mu.Unlock()
observability.IncRateLimited(ip)
c.Header("Retry-After", "60")
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"error": "rate_limited",
"success": false,
"error": gin.H{
"code": "GW_RATE_LIMITED",
"message": "rate limit exceeded",
},
"retry_after": 60,
})
return