You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

171 lines
5.7 KiB

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()})
}