Files
sundynix-agentix/sundynix-gateway/internal/middleware/auth.go
T
Blizzard 7bfea74cc0 feat(perf): 容量压测器 + 平台天花板实测曲线
cmd/loadtest:闭环加压器,阶梯并发,经 SSE 流检测完成(非轮询,避免轮询放大把网关
压成假瓶颈),输出吞吐 + 延迟分位。配套两个 benchmark 开关:
- dispatcher LLM_FORCE_STUB=1 + LLM_STUB_TTFT_MS/INTERTOKEN_MS=0:绕开真实 LLM 推理,
  量平台自身全链路天花板(不烧 token、不被模型节奏掩盖)。
- gateway RATE_LIMIT_PER_MIN 可配(缺省 120):压测放开限流。

实测(单 dispatcher、并发64、stub):
- 单任务纯平台开销 ~42ms;吞吐峰值 ~110 全链路任务/秒(饱和点 并发32–64);
  并发128 优雅降速,256 硬崩。
- 吞吐瓶颈不是 DB 连接数(池 25→80 吞吐不变),是每任务多跳管线综合成本;
  256 崩根因为 DSN 用 localhost、pgx 每连接解析 → 连接churn致 DNS 取消(易修)。
- 关键判断:平台 42ms ≪ LLM 秒级出答案,平台不是瓶颈、GPU 才是;横向拆服务喂满 GPU 集群
  的意义成立。细节与曲线见 project_analysis「容量实测」。

stub 延迟改 env 可配(默认值不变);不影响线上路径。dispatcher+gateway build/vet/test 全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 11:46:50 +08:00

97 lines
2.7 KiB
Go
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.
package middleware
import (
"net/http"
"os"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/sundynix/sundynix-gateway/internal/auth"
)
// CtxUserID 是鉴权后写入 gin.Context 的已验证用户 ID 键。
const CtxUserID = "uid"
// Auth 解析 Authorization: Bearer <JWT>,校验通过则把已验证 userID 写入上下文。
// 非阻断:无 token / 无效 token 时不报错,由各 handler(经 userID 兜底 header)或
// 后续 RequireAuth 决定是否放行。
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
h := c.GetHeader("Authorization")
if strings.HasPrefix(h, "Bearer ") {
if uid, err := auth.Parse(strings.TrimSpace(h[len("Bearer "):])); err == nil {
c.Set(CtxUserID, uid)
}
}
c.Next()
}
}
// RequireAuth 在 Auth 之后使用:上下文无已验证 userID 则 401 拒绝。
// 用于 owner 作用域的业务路由;SSE/导出等按 task_id 寻址的端点不挂(EventSource 无法带头)。
func RequireAuth() gin.HandlerFunc {
return func(c *gin.Context) {
if v, ok := c.Get(CtxUserID); ok {
if s, _ := v.(string); s != "" {
c.Next()
return
}
}
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "需要登录"})
}
}
// RequireAdmin 保护运维控制面:必须登录,且(设了 ADMIN_USER_IDS 时)uid 须在白名单内。
// ADMIN_USER_IDS 为空:开发期放行任意登录用户;生产期(APP_ENV=prod/GIN_MODE=release)直接拒绝
// ——逼运维显式配置管理员,杜绝"任意账号改模型/密钥配置"。
func RequireAdmin() gin.HandlerFunc {
allow := splitEnv("ADMIN_USER_IDS")
prod := strings.EqualFold(os.Getenv("APP_ENV"), "production") || strings.EqualFold(os.Getenv("APP_ENV"), "prod") ||
strings.EqualFold(os.Getenv("GIN_MODE"), "release")
return func(c *gin.Context) {
uid, _ := c.Get(CtxUserID)
id, _ := uid.(string)
if id == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "需要登录"})
return
}
if len(allow) == 0 {
if prod {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "未配置管理员(ADMIN_USER_IDS"})
return
}
c.Next() // 开发期放行
return
}
for _, a := range allow {
if a == id {
c.Next()
return
}
}
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "需要管理员权限"})
}
}
func splitEnv(key string) []string {
var out []string
for _, p := range strings.Split(os.Getenv(key), ",") {
if p = strings.TrimSpace(p); p != "" {
out = append(out, p)
}
}
return out
}
// envInt 读正整数环境变量,缺省回退 def。
func envInt(key string, def int) int {
if v := os.Getenv(key); v != "" {
if n, err := strconv.Atoi(v); err == nil && n > 0 {
return n
}
}
return def
}