package console import ( "fmt" "strings" "time" "yunyan/comm" "yunyan/lego/sys/postgres" "yunyan/pb" "yunyan/sys/email" "github.com/gin-gonic/gin" ) // ============================ 平台系统服务配置域 (sysconfig) ============================ // // 存 console 主库(postgres.GetSys())的 sys_service_config 表,平台自用(对象存储/邮件/翻译), // 与面向客户端下发的 svc_config 彻底分离。加密字段值 AES-256-GCM 落库、返回前端脱敏为 "[encrypted]"。 // // 类型别名,包内沿用简名。 type PlatformService = comm.PlatformService // getSysConfigs 列出全部平台服务配置(加密字段脱敏)。 func (this *serverComp) getSysConfigs(c *gin.Context) { items := make([]*PlatformService, 0) if err := postgres.Find(comm.TableSysServiceConfig, &items, ""); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } for _, it := range items { maskSvcFields(it.Fields) } writeOK(c, gin.H{"items": items}) } // saveSysConfig 新增/更新一条平台服务配置(按 id upsert)。加密字段留空/占位=保留原密文。 func (this *serverComp) saveSysConfig(c *gin.Context) { var m PlatformService if err := c.ShouldBindJSON(&m); err != nil { writeErr(c, pb.ErrorCode_ReqParameterError, err.Error()) return } if strings.TrimSpace(m.Id) == "" || strings.TrimSpace(m.Category) == "" { writeErr(c, pb.ErrorCode_ReqParameterError, "id 和 category 必填") return } now := time.Now().Unix() exist := &PlatformService{} isUpdate := postgres.FindOne(comm.TableSysServiceConfig, exist, "id=?", m.Id) == nil // 加密字段:更新时空值/占位保留旧密文;新增时直接加密非空值。 var oldMap map[string]string if isUpdate { oldMap = map[string]string{} for _, f := range exist.Fields { if f.Encrypted { oldMap[f.Key] = f.DefValue } } } if err := encryptFieldsSlice(this.options.EncryptKey, m.Fields, oldMap); err != nil { writeErr(c, pb.ErrorCode_SystemError, "字段加密失败: "+err.Error()) return } m.Updatetime = now if isUpdate { m.Createtime = exist.Createtime if err := postgres.Save(comm.TableSysServiceConfig, &m); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } } else { m.Createtime = now if err := postgres.Insert(comm.TableSysServiceConfig, &m); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } } // 若本条被设为默认,则清掉同类目其它行的默认标记(默认唯一)。 if m.IsDefault { if res := postgres.Exec( "UPDATE "+comm.TableSysServiceConfig+" SET is_default=false WHERE category=? AND id<>?", m.Category, m.Id); res.Error != nil { writeErr(c, pb.ErrorCode_DBError, res.Error.Error()) return } } writeOK(c, gin.H{"id": m.Id}) } // setSysDefault 把某类目下的指定服务设为默认(其余清默认),并顺带启用它。 func (this *serverComp) setSysDefault(c *gin.Context) { var req struct { Id string `json:"id"` Category string `json:"category"` } _ = c.ShouldBindJSON(&req) if req.Id == "" || req.Category == "" { writeErr(c, pb.ErrorCode_ReqParameterError, "id 和 category 必填") return } if res := postgres.Exec( "UPDATE "+comm.TableSysServiceConfig+" SET is_default=false WHERE category=?", req.Category); res.Error != nil { writeErr(c, pb.ErrorCode_DBError, res.Error.Error()) return } if res := postgres.Exec( "UPDATE "+comm.TableSysServiceConfig+" SET is_default=true, enable=true, updatetime=? WHERE id=?", time.Now().Unix(), req.Id); res.Error != nil { writeErr(c, pb.ErrorCode_DBError, res.Error.Error()) return } writeOK(c, gin.H{"id": req.Id}) } // delSysConfig 删除一条平台服务配置。 func (this *serverComp) delSysConfig(c *gin.Context) { var req struct { Id string `json:"id"` } _ = c.ShouldBindJSON(&req) if req.Id == "" { writeErr(c, pb.ErrorCode_ReqParameterError, "id 必填") return } if err := postgres.Delete(comm.TableSysServiceConfig, "id=?", req.Id); err != nil { writeErr(c, pb.ErrorCode_DBError, err.Error()) return } writeOK(c, gin.H{"id": req.Id}) } // loadSysStorageAliyun 读平台对象存储里启用的阿里云 OSS 配置,解密为可用凭据; // 无有效配置返回 nil(调用方据此回退旧的 svc_config 存储配置)。 func (this *serverComp) loadSysStorageAliyun() *aliyunOSSConf { rows := make([]*PlatformService, 0) if err := postgres.Find(comm.TableSysServiceConfig, &rows, "category=? AND provider=? AND enable=?", comm.SysCatStorage, "aliyun_oss", true); err != nil || len(rows) == 0 { return nil } // 有 is_default 优先取默认,否则取第一条启用的。 cfg := rows[0] for _, r := range rows { if r.IsDefault { cfg = r break } } key := this.options.EncryptKey oc := &aliyunOSSConf{ Endpoint: strings.TrimSpace(cfg.SysFieldPlain("endpoint", key)), AccessKeyId: strings.TrimSpace(cfg.SysFieldPlain("access_key_id", key)), AccessSecret: strings.TrimSpace(cfg.SysFieldPlain("access_key_secret", key)), Bucket: strings.TrimSpace(cfg.SysFieldPlain("bucket", key)), Domain: strings.TrimSpace(cfg.SysFieldPlain("domain", key)), } if oc.Endpoint == "" || oc.AccessKeyId == "" || oc.AccessSecret == "" || oc.Bucket == "" { return nil } if !strings.HasPrefix(oc.Endpoint, "http://") && !strings.HasPrefix(oc.Endpoint, "https://") { oc.Endpoint = "https://" + oc.Endpoint } return oc } // loadSysEmail 取平台邮件配置(启用的那条;无则 nil)。 func (this *serverComp) loadSysEmail() *PlatformService { rows := make([]*PlatformService, 0) if err := postgres.Find(comm.TableSysServiceConfig, &rows, "category=? AND enable=?", comm.SysCatEmail, true); err != nil || len(rows) == 0 { return nil } return rows[0] } // applySysEmail 用平台邮件配置热重载 email 系统(供发信前调用)。未配置/不合法返回 error。 func (this *serverComp) applySysEmail() error { cfg := this.loadSysEmail() if cfg == nil { return fmt.Errorf("未配置邮件服务:请在【系统配置 → 邮件服务】填写并启用") } key := this.options.EncryptKey conf := map[string]interface{}{ "EmailType": cfg.SysFieldInt("EmailType", 1), "FromEmail": cfg.SysFieldPlain("FromEmail", key), "FromName": cfg.SysFieldPlain("FromName", key), "Password": cfg.SysFieldPlain("Password", key), } return email.Reload(conf) } // testSysEmail 用当前邮件配置向指定邮箱发一封测试邮件。 func (this *serverComp) testSysEmail(c *gin.Context) { var req struct { To string `json:"to"` } _ = c.ShouldBindJSON(&req) if strings.TrimSpace(req.To) == "" { writeErr(c, pb.ErrorCode_ReqParameterError, "测试收件邮箱必填") return } if err := this.applySysEmail(); err != nil { writeErr(c, pb.ErrorCode_SystemError, err.Error()) return } if err := email.SendMail("【系统配置】邮件服务测试", "这是一封来自后台系统配置的测试邮件,收到即表示邮件服务配置正确。", req.To); err != nil { writeErr(c, pb.ErrorCode_SystemError, "发送失败: "+err.Error()) return } writeOK(c, gin.H{"ok": true}) }