diff --git a/api/admin/admin.go b/api/admin/admin.go index ebfa2dd..a981171 100644 --- a/api/admin/admin.go +++ b/api/admin/admin.go @@ -208,6 +208,10 @@ type IAdminV1 interface { TenantExport(ctx context.Context, req *v1.TenantExportReq) (res *v1.TenantExportRes, err error) AdminMemberExport(ctx context.Context, req *v1.AdminMemberExportReq) (res *v1.AdminMemberExportRes, err error) TenantSelect(ctx context.Context, req *v1.TenantSelectReq) (res *v1.TenantSelectRes, err error) + TenantLevelConfigList(ctx context.Context, req *v1.TenantLevelConfigListReq) (res *v1.TenantLevelConfigListRes, err error) + TenantLevelConfigCreate(ctx context.Context, req *v1.TenantLevelConfigCreateReq) (res *v1.TenantLevelConfigCreateRes, err error) + TenantLevelConfigUpdate(ctx context.Context, req *v1.TenantLevelConfigUpdateReq) (res *v1.TenantLevelConfigUpdateRes, err error) + TenantLevelConfigDelete(ctx context.Context, req *v1.TenantLevelConfigDeleteReq) (res *v1.TenantLevelConfigDeleteRes, err error) TenantModelList(ctx context.Context, req *v1.TenantModelListReq) (res *v1.TenantModelListRes, err error) TenantModelBatchAssign(ctx context.Context, req *v1.TenantModelBatchAssignReq) (res *v1.TenantModelBatchAssignRes, err error) TenantModelUpdate(ctx context.Context, req *v1.TenantModelUpdateReq) (res *v1.TenantModelUpdateRes, err error) diff --git a/api/admin/v1/tenant.go b/api/admin/v1/tenant.go index d3eee21..6bbd504 100644 --- a/api/admin/v1/tenant.go +++ b/api/admin/v1/tenant.go @@ -47,6 +47,8 @@ type TenantItem struct { DefaultChannelScope string `json:"default_channel_scope"` MemberCount int `json:"member_count"` WalletBalance string `json:"wallet_balance"` + Level int `json:"level"` + LevelName string `json:"level_name"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } @@ -85,6 +87,7 @@ type TenantUpdateReq struct { Name string `json:"name" dc:"租户名称"` MaxMembers *int `json:"max_members" dc:"最大成员数"` MaxConcurrency *int `json:"max_concurrency" dc:"租户总并发上限(0表示不限制)"` + Level *int `json:"level" dc:"租户等级(调整等级会同步更新成员数和并发数为该等级的配置值)"` } type TenantUpdateRes struct{} diff --git a/api/admin/v1/tenant_level.go b/api/admin/v1/tenant_level.go new file mode 100644 index 0000000..00e8fd1 --- /dev/null +++ b/api/admin/v1/tenant_level.go @@ -0,0 +1,59 @@ +package v1 + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// === 租户等级配置管理 === + +type TenantLevelConfigItem struct { + Id int64 `json:"id"` + Level int `json:"level"` + Name string `json:"name"` + CumulativeRechargeThreshold float64 `json:"cumulative_recharge_threshold"` + MaxMembers int `json:"max_members"` + MaxConcurrency int `json:"max_concurrency"` + PriceMultiplier float64 `json:"price_multiplier"` + SortOrder int `json:"sort_order"` + CreatedAt *gtime.Time `json:"created_at"` + UpdatedAt *gtime.Time `json:"updated_at"` +} + +type TenantLevelConfigListReq struct { + g.Meta `path:"/tenant-level-configs" method:"get" mime:"json" tags:"管理后台-租户等级" summary:"等级配置列表"` +} + +type TenantLevelConfigListRes struct { + List []*TenantLevelConfigItem `json:"list"` +} + +type TenantLevelConfigCreateReq struct { + g.Meta `path:"/tenant-level-configs" method:"post" mime:"json" tags:"管理后台-租户等级" summary:"创建等级配置"` + Level int `json:"level" v:"required|min:1#请输入等级号|等级号最小为1"` + Name string `json:"name" v:"required#请输入等级名称"` + CumulativeRechargeThreshold float64 `json:"cumulative_recharge_threshold" v:"required|min:0#请输入累计充值阈值|阈值不能为负数"` + MaxMembers int `json:"max_members" v:"min:0#最大成员数不能为负数"` + MaxConcurrency int `json:"max_concurrency" v:"min:0#最大并发数不能为负数"` + PriceMultiplier float64 `json:"price_multiplier" d:"1.0000"` + SortOrder int `json:"sort_order" d:"0"` +} + +type TenantLevelConfigCreateRes struct { + ID int64 `json:"id"` +} + +type TenantLevelConfigUpdateReq struct { + g.Meta `path:"/tenant-level-configs/{id}" method:"put" mime:"json" tags:"管理后台-租户等级" summary:"更新等级配置"` + Id int64 `json:"id" in:"path" v:"required|min:1"` + Update map[string]interface{} `json:"update" v:"required"` +} + +type TenantLevelConfigUpdateRes struct{} + +type TenantLevelConfigDeleteReq struct { + g.Meta `path:"/tenant-level-configs/{id}" method:"delete" mime:"json" tags:"管理后台-租户等级" summary:"删除等级配置"` + Id int64 `json:"id" in:"path" v:"required|min:1"` +} + +type TenantLevelConfigDeleteRes struct{} diff --git a/api/tenant/v1/organization.go b/api/tenant/v1/organization.go index 9186d27..d69512d 100644 --- a/api/tenant/v1/organization.go +++ b/api/tenant/v1/organization.go @@ -13,6 +13,8 @@ type TenantOrgInfoRes struct { Code string `json:"code"` LogoURL string `json:"logo_url"` Status string `json:"status"` + Level int `json:"level"` + LevelName string `json:"level_name"` MaxMembers int `json:"max_members"` MemberCount int `json:"member_count"` CreatedAt string `json:"created_at"` diff --git a/api/tenant/v1/wallet_tenant.go b/api/tenant/v1/wallet_tenant.go index 357307c..d289657 100644 --- a/api/tenant/v1/wallet_tenant.go +++ b/api/tenant/v1/wallet_tenant.go @@ -9,11 +9,16 @@ type TenantWalletReq struct { } type TenantWalletRes struct { - Balance float64 `json:"balance"` - FrozenBalance float64 `json:"frozen_balance"` - AvailableBalance float64 `json:"available_balance"` - WarningThreshold float64 `json:"warning_threshold"` - Currency string `json:"currency"` + Balance float64 `json:"balance"` + FrozenBalance float64 `json:"frozen_balance"` + AvailableBalance float64 `json:"available_balance"` + WarningThreshold float64 `json:"warning_threshold"` + Currency string `json:"currency"` + Level int `json:"level"` + LevelName string `json:"level_name"` + CumulativeRecharge float64 `json:"cumulative_recharge"` + NextLevelThreshold float64 `json:"next_level_threshold"` + NextLevelName string `json:"next_level_name"` } type TenantWalletTransactionsReq struct { diff --git a/internal/controller/admin/admin_v1_tenant_level_config_create.go b/internal/controller/admin/admin_v1_tenant_level_config_create.go new file mode 100644 index 0000000..0399533 --- /dev/null +++ b/internal/controller/admin/admin_v1_tenant_level_config_create.go @@ -0,0 +1,12 @@ +package admin + +import ( + "context" + + v1 "github.com/qianfree/team-api/api/admin/v1" + "github.com/qianfree/team-api/internal/service" +) + +func (c *ControllerV1) TenantLevelConfigCreate(ctx context.Context, req *v1.TenantLevelConfigCreateReq) (res *v1.TenantLevelConfigCreateRes, err error) { + return service.Admin().CreateTenantLevelConfig(ctx, req) +} diff --git a/internal/controller/admin/admin_v1_tenant_level_config_delete.go b/internal/controller/admin/admin_v1_tenant_level_config_delete.go new file mode 100644 index 0000000..6bae9b8 --- /dev/null +++ b/internal/controller/admin/admin_v1_tenant_level_config_delete.go @@ -0,0 +1,12 @@ +package admin + +import ( + "context" + + v1 "github.com/qianfree/team-api/api/admin/v1" + "github.com/qianfree/team-api/internal/service" +) + +func (c *ControllerV1) TenantLevelConfigDelete(ctx context.Context, req *v1.TenantLevelConfigDeleteReq) (res *v1.TenantLevelConfigDeleteRes, err error) { + return service.Admin().DeleteTenantLevelConfig(ctx, req) +} diff --git a/internal/controller/admin/admin_v1_tenant_level_config_list.go b/internal/controller/admin/admin_v1_tenant_level_config_list.go new file mode 100644 index 0000000..4342f40 --- /dev/null +++ b/internal/controller/admin/admin_v1_tenant_level_config_list.go @@ -0,0 +1,12 @@ +package admin + +import ( + "context" + + v1 "github.com/qianfree/team-api/api/admin/v1" + "github.com/qianfree/team-api/internal/service" +) + +func (c *ControllerV1) TenantLevelConfigList(ctx context.Context, req *v1.TenantLevelConfigListReq) (res *v1.TenantLevelConfigListRes, err error) { + return service.Admin().ListTenantLevelConfigs(ctx, req) +} diff --git a/internal/controller/admin/admin_v1_tenant_level_config_update.go b/internal/controller/admin/admin_v1_tenant_level_config_update.go new file mode 100644 index 0000000..3610a75 --- /dev/null +++ b/internal/controller/admin/admin_v1_tenant_level_config_update.go @@ -0,0 +1,12 @@ +package admin + +import ( + "context" + + v1 "github.com/qianfree/team-api/api/admin/v1" + "github.com/qianfree/team-api/internal/service" +) + +func (c *ControllerV1) TenantLevelConfigUpdate(ctx context.Context, req *v1.TenantLevelConfigUpdateReq) (res *v1.TenantLevelConfigUpdateRes, err error) { + return service.Admin().UpdateTenantLevelConfig(ctx, req) +} diff --git a/internal/dao/internal/aud_content_filter_logs.go b/internal/dao/internal/aud_content_filter_logs.go index ea69ed5..9fbc4f5 100644 --- a/internal/dao/internal/aud_content_filter_logs.go +++ b/internal/dao/internal/aud_content_filter_logs.go @@ -25,6 +25,7 @@ type AudContentFilterLogsColumns struct { TenantId string // 租户ID UserId string // 用户ID ApiKeyId string // API Key ID + ProjectId string // 项目ID RequestId string // 请求唯一ID Method string // HTTP 方法 Path string // 请求路径 @@ -34,7 +35,6 @@ type AudContentFilterLogsColumns struct { OriginalSnippet string // 原始请求体片段(截断存储,仅 replace 模式) Blocked string // 是否被拦截(mode=block 时为 true) CreatedAt string // 创建时间 - ProjectId string // 项目ID } // audContentFilterLogsColumns holds the columns for the table aud_content_filter_logs. @@ -43,6 +43,7 @@ var audContentFilterLogsColumns = AudContentFilterLogsColumns{ TenantId: "tenant_id", UserId: "user_id", ApiKeyId: "api_key_id", + ProjectId: "project_id", RequestId: "request_id", Method: "method", Path: "path", @@ -52,7 +53,6 @@ var audContentFilterLogsColumns = AudContentFilterLogsColumns{ OriginalSnippet: "original_snippet", Blocked: "blocked", CreatedAt: "created_at", - ProjectId: "project_id", } // NewAudContentFilterLogsDao creates and returns a new DAO object for table data access. diff --git a/internal/dao/internal/bil_wallets.go b/internal/dao/internal/bil_wallets.go index 3c4cd21..8ff62d3 100644 --- a/internal/dao/internal/bil_wallets.go +++ b/internal/dao/internal/bil_wallets.go @@ -21,26 +21,28 @@ type BilWalletsDao struct { // BilWalletsColumns defines and stores column names for the table bil_wallets. type BilWalletsColumns struct { - Id string // 主键ID - TenantId string // 租户ID(每个租户一个钱包) - Balance string // 总余额 - FrozenBalance string // 冻结余额(支付中/退款中,可用余额 = balance - frozen_balance) - WarningThreshold string // 余额预警线(低于此值触发通知) - Currency string // 货币(USD) - CreatedAt string // 创建时间 - UpdatedAt string // 更新时间 + Id string // 主键ID + TenantId string // 租户ID(每个租户一个钱包) + Balance string // 总余额 + FrozenBalance string // 冻结余额(支付中/退款中,可用余额 = balance - frozen_balance) + WarningThreshold string // 余额预警线(低于此值触发通知) + Currency string // 货币(USD) + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 + CumulativeRecharge string // 累计充值总额(USD) } // bilWalletsColumns holds the columns for the table bil_wallets. var bilWalletsColumns = BilWalletsColumns{ - Id: "id", - TenantId: "tenant_id", - Balance: "balance", - FrozenBalance: "frozen_balance", - WarningThreshold: "warning_threshold", - Currency: "currency", - CreatedAt: "created_at", - UpdatedAt: "updated_at", + Id: "id", + TenantId: "tenant_id", + Balance: "balance", + FrozenBalance: "frozen_balance", + WarningThreshold: "warning_threshold", + Currency: "currency", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + CumulativeRecharge: "cumulative_recharge", } // NewBilWalletsDao creates and returns a new DAO object for table data access. diff --git a/internal/dao/internal/chn_channel_keys.go b/internal/dao/internal/chn_channel_keys.go index 5a21b93..218ce66 100644 --- a/internal/dao/internal/chn_channel_keys.go +++ b/internal/dao/internal/chn_channel_keys.go @@ -29,9 +29,9 @@ type ChnChannelKeysColumns struct { LastUsedAt string // 最后使用时间 LastError string // 最后一次错误信息 CreatedAt string // 创建时间 - UpdatedAt string // 更新时间 KeyType string // Key 类型:apikey(传统静态密钥)/ oauth(OAuth 令牌) TokenExpiresAt string // OAuth access_token 过期时间(仅 key_type=oauth 时有值) + UpdatedAt string // 更新时间 } // chnChannelKeysColumns holds the columns for the table chn_channel_keys. @@ -44,9 +44,9 @@ var chnChannelKeysColumns = ChnChannelKeysColumns{ LastUsedAt: "last_used_at", LastError: "last_error", CreatedAt: "created_at", - UpdatedAt: "updated_at", KeyType: "key_type", TokenExpiresAt: "token_expires_at", + UpdatedAt: "updated_at", } // NewChnChannelKeysDao creates and returns a new DAO object for table data access. diff --git a/internal/dao/internal/mdl_models.go b/internal/dao/internal/mdl_models.go index 6e7d88d..88fe9ec 100644 --- a/internal/dao/internal/mdl_models.go +++ b/internal/dao/internal/mdl_models.go @@ -30,12 +30,12 @@ type MdlModelsColumns struct { MaxOutputTokens string // 最大输出 token 数 Description string // 模型描述 Tags string // 标签(如 reasoning、vision、function_calling) + Capabilities string // 模型能力特性(如 vision、function_calling、reasoning 等) CreatedAt string // 创建时间 UpdatedAt string // 更新时间 DeprecatedAt string // 标记弃用的时间(NULL表示未弃用) SunsetDate string // 计划下线日期(到达后返回410 Gone,NULL表示未设置) ReplacementModel string // 推荐替代模型名(NULL表示无替代) - Capabilities string // 模型能力特性(如 vision、function_calling、reasoning 等) } // mdlModelsColumns holds the columns for the table mdl_models. @@ -49,12 +49,12 @@ var mdlModelsColumns = MdlModelsColumns{ MaxOutputTokens: "max_output_tokens", Description: "description", Tags: "tags", + Capabilities: "capabilities", CreatedAt: "created_at", UpdatedAt: "updated_at", DeprecatedAt: "deprecated_at", SunsetDate: "sunset_date", ReplacementModel: "replacement_model", - Capabilities: "capabilities", } // NewMdlModelsDao creates and returns a new DAO object for table data access. diff --git a/internal/dao/internal/ntf_messages.go b/internal/dao/internal/ntf_messages.go index c6d277b..e84cd42 100644 --- a/internal/dao/internal/ntf_messages.go +++ b/internal/dao/internal/ntf_messages.go @@ -31,8 +31,8 @@ type NtfMessagesColumns struct { IsRead string // 是否已读:0=未读, 1=已读 IsBroadcast string // 是否广播消息:0=个人消息, 1=广播消息 Metadata string // 附加元数据(JSONB,如关联资源ID、跳转链接等) - CreatedAt string // 创建时间 TargetRoles string // 目标角色(NULL=全部角色,逗号分隔如 owner,admin 表示仅限这些角色) + CreatedAt string // 创建时间 } // ntfMessagesColumns holds the columns for the table ntf_messages. @@ -47,8 +47,8 @@ var ntfMessagesColumns = NtfMessagesColumns{ IsRead: "is_read", IsBroadcast: "is_broadcast", Metadata: "metadata", - CreatedAt: "created_at", TargetRoles: "target_roles", + CreatedAt: "created_at", } // NewNtfMessagesDao creates and returns a new DAO object for table data access. diff --git a/internal/dao/internal/tnt_tenant_level_configs.go b/internal/dao/internal/tnt_tenant_level_configs.go new file mode 100644 index 0000000..60bf7d7 --- /dev/null +++ b/internal/dao/internal/tnt_tenant_level_configs.go @@ -0,0 +1,97 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// TntTenantLevelConfigsDao is the data access object for the table tnt_tenant_level_configs. +type TntTenantLevelConfigsDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns TntTenantLevelConfigsColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// TntTenantLevelConfigsColumns defines and stores column names for the table tnt_tenant_level_configs. +type TntTenantLevelConfigsColumns struct { + Id string // + Level string // 等级号(1, 2, 3...) + Name string // 等级名称 + CumulativeRechargeThreshold string // 累计充值阈值(USD),达到此值自动升级 + MaxMembers string // 该等级最大成员数 + MaxConcurrency string // 该等级最大并发数,0=无限 + PriceMultiplier string // 价格乘数(折扣,如 0.9=九折) + SortOrder string // 排序权重 + CreatedAt string // + UpdatedAt string // +} + +// tntTenantLevelConfigsColumns holds the columns for the table tnt_tenant_level_configs. +var tntTenantLevelConfigsColumns = TntTenantLevelConfigsColumns{ + Id: "id", + Level: "level", + Name: "name", + CumulativeRechargeThreshold: "cumulative_recharge_threshold", + MaxMembers: "max_members", + MaxConcurrency: "max_concurrency", + PriceMultiplier: "price_multiplier", + SortOrder: "sort_order", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewTntTenantLevelConfigsDao creates and returns a new DAO object for table data access. +func NewTntTenantLevelConfigsDao(handlers ...gdb.ModelHandler) *TntTenantLevelConfigsDao { + return &TntTenantLevelConfigsDao{ + group: "default", + table: "tnt_tenant_level_configs", + columns: tntTenantLevelConfigsColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *TntTenantLevelConfigsDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *TntTenantLevelConfigsDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *TntTenantLevelConfigsDao) Columns() TntTenantLevelConfigsColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *TntTenantLevelConfigsDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *TntTenantLevelConfigsDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *TntTenantLevelConfigsDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/internal/dao/internal/tnt_tenants.go b/internal/dao/internal/tnt_tenants.go index 7004d45..c47b7e3 100644 --- a/internal/dao/internal/tnt_tenants.go +++ b/internal/dao/internal/tnt_tenants.go @@ -38,6 +38,7 @@ type TntTenantsColumns struct { DataRemovalAt string // 数据清除时间(冻结 30 天后) MaxConcurrency string // 租户总并发上限(0表示不限制) DefaultChannelScope string // 默认渠道范围(NULL或[]表示全部可用,否则为channel_id数组) + Level string // 当前等级(对应 tnt_tenant_level_configs.level) } // tntTenantsColumns holds the columns for the table tnt_tenants. @@ -59,6 +60,7 @@ var tntTenantsColumns = TntTenantsColumns{ DataRemovalAt: "data_removal_at", MaxConcurrency: "max_concurrency", DefaultChannelScope: "default_channel_scope", + Level: "level", } // NewTntTenantsDao creates and returns a new DAO object for table data access. diff --git a/internal/dao/internal/tsk_model_tasks.go b/internal/dao/internal/tsk_model_tasks.go index 2cadd47..53660ff 100644 --- a/internal/dao/internal/tsk_model_tasks.go +++ b/internal/dao/internal/tsk_model_tasks.go @@ -21,30 +21,30 @@ type TskModelTasksDao struct { // TskModelTasksColumns defines and stores column names for the table tsk_model_tasks. type TskModelTasksColumns struct { - Id string // - PublicTaskId string // - Platform string // - Action string // - Status string // - Progress string // - FailReason string // - TenantId string // - UserId string // - ApiKeyId string // - ChannelId string // - ModelName string // - UpstreamModel string // - PreDeductAmount string // - ActualCost string // - BillingSettled string // - ResultUrl string // - Data string // - PrivateData string // - SubmitTime string // - StartTime string // - FinishTime string // - CreatedAt string // - UpdatedAt string // + Id string // 主键ID + PublicTaskId string // 对外公开的任务ID(task_xxxxx 格式),用于 API 响应和客户端查询 + Platform string // 任务所属平台:sora、kling、suno、midjourney、volcengine + Action string // 任务动作类型。通用:generate;Suno:music、lyrics; + Status string // 任务状态:NOT_START-未开始、SUBMITTED-已提交、IN_PROGRESS-进行中、SUCCESS-成功、FAILURE-失败 + Progress string // 任务进度,字符串格式百分比(如 0%、50%、100%) + FailReason string // 任务失败原因文本 + TenantId string // 发起任务的租户ID + UserId string // 发起任务的租户用户ID + ApiKeyId string // 调用方使用的 API Key ID + ChannelId string // 转发请求的渠道ID + ModelName string // 用户请求的模型名称(如 sora-1.0-turbo、midjourney 等) + UpstreamModel string // 上游供应商实际使用的模型名称,可能与请求模型不同 + PreDeductAmount string // 提交任务时的预扣金额(USD),任务完成后根据实际用量结算差额 + ActualCost string // 任务实际消费金额(USD),成功时按此结算,失败时退还预扣金额 + BillingSettled string // 计费是否已结算(true 表示已完成预扣与实际金额的多退少补) + ResultUrl string // 任务完成后的结果资源 URL(如生成的视频/图片/音频的下载地址) + Data string // 上游供应商返回的完整响应数据(JSONB),可返回给用户查看 + PrivateData string // 内部私有数据(JSONB),含 upstream_task_id 等敏感字段,用于轮询上游状态,不返回给用户 + SubmitTime string // 任务提交到上游供应商的时间 + StartTime string // 上游供应商开始执行任务的时间 + FinishTime string // 任务完成(成功或失败)的时间 + CreatedAt string // 记录创建时间 + UpdatedAt string // 记录更新时间 RequestId string // 任务提交时的原始请求 ID(req_xxxxx),关联 aud_request_logs.request_id } diff --git a/internal/dao/tnt_tenant_level_configs.go b/internal/dao/tnt_tenant_level_configs.go new file mode 100644 index 0000000..6023169 --- /dev/null +++ b/internal/dao/tnt_tenant_level_configs.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "github.com/qianfree/team-api/internal/dao/internal" +) + +// tntTenantLevelConfigsDao is the data access object for the table tnt_tenant_level_configs. +// You can define custom methods on it to extend its functionality as needed. +type tntTenantLevelConfigsDao struct { + *internal.TntTenantLevelConfigsDao +} + +var ( + // TntTenantLevelConfigs is a globally accessible object for table tnt_tenant_level_configs operations. + TntTenantLevelConfigs = tntTenantLevelConfigsDao{internal.NewTntTenantLevelConfigsDao()} +) + +// Add your custom methods and functionality below. diff --git a/internal/logic/admin/tenant.go b/internal/logic/admin/tenant.go index 2a63a11..1743d63 100644 --- a/internal/logic/admin/tenant.go +++ b/internal/logic/admin/tenant.go @@ -14,6 +14,7 @@ import ( "github.com/qianfree/team-api/internal/dao" "github.com/qianfree/team-api/internal/logic/common" do "github.com/qianfree/team-api/internal/model/do" + "github.com/qianfree/team-api/internal/model/entity" "github.com/qianfree/team-api/internal/utility/crypto" "github.com/qianfree/team-api/internal/utility/export" ) @@ -118,6 +119,7 @@ func (s *sAdmin) CreateTenant(ctx context.Context, req *v1.TenantCreateReq) (*v1 Code: tenantCode, MaxMembers: maxMembers, MaxConcurrency: maxConcurrency, + Level: 1, Settings: "{}", }).Insert() if err != nil { @@ -289,6 +291,7 @@ func (s *sAdmin) GetTenant(ctx context.Context, req *v1.TenantGetReq) (*v1.Tenan MaxConcurrency int `json:"max_concurrency"` DefaultChannelScope string `json:"default_channel_scope"` Settings string `json:"settings"` + Level int `json:"level"` CreatedAt *gtime.Time `json:"created_at"` UpdatedAt *gtime.Time `json:"updated_at"` } @@ -327,6 +330,15 @@ func (s *sAdmin) GetTenant(ctx context.Context, req *v1.TenantGetReq) (*v1.Tenan if owner != nil { ownerName = owner.DisplayName } + + // Get level name + levelName := "" + var levelConfig entity.TntTenantLevelConfigs + _ = dao.TntTenantLevelConfigs.Ctx(ctx).Where("level", tenant.Level).Scan(&levelConfig) + if levelConfig.Id > 0 { + levelName = levelConfig.Name + } + return &v1.TenantGetRes{ TenantItem: v1.TenantItem{ ID: tenant.Id, @@ -341,6 +353,8 @@ func (s *sAdmin) GetTenant(ctx context.Context, req *v1.TenantGetReq) (*v1.Tenan DefaultChannelScope: tenant.DefaultChannelScope, MemberCount: memberCount, WalletBalance: walletBalance, + Level: tenant.Level, + LevelName: levelName, CreatedAt: tenant.CreatedAt.String(), UpdatedAt: tenant.UpdatedAt.String(), }, @@ -394,6 +408,20 @@ func (s *sAdmin) UpdateTenant(ctx context.Context, req *v1.TenantUpdateReq) (*v1 data.MaxConcurrency = *req.MaxConcurrency } + // 管理员手动调整等级:同步更新 max_members 和 max_concurrency + if req.Level != nil { + data.Level = *req.Level + var config entity.TntTenantLevelConfigs + err := dao.TntTenantLevelConfigs.Ctx(ctx).Where("level", *req.Level).Scan(&config) + if err != nil { + return nil, common.NewBadRequestError("等级配置不存在") + } + if config.Id > 0 { + data.MaxMembers = config.MaxMembers + data.MaxConcurrency = config.MaxConcurrency + } + } + _, err := dao.TntTenants.Ctx(ctx).Where("id", req.Id).Update(data) if err != nil { return nil, err diff --git a/internal/logic/admin/tenant_level.go b/internal/logic/admin/tenant_level.go new file mode 100644 index 0000000..13b5cd6 --- /dev/null +++ b/internal/logic/admin/tenant_level.go @@ -0,0 +1,96 @@ +package admin + +import ( + "context" + + "github.com/gogf/gf/v2/errors/gerror" + + "github.com/qianfree/team-api/api/admin/v1" + "github.com/qianfree/team-api/internal/dao" + "github.com/qianfree/team-api/internal/model/do" + "github.com/qianfree/team-api/internal/model/entity" +) + +func (s *sAdmin) ListTenantLevelConfigs(ctx context.Context, _ *v1.TenantLevelConfigListReq) (*v1.TenantLevelConfigListRes, error) { + var configs []*entity.TntTenantLevelConfigs + err := dao.TntTenantLevelConfigs.Ctx(ctx). + OrderAsc("level"). + Scan(&configs) + if err != nil { + return nil, err + } + + list := make([]*v1.TenantLevelConfigItem, 0, len(configs)) + for _, c := range configs { + list = append(list, &v1.TenantLevelConfigItem{ + Id: c.Id, + Level: c.Level, + Name: c.Name, + CumulativeRechargeThreshold: c.CumulativeRechargeThreshold, + MaxMembers: c.MaxMembers, + MaxConcurrency: c.MaxConcurrency, + PriceMultiplier: c.PriceMultiplier, + SortOrder: c.SortOrder, + CreatedAt: c.CreatedAt, + UpdatedAt: c.UpdatedAt, + }) + } + + return &v1.TenantLevelConfigListRes{List: list}, nil +} + +func (s *sAdmin) CreateTenantLevelConfig(ctx context.Context, req *v1.TenantLevelConfigCreateReq) (*v1.TenantLevelConfigCreateRes, error) { + // 检查 level 是否已存在 + count, _ := dao.TntTenantLevelConfigs.Ctx(ctx).Where("level", req.Level).Count() + if count > 0 { + return nil, gerror.New("等级号已存在") + } + + result, err := dao.TntTenantLevelConfigs.Ctx(ctx).Insert(do.TntTenantLevelConfigs{ + Level: req.Level, + Name: req.Name, + CumulativeRechargeThreshold: req.CumulativeRechargeThreshold, + MaxMembers: req.MaxMembers, + MaxConcurrency: req.MaxConcurrency, + PriceMultiplier: req.PriceMultiplier, + SortOrder: req.SortOrder, + }) + if err != nil { + return nil, err + } + + id, _ := result.LastInsertId() + return &v1.TenantLevelConfigCreateRes{ID: id}, nil +} + +func (s *sAdmin) UpdateTenantLevelConfig(ctx context.Context, req *v1.TenantLevelConfigUpdateReq) (*v1.TenantLevelConfigUpdateRes, error) { + _, err := dao.TntTenantLevelConfigs.Ctx(ctx). + Where("id", req.Id). + Data(req.Update). + Update() + if err != nil { + return nil, err + } + return &v1.TenantLevelConfigUpdateRes{}, nil +} + +func (s *sAdmin) DeleteTenantLevelConfig(ctx context.Context, req *v1.TenantLevelConfigDeleteReq) (*v1.TenantLevelConfigDeleteRes, error) { + // 不允许删除 level=1(默认等级) + var config entity.TntTenantLevelConfigs + err := dao.TntTenantLevelConfigs.Ctx(ctx).Where("id", req.Id).Scan(&config) + if err != nil { + return nil, err + } + if config.Id == 0 { + return nil, gerror.New("等级配置不存在") + } + if config.Level == 1 { + return nil, gerror.New("不允许删除默认等级 LV1") + } + + _, err = dao.TntTenantLevelConfigs.Ctx(ctx).Where("id", req.Id).Delete() + if err != nil { + return nil, err + } + return &v1.TenantLevelConfigDeleteRes{}, nil +} diff --git a/internal/logic/billing/level.go b/internal/logic/billing/level.go new file mode 100644 index 0000000..16cd823 --- /dev/null +++ b/internal/logic/billing/level.go @@ -0,0 +1,118 @@ +package billing + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/frame/g" + + "github.com/qianfree/team-api/internal/dao" + "github.com/qianfree/team-api/internal/model/entity" +) + +// GetTenantLevelConfig 获取指定等级的配置 +func GetTenantLevelConfig(ctx context.Context, level int) (*entity.TntTenantLevelConfigs, error) { + var config entity.TntTenantLevelConfigs + err := dao.TntTenantLevelConfigs.Ctx(ctx). + Where("level", level). + Scan(&config) + if err != nil { + return nil, err + } + if config.Id == 0 { + return nil, fmt.Errorf("level config not found for level %d", level) + } + return &config, nil +} + +// CheckAndUpgradeLevel 检查并升级租户等级(充值后调用) +// 升级策略:仅升不降 — max_members/max_concurrency 仅在新值 > 当前值时才更新 +func CheckAndUpgradeLevel(ctx context.Context, tenantID int64) error { + // 1. 读取租户当前等级 + var tenant entity.TntTenants + err := dao.TntTenants.Ctx(ctx).Where("id", tenantID).Scan(&tenant) + if err != nil { + return err + } + if tenant.Id == 0 { + return nil + } + + // 2. 读取累计充值 + var wallet entity.BilWallets + err = dao.BilWallets.Ctx(ctx).Where("tenant_id", tenantID).Scan(&wallet) + if err != nil { + return err + } + if wallet.Id == 0 { + return nil + } + + // 3. 根据累计充值计算应得等级 + newLevel, err := RecalculateLevel(ctx, wallet.CumulativeRecharge) + if err != nil { + return err + } + + // 4. 仅当等级提升时才更新 + if newLevel <= tenant.Level { + return nil + } + + // 5. 获取新等级配置 + config, err := GetTenantLevelConfig(ctx, newLevel) + if err != nil { + return err + } + + // 6. 构建更新数据:仅升不降策略 + updateData := g.Map{ + "level": newLevel, + "updated_at": "NOW()", + } + + // max_members:新值 > 当前值才更新(0=无限,视为最大) + if config.MaxMembers == 0 { + // 新等级允许无限成员,直接更新 + updateData["max_members"] = 0 + } else if config.MaxMembers > tenant.MaxMembers { + updateData["max_members"] = config.MaxMembers + } + + // max_concurrency:新值 > 当前值才更新(0=无限,视为最大) + if config.MaxConcurrency == 0 { + updateData["max_concurrency"] = 0 + } else if config.MaxConcurrency > tenant.MaxConcurrency { + updateData["max_concurrency"] = config.MaxConcurrency + } + + _, err = dao.TntTenants.Ctx(ctx). + Where("id", tenantID). + Data(updateData). + Update() + if err != nil { + return err + } + + // 7. 清除并发限制缓存 + _, _ = g.Redis().Do(ctx, "DEL", fmt.Sprintf("tenant:conc_limit:%d", tenantID)) + + return nil +} + +// RecalculateLevel 根据累计充值金额重新计算等级 +func RecalculateLevel(ctx context.Context, cumulativeRecharge float64) (int, error) { + var config entity.TntTenantLevelConfigs + err := dao.TntTenantLevelConfigs.Ctx(ctx). + Where("cumulative_recharge_threshold <= ?", cumulativeRecharge). + OrderDesc("level"). + Limit(1). + Scan(&config) + if err != nil { + return 1, err + } + if config.Id == 0 { + return 1, nil + } + return config.Level, nil +} diff --git a/internal/logic/payment/fulfill.go b/internal/logic/payment/fulfill.go index d552b4c..27542c1 100644 --- a/internal/logic/payment/fulfill.go +++ b/internal/logic/payment/fulfill.go @@ -53,6 +53,8 @@ func FulfillOrder(ctx context.Context, orderID int64) error { if err != nil { return gerror.Wrapf(err, "credit wallet failed") } + // 检查并升级租户等级 + _ = billing.CheckAndUpgradeLevel(ctx, order.TenantID) default: return gerror.Newf("unsupported order type for fulfillment: %s", order.OrderType) @@ -134,8 +136,8 @@ func creditWallet(ctx context.Context, tenantID int64, amount float64, descripti err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { _, err := tx.Ctx(ctx).Exec( - "UPDATE bil_wallets SET balance = balance + ?, updated_at = NOW() WHERE id = ?", - amount, w.ID) + "UPDATE bil_wallets SET balance = balance + ?, cumulative_recharge = cumulative_recharge + ?, updated_at = NOW() WHERE id = ?", + amount, amount, w.ID) if err != nil { return err } diff --git a/internal/logic/tenant/auth.go b/internal/logic/tenant/auth.go index ea99e00..9ef750f 100644 --- a/internal/logic/tenant/auth.go +++ b/internal/logic/tenant/auth.go @@ -80,10 +80,12 @@ func (s *sTenant) Register(ctx context.Context, req *v1.TenantRegisterReq) (*v1. err = dao.TntTenants.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { // Create tenant tenantResult, err := tx.Model("tnt_tenants").Ctx(ctx).Data(do.TntTenants{ - Name: strings.TrimSpace(req.TenantName), - Code: tenantCode, - MaxMembers: 10, - Settings: "{}", + Name: strings.TrimSpace(req.TenantName), + Code: tenantCode, + MaxMembers: 10, + MaxConcurrency: 10, + Level: 1, + Settings: "{}", }).Insert() if err != nil { return gerror.Wrapf(err, "create tenant") diff --git a/internal/logic/tenant/billing.go b/internal/logic/tenant/billing.go index c4bf633..06368bb 100644 --- a/internal/logic/tenant/billing.go +++ b/internal/logic/tenant/billing.go @@ -26,16 +26,17 @@ func (s *sTenant) Wallet(ctx context.Context, req *v1.TenantWalletReq) (*v1.Tena tenantID := ctxTenantID(ctx) type walletRow struct { - Balance float64 `json:"balance"` - FrozenBalance float64 `json:"frozen_balance"` - WarningThreshold float64 `json:"warning_threshold"` - Currency string `json:"currency"` + Balance float64 `json:"balance"` + FrozenBalance float64 `json:"frozen_balance"` + WarningThreshold float64 `json:"warning_threshold"` + Currency string `json:"currency"` + CumulativeRecharge float64 `json:"cumulative_recharge"` } var w *walletRow err := dao.BilWallets.Ctx(ctx). Where("tenant_id", tenantID). - Fields("balance, frozen_balance, warning_threshold, currency"). + Fields("balance, frozen_balance, warning_threshold, currency, cumulative_recharge"). Scan(&w) if err != nil { return nil, err diff --git a/internal/logic/tenant/organization.go b/internal/logic/tenant/organization.go index 9be325c..b466e24 100644 --- a/internal/logic/tenant/organization.go +++ b/internal/logic/tenant/organization.go @@ -30,6 +30,7 @@ func (s *sTenant) GetOrgInfo(ctx context.Context, req *v1.TenantOrgInfoReq) (*v1 Code string `json:"code"` LogoUrl string `json:"logo_url"` Status string `json:"status"` + Level int `json:"level"` MaxMembers int `json:"max_members"` CreatedAt string `json:"created_at"` } @@ -50,12 +51,23 @@ func (s *sTenant) GetOrgInfo(ctx context.Context, req *v1.TenantOrgInfoReq) (*v1 return nil, err } + // Look up level name + var levelName string + if tenant.Level > 0 { + _ = dao.TntTenantLevelConfigs.Ctx(ctx). + Where("level", tenant.Level). + Fields("name"). + Scan(&levelName) + } + return &v1.TenantOrgInfoRes{ ID: tenant.Id, Name: tenant.Name, Code: tenant.Code, LogoURL: tenant.LogoUrl, Status: tenant.Status, + Level: tenant.Level, + LevelName: levelName, MaxMembers: tenant.MaxMembers, MemberCount: int(memberCount), CreatedAt: tenant.CreatedAt, diff --git a/internal/model/do/aud_content_filter_logs.go b/internal/model/do/aud_content_filter_logs.go index 6204d32..ec96e3c 100644 --- a/internal/model/do/aud_content_filter_logs.go +++ b/internal/model/do/aud_content_filter_logs.go @@ -16,6 +16,7 @@ type AudContentFilterLogs struct { TenantId any // 租户ID UserId any // 用户ID ApiKeyId any // API Key ID + ProjectId any // 项目ID RequestId any // 请求唯一ID Method any // HTTP 方法 Path any // 请求路径 @@ -25,5 +26,4 @@ type AudContentFilterLogs struct { OriginalSnippet any // 原始请求体片段(截断存储,仅 replace 模式) Blocked any // 是否被拦截(mode=block 时为 true) CreatedAt *gtime.Time // 创建时间 - ProjectId any // 项目ID } diff --git a/internal/model/do/bil_wallets.go b/internal/model/do/bil_wallets.go index aadb6c1..9a090fc 100644 --- a/internal/model/do/bil_wallets.go +++ b/internal/model/do/bil_wallets.go @@ -11,13 +11,14 @@ import ( // BilWallets is the golang structure of table bil_wallets for DAO operations like Where/Data. type BilWallets struct { - g.Meta `orm:"table:bil_wallets, do:true"` - Id any // 主键ID - TenantId any // 租户ID(每个租户一个钱包) - Balance any // 总余额 - FrozenBalance any // 冻结余额(支付中/退款中,可用余额 = balance - frozen_balance) - WarningThreshold any // 余额预警线(低于此值触发通知) - Currency any // 货币(USD) - CreatedAt *gtime.Time // 创建时间 - UpdatedAt *gtime.Time // 更新时间 + g.Meta `orm:"table:bil_wallets, do:true"` + Id any // 主键ID + TenantId any // 租户ID(每个租户一个钱包) + Balance any // 总余额 + FrozenBalance any // 冻结余额(支付中/退款中,可用余额 = balance - frozen_balance) + WarningThreshold any // 余额预警线(低于此值触发通知) + Currency any // 货币(USD) + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 + CumulativeRecharge any // 累计充值总额(USD) } diff --git a/internal/model/do/chn_channel_keys.go b/internal/model/do/chn_channel_keys.go index 011dd40..529e757 100644 --- a/internal/model/do/chn_channel_keys.go +++ b/internal/model/do/chn_channel_keys.go @@ -20,7 +20,7 @@ type ChnChannelKeys struct { LastUsedAt *gtime.Time // 最后使用时间 LastError any // 最后一次错误信息 CreatedAt *gtime.Time // 创建时间 - UpdatedAt *gtime.Time // 更新时间 KeyType any // Key 类型:apikey(传统静态密钥)/ oauth(OAuth 令牌) TokenExpiresAt *gtime.Time // OAuth access_token 过期时间(仅 key_type=oauth 时有值) + UpdatedAt *gtime.Time // 更新时间 } diff --git a/internal/model/do/mdl_models.go b/internal/model/do/mdl_models.go index 34083f2..0a54954 100644 --- a/internal/model/do/mdl_models.go +++ b/internal/model/do/mdl_models.go @@ -21,10 +21,10 @@ type MdlModels struct { MaxOutputTokens any // 最大输出 token 数 Description any // 模型描述 Tags []string // 标签(如 reasoning、vision、function_calling) + Capabilities any // 模型能力特性(如 vision、function_calling、reasoning 等) CreatedAt *gtime.Time // 创建时间 UpdatedAt *gtime.Time // 更新时间 DeprecatedAt *gtime.Time // 标记弃用的时间(NULL表示未弃用) SunsetDate *gtime.Time // 计划下线日期(到达后返回410 Gone,NULL表示未设置) ReplacementModel any // 推荐替代模型名(NULL表示无替代) - Capabilities any // 模型能力特性(如 vision、function_calling、reasoning 等) } diff --git a/internal/model/do/ntf_messages.go b/internal/model/do/ntf_messages.go index 0ab3f96..fad9fdb 100644 --- a/internal/model/do/ntf_messages.go +++ b/internal/model/do/ntf_messages.go @@ -22,6 +22,6 @@ type NtfMessages struct { IsRead any // 是否已读:0=未读, 1=已读 IsBroadcast any // 是否广播消息:0=个人消息, 1=广播消息 Metadata any // 附加元数据(JSONB,如关联资源ID、跳转链接等) - CreatedAt *gtime.Time // 创建时间 TargetRoles any // 目标角色(NULL=全部角色,逗号分隔如 owner,admin 表示仅限这些角色) + CreatedAt *gtime.Time // 创建时间 } diff --git a/internal/model/do/tnt_tenant_level_configs.go b/internal/model/do/tnt_tenant_level_configs.go new file mode 100644 index 0000000..76649da --- /dev/null +++ b/internal/model/do/tnt_tenant_level_configs.go @@ -0,0 +1,25 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// TntTenantLevelConfigs is the golang structure of table tnt_tenant_level_configs for DAO operations like Where/Data. +type TntTenantLevelConfigs struct { + g.Meta `orm:"table:tnt_tenant_level_configs, do:true"` + Id any // + Level any // 等级号(1, 2, 3...) + Name any // 等级名称 + CumulativeRechargeThreshold any // 累计充值阈值(USD),达到此值自动升级 + MaxMembers any // 该等级最大成员数 + MaxConcurrency any // 该等级最大并发数,0=无限 + PriceMultiplier any // 价格乘数(折扣,如 0.9=九折) + SortOrder any // 排序权重 + CreatedAt *gtime.Time // + UpdatedAt *gtime.Time // +} diff --git a/internal/model/do/tnt_tenants.go b/internal/model/do/tnt_tenants.go index fbf4f2a..4e8eefe 100644 --- a/internal/model/do/tnt_tenants.go +++ b/internal/model/do/tnt_tenants.go @@ -29,4 +29,5 @@ type TntTenants struct { DataRemovalAt *gtime.Time // 数据清除时间(冻结 30 天后) MaxConcurrency any // 租户总并发上限(0表示不限制) DefaultChannelScope any // 默认渠道范围(NULL或[]表示全部可用,否则为channel_id数组) + Level any // 当前等级(对应 tnt_tenant_level_configs.level) } diff --git a/internal/model/do/tsk_model_tasks.go b/internal/model/do/tsk_model_tasks.go index ae22026..0766f75 100644 --- a/internal/model/do/tsk_model_tasks.go +++ b/internal/model/do/tsk_model_tasks.go @@ -12,29 +12,29 @@ import ( // TskModelTasks is the golang structure of table tsk_model_tasks for DAO operations like Where/Data. type TskModelTasks struct { g.Meta `orm:"table:tsk_model_tasks, do:true"` - Id any // - PublicTaskId any // - Platform any // - Action any // - Status any // - Progress any // - FailReason any // - TenantId any // - UserId any // - ApiKeyId any // - ChannelId any // - ModelName any // - UpstreamModel any // - PreDeductAmount any // - ActualCost any // - BillingSettled any // - ResultUrl any // - Data any // - PrivateData any // - SubmitTime *gtime.Time // - StartTime *gtime.Time // - FinishTime *gtime.Time // - CreatedAt *gtime.Time // - UpdatedAt *gtime.Time // + Id any // 主键ID + PublicTaskId any // 对外公开的任务ID(task_xxxxx 格式),用于 API 响应和客户端查询 + Platform any // 任务所属平台:sora、kling、suno、midjourney、volcengine + Action any // 任务动作类型。通用:generate;Suno:music、lyrics; + Status any // 任务状态:NOT_START-未开始、SUBMITTED-已提交、IN_PROGRESS-进行中、SUCCESS-成功、FAILURE-失败 + Progress any // 任务进度,字符串格式百分比(如 0%、50%、100%) + FailReason any // 任务失败原因文本 + TenantId any // 发起任务的租户ID + UserId any // 发起任务的租户用户ID + ApiKeyId any // 调用方使用的 API Key ID + ChannelId any // 转发请求的渠道ID + ModelName any // 用户请求的模型名称(如 sora-1.0-turbo、midjourney 等) + UpstreamModel any // 上游供应商实际使用的模型名称,可能与请求模型不同 + PreDeductAmount any // 提交任务时的预扣金额(USD),任务完成后根据实际用量结算差额 + ActualCost any // 任务实际消费金额(USD),成功时按此结算,失败时退还预扣金额 + BillingSettled any // 计费是否已结算(true 表示已完成预扣与实际金额的多退少补) + ResultUrl any // 任务完成后的结果资源 URL(如生成的视频/图片/音频的下载地址) + Data any // 上游供应商返回的完整响应数据(JSONB),可返回给用户查看 + PrivateData any // 内部私有数据(JSONB),含 upstream_task_id 等敏感字段,用于轮询上游状态,不返回给用户 + SubmitTime *gtime.Time // 任务提交到上游供应商的时间 + StartTime *gtime.Time // 上游供应商开始执行任务的时间 + FinishTime *gtime.Time // 任务完成(成功或失败)的时间 + CreatedAt *gtime.Time // 记录创建时间 + UpdatedAt *gtime.Time // 记录更新时间 RequestId any // 任务提交时的原始请求 ID(req_xxxxx),关联 aud_request_logs.request_id } diff --git a/internal/model/entity/aud_content_filter_logs.go b/internal/model/entity/aud_content_filter_logs.go index d5db34d..7a72b3b 100644 --- a/internal/model/entity/aud_content_filter_logs.go +++ b/internal/model/entity/aud_content_filter_logs.go @@ -14,6 +14,7 @@ type AudContentFilterLogs struct { TenantId int64 `json:"tenant_id" orm:"tenant_id" description:"租户ID"` // 租户ID UserId int64 `json:"user_id" orm:"user_id" description:"用户ID"` // 用户ID ApiKeyId int64 `json:"api_key_id" orm:"api_key_id" description:"API Key ID"` // API Key ID + ProjectId int64 `json:"project_id" orm:"project_id" description:"项目ID"` // 项目ID RequestId string `json:"request_id" orm:"request_id" description:"请求唯一ID"` // 请求唯一ID Method string `json:"method" orm:"method" description:"HTTP 方法"` // HTTP 方法 Path string `json:"path" orm:"path" description:"请求路径"` // 请求路径 @@ -23,5 +24,4 @@ type AudContentFilterLogs struct { OriginalSnippet string `json:"original_snippet" orm:"original_snippet" description:"原始请求体片段(截断存储,仅 replace 模式)"` // 原始请求体片段(截断存储,仅 replace 模式) Blocked bool `json:"blocked" orm:"blocked" description:"是否被拦截(mode=block 时为 true)"` // 是否被拦截(mode=block 时为 true) CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间 - ProjectId int64 `json:"project_id" orm:"project_id" description:"项目ID"` // 项目ID } diff --git a/internal/model/entity/bil_transactions.go b/internal/model/entity/bil_transactions.go index 094a48e..49edeab 100644 --- a/internal/model/entity/bil_transactions.go +++ b/internal/model/entity/bil_transactions.go @@ -10,22 +10,22 @@ import ( // BilTransactions is the golang structure for table bil_transactions. type BilTransactions struct { - Id int64 `json:"id" orm:"id" description:"主键ID"` // 主键ID - TenantId int64 `json:"tenant_id" orm:"tenant_id" description:"租户ID"` // 租户ID - WalletId int64 `json:"wallet_id" orm:"wallet_id" description:"关联钱包ID"` // 关联钱包ID - Type string `json:"type" orm:"type" description:"类型:consume(消费)/ recharge(充值)/ adjust(调整)/ pre_deduct(预扣,已废弃)/ settle(结算,已废弃)/ refund(退款,已废弃)/ freeze(冻结,已废弃)/ unfreeze(解冻,已废弃)"` // 类型:consume(消费)/ recharge(充值)/ adjust(调整)/ pre_deduct(预扣,已废弃)/ settle(结算,已废弃)/ refund(退款,已废弃)/ freeze(冻结,已废弃)/ unfreeze(解冻,已废弃) - Amount float64 `json:"amount" orm:"amount" description:"变动金额(正数=收入,负数=支出)"` // 变动金额(正数=收入,负数=支出) - BalanceAfter float64 `json:"balance_after" orm:"balance_after" description:"变动后总余额"` // 变动后总余额 - FrozenAfter float64 `json:"frozen_after" orm:"frozen_after" description:"变动后冻结余额"` // 变动后冻结余额 - RelatedId int64 `json:"related_id" orm:"related_id" description:"关联业务ID(如计费记录ID、订单ID等)"` // 关联业务ID(如计费记录ID、订单ID等) - RelatedType string `json:"related_type" orm:"related_type" description:"关联业务类型:billing_record / order / refund / adjustment / redemption"` // 关联业务类型:billing_record / order / refund / adjustment / redemption - Description string `json:"description" orm:"description" description:"交易描述"` // 交易描述 - CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间 - UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"更新时间"` // 更新时间 - UserId int64 `json:"user_id" orm:"user_id" description:"关联用户ID(consume 类型为实际消费用户,recharge 类型为操作用户,adjust 类型为空)"` // 关联用户ID(consume 类型为实际消费用户,recharge 类型为操作用户,adjust 类型为空) - RequestId string `json:"request_id" orm:"request_id" description:"关联请求ID(consume 类型对应 API 调用的 request_id,其他类型为空)"` // 关联请求ID(consume 类型对应 API 调用的 request_id,其他类型为空) - ModelName string `json:"model_name" orm:"model_name" description:"关联模型名(consume 类型为调用的模型名,其他类型为空)"` // 关联模型名(consume 类型为调用的模型名,其他类型为空) - ProjectId int64 `json:"project_id" orm:"project_id" description:"关联项目ID(consume 类型为 API Key 所属项目,个人密钥为空)"` // 关联项目ID(consume 类型为 API Key 所属项目,个人密钥为空) - ApiKeyId int64 `json:"api_key_id" orm:"api_key_id" description:"关联API密钥ID(consume 类型为发起请求的密钥)"` // 关联API密钥ID(consume 类型为发起请求的密钥) - TaskId string `json:"task_id" orm:"task_id" description:"关联异步任务公开ID(consume+relay_mode=task 时关联 tsk_model_tasks.public_task_id)"` // 关联异步任务公开ID(consume+relay_mode=task 时关联 tsk_model_tasks.public_task_id) + Id int64 `json:"id" orm:"id" description:"主键ID"` // 主键ID + TenantId int64 `json:"tenant_id" orm:"tenant_id" description:"租户ID"` // 租户ID + WalletId int64 `json:"wallet_id" orm:"wallet_id" description:"关联钱包ID"` // 关联钱包ID + Type string `json:"type" orm:"type" description:"类型:consume(消费)/ recharge(充值)/ adjust(调整)/ pre_deduct(预扣,已废弃)/ settle(结算,已废弃)/ refund(退款,已废弃)/ freeze(冻结,已废弃)/ unfreeze(解冻,已废弃)"` // 类型:consume(消费)/ recharge(充值)/ adjust(调整)/ pre_deduct(预扣,已废弃)/ settle(结算,已废弃)/ refund(退款,已废弃)/ freeze(冻结,已废弃)/ unfreeze(解冻,已废弃) + Amount float64 `json:"amount" orm:"amount" description:"变动金额(正数=收入,负数=支出)"` // 变动金额(正数=收入,负数=支出) + BalanceAfter float64 `json:"balance_after" orm:"balance_after" description:"变动后总余额"` // 变动后总余额 + FrozenAfter float64 `json:"frozen_after" orm:"frozen_after" description:"变动后冻结余额"` // 变动后冻结余额 + RelatedId int64 `json:"related_id" orm:"related_id" description:"关联业务ID(如计费记录ID、订单ID等)"` // 关联业务ID(如计费记录ID、订单ID等) + RelatedType string `json:"related_type" orm:"related_type" description:"关联业务类型:billing_record / order / refund / adjustment / redemption"` // 关联业务类型:billing_record / order / refund / adjustment / redemption + Description string `json:"description" orm:"description" description:"交易描述"` // 交易描述 + CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间 + UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"更新时间"` // 更新时间 + UserId int64 `json:"user_id" orm:"user_id" description:"关联用户ID(consume 类型为实际消费用户,recharge 类型为操作用户,adjust 类型为空)"` // 关联用户ID(consume 类型为实际消费用户,recharge 类型为操作用户,adjust 类型为空) + RequestId string `json:"request_id" orm:"request_id" description:"关联请求ID(consume 类型对应 API 调用的 request_id,其他类型为空)"` // 关联请求ID(consume 类型对应 API 调用的 request_id,其他类型为空) + ModelName string `json:"model_name" orm:"model_name" description:"关联模型名(consume 类型为调用的模型名,其他类型为空)"` // 关联模型名(consume 类型为调用的模型名,其他类型为空) + ProjectId int64 `json:"project_id" orm:"project_id" description:"关联项目ID(consume 类型为 API Key 所属项目,个人密钥为空)"` // 关联项目ID(consume 类型为 API Key 所属项目,个人密钥为空) + ApiKeyId int64 `json:"api_key_id" orm:"api_key_id" description:"关联API密钥ID(consume 类型为发起请求的密钥)"` // 关联API密钥ID(consume 类型为发起请求的密钥) + TaskId string `json:"task_id" orm:"task_id" description:"关联异步任务公开ID(consume+relay_mode=task 时关联 tsk_model_tasks.public_task_id)"` // 关联异步任务公开ID(consume+relay_mode=task 时关联 tsk_model_tasks.public_task_id) } diff --git a/internal/model/entity/bil_wallets.go b/internal/model/entity/bil_wallets.go index b83778f..d25b6cb 100644 --- a/internal/model/entity/bil_wallets.go +++ b/internal/model/entity/bil_wallets.go @@ -10,12 +10,13 @@ import ( // BilWallets is the golang structure for table bil_wallets. type BilWallets struct { - Id int64 `json:"id" orm:"id" description:"主键ID"` // 主键ID - TenantId int64 `json:"tenant_id" orm:"tenant_id" description:"租户ID(每个租户一个钱包)"` // 租户ID(每个租户一个钱包) - Balance float64 `json:"balance" orm:"balance" description:"总余额"` // 总余额 - FrozenBalance float64 `json:"frozen_balance" orm:"frozen_balance" description:"冻结余额(支付中/退款中,可用余额 = balance - frozen_balance)"` // 冻结余额(支付中/退款中,可用余额 = balance - frozen_balance) - WarningThreshold float64 `json:"warning_threshold" orm:"warning_threshold" description:"余额预警线(低于此值触发通知)"` // 余额预警线(低于此值触发通知) - Currency string `json:"currency" orm:"currency" description:"货币(USD)"` // 货币(USD) - CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间 - UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"更新时间"` // 更新时间 + Id int64 `json:"id" orm:"id" description:"主键ID"` // 主键ID + TenantId int64 `json:"tenant_id" orm:"tenant_id" description:"租户ID(每个租户一个钱包)"` // 租户ID(每个租户一个钱包) + Balance float64 `json:"balance" orm:"balance" description:"总余额"` // 总余额 + FrozenBalance float64 `json:"frozen_balance" orm:"frozen_balance" description:"冻结余额(支付中/退款中,可用余额 = balance - frozen_balance)"` // 冻结余额(支付中/退款中,可用余额 = balance - frozen_balance) + WarningThreshold float64 `json:"warning_threshold" orm:"warning_threshold" description:"余额预警线(低于此值触发通知)"` // 余额预警线(低于此值触发通知) + Currency string `json:"currency" orm:"currency" description:"货币(USD)"` // 货币(USD) + CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间 + UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"更新时间"` // 更新时间 + CumulativeRecharge float64 `json:"cumulative_recharge" orm:"cumulative_recharge" description:"累计充值总额(USD)"` // 累计充值总额(USD) } diff --git a/internal/model/entity/chn_channel_keys.go b/internal/model/entity/chn_channel_keys.go index 729b72d..a913972 100644 --- a/internal/model/entity/chn_channel_keys.go +++ b/internal/model/entity/chn_channel_keys.go @@ -18,7 +18,7 @@ type ChnChannelKeys struct { LastUsedAt *gtime.Time `json:"last_used_at" orm:"last_used_at" description:"最后使用时间"` // 最后使用时间 LastError string `json:"last_error" orm:"last_error" description:"最后一次错误信息"` // 最后一次错误信息 CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间 - UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"更新时间"` // 更新时间 KeyType string `json:"key_type" orm:"key_type" description:"Key 类型:apikey(传统静态密钥)/ oauth(OAuth 令牌)"` // Key 类型:apikey(传统静态密钥)/ oauth(OAuth 令牌) TokenExpiresAt *gtime.Time `json:"token_expires_at" orm:"token_expires_at" description:"OAuth access_token 过期时间(仅 key_type=oauth 时有值)"` // OAuth access_token 过期时间(仅 key_type=oauth 时有值) + UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"更新时间"` // 更新时间 } diff --git a/internal/model/entity/mdl_models.go b/internal/model/entity/mdl_models.go index 96dfe97..5fa76c0 100644 --- a/internal/model/entity/mdl_models.go +++ b/internal/model/entity/mdl_models.go @@ -19,10 +19,10 @@ type MdlModels struct { MaxOutputTokens int `json:"max_output_tokens" orm:"max_output_tokens" description:"最大输出 token 数"` // 最大输出 token 数 Description string `json:"description" orm:"description" description:"模型描述"` // 模型描述 Tags []string `json:"tags" orm:"tags" description:"标签(如 reasoning、vision、function_calling)"` // 标签(如 reasoning、vision、function_calling) + Capabilities string `json:"capabilities" orm:"capabilities" description:"模型能力特性(如 vision、function_calling、reasoning 等)"` // 模型能力特性(如 vision、function_calling、reasoning 等) CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间 UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"更新时间"` // 更新时间 DeprecatedAt *gtime.Time `json:"deprecated_at" orm:"deprecated_at" description:"标记弃用的时间(NULL表示未弃用)"` // 标记弃用的时间(NULL表示未弃用) SunsetDate *gtime.Time `json:"sunset_date" orm:"sunset_date" description:"计划下线日期(到达后返回410 Gone,NULL表示未设置)"` // 计划下线日期(到达后返回410 Gone,NULL表示未设置) ReplacementModel string `json:"replacement_model" orm:"replacement_model" description:"推荐替代模型名(NULL表示无替代)"` // 推荐替代模型名(NULL表示无替代) - Capabilities string `json:"capabilities" orm:"capabilities" description:"模型能力特性(如 vision、function_calling、reasoning 等)"` // 模型能力特性(如 vision、function_calling、reasoning 等) } diff --git a/internal/model/entity/ntf_messages.go b/internal/model/entity/ntf_messages.go index e0a3751..267790e 100644 --- a/internal/model/entity/ntf_messages.go +++ b/internal/model/entity/ntf_messages.go @@ -20,6 +20,6 @@ type NtfMessages struct { IsRead int `json:"is_read" orm:"is_read" description:"是否已读:0=未读, 1=已读"` // 是否已读:0=未读, 1=已读 IsBroadcast int `json:"is_broadcast" orm:"is_broadcast" description:"是否广播消息:0=个人消息, 1=广播消息"` // 是否广播消息:0=个人消息, 1=广播消息 Metadata string `json:"metadata" orm:"metadata" description:"附加元数据(JSONB,如关联资源ID、跳转链接等)"` // 附加元数据(JSONB,如关联资源ID、跳转链接等) - CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间 TargetRoles string `json:"target_roles" orm:"target_roles" description:"目标角色(NULL=全部角色,逗号分隔如 owner,admin 表示仅限这些角色)"` // 目标角色(NULL=全部角色,逗号分隔如 owner,admin 表示仅限这些角色) + CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间 } diff --git a/internal/model/entity/tnt_tenant_level_configs.go b/internal/model/entity/tnt_tenant_level_configs.go new file mode 100644 index 0000000..b3983c7 --- /dev/null +++ b/internal/model/entity/tnt_tenant_level_configs.go @@ -0,0 +1,23 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" +) + +// TntTenantLevelConfigs is the golang structure for table tnt_tenant_level_configs. +type TntTenantLevelConfigs struct { + Id int64 `json:"id" orm:"id" description:""` // + Level int `json:"level" orm:"level" description:"等级号(1, 2, 3...)"` // 等级号(1, 2, 3...) + Name string `json:"name" orm:"name" description:"等级名称"` // 等级名称 + CumulativeRechargeThreshold float64 `json:"cumulative_recharge_threshold" orm:"cumulative_recharge_threshold" description:"累计充值阈值(USD),达到此值自动升级"` // 累计充值阈值(USD),达到此值自动升级 + MaxMembers int `json:"max_members" orm:"max_members" description:"该等级最大成员数"` // 该等级最大成员数 + MaxConcurrency int `json:"max_concurrency" orm:"max_concurrency" description:"该等级最大并发数,0=无限"` // 该等级最大并发数,0=无限 + PriceMultiplier float64 `json:"price_multiplier" orm:"price_multiplier" description:"价格乘数(折扣,如 0.9=九折)"` // 价格乘数(折扣,如 0.9=九折) + SortOrder int `json:"sort_order" orm:"sort_order" description:"排序权重"` // 排序权重 + CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:""` // + UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:""` // +} diff --git a/internal/model/entity/tnt_tenants.go b/internal/model/entity/tnt_tenants.go index aea9693..5e151c6 100644 --- a/internal/model/entity/tnt_tenants.go +++ b/internal/model/entity/tnt_tenants.go @@ -27,4 +27,5 @@ type TntTenants struct { DataRemovalAt *gtime.Time `json:"data_removal_at" orm:"data_removal_at" description:"数据清除时间(冻结 30 天后)"` // 数据清除时间(冻结 30 天后) MaxConcurrency int `json:"max_concurrency" orm:"max_concurrency" description:"租户总并发上限(0表示不限制)"` // 租户总并发上限(0表示不限制) DefaultChannelScope string `json:"default_channel_scope" orm:"default_channel_scope" description:"默认渠道范围(NULL或[]表示全部可用,否则为channel_id数组)"` // 默认渠道范围(NULL或[]表示全部可用,否则为channel_id数组) + Level int `json:"level" orm:"level" description:"当前等级(对应 tnt_tenant_level_configs.level)"` // 当前等级(对应 tnt_tenant_level_configs.level) } diff --git a/internal/model/entity/tsk_model_tasks.go b/internal/model/entity/tsk_model_tasks.go index 585d6c6..31ad672 100644 --- a/internal/model/entity/tsk_model_tasks.go +++ b/internal/model/entity/tsk_model_tasks.go @@ -10,29 +10,29 @@ import ( // TskModelTasks is the golang structure for table tsk_model_tasks. type TskModelTasks struct { - Id int64 `json:"id" orm:"id" description:""` // - PublicTaskId string `json:"public_task_id" orm:"public_task_id" description:""` // - Platform string `json:"platform" orm:"platform" description:""` // - Action string `json:"action" orm:"action" description:""` // - Status string `json:"status" orm:"status" description:""` // - Progress string `json:"progress" orm:"progress" description:""` // - FailReason string `json:"fail_reason" orm:"fail_reason" description:""` // - TenantId int64 `json:"tenant_id" orm:"tenant_id" description:""` // - UserId int64 `json:"user_id" orm:"user_id" description:""` // - ApiKeyId int64 `json:"api_key_id" orm:"api_key_id" description:""` // - ChannelId int64 `json:"channel_id" orm:"channel_id" description:""` // - ModelName string `json:"model_name" orm:"model_name" description:""` // - UpstreamModel string `json:"upstream_model" orm:"upstream_model" description:""` // - PreDeductAmount float64 `json:"pre_deduct_amount" orm:"pre_deduct_amount" description:""` // - ActualCost float64 `json:"actual_cost" orm:"actual_cost" description:""` // - BillingSettled bool `json:"billing_settled" orm:"billing_settled" description:""` // - ResultUrl string `json:"result_url" orm:"result_url" description:""` // - Data string `json:"data" orm:"data" description:""` // - PrivateData string `json:"private_data" orm:"private_data" description:""` // - SubmitTime *gtime.Time `json:"submit_time" orm:"submit_time" description:""` // - StartTime *gtime.Time `json:"start_time" orm:"start_time" description:""` // - FinishTime *gtime.Time `json:"finish_time" orm:"finish_time" description:""` // - CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:""` // - UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:""` // - RequestId string `json:"request_id" orm:"request_id" description:"任务提交时的原始请求 ID(req_xxxxx),关联 aud_request_logs.request_id"` // 任务提交时的原始请求 ID(req_xxxxx),关联 aud_request_logs.request_id + Id int64 `json:"id" orm:"id" description:"主键ID"` // 主键ID + PublicTaskId string `json:"public_task_id" orm:"public_task_id" description:"对外公开的任务ID(task_xxxxx 格式),用于 API 响应和客户端查询"` // 对外公开的任务ID(task_xxxxx 格式),用于 API 响应和客户端查询 + Platform string `json:"platform" orm:"platform" description:"任务所属平台:sora、kling、suno、midjourney、volcengine"` // 任务所属平台:sora、kling、suno、midjourney、volcengine + Action string `json:"action" orm:"action" description:"任务动作类型。通用:generate;Suno:music、lyrics;"` // 任务动作类型。通用:generate;Suno:music、lyrics; + Status string `json:"status" orm:"status" description:"任务状态:NOT_START-未开始、SUBMITTED-已提交、IN_PROGRESS-进行中、SUCCESS-成功、FAILURE-失败"` // 任务状态:NOT_START-未开始、SUBMITTED-已提交、IN_PROGRESS-进行中、SUCCESS-成功、FAILURE-失败 + Progress string `json:"progress" orm:"progress" description:"任务进度,字符串格式百分比(如 0%、50%、100%)"` // 任务进度,字符串格式百分比(如 0%、50%、100%) + FailReason string `json:"fail_reason" orm:"fail_reason" description:"任务失败原因文本"` // 任务失败原因文本 + TenantId int64 `json:"tenant_id" orm:"tenant_id" description:"发起任务的租户ID"` // 发起任务的租户ID + UserId int64 `json:"user_id" orm:"user_id" description:"发起任务的租户用户ID"` // 发起任务的租户用户ID + ApiKeyId int64 `json:"api_key_id" orm:"api_key_id" description:"调用方使用的 API Key ID"` // 调用方使用的 API Key ID + ChannelId int64 `json:"channel_id" orm:"channel_id" description:"转发请求的渠道ID"` // 转发请求的渠道ID + ModelName string `json:"model_name" orm:"model_name" description:"用户请求的模型名称(如 sora-1.0-turbo、midjourney 等)"` // 用户请求的模型名称(如 sora-1.0-turbo、midjourney 等) + UpstreamModel string `json:"upstream_model" orm:"upstream_model" description:"上游供应商实际使用的模型名称,可能与请求模型不同"` // 上游供应商实际使用的模型名称,可能与请求模型不同 + PreDeductAmount float64 `json:"pre_deduct_amount" orm:"pre_deduct_amount" description:"提交任务时的预扣金额(USD),任务完成后根据实际用量结算差额"` // 提交任务时的预扣金额(USD),任务完成后根据实际用量结算差额 + ActualCost float64 `json:"actual_cost" orm:"actual_cost" description:"任务实际消费金额(USD),成功时按此结算,失败时退还预扣金额"` // 任务实际消费金额(USD),成功时按此结算,失败时退还预扣金额 + BillingSettled bool `json:"billing_settled" orm:"billing_settled" description:"计费是否已结算(true 表示已完成预扣与实际金额的多退少补)"` // 计费是否已结算(true 表示已完成预扣与实际金额的多退少补) + ResultUrl string `json:"result_url" orm:"result_url" description:"任务完成后的结果资源 URL(如生成的视频/图片/音频的下载地址)"` // 任务完成后的结果资源 URL(如生成的视频/图片/音频的下载地址) + Data string `json:"data" orm:"data" description:"上游供应商返回的完整响应数据(JSONB),可返回给用户查看"` // 上游供应商返回的完整响应数据(JSONB),可返回给用户查看 + PrivateData string `json:"private_data" orm:"private_data" description:"内部私有数据(JSONB),含 upstream_task_id 等敏感字段,用于轮询上游状态,不返回给用户"` // 内部私有数据(JSONB),含 upstream_task_id 等敏感字段,用于轮询上游状态,不返回给用户 + SubmitTime *gtime.Time `json:"submit_time" orm:"submit_time" description:"任务提交到上游供应商的时间"` // 任务提交到上游供应商的时间 + StartTime *gtime.Time `json:"start_time" orm:"start_time" description:"上游供应商开始执行任务的时间"` // 上游供应商开始执行任务的时间 + FinishTime *gtime.Time `json:"finish_time" orm:"finish_time" description:"任务完成(成功或失败)的时间"` // 任务完成(成功或失败)的时间 + CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"记录创建时间"` // 记录创建时间 + UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"记录更新时间"` // 记录更新时间 + RequestId string `json:"request_id" orm:"request_id" description:"任务提交时的原始请求 ID(req_xxxxx),关联 aud_request_logs.request_id"` // 任务提交时的原始请求 ID(req_xxxxx),关联 aud_request_logs.request_id } diff --git a/internal/service/admin.go b/internal/service/admin.go index 0ae40ca..250b2cb 100644 --- a/internal/service/admin.go +++ b/internal/service/admin.go @@ -363,6 +363,10 @@ type ( UpdateTenantChannelScope(ctx context.Context, req *v1.TenantChannelScopeUpdateReq) (*v1.TenantChannelScopeUpdateRes, error) // ExportTenants exports tenant list to CSV or Excel. ExportTenants(ctx context.Context, req *v1.TenantExportReq) (*v1.TenantExportRes, error) + ListTenantLevelConfigs(ctx context.Context, _ *v1.TenantLevelConfigListReq) (*v1.TenantLevelConfigListRes, error) + CreateTenantLevelConfig(ctx context.Context, req *v1.TenantLevelConfigCreateReq) (*v1.TenantLevelConfigCreateRes, error) + UpdateTenantLevelConfig(ctx context.Context, req *v1.TenantLevelConfigUpdateReq) (*v1.TenantLevelConfigUpdateRes, error) + DeleteTenantLevelConfig(ctx context.Context, req *v1.TenantLevelConfigDeleteReq) (*v1.TenantLevelConfigDeleteRes, error) // ListTenantModels 列出租户已分配的模型 ListTenantModels(ctx context.Context, req *v1.TenantModelListReq) (*v1.TenantModelListRes, error) // BatchAssignModels 批量分配模型给租户 diff --git a/migrations/000005_tenant_level_system.sql b/migrations/000005_tenant_level_system.sql new file mode 100644 index 0000000..6bccb83 --- /dev/null +++ b/migrations/000005_tenant_level_system.sql @@ -0,0 +1,47 @@ +-- +goose Up +-- 租户等级系统:基于累计充值的自动等级体系 + +-- 1. 新建等级配置表 +CREATE TABLE tnt_tenant_level_configs ( + id BIGSERIAL PRIMARY KEY, + level INT NOT NULL, + name VARCHAR(32) NOT NULL, + cumulative_recharge_threshold NUMERIC(20,10) NOT NULL, + max_members INT NOT NULL DEFAULT 5, + max_concurrency INT NOT NULL DEFAULT 10, + price_multiplier NUMERIC(5,4) NOT NULL DEFAULT 1.0000, + sort_order INT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ DEFAULT now() NOT NULL, + updated_at TIMESTAMPTZ DEFAULT now() NOT NULL, + CONSTRAINT uk_tnt_tenant_level_configs_level UNIQUE (level) +); + +COMMENT ON TABLE tnt_tenant_level_configs IS '租户等级配置表'; +COMMENT ON COLUMN tnt_tenant_level_configs.level IS '等级号(1, 2, 3...)'; +COMMENT ON COLUMN tnt_tenant_level_configs.name IS '等级名称'; +COMMENT ON COLUMN tnt_tenant_level_configs.cumulative_recharge_threshold IS '累计充值阈值(USD),达到此值自动升级'; +COMMENT ON COLUMN tnt_tenant_level_configs.max_members IS '该等级最大成员数'; +COMMENT ON COLUMN tnt_tenant_level_configs.max_concurrency IS '该等级最大并发数,0=无限'; +COMMENT ON COLUMN tnt_tenant_level_configs.price_multiplier IS '价格乘数(折扣,如 0.9=九折)'; +COMMENT ON COLUMN tnt_tenant_level_configs.sort_order IS '排序权重'; + +-- 2. 插入默认等级数据 +INSERT INTO tnt_tenant_level_configs (level, name, cumulative_recharge_threshold, max_members, max_concurrency, price_multiplier, sort_order) VALUES +(1, 'LV1', 0, 5, 10, 1.0000, 1), +(2, 'LV2', 100, 10, 20, 0.9500, 2), +(3, 'LV3', 500, 20, 50, 0.9000, 3), +(4, 'LV4', 2000, 50, 100, 0.8500, 4), +(5, 'LV5', 10000, 0, 0, 0.8000, 5); + +-- 3. bil_wallets 添加累计充值字段 +ALTER TABLE bil_wallets ADD COLUMN cumulative_recharge NUMERIC(20,10) DEFAULT 0 NOT NULL; +COMMENT ON COLUMN bil_wallets.cumulative_recharge IS '累计充值总额(USD)'; + +-- 4. tnt_tenants 添加等级字段 +ALTER TABLE tnt_tenants ADD COLUMN level INT DEFAULT 1 NOT NULL; +COMMENT ON COLUMN tnt_tenants.level IS '当前等级(对应 tnt_tenant_level_configs.level)'; + +-- +goose Down +ALTER TABLE tnt_tenants DROP COLUMN IF EXISTS level; +ALTER TABLE bil_wallets DROP COLUMN IF EXISTS cumulative_recharge; +DROP TABLE IF EXISTS tnt_tenant_level_configs; diff --git a/web/admin/src/router/admin.ts b/web/admin/src/router/admin.ts index 465c2f6..0fc0bc9 100644 --- a/web/admin/src/router/admin.ts +++ b/web/admin/src/router/admin.ts @@ -150,6 +150,12 @@ const adminRoutes: RouteRecordRaw[] = [ component: () => import('@/views/tenant/TenantDetailPage.vue'), meta: { title: '租户详情' }, }, + { + path: 'tenant-levels', + name: 'AdminTenantLevels', + component: () => import('@/views/tenant/TenantLevelsPage.vue'), + meta: { title: '租户级别' }, + }, { path: 'members', name: 'AdminMembers', diff --git a/web/admin/src/views/AdminLayout.vue b/web/admin/src/views/AdminLayout.vue index 9c478f0..5441342 100644 --- a/web/admin/src/views/AdminLayout.vue +++ b/web/admin/src/views/AdminLayout.vue @@ -163,6 +163,7 @@ const menuGroups = [ icon: IconUserGroup, items: [ { name: 'AdminTenants', label: '租户列表', icon: IconHome }, + { name: 'AdminTenantLevels', label: '租户级别', icon: IconLayers }, { name: 'AdminMembers', label: '成员列表', icon: IconUser }, { name: 'AdminUsageLogs', label: '用量日志', icon: IconFile }, ], diff --git a/web/admin/src/views/tenant/TenantDetailPage.vue b/web/admin/src/views/tenant/TenantDetailPage.vue index ed54f85..f0a5359 100644 --- a/web/admin/src/views/tenant/TenantDetailPage.vue +++ b/web/admin/src/views/tenant/TenantDetailPage.vue @@ -63,6 +63,7 @@ function startEdit() { editForm.name = detail.value.name || '' editForm.max_members = detail.value.max_members || null editForm.max_concurrency = detail.value.max_concurrency || null + editForm.level = detail.value.level || null } async function saveEdit() { @@ -497,6 +498,9 @@ onMounted(() => { + + + 保存 diff --git a/web/admin/src/views/tenant/TenantLevelsPage.vue b/web/admin/src/views/tenant/TenantLevelsPage.vue new file mode 100644 index 0000000..0f2be34 --- /dev/null +++ b/web/admin/src/views/tenant/TenantLevelsPage.vue @@ -0,0 +1,182 @@ + + + diff --git a/web/tenant/src/views/settings/OrganizationPage.vue b/web/tenant/src/views/settings/OrganizationPage.vue index 7d60f6e..efddadf 100644 --- a/web/tenant/src/views/settings/OrganizationPage.vue +++ b/web/tenant/src/views/settings/OrganizationPage.vue @@ -214,6 +214,14 @@ onMounted(() => { + +
+

等级

+ + {{ orgInfo.level_name || 'LV' + orgInfo.level }} + +
+

成员数