ops_user_error.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package service
  2. import "time"
  3. // UserErrorRequest 是面向终端用户的"错误请求"精简脱敏视图(白名单)。
  4. // 严禁包含 account / api_key_prefix / upstream_endpoint / user_email 等
  5. // 敏感或内部字段。注:message(网关标准化错误描述)与 key_name
  6. // (用户自有 API Key 名称,KeysView 中本就可见)经产品决策对该用户开放;
  7. // client_ip / user_agent / group_name / request_type / stream 均为该用户
  8. // 自己请求的属性,经产品决策(2026-07-03)开放,
  9. // 与用量明细已向用户展示自身 ip_address/user_agent/分组/类型 的口径对齐;
  10. // error_body 仅在详情接口(GetUserErrorRequestDetail)按归属校验后返回。
  11. type UserErrorRequest struct {
  12. ID int64 `json:"id"`
  13. CreatedAt time.Time `json:"created_at"`
  14. Model string `json:"model"`
  15. InboundEndpoint string `json:"inbound_endpoint"`
  16. StatusCode int `json:"status_code"`
  17. Category string `json:"category"`
  18. Platform string `json:"platform"`
  19. Message string `json:"message"`
  20. KeyName string `json:"key_name"`
  21. KeyDeleted bool `json:"key_deleted"`
  22. ClientIP string `json:"client_ip,omitempty"`
  23. GroupName string `json:"group_name,omitempty"`
  24. RequestType *int16 `json:"request_type,omitempty"`
  25. Stream bool `json:"stream"`
  26. UserAgent string `json:"user_agent,omitempty"`
  27. }
  28. // UserErrorRequestList 是用户错误请求分页结果。
  29. type UserErrorRequestList struct {
  30. Items []*UserErrorRequest `json:"items"`
  31. Total int `json:"total"`
  32. Page int `json:"page"`
  33. PageSize int `json:"page_size"`
  34. }
  35. // MapUserErrorCategory 把后端 error_phase + error_type 映射为用户侧粗分类码。
  36. // 返回的是稳定的分类 code(前端做 i18n),不是展示文案。
  37. func MapUserErrorCategory(phase, errType string) string {
  38. switch phase {
  39. case "auth":
  40. return "auth"
  41. case "routing":
  42. return "service_unavailable"
  43. case "account_auth", "upstream", "network":
  44. return "upstream"
  45. case "internal":
  46. return "internal"
  47. case "request":
  48. switch errType {
  49. case "rate_limit_error":
  50. return "rate_limit"
  51. case "billing_error", "subscription_error":
  52. return "quota"
  53. case "invalid_request_error":
  54. return "invalid_request"
  55. case "cyber_policy":
  56. return "cyber"
  57. }
  58. }
  59. return "other"
  60. }
  61. // CategoryToFilter 把用户侧分类码反向映射为后端过滤条件(plain ANY)。
  62. // 未知分类返回两个空切片(即不施加分类过滤)。
  63. // 注意:"other" 与未知分类都走 default 返回空切片——"other" 无对应的 phase/type 组合,无法精确反查,因此等价于不过滤。
  64. func CategoryToFilter(category string) (phases []string, errorTypes []string) {
  65. switch category {
  66. case "auth":
  67. return []string{"auth"}, nil
  68. case "service_unavailable":
  69. return []string{"routing"}, nil
  70. case "upstream":
  71. return []string{"account_auth", "upstream", "network"}, nil
  72. case "internal":
  73. return []string{"internal"}, nil
  74. case "rate_limit":
  75. return nil, []string{"rate_limit_error"}
  76. case "quota":
  77. return nil, []string{"billing_error", "subscription_error"}
  78. case "invalid_request":
  79. return nil, []string{"invalid_request_error"}
  80. case "cyber":
  81. return []string{"request"}, []string{"cyber_policy"}
  82. default:
  83. return nil, nil
  84. }
  85. }
  86. // ToUserErrorRequest 把内部 OpsErrorLog 裁剪为用户安全视图。
  87. func ToUserErrorRequest(e *OpsErrorLog) *UserErrorRequest {
  88. if e == nil {
  89. return nil
  90. }
  91. model := e.RequestedModel
  92. if model == "" {
  93. model = e.Model
  94. }
  95. clientIP := ""
  96. if e.ClientIP != nil {
  97. clientIP = *e.ClientIP
  98. }
  99. return &UserErrorRequest{
  100. ID: e.ID,
  101. CreatedAt: e.CreatedAt,
  102. Model: model,
  103. InboundEndpoint: e.InboundEndpoint,
  104. StatusCode: e.StatusCode,
  105. Category: MapUserErrorCategory(e.Phase, e.Type),
  106. Platform: e.Platform,
  107. Message: e.Message,
  108. KeyName: e.APIKeyName,
  109. KeyDeleted: e.APIKeyDeleted,
  110. ClientIP: clientIP,
  111. GroupName: e.GroupName,
  112. RequestType: e.RequestType,
  113. Stream: e.Stream,
  114. UserAgent: e.UserAgent,
  115. }
  116. }
  117. // UserErrorRequestDetail 是错误请求详情的脱敏视图(点击单行查看)。
  118. // 在 UserErrorRequest 基础上额外暴露 error_body(上游错误响应正文)与 upstream_status_code;
  119. // 仍严禁任何内部/敏感字段。
  120. type UserErrorRequestDetail struct {
  121. UserErrorRequest
  122. ErrorBody string `json:"error_body"`
  123. UpstreamStatusCode *int `json:"upstream_status_code,omitempty"`
  124. }
  125. // ToUserErrorRequestDetail 把内部 OpsErrorLogDetail 裁剪为用户安全详情视图。
  126. func ToUserErrorRequestDetail(e *OpsErrorLogDetail) *UserErrorRequestDetail {
  127. if e == nil {
  128. return nil
  129. }
  130. base := ToUserErrorRequest(&e.OpsErrorLog)
  131. return &UserErrorRequestDetail{
  132. UserErrorRequest: *base,
  133. ErrorBody: e.ErrorBody,
  134. UpstreamStatusCode: e.UpstreamStatusCode,
  135. }
  136. }