package console import ( "strings" "time" "yunyan/comm" "yunyan/pb" "github.com/gin-gonic/gin" ) // ============================ 应用×服务 基础设施/运行配置 (svcruntime) ============================ // // 「应用的服务配置」页(admin appconfig.vue)的后端:读写 app_service_config 表。 // 数据由业务服务启动时按「库优先,无则读文件回写」seed(见 comm.LoadOrSeedServiceConfig), // console 这里提供后台查看/修改。密钥字段(DSN/密码/secret/token)返回前端脱敏为 comm.EncMask, // 保存时前端回传占位/空则保留原密文,否则用新明文加密覆盖。 // maskSvcRuntime 返回前端前脱敏加密项的值。 func maskSvcRuntime(m *comm.AppServiceConfig) { if m.Encrypted && m.Value != "" { m.Value = comm.EncMask } } // svcRuntimeList 列出某(应用,区域,服务)的配置项(密钥脱敏)。 func (this *serverComp) svcRuntimeList(c *gin.Context) { var req struct { AppName string `json:"app_name"` Region string `json:"region"` Service string `json:"service"` } _ = c.ShouldBindJSON(&req) if strings.TrimSpace(req.AppName) == "" || strings.TrimSpace(req.Service) == "" { writeErr(c, pb.ErrorCode_ReqParameterError, "app_name 与 service 必填") return } items, err := this.module.model.listSvcRuntime(req.AppName, req.Region, req.Service) if err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } for _, m := range items { maskSvcRuntime(m) } writeOK(c, gin.H{"items": items}) } // svcRuntimeServices 列出某(应用,区域)下已有配置的服务类型,供前端标记哪些服务已上报默认配置。 func (this *serverComp) svcRuntimeServices(c *gin.Context) { var req struct { AppName string `json:"app_name"` Region string `json:"region"` } _ = c.ShouldBindJSON(&req) if strings.TrimSpace(req.AppName) == "" { writeErr(c, pb.ErrorCode_ReqParameterError, "app_name 必填") return } services, err := this.module.model.listSvcRuntimeServices(req.AppName, req.Region) if err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } writeOK(c, gin.H{"services": services}) } // svcRuntimeSave 逐项 upsert(id>0 更新、否则新增),保存即标 source=db,末尾广播配置变更。 func (this *serverComp) svcRuntimeSave(c *gin.Context) { var req struct { AppName string `json:"app_name"` Region string `json:"region"` Service string `json:"service"` Items []*comm.AppServiceConfig `json:"items"` } if err := c.ShouldBindJSON(&req); err != nil { writeErr(c, pb.ErrorCode_ReqParameterError, err.Error()) return } if strings.TrimSpace(req.AppName) == "" || strings.TrimSpace(req.Service) == "" { writeErr(c, pb.ErrorCode_ReqParameterError, "app_name 与 service 必填") return } key := this.options.EncryptKey now := time.Now().Unix() for _, it := range req.Items { if strings.TrimSpace(it.Key) == "" || strings.TrimSpace(it.Section) == "" { writeErr(c, pb.ErrorCode_ReqParameterError, "配置项 section/key 必填") return } // 归属三键以外层为准,防止前端串改到别的应用/服务。 it.AppName, it.Region, it.Service = req.AppName, req.Region, req.Service it.Source = "db" it.Updatetime = now // 加密字段:占位/空 → 保留原密文;否则加密新明文。 if it.Encrypted { if it.Value == "" || it.Value == comm.EncMask { if it.Id != 0 { if old, e := this.module.model.getSvcRuntimeItem(it.Id); e == nil { it.Value = old.Value } } else { it.Value = "" } } else { ct, e := comm.Encrypt(key, it.Value) if e != nil { writeErr(c, pb.ErrorCode_SystemError, "加密失败: "+e.Error()) return } it.Value = ct } } if it.Id != 0 { old, e := this.module.model.getSvcRuntimeItem(it.Id) if e == nil { it.Createtime = old.Createtime } if e := this.module.model.saveSvcRuntimeItem(it); e != nil { writeErr(c, pb.ErrorCode_DBError, e.Error()) return } } else { it.Createtime = now if e := this.module.model.addSvcRuntimeItem(it); e != nil { writeErr(c, pb.ErrorCode_DBError, e.Error()) return } } } broadcastConfigChanged(comm.ConfigKindServiceConfig, "update", 0) writeOK(c, gin.H{"saved": len(req.Items)}) } // svcRuntimeDel 按 id 批量删除配置项。 func (this *serverComp) svcRuntimeDel(c *gin.Context) { var req struct { Ids []uint64 `json:"ids"` } _ = c.ShouldBindJSON(&req) if len(req.Ids) == 0 { writeErr(c, pb.ErrorCode_ReqParameterError, "ids 必填") return } if err := this.module.model.delSvcRuntimeItems(req.Ids); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } broadcastConfigChanged(comm.ConfigKindServiceConfig, "delete", 0) writeOK(c, gin.H{"deleted": len(req.Ids)}) } // appsBindEnv 轻量「绑定/切换部署环境」:只改某部署行的 env_id,不走完整表单+连通性复检。 // 用于给历史部署行(env_id=0)快速绑定环境,或在应用服务配置页切换环境。 func (this *serverComp) appsBindEnv(c *gin.Context) { var req struct { Id uint32 `json:"id"` EnvId uint32 `json:"env_id"` } if err := c.ShouldBindJSON(&req); err != nil || req.Id == 0 { writeErr(c, pb.ErrorCode_ReqParameterError, "id 必填") return } model, err := this.module.model.getApp(req.Id) if err != nil { writeErr(c, pb.ErrorCode_DBError, "部署不存在: "+err.Error()) return } // env_id>0 时校验目标环境存在(0=解绑,允许)。 if req.EnvId != 0 { if _, e := this.module.model.getEnv(req.EnvId); e != nil { writeErr(c, pb.ErrorCode_ReqParameterError, "目标环境不存在") return } } model.EnvId = req.EnvId model.Updatetime = time.Now().Unix() if err := this.module.model.saveApp(model); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } this.module.registry.invalidate(model.Id) // 配置变了,失效连接缓存 writeOK(c, model) }