diff --git a/api/admin/v1/model_group.go b/api/admin/v1/model_group.go index 5647b23..1a7cc74 100644 --- a/api/admin/v1/model_group.go +++ b/api/admin/v1/model_group.go @@ -28,6 +28,7 @@ type ModelGroupItem struct { Code string `json:"code"` Description string `json:"description"` Status string `json:"status"` + IsDefault bool `json:"is_default"` ModelCount int `json:"model_count"` TenantCount int `json:"tenant_count"` CreatedAt string `json:"created_at"` @@ -40,6 +41,7 @@ type ModelGroupCreateReq struct { Name string `json:"name" v:"required|length:1,100#请输入分组名称|分组名称长度1-100" dc:"分组名称"` Code string `json:"code" v:"required|length:1,50#请输入分组标识|分组标识长度1-50" dc:"分组唯一标识"` Description string `json:"description" dc:"分组描述"` + IsDefault *bool `json:"is_default" dc:"是否为新租户默认模型组"` ModelIds []int64 `json:"model_ids" dc:"初始包含的模型ID列表"` } @@ -55,6 +57,7 @@ type ModelGroupUpdateReq struct { Name string `json:"name" dc:"分组名称"` Description string `json:"description" dc:"分组描述"` Status string `json:"status" v:"in:active,disabled" dc:"状态:active/disabled"` + IsDefault *bool `json:"is_default" dc:"是否为新租户默认模型组"` } // ModelGroupUpdateRes 更新模型分组响应 @@ -150,5 +153,6 @@ type ModelGroupOptionItem struct { ID int64 `json:"id"` Name string `json:"name"` Code string `json:"code"` + IsDefault bool `json:"is_default"` ModelCount int `json:"model_count"` } diff --git a/internal/dao/internal/mdl_model_groups.go b/internal/dao/internal/mdl_model_groups.go index e12ac3a..5b895d6 100644 --- a/internal/dao/internal/mdl_model_groups.go +++ b/internal/dao/internal/mdl_model_groups.go @@ -26,6 +26,7 @@ type MdlModelGroupsColumns struct { Code string // 分组唯一标识(如 full_access、basic_chat) Description string // 分组描述 Status string // 状态:active(启用)/ disabled(禁用) + IsDefault string // 是否为新租户默认模型组,注册时自动关联 CreatedAt string // 创建时间 UpdatedAt string // 更新时间 } @@ -37,6 +38,7 @@ var mdlModelGroupsColumns = MdlModelGroupsColumns{ Code: "code", Description: "description", Status: "status", + IsDefault: "is_default", CreatedAt: "created_at", UpdatedAt: "updated_at", } diff --git a/internal/logic/admin/model_group.go b/internal/logic/admin/model_group.go index d89930d..afb2874 100644 --- a/internal/logic/admin/model_group.go +++ b/internal/logic/admin/model_group.go @@ -44,6 +44,7 @@ func (s *sAdmin) ListModelGroups(ctx context.Context, req *v1.ModelGroupListReq) Code string `json:"code"` Description string `json:"description"` Status string `json:"status"` + IsDefault bool `json:"is_default"` ModelCount int `json:"model_count"` TenantCount int `json:"tenant_count"` CreatedAt string `json:"created_at"` @@ -51,7 +52,7 @@ func (s *sAdmin) ListModelGroups(ctx context.Context, req *v1.ModelGroupListReq) } err := query. - Fields("mg.id, mg.name, mg.code, mg.description, mg.status, COALESCE(mc.model_count, 0) AS model_count, COALESCE(tc.tenant_count, 0) AS tenant_count, mg.created_at, mg.updated_at"). + Fields("mg.id, mg.name, mg.code, mg.description, mg.status, mg.is_default, COALESCE(mc.model_count, 0) AS model_count, COALESCE(tc.tenant_count, 0) AS tenant_count, mg.created_at, mg.updated_at"). OrderAsc("mg.id"). Page(req.Page, req.PageSize). Scan(&results) @@ -67,6 +68,7 @@ func (s *sAdmin) ListModelGroups(ctx context.Context, req *v1.ModelGroupListReq) Code: r.Code, Description: r.Description, Status: r.Status, + IsDefault: r.IsDefault, ModelCount: r.ModelCount, TenantCount: r.TenantCount, CreatedAt: r.CreatedAt, @@ -93,6 +95,7 @@ func (s *sAdmin) CreateModelGroup(ctx context.Context, req *v1.ModelGroupCreateR Name: req.Name, Code: req.Code, Description: req.Description, + IsDefault: req.IsDefault, }) if err != nil { return nil, err @@ -131,6 +134,9 @@ func (s *sAdmin) UpdateModelGroup(ctx context.Context, req *v1.ModelGroupUpdateR if req.Status != "" { data.Status = req.Status } + if req.IsDefault != nil { + data.IsDefault = *req.IsDefault + } _, err := dao.MdlModelGroups.Ctx(ctx).Where("id", req.ID).Data(data).Update() if err != nil { @@ -182,11 +188,12 @@ func (s *sAdmin) ListModelGroupOptions(ctx context.Context, req *v1.ModelGroupOp ID int64 `json:"id"` Name string `json:"name"` Code string `json:"code"` + IsDefault bool `json:"is_default"` ModelCount int `json:"model_count"` } err := query. - Fields("mg.id, mg.name, mg.code, COALESCE(mc.model_count, 0) AS model_count"). + Fields("mg.id, mg.name, mg.code, mg.is_default, COALESCE(mc.model_count, 0) AS model_count"). OrderAsc("mg.id"). Scan(&results) if err != nil { @@ -199,6 +206,7 @@ func (s *sAdmin) ListModelGroupOptions(ctx context.Context, req *v1.ModelGroupOp ID: r.ID, Name: r.Name, Code: r.Code, + IsDefault: r.IsDefault, ModelCount: r.ModelCount, }) } diff --git a/internal/logic/admin/tenant.go b/internal/logic/admin/tenant.go index e5dd80f..0acd8a6 100644 --- a/internal/logic/admin/tenant.go +++ b/internal/logic/admin/tenant.go @@ -7,6 +7,7 @@ import ( "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" v1 "github.com/qianfree/team-api/api/admin/v1" @@ -171,6 +172,26 @@ func (s *sAdmin) CreateTenant(ctx context.Context, req *v1.TenantCreateReq) (*v1 return gerror.Wrapf(err, "create wallet") } + // Assign default model groups + var defaultGroups []struct { + Id int64 + } + if err := tx.Model("mdl_model_groups").Ctx(ctx). + Where("is_default", true).Where("status", "active"). + Fields("id").Scan(&defaultGroups); err == nil && len(defaultGroups) > 0 { + insertData := make([]do.MdlTenantGroups, 0, len(defaultGroups)) + for _, dg := range defaultGroups { + insertData = append(insertData, do.MdlTenantGroups{ + TenantId: tenantID, + GroupId: dg.Id, + }) + } + if _, err := tx.Model("mdl_tenant_groups").Ctx(ctx). + Batch(len(insertData)).Insert(insertData); err != nil { + g.Log().Warningf(ctx, "assign default model groups failed: %v", err) + } + } + return nil }) if err != nil { diff --git a/internal/logic/relay/provider.go b/internal/logic/relay/provider.go index 34aa549..ac1ccf7 100644 --- a/internal/logic/relay/provider.go +++ b/internal/logic/relay/provider.go @@ -258,8 +258,7 @@ func checkGroupModelAccess(ctx context.Context, tenantID int64, modelName string cachedSet, found := lcommon.TenantGroupModelCache.Get(ctx, cacheKey) if found { if cachedSet == nil { - // nil 表示无分组 → 向后兼容,允许所有模型 - return true, nil, nil + return false, nil, nil } if modelSet, ok := cachedSet.(map[string]bool); ok { _, inGroup := modelSet[modelName] @@ -273,9 +272,8 @@ func checkGroupModelAccess(ctx context.Context, tenantID int64, modelName string Count() if groupCount == 0 { - // 无分组 → 向后兼容,允许所有模型 lcommon.TenantGroupModelCache.Set(ctx, cacheKey, nil) - return true, nil, nil + return false, nil, nil } // 查询租户所有活跃分组中的可用模型 @@ -747,13 +745,8 @@ func (p *DataProviderImpl) GetAvailableModels(ctx context.Context, tenantID, api hasGroup := len(groupModelIDs) > 0 if !hasExplicit && !hasGroup { - // 无分配记录且无分组:返回所有活跃模型(向后兼容) - err = dao.MdlModels.Ctx(ctx). - Where("status", "active"). - Fields(fieldsNoAlias). - OrderAsc("category"). - OrderAsc("model_id"). - Scan(&models) + // 无分配记录且无分组:返回空列表 + models = make([]modelRow, 0) } else { // 构建合并的模型 ID 集合 allModelIDs := make([]int64, 0) diff --git a/internal/logic/tenant/auth.go b/internal/logic/tenant/auth.go index 4c4b99a..d15ded8 100644 --- a/internal/logic/tenant/auth.go +++ b/internal/logic/tenant/auth.go @@ -148,6 +148,26 @@ func (s *sTenant) Register(ctx context.Context, req *v1.TenantRegisterReq) (*v1. return gerror.Wrapf(err, "create wallet") } + // Assign default model groups + var defaultGroups []struct { + Id int64 + } + if err := tx.Model("mdl_model_groups").Ctx(ctx). + Where("is_default", true).Where("status", "active"). + Fields("id").Scan(&defaultGroups); err == nil && len(defaultGroups) > 0 { + insertData := make([]do.MdlTenantGroups, 0, len(defaultGroups)) + for _, dg := range defaultGroups { + insertData = append(insertData, do.MdlTenantGroups{ + TenantId: tenantID, + GroupId: dg.Id, + }) + } + if _, err := tx.Model("mdl_tenant_groups").Ctx(ctx). + Batch(len(insertData)).Insert(insertData); err != nil { + g.Log().Warningf(ctx, "assign default model groups failed: %v", err) + } + } + return nil }) if err != nil { diff --git a/internal/model/do/mdl_model_groups.go b/internal/model/do/mdl_model_groups.go index 2a2b3e0..43e2579 100644 --- a/internal/model/do/mdl_model_groups.go +++ b/internal/model/do/mdl_model_groups.go @@ -17,6 +17,7 @@ type MdlModelGroups struct { Code any // 分组唯一标识(如 full_access、basic_chat) Description any // 分组描述 Status any // 状态:active(启用)/ disabled(禁用) + IsDefault any // 是否为新租户默认模型组,注册时自动关联 CreatedAt *gtime.Time // 创建时间 UpdatedAt *gtime.Time // 更新时间 } diff --git a/internal/model/entity/mdl_model_groups.go b/internal/model/entity/mdl_model_groups.go index 9b549f2..6ffa199 100644 --- a/internal/model/entity/mdl_model_groups.go +++ b/internal/model/entity/mdl_model_groups.go @@ -15,6 +15,7 @@ type MdlModelGroups struct { Code string `json:"code" orm:"code" description:"分组唯一标识(如 full_access、basic_chat)"` // 分组唯一标识(如 full_access、basic_chat) Description string `json:"description" orm:"description" description:"分组描述"` // 分组描述 Status string `json:"status" orm:"status" description:"状态:active(启用)/ disabled(禁用)"` // 状态:active(启用)/ disabled(禁用) + IsDefault bool `json:"is_default" orm:"is_default" description:"是否为新租户默认模型组,注册时自动关联"` // 是否为新租户默认模型组,注册时自动关联 CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间 UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"更新时间"` // 更新时间 } diff --git a/migrations/000008_add_model_group_is_default.sql b/migrations/000008_add_model_group_is_default.sql new file mode 100644 index 0000000..574a75e --- /dev/null +++ b/migrations/000008_add_model_group_is_default.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- 模型分组增加"默认分组"标记,标记为默认的分组在新租户注册时自动关联 + +ALTER TABLE mdl_model_groups ADD COLUMN is_default BOOLEAN NOT NULL DEFAULT FALSE; +COMMENT ON COLUMN mdl_model_groups.is_default IS '是否为新租户默认模型组,注册时自动关联'; + +-- +goose Down + +ALTER TABLE mdl_model_groups DROP COLUMN IF EXISTS is_default; diff --git a/web/admin/src/views/ai/ModelGroupsPage.vue b/web/admin/src/views/ai/ModelGroupsPage.vue index 0e956fe..6bc5ce3 100644 --- a/web/admin/src/views/ai/ModelGroupsPage.vue +++ b/web/admin/src/views/ai/ModelGroupsPage.vue @@ -18,6 +18,10 @@ const pagination = reactive({ const filterSearch = ref('') const filterStatus = ref(null) +const hasDefaultGroup = computed(() => { + return data.value.some((item: any) => item.is_default && item.status === 'active') +}) + async function fetchData() { loading.value = true try { @@ -67,6 +71,12 @@ const columns: TableColumnData[] = [ return h(Tag, { color, size: 'small' }, () => label) }, }, + { + title: '默认', dataIndex: 'is_default', width: 70, + render({ record }) { + return record.is_default ? h(Tag, { color: 'blue', size: 'small' }, () => '默认') : '-' + }, + }, { title: '模型数', dataIndex: 'model_count', width: 80 }, { title: '租户数', dataIndex: 'tenant_count', width: 80 }, { @@ -100,12 +110,14 @@ const form = reactive({ name: '', code: '', description: '', + is_default: false, }) function resetForm() { form.name = '' form.code = '' form.description = '' + form.is_default = false } function openCreate() { @@ -119,6 +131,7 @@ function openEdit(row: any) { form.name = row.name || '' form.code = row.code || '' form.description = row.description || '' + form.is_default = !!row.is_default editingId.value = row.id modalTitle.value = '编辑分组' showModal.value = true @@ -131,7 +144,7 @@ async function handleSubmit(done: () => void) { } formLoading.value = true try { - const payload = { name: form.name, code: form.code, description: form.description } + const payload: any = { name: form.name, description: form.description, is_default: form.is_default } if (editingId.value) { await request.put(`/admin/model-groups/${editingId.value}`, payload) Message.success('更新成功') @@ -141,6 +154,7 @@ async function handleSubmit(done: () => void) { formLoading.value = false return } + payload.code = form.code await request.post('/admin/model-groups', payload) Message.success('创建成功') } @@ -232,6 +246,10 @@ onMounted(() => { + + 当前未设置默认模型组,新注册租户将无法使用任何模型。请创建一个分组并标记为「默认」。 + + @@ -294,6 +312,10 @@ onMounted(() => { + + + 开启后,新注册的租户将自动关联此分组 +