package console import ( "strconv" "strings" "time" "yunyan/comm" "yunyan/lego/sys/log" "yunyan/lego/sys/postgres" "yunyan/pb" "github.com/gin-gonic/gin" ) // ============================ 第三方服务类别 CRUD ============================ // // 后台「服务与环境配置 → 第三方服务 → 类别管理」的后端。规则见 model_svccategory.go 头部注释: // 内置类别只能改展示属性,自定义类别可增删改,删除前查引用。 // seedSvcCategories 幂等 seed 内置类别:不存在则插入;已存在只补 builtin/voice 两个语义标记, // 展示属性(label/short/color/sort/enabled)一律保留运营改过的值。启动时跑一次。 func seedSvcCategories() { inserted := 0 for _, c := range builtinSvcCategories() { exist := &SvcCategory{} if err := postgres.FindOne(comm.TableSvcCategory, exist, "id=?", c.Id); err == nil { if exist.Builtin != true || exist.Voice != c.Voice { exist.Builtin = true exist.Voice = c.Voice _ = postgres.Save(comm.TableSvcCategory, exist) } continue } if err := postgres.Insert(comm.TableSvcCategory, &c); err != nil { log.Warnf("console: seed 服务类别 %d 失败: %v", c.Id, err) continue } inserted++ } if inserted > 0 { log.Infof("console: 内置服务类别 seed 完成 inserted=%d", inserted) } } // getSvcCategories 全部类别(含停用的,前端自己按 enabled 决定在哪些地方展示),按 sort,id 升序。 func (this *serverComp) getSvcCategories(c *gin.Context) { items := make([]*SvcCategory, 0, 16) if err := postgres.Table(comm.TableSvcCategory).Order("sort ASC, id ASC").Find(&items).Error; err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } for _, it := range items { fillSvcCategoryRuntime(it) } writeOK(c, gin.H{"items": items}) } // saveSvcCategory 新增或更新一个类别。 // - id=0 → 新增自定义类别,id 由服务端从 100 起分配; // - id>0 → 更新:内置类别只接受展示属性,builtin/id 不可改;voice 内置的也不让改 // (音色预填是按类别语义定的,改了新建 TTS 服务时 languages 就不预填了)。 func (this *serverComp) saveSvcCategory(c *gin.Context) { var m SvcCategory if err := c.ShouldBindJSON(&m); err != nil { writeErr(c, pb.ErrorCode_ReqParameterError, err.Error()) return } m.Label = strings.TrimSpace(m.Label) m.Short = strings.TrimSpace(m.Short) if m.Label == "" || m.Short == "" { writeErr(c, pb.ErrorCode_ReqParameterError, "名称与角标 必填") return } if len([]rune(m.Short)) > 8 { writeErr(c, pb.ErrorCode_ReqParameterError, "角标最多 8 个字符(卡片上放不下)") return } if m.Color == "" { m.Color = "#475569" } if m.Bg == "" { m.Bg = "#f1f5f9" } if m.Id == 0 { // 新增:分配 id。max(id, 99)+1 保证自定义从 100 起,与内置永不相撞。 var maxId int32 _ = postgres.Table(comm.TableSvcCategory).Select("COALESCE(MAX(id), 0)").Scan(&maxId).Error if maxId < svcCategoryCustomIdStart-1 { maxId = svcCategoryCustomIdStart - 1 } m.Id = maxId + 1 m.Builtin = false if err := postgres.Insert(comm.TableSvcCategory, &m); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } fillSvcCategoryRuntime(&m) writeOK(c, &m) return } old := &SvcCategory{} if err := postgres.FindOne(comm.TableSvcCategory, old, "id=?", m.Id); err != nil { writeErr(c, pb.ErrorCode_DBError, "类别不存在: "+strconv.Itoa(int(m.Id))) return } // 语义标记只认库里的:内置的 builtin/voice 不可改,自定义的 voice 可改、builtin 恒 false。 m.Builtin = old.Builtin if old.Builtin { m.Voice = old.Voice } if err := postgres.Save(comm.TableSvcCategory, &m); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } fillSvcCategoryRuntime(&m) writeOK(c, &m) } // delSvcCategory 删除自定义类别。内置类别拒绝;被服务或模板引用的也拒绝,并把引用列出来。 func (this *serverComp) delSvcCategory(c *gin.Context) { var req struct { Id int32 `json:"id"` } if err := c.ShouldBindJSON(&req); err != nil || req.Id == 0 { writeErr(c, pb.ErrorCode_ReqParameterError, "id 必填") return } old := &SvcCategory{} if err := postgres.FindOne(comm.TableSvcCategory, old, "id=?", req.Id); err != nil { writeErr(c, pb.ErrorCode_DBError, "类别不存在") return } if old.Builtin { writeErr(c, pb.ErrorCode_ReqParameterError, "内置类别不能删除:运行时与内置模板按这个 id 引用(如 MT=3 用于 user_translate、实名=11 决定不下发)。不想用可以停用。") return } // 引用检查:服务实例的 categories 是逗号分隔多值,逐条拆开比对,别用 LIKE('1' 会命中 '10'/'11')。 svcs := make([]*ThirdSvcConfig, 0) _ = postgres.Find(comm.TableSvcConfig, &svcs, "") refs := make([]string, 0, 4) for _, s := range svcs { if categoriesHas(s.Categories, req.Id) { scope := s.AppName if scope == "" { scope = "<全局>" } refs = append(refs, "服务 "+s.Id+"("+scope+")") } } tpls := make([]*ThirdSvcTemplate, 0) _ = postgres.Find(comm.TableThirdSvcTemplate, &tpls, "category=?", req.Id) for _, t := range tpls { refs = append(refs, "模板 "+t.Id) } if len(refs) > 0 { if len(refs) > 6 { refs = append(refs[:6], "…等 "+strconv.Itoa(len(refs))+" 处") } writeErr(c, pb.ErrorCode_ReqParameterError, "该类别仍被引用,先把它们改到别的类别:"+strings.Join(refs, "、")) return } if err := postgres.Delete(comm.TableSvcCategory, "id=?", req.Id); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } writeOK(c, gin.H{"deleted": 1, "at": time.Now().Unix()}) }