- 系统配置键 log_raw_requests:开启后,仅管理员账号的每次请求 在用量明细中保存客户端原始请求体与上游原始响应体 (流式含全部 SSE 事件),用于排障 - UsageLog 新增 raw_request / raw_response 字段(type:text) - AuthLLM 附带 user_role 供网关判断管理员 - gateway:10s TTL 缓存开关;streamResponse/bufferResponse 支持累积上游原始响应;recordUsage 填充原始字段 - 前端 SystemConfig 新增开关(会显著增加存储的提示) - 新增 doc/flow.md 网关调用流程示意图
74 lines
2.0 KiB
Go
74 lines
2.0 KiB
Go
package middleware
|
||
|
||
import (
|
||
"net/http"
|
||
"opencatd-open/internal/store"
|
||
"strings"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// keyPrefixLen 是 key_prefix 列的截断长度,必须与 api.go:459 的 keyValue[:12] 一致。
|
||
// 真实 key 为 sk-ot- + 48 位 hex(54 字符),故 12 位足够唯一。
|
||
const keyPrefixLen = 12
|
||
|
||
func AuthLLM(db *gorm.DB) gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
key := extractAPIKey(c.GetHeader("Authorization"))
|
||
|
||
// 区分「没传」和「传了但不对」,便于排查客户端配置。
|
||
if strings.TrimSpace(c.GetHeader("Authorization")) == "" {
|
||
unauthorized(c, "未提供认证信息")
|
||
return
|
||
}
|
||
// 长度不足时直接拒绝:避免下方 authToken[:12] 越界 panic 打崩进程。
|
||
if len(key) < keyPrefixLen {
|
||
unauthorized(c, "无效的API密钥")
|
||
return
|
||
}
|
||
|
||
var apiKey store.APIKey
|
||
if err := db.Where("key_prefix = ? AND status = ?", key[:keyPrefixLen], store.KeyStatusActive).First(&apiKey).Error; err != nil {
|
||
unauthorized(c, "无效的API密钥")
|
||
return
|
||
}
|
||
|
||
// Verify full key hash
|
||
if apiKey.KeyHash != store.HashAPIKey(key) {
|
||
unauthorized(c, "无效的API密钥")
|
||
return
|
||
}
|
||
|
||
// 附带用户角色(判断是否管理员,供原始请求/响应记录等管理能力使用)。
|
||
var user store.User
|
||
if err := db.First(&user, apiKey.UserID).Error; err != nil {
|
||
unauthorized(c, "无效的API密钥")
|
||
return
|
||
}
|
||
|
||
c.Set("api_key", &apiKey)
|
||
c.Set("user_id", apiKey.UserID)
|
||
c.Set("user_role", user.Role)
|
||
c.Next()
|
||
}
|
||
}
|
||
|
||
// extractAPIKey 从 Authorization 头取 Bearer token,兼容无 "Bearer " 前缀的直传。
|
||
func extractAPIKey(auth string) string {
|
||
auth = strings.TrimSpace(auth)
|
||
if strings.HasPrefix(auth, "Bearer ") {
|
||
return strings.TrimSpace(auth[len("Bearer "):])
|
||
}
|
||
return auth
|
||
}
|
||
|
||
func unauthorized(c *gin.Context, message string) {
|
||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||
"error": map[string]interface{}{
|
||
"message": message,
|
||
"type": "invalid_request_error",
|
||
},
|
||
})
|
||
}
|