feat(channel): 缓存扁平化 + 网关映射集成 + 计费模式统一 + 模型限制
- 缓存重构为 O(1) 哈希结构 (pricingByGroupModel, mappingByGroupModel) - 渠道模型映射接入网关流程 (Forward 前应用, a→b→c 映射链) - 新增 billing_model_source 配置 (请求模型/最终模型计费) - usage_logs 新增 channel_id, model_mapping_chain, billing_tier 字段 - 每种计费模式统一支持默认价格 + 区间定价 - 渠道模型限制开关 (restrict_models) - 分组按平台分类展示 + 彩色图标 - 必填字段红色星号 + 模型映射 UI - 去除模型通配符支持
This commit is contained in:
@@ -158,6 +158,24 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
|
||||
reqStream := parsedReq.Stream
|
||||
reqLog = reqLog.With(zap.String("model", reqModel), zap.Bool("stream", reqStream))
|
||||
|
||||
// 解析渠道级模型映射
|
||||
var channelMapping service.ChannelMappingResult
|
||||
if apiKey.GroupID != nil {
|
||||
channelMapping = h.gatewayService.ResolveChannelMapping(c.Request.Context(), *apiKey.GroupID, reqModel)
|
||||
}
|
||||
|
||||
// 渠道模型限制检查
|
||||
if apiKey.GroupID != nil {
|
||||
checkModel := reqModel
|
||||
if channelMapping.Mapped {
|
||||
checkModel = channelMapping.MappedModel
|
||||
}
|
||||
if h.gatewayService.IsModelRestricted(c.Request.Context(), *apiKey.GroupID, checkModel) {
|
||||
h.errorResponse(c, http.StatusForbidden, "invalid_request_error", "Model not available in current channel: "+reqModel)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 设置 max_tokens=1 + haiku 探测请求标识到 context 中
|
||||
// 必须在 SetClaudeCodeClientContext 之前设置,因为 ClaudeCodeValidator 需要读取此标识进行绕过判断
|
||||
if isMaxTokensOneHaikuRequest(reqModel, parsedReq.MaxTokens, reqStream) {
|
||||
@@ -478,6 +496,21 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
|
||||
RequestPayloadHash: requestPayloadHash,
|
||||
ForceCacheBilling: fs.ForceCacheBilling,
|
||||
APIKeyService: h.apiKeyService,
|
||||
ChannelID: channelMapping.ChannelID,
|
||||
OriginalModel: reqModel,
|
||||
BillingModelSource: channelMapping.BillingModelSource,
|
||||
ModelMappingChain: func() string {
|
||||
if !channelMapping.Mapped {
|
||||
if result.UpstreamModel != "" && result.UpstreamModel != result.Model {
|
||||
return reqModel + "→" + result.UpstreamModel
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if result.UpstreamModel != "" && result.UpstreamModel != channelMapping.MappedModel {
|
||||
return reqModel + "→" + channelMapping.MappedModel + "→" + result.UpstreamModel
|
||||
}
|
||||
return reqModel + "→" + channelMapping.MappedModel
|
||||
}(),
|
||||
}); err != nil {
|
||||
logger.L().With(
|
||||
zap.String("component", "handler.gateway.messages"),
|
||||
@@ -660,6 +693,13 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
|
||||
parsedReq.OnUpstreamAccepted = queueRelease
|
||||
// ===== 用户消息串行队列 END =====
|
||||
|
||||
// 应用渠道模型映射到请求
|
||||
if channelMapping.Mapped {
|
||||
parsedReq.Model = channelMapping.MappedModel
|
||||
parsedReq.Body = h.gatewayService.ReplaceModelInBody(parsedReq.Body, channelMapping.MappedModel)
|
||||
body = h.gatewayService.ReplaceModelInBody(body, channelMapping.MappedModel)
|
||||
}
|
||||
|
||||
// 转发请求 - 根据账号平台分流
|
||||
var result *service.ForwardResult
|
||||
requestCtx := c.Request.Context()
|
||||
@@ -810,6 +850,21 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
|
||||
RequestPayloadHash: requestPayloadHash,
|
||||
ForceCacheBilling: fs.ForceCacheBilling,
|
||||
APIKeyService: h.apiKeyService,
|
||||
ChannelID: channelMapping.ChannelID,
|
||||
OriginalModel: reqModel,
|
||||
BillingModelSource: channelMapping.BillingModelSource,
|
||||
ModelMappingChain: func() string {
|
||||
if !channelMapping.Mapped {
|
||||
if result.UpstreamModel != "" && result.UpstreamModel != result.Model {
|
||||
return reqModel + "→" + result.UpstreamModel
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if result.UpstreamModel != "" && result.UpstreamModel != channelMapping.MappedModel {
|
||||
return reqModel + "→" + channelMapping.MappedModel + "→" + result.UpstreamModel
|
||||
}
|
||||
return reqModel + "→" + channelMapping.MappedModel
|
||||
}(),
|
||||
}); err != nil {
|
||||
logger.L().With(
|
||||
zap.String("component", "handler.gateway.messages"),
|
||||
|
||||
Reference in New Issue
Block a user