perf(backend): 使用 gjson/sjson 优化热路径 JSON 处理
将 API 网关热路径中的 json.Unmarshal+json.Marshal 替换为 gjson 零拷贝查询和 sjson 精准写入: - unwrapV1InternalResponse 性能提升 22x(4009ns→182ns),内存分配减少 28.5x - unwrapGeminiResponse、extractGeminiUsage、estimateGeminiCountTokens、ParseGeminiRateLimitResetTime 改为接收 []byte 使用 gjson 提取 - ParseGatewayRequest 的 model/stream/metadata/thinking/max_tokens 改用 gjson 类型安全提取 - Handler 层(sora/openai)改用 gjson 提取字段、sjson 注入/修改字段,移除 map[string]any 中间变量 - Sora Client 响应解析改用 gjson ForEach 遍历,减少内存分配 - 新增约 100 个单元测试用例,所有改动函数覆盖率 >85% Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/domain"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// SessionContext 粘性会话上下文,用于区分不同来源的请求。
|
||||
@@ -48,38 +49,58 @@ type ParsedRequest struct {
|
||||
// protocol 指定请求协议格式(domain.PlatformAnthropic / domain.PlatformGemini),
|
||||
// 不同协议使用不同的 system/messages 字段名。
|
||||
func ParseGatewayRequest(body []byte, protocol string) (*ParsedRequest, error) {
|
||||
var req map[string]any
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsed := &ParsedRequest{
|
||||
Body: body,
|
||||
}
|
||||
|
||||
if rawModel, exists := req["model"]; exists {
|
||||
model, ok := rawModel.(string)
|
||||
if !ok {
|
||||
// --- gjson 提取简单字段(避免完整 Unmarshal) ---
|
||||
|
||||
// model: 需要严格类型校验,非 string 返回错误
|
||||
modelResult := gjson.GetBytes(body, "model")
|
||||
if modelResult.Exists() {
|
||||
if modelResult.Type != gjson.String {
|
||||
return nil, fmt.Errorf("invalid model field type")
|
||||
}
|
||||
parsed.Model = model
|
||||
parsed.Model = modelResult.String()
|
||||
}
|
||||
if rawStream, exists := req["stream"]; exists {
|
||||
stream, ok := rawStream.(bool)
|
||||
if !ok {
|
||||
|
||||
// stream: 需要严格类型校验,非 bool 返回错误
|
||||
streamResult := gjson.GetBytes(body, "stream")
|
||||
if streamResult.Exists() {
|
||||
if streamResult.Type != gjson.True && streamResult.Type != gjson.False {
|
||||
return nil, fmt.Errorf("invalid stream field type")
|
||||
}
|
||||
parsed.Stream = stream
|
||||
parsed.Stream = streamResult.Bool()
|
||||
}
|
||||
if metadata, ok := req["metadata"].(map[string]any); ok {
|
||||
if userID, ok := metadata["user_id"].(string); ok {
|
||||
parsed.MetadataUserID = userID
|
||||
|
||||
// metadata.user_id: 直接路径提取,不需要严格类型校验
|
||||
parsed.MetadataUserID = gjson.GetBytes(body, "metadata.user_id").String()
|
||||
|
||||
// thinking.type: 直接路径提取
|
||||
if gjson.GetBytes(body, "thinking.type").String() == "enabled" {
|
||||
parsed.ThinkingEnabled = true
|
||||
}
|
||||
|
||||
// max_tokens: 仅接受整数值
|
||||
maxTokensResult := gjson.GetBytes(body, "max_tokens")
|
||||
if maxTokensResult.Exists() && maxTokensResult.Type == gjson.Number {
|
||||
f := maxTokensResult.Float()
|
||||
if !math.IsNaN(f) && !math.IsInf(f, 0) && f == math.Trunc(f) &&
|
||||
f <= float64(math.MaxInt) && f >= float64(math.MinInt) {
|
||||
parsed.MaxTokens = int(f)
|
||||
}
|
||||
}
|
||||
|
||||
// --- 保留 Unmarshal 用于 system/messages 提取 ---
|
||||
// 这些字段需要作为 any/[]any 传递给下游消费者,无法用 gjson 替代
|
||||
|
||||
switch protocol {
|
||||
case domain.PlatformGemini:
|
||||
// Gemini 原生格式: systemInstruction.parts / contents
|
||||
var req map[string]any
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sysInst, ok := req["systemInstruction"].(map[string]any); ok {
|
||||
if parts, ok := sysInst["parts"].([]any); ok {
|
||||
parsed.System = parts
|
||||
@@ -92,6 +113,10 @@ func ParseGatewayRequest(body []byte, protocol string) (*ParsedRequest, error) {
|
||||
// Anthropic / OpenAI 格式: system / messages
|
||||
// system 字段只要存在就视为显式提供(即使为 null),
|
||||
// 以避免客户端传 null 时被默认 system 误注入。
|
||||
var req map[string]any
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if system, ok := req["system"]; ok {
|
||||
parsed.HasSystem = true
|
||||
parsed.System = system
|
||||
@@ -101,20 +126,6 @@ func ParseGatewayRequest(body []byte, protocol string) (*ParsedRequest, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// thinking: {type: "enabled"}
|
||||
if rawThinking, ok := req["thinking"].(map[string]any); ok {
|
||||
if t, ok := rawThinking["type"].(string); ok && t == "enabled" {
|
||||
parsed.ThinkingEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
// max_tokens
|
||||
if rawMaxTokens, exists := req["max_tokens"]; exists {
|
||||
if maxTokens, ok := parseIntegralNumber(rawMaxTokens); ok {
|
||||
parsed.MaxTokens = maxTokens
|
||||
}
|
||||
}
|
||||
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user