feat: 三协议互转网关 + 鉴权修复 + 管理端增强
后端 - 新增 proxy/convert 三协议(chat/messages/responses)请求、响应与 SSE 流式互转, 以 Chat 为中间模型;usage.go 统一提取三协议 token 用量(含单测) - gateway: 跨协议调度(渠道未声明客户端协议时转为渠道首选格式), streamResponse 按 \n\n 分块逐行转换直通,bufferResponse 转换失败时剥非 JSON 前缀 - gateway: 新增 SetUsageRecorder 注入异步用量记录器 - auth_llm: 修复 key_prefix 查询长度错配([:8] vs 存储的 [:12])导致全部 401; 修复长度 8-11 的 key 切片越界 panic;统一 unauthorized 响应 - usage: 日报表改为增量累加 upsert,避免多次 flush 互相清零;记录协议/错误码/时延等字段 - channel: 新增渠道并发槽 TryAcquire;健康检查支持可配置参数 - api: 新增 admin 渠道/模型/系统配置管理端点(旧端点保留兼容) 前端 - 新增渠道管理、模型管理、系统配置视图与 ChannelModelsDrawer - 新增 ui 基础组件(Button/Badge/Input/Modal)与 protocol.ts - 调整 Toast 样式、密钥页、路由菜单;dev 代理默认指向 3000 端口
This commit is contained in:
@@ -2,6 +2,7 @@ package usage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"opencatd-open/internal/dao"
|
||||
"opencatd-open/internal/store"
|
||||
@@ -11,16 +12,26 @@ import (
|
||||
|
||||
// Event represents a usage event to be recorded
|
||||
type Event struct {
|
||||
UserID uint64
|
||||
ModelName string
|
||||
ChannelID uint64
|
||||
PromptTokens int
|
||||
CompletionTokens int
|
||||
CacheReadTokens int
|
||||
Cost float64
|
||||
IsError bool
|
||||
IsCanceled bool
|
||||
RequestID string
|
||||
UserID uint64
|
||||
ModelName string
|
||||
ChannelID uint64
|
||||
PromptTokens int
|
||||
CompletionTokens int
|
||||
CacheReadTokens int
|
||||
CacheCreationTokens int
|
||||
Cost float64
|
||||
IsError bool
|
||||
IsCanceled bool
|
||||
RequestID string
|
||||
KeyID uint64
|
||||
Protocol string
|
||||
ErrorCode string
|
||||
LatencyMS int
|
||||
InputPrice float64
|
||||
OutputPrice float64
|
||||
CacheReadPrice float64
|
||||
TraceID string // TraceID for distributed tracing
|
||||
ModelID uint64 // Model ID from channel-model binding
|
||||
}
|
||||
|
||||
// Recorder handles async usage recording
|
||||
@@ -117,16 +128,31 @@ func (r *Recorder) flush(events []Event) {
|
||||
status = store.UsageStatusCanceled
|
||||
}
|
||||
|
||||
var errCode *string
|
||||
if e.ErrorCode != "" {
|
||||
errCode = &e.ErrorCode
|
||||
}
|
||||
|
||||
log := &store.UsageLog{
|
||||
UserID: e.UserID,
|
||||
ModelName: e.ModelName,
|
||||
ChannelID: e.ChannelID,
|
||||
InputTokens: int64(e.PromptTokens),
|
||||
OutputTokens: int64(e.CompletionTokens),
|
||||
CacheReadTokens: int64(e.CacheReadTokens),
|
||||
Cost: e.Cost,
|
||||
Status: status,
|
||||
RequestID: e.RequestID,
|
||||
UserID: e.UserID,
|
||||
KeyID: e.KeyID,
|
||||
ChannelID: e.ChannelID,
|
||||
ModelID: e.ModelID,
|
||||
ModelName: e.ModelName,
|
||||
Protocol: e.Protocol,
|
||||
InputTokens: int64(e.PromptTokens),
|
||||
OutputTokens: int64(e.CompletionTokens),
|
||||
CacheReadTokens: int64(e.CacheReadTokens),
|
||||
CacheCreationTokens: int64(e.CacheCreationTokens),
|
||||
InputPrice: e.InputPrice,
|
||||
OutputPrice: e.OutputPrice,
|
||||
CacheReadPrice: e.CacheReadPrice,
|
||||
Cost: e.Cost,
|
||||
LatencyMS: e.LatencyMS,
|
||||
Status: status,
|
||||
ErrorCode: errCode,
|
||||
RequestID: e.RequestID,
|
||||
TraceID: e.TraceID,
|
||||
}
|
||||
logs = append(logs, log)
|
||||
}
|
||||
@@ -136,5 +162,39 @@ func (r *Recorder) flush(events []Event) {
|
||||
log.Printf("Failed to batch create usage logs: %v", err)
|
||||
}
|
||||
|
||||
// Daily rollup for success and canceled requests
|
||||
dailyMap := make(map[string]*store.UsageDaily)
|
||||
for _, e := range events {
|
||||
if e.IsError {
|
||||
continue
|
||||
}
|
||||
date := time.Now().Format("2006-01-02")
|
||||
key := fmt.Sprintf("%d:%d:%s", e.UserID, e.ModelID, date)
|
||||
d := dailyMap[key]
|
||||
if d == nil {
|
||||
d = &store.UsageDaily{
|
||||
UserID: e.UserID,
|
||||
ModelID: e.ModelID,
|
||||
Date: date,
|
||||
Requests: 0,
|
||||
InputTokens: 0,
|
||||
OutputTokens: 0,
|
||||
CacheReadTokens: 0,
|
||||
Cost: 0,
|
||||
}
|
||||
dailyMap[key] = d
|
||||
}
|
||||
d.Requests++
|
||||
d.InputTokens += int64(e.PromptTokens)
|
||||
d.OutputTokens += int64(e.CompletionTokens)
|
||||
d.CacheReadTokens += int64(e.CacheReadTokens)
|
||||
d.Cost += e.Cost
|
||||
}
|
||||
for _, d := range dailyMap {
|
||||
if err := r.dailyDAO.UpsertDailyUsage(context.Background(), d); err != nil {
|
||||
log.Printf("Failed to upsert daily usage: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Flushed %d usage logs", len(logs))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user