feat(usage-log): 增加请求 User-Agent 记录
在使用记录中添加 user_agent 字段,用于记录 API 请求的 User-Agent 头信息, 便于分析客户端类型和调试。 变更内容: - 新增数据库迁移 028_add_usage_logs_user_agent.sql - 更新 UsageLog 模型和 Ent Schema 添加 user_agent 字段 - 更新 Repository 层的 Create 和 scanUsageLog 方法 - 更新 RecordUsageInput 结构体支持传入 UserAgent - 更新 Claude/OpenAI/Gemini 三个网关 Handler 传递 UserAgent 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -371,6 +371,7 @@ var (
|
||||
{Name: "stream", Type: field.TypeBool, Default: false},
|
||||
{Name: "duration_ms", Type: field.TypeInt, Nullable: true},
|
||||
{Name: "first_token_ms", Type: field.TypeInt, Nullable: true},
|
||||
{Name: "user_agent", Type: field.TypeString, Nullable: true, Size: 512},
|
||||
{Name: "image_count", Type: field.TypeInt, Default: 0},
|
||||
{Name: "image_size", Type: field.TypeString, Nullable: true, Size: 10},
|
||||
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
@@ -388,31 +389,31 @@ var (
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "usage_logs_api_keys_usage_logs",
|
||||
Columns: []*schema.Column{UsageLogsColumns[23]},
|
||||
Columns: []*schema.Column{UsageLogsColumns[24]},
|
||||
RefColumns: []*schema.Column{APIKeysColumns[0]},
|
||||
OnDelete: schema.NoAction,
|
||||
},
|
||||
{
|
||||
Symbol: "usage_logs_accounts_usage_logs",
|
||||
Columns: []*schema.Column{UsageLogsColumns[24]},
|
||||
Columns: []*schema.Column{UsageLogsColumns[25]},
|
||||
RefColumns: []*schema.Column{AccountsColumns[0]},
|
||||
OnDelete: schema.NoAction,
|
||||
},
|
||||
{
|
||||
Symbol: "usage_logs_groups_usage_logs",
|
||||
Columns: []*schema.Column{UsageLogsColumns[25]},
|
||||
Columns: []*schema.Column{UsageLogsColumns[26]},
|
||||
RefColumns: []*schema.Column{GroupsColumns[0]},
|
||||
OnDelete: schema.SetNull,
|
||||
},
|
||||
{
|
||||
Symbol: "usage_logs_users_usage_logs",
|
||||
Columns: []*schema.Column{UsageLogsColumns[26]},
|
||||
Columns: []*schema.Column{UsageLogsColumns[27]},
|
||||
RefColumns: []*schema.Column{UsersColumns[0]},
|
||||
OnDelete: schema.NoAction,
|
||||
},
|
||||
{
|
||||
Symbol: "usage_logs_user_subscriptions_usage_logs",
|
||||
Columns: []*schema.Column{UsageLogsColumns[27]},
|
||||
Columns: []*schema.Column{UsageLogsColumns[28]},
|
||||
RefColumns: []*schema.Column{UserSubscriptionsColumns[0]},
|
||||
OnDelete: schema.SetNull,
|
||||
},
|
||||
@@ -421,32 +422,32 @@ var (
|
||||
{
|
||||
Name: "usagelog_user_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{UsageLogsColumns[26]},
|
||||
Columns: []*schema.Column{UsageLogsColumns[27]},
|
||||
},
|
||||
{
|
||||
Name: "usagelog_api_key_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{UsageLogsColumns[23]},
|
||||
Columns: []*schema.Column{UsageLogsColumns[24]},
|
||||
},
|
||||
{
|
||||
Name: "usagelog_account_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{UsageLogsColumns[24]},
|
||||
Columns: []*schema.Column{UsageLogsColumns[25]},
|
||||
},
|
||||
{
|
||||
Name: "usagelog_group_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{UsageLogsColumns[25]},
|
||||
Columns: []*schema.Column{UsageLogsColumns[26]},
|
||||
},
|
||||
{
|
||||
Name: "usagelog_subscription_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{UsageLogsColumns[27]},
|
||||
Columns: []*schema.Column{UsageLogsColumns[28]},
|
||||
},
|
||||
{
|
||||
Name: "usagelog_created_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{UsageLogsColumns[22]},
|
||||
Columns: []*schema.Column{UsageLogsColumns[23]},
|
||||
},
|
||||
{
|
||||
Name: "usagelog_model",
|
||||
@@ -461,12 +462,12 @@ var (
|
||||
{
|
||||
Name: "usagelog_user_id_created_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{UsageLogsColumns[26], UsageLogsColumns[22]},
|
||||
Columns: []*schema.Column{UsageLogsColumns[27], UsageLogsColumns[23]},
|
||||
},
|
||||
{
|
||||
Name: "usagelog_api_key_id_created_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{UsageLogsColumns[23], UsageLogsColumns[22]},
|
||||
Columns: []*schema.Column{UsageLogsColumns[24], UsageLogsColumns[23]},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8107,6 +8107,7 @@ type UsageLogMutation struct {
|
||||
addduration_ms *int
|
||||
first_token_ms *int
|
||||
addfirst_token_ms *int
|
||||
user_agent *string
|
||||
image_count *int
|
||||
addimage_count *int
|
||||
image_size *string
|
||||
@@ -9463,6 +9464,55 @@ func (m *UsageLogMutation) ResetFirstTokenMs() {
|
||||
delete(m.clearedFields, usagelog.FieldFirstTokenMs)
|
||||
}
|
||||
|
||||
// SetUserAgent sets the "user_agent" field.
|
||||
func (m *UsageLogMutation) SetUserAgent(s string) {
|
||||
m.user_agent = &s
|
||||
}
|
||||
|
||||
// UserAgent returns the value of the "user_agent" field in the mutation.
|
||||
func (m *UsageLogMutation) UserAgent() (r string, exists bool) {
|
||||
v := m.user_agent
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldUserAgent returns the old "user_agent" field's value of the UsageLog entity.
|
||||
// If the UsageLog object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *UsageLogMutation) OldUserAgent(ctx context.Context) (v *string, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldUserAgent is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldUserAgent requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldUserAgent: %w", err)
|
||||
}
|
||||
return oldValue.UserAgent, nil
|
||||
}
|
||||
|
||||
// ClearUserAgent clears the value of the "user_agent" field.
|
||||
func (m *UsageLogMutation) ClearUserAgent() {
|
||||
m.user_agent = nil
|
||||
m.clearedFields[usagelog.FieldUserAgent] = struct{}{}
|
||||
}
|
||||
|
||||
// UserAgentCleared returns if the "user_agent" field was cleared in this mutation.
|
||||
func (m *UsageLogMutation) UserAgentCleared() bool {
|
||||
_, ok := m.clearedFields[usagelog.FieldUserAgent]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ResetUserAgent resets all changes to the "user_agent" field.
|
||||
func (m *UsageLogMutation) ResetUserAgent() {
|
||||
m.user_agent = nil
|
||||
delete(m.clearedFields, usagelog.FieldUserAgent)
|
||||
}
|
||||
|
||||
// SetImageCount sets the "image_count" field.
|
||||
func (m *UsageLogMutation) SetImageCount(i int) {
|
||||
m.image_count = &i
|
||||
@@ -9773,7 +9823,7 @@ func (m *UsageLogMutation) Type() string {
|
||||
// order to get all numeric fields that were incremented/decremented, call
|
||||
// AddedFields().
|
||||
func (m *UsageLogMutation) Fields() []string {
|
||||
fields := make([]string, 0, 27)
|
||||
fields := make([]string, 0, 28)
|
||||
if m.user != nil {
|
||||
fields = append(fields, usagelog.FieldUserID)
|
||||
}
|
||||
@@ -9846,6 +9896,9 @@ func (m *UsageLogMutation) Fields() []string {
|
||||
if m.first_token_ms != nil {
|
||||
fields = append(fields, usagelog.FieldFirstTokenMs)
|
||||
}
|
||||
if m.user_agent != nil {
|
||||
fields = append(fields, usagelog.FieldUserAgent)
|
||||
}
|
||||
if m.image_count != nil {
|
||||
fields = append(fields, usagelog.FieldImageCount)
|
||||
}
|
||||
@@ -9911,6 +9964,8 @@ func (m *UsageLogMutation) Field(name string) (ent.Value, bool) {
|
||||
return m.DurationMs()
|
||||
case usagelog.FieldFirstTokenMs:
|
||||
return m.FirstTokenMs()
|
||||
case usagelog.FieldUserAgent:
|
||||
return m.UserAgent()
|
||||
case usagelog.FieldImageCount:
|
||||
return m.ImageCount()
|
||||
case usagelog.FieldImageSize:
|
||||
@@ -9974,6 +10029,8 @@ func (m *UsageLogMutation) OldField(ctx context.Context, name string) (ent.Value
|
||||
return m.OldDurationMs(ctx)
|
||||
case usagelog.FieldFirstTokenMs:
|
||||
return m.OldFirstTokenMs(ctx)
|
||||
case usagelog.FieldUserAgent:
|
||||
return m.OldUserAgent(ctx)
|
||||
case usagelog.FieldImageCount:
|
||||
return m.OldImageCount(ctx)
|
||||
case usagelog.FieldImageSize:
|
||||
@@ -10157,6 +10214,13 @@ func (m *UsageLogMutation) SetField(name string, value ent.Value) error {
|
||||
}
|
||||
m.SetFirstTokenMs(v)
|
||||
return nil
|
||||
case usagelog.FieldUserAgent:
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetUserAgent(v)
|
||||
return nil
|
||||
case usagelog.FieldImageCount:
|
||||
v, ok := value.(int)
|
||||
if !ok {
|
||||
@@ -10427,6 +10491,9 @@ func (m *UsageLogMutation) ClearedFields() []string {
|
||||
if m.FieldCleared(usagelog.FieldFirstTokenMs) {
|
||||
fields = append(fields, usagelog.FieldFirstTokenMs)
|
||||
}
|
||||
if m.FieldCleared(usagelog.FieldUserAgent) {
|
||||
fields = append(fields, usagelog.FieldUserAgent)
|
||||
}
|
||||
if m.FieldCleared(usagelog.FieldImageSize) {
|
||||
fields = append(fields, usagelog.FieldImageSize)
|
||||
}
|
||||
@@ -10456,6 +10523,9 @@ func (m *UsageLogMutation) ClearField(name string) error {
|
||||
case usagelog.FieldFirstTokenMs:
|
||||
m.ClearFirstTokenMs()
|
||||
return nil
|
||||
case usagelog.FieldUserAgent:
|
||||
m.ClearUserAgent()
|
||||
return nil
|
||||
case usagelog.FieldImageSize:
|
||||
m.ClearImageSize()
|
||||
return nil
|
||||
@@ -10539,6 +10609,9 @@ func (m *UsageLogMutation) ResetField(name string) error {
|
||||
case usagelog.FieldFirstTokenMs:
|
||||
m.ResetFirstTokenMs()
|
||||
return nil
|
||||
case usagelog.FieldUserAgent:
|
||||
m.ResetUserAgent()
|
||||
return nil
|
||||
case usagelog.FieldImageCount:
|
||||
m.ResetImageCount()
|
||||
return nil
|
||||
|
||||
@@ -521,16 +521,20 @@ func init() {
|
||||
usagelogDescStream := usagelogFields[21].Descriptor()
|
||||
// usagelog.DefaultStream holds the default value on creation for the stream field.
|
||||
usagelog.DefaultStream = usagelogDescStream.Default.(bool)
|
||||
// usagelogDescUserAgent is the schema descriptor for user_agent field.
|
||||
usagelogDescUserAgent := usagelogFields[24].Descriptor()
|
||||
// usagelog.UserAgentValidator is a validator for the "user_agent" field. It is called by the builders before save.
|
||||
usagelog.UserAgentValidator = usagelogDescUserAgent.Validators[0].(func(string) error)
|
||||
// usagelogDescImageCount is the schema descriptor for image_count field.
|
||||
usagelogDescImageCount := usagelogFields[24].Descriptor()
|
||||
usagelogDescImageCount := usagelogFields[25].Descriptor()
|
||||
// usagelog.DefaultImageCount holds the default value on creation for the image_count field.
|
||||
usagelog.DefaultImageCount = usagelogDescImageCount.Default.(int)
|
||||
// usagelogDescImageSize is the schema descriptor for image_size field.
|
||||
usagelogDescImageSize := usagelogFields[25].Descriptor()
|
||||
usagelogDescImageSize := usagelogFields[26].Descriptor()
|
||||
// usagelog.ImageSizeValidator is a validator for the "image_size" field. It is called by the builders before save.
|
||||
usagelog.ImageSizeValidator = usagelogDescImageSize.Validators[0].(func(string) error)
|
||||
// usagelogDescCreatedAt is the schema descriptor for created_at field.
|
||||
usagelogDescCreatedAt := usagelogFields[26].Descriptor()
|
||||
usagelogDescCreatedAt := usagelogFields[27].Descriptor()
|
||||
// usagelog.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||
usagelog.DefaultCreatedAt = usagelogDescCreatedAt.Default.(func() time.Time)
|
||||
userMixin := schema.User{}.Mixin()
|
||||
|
||||
@@ -96,6 +96,10 @@ func (UsageLog) Fields() []ent.Field {
|
||||
field.Int("first_token_ms").
|
||||
Optional().
|
||||
Nillable(),
|
||||
field.String("user_agent").
|
||||
MaxLen(512).
|
||||
Optional().
|
||||
Nillable(),
|
||||
|
||||
// 图片生成字段(仅 gemini-3-pro-image 等图片模型使用)
|
||||
field.Int("image_count").
|
||||
|
||||
@@ -70,6 +70,8 @@ type UsageLog struct {
|
||||
DurationMs *int `json:"duration_ms,omitempty"`
|
||||
// FirstTokenMs holds the value of the "first_token_ms" field.
|
||||
FirstTokenMs *int `json:"first_token_ms,omitempty"`
|
||||
// UserAgent holds the value of the "user_agent" field.
|
||||
UserAgent *string `json:"user_agent,omitempty"`
|
||||
// ImageCount holds the value of the "image_count" field.
|
||||
ImageCount int `json:"image_count,omitempty"`
|
||||
// ImageSize holds the value of the "image_size" field.
|
||||
@@ -165,7 +167,7 @@ func (*UsageLog) scanValues(columns []string) ([]any, error) {
|
||||
values[i] = new(sql.NullFloat64)
|
||||
case usagelog.FieldID, usagelog.FieldUserID, usagelog.FieldAPIKeyID, usagelog.FieldAccountID, usagelog.FieldGroupID, usagelog.FieldSubscriptionID, usagelog.FieldInputTokens, usagelog.FieldOutputTokens, usagelog.FieldCacheCreationTokens, usagelog.FieldCacheReadTokens, usagelog.FieldCacheCreation5mTokens, usagelog.FieldCacheCreation1hTokens, usagelog.FieldBillingType, usagelog.FieldDurationMs, usagelog.FieldFirstTokenMs, usagelog.FieldImageCount:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case usagelog.FieldRequestID, usagelog.FieldModel, usagelog.FieldImageSize:
|
||||
case usagelog.FieldRequestID, usagelog.FieldModel, usagelog.FieldUserAgent, usagelog.FieldImageSize:
|
||||
values[i] = new(sql.NullString)
|
||||
case usagelog.FieldCreatedAt:
|
||||
values[i] = new(sql.NullTime)
|
||||
@@ -338,6 +340,13 @@ func (_m *UsageLog) assignValues(columns []string, values []any) error {
|
||||
_m.FirstTokenMs = new(int)
|
||||
*_m.FirstTokenMs = int(value.Int64)
|
||||
}
|
||||
case usagelog.FieldUserAgent:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field user_agent", values[i])
|
||||
} else if value.Valid {
|
||||
_m.UserAgent = new(string)
|
||||
*_m.UserAgent = value.String
|
||||
}
|
||||
case usagelog.FieldImageCount:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field image_count", values[i])
|
||||
@@ -498,6 +507,11 @@ func (_m *UsageLog) String() string {
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.UserAgent; v != nil {
|
||||
builder.WriteString("user_agent=")
|
||||
builder.WriteString(*v)
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("image_count=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.ImageCount))
|
||||
builder.WriteString(", ")
|
||||
|
||||
@@ -62,6 +62,8 @@ const (
|
||||
FieldDurationMs = "duration_ms"
|
||||
// FieldFirstTokenMs holds the string denoting the first_token_ms field in the database.
|
||||
FieldFirstTokenMs = "first_token_ms"
|
||||
// FieldUserAgent holds the string denoting the user_agent field in the database.
|
||||
FieldUserAgent = "user_agent"
|
||||
// FieldImageCount holds the string denoting the image_count field in the database.
|
||||
FieldImageCount = "image_count"
|
||||
// FieldImageSize holds the string denoting the image_size field in the database.
|
||||
@@ -144,6 +146,7 @@ var Columns = []string{
|
||||
FieldStream,
|
||||
FieldDurationMs,
|
||||
FieldFirstTokenMs,
|
||||
FieldUserAgent,
|
||||
FieldImageCount,
|
||||
FieldImageSize,
|
||||
FieldCreatedAt,
|
||||
@@ -194,6 +197,8 @@ var (
|
||||
DefaultBillingType int8
|
||||
// DefaultStream holds the default value on creation for the "stream" field.
|
||||
DefaultStream bool
|
||||
// UserAgentValidator is a validator for the "user_agent" field. It is called by the builders before save.
|
||||
UserAgentValidator func(string) error
|
||||
// DefaultImageCount holds the default value on creation for the "image_count" field.
|
||||
DefaultImageCount int
|
||||
// ImageSizeValidator is a validator for the "image_size" field. It is called by the builders before save.
|
||||
@@ -330,6 +335,11 @@ func ByFirstTokenMs(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldFirstTokenMs, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByUserAgent orders the results by the user_agent field.
|
||||
func ByUserAgent(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldUserAgent, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByImageCount orders the results by the image_count field.
|
||||
func ByImageCount(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldImageCount, opts...).ToFunc()
|
||||
|
||||
@@ -175,6 +175,11 @@ func FirstTokenMs(v int) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldEQ(FieldFirstTokenMs, v))
|
||||
}
|
||||
|
||||
// UserAgent applies equality check predicate on the "user_agent" field. It's identical to UserAgentEQ.
|
||||
func UserAgent(v string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldEQ(FieldUserAgent, v))
|
||||
}
|
||||
|
||||
// ImageCount applies equality check predicate on the "image_count" field. It's identical to ImageCountEQ.
|
||||
func ImageCount(v int) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldEQ(FieldImageCount, v))
|
||||
@@ -1110,6 +1115,81 @@ func FirstTokenMsNotNil() predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldNotNull(FieldFirstTokenMs))
|
||||
}
|
||||
|
||||
// UserAgentEQ applies the EQ predicate on the "user_agent" field.
|
||||
func UserAgentEQ(v string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldEQ(FieldUserAgent, v))
|
||||
}
|
||||
|
||||
// UserAgentNEQ applies the NEQ predicate on the "user_agent" field.
|
||||
func UserAgentNEQ(v string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldNEQ(FieldUserAgent, v))
|
||||
}
|
||||
|
||||
// UserAgentIn applies the In predicate on the "user_agent" field.
|
||||
func UserAgentIn(vs ...string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldIn(FieldUserAgent, vs...))
|
||||
}
|
||||
|
||||
// UserAgentNotIn applies the NotIn predicate on the "user_agent" field.
|
||||
func UserAgentNotIn(vs ...string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldNotIn(FieldUserAgent, vs...))
|
||||
}
|
||||
|
||||
// UserAgentGT applies the GT predicate on the "user_agent" field.
|
||||
func UserAgentGT(v string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldGT(FieldUserAgent, v))
|
||||
}
|
||||
|
||||
// UserAgentGTE applies the GTE predicate on the "user_agent" field.
|
||||
func UserAgentGTE(v string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldGTE(FieldUserAgent, v))
|
||||
}
|
||||
|
||||
// UserAgentLT applies the LT predicate on the "user_agent" field.
|
||||
func UserAgentLT(v string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldLT(FieldUserAgent, v))
|
||||
}
|
||||
|
||||
// UserAgentLTE applies the LTE predicate on the "user_agent" field.
|
||||
func UserAgentLTE(v string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldLTE(FieldUserAgent, v))
|
||||
}
|
||||
|
||||
// UserAgentContains applies the Contains predicate on the "user_agent" field.
|
||||
func UserAgentContains(v string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldContains(FieldUserAgent, v))
|
||||
}
|
||||
|
||||
// UserAgentHasPrefix applies the HasPrefix predicate on the "user_agent" field.
|
||||
func UserAgentHasPrefix(v string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldHasPrefix(FieldUserAgent, v))
|
||||
}
|
||||
|
||||
// UserAgentHasSuffix applies the HasSuffix predicate on the "user_agent" field.
|
||||
func UserAgentHasSuffix(v string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldHasSuffix(FieldUserAgent, v))
|
||||
}
|
||||
|
||||
// UserAgentIsNil applies the IsNil predicate on the "user_agent" field.
|
||||
func UserAgentIsNil() predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldIsNull(FieldUserAgent))
|
||||
}
|
||||
|
||||
// UserAgentNotNil applies the NotNil predicate on the "user_agent" field.
|
||||
func UserAgentNotNil() predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldNotNull(FieldUserAgent))
|
||||
}
|
||||
|
||||
// UserAgentEqualFold applies the EqualFold predicate on the "user_agent" field.
|
||||
func UserAgentEqualFold(v string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldEqualFold(FieldUserAgent, v))
|
||||
}
|
||||
|
||||
// UserAgentContainsFold applies the ContainsFold predicate on the "user_agent" field.
|
||||
func UserAgentContainsFold(v string) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldContainsFold(FieldUserAgent, v))
|
||||
}
|
||||
|
||||
// ImageCountEQ applies the EQ predicate on the "image_count" field.
|
||||
func ImageCountEQ(v int) predicate.UsageLog {
|
||||
return predicate.UsageLog(sql.FieldEQ(FieldImageCount, v))
|
||||
|
||||
@@ -323,6 +323,20 @@ func (_c *UsageLogCreate) SetNillableFirstTokenMs(v *int) *UsageLogCreate {
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetUserAgent sets the "user_agent" field.
|
||||
func (_c *UsageLogCreate) SetUserAgent(v string) *UsageLogCreate {
|
||||
_c.mutation.SetUserAgent(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableUserAgent sets the "user_agent" field if the given value is not nil.
|
||||
func (_c *UsageLogCreate) SetNillableUserAgent(v *string) *UsageLogCreate {
|
||||
if v != nil {
|
||||
_c.SetUserAgent(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetImageCount sets the "image_count" field.
|
||||
func (_c *UsageLogCreate) SetImageCount(v int) *UsageLogCreate {
|
||||
_c.mutation.SetImageCount(v)
|
||||
@@ -567,6 +581,11 @@ func (_c *UsageLogCreate) check() error {
|
||||
if _, ok := _c.mutation.Stream(); !ok {
|
||||
return &ValidationError{Name: "stream", err: errors.New(`ent: missing required field "UsageLog.stream"`)}
|
||||
}
|
||||
if v, ok := _c.mutation.UserAgent(); ok {
|
||||
if err := usagelog.UserAgentValidator(v); err != nil {
|
||||
return &ValidationError{Name: "user_agent", err: fmt.Errorf(`ent: validator failed for field "UsageLog.user_agent": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.ImageCount(); !ok {
|
||||
return &ValidationError{Name: "image_count", err: errors.New(`ent: missing required field "UsageLog.image_count"`)}
|
||||
}
|
||||
@@ -690,6 +709,10 @@ func (_c *UsageLogCreate) createSpec() (*UsageLog, *sqlgraph.CreateSpec) {
|
||||
_spec.SetField(usagelog.FieldFirstTokenMs, field.TypeInt, value)
|
||||
_node.FirstTokenMs = &value
|
||||
}
|
||||
if value, ok := _c.mutation.UserAgent(); ok {
|
||||
_spec.SetField(usagelog.FieldUserAgent, field.TypeString, value)
|
||||
_node.UserAgent = &value
|
||||
}
|
||||
if value, ok := _c.mutation.ImageCount(); ok {
|
||||
_spec.SetField(usagelog.FieldImageCount, field.TypeInt, value)
|
||||
_node.ImageCount = value
|
||||
@@ -1247,6 +1270,24 @@ func (u *UsageLogUpsert) ClearFirstTokenMs() *UsageLogUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetUserAgent sets the "user_agent" field.
|
||||
func (u *UsageLogUpsert) SetUserAgent(v string) *UsageLogUpsert {
|
||||
u.Set(usagelog.FieldUserAgent, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateUserAgent sets the "user_agent" field to the value that was provided on create.
|
||||
func (u *UsageLogUpsert) UpdateUserAgent() *UsageLogUpsert {
|
||||
u.SetExcluded(usagelog.FieldUserAgent)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearUserAgent clears the value of the "user_agent" field.
|
||||
func (u *UsageLogUpsert) ClearUserAgent() *UsageLogUpsert {
|
||||
u.SetNull(usagelog.FieldUserAgent)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetImageCount sets the "image_count" field.
|
||||
func (u *UsageLogUpsert) SetImageCount(v int) *UsageLogUpsert {
|
||||
u.Set(usagelog.FieldImageCount, v)
|
||||
@@ -1804,6 +1845,27 @@ func (u *UsageLogUpsertOne) ClearFirstTokenMs() *UsageLogUpsertOne {
|
||||
})
|
||||
}
|
||||
|
||||
// SetUserAgent sets the "user_agent" field.
|
||||
func (u *UsageLogUpsertOne) SetUserAgent(v string) *UsageLogUpsertOne {
|
||||
return u.Update(func(s *UsageLogUpsert) {
|
||||
s.SetUserAgent(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUserAgent sets the "user_agent" field to the value that was provided on create.
|
||||
func (u *UsageLogUpsertOne) UpdateUserAgent() *UsageLogUpsertOne {
|
||||
return u.Update(func(s *UsageLogUpsert) {
|
||||
s.UpdateUserAgent()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearUserAgent clears the value of the "user_agent" field.
|
||||
func (u *UsageLogUpsertOne) ClearUserAgent() *UsageLogUpsertOne {
|
||||
return u.Update(func(s *UsageLogUpsert) {
|
||||
s.ClearUserAgent()
|
||||
})
|
||||
}
|
||||
|
||||
// SetImageCount sets the "image_count" field.
|
||||
func (u *UsageLogUpsertOne) SetImageCount(v int) *UsageLogUpsertOne {
|
||||
return u.Update(func(s *UsageLogUpsert) {
|
||||
@@ -2533,6 +2595,27 @@ func (u *UsageLogUpsertBulk) ClearFirstTokenMs() *UsageLogUpsertBulk {
|
||||
})
|
||||
}
|
||||
|
||||
// SetUserAgent sets the "user_agent" field.
|
||||
func (u *UsageLogUpsertBulk) SetUserAgent(v string) *UsageLogUpsertBulk {
|
||||
return u.Update(func(s *UsageLogUpsert) {
|
||||
s.SetUserAgent(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUserAgent sets the "user_agent" field to the value that was provided on create.
|
||||
func (u *UsageLogUpsertBulk) UpdateUserAgent() *UsageLogUpsertBulk {
|
||||
return u.Update(func(s *UsageLogUpsert) {
|
||||
s.UpdateUserAgent()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearUserAgent clears the value of the "user_agent" field.
|
||||
func (u *UsageLogUpsertBulk) ClearUserAgent() *UsageLogUpsertBulk {
|
||||
return u.Update(func(s *UsageLogUpsert) {
|
||||
s.ClearUserAgent()
|
||||
})
|
||||
}
|
||||
|
||||
// SetImageCount sets the "image_count" field.
|
||||
func (u *UsageLogUpsertBulk) SetImageCount(v int) *UsageLogUpsertBulk {
|
||||
return u.Update(func(s *UsageLogUpsert) {
|
||||
|
||||
@@ -504,6 +504,26 @@ func (_u *UsageLogUpdate) ClearFirstTokenMs() *UsageLogUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetUserAgent sets the "user_agent" field.
|
||||
func (_u *UsageLogUpdate) SetUserAgent(v string) *UsageLogUpdate {
|
||||
_u.mutation.SetUserAgent(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableUserAgent sets the "user_agent" field if the given value is not nil.
|
||||
func (_u *UsageLogUpdate) SetNillableUserAgent(v *string) *UsageLogUpdate {
|
||||
if v != nil {
|
||||
_u.SetUserAgent(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearUserAgent clears the value of the "user_agent" field.
|
||||
func (_u *UsageLogUpdate) ClearUserAgent() *UsageLogUpdate {
|
||||
_u.mutation.ClearUserAgent()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetImageCount sets the "image_count" field.
|
||||
func (_u *UsageLogUpdate) SetImageCount(v int) *UsageLogUpdate {
|
||||
_u.mutation.ResetImageCount()
|
||||
@@ -644,6 +664,11 @@ func (_u *UsageLogUpdate) check() error {
|
||||
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.model": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.UserAgent(); ok {
|
||||
if err := usagelog.UserAgentValidator(v); err != nil {
|
||||
return &ValidationError{Name: "user_agent", err: fmt.Errorf(`ent: validator failed for field "UsageLog.user_agent": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.ImageSize(); ok {
|
||||
if err := usagelog.ImageSizeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "image_size", err: fmt.Errorf(`ent: validator failed for field "UsageLog.image_size": %w`, err)}
|
||||
@@ -784,6 +809,12 @@ func (_u *UsageLogUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if _u.mutation.FirstTokenMsCleared() {
|
||||
_spec.ClearField(usagelog.FieldFirstTokenMs, field.TypeInt)
|
||||
}
|
||||
if value, ok := _u.mutation.UserAgent(); ok {
|
||||
_spec.SetField(usagelog.FieldUserAgent, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.UserAgentCleared() {
|
||||
_spec.ClearField(usagelog.FieldUserAgent, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.ImageCount(); ok {
|
||||
_spec.SetField(usagelog.FieldImageCount, field.TypeInt, value)
|
||||
}
|
||||
@@ -1433,6 +1464,26 @@ func (_u *UsageLogUpdateOne) ClearFirstTokenMs() *UsageLogUpdateOne {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetUserAgent sets the "user_agent" field.
|
||||
func (_u *UsageLogUpdateOne) SetUserAgent(v string) *UsageLogUpdateOne {
|
||||
_u.mutation.SetUserAgent(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableUserAgent sets the "user_agent" field if the given value is not nil.
|
||||
func (_u *UsageLogUpdateOne) SetNillableUserAgent(v *string) *UsageLogUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetUserAgent(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearUserAgent clears the value of the "user_agent" field.
|
||||
func (_u *UsageLogUpdateOne) ClearUserAgent() *UsageLogUpdateOne {
|
||||
_u.mutation.ClearUserAgent()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetImageCount sets the "image_count" field.
|
||||
func (_u *UsageLogUpdateOne) SetImageCount(v int) *UsageLogUpdateOne {
|
||||
_u.mutation.ResetImageCount()
|
||||
@@ -1586,6 +1637,11 @@ func (_u *UsageLogUpdateOne) check() error {
|
||||
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.model": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.UserAgent(); ok {
|
||||
if err := usagelog.UserAgentValidator(v); err != nil {
|
||||
return &ValidationError{Name: "user_agent", err: fmt.Errorf(`ent: validator failed for field "UsageLog.user_agent": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.ImageSize(); ok {
|
||||
if err := usagelog.ImageSizeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "image_size", err: fmt.Errorf(`ent: validator failed for field "UsageLog.image_size": %w`, err)}
|
||||
@@ -1743,6 +1799,12 @@ func (_u *UsageLogUpdateOne) sqlSave(ctx context.Context) (_node *UsageLog, err
|
||||
if _u.mutation.FirstTokenMsCleared() {
|
||||
_spec.ClearField(usagelog.FieldFirstTokenMs, field.TypeInt)
|
||||
}
|
||||
if value, ok := _u.mutation.UserAgent(); ok {
|
||||
_spec.SetField(usagelog.FieldUserAgent, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.UserAgentCleared() {
|
||||
_spec.ClearField(usagelog.FieldUserAgent, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.ImageCount(); ok {
|
||||
_spec.SetField(usagelog.FieldImageCount, field.TypeInt, value)
|
||||
}
|
||||
|
||||
@@ -108,6 +108,9 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
|
||||
// 获取订阅信息(可能为nil)- 提前获取用于后续检查
|
||||
subscription, _ := middleware2.GetSubscriptionFromContext(c)
|
||||
|
||||
// 获取 User-Agent
|
||||
userAgent := c.Request.UserAgent()
|
||||
|
||||
// 0. 检查wait队列是否已满
|
||||
maxWait := service.CalculateMaxWait(subject.Concurrency)
|
||||
canWait, err := h.concurrencyHelper.IncrementWaitCount(c.Request.Context(), subject.UserID, maxWait)
|
||||
@@ -267,7 +270,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 异步记录使用量(subscription已在函数开头获取)
|
||||
go func(result *service.ForwardResult, usedAccount *service.Account) {
|
||||
go func(result *service.ForwardResult, usedAccount *service.Account, ua string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
if err := h.gatewayService.RecordUsage(ctx, &service.RecordUsageInput{
|
||||
@@ -276,10 +279,11 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
|
||||
User: apiKey.User,
|
||||
Account: usedAccount,
|
||||
Subscription: subscription,
|
||||
UserAgent: ua,
|
||||
}); err != nil {
|
||||
log.Printf("Record usage failed: %v", err)
|
||||
}
|
||||
}(result, account)
|
||||
}(result, account, userAgent)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -394,7 +398,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 异步记录使用量(subscription已在函数开头获取)
|
||||
go func(result *service.ForwardResult, usedAccount *service.Account) {
|
||||
go func(result *service.ForwardResult, usedAccount *service.Account, ua string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
if err := h.gatewayService.RecordUsage(ctx, &service.RecordUsageInput{
|
||||
@@ -403,10 +407,11 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
|
||||
User: apiKey.User,
|
||||
Account: usedAccount,
|
||||
Subscription: subscription,
|
||||
UserAgent: ua,
|
||||
}); err != nil {
|
||||
log.Printf("Record usage failed: %v", err)
|
||||
}
|
||||
}(result, account)
|
||||
}(result, account, userAgent)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,9 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) {
|
||||
// Get subscription (may be nil)
|
||||
subscription, _ := middleware.GetSubscriptionFromContext(c)
|
||||
|
||||
// 获取 User-Agent
|
||||
userAgent := c.Request.UserAgent()
|
||||
|
||||
// For Gemini native API, do not send Claude-style ping frames.
|
||||
geminiConcurrency := NewConcurrencyHelper(h.concurrencyHelper.concurrencyService, SSEPingFormatNone, 0)
|
||||
|
||||
@@ -300,7 +303,7 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 6) record usage async
|
||||
go func(result *service.ForwardResult, usedAccount *service.Account) {
|
||||
go func(result *service.ForwardResult, usedAccount *service.Account, ua string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
if err := h.gatewayService.RecordUsage(ctx, &service.RecordUsageInput{
|
||||
@@ -309,10 +312,11 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) {
|
||||
User: apiKey.User,
|
||||
Account: usedAccount,
|
||||
Subscription: subscription,
|
||||
UserAgent: ua,
|
||||
}); err != nil {
|
||||
log.Printf("Record usage failed: %v", err)
|
||||
}
|
||||
}(result, account)
|
||||
}(result, account, userAgent)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Async record usage
|
||||
go func(result *service.OpenAIForwardResult, usedAccount *service.Account) {
|
||||
go func(result *service.OpenAIForwardResult, usedAccount *service.Account, ua string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
if err := h.gatewayService.RecordUsage(ctx, &service.OpenAIRecordUsageInput{
|
||||
@@ -251,10 +251,11 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
|
||||
User: apiKey.User,
|
||||
Account: usedAccount,
|
||||
Subscription: subscription,
|
||||
UserAgent: ua,
|
||||
}); err != nil {
|
||||
log.Printf("Record usage failed: %v", err)
|
||||
}
|
||||
}(result, account)
|
||||
}(result, account, userAgent)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
const usageLogSelectColumns = "id, user_id, api_key_id, account_id, request_id, model, group_id, subscription_id, input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens, cache_creation_5m_tokens, cache_creation_1h_tokens, input_cost, output_cost, cache_creation_cost, cache_read_cost, total_cost, actual_cost, rate_multiplier, billing_type, stream, duration_ms, first_token_ms, image_count, image_size, created_at"
|
||||
const usageLogSelectColumns = "id, user_id, api_key_id, account_id, request_id, model, group_id, subscription_id, input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens, cache_creation_5m_tokens, cache_creation_1h_tokens, input_cost, output_cost, cache_creation_cost, cache_read_cost, total_cost, actual_cost, rate_multiplier, billing_type, stream, duration_ms, first_token_ms, user_agent, image_count, image_size, created_at"
|
||||
|
||||
type usageLogRepository struct {
|
||||
client *dbent.Client
|
||||
@@ -109,6 +109,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
|
||||
stream,
|
||||
duration_ms,
|
||||
first_token_ms,
|
||||
user_agent,
|
||||
image_count,
|
||||
image_size,
|
||||
created_at
|
||||
@@ -118,8 +119,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
|
||||
$8, $9, $10, $11,
|
||||
$12, $13,
|
||||
$14, $15, $16, $17, $18, $19,
|
||||
$20, $21, $22, $23, $24,
|
||||
$25, $26, $27
|
||||
$20, $21, $22, $23, $24, $25, $26, $27, $28
|
||||
)
|
||||
ON CONFLICT (request_id, api_key_id) DO NOTHING
|
||||
RETURNING id, created_at
|
||||
@@ -129,6 +129,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
|
||||
subscriptionID := nullInt64(log.SubscriptionID)
|
||||
duration := nullInt(log.DurationMs)
|
||||
firstToken := nullInt(log.FirstTokenMs)
|
||||
userAgent := nullString(log.UserAgent)
|
||||
imageSize := nullString(log.ImageSize)
|
||||
|
||||
var requestIDArg any
|
||||
@@ -161,6 +162,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
|
||||
log.Stream,
|
||||
duration,
|
||||
firstToken,
|
||||
userAgent,
|
||||
log.ImageCount,
|
||||
imageSize,
|
||||
createdAt,
|
||||
@@ -1870,6 +1872,7 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
|
||||
stream bool
|
||||
durationMs sql.NullInt64
|
||||
firstTokenMs sql.NullInt64
|
||||
userAgent sql.NullString
|
||||
imageCount int
|
||||
imageSize sql.NullString
|
||||
createdAt time.Time
|
||||
@@ -1901,6 +1904,7 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
|
||||
&stream,
|
||||
&durationMs,
|
||||
&firstTokenMs,
|
||||
&userAgent,
|
||||
&imageCount,
|
||||
&imageSize,
|
||||
&createdAt,
|
||||
@@ -1952,6 +1956,9 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
|
||||
value := int(firstTokenMs.Int64)
|
||||
log.FirstTokenMs = &value
|
||||
}
|
||||
if userAgent.Valid {
|
||||
log.UserAgent = &userAgent.String
|
||||
}
|
||||
if imageSize.Valid {
|
||||
log.ImageSize = &imageSize.String
|
||||
}
|
||||
|
||||
@@ -2152,6 +2152,7 @@ type RecordUsageInput struct {
|
||||
User *User
|
||||
Account *Account
|
||||
Subscription *UserSubscription // 可选:订阅信息
|
||||
UserAgent string // 请求的 User-Agent
|
||||
}
|
||||
|
||||
// RecordUsage 记录使用量并扣费(或更新订阅用量)
|
||||
@@ -2237,6 +2238,11 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// 添加 UserAgent
|
||||
if input.UserAgent != "" {
|
||||
usageLog.UserAgent = &input.UserAgent
|
||||
}
|
||||
|
||||
// 添加分组和订阅关联
|
||||
if apiKey.GroupID != nil {
|
||||
usageLog.GroupID = apiKey.GroupID
|
||||
|
||||
@@ -1092,6 +1092,7 @@ type OpenAIRecordUsageInput struct {
|
||||
User *User
|
||||
Account *Account
|
||||
Subscription *UserSubscription
|
||||
UserAgent string // 请求的 User-Agent
|
||||
}
|
||||
|
||||
// RecordUsage records usage and deducts balance
|
||||
@@ -1161,6 +1162,11 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// 添加 UserAgent
|
||||
if input.UserAgent != "" {
|
||||
usageLog.UserAgent = &input.UserAgent
|
||||
}
|
||||
|
||||
if apiKey.GroupID != nil {
|
||||
usageLog.GroupID = apiKey.GroupID
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ type UsageLog struct {
|
||||
Stream bool
|
||||
DurationMs *int
|
||||
FirstTokenMs *int
|
||||
UserAgent *string
|
||||
|
||||
// 图片生成字段
|
||||
ImageCount int
|
||||
|
||||
10
backend/migrations/028_add_usage_logs_user_agent.sql
Normal file
10
backend/migrations/028_add_usage_logs_user_agent.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
-- Add user_agent column to usage_logs table
|
||||
-- Records the User-Agent header from API requests for analytics and debugging
|
||||
|
||||
ALTER TABLE usage_logs
|
||||
ADD COLUMN IF NOT EXISTS user_agent VARCHAR(512);
|
||||
|
||||
-- Optional: Add index for user_agent queries (uncomment if needed for analytics)
|
||||
-- CREATE INDEX IF NOT EXISTS idx_usage_logs_user_agent ON usage_logs(user_agent);
|
||||
|
||||
COMMENT ON COLUMN usage_logs.user_agent IS 'User-Agent header from the API request';
|
||||
Reference in New Issue
Block a user