- 后端
- 网络/通信
- 云原生
【免费下载链接】bfe
A modern layer 7 load balancer from baidu
本文以 BFE 开源仓库(Go 语言实现的七层负载均衡器)中 AI 网关能力为背景,完整解读一份关于 AI 访问日志可观测字段升级的设计变更方案。文章从协议字段重命名与新增的动机出发,逐层展开
bfe-access-pb依赖升级、AiBasicInfo/AiAuthInfo结构扩展、各 AI 模块采集逻辑填充、访问日志赋值改造、测试计划、兼容性与回滚策略,并辅以仓库源码作为实现证据。读者读完可掌握在 BFE 中为 AI 请求补齐 provider、retry、cost、路由命中、cluster/key 尝试等观测字段的完整改造路径。
1. 背景:bfe-access-pb协议升级带来的配套改造需求
BFE 通过mod_access_pb3模块输出基于 protobuf 的访问日志(b2log)。随着 AI 网关业务的发展,承载访问日志协议的独立仓库bfe-access-pb完成了 AI 可观测字段的扩展与重命名(协议定义详见bfe-access-pb/docs/protobuf.md)。本次设计变更的核心目标,就是让 BFE 侧的访问日志代码与新版协议对齐,从而把 AI 请求在网关内部的关键行为完整、安全地落进日志。
1.1 字段重命名(编号不变)
| 旧字段名 | 新字段名 | 语义变化 |
|---|---|---|
ai_apikey | ai_apikey_id | 不再记录原始 API Key 值,改为记录 API Key 内部标识 |
ai_mapped_model | ai_target_model | 记录路由/映射后的目标模型 |
ai_prompt_tokens | ai_input_tokens | 记录输入(prompt)token 数 |
三个字段的 protobuf 编号保持不变(分别为 701、704、706),二进制层面完全兼容;变化只体现在生成代码的 Go 字段名上。
1.2 新增字段
| 字段 | 含义 |
|---|---|
ai_provider | 上游模型提供商标识(如 openai、deepseek) |
ai_retry_count | 模型调用层重试次数 |
ai_cost_value/ai_cost_currency | RMB 成本计量与币种 |
ai_route_rule_hits | 命中的 AI 路由规则列表 |
ai_cluster_key_names | 请求处理过程中尝试过的 (cluster, key) 列表 |
ai_auth_hit_quota_plans | 正常请求时命中的 Quota Plan ID 列表 |
1.3 新增辅助消息
AIRouteRuleHit:描述命中路由规则的 owner / owner_type / name;ClusterKeyName:描述尝试过的 cluster 与 key 名称组合。
变更发生前,BFE 依赖的是github.com/bfenetworks/bfe-access-pb v0.1.0,访问日志代码中引用的仍是旧字段名(AiApikey、AiMappedModel、AiPromptTokens),因此需要一次系统性的配套改造。
2. 目标与变更总览
本次改造的六项目标:
- 将 BFE 依赖的
bfe-access-pb升级到包含新字段的版本(方案建议v0.2.0或本地 replace); - 修正访问日志中对重名字段的引用;
- 将
ai_apikey_id的数据源从原始 API Key 改为 API Key 内部 ID(Token.KeyId/AiBasicInfo.ClientKeyId); - 在 BFE 各 AI 模块中补充新增字段的采集逻辑;
- 更新单元测试
bfe_modules/mod_access_pb3/request_log_test.go; - 保持非 AI 请求和未升级配置场景下的向后兼容。
从仓库当前实现看,目标 3 已在token_rule_table.go中落地:ValidateUserTokenByReq在鉴权早期就把token.KeyId写入aiBasicInfo.ClientKeyId(见 token_rule_table.go),保证即使请求后续被拒绝,访问日志仍能识别 token 身份。
下表是方案给出的全量字段映射(Proto 字段 → 编号 → 旧 BFE 字段/状态 → 新 BFE 数据源 → 需修改的文件):
| Proto 字段 | 编号 | 旧 BFE 字段/状态 | 新 BFE 数据源 | 需修改的文件 |
|---|---|---|---|---|
ai_apikey_id | 701 | AiApikey(记录ClientApiKey) | AiBasicInfo.ClientKeyId | request_log.go |
ai_apikeytags | 702 | 已支持 | AiBasicInfo.ApikeyTags | 不变 |
ai_requested_model | 703 | 已支持 | AiBasicInfo.ClientModel | 不变 |
ai_target_model | 704 | AiMappedModel | AiBasicInfo.TargetModel | request_log.go |
ai_stream | 705 | 已支持 | req.IsSse | 不变 |
ai_input_tokens | 706 | AiPromptTokens | TokenUsage.PromptTokens | request_log.go |
ai_output_tokens | 707 | 已支持 | TokenUsage.CompletionTokens | 不变 |
ai_total_tokens | 708 | 已支持 | TokenUsage.UsedQuota | 不变 |
ai_ttft_us | 709 | 已支持 | TokenTimeInfo.TTFT | 不变 |
ai_tpot_us | 710 | 已支持 | TokenTimeInfo.TPOT | 不变 |
ai_rate_limit_hits | 711 | 已支持 | AiRateLimitHitInfo | 不变 |
ai_auth_reject_reason | 712 | 已支持 | AiAuthInfo.RejectReason | 不变 |
ai_auth_reject_quota_plans | 713 | 已支持 | AiAuthInfo.RejectQuotaPlans | 不变 |
ai_provider | 714 | 缺失 | cluster.AIConf.Provider | request_ai_basic.go,reverseproxy.go |
ai_retry_count | 715 | 缺失 | AiBasicInfo.RetryCount | request_ai_basic.go,reverseproxy.go |
ai_cost_value | 761 | 缺失 | TokenUsage.UsedCost | request_log.go |
ai_cost_currency | 762 | 缺失 | cluster.AIConf.ModelTable.Currency | request_ai_basic.go,reverseproxy.go,request_log.go |
ai_route_rule_hits | 801 | 缺失 | AiRouteResult→AIRouteRuleHit | request_ai_route.go,request_log.go |
ai_cluster_key_names | 802 | 缺失 | AiBasicInfo.ClusterKeyNames | request_ai_basic.go,reverseproxy.go,request_log.go |
ai_auth_hit_quota_plans | 841 | 缺失 | AiAuthInfo.HitQuotaPlans | request_ai_basic.go,token_rule_table.go,request_log.go |
3. 依赖升级:bfe-access-pb版本替换
涉及文件:go.mod
方案要求将bfe-access-pb从v0.1.0升级到包含新字段的版本:
require ( ... github.com/bfenetworks/bfe-access-pb v0.2.0 ... )本地开发阶段可临时启用 replace 指向本地源码目录:
replace github.com/bfenetworks/bfe-access-pb => ../bfe-access-pb升级后执行:
cd bfe go mod tidy go mod download注意:
bfe-access-pb的.pb.go文件需要在 Linux 环境下执行build.sh重新生成并打 tag 后,BFE 才能引用到正确版本。
从当前仓库的 go.mod 可以看到,依赖已演进到github.com/bfenetworks/bfe-access-pb v0.3.6,且go.sum随go mod tidy自动更新,说明该升级链路已在仓库中完成闭环。
4. 核心数据结构扩展:AiBasicInfo与AiAuthInfo
4.1 扩展AiBasicInfo
涉及文件:request_ai_basic.go
方案在AiBasicInfo中新增四个字段与一个辅助结构:Provider(上游 provider,如 openai / deepseek)、RetryCount(模型调用层重试次数)、CostCurrency(成本币种,如 RMB / USD)、ClusterKeyNames(尝试过的 (cluster, key) 列表),并定义ClusterKeyName描述一次尝试的 cluster 与 key 名称组合:
type AiBasicInfo struct { ClientApiKey string ClientKeyId string ClientModel string TargetModel string Provider string // 新增:上游 provider,如 openai / deepseek RetryCount uint32 // 新增:模型调用层重试次数 CostCurrency string // 新增:成本币种,如 RMB / USD tokenUsage TokenUsage ApikeyTags []ApikeyTag TokenTimeInfo TokenTimeInfo AiAuthInfo AiAuthInfo ClusterKeyNames []ClusterKeyName // 新增:尝试过的 (cluster, key) 列表 allowEstimateToken bool } // ClusterKeyName 描述一次尝试的 cluster 与 key 名称组合 type ClusterKeyName struct { ClusterName string KeyName string }同时新增两个辅助方法:
func (aiinfo *AiBasicInfo) AppendClusterKeyName(clusterName, keyName string) { aiinfo.ClusterKeyNames = append(aiinfo.ClusterKeyNames, ClusterKeyName{ ClusterName: clusterName, KeyName: keyName, }) } func (aiinfo *AiBasicInfo) IncrementRetryCount() { aiinfo.RetryCount++ }从当前仓库源码看,这两处扩展均已落地:request_ai_basic.go 中AiBasicInfo已包含Provider、RetryCount、CostCurrency、ClusterKeyNames字段,AppendClusterKeyName与IncrementRetryCount方法也存在于 request_ai_basic.go。
4.2 扩展AiAuthInfo
涉及文件:request_ai_basic.go
在AiAuthInfo中新增HitQuotaPlans,用于记录成功鉴权时参与余额检查并放行的 Quota Plan ID:
type AiAuthInfo struct { RejectReason string // 拒绝原因 RejectQuotaPlans []string // 拒绝时余额不足的 Quota Plan IDs HitQuotaPlans []string // 新增:成功时命中的 Quota Plan IDs }该结构在仓库中的实现与方案完全一致(见 request_ai_basic.go)。
5. 模块数据填充:把采集逻辑写入各 AI 模块
5.1mod_ai_token_auth:记录命中配额计划
涉及文件:token_rule_table.go
在ValidateUserTokenByReq的for _, plan := range token.QuotaPlans循环中,当 Quota Plan 通过余额检查(hasBalance == true)时,将其 ID 记录到AiAuthInfo.HitQuotaPlans:
// 在 for _, plan := range token.QuotaPlans 循环内 if !hasBalance { SetAiAuthInfo(req, bfe_basic.CodeQuotaExhausted, []string{plan.Id}) ... } // 新增:记录成功命中的 quota plan aiBasicInfo := req.GetAiBasicInfo() if aiBasicInfo != nil { aiBasicInfo.AiAuthInfo.HitQuotaPlans = append(aiBasicInfo.AiAuthInfo.HitQuotaPlans, plan.Id) }说明:
Unlimited或PassNoQuota的 plan 不经过HasBalance检查,因此不会进入HitQuotaPlans。这符合语义:HitQuotaPlans仅记录实际参与余额校验并命中的计划。
仓库中的实现印证了这一点:token_rule_table.go 中先跳过plan.Unlimited || plan.PassNoQuota,再依次处理过期、余额不足(写入RejectQuotaPlans)与通过(追加到HitQuotaPlans)三种情况。
5.2bfe_server/reverseproxy.go:记录 provider、currency、retry、cluster/key
涉及文件:reverseproxy.go
A. 在doSingleAIForward中记录 provider、currency 和 cluster/key:
func (p *ReverseProxy) doSingleAIForward(..., selectedKey cluster_conf.AIKey) (...) { ... if cluster.AIConf != nil && aiMeta != nil { if cluster.AIConf.Provider != "" { aiMeta.Provider = cluster.AIConf.Provider } if cluster.AIConf.ModelTable != nil && cluster.AIConf.ModelTable.Currency != "" { aiMeta.CostCurrency = cluster.AIConf.ModelTable.Currency } aiMeta.AppendClusterKeyName(cluster.Name, selectedKey.Name) } ... }注意:需要确认
cluster对象是否有Name字段;如果没有,使用attempt.ClusterName。
仓库中的doSingleAIForward实现在 reverseproxy.go 中,Provider/CostCurrency赋值与aiMeta.AppendClusterKeyName(cluster.Name, selectedKey.Name)调用均与方案一致,且确认cluster对象具备Name字段。
B. 在aiClusterInvoke中统计重试次数:
for retry := 0; retry <= policy.MaxRetries; retry++ { if retry > 0 { if aiMeta != nil { aiMeta.IncrementRetryCount() } ... } ... }仓库实现中,aiClusterInvoke的 key-level 重试循环(for retry := 0; retry <= policy.MaxRetries; retry++)在每次重试前调用aiMeta.IncrementRetryCount()(见 reverseproxy.go)。
说明:
RetryCount只统计同一 cluster 内 key-level 的重试次数,与 HTTP 层basicReq.RetryTime解耦。fallback 到另一个 cluster 时,该计数器不累加(符合协议语义)。这与访问日志中已有的backend_retry字段(记录 HTTP 层重试,见 request_log.go)形成互补:一个是模型调用层视角,一个是 HTTP 转发层视角。
5.3 数据源的配置层面支撑:cluster.AIConf
从配置加载代码可以看到,AIConf位于 cluster_conf_load.go,包含Provider(模型价格表中的 provider 名称)与ModelTable(含Currency字段)等配置。配置测试用例覆盖了RMB与USD两种币种(见 cluster_conf_load_test.go)。这意味着ai_provider与ai_cost_currency的实际取值直接来自集群的 AI 配置项,而非运行时推导。
6. 访问日志赋值改造:reqAiInfoGen
涉及文件:request_log.go
方案要求将reqAiInfoGen函数更新为使用新字段名并填充新增字段。核心逻辑如下:
func reqAiInfoGen(reqLog *bfe_access_pb3.RequestLog, req *bfe_basic.Request, res *bfe_http.Response) { aiInfo := req.GetAiBasicInfo() if aiInfo == nil { return } // API Key ID(不再记录原始 key) if aiInfo.ClientKeyId != "" { reqLog.AiApikeyId = proto.String(aiInfo.ClientKeyId) } // API Key Tags if len(aiInfo.ApikeyTags) > 0 { for _, tag := range aiInfo.ApikeyTags { reqLog.AiApikeytags = append(reqLog.AiApikeytags, &bfe_access_pb3.ApikeyTag{ Tagname: proto.String(tag.TagName), Tagvalue: proto.String(tag.TagValue), }) } } // Model if aiInfo.ClientModel != "" { reqLog.AiRequestedModel = proto.String(aiInfo.ClientModel) } if aiInfo.TargetModel != "" { reqLog.AiTargetModel = proto.String(aiInfo.TargetModel) } // Provider if aiInfo.Provider != "" { reqLog.AiProvider = proto.String(aiInfo.Provider) } // Stream reqLog.AiStream = proto.Bool(isStreamResponse(req, res)) // Token usage usage := aiInfo.GetTokenUsage() if usage != nil { reqLog.AiInputTokens = proto.Int64(usage.PromptTokens) reqLog.AiOutputTokens = proto.Int64(usage.CompletionTokens) reqLog.AiTotalTokens = proto.Int64(usage.UsedQuota) if usage.UsedCost > 0 { reqLog.AiCostValue = proto.Int64(usage.UsedCost) } } // Cost currency if aiInfo.CostCurrency != "" { reqLog.AiCostCurrency = proto.String(aiInfo.CostCurrency) } // Retry count if aiInfo.RetryCount > 0 { reqLog.AiRetryCount = proto.Uint32(aiInfo.RetryCount) } // TTFT / TPOT ti := aiInfo.TokenTimeInfo if ti.TTFT > 0 { reqLog.AiTtftUs = proto.Int64(ti.TTFT) } if ti.TPOT > 0 { reqLog.AiTpotUs = proto.Int64(ti.TPOT) } // Auth reject info if len(aiInfo.AiAuthInfo.RejectReason) > 0 { reqLog.AiAuthRejectReason = proto.String(aiInfo.AiAuthInfo.RejectReason) } for _, item := range aiInfo.AiAuthInfo.RejectQuotaPlans { reqLog.AiAuthRejectQuotaPlans = append(reqLog.AiAuthRejectQuotaPlans, item) } for _, item := range aiInfo.AiAuthInfo.HitQuotaPlans { reqLog.AiAuthHitQuotaPlans = append(reqLog.AiAuthHitQuotaPlans, item) } // Route rule hits if routeResult := req.GetAiRouteResult(); routeResult != nil { reqLog.AiRouteRuleHits = append(reqLog.AiRouteRuleHits, &bfe_access_pb3.AIRouteRuleHit{ RuleOwner: proto.String(routeResult.Owner), RuleOwnerType: proto.String(routeResult.RouteType), RuleName: proto.String(routeResult.RuleName), }) } // Cluster / key attempts for _, ckn := range aiInfo.ClusterKeyNames { reqLog.AiClusterKeyNames = append(reqLog.AiClusterKeyNames, &bfe_access_pb3.ClusterKeyName{ ClusterName: proto.String(ckn.ClusterName), KeyName: proto.String(ckn.KeyName), }) } // Rate limit hit info(保持不变) hitInfo := req.GetAiRateLimitHitInfo() if hitInfo != nil && len(hitInfo.HitPolicyDict) > 0 { for policyId, info := range hitInfo.HitPolicyDict { ... } } }仓库中的reqAiInfoGen实现(见 request_log.go)与方案完全对齐,并在此基础上做了进一步细化:AiApikeytags多填充了Taglevel字段,Token 部分补充了CacheReadTokens、CacheWriteTokens、AudioInputTokens等细分 token 字段,Rate Limit 部分区分了 tpm/rpm/concurrency/redis_error 四种命中类型。
6.1AiRouteResult结构
路由命中信息来自请求上下文中的AiRouteResult(定义于 request_ai_route.go),包含RouteType(apikey / entity / global)、Owner(路由表 owner)、RuleName(命中规则名),并通过SetAiRouteResult/GetAiRouteResult在请求上下文中传递。方案中"可选:为AiRouteResult增加导出方法,便于request_log.go读取",从当前实现看字段本身已可直接访问。
6.2 敏感信息防护:原始 API Key 永不落日志
值得注意的是,仓库中requestLogGen在调用reqAiInfoGen之后还有一道maskSensitiveCredentials(requestLog, req)兜底(见 request_log.go),作为"日志输出前的最后防线",确保原始 API Key 不会进入任何字段。这与本次改造把ai_apikey_id数据源改为内部key_id的语义方向一致,二者共同落实了"日志中不泄露敏感凭证"的硬性要求。
7. 涉及文件清单
| 文件 | 修改内容 |
|---|---|
bfe/go.mod | 升级bfe-access-pb到v0.2.0(或启用 replace) |
bfe/go.sum | 随go mod tidy自动更新 |
bfe/bfe_basic/request_ai_basic.go | AiBasicInfo新增Provider、RetryCount、CostCurrency、ClusterKeyNames;新增ClusterKeyName结构及辅助方法;AiAuthInfo新增HitQuotaPlans |
bfe/bfe_basic/request_ai_route.go | 可选:为AiRouteResult增加导出方法,便于request_log.go读取 |
bfe/bfe_modules/mod_ai_token_auth/token_rule_table.go | ValidateUserTokenByReq中记录成功命中的HitQuotaPlans |
bfe/bfe_server/reverseproxy.go | doSingleAIForward记录 provider / currency / cluster-key;aiClusterInvoke统计 retry count |
bfe/bfe_modules/mod_access_pb3/request_log.go | reqAiInfoGen使用新字段名并填充新增字段 |
bfe/bfe_modules/mod_access_pb3/request_log_test.go | 更新测试断言,覆盖新字段 |
8. 测试计划
8.1 单元测试
涉及文件:request_log_test.go
更新TestReqAiInfoGen,覆盖以下断言变更:
- 将
ClientApiKey替换为ClientKeyId,并断言AiApikeyId; - 将
AiMappedModel断言改为AiTargetModel; - 将
AiPromptTokens断言改为AiInputTokens; - 新增断言:
AiProvider、AiRetryCount、AiCostValue/AiCostCurrency、AiRouteRuleHits、AiClusterKeyNames、AiAuthHitQuotaPlans。
测试示例:
aiInfo := &bfe_basic.AiBasicInfo{ ClientKeyId: "key-id-123", ClientModel: "model-a", TargetModel: "model-b", Provider: "deepseek", RetryCount: 1, CostCurrency: "RMB", ClusterKeyNames: []bfe_basic.ClusterKeyName{ {ClusterName: "cluster-a", KeyName: "key-001"}, }, ... } usage.UsedCost = 5000 // 1e-8 元从仓库测试代码看,上述断言均已实现:TestReqAiInfoGen构造了ClientKeyId: "key-id-123"的AiBasicInfo,并分别断言AiProvider == "deepseek"、AiCostValue == 5000、AiRetryCount == 1、AiAuthHitQuotaPlans长度为 2、AiRouteRuleHits长度为 1、AiClusterKeyNames长度为 1(见 request_log_test.go);另有测试验证路由命中 owner 与 quota plan ID 的精确匹配,以及未鉴权场景下AiAuthHitQuotaPlans应被清空(见 request_log_test.go)。
8.2 编译验证
cd bfe go build ./... go test ./bfe_modules/mod_access_pb3/...8.3 集成验证
- 启用 AI 网关,发起一次带 API Key 的模型请求;
- 收集
mod_access_pb3输出的 b2log,解码RequestLog; - 校验字段:
ai_apikey_id等于 Token 的key_id,而不是原始key;ai_target_model正确反映路由/映射后的模型;ai_provider、ai_retry_count、ai_cost_value、ai_cost_currency非空(RMB 配额场景);ai_route_rule_hits、ai_cluster_key_names、ai_auth_hit_quota_plans与请求行为一致。
9. 兼容性说明
- 字段重命名:proto 字段编号不变(701、704、706),因此 protobuf 二进制层面完全兼容;变化只体现在生成代码的 Go 字段名上。
- 语义变化:
ai_apikey_id从记录原始 API Key 改为记录内部key_id,避免在日志中泄露敏感信息。需要确认上游日志消费方不再依赖原始 key 值。 - 新增字段:均为
optional,对未升级的旧 BFE 版本无影响。 - 版本依赖:升级
bfe-access-pb后,旧 BFE 代码无法直接编译,因此这是一个需要同步发布的破坏性变更(仅对 BFE 代码编译层面)。
从仓库实现看,"非 AI 请求向后兼容"也已落地:reqAiInfoGen开头对aiInfo == nil直接返回(request_log.go),非 AI 请求不会进入 AI 字段填充逻辑。
10. 风险与回滚
10.1 主要风险
| 风险 | 说明 | 规避措施 |
|---|---|---|
| 编译失败 | 新 proto 字段名与旧 BFE 代码不匹配 | 按本方案一次性更新所有引用 |
| 日志消费方依赖旧字段名 | 下游解析ai_apikey、ai_mapped_model、ai_prompt_tokens会失败 | 提前通知下游,按 proto 编号而非字段名解析;或在下游做映射 |
ai_apikey_id为空 | 如果Token.KeyId未配置,日志中将缺失 key 标识 | 确保ai-gateway-api导出的 Token 配置始终包含key_id |
| 重试计数语义不清 | RetryCount仅统计 key-level 重试,不统计 cluster fallback | 文档中明确语义;访问日志中已有backend_retry字段记录 HTTP 层重试 |
10.2 回滚方案
如需回滚到旧协议:
- 将
bfe/go.mod中的bfe-access-pb版本改回v0.1.0; - 回滚
request_log.go到旧字段名(AiApikey、AiMappedModel、AiPromptTokens); - 移除
request_ai_basic.go、reverseproxy.go、token_rule_table.go中新增字段的采集逻辑; - 重新编译部署。
注意:回滚后新字段(provider、retry、cost 等)将不再输出到日志。
11. 后续可选扩展
ai_route_rule_hits支持多条命中记录:当前AiRouteResult只记录最终命中的规则。未来如果路由模块支持记录所有匹配规则,可将HitPolicyDict式的列表写入日志。ai_cluster_key_names区分成功与失败尝试:当前记录所有尝试;可扩展为标记最终成功的 key。ai_auth_hit_quota_plans与 RMB 扣减计划对齐:当前记录所有通过余额检查的 plan;可与实际扣减计划做交叉验证。
12. 小结
本次设计变更以bfe-access-pb协议扩展为牵引,在 BFE 侧完成了一次从"数据结构 → 采集逻辑 → 日志赋值 → 测试覆盖"的闭环改造:AiBasicInfo/AiAuthInfo承载新增观测数据,mod_ai_token_auth、bfe_server/reverseproxy.go负责在请求处理链路中填充,mod_access_pb3/request_log.go的reqAiInfoGen负责最终落盘,并以字段编号不变保证二进制兼容、以key_id替代原始 key 落实安全要求。对于需要在 BFE 中扩展 AI 可观测性的开发者,本文给出的字段映射表、代码插桩点与测试用例可作为直接参考的改造模板。
文档生成日期:2026-08-19
- 后端
- 网络/通信
- 云原生
【免费下载链接】bfe
A modern layer 7 load balancer from baidu
相关推荐
BFE AI 网关访问日志可观测字段设计:从认证、路由到计费限流的全链路埋点解析
BFE AI 网关访问日志可观测字段设计:从认证、路由到计费限流的全链路埋点解析 BFE 在 AI 网关场景下,需要把一次 AI 请求在认证、路由、转发、计费、
后端网络/通信云原生TypeGraphQL字段级访问日志:记录数据访问详情
TypeGraphQL字段级访问日志:记录数据访问详情 TypeGraphQL允许开发者通过装饰器和中间件实现灵活的字段级访问日志功能,帮助追踪数据访问行为、排
后端GraphQLAPI设计BFE mod_auth_basic 规则配置详解:auth_basic_rule.data 从字段到源码实现
BFE mod_auth_basic 规则配置详解:auth_basic_rule.data 从字段到源码实现 auth_basic_rule.data 是 B
后端网络/通信云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考