feat(usage): 用量记录落库上游请求标识并支持按账户配置响应头名

- usage_logs 新增 upstream_request_id 列(迁移 232)与仅索引非空行的 CONCURRENTLY 部分索引(迁移 233,runner 登记失效索引清理)
- 账户 extra.upstream_request_id_header 指定要读取的上游响应头,只读该头;留空不记录;创建/更新/批量入口校验头名合法且不超过 64 字节
- ForwardResult/OpenAIForwardResult 携带 UpstreamHeaders,中央计费按账户解析落库,WS 轮次保持 NULL
- 管理端用量明细新增默认隐藏的"上游ID"列,带与请求ID列一致的复制按钮,并同步导出
- 创建/编辑账户弹窗新增"上游ID"输入(UpstreamRequestIdHeaderField),提示按接入类型与平台分条列出常见头名
- HelpTooltip 悬停模式下指针移入提示框时保持打开,便于选中复制提示内容
This commit is contained in:
feeeei
2026-09-03 18:02:15 +08:00
parent 5097b31457
commit de27905e88
62 changed files with 901 additions and 56 deletions
@@ -837,6 +837,10 @@ func (h *AccountHandler) Create(c *gin.Context) {
}
// base_rpm 输入校验:负值归零,超过 10000 截断
sanitizeExtraBaseRPM(req.Extra)
if err := service.ValidateUpstreamRequestIDHeaderExtra(req.Extra); err != nil {
response.ErrorFrom(c, err)
return
}
// 确定是否跳过混合渠道检查
skipCheck := req.ConfirmMixedChannelRisk != nil && *req.ConfirmMixedChannelRisk
@@ -970,6 +974,10 @@ func (h *AccountHandler) Update(c *gin.Context) {
}
// base_rpm 输入校验:负值归零,超过 10000 截断
sanitizeExtraBaseRPM(req.Extra)
if err := service.ValidateUpstreamRequestIDHeaderExtra(req.Extra); err != nil {
response.ErrorFrom(c, err)
return
}
// 确定是否跳过混合渠道检查
skipCheck := req.ConfirmMixedChannelRisk != nil && *req.ConfirmMixedChannelRisk
@@ -1424,6 +1432,10 @@ func (h *AccountHandler) ApplyOAuthCredentials(c *gin.Context) {
response.ErrorFrom(c, err)
return
}
if err := service.ValidateUpstreamRequestIDHeaderExtra(req.Extra); err != nil {
response.ErrorFrom(c, err)
return
}
// Drop SSO/password residue; re-auth must leave only OAuth tokens on disk.
req.Credentials = service.SanitizeStoredCredentials(existing.Platform, req.Credentials)
@@ -1897,6 +1909,15 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) {
// base_rpm 输入校验:负值归零,超过 10000 截断
sanitizeExtraBaseRPM(item.Extra)
if err := service.ValidateUpstreamRequestIDHeaderExtra(item.Extra); err != nil {
failed++
results = append(results, gin.H{
"name": item.Name,
"success": false,
"error": err.Error(),
})
continue
}
skipCheck := item.ConfirmMixedChannelRisk != nil && *item.ConfirmMixedChannelRisk
@@ -2091,6 +2112,10 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) {
}
// base_rpm 输入校验:负值归零,超过 10000 截断
sanitizeExtraBaseRPM(req.Extra)
if err := service.ValidateUpstreamRequestIDHeaderExtra(req.Extra); err != nil {
response.ErrorFrom(c, err)
return
}
// 确定是否跳过混合渠道检查
skipCheck := req.ConfirmMixedChannelRisk != nil && *req.ConfirmMixedChannelRisk
+1
View File
@@ -723,6 +723,7 @@ func UsageLogFromServiceAdmin(l *service.UsageLog) *AdminUsageLog {
UpstreamModelMismatch: l.UpstreamModelMismatch,
ChannelID: l.ChannelID,
ModelMappingChain: l.ModelMappingChain,
UpstreamRequestID: l.UpstreamRequestID,
BillingTier: l.BillingTier,
AccountRateMultiplier: l.AccountRateMultiplier,
AccountStatsCost: l.AccountStatsCost,
+2
View File
@@ -588,6 +588,8 @@ type AdminUsageLog struct {
ChannelID *int64 `json:"channel_id,omitempty"`
// ModelMappingChain 模型映射链,如 "a→b→c"
ModelMappingChain *string `json:"model_mapping_chain,omitempty"`
// UpstreamRequestID 是直接上游声明的请求标识,仅管理端可见。
UpstreamRequestID *string `json:"upstream_request_id,omitempty"`
// BillingTier 计费层级标签(per_request/image 模式)
BillingTier *string `json:"billing_tier,omitempty"`