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 @@
-
+
-
-
+
+
-
-
+
+
-
+
-
-
+
@@ -64,9 +63,9 @@ const loading = ref(true)
const active = ref('smtp')
const smtp = ref({ use_tls: false, host: '', port: 25, app_name: '', from: '', password: '' })
const sms = ref({
- active: 'Ali',
- ali: { access_key: '', access_secret: '', sign: '', code_temp_id: '' },
- bao: { username: '', password: '', domain: '', sign: '', code_template: '' },
+ Active: 'Ali',
+ Ali: { AccessKey: '', AccessSecret: '', Sign: '', CodeTempId: '' },
+ Bao: { Username: '', Password: '', Sign: '', CodeTemplate: '' },
})
onMounted(() => {
@@ -79,8 +78,8 @@ onMounted(() => {
sms.value = {
...sms.value,
...smsData,
- ali: { ...sms.value.ali, ...(smsData.ali || {}) },
- bao: { ...sms.value.bao, ...(smsData.bao || {}) },
+ Ali: { ...sms.value.Ali, ...(smsData.Ali || smsData.ali || {}) },
+ Bao: { ...sms.value.Bao, ...(smsData.Bao || smsData.bao || {}) },
}
})
.catch(() => {})
@@ -88,16 +87,19 @@ onMounted(() => {
})
const save = (key) => {
- const map = { smtp, sms }
- httpPost('/api/admin/config/update', { key, config: map[key].value })
- .then(() => ElMessage.success('保存成功'))
- .catch((e) => ElMessage.error(e.message))
+ if (key === 'smtp') {
+ httpPost('/api/admin/config/update/smtp', smtp.value)
+ .then(() => ElMessage.success('保存成功'))
+ .catch((e) => ElMessage.error(e.message))
+ } else if (key === 'sms') {
+ httpPost('/api/admin/config/update/sms', sms.value)
+ .then(() => ElMessage.success('保存成功'))
+ .catch((e) => ElMessage.error(e.message))
+ }
}
const test = (key) => {
- httpPost('/api/admin/config/test', { key })
- .then((res) => ElMessage.success(res.message || '测试成功'))
- .catch((e) => ElMessage.error(e.message || '测试失败'))
+ ElMessage.info('请在对应服务侧进行验证')
}
diff --git a/web/src/views/admin/settings/LicenseConfig.vue b/web/src/views/admin/settings/LicenseConfig.vue
index 4daf4fca..5ca11ba8 100644
--- a/web/src/views/admin/settings/LicenseConfig.vue
+++ b/web/src/views/admin/settings/LicenseConfig.vue
@@ -79,7 +79,7 @@ onMounted(() => {
})
const fetchLicense = () => {
- httpGet('/api/admin/config/license')
+ httpGet('/api/admin/config/license/get')
.then((res) => {
license.value = res.data
})
@@ -96,7 +96,7 @@ const active = () => {
if (licenseKey.value === '') {
return ElMessage.error('请输入授权码')
}
- httpPost('/api/admin/config/active', { license: licenseKey.value })
+ httpPost('/api/admin/config/license/active', { license: licenseKey.value })
.then((res) => {
ElMessage.success('授权成功,机器编码为:' + res.data)
fetchLicense()
diff --git a/web/src/views/admin/settings/NoticeConfig.vue b/web/src/views/admin/settings/NoticeConfig.vue
index 91346b27..82d6c8d8 100644
--- a/web/src/views/admin/settings/NoticeConfig.vue
+++ b/web/src/views/admin/settings/NoticeConfig.vue
@@ -38,10 +38,7 @@ onMounted(() => {
})
const save = () => {
- httpPost('/api/admin/config/update', {
- key: 'notice',
- config: { content: notice.value, updated: true },
- })
+ httpPost('/api/admin/config/update/notice', { content: notice.value })
.then(() => {
ElMessage.success('操作成功!')
})
diff --git a/web/src/views/admin/settings/PaymentConfig.vue b/web/src/views/admin/settings/PaymentConfig.vue
index f0190741..860283ee 100644
--- a/web/src/views/admin/settings/PaymentConfig.vue
+++ b/web/src/views/admin/settings/PaymentConfig.vue
@@ -4,7 +4,7 @@
-
+
-
-
+
保存
测试
@@ -21,7 +20,7 @@
-
+
@@ -31,25 +30,26 @@
>
-
+
- 保存
- 测试
+ 保存
+ 测试
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
- 保存
- 测试
+ 保存
+ 测试
@@ -86,39 +86,47 @@ import { onMounted, ref } from 'vue'
const loading = ref(true)
const active = ref('alipay')
-const alipay = ref({ enabled: false, sand_box: false })
-const wechat = ref({ enabled: false })
-const hupi = ref({ enabled: false })
-const geekpay = ref({ enabled: false, methods: [] })
+const alipay = ref({
+ enabled: false,
+ sandbox: false,
+ app_id: '',
+ private_key: '',
+ alipay_public_key: '',
+ domain: '',
+})
+const wechat = ref({
+ enabled: false,
+ app_id: '',
+ mch_id: '',
+ serial_no: '',
+ private_key: '',
+ api_v3_key: '',
+ domain: '',
+})
+const epay = ref({ enabled: false, app_id: '', private_key: '', api_url: '', domain: '' })
onMounted(() => {
- Promise.all([
- httpGet('/api/admin/config/get?key=alipay'),
- httpGet('/api/admin/config/get?key=wechat'),
- httpGet('/api/admin/config/get?key=hupi'),
- httpGet('/api/admin/config/get?key=geekpay'),
- ])
- .then(([a, w, h, g]) => {
- alipay.value = a.data || alipay.value
- wechat.value = w.data || wechat.value
- hupi.value = h.data || hupi.value
- geekpay.value = g.data || geekpay.value
+ httpGet('/api/admin/config/get?key=payment')
+ .then((res) => {
+ const data = res.data || {}
+ alipay.value = { ...alipay.value, ...(data.alipay || {}) }
+ wechat.value = { ...wechat.value, ...(data.wxpay || data.wechat || {}) }
+ epay.value = { ...epay.value, ...(data.epay || {}) }
})
.catch(() => {})
.finally(() => (loading.value = false))
})
const save = (key) => {
- const map = { alipay, wechat, hupi, geekpay }
- httpPost('/api/admin/config/update', { key, config: map[key].value })
+ const payload = { alipay: alipay.value, wxpay: wechat.value, epay: epay.value }
+ httpPost('/api/admin/config/update/payment', payload)
.then(() => ElMessage.success('保存成功'))
.catch((e) => ElMessage.error(e.message))
}
const test = (key) => {
- httpPost('/api/admin/config/test', { key })
- .then((res) => ElMessage.success(res.message || '测试成功'))
- .catch((e) => ElMessage.error(e.message || '测试失败'))
+ // 后端未提供独立测试接口,保留占位提示
+ ElMessage.info('请在支付端自行验证配置')
}
diff --git a/web/src/views/admin/settings/PrivacyConfig.vue b/web/src/views/admin/settings/PrivacyConfig.vue
index e9e905c4..0cbfb970 100644
--- a/web/src/views/admin/settings/PrivacyConfig.vue
+++ b/web/src/views/admin/settings/PrivacyConfig.vue
@@ -43,10 +43,7 @@ onMounted(() => {
})
const save = () => {
- httpPost('/api/admin/config/update', {
- key: 'privacy',
- config: { content: privacy.value, updated: true },
- })
+ httpPost('/api/admin/config/update/notice', { content: privacy.value })
.then(() => {
ElMessage.success('操作成功!')
})
diff --git a/web/src/views/admin/settings/StorageConfig.vue b/web/src/views/admin/settings/StorageConfig.vue
index a5b1ff3d..ecd31e13 100644
--- a/web/src/views/admin/settings/StorageConfig.vue
+++ b/web/src/views/admin/settings/StorageConfig.vue
@@ -12,46 +12,46 @@
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
@@ -70,58 +70,62 @@ import { onMounted, ref } from 'vue'
const loading = ref(true)
const active = ref('local')
-const local = ref({ base_path: '', base_url: '' })
+const local = ref({ BasePath: '', BaseURL: '' })
const minio = ref({
- endpoint: '',
- access_key: '',
- access_secret: '',
- bucket: '',
- use_ssl: false,
- domain: '',
+ Endpoint: '',
+ AccessKey: '',
+ AccessSecret: '',
+ Bucket: '',
+ SubDir: '',
+ UseSSL: false,
+ Domain: '',
+})
+const qiniu = ref({
+ Zone: 'z2',
+ AccessKey: '',
+ AccessSecret: '',
+ Bucket: '',
+ SubDir: '',
+ Domain: '',
})
-const qiniu = ref({ zone: 'z2', access_key: '', access_secret: '', bucket: '', domain: '' })
const aliyun = ref({
- endpoint: '',
- access_key: '',
- access_secret: '',
- bucket: '',
- sub_dir: '',
- domain: '',
+ Endpoint: '',
+ AccessKey: '',
+ AccessSecret: '',
+ Bucket: '',
+ SubDir: '',
+ Domain: '',
})
onMounted(() => {
httpGet('/api/admin/config/get?key=oss')
.then((res) => {
const data = res.data || {}
- active.value = (data.active || 'local').toLowerCase()
- local.value = data.local || local.value
- minio.value = data.minio || minio.value
- qiniu.value = data.qiniu || qiniu.value
- aliyun.value = data.aliyun || aliyun.value
+ const Active = data.Active || data.active || 'local'
+ active.value = String(Active).toLowerCase()
+ local.value = data.Local || data.local || local.value
+ minio.value = data.Minio || data.minio || minio.value
+ qiniu.value = data.QiNiu || data.qiniu || qiniu.value
+ aliyun.value = data.AliYun || data.aliyun || aliyun.value
})
.catch(() => {})
.finally(() => (loading.value = false))
})
const save = () => {
- httpPost('/api/admin/config/update', {
- key: 'oss',
- config: {
- active: active.value,
- local: local.value,
- minio: minio.value,
- qiniu: qiniu.value,
- aliyun: aliyun.value,
- },
+ httpPost('/api/admin/config/update/oss', {
+ active: active.value,
+ local: local.value,
+ minio: minio.value,
+ qiniu: qiniu.value,
+ aliyun: aliyun.value,
})
.then(() => ElMessage.success('保存成功'))
.catch((e) => ElMessage.error(e.message))
}
const test = () => {
- httpPost('/api/admin/config/test', { key: 'oss' })
- .then((res) => ElMessage.success(res.message || '连接成功'))
- .catch((e) => ElMessage.error(e.message || '连接失败'))
+ ElMessage.info('请在对象存储端验证配置')
}