usage_log.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. // Package schema 定义 Ent ORM 的数据库 schema。
  2. package schema
  3. import (
  4. "time"
  5. "entgo.io/ent"
  6. "entgo.io/ent/dialect"
  7. "entgo.io/ent/dialect/entsql"
  8. "entgo.io/ent/schema"
  9. "entgo.io/ent/schema/edge"
  10. "entgo.io/ent/schema/field"
  11. "entgo.io/ent/schema/index"
  12. )
  13. // UsageLog 定义使用日志实体的 schema。
  14. //
  15. // 使用日志记录每次 API 调用的详细信息,包括 token 使用量、成本计算等。
  16. // 这是一个只追加的表,不支持更新和删除。
  17. type UsageLog struct {
  18. ent.Schema
  19. }
  20. // Annotations 返回 schema 的注解配置。
  21. func (UsageLog) Annotations() []schema.Annotation {
  22. return []schema.Annotation{
  23. entsql.Annotation{Table: "usage_logs"},
  24. }
  25. }
  26. // Fields 定义使用日志实体的所有字段。
  27. func (UsageLog) Fields() []ent.Field {
  28. return []ent.Field{
  29. // 关联字段
  30. field.Int64("user_id"),
  31. field.Int64("api_key_id"),
  32. field.Int64("account_id"),
  33. field.String("request_id").
  34. MaxLen(64).
  35. NotEmpty(),
  36. field.String("model").
  37. MaxLen(100).
  38. NotEmpty(),
  39. // RequestedModel stores the client-requested model name for stable display and analytics.
  40. // NULL means historical rows written before requested_model dual-write was introduced.
  41. field.String("requested_model").
  42. MaxLen(100).
  43. Optional().
  44. Nillable(),
  45. // UpstreamModel stores the actual upstream model name when model mapping
  46. // is applied. NULL means no mapping — the requested model was used as-is.
  47. field.String("upstream_model").
  48. MaxLen(100).
  49. Optional().
  50. Nillable(),
  51. // UpstreamResponseModel stores the model name declared by the upstream
  52. // response before any protocol conversion or client-facing rewrite.
  53. field.String("upstream_response_model").
  54. MaxLen(200).
  55. Optional().
  56. Nillable(),
  57. // UpstreamModelMismatch is tri-state: NULL means the upstream response did
  58. // not declare a model (or predates this field); false/true means observed.
  59. field.Bool("upstream_model_mismatch").
  60. Optional().
  61. Nillable(),
  62. field.Int64("channel_id").Optional().Nillable().Comment("渠道 ID"),
  63. field.String("model_mapping_chain").MaxLen(500).Optional().Nillable().Comment("模型映射链"),
  64. field.String("billing_tier").MaxLen(50).Optional().Nillable().Comment("计费层级标签"),
  65. field.String("billing_mode").MaxLen(20).Optional().Nillable().Comment("计费模式:token/per_request/image"),
  66. field.Int64("group_id").
  67. Optional().
  68. Nillable(),
  69. field.Int64("subscription_id").
  70. Optional().
  71. Nillable(),
  72. // Token 计数字段
  73. field.Int("input_tokens").
  74. Default(0),
  75. field.Int("output_tokens").
  76. Default(0),
  77. field.Int("cache_creation_tokens").
  78. Default(0),
  79. field.Int("cache_read_tokens").
  80. Default(0),
  81. field.Int("cache_creation_5m_tokens").
  82. Default(0),
  83. field.Int("cache_creation_1h_tokens").
  84. Default(0),
  85. // 成本字段
  86. field.Float("input_cost").
  87. Default(0).
  88. SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
  89. field.Float("output_cost").
  90. Default(0).
  91. SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
  92. field.Float("cache_creation_cost").
  93. Default(0).
  94. SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
  95. field.Float("cache_read_cost").
  96. Default(0).
  97. SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
  98. field.Float("total_cost").
  99. Default(0).
  100. SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
  101. field.Float("actual_cost").
  102. Default(0).
  103. SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
  104. field.Float("rate_multiplier").
  105. Default(1).
  106. SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}),
  107. field.Bool("long_context_billing_applied").
  108. Default(false).
  109. Comment("Whether long-context pricing changed token prices for this request"),
  110. // account_rate_multiplier: 账号计费倍率快照(NULL 表示按 1.0 处理)
  111. field.Float("account_rate_multiplier").
  112. Optional().
  113. Nillable().
  114. SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}),
  115. // 其他字段
  116. field.Int8("billing_type").
  117. Default(0),
  118. field.Bool("stream").
  119. Default(false),
  120. field.Int("duration_ms").
  121. Optional().
  122. Nillable(),
  123. field.Int("first_token_ms").
  124. Optional().
  125. Nillable(),
  126. field.String("user_agent").
  127. MaxLen(512).
  128. Optional().
  129. Nillable(),
  130. field.String("ip_address").
  131. MaxLen(45). // 支持 IPv6
  132. Optional().
  133. Nillable(),
  134. // 图片生成字段(仅 gemini-3-pro-image 等图片模型使用)
  135. field.Int("image_count").
  136. Default(0),
  137. field.String("image_size").
  138. MaxLen(10).
  139. Optional().
  140. Nillable(),
  141. field.String("image_input_size").
  142. MaxLen(32).
  143. Optional().
  144. Nillable(),
  145. field.String("image_output_size").
  146. MaxLen(32).
  147. Optional().
  148. Nillable(),
  149. field.String("image_size_source").
  150. MaxLen(16).
  151. Optional().
  152. Nillable(),
  153. field.JSON("image_size_breakdown", map[string]int{}).
  154. Optional().
  155. SchemaType(map[string]string{dialect.Postgres: "jsonb"}),
  156. // 视频生成字段(Grok 视频按秒计费;billing_mode 走 token/其他模式时这些列仍标记视频用量)
  157. field.Int("video_count").
  158. Default(0).
  159. Comment("视频生成数量;>0 表示本行是视频生成用量"),
  160. field.String("video_resolution").
  161. MaxLen(10).
  162. Optional().
  163. Nillable().
  164. Comment("计费用视频分辨率 480p/720p/1080p"),
  165. field.Int("video_duration_seconds").
  166. Optional().
  167. Nillable().
  168. Comment("提交时请求的视频时长(秒),按秒计费的乘数"),
  169. // Cache TTL Override 标记(管理员强制替换了缓存 TTL 计费)
  170. field.Bool("cache_ttl_overridden").
  171. Default(false),
  172. // 时间戳(只有 created_at,日志不可修改)
  173. field.Time("created_at").
  174. Default(time.Now).
  175. Immutable().
  176. SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
  177. }
  178. }
  179. // Edges 定义使用日志实体的关联关系。
  180. func (UsageLog) Edges() []ent.Edge {
  181. return []ent.Edge{
  182. edge.From("user", User.Type).
  183. Ref("usage_logs").
  184. Field("user_id").
  185. Required().
  186. Unique(),
  187. edge.From("api_key", APIKey.Type).
  188. Ref("usage_logs").
  189. Field("api_key_id").
  190. Required().
  191. Unique(),
  192. edge.From("account", Account.Type).
  193. Ref("usage_logs").
  194. Field("account_id").
  195. Required().
  196. Unique(),
  197. edge.From("group", Group.Type).
  198. Ref("usage_logs").
  199. Field("group_id").
  200. Unique(),
  201. edge.From("subscription", UserSubscription.Type).
  202. Ref("usage_logs").
  203. Field("subscription_id").
  204. Unique(),
  205. }
  206. }
  207. // Indexes 定义数据库索引,优化查询性能。
  208. func (UsageLog) Indexes() []ent.Index {
  209. return []ent.Index{
  210. index.Fields("user_id"),
  211. index.Fields("api_key_id"),
  212. index.Fields("account_id"),
  213. index.Fields("group_id"),
  214. index.Fields("subscription_id"),
  215. index.Fields("created_at"),
  216. index.Fields("model"),
  217. index.Fields("requested_model"),
  218. index.Fields("request_id"),
  219. // 复合索引用于时间范围查询
  220. index.Fields("user_id", "created_at"),
  221. index.Fields("api_key_id", "created_at"),
  222. // 分组维度时间范围查询(线上由 SQL 迁移创建 group_id IS NOT NULL 的部分索引)
  223. index.Fields("group_id", "created_at"),
  224. }
  225. }