From 80b47542087edbf42ba316eb2fd1edbaabc18f3f Mon Sep 17 00:00:00 2001 From: qian Date: Sun, 6 Sep 2026 23:02:34 +0800 Subject: [PATCH] =?UTF-8?q?fix(model):=E4=BF=AE=E5=A4=8D=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=BD=93=E6=9C=AA=E8=AE=BE=E7=BD=AE=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=9F=A5=E8=AF=A2=E5=87=BA=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/reference/goframe-conventions.md | 29 +++++++++++ internal/logic/admin/admin_user.go | 38 +++++++-------- internal/logic/admin/channel.go | 14 +++--- internal/logic/admin/model.go | 24 ++++----- internal/logic/admin/permission.go | 14 +++--- internal/logic/admin/role_crud.go | 16 +++--- internal/logic/admin/tenant.go | 29 ++++++----- internal/logic/admin/usage_log_cleanup.go | 16 +++--- internal/logic/open/open_api.go | 24 ++++----- internal/logic/settings/settings.go | 14 +++--- internal/logic/tenant/member.go | 54 ++++++++++----------- internal/logic/tenant/member_model_scope.go | 2 +- internal/logic/tenant/project.go | 6 +-- internal/middleware/idempotency.go | 6 +-- 14 files changed, 159 insertions(+), 127 deletions(-) diff --git a/docs/reference/goframe-conventions.md b/docs/reference/goframe-conventions.md index ca4aee4..515aff5 100644 --- a/docs/reference/goframe-conventions.md +++ b/docs/reference/goframe-conventions.md @@ -927,3 +927,32 @@ s.Start() **修复方式**:显式指定列 `dao.SysOptions.Ctx(ctx).Where("key", key).Fields("value").Value()`,与代码库其余 5 处 `Fields("xxx").Value()` 的既有写法保持一致。 **正确做法(通用规则)**:`Value()` / `Array()` 这类取单列的快捷方法**必须**显式 `Fields("列名")`,否则取到的是 `id`。评审信号:`grep -rn '\.Value()'` 命中且前面没有 `.Fields(` 的调用。 + +### 2026-09-05:Scan 进无 orm 标签的匿名结构体——TableFields 元数据查询失败时帕斯卡列名裸进 SQL + +**问题**:管理后台租户列表接口 `/api/admin/tenants` 偶发报 `pq: column "Id" does not exist`,SQL 里出现 `"Id","Name","LogoURL"` 等帕斯卡命名列。PostgreSQL 引号标识符大小写敏感,真实列是 `id`/`logo_url`,查询必然失败。 + +**原因**:`internal/logic/admin/tenant.go` 的 `ListTenants` 把查询结果 `Scan` 进一个**只有 json 标签、没有 orm 标签的匿名结构体**。gf 的 `Model.Scan`(`doStructs`)有个内置行为 *"Auto selecting fields by struct attributes"*:模型未显式 `Fields()` 时,会用**扫描结构体的字段名**自动生成 SELECT 字段列表。字段解析链路(`gdb_func.go` 的 `getFieldsFromStructOrMap`):有 orm 标签取标签值,**没有则取 Go 字段名原样**;随后 `mappingAndFilterToTableFields` 尝试用真实表结构把 `Id→id`、`LogoURL→logo_url` 纠偏,但它对元数据查询失败的容忍方式是(`gdb_model_utility.go`): + +```go +fieldsMap, _ := m.TableFields(fieldsTable) // 错误被静默吞掉 +if len(fieldsMap) == 0 { + return fields // 元数据拿不到就原样透传 Go 字段名 +} +``` + +pgsql 驱动的 `TableFields` 要实时查 `pg_attribute` 系统表(`'表名'::regclass`)。远程数据库瞬时抖动一次、这条目录查询失败,帕斯卡字段名就直接进了 SQL。**平时不炸的原因**:TableFields 成功结果进程内永久缓存(`gcache.DurationNoExpire`),启动后第一次查成功就一直复用;失败不缓存,所以重试通常即恢复。同请求内先执行的 `Count()` 走数据链路成功,进一步印证只有目录查询那一步挂了。 + +**修复方式**:给匿名结构体补上 orm 标签(`json:"id" orm:"id"` 等 13 个字段)。orm 标签值直接成为 SELECT 字段名——即使 TableFields 失败、字段名原样透传,透传出去的也是正确的小写蛇形列名,SQL 依然成立,彻底消除对运行时元数据的依赖。随后用 go/AST 脚本全仓排查同款写法,批量修复 24 处(幂等中间件、admin/tenant/open 列表与导出、成员模型范围、预算巡检等)。 + +**不受影响的两种同貌写法(排查时排除)**: + +- **模型链上有显式 `.Fields(...)`**:`doStructs` 的自动投影有前置条件 `len(fields)==0 && len(fieldsEx)==0`,显式 Fields 时不触发,SELECT 列已固定。 +- **`Raw(sql).Scan(&x)` 与 `result[0].Struct(&x)`**:Raw 路径在 `getFormattedSqlAndArgs` 里直接透传 rawSql、不拼接 Fields;`Record.Struct` 是内存行→结构体转换,根本不生成 SQL。 + +**正确做法(通用规则)**: + +- **凡 `Scan`/`Struct`/`Structs` 进非 `model/entity` 生成的结构体(匿名结构体、手写 DTO),每个字段必须带 `orm` 标签**,列名写死不依赖运行时映射;`entity.Xxx` 自带 orm 标签天然安全。 +- 判断风险的方法:`grep -rn 'var .* \[\]struct {' internal/` 找到匿名结构体,再看它是否被传给 `dao.Xxx.Ctx(ctx)...Scan(&x)` 链路且链上无 `Fields(...)`/`Raw(...)`——是则全部依赖这条元数据映射,远程 DB 抖动哪个请求撞上哪个接口炸,且报错完全看不出是网络问题。 + +排查信号:`pq: column "Xxx" does not exist` 且 SQL 列名是帕斯卡命名;偶发、重试即好、与网络抖动时间点吻合。 diff --git a/internal/logic/admin/admin_user.go b/internal/logic/admin/admin_user.go index b960f15..32594ed 100644 --- a/internal/logic/admin/admin_user.go +++ b/internal/logic/admin/admin_user.go @@ -29,16 +29,16 @@ func (s *sAdmin) ListUsers(ctx context.Context, req *v1.AdminUserListReq) (*v1.A m := buildUserFilters(dao.SysAdminUsers.Ctx(ctx), req.Keyword, req.Role, req.Status) var users []struct { - Id int64 `json:"id"` - Username string `json:"username"` - Email string `json:"email"` - DisplayName string `json:"display_name"` - Role string `json:"role"` - Status string `json:"status"` - LastLoginAt *gtime.Time `json:"last_login_at"` - LastLoginIp string `json:"last_login_ip"` - LockedUntil *gtime.Time `json:"locked_until"` - CreatedAt *gtime.Time `json:"created_at"` + Id int64 `json:"id" orm:"id"` + Username string `json:"username" orm:"username"` + Email string `json:"email" orm:"email"` + DisplayName string `json:"display_name" orm:"display_name"` + Role string `json:"role" orm:"role"` + Status string `json:"status" orm:"status"` + LastLoginAt *gtime.Time `json:"last_login_at" orm:"last_login_at"` + LastLoginIp string `json:"last_login_ip" orm:"last_login_ip"` + LockedUntil *gtime.Time `json:"locked_until" orm:"locked_until"` + CreatedAt *gtime.Time `json:"created_at" orm:"created_at"` } err = m.OrderDesc("id"). Page(page, pageSize). @@ -517,15 +517,15 @@ func (s *sAdmin) ExportUsers(ctx context.Context, req *v1.AdminUserExportReq) (* for { m := buildUserFilters(dao.SysAdminUsers.Ctx(ctx), req.Keyword, req.Role, req.Status) var batch []struct { - Id int64 `json:"id"` - Username string `json:"username"` - Email string `json:"email"` - DisplayName string `json:"display_name"` - Role string `json:"role"` - Status string `json:"status"` - LastLoginAt *gtime.Time `json:"last_login_at"` - LastLoginIp string `json:"last_login_ip"` - CreatedAt *gtime.Time `json:"created_at"` + Id int64 `json:"id" orm:"id"` + Username string `json:"username" orm:"username"` + Email string `json:"email" orm:"email"` + DisplayName string `json:"display_name" orm:"display_name"` + Role string `json:"role" orm:"role"` + Status string `json:"status" orm:"status"` + LastLoginAt *gtime.Time `json:"last_login_at" orm:"last_login_at"` + LastLoginIp string `json:"last_login_ip" orm:"last_login_ip"` + CreatedAt *gtime.Time `json:"created_at" orm:"created_at"` } if err := m.OrderDesc("id").Limit(1000).Offset(offset).Scan(&batch); err != nil { return diff --git a/internal/logic/admin/channel.go b/internal/logic/admin/channel.go index 809a674..d2f9548 100644 --- a/internal/logic/admin/channel.go +++ b/internal/logic/admin/channel.go @@ -719,13 +719,13 @@ func (s *sAdmin) GetChannelKeys(ctx context.Context, req *v1.ChannelKeyListReq) // GetChannelAbilities 获取渠道模型能力列表 func (s *sAdmin) GetChannelAbilities(ctx context.Context, req *v1.ChannelAbilitiesGetReq) (*v1.ChannelAbilitiesGetRes, error) { var abilities []struct { - ID int64 `json:"id"` - ModelName string `json:"model_name"` - UpstreamModel string `json:"upstream_model"` - Enabled bool `json:"enabled"` - CostRatio float64 `json:"cost_ratio"` - SupportsResponses bool `json:"supports_responses"` - ChatViaResponses bool `json:"chat_via_responses"` + ID int64 `json:"id" orm:"id"` + ModelName string `json:"model_name" orm:"model_name"` + UpstreamModel string `json:"upstream_model" orm:"upstream_model"` + Enabled bool `json:"enabled" orm:"enabled"` + CostRatio float64 `json:"cost_ratio" orm:"cost_ratio"` + SupportsResponses bool `json:"supports_responses" orm:"supports_responses"` + ChatViaResponses bool `json:"chat_via_responses" orm:"chat_via_responses"` } err := dao.ChnAbilities.Ctx(ctx). diff --git a/internal/logic/admin/model.go b/internal/logic/admin/model.go index 6f1e022..e03a4d8 100644 --- a/internal/logic/admin/model.go +++ b/internal/logic/admin/model.go @@ -443,18 +443,18 @@ func parseCapabilities(raw string) map[string]bool { // GetModelPricing 获取模型定价 func (s *sAdmin) GetModelPricing(ctx context.Context, req *v1.PricingGetReq) (*v1.PricingGetRes, error) { var rows []struct { - BillingMode string `json:"billing_mode"` - MinTokens int64 `json:"min_tokens"` - MaxTokens *int64 `json:"max_tokens"` - InputPrice float64 `json:"input_price"` - OutputPrice float64 `json:"output_price"` - PerRequestPrice *float64 `json:"per_request_price"` - CacheReadPrice float64 `json:"cache_read_price"` - CacheCreationPrice float64 `json:"cache_creation_price"` - TimeSegments string `json:"time_segments"` - PriceNote *string `json:"price_note"` - DiscountLabel *string `json:"discount_label"` - PriceChangeNote *string `json:"price_change_note"` + BillingMode string `json:"billing_mode" orm:"billing_mode"` + MinTokens int64 `json:"min_tokens" orm:"min_tokens"` + MaxTokens *int64 `json:"max_tokens" orm:"max_tokens"` + InputPrice float64 `json:"input_price" orm:"input_price"` + OutputPrice float64 `json:"output_price" orm:"output_price"` + PerRequestPrice *float64 `json:"per_request_price" orm:"per_request_price"` + CacheReadPrice float64 `json:"cache_read_price" orm:"cache_read_price"` + CacheCreationPrice float64 `json:"cache_creation_price" orm:"cache_creation_price"` + TimeSegments string `json:"time_segments" orm:"time_segments"` + PriceNote *string `json:"price_note" orm:"price_note"` + DiscountLabel *string `json:"discount_label" orm:"discount_label"` + PriceChangeNote *string `json:"price_change_note" orm:"price_change_note"` } err := dao.MdlPricing.Ctx(ctx). diff --git a/internal/logic/admin/permission.go b/internal/logic/admin/permission.go index 5bec54d..8c6cc5a 100644 --- a/internal/logic/admin/permission.go +++ b/internal/logic/admin/permission.go @@ -163,7 +163,7 @@ func PermissionGroups() []v1.PermissionGroup { func (s *sAdmin) GetUserPermissions(ctx context.Context, req *v1.AdminPermissionListReq) (*v1.AdminPermissionListRes, error) { // Get permission points var perms []struct { - PermissionPoint string `json:"permission_point"` + PermissionPoint string `json:"permission_point" orm:"permission_point"` } err := dao.SysAdminRolePerms.Ctx(ctx). Where("admin_user_id", req.Id). @@ -179,9 +179,9 @@ func (s *sAdmin) GetUserPermissions(ctx context.Context, req *v1.AdminPermission // Get data scopes var scopes []struct { - ID int64 `json:"id"` - ScopeType string `json:"scope_type"` - ScopeValue string `json:"scope_value"` + ID int64 `json:"id" orm:"id"` + ScopeType string `json:"scope_type" orm:"scope_type"` + ScopeValue string `json:"scope_value" orm:"scope_value"` } err = dao.SysAdminDataScopes.Ctx(ctx). Where("admin_user_id", req.Id). @@ -348,9 +348,9 @@ func GetDataScopes(ctx context.Context, userID int64, role string) ([]v1.DataSco } var scopes []struct { - ID int64 `json:"id"` - ScopeType string `json:"scope_type"` - ScopeValue string `json:"scope_value"` + ID int64 `json:"id" orm:"id"` + ScopeType string `json:"scope_type" orm:"scope_type"` + ScopeValue string `json:"scope_value" orm:"scope_value"` } err := dao.SysAdminDataScopes.Ctx(ctx). Where("admin_user_id", userID). diff --git a/internal/logic/admin/role_crud.go b/internal/logic/admin/role_crud.go index 6e4961f..e114eb9 100644 --- a/internal/logic/admin/role_crud.go +++ b/internal/logic/admin/role_crud.go @@ -101,14 +101,14 @@ func (s *sAdmin) ListRoles(ctx context.Context, _ *v1.AdminRoleListReq) (*v1.Adm return nil, err } var roles []struct { - ID int64 `json:"id"` - Code string `json:"code"` - Name string `json:"name"` - Description string `json:"description"` - IsBuiltin bool `json:"is_builtin"` - IsEnabled bool `json:"is_enabled"` - Sort int `json:"sort"` - CreatedAt string `json:"created_at"` + ID int64 `json:"id" orm:"id"` + Code string `json:"code" orm:"code"` + Name string `json:"name" orm:"name"` + Description string `json:"description" orm:"description"` + IsBuiltin bool `json:"is_builtin" orm:"is_builtin"` + IsEnabled bool `json:"is_enabled" orm:"is_enabled"` + Sort int `json:"sort" orm:"sort"` + CreatedAt string `json:"created_at" orm:"created_at"` } err := dao.SysAdminRoles.Ctx(ctx).OrderAsc("sort").OrderAsc("id").Scan(&roles) if err = lcommon.IgnoreScanNoRows(err); err != nil { diff --git a/internal/logic/admin/tenant.go b/internal/logic/admin/tenant.go index 38308f8..4c4f408 100644 --- a/internal/logic/admin/tenant.go +++ b/internal/logic/admin/tenant.go @@ -290,20 +290,23 @@ func (s *sAdmin) ListTenants(ctx context.Context, req *v1.TenantListReq) (*v1.Te m = m.Where("status", req.Status) } + // 扫描结构体必须带 orm 标签:gf 的 Scan 会用「结构体字段名」自动生成 SELECT 字段列表, + // 无 orm 标签时靠运行时查表结构(TableFields)做 Id→id 映射;该元数据查询失败会被框架静默吞掉, + // 帕斯卡字段名将原样进 SQL,PostgreSQL 引号列名大小写敏感,报 column "Id" does not exist。 var tenants []struct { - Id int64 `json:"id"` - Name string `json:"name"` - Code string `json:"code"` - LogoURL string `json:"logo_url"` - OwnerUserID int64 `json:"owner_user_id"` - Status string `json:"status"` - MaxMembers *int `json:"max_members"` - 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"` + Id int64 `json:"id" orm:"id"` + Name string `json:"name" orm:"name"` + Code string `json:"code" orm:"code"` + LogoURL string `json:"logo_url" orm:"logo_url"` + OwnerUserID int64 `json:"owner_user_id" orm:"owner_user_id"` + Status string `json:"status" orm:"status"` + MaxMembers *int `json:"max_members" orm:"max_members"` + MaxConcurrency *int `json:"max_concurrency" orm:"max_concurrency"` + DefaultChannelScope string `json:"default_channel_scope" orm:"default_channel_scope"` + Settings string `json:"settings" orm:"settings"` + Level int `json:"level" orm:"level"` + CreatedAt *gtime.Time `json:"created_at" orm:"created_at"` + UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at"` } err = m.OrderDesc("id"). Page(page, pageSize). diff --git a/internal/logic/admin/usage_log_cleanup.go b/internal/logic/admin/usage_log_cleanup.go index 7ec4871..60565e6 100644 --- a/internal/logic/admin/usage_log_cleanup.go +++ b/internal/logic/admin/usage_log_cleanup.go @@ -82,14 +82,14 @@ func (s *sAdmin) UsageLogCleanupList(ctx context.Context, req *v1.UsageLogCleanu } var tasks []struct { - Id int64 `json:"id"` - Name string `json:"name"` - Status string `json:"status"` - ErrorMessage string `json:"error_message"` - Result string `json:"result"` - StartedAt *time.Time `json:"started_at"` - FinishedAt *time.Time `json:"finished_at"` - CreatedAt *time.Time `json:"created_at"` + Id int64 `json:"id" orm:"id"` + Name string `json:"name" orm:"name"` + Status string `json:"status" orm:"status"` + ErrorMessage string `json:"error_message" orm:"error_message"` + Result string `json:"result" orm:"result"` + StartedAt *time.Time `json:"started_at" orm:"started_at"` + FinishedAt *time.Time `json:"finished_at" orm:"finished_at"` + CreatedAt *time.Time `json:"created_at" orm:"created_at"` } err = m.Page(page, pageSize).Scan(&tasks) if err != nil { diff --git a/internal/logic/open/open_api.go b/internal/logic/open/open_api.go index 6745cb1..6714abe 100644 --- a/internal/logic/open/open_api.go +++ b/internal/logic/open/open_api.go @@ -101,12 +101,12 @@ func (s *sOpen) OpenMemberList(ctx context.Context, req *v1.OpenMemberListReq) ( } var users []struct { - Id int64 `json:"id"` - Username string `json:"username"` - Email string `json:"email"` - Role string `json:"role"` - Status string `json:"status"` - CreatedAt string `json:"created_at"` + Id int64 `json:"id" orm:"id"` + Username string `json:"username" orm:"username"` + Email string `json:"email" orm:"email"` + Role string `json:"role" orm:"role"` + Status string `json:"status" orm:"status"` + CreatedAt string `json:"created_at" orm:"created_at"` } err = m.OrderDesc("id").Page(page, pageSize).Scan(&users) if err != nil { @@ -354,7 +354,7 @@ func (s *sOpen) OpenMemberModels(ctx context.Context, req *v1.OpenMemberModelsRe } var scopes []struct { - ModelID string `json:"model_id"` + ModelID string `json:"model_id" orm:"model_id"` } err := dao.TntMemberModelScopes.Ctx(ctx). Where("tenant_id", tenantID). @@ -460,11 +460,11 @@ func (s *sOpen) OpenKeyList(ctx context.Context, req *v1.OpenKeyListReq) (*v1.Op } var keys []struct { - Id int64 `json:"id"` - Name string `json:"name"` - KeyPrefix string `json:"key_prefix"` - Status string `json:"status"` - CreatedAt *gtime.Time `json:"created_at"` + Id int64 `json:"id" orm:"id"` + Name string `json:"name" orm:"name"` + KeyPrefix string `json:"key_prefix" orm:"key_prefix"` + Status string `json:"status" orm:"status"` + CreatedAt *gtime.Time `json:"created_at" orm:"created_at"` } err = m.OrderDesc("id").Page(page, pageSize).Scan(&keys) if err != nil { diff --git a/internal/logic/settings/settings.go b/internal/logic/settings/settings.go index 071ead7..186422e 100644 --- a/internal/logic/settings/settings.go +++ b/internal/logic/settings/settings.go @@ -65,13 +65,13 @@ func (s *sSettings) PublicAnnouncements(ctx context.Context, req *v1.PublicAnnou } var items []struct { - Id int64 `json:"id"` - Title string `json:"title"` - Type string `json:"type"` - Content string `json:"content"` - IsPinned int `json:"is_pinned"` - DisplayPosition string `json:"display_position"` - CreatedAt string `json:"created_at"` + Id int64 `json:"id" orm:"id"` + Title string `json:"title" orm:"title"` + Type string `json:"type" orm:"type"` + Content string `json:"content" orm:"content"` + IsPinned int `json:"is_pinned" orm:"is_pinned"` + DisplayPosition string `json:"display_position" orm:"display_position"` + CreatedAt string `json:"created_at" orm:"created_at"` } err := m.OrderDesc("is_pinned").OrderDesc("created_at"). Limit(20). diff --git a/internal/logic/tenant/member.go b/internal/logic/tenant/member.go index 9be1fe9..40c07bb 100644 --- a/internal/logic/tenant/member.go +++ b/internal/logic/tenant/member.go @@ -49,18 +49,18 @@ func (s *sTenant) ListMembers(ctx context.Context, req *v1.TenantMemberListReq) } var users []struct { - Id int64 `json:"id"` - Username string `json:"username"` - Email string `json:"email"` - DisplayName string `json:"display_name"` - Role string `json:"role"` - Status string `json:"status"` - LockedUntil string `json:"locked_until"` - CreatedAt string `json:"created_at"` - QuotaType string `json:"quota_type"` - QuotaLimit float64 `json:"quota_limit"` - QuotaPeriod string `json:"quota_period"` - UpdatedAt string `json:"updated_at"` + Id int64 `json:"id" orm:"id"` + Username string `json:"username" orm:"username"` + Email string `json:"email" orm:"email"` + DisplayName string `json:"display_name" orm:"display_name"` + Role string `json:"role" orm:"role"` + Status string `json:"status" orm:"status"` + LockedUntil string `json:"locked_until" orm:"locked_until"` + CreatedAt string `json:"created_at" orm:"created_at"` + QuotaType string `json:"quota_type" orm:"quota_type"` + QuotaLimit float64 `json:"quota_limit" orm:"quota_limit"` + QuotaPeriod string `json:"quota_period" orm:"quota_period"` + UpdatedAt string `json:"updated_at" orm:"updated_at"` } err = model.OrderDesc("id"). Page(page, pageSize). @@ -81,8 +81,8 @@ func (s *sTenant) ListMembers(ctx context.Context, req *v1.TenantMemberListReq) hasScopeMap := make(map[int64]bool) if len(userIds) > 0 { var scopeRows []struct { - UserId int64 `json:"user_id"` - ModelId int64 `json:"model_id"` + UserId int64 `json:"user_id" orm:"user_id"` + ModelId int64 `json:"model_id" orm:"model_id"` } if err = dao.TntMemberModelScopes.Ctx(ctx). Where("tenant_id", tenantID). @@ -1176,17 +1176,17 @@ func (s *sTenant) ExportMembers(ctx context.Context, req *v1.TenantMemberExportR } var users []struct { - Id int64 `json:"id"` - Username string `json:"username"` - Email string `json:"email"` - DisplayName string `json:"display_name"` - Role string `json:"role"` - Status string `json:"status"` - CreatedAt string `json:"created_at"` - QuotaType string `json:"quota_type"` - QuotaLimit float64 `json:"quota_limit"` - QuotaPeriod string `json:"quota_period"` - UpdatedAt string `json:"updated_at"` + Id int64 `json:"id" orm:"id"` + Username string `json:"username" orm:"username"` + Email string `json:"email" orm:"email"` + DisplayName string `json:"display_name" orm:"display_name"` + Role string `json:"role" orm:"role"` + Status string `json:"status" orm:"status"` + CreatedAt string `json:"created_at" orm:"created_at"` + QuotaType string `json:"quota_type" orm:"quota_type"` + QuotaLimit float64 `json:"quota_limit" orm:"quota_limit"` + QuotaPeriod string `json:"quota_period" orm:"quota_period"` + UpdatedAt string `json:"updated_at" orm:"updated_at"` } err := model.OrderDesc("id").Limit(1000).Offset(offset).Scan(&users) if err = common.IgnoreScanNoRows(err); err != nil { @@ -1203,8 +1203,8 @@ func (s *sTenant) ExportMembers(ctx context.Context, req *v1.TenantMemberExportR monthCostMap := make(map[int64]float64) if len(userIds) > 0 { var scopeRows []struct { - UserId int64 `json:"user_id"` - ModelId int64 `json:"model_id"` + UserId int64 `json:"user_id" orm:"user_id"` + ModelId int64 `json:"model_id" orm:"model_id"` } if err = dao.TntMemberModelScopes.Ctx(ctx). Where("tenant_id", tenantID). diff --git a/internal/logic/tenant/member_model_scope.go b/internal/logic/tenant/member_model_scope.go index e9bfdac..c39acad 100644 --- a/internal/logic/tenant/member_model_scope.go +++ b/internal/logic/tenant/member_model_scope.go @@ -34,7 +34,7 @@ func (s *sTenant) MemberModelScopes(ctx context.Context, req *v1.TenantMemberMod } var rows []struct { - ModelID int64 `json:"model_id"` + ModelID int64 `json:"model_id" orm:"model_id"` } err = dao.TntMemberModelScopes.Ctx(ctx). Where("tenant_id", tenantID). diff --git a/internal/logic/tenant/project.go b/internal/logic/tenant/project.go index c4f6045..d287403 100644 --- a/internal/logic/tenant/project.go +++ b/internal/logic/tenant/project.go @@ -399,9 +399,9 @@ func (s *sTenant) ProjectUnarchive(ctx context.Context, req *v1.TenantProjectUna func CheckBudgetExhausted(ctx context.Context) error { // Find active projects with budget set var projects []struct { - ID int64 `json:"id"` - TenantID int64 `json:"tenant_id"` - Budget float64 `json:"budget"` + ID int64 `json:"id" orm:"id"` + TenantID int64 `json:"tenant_id" orm:"tenant_id"` + Budget float64 `json:"budget" orm:"budget"` } err := dao.TntProjects.Ctx(ctx). Where("status", "active"). diff --git a/internal/middleware/idempotency.go b/internal/middleware/idempotency.go index dede4d6..3903992 100644 --- a/internal/middleware/idempotency.go +++ b/internal/middleware/idempotency.go @@ -58,9 +58,9 @@ func Idempotency(r *ghttp.Request) { // Check if this key has been processed before var record struct { - Id int64 `json:"id"` - Status string `json:"status"` - ResponseBody string `json:"response_body"` + Id int64 `json:"id" orm:"id"` + Status string `json:"status" orm:"status"` + ResponseBody string `json:"response_body" orm:"response_body"` } err := dao.SysIdempotencyRecords.Ctx(ctx).