From b3b198126408e977f5505c3f4b50480efb4e2581 Mon Sep 17 00:00:00 2001 From: GeekMaster Date: Tue, 26 Aug 2025 15:33:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=85=8D=E7=BD=AE=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/core/config.go | 20 +- api/core/types/config.go | 6 +- api/core/types/geekai.go | 5 - api/core/types/payment.go | 2 +- api/handler/admin/config_handler.go | 407 ++++++++++++++---- api/handler/captcha_handler.go | 2 +- api/main.go | 5 +- api/service/captcha_service.go | 4 + api/service/config_service.go | 146 ------- api/service/license_service.go | 6 +- api/service/oss/qiniu_oss.go | 18 +- api/service/oss/uploader_manager.go | 4 +- api/service/smtp_sms_service.go | 4 + .../views/admin/settings/AgreementConfig.vue | 5 +- web/src/views/admin/settings/ApiConfig.vue | 10 +- web/src/views/admin/settings/BasicConfig.vue | 6 +- .../admin/settings/CommunicationConfig.vue | 48 ++- .../views/admin/settings/LicenseConfig.vue | 4 +- web/src/views/admin/settings/NoticeConfig.vue | 5 +- .../views/admin/settings/PaymentConfig.vue | 82 ++-- .../views/admin/settings/PrivacyConfig.vue | 5 +- .../views/admin/settings/StorageConfig.vue | 104 ++--- 22 files changed, 510 insertions(+), 388 deletions(-) delete mode 100644 api/service/config_service.go diff --git a/api/core/config.go b/api/core/config.go index 70f8727f..4d4699f4 100644 --- a/api/core/config.go +++ b/api/core/config.go @@ -94,15 +94,24 @@ func LoadSystemConfig(db *gorm.DB) *types.SystemConfig { logger.Error("load license config error: ", err) } - // 加载 GeekAPI 配置 - var geekAPIConfig types.GeekAPIConfig + // 加载验证码配置 + var captchaConfig types.CaptchaConfig sysConfig.Id = 0 - db.Where("name", types.ConfigKeyGeekAPI).First(&sysConfig) - err = utils.JsonDecode(sysConfig.Value, &geekAPIConfig) + db.Where("name", types.ConfigKeyCaptcha).First(&sysConfig) + err = utils.JsonDecode(sysConfig.Value, &captchaConfig) if err != nil { logger.Error("load geek service config error: ", err) } + // 加载微信登录配置 + var wxLoginConfig types.WxLoginConfig + sysConfig.Id = 0 + db.Where("name", types.ConfigKeyWxLogin).First(&sysConfig) + err = utils.JsonDecode(sysConfig.Value, &wxLoginConfig) + if err != nil { + logger.Error("load wx login config error: ", err) + } + // 加载短信配置 var smsConfig types.SMSConfig sysConfig.Id = 0 @@ -146,6 +155,7 @@ func LoadSystemConfig(db *gorm.DB) *types.SystemConfig { OSS: ossConfig, SMTP: smtpConfig, Payment: paymentConfig, - GeekAPI: geekAPIConfig, + Captcha: captchaConfig, + WxLogin: wxLoginConfig, } } diff --git a/api/core/types/config.go b/api/core/types/config.go index 7ea21ac4..01e0cbac 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -110,7 +110,8 @@ type SystemConfig struct { OSS OSSConfig SMS SMSConfig SMTP SmtpConfig - GeekAPI GeekAPIConfig + Captcha CaptchaConfig + WxLogin WxLoginConfig Jimeng JimengConfig License License } @@ -121,7 +122,8 @@ const ( ConfigKeyNotice = "notice" ConfigKeyAgreement = "agreement" ConfigKeyPrivacy = "privacy" - ConfigKeyGeekAPI = "geekapi" + ConfigKeyCaptcha = "captcha" + ConfigKeyWxLogin = "wx_login" ConfigKeyLicense = "license" ConfigKeySms = "sms" ConfigKeySmtp = "smtp" diff --git a/api/core/types/geekai.go b/api/core/types/geekai.go index c9db2b4c..1e525fe6 100644 --- a/api/core/types/geekai.go +++ b/api/core/types/geekai.go @@ -31,8 +31,3 @@ type WxLoginConfig struct { NotifyURL string `json:"notify_url"` // 登录成功回调 URL Enabled bool `json:"enabled"` // 是否启用微信登录 } - -type GeekAPIConfig struct { - Captcha CaptchaConfig - WxLogin WxLoginConfig -} diff --git a/api/core/types/payment.go b/api/core/types/payment.go index e60d71b5..83011356 100644 --- a/api/core/types/payment.go +++ b/api/core/types/payment.go @@ -2,7 +2,7 @@ package types type PaymentConfig struct { Alipay AlipayConfig `json:"alipay"` // 支付宝支付渠道配置 - Epay EpayConfig `json:"epay"` // GEEK 支付配置 + Epay EpayConfig `json:"epay"` // 易支付配置 WxPay WxPayConfig `json:"wxpay"` // 微信支付渠道配置 } diff --git a/api/handler/admin/config_handler.go b/api/handler/admin/config_handler.go index 2bbecf08..4739e6e4 100644 --- a/api/handler/admin/config_handler.go +++ b/api/handler/admin/config_handler.go @@ -8,109 +8,379 @@ package admin // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import ( - "encoding/json" - "errors" "geekai/core" "geekai/core/middleware" "geekai/core/types" "geekai/handler" "geekai/service" - "geekai/store" + "geekai/service/oss" + "geekai/service/payment" + "geekai/service/sms" "geekai/store/model" "geekai/utils" "geekai/utils/resp" "github.com/gin-gonic/gin" - "github.com/shirou/gopsutil/host" "gorm.io/gorm" ) type ConfigHandler struct { handler.BaseHandler - levelDB *store.LevelDB licenseService *service.LicenseService - configService *service.ConfigService + sysConfig *types.SystemConfig + alipayService *payment.AlipayService + wxpayService *payment.WxPayService + epayService *payment.EPayService + smsAliyun *sms.AliYunSmsService + smsBao *sms.BaoSmsService + smsManager *sms.SmsManager + localOss *oss.LocalStorage + qiniuOss *oss.QiNiuOss + aliyunOss *oss.AliYunOss + minioOss *oss.MiniOss + smtpService *service.SmtpService + captchaService *service.CaptchaService + wxLoginService *service.WxLoginService } -func NewConfigHandler(app *core.AppServer, db *gorm.DB, levelDB *store.LevelDB, licenseService *service.LicenseService, configService *service.ConfigService) *ConfigHandler { +func NewConfigHandler( + app *core.AppServer, + db *gorm.DB, + licenseService *service.LicenseService, + sysConfig *types.SystemConfig, + alipayService *payment.AlipayService, + wxpayService *payment.WxPayService, + epayService *payment.EPayService, + smsAliyun *sms.AliYunSmsService, + smsBao *sms.BaoSmsService, + smsManager *sms.SmsManager, + localOss *oss.LocalStorage, + qiniuOss *oss.QiNiuOss, + aliyunOss *oss.AliYunOss, + minioOss *oss.MiniOss, + smtpService *service.SmtpService, + captchaService *service.CaptchaService, + wxLoginService *service.WxLoginService, +) *ConfigHandler { return &ConfigHandler{ BaseHandler: handler.BaseHandler{App: app, DB: db}, - levelDB: levelDB, licenseService: licenseService, - configService: configService, + sysConfig: sysConfig, + alipayService: alipayService, + wxpayService: wxpayService, + epayService: epayService, + smsAliyun: smsAliyun, + smsBao: smsBao, + smsManager: smsManager, + localOss: localOss, + qiniuOss: qiniuOss, + aliyunOss: aliyunOss, + minioOss: minioOss, + smtpService: smtpService, + captchaService: captchaService, + wxLoginService: wxLoginService, } } // RegisterRoutes 注册路由 func (h *ConfigHandler) RegisterRoutes() { - group := h.App.Engine.Group("/api/admin/config/") - group.GET("get", h.Get) + rg := h.App.Engine.Group("/api/admin/config") - // 需要管理员授权的接口 - group.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis)) + // 需要管理员登录的接口 + rg.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis)) { - group.POST("update", h.Update) - group.POST("active", h.Active) - group.POST("test", h.Test) - group.GET("license", h.GetLicense) + rg.POST("update/base", h.UpdateBase) + rg.POST("update/notice", h.UpdateNotice) + rg.POST("update/captcha", h.UpdateCaptcha) + rg.POST("update/wx_login", h.UpdateWxLogin) + rg.POST("update/payment", h.UpdatePayment) + rg.POST("update/sms", h.UpdateSms) + rg.POST("update/oss", h.UpdateOss) + rg.POST("update/smtp", h.UpdateStmp) + rg.GET("get", h.Get) + rg.POST("license/active", h.Active) + rg.GET("license/get", h.GetLicense) } } -func (h *ConfigHandler) Update(c *gin.Context) { - var payload struct { - Key string `json:"key"` - Config json.RawMessage `json:"config"` - ConfigBak types.SystemConfig `json:"config_bak,omitempty"` - } +// UpdateBase 更新基础配置 +func (h *ConfigHandler) UpdateBase(c *gin.Context) { + var data types.BaseConfig - if err := c.ShouldBindJSON(&payload); err != nil { - logger.Errorf("Update config failed: %v", err) + if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) return } - if payload.Key == "system" { - var sys types.SystemConfig - if err := json.Unmarshal(payload.Config, &sys); err != nil { - resp.ERROR(c, "系统配置解析失败: "+err.Error()) - return - } - if (sys.Base.Copyright != payload.ConfigBak.Base.Copyright) && !h.licenseService.GetLicense().Configs.DeCopy { - resp.ERROR(c, "您无权修改版权信息,请先联系作者获取授权") - return - } - + // 未授权的话不允许修改版权 + license := h.licenseService.GetLicense() + if !license.IsActive && data.Copyright != h.sysConfig.Base.Copyright { + resp.ERROR(c, "未授权系统不允许修改版权信息") + return } - // 使用统一配置服务写入与广播 - if err := h.configService.Set(payload.Key, payload.Config); err != nil { + // 未授权的话不允许修改 Logo + if !license.IsActive && data.Logo != h.sysConfig.Base.Logo { + resp.ERROR(c, "未授权系统不允许修改 Logo") + return + } + + err := h.Update(types.ConfigKeySystem, data) + if err != nil { resp.ERROR(c, err.Error()) return } - if payload.Key == "system" { - var sys types.SystemConfig - if err := json.Unmarshal(payload.Config, &sys); err == nil { - h.App.SysConfig = &sys - } - } - resp.SUCCESS(c) + + h.sysConfig.Base = data + + resp.SUCCESS(c, data) } -// Get 获取指定的系统配置 -func (h *ConfigHandler) Get(c *gin.Context) { - key := c.Query("key") +// UpdateNotice 更新公告配置 +func (h *ConfigHandler) UpdateNotice(c *gin.Context) { + var data struct { + Content string `json:"content"` + } + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + err := h.Update(types.ConfigKeyNotice, data) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + resp.SUCCESS(c, data) +} + +// UpdateCaptcha 更新行为验证码配置 +func (h *ConfigHandler) UpdateCaptcha(c *gin.Context) { + var data types.CaptchaConfig + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + err := h.Update(types.ConfigKeyCaptcha, data) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + if data.Enabled { + h.captchaService.UpdateConfig(data) + } + h.sysConfig.Captcha = data + resp.SUCCESS(c, data) + +} + +// UpdatePayment 更新支付配置 +func (h *ConfigHandler) UpdatePayment(c *gin.Context) { + var data types.PaymentConfig + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + var config model.Config - res := h.DB.Where("name", key).First(&config) - if res.Error != nil { - if errors.Is(res.Error, gorm.ErrRecordNotFound) { - resp.SUCCESS(c, map[string]interface{}{}) + oldData := types.PaymentConfig{} + err := h.DB.Where("name", types.ConfigKeyPayment).First(&config).Error + if err == nil { + utils.JsonDecode(config.Value, &oldData) + } + + err = h.Update(types.ConfigKeyPayment, data) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + // 如果启用状态发生改变,则需要更新支付服务配置 + if data.WxPay.Enabled { + err = h.wxpayService.UpdateConfig(&data.WxPay) + if err != nil { + resp.ERROR(c, err.Error()) return } + } + if data.Epay.Enabled { + h.epayService.UpdateConfig(&data.Epay) + } + if data.Alipay.Enabled { + err = h.alipayService.UpdateConfig(&data.Alipay) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + } + + h.sysConfig.Payment = data + resp.SUCCESS(c, data) +} + +// UpdateSms 更新短信配置 +func (h *ConfigHandler) UpdateSms(c *gin.Context) { + var data types.SMSConfig + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + var config model.Config + oldData := types.SMSConfig{} + err := h.DB.Where("name", types.ConfigKeySms).First(&config).Error + if err == nil { + utils.JsonDecode(config.Value, &oldData) + } + + err = h.Update(types.ConfigKeySms, data) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + // 更新服务配置 + switch data.Active { + case sms.Ali: + err = h.smsAliyun.UpdateConfig(&data.Ali) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + case sms.Bao: + h.smsBao.UpdateConfig(&data.Bao) + } + + h.smsManager.SetActive(data.Active) + + resp.SUCCESS(c, data) +} + +// UpdateOss 更新 Oss 配置 +func (h *ConfigHandler) UpdateOss(c *gin.Context) { + var data types.OSSConfig + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + var config model.Config + oldData := types.OSSConfig{} + err := h.DB.Where("name", types.ConfigKeyOss).First(&config).Error + if err == nil { + utils.JsonDecode(config.Value, &oldData) + } + + err = h.Update("oss", data) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + // 更新服务配置 + switch data.Active { + case oss.Local: + h.localOss.UpdateConfig(&data.Local) + case oss.QiNiu: + h.qiniuOss.UpdateConfig(&data.QiNiu) + case oss.AliYun: + err := h.aliyunOss.UpdateConfig(&data.AliYun) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + case oss.Minio: + err := h.minioOss.UpdateConfig(&data.Minio) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + } + + h.sysConfig.OSS = data + + resp.SUCCESS(c, data) +} + +// UpdateStmp 更新 Stmp 配置 +func (h *ConfigHandler) UpdateStmp(c *gin.Context) { + var data types.SmtpConfig + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + var config model.Config + oldData := types.SmtpConfig{} + err := h.DB.Where("name", types.ConfigKeySmtp).First(&config).Error + if err == nil { + utils.JsonDecode(config.Value, &oldData) + } + + err = h.Update(types.ConfigKeySmtp, data) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + // 配置发生改变时更新服务配置 + if !data.Equal(&oldData) { + h.smtpService.UpdateConfig(&data) + } + + h.sysConfig.SMTP = data + resp.SUCCESS(c, data) +} + +// UpdateWxLogin 更新微信登录配置 +func (h *ConfigHandler) UpdateWxLogin(c *gin.Context) { + var data types.WxLoginConfig + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + err := h.Update(types.ConfigKeyWxLogin, data) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + if data.Enabled { + h.wxLoginService.UpdateConfig(data) + } + + h.sysConfig.WxLogin = data + resp.SUCCESS(c, data) +} + +// Update 更新系统配置 +func (h *ConfigHandler) Update(name string, value any) error { + var config model.Config + err := h.DB.Where("name", name).First(&config).Error + if err != nil { // 不存在则创建 + config.Name = name + config.Value = utils.JsonEncode(value) + return h.DB.Create(&config).Error + } else { // 存在则更新 + config.Value = utils.JsonEncode(value) + return h.DB.Updates(&config).Error + } + +} + +// Get 获取指定名称的系统配置 +func (h *ConfigHandler) Get(c *gin.Context) { + name := c.Query("key") + var config model.Config + res := h.DB.Where("name", name).First(&config) + if res.Error != nil { resp.ERROR(c, res.Error.Error()) return } - var value map[string]interface{} + var value map[string]any err := utils.JsonDecode(config.Value, &value) if err != nil { resp.ERROR(c, err.Error()) @@ -120,23 +390,6 @@ func (h *ConfigHandler) Get(c *gin.Context) { resp.SUCCESS(c, value) } -// Test 配置测试(占位) -func (h *ConfigHandler) Test(c *gin.Context) { - var data struct { - Key string `json:"key"` - } - if err := c.ShouldBindJSON(&data); err != nil { - resp.ERROR(c, types.InvalidArgs) - return - } - msg, err := h.configService.Test(data.Key) - if err != nil { - resp.ERROR(c, err.Error()) - return - } - resp.SUCCESS(c, msg) -} - // Active 激活系统 func (h *ConfigHandler) Active(c *gin.Context) { var data struct { @@ -146,19 +399,21 @@ func (h *ConfigHandler) Active(c *gin.Context) { resp.ERROR(c, types.InvalidArgs) return } - info, err := host.Info() + + err := h.licenseService.ActiveLicense(data.License) + license := h.licenseService.GetLicense() if err != nil { resp.ERROR(c, err.Error()) return } - - err = h.licenseService.ActiveLicense(data.License, info.HostID) - if err != nil { + if err := h.Update(types.ConfigKeyLicense, license); err != nil { resp.ERROR(c, err.Error()) return } + // 更新系统配置 + h.sysConfig.License = *license - resp.SUCCESS(c) + resp.SUCCESS(c, license.MachineId) } @@ -167,3 +422,5 @@ func (h *ConfigHandler) GetLicense(c *gin.Context) { license := h.licenseService.GetLicense() resp.SUCCESS(c, license) } + +// diff --git a/api/handler/captcha_handler.go b/api/handler/captcha_handler.go index e5284e3a..be4952d4 100644 --- a/api/handler/captcha_handler.go +++ b/api/handler/captcha_handler.go @@ -25,7 +25,7 @@ type CaptchaHandler struct { } func NewCaptchaHandler(app *core.AppServer, s *service.CaptchaService, sysConfig *types.SystemConfig) *CaptchaHandler { - return &CaptchaHandler{App: app, service: s, config: sysConfig.GeekAPI.Captcha} + return &CaptchaHandler{App: app, service: s, config: sysConfig.Captcha} } // RegisterRoutes 注册路由 diff --git a/api/main.go b/api/main.go index 24ddb04a..71c2221e 100644 --- a/api/main.go +++ b/api/main.go @@ -144,7 +144,6 @@ func main() { fx.Provide(handler.NewPowerLogHandler), fx.Provide(handler.NewJimengHandler), - fx.Provide(service.NewConfigService), fx.Provide(service.NewMigrationService), fx.Invoke(func(migrationService *service.MigrationService) { migrationService.StartMigrate() @@ -221,10 +220,10 @@ func main() { fx.Provide(sms.NewBaoSmsService), fx.Provide(sms.NewSmsManager), fx.Provide(func(config *types.SystemConfig) *service.CaptchaService { - return service.NewCaptchaService(config.GeekAPI.Captcha) + return service.NewCaptchaService(config.Captcha) }), fx.Provide(func(config *types.SystemConfig, client *redis.Client) *service.WxLoginService { - return service.NewWxLoginService(config.GeekAPI.WxLogin, client) + return service.NewWxLoginService(config.WxLogin, client) }), // 支付服务 diff --git a/api/service/captcha_service.go b/api/service/captcha_service.go index b2e76d45..0cc5e503 100644 --- a/api/service/captcha_service.go +++ b/api/service/captcha_service.go @@ -27,6 +27,10 @@ func NewCaptchaService(captchaConfig types.CaptchaConfig) *CaptchaService { } } +func (s *CaptchaService) UpdateConfig(config types.CaptchaConfig) { + s.config = config +} + func (s *CaptchaService) Get() (interface{}, error) { url := fmt.Sprintf("%s/api/captcha/get", types.GeekAPIURL) var res types.BizVo diff --git a/api/service/config_service.go b/api/service/config_service.go deleted file mode 100644 index 88c076e3..00000000 --- a/api/service/config_service.go +++ /dev/null @@ -1,146 +0,0 @@ -package service - -// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -// Copyright 2023 The Geek-AI Authors. All rights reserved. -// Use of this source code is governed by a Apache-2.0 license -// that can be found in the LICENSE file. -// @Author yangjian102621@163.com -// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -import ( - "context" - "encoding/json" - "fmt" - "geekai/store/model" - "sync" - - "github.com/go-redis/redis/v8" - "gorm.io/gorm" -) - -// ConfigService 统一的配置访问、缓存与通知服务 -type ConfigService struct { - db *gorm.DB - rdb *redis.Client - mu sync.RWMutex - cache map[string]json.RawMessage - watchers map[string][]chan struct{} -} - -func NewConfigService(db *gorm.DB, rdb *redis.Client) *ConfigService { - s := &ConfigService{ - db: db, - rdb: rdb, - cache: make(map[string]json.RawMessage), - watchers: make(map[string][]chan struct{}), - } - go s.subscribe() - return s -} - -// Get 以原始 JSON 获取配置(带本地缓存) -func (s *ConfigService) Get(key string) (json.RawMessage, error) { - s.mu.RLock() - if v, ok := s.cache[key]; ok { - s.mu.RUnlock() - return v, nil - } - s.mu.RUnlock() - - var cfg model.Config - if err := s.db.Where("name", key).First(&cfg).Error; err != nil { - return nil, err - } - s.mu.Lock() - s.cache[key] = json.RawMessage(cfg.Value) - s.mu.Unlock() - return json.RawMessage(cfg.Value), nil -} - -// GetInto 将配置解析进传入结构体 -func (s *ConfigService) GetInto(key string, dest interface{}) error { - data, err := s.Get(key) - if err != nil { - return err - } - if len(data) == 0 { - return nil - } - return json.Unmarshal(data, dest) -} - -// Set 设置配置并写入数据库,同时触发通知 -func (s *ConfigService) Set(key string, config json.RawMessage) error { - value := string(config) - cfg := model.Config{Name: key, Value: value} - if err := s.db.Where("name", key).FirstOrCreate(&cfg, model.Config{Name: key}).Error; err != nil { - return err - } - if cfg.Id > 0 { - cfg.Value = value - if err := s.db.Updates(&cfg).Error; err != nil { - return err - } - } - s.mu.Lock() - s.cache[key] = json.RawMessage(value) - s.mu.Unlock() - s.notifyLocal(key) - s.publish(key) - return nil -} - -// Watch 返回一个通道,当指定 key 发生变化时收到事件 -func (s *ConfigService) Watch(key string) <-chan struct{} { - ch := make(chan struct{}, 1) - s.mu.Lock() - s.watchers[key] = append(s.watchers[key], ch) - s.mu.Unlock() - return ch -} - -func (s *ConfigService) notifyLocal(key string) { - s.mu.RLock() - list := s.watchers[key] - s.mu.RUnlock() - for _, ch := range list { - select { // 非阻塞通知 - case ch <- struct{}{}: - default: - } - } -} - -// 通过 Redis 发布配置变更,便于多实例同步 -func (s *ConfigService) publish(key string) { - if s.rdb == nil { - return - } - channel := "config:changed" - if err := s.rdb.Publish(context.Background(), channel, key).Err(); err != nil { - logger.Warnf("publish config change failed: %v", err) - } -} - -func (s *ConfigService) subscribe() { - if s.rdb == nil { - return - } - channel := "config:changed" - sub := s.rdb.Subscribe(context.Background(), channel) - for msg := range sub.Channel() { - key := msg.Payload - logger.Infof("config changed: %s", key) - // 失效本地缓存并本地广播 - s.mu.Lock() - delete(s.cache, key) - s.mu.Unlock() - s.notifyLocal(key) - } -} - -// Test 预留统一测试入口,根据 key 执行连通性检查 -func (s *ConfigService) Test(key string) (string, error) { - // TODO: 实现各配置类型的测试逻辑 - return fmt.Sprintf("%s ok", key), nil -} diff --git a/api/service/license_service.go b/api/service/license_service.go index 0b6a949a..96b1b6a0 100644 --- a/api/service/license_service.go +++ b/api/service/license_service.go @@ -53,7 +53,7 @@ type License struct { } // ActiveLicense 激活 License -func (s *LicenseService) ActiveLicense(license string, machineId string) error { +func (s *LicenseService) ActiveLicense(license string) error { var res struct { Code types.BizCode `json:"code"` Message string `json:"message"` @@ -61,7 +61,7 @@ func (s *LicenseService) ActiveLicense(license string, machineId string) error { } apiURL := fmt.Sprintf("%s/%s", types.GeekAPIURL, "api/license/active") response, err := req.C().R(). - SetBody(map[string]string{"license": license, "machine_id": machineId}). + SetBody(map[string]string{"license": license, "machine_id": s.machineId}). SetSuccessResult(&res).Post(apiURL) if err != nil { return fmt.Errorf("发送激活请求失败: %v", err) @@ -81,7 +81,7 @@ func (s *LicenseService) ActiveLicense(license string, machineId string) error { s.license = &types.License{ Key: license, - MachineId: machineId, + MachineId: s.machineId, Configs: res.Data.Configs, ExpiredAt: res.Data.ExpiredAt, IsActive: true, diff --git a/api/service/oss/qiniu_oss.go b/api/service/oss/qiniu_oss.go index aec18918..d99752a2 100644 --- a/api/service/oss/qiniu_oss.go +++ b/api/service/oss/qiniu_oss.go @@ -24,7 +24,7 @@ import ( "github.com/qiniu/go-sdk/v7/storage" ) -type QinNiuOss struct { +type QiNiuOss struct { config *types.QiNiuOssConfig mac *qbox.Mac putPolicy storage.PutPolicy @@ -33,8 +33,8 @@ type QinNiuOss struct { proxyURL string } -func NewQiNiuOss(sysConfig *types.SystemConfig, appConfig *types.AppConfig) *QinNiuOss { - s := &QinNiuOss{ +func NewQiNiuOss(sysConfig *types.SystemConfig, appConfig *types.AppConfig) *QiNiuOss { + s := &QiNiuOss{ proxyURL: appConfig.ProxyURL, } if sysConfig.OSS.Active == QiNiu { @@ -43,7 +43,7 @@ func NewQiNiuOss(sysConfig *types.SystemConfig, appConfig *types.AppConfig) *Qin return s } -func (s *QinNiuOss) UpdateConfig(config *types.QiNiuOssConfig) { +func (s *QiNiuOss) UpdateConfig(config *types.QiNiuOssConfig) { zone, ok := storage.GetRegionByID(storage.RegionID(config.Zone)) if !ok { zone = storage.ZoneHuanan @@ -61,7 +61,7 @@ func (s *QinNiuOss) UpdateConfig(config *types.QiNiuOssConfig) { s.uploader = formUploader s.bucket = storage.NewBucketManager(mac, &storeConfig) } -func (s QinNiuOss) PutFile(ctx *gin.Context, name string) (File, error) { +func (s QiNiuOss) PutFile(ctx *gin.Context, name string) (File, error) { // 解析表单 file, err := ctx.FormFile(name) if err != nil { @@ -94,7 +94,7 @@ func (s QinNiuOss) PutFile(ctx *gin.Context, name string) (File, error) { } -func (s QinNiuOss) PutUrlFile(fileURL string, ext string, useProxy bool) (string, error) { +func (s QiNiuOss) PutUrlFile(fileURL string, ext string, useProxy bool) (string, error) { var fileData []byte var err error if useProxy { @@ -123,7 +123,7 @@ func (s QinNiuOss) PutUrlFile(fileURL string, ext string, useProxy bool) (string return fmt.Sprintf("%s/%s", s.config.Domain, ret.Key), nil } -func (s QinNiuOss) PutBase64(base64Img string) (string, error) { +func (s QiNiuOss) PutBase64(base64Img string) (string, error) { imageData, err := base64.StdEncoding.DecodeString(base64Img) if err != nil { return "", fmt.Errorf("error decoding base64:%v", err) @@ -139,7 +139,7 @@ func (s QinNiuOss) PutBase64(base64Img string) (string, error) { return fmt.Sprintf("%s/%s", s.config.Domain, ret.Key), nil } -func (s QinNiuOss) Delete(fileURL string) error { +func (s QiNiuOss) Delete(fileURL string) error { var objectKey string if strings.HasPrefix(fileURL, "http") { filename := filepath.Base(fileURL) @@ -151,4 +151,4 @@ func (s QinNiuOss) Delete(fileURL string) error { return s.bucket.Delete(s.config.Bucket, objectKey) } -var _ Uploader = QinNiuOss{} +var _ Uploader = QiNiuOss{} diff --git a/api/service/oss/uploader_manager.go b/api/service/oss/uploader_manager.go index be011aff..64c05b85 100644 --- a/api/service/oss/uploader_manager.go +++ b/api/service/oss/uploader_manager.go @@ -20,11 +20,11 @@ type UploaderManager struct { local *LocalStorage aliyun *AliYunOss mini *MiniOss - qiniu *QinNiuOss + qiniu *QiNiuOss config *types.OSSConfig } -func NewUploaderManager(sysConfig *types.SystemConfig, local *LocalStorage, aliyun *AliYunOss, mini *MiniOss, qiniu *QinNiuOss) (*UploaderManager, error) { +func NewUploaderManager(sysConfig *types.SystemConfig, local *LocalStorage, aliyun *AliYunOss, mini *MiniOss, qiniu *QiNiuOss) (*UploaderManager, error) { if sysConfig.OSS.Active == "" { sysConfig.OSS.Active = Local } diff --git a/api/service/smtp_sms_service.go b/api/service/smtp_sms_service.go index c6abf586..cb057d92 100644 --- a/api/service/smtp_sms_service.go +++ b/api/service/smtp_sms_service.go @@ -27,6 +27,10 @@ func NewSmtpService(appConfig *types.AppConfig) *SmtpService { } } +func (s *SmtpService) UpdateConfig(config *types.SmtpConfig) { + s.config = config +} + func (s *SmtpService) SendVerifyCode(to string, code int) error { subject := fmt.Sprintf("%s 注册验证码", s.config.AppName) body := fmt.Sprintf("【%s】:您的验证码为 %d,请不要告诉他人。如非本人操作,请忽略此邮件。", s.config.AppName, code) diff --git a/web/src/views/admin/settings/AgreementConfig.vue b/web/src/views/admin/settings/AgreementConfig.vue index aee1be16..687aaffe 100644 --- a/web/src/views/admin/settings/AgreementConfig.vue +++ b/web/src/views/admin/settings/AgreementConfig.vue @@ -43,10 +43,7 @@ onMounted(() => { }) const save = () => { - httpPost('/api/admin/config/update', { - key: 'agreement', - config: { content: agreement.value, updated: true }, - }) + httpPost('/api/admin/config/update/base', { mark_map_text: agreement.value }) .then(() => { ElMessage.success('操作成功!') }) diff --git a/web/src/views/admin/settings/ApiConfig.vue b/web/src/views/admin/settings/ApiConfig.vue index 272018cb..05a4d4cb 100644 --- a/web/src/views/admin/settings/ApiConfig.vue +++ b/web/src/views/admin/settings/ApiConfig.vue @@ -21,7 +21,7 @@ diff --git a/web/src/views/admin/settings/BasicConfig.vue b/web/src/views/admin/settings/BasicConfig.vue index d76fcdff..14191239 100644 --- a/web/src/views/admin/settings/BasicConfig.vue +++ b/web/src/views/admin/settings/BasicConfig.vue @@ -282,11 +282,7 @@ const rules = reactive({ const save = function () { systemFormRef.value.validate((valid) => { if (valid) { - httpPost('/api/admin/config/update', { - key: 'system', - config: system.value, - config_bak: configBak.value, - }) + httpPost('/api/admin/config/update/base', system.value) .then(() => { ElMessage.success('操作成功!') }) diff --git a/web/src/views/admin/settings/CommunicationConfig.vue b/web/src/views/admin/settings/CommunicationConfig.vue index d2fb0a9d..97929f74 100644 --- a/web/src/views/admin/settings/CommunicationConfig.vue +++ b/web/src/views/admin/settings/CommunicationConfig.vue @@ -21,28 +21,27 @@ - + -