Files
model-gateway/service/model_manage_service.go
T
19904408334andClaude 6cff7a934b feat: 系统模型被引用拒删,系统 apiKey 对非创建者脱敏
- Delete:系统模型被引用 → 拒绝;引用行/自有行仅创建者可删
- List/Get:系统模型 apiKey 对非创建者返回空

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-29 14:38:49 +08:00

373 lines
11 KiB
Go

package service
import (
"context"
"fmt"
"model-gateway/consts/model"
"model-gateway/consts/public"
"model-gateway/dao"
"model-gateway/model/dto"
"model-gateway/model/entity"
"gitea.redpowerfuture.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.com/red-future/common/utils"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
var ModelManage = &modelManageService{}
type modelManageService struct{}
// Create 创建模型
func (s *modelManageService) Create(ctx context.Context, req *dto.CreateModelManageReq) (res *dto.CreateModelManageRes, err error) {
err = gfdb.DB(ctx, public.DbNameModelGateway).Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
// 1)检查是否是超管
var isSuperAdmin bool
isSuperAdmin, err = IsSuperAdmin(ctx)
if err != nil {
return
}
req.SystemModel = &isSuperAdmin
// 1)如果设为会话模型,先把该用户旧会话模型取消
err = s.CancelChatModel(ctx, req.ModelType, req.ChatModel, isSuperAdmin)
if err != nil {
return
}
// 2)模型名称唯一性:同一用户下不允许同名模型
var user *beans.User
user, err = utils.GetUserInfo(ctx)
if err != nil {
return
}
if !g.IsEmpty(req.ModelName) {
var exist *entity.ModelManage
exist, err = dao.ModelManage.GetByCreatorAndName(ctx, user.UserName, req.ModelName)
if err != nil {
return
}
if exist != nil {
return fmt.Errorf("模型名称 [%s] 已存在,同一用户下不能重复添加同名模型", req.ModelName)
}
}
// 3)插入数据
id, err := dao.ModelManage.Insert(ctx, req)
if err != nil {
return
}
res = &dto.CreateModelManageRes{Id: id}
return
})
return
}
// ReferenceSystemModel 用户引用系统模型:只填 apiKey + 个人开关,配置实时跟随系统模型。
// 不拷贝配置(解析层实时取系统行);同名唯一(复用 Create 语义);会话模型开关复用 CancelChatModel。
func (s *modelManageService) ReferenceSystemModel(ctx context.Context, req *dto.ReferenceSystemModelReq) (res *dto.ReferenceSystemModelRes, err error) {
err = gfdb.DB(ctx, public.DbNameModelGateway).Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
user, err := utils.GetUserInfo(ctx)
if err != nil {
return err
}
// 1)目标必须是系统模型
sys, err := dao.ModelManage.GetNotTenantId(ctx, &dto.GetModelManageReq{Id: req.SystemModelId})
if err != nil {
return err
}
if sys == nil || sys.SystemModel == nil || !*sys.SystemModel {
return fmt.Errorf("被引用的模型不存在或不是系统模型")
}
// 2)同名唯一:同一用户下不允许重复(引用行与自有模型同名同样拒绝)
exist, err := dao.ModelManage.GetByCreatorAndName(ctx, user.UserName, sys.ModelName)
if err != nil {
return err
}
if exist != nil {
return fmt.Errorf("模型名称 [%s] 已存在,同一用户下不能重复添加同名模型", sys.ModelName)
}
// 3)设为会话模型 → 先取消旧会话模型(isSuperAdmin 语义与 Create 一致)
var isSuperAdmin bool
isSuperAdmin, err = IsSuperAdmin(ctx)
if err != nil {
return err
}
if err = s.CancelChatModel(ctx, sys.ModelType, req.ChatModel, isSuperAdmin); err != nil {
return err
}
// 4)插引用行(配置列零值即可,解析层只读系统行配置)
enabled := true
if req.Enabled != nil {
enabled = *req.Enabled
}
id, err := dao.ModelManage.Insert(ctx, &dto.CreateModelManageReq{
ModelSupplier: sys.ModelSupplier,
ModelName: sys.ModelName,
ModelType: sys.ModelType,
SystemModel: gconv.PtrBool(false),
ChatModel: req.ChatModel,
ApiKey: req.ApiKey,
Enabled: gconv.PtrBool(enabled),
RefSystemModelId: sys.Id,
})
if err != nil {
return err
}
res = &dto.ReferenceSystemModelRes{Id: id}
return nil
})
return
}
// Update 更新模型配置
// 引用行:只改 apiKey/enabled/chatModel,配置锁定跟随系统模型(其余字段忽略);
// 系统模型:仅创建者(超管)可改配置,改名时同步引用行 model_name;
// 用户自有模型:创建者可改全配置。非创建者操作他人行 → 无权限(引用不再靠"编辑系统模型"触发拷贝,改走 referenceSystemModel)。
func (s *modelManageService) Update(ctx context.Context, req *dto.UpdateModelManageReq) (res *dto.GetModelManageRes, err error) {
err = gfdb.DB(ctx, public.DbNameModelGateway).Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
var get *entity.ModelManage
get, err = dao.ModelManage.GetNotTenantId(ctx, &dto.GetModelManageReq{
Id: req.Id,
})
if err != nil {
return
}
if get == nil {
return fmt.Errorf("模型不存在")
}
var user *beans.User
user, err = utils.GetUserInfo(ctx)
if err != nil {
return
}
// 引用行:只应用个人字段,配置忽略(跟随系统模型;isSuperAdmin=false=个人会话模型开关)
if get.RefSystemModelId > 0 {
if err = s.CancelChatModel(ctx, get.ModelType, req.ChatModel, false); err != nil {
return err
}
_, err = dao.ModelManage.Update(ctx, &dto.UpdateModelManageReq{
Id: req.Id,
ApiKey: req.ApiKey,
Enabled: req.Enabled,
ChatModel: req.ChatModel,
})
return
}
// 非创建者 → 无权限(引用已由 referenceSystemModel 端点管理,不再拷贝)
if get.Creator != user.UserName {
return fmt.Errorf("无权限操作")
}
// 1)检查是否是超管
var isSuperAdmin bool
isSuperAdmin, err = IsSuperAdmin(ctx)
if err != nil {
return
}
modelType := req.ModelType
if g.IsEmpty(modelType) {
modelType = get.ModelType
}
// 1)如果设为会话模型,先把该用户旧会话模型取消
err = s.CancelChatModel(ctx, modelType, req.ChatModel, isSuperAdmin)
if err != nil {
return
}
// 2)模型名称唯一性:同一用户下不允许同名模型(排除自身)
if !g.IsEmpty(req.ModelName) {
var exist *entity.ModelManage
exist, err = dao.ModelManage.GetByCreatorAndName(ctx, user.UserName, req.ModelName)
if err != nil {
return
}
if exist != nil && exist.Id != req.Id {
return fmt.Errorf("模型名称 [%s] 已存在,同一用户下不能重复添加同名模型", req.ModelName)
}
}
// 3)系统模型改名 → 同步引用行 model_name(保列表 DISTINCT ON 去重正确)
if get.SystemModel != nil && *get.SystemModel && req.ModelName != "" && req.ModelName != get.ModelName {
if _, err = dao.ModelManage.UpdateReferencesName(ctx, get.Id, req.ModelName); err != nil {
return err
}
}
// 4)更新数据
_, err = dao.ModelManage.Update(ctx, req)
return
})
return
}
func (s *modelManageService) CancelChatModel(ctx context.Context, modelType model.ModelType, chatModel *bool, isSuperAdmin bool) (err error) {
if !g.IsEmpty(chatModel) && *chatModel {
if !g.IsEmpty(modelType) && *modelType == *model.ModelTypeInference.Code {
if isSuperAdmin {
return fmt.Errorf("超级管理员不能设置会话模型")
}
// 2)获取该用户信息
var user *beans.User
user, err = utils.GetUserInfo(ctx)
if err != nil {
return
}
// 3)取消该用户之前的会话模型
var get *entity.ModelManage
get, err = dao.ModelManage.Get(ctx, &dto.GetModelManage{
Creator: user.UserName,
ChatModel: chatModel,
})
if err != nil {
return
}
if g.IsEmpty(get) {
return
}
_, err = dao.ModelManage.Update(ctx, &dto.UpdateModelManageReq{
Id: get.Id,
ChatModel: gconv.PtrBool(false),
})
if err != nil {
return
}
} else {
return fmt.Errorf("只有推理模型可以设置成会话模型")
}
}
return
}
// Delete 删除模型:系统模型被引用 → 拒绝;引用行/自有行仅创建者可删
func (s *modelManageService) Delete(ctx context.Context, req *dto.DeleteModelManageReq) error {
user, err := utils.GetUserInfo(ctx)
if err != nil {
return err
}
get, err := dao.ModelManage.GetNotTenantId(ctx, &dto.GetModelManageReq{Id: req.Id})
if err != nil {
return err
}
if get == nil {
return nil
}
// 系统模型被引用 → 拒绝删除(防悬挂)
if get.SystemModel != nil && *get.SystemModel {
n, err := dao.ModelManage.CountReferences(ctx, get.Id)
if err != nil {
return err
}
if n > 0 {
return fmt.Errorf("系统模型已被 %d 个用户引用,不能删除", n)
}
_, err = dao.ModelManage.Delete(ctx, req)
return err
}
// 引用行/自有行:仅创建者可删(删引用行即解除引用)
if get.Creator != user.UserName {
return fmt.Errorf("无权限操作")
}
_, err = dao.ModelManage.Delete(ctx, req)
return err
}
func (s *modelManageService) Get(ctx context.Context, req *dto.GetModelManageReq) (res *dto.GetModelManageRes, err error) {
get, err := dao.ModelManage.GetNotTenantId(ctx, req)
if err != nil {
return
}
if get == nil {
return new(dto.GetModelManageRes), nil
}
// 系统模型 apiKey 对非创建者脱敏(引用行/自有行仅本人可见)
user, err := utils.GetUserInfo(ctx)
if err != nil {
return
}
if get.SystemModel != nil && *get.SystemModel && get.Creator != user.UserName {
get.ApiKey = ""
}
res = new(dto.GetModelManageRes)
err = gconv.Struct(get, &res.ModelManage)
return
}
func (s *modelManageService) GetChatModel(ctx context.Context, req *dto.GetChatModelReq) (res *dto.GetChatModelRes, err error) {
user, err := utils.GetUserInfo(ctx)
if err != nil {
return
}
get, err := dao.ModelManage.Get(ctx, &dto.GetModelManage{
Creator: user.UserName,
ChatModel: gconv.PtrBool(true),
})
if err != nil {
return
}
res = &dto.GetChatModelRes{
ModelManage: get,
}
return
}
// List 获取模型列表
func (s *modelManageService) List(ctx context.Context, req *dto.ListModelManageReq) (res *dto.ListModelManageRes, err error) {
if req.IsSameType && !g.IsEmpty(req.Id) {
var get *entity.ModelManage
get, err = dao.ModelManage.GetNotTenantId(ctx, &dto.GetModelManageReq{
Id: req.Id,
})
if err != nil {
return
}
req.ModelType = get.ModelType
}
var user *beans.User
user, err = utils.GetUserInfo(ctx)
if err != nil {
return
}
req.Creator = user.UserName
list, total, err := dao.ModelManage.ListNotTenantId(ctx, req)
if err != nil {
return
}
// 系统模型 apiKey 对非创建者脱敏(引用行/自有行仅本人可见)
for _, row := range list {
if row.SystemModel != nil && *row.SystemModel && row.Creator != user.UserName {
row.ApiKey = ""
}
}
res = &dto.ListModelManageRes{
Total: total,
}
err = gconv.Struct(list, &res.List)
return
}
func (s *modelManageService) CheckChatModel(ctx context.Context, req *dto.CheckChatModelReq) (res *dto.CheckChatModelRes, err error) {
get, err := s.GetChatModel(ctx, &dto.GetChatModelReq{})
if err != nil {
return
}
res = &dto.CheckChatModelRes{
IsChatModel: !g.IsEmpty(get),
}
return
}
// GetModelType 获取模型类型
func (s *modelManageService) GetModelType(ctx context.Context, req *dto.ModelTypeReq) (res *dto.ModelTypeRes, err error) {
res = &dto.ModelTypeRes{
List: model.GetTypeTreeList(),
}
return res, nil
}
// GetModelSupplier 获取运营商列表
func (s *modelManageService) GetModelSupplier(ctx context.Context, req *dto.ModelSupplierReq) (res *dto.ModelSupplierRes, err error) {
return &dto.ModelSupplierRes{
List: model.GetSupplierOptionList(),
}, nil
}