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(() => {
等级
+ + {{ orgInfo.level_name || 'LV' + orgInfo.level }} + +成员数