package console import ( "strings" "time" "yunyan/comm" "yunyan/pb" "github.com/gin-gonic/gin" ) // ============================ 会议记录服务配置 (echomeetsvc) ============================ // // admin「会议记录服务配置」页的后端:读写**应用业务库**里的 echomeet_svc_config 表 // (识别/翻译/总结三类,各自按优先级;识别可配支持语言;总结为具体模型条目)。全局一份、无 app_name。 // // 业务库连接按部署 appId 从 registry 动态取(与 modulecfg 同机制); // 密钥字段(是否密钥以 comm.EchoSvcCatalog 为准)AES-GCM 加密落库、返回前端脱敏为 comm.EncMask。 // 改配置后广播 ConfigKindEchomeetSvc;home 侧 echomeet 改配置需重启生效。 // echoSvcCatalog 返回会议记录服务字段目录(前端据此渲染各服务商表单、识别语言选择器)。与选中应用无关。 func (this *serverComp) echoSvcCatalog(c *gin.Context) { writeOK(c, gin.H{ "catalog": comm.EchoSvcCatalog, "categories": []gin.H{ {"value": comm.EchoSvcCatASR, "label": "识别"}, {"value": comm.EchoSvcCatMT, "label": "翻译"}, {"value": comm.EchoSvcCatLLM, "label": "总结"}, }, }) } // echoSvcList 列出某部署业务库里的会议记录服务配置;密钥脱敏。 func (this *serverComp) echoSvcList(c *gin.Context) { var req struct { AppId uint32 `json:"app_id"` } _ = c.ShouldBindJSON(&req) if req.AppId == 0 { writeErr(c, pb.ErrorCode_ReqParameterError, "app_id 必填") return } db, err := this.module.registry.getServiceDB(req.AppId) if err != nil { writeErr(c, pb.ErrorCode_DBError, "连接应用业务库失败: "+err.Error()) return } if err := comm.EnsureEchoSvcTable(db); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } rows, err := comm.LoadEchoSvcConfigs(db) if err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } for _, r := range rows { r.Values = comm.MaskEchoValues(r.Category, r.Provider, r.Values) } writeOK(c, gin.H{"items": rows}) } // echoSvcSave 保存单条服务配置:新增/更新(按 id)。 // 是否密钥以目录为准(不信前端);密钥字段回传占位/空则保留原密文,否则加密新明文;目录外的键忽略。 func (this *serverComp) echoSvcSave(c *gin.Context) { var req struct { AppId uint32 `json:"app_id"` Config comm.EchoSvcConfig `json:"config"` } if err := c.ShouldBindJSON(&req); err != nil { writeErr(c, pb.ErrorCode_ReqParameterError, err.Error()) return } cfg := req.Config cfg.Id = strings.TrimSpace(cfg.Id) cfg.Provider = strings.TrimSpace(cfg.Provider) if req.AppId == 0 || cfg.Id == "" { writeErr(c, pb.ErrorCode_ReqParameterError, "app_id 与 id 必填") return } def := comm.FindEchoSvcDef(cfg.Category, cfg.Provider) if def == nil { writeErr(c, pb.ErrorCode_ReqParameterError, "未知的服务类别/服务商组合") return } db, err := this.module.registry.getServiceDB(req.AppId) if err != nil { writeErr(c, pb.ErrorCode_DBError, "连接应用业务库失败: "+err.Error()) return } if err := comm.EnsureEchoSvcTable(db); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } // 取旧行以保留密钥密文。 old := &comm.EchoSvcConfig{} hasOld := db.FindOne(comm.TableEchomeetSvcConfig, old, "id=?", cfg.Id) == nil && old.Id != "" // 合法字段过滤 + 密钥处理。 allowed := make(map[string]bool, len(def.Fields)) for _, f := range def.Fields { allowed[f.Key] = true } encKey := this.options.EncryptKey stored := make(map[string]string, len(cfg.Values)) for k, v := range cfg.Values { if !allowed[k] { continue } if comm.EchoSvcFieldIsSecret(cfg.Category, cfg.Provider, k) { if v == "" || v == comm.EncMask { if hasOld { stored[k] = old.Values[k] // 保留原密文 } continue } ct, ee := comm.Encrypt(encKey, v) if ee != nil { writeErr(c, pb.ErrorCode_SystemError, "加密失败: "+ee.Error()) return } stored[k] = ct continue } stored[k] = v } cfg.Values = stored now := time.Now().Unix() cfg.Updatetime = now if hasOld { cfg.Createtime = old.Createtime if e := db.Save(comm.TableEchomeetSvcConfig, &cfg); e != nil { writeErr(c, pb.ErrorCode_DBError, e.Error()) return } } else { cfg.Createtime = now if e := db.Insert(comm.TableEchomeetSvcConfig, &cfg); e != nil { writeErr(c, pb.ErrorCode_DBError, e.Error()) return } } broadcastConfigChanged(comm.ConfigKindEchomeetSvc, "update", 0) writeOK(c, gin.H{"id": cfg.Id}) } // echoSvcDel 删除一条服务配置。 func (this *serverComp) echoSvcDel(c *gin.Context) { var req struct { AppId uint32 `json:"app_id"` Id string `json:"id"` } _ = c.ShouldBindJSON(&req) req.Id = strings.TrimSpace(req.Id) if req.AppId == 0 || req.Id == "" { writeErr(c, pb.ErrorCode_ReqParameterError, "app_id 与 id 必填") return } db, err := this.module.registry.getServiceDB(req.AppId) if err != nil { writeErr(c, pb.ErrorCode_DBError, "连接应用业务库失败: "+err.Error()) return } if err := db.Delete(comm.TableEchomeetSvcConfig, "id=?", req.Id); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } broadcastConfigChanged(comm.ConfigKindEchomeetSvc, "delete", 0) writeOK(c, gin.H{"deleted": req.Id}) } // echoSvcReorder 批量调整同类内优先级(拖拽排序后提交)。 func (this *serverComp) echoSvcReorder(c *gin.Context) { var req struct { AppId uint32 `json:"app_id"` Items []struct { Id string `json:"id"` Priority int32 `json:"priority"` } `json:"items"` } if err := c.ShouldBindJSON(&req); err != nil { writeErr(c, pb.ErrorCode_ReqParameterError, err.Error()) return } if req.AppId == 0 { writeErr(c, pb.ErrorCode_ReqParameterError, "app_id 必填") return } db, err := this.module.registry.getServiceDB(req.AppId) if err != nil { writeErr(c, pb.ErrorCode_DBError, "连接应用业务库失败: "+err.Error()) return } now := time.Now().Unix() updated := 0 for _, it := range req.Items { id := strings.TrimSpace(it.Id) if id == "" { continue } row := &comm.EchoSvcConfig{} if e := db.FindOne(comm.TableEchomeetSvcConfig, row, "id=?", id); e != nil || row.Id == "" { continue } row.Priority = it.Priority row.Updatetime = now if e := db.Save(comm.TableEchomeetSvcConfig, row); e != nil { writeErr(c, pb.ErrorCode_DBError, e.Error()) return } updated++ } broadcastConfigChanged(comm.ConfigKindEchomeetSvc, "update", 0) writeOK(c, gin.H{"updated": updated}) }