mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-09-16 18:27:13 +00:00
d4fd38ab7e
- 同步 Plus v4.3.1 功能源并移除商业 License 闭环 - 更新开源镜像命名、Docker 部署版本和 geekai 数据库配置 - 补充前端 ESLint 检查配置并修复存量解析与模板问题 - 保留 JWT、管理员权限、API Key 和 OAuth 等正常鉴权机制
540 lines
14 KiB
Go
540 lines
14 KiB
Go
package admin
|
|
|
|
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
// * 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 (
|
|
"errors"
|
|
"strings"
|
|
|
|
"geekai/core"
|
|
"geekai/core/middleware"
|
|
"geekai/core/types"
|
|
"geekai/handler"
|
|
"geekai/service"
|
|
"geekai/service/oss"
|
|
"geekai/service/payment"
|
|
"geekai/service/sms"
|
|
"geekai/store/model"
|
|
"geekai/utils"
|
|
"geekai/utils/resp"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type ConfigHandler struct {
|
|
handler.BaseHandler
|
|
sysConfig *types.SystemConfig
|
|
alipayService *payment.AlipayService
|
|
wxpayService *payment.WxPayService
|
|
epayService *payment.EPayService
|
|
stripeService *payment.StripeService
|
|
smsManager *sms.SmsManager
|
|
uploaderManager *oss.UploaderManager
|
|
smtpService *service.SmtpService
|
|
captchaService *service.CaptchaService
|
|
wxLoginService *service.WxLoginService
|
|
wxGzhService *service.WxGzhService
|
|
}
|
|
|
|
func NewConfigHandler(
|
|
app *core.AppServer,
|
|
db *gorm.DB,
|
|
sysConfig *types.SystemConfig,
|
|
alipayService *payment.AlipayService,
|
|
wxpayService *payment.WxPayService,
|
|
epayService *payment.EPayService,
|
|
stripeService *payment.StripeService,
|
|
smsManager *sms.SmsManager,
|
|
uploaderManager *oss.UploaderManager,
|
|
smtpService *service.SmtpService,
|
|
captchaService *service.CaptchaService,
|
|
wxLoginService *service.WxLoginService,
|
|
wxChatService *service.WxGzhService,
|
|
) *ConfigHandler {
|
|
return &ConfigHandler{
|
|
BaseHandler: handler.BaseHandler{App: app, DB: db},
|
|
sysConfig: sysConfig,
|
|
alipayService: alipayService,
|
|
wxpayService: wxpayService,
|
|
epayService: epayService,
|
|
stripeService: stripeService,
|
|
smsManager: smsManager,
|
|
uploaderManager: uploaderManager,
|
|
smtpService: smtpService,
|
|
captchaService: captchaService,
|
|
wxLoginService: wxLoginService,
|
|
wxGzhService: wxChatService,
|
|
}
|
|
}
|
|
|
|
// RegisterRoutes 注册路由
|
|
func (h *ConfigHandler) RegisterRoutes() {
|
|
rg := h.App.Engine.Group("/api/admin/config")
|
|
|
|
// 需要管理员登录的接口
|
|
rg.Use(middleware.AdminAuthMiddleware(h.App.Config.AdminSession.SecretKey, h.App.Redis))
|
|
{
|
|
rg.POST("update/base", h.UpdateBase)
|
|
rg.POST("update/power", h.UpdatePower)
|
|
rg.POST("update/notice", h.UpdateNotice)
|
|
rg.POST("update/agreement", h.UpdateAgreement)
|
|
rg.POST("update/privacy", h.UpdatePrivacy)
|
|
rg.POST("update/mark_map", h.UpdateMarkMap)
|
|
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("update/wx_gzh", h.UpdateWxGzh)
|
|
rg.POST("update/wx_gzh_menu", h.UpdateWxGzhMenu)
|
|
rg.POST("wx_gzh/menu/publish", h.PublishWxGzhMenu)
|
|
rg.GET("wx_gzh/menu/query", h.QueryWxGzhMenu)
|
|
}
|
|
}
|
|
|
|
// UpdateBase 更新基础配置
|
|
func (h *ConfigHandler) UpdateBase(c *gin.Context) {
|
|
var data types.BaseConfig
|
|
|
|
if err := c.ShouldBindJSON(&data); err != nil {
|
|
resp.ERROR(c, types.InvalidArgs)
|
|
return
|
|
}
|
|
|
|
err := h.Update(types.ConfigKeySystem, data)
|
|
if err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
|
|
h.sysConfig.Base = data
|
|
|
|
resp.SUCCESS(c, data)
|
|
}
|
|
|
|
// UpdatePower 更新系统配置
|
|
func (h *ConfigHandler) UpdatePower(c *gin.Context) {
|
|
var data struct {
|
|
InitPower int `json:"init_power,omitempty"` // 新用户注册赠送算力值
|
|
DailyPower int `json:"daily_power,omitempty"` // 每日签到赠送算力
|
|
InvitePower int `json:"invite_power,omitempty"` // 邀请新用户赠送算力值
|
|
MjPower int `json:"mj_power,omitempty"` // MJ 绘画消耗算力
|
|
MjActionPower int `json:"mj_action_power,omitempty"` // MJ 操作(放大,变换)消耗算力
|
|
MjUpscalePower int `json:"mj_upscale_power,omitempty"` // MJ 放大/变换消耗算力
|
|
MjBlendPower int `json:"mj_blend_power,omitempty"` // MJ 融图消耗算力
|
|
MjSwapFacePower int `json:"mj_swap_face_power,omitempty"` // MJ 换脸消耗算力
|
|
MjModalPower int `json:"mj_modal_power,omitempty"` // MJ 局部重绘消耗算力
|
|
SunoPower int `json:"suno_power,omitempty"` // Suno 生成歌曲消耗算力
|
|
LumaPower int `json:"luma_power,omitempty"` // Luma 生成视频消耗算力
|
|
KeLingPowers map[string]int `json:"keling_powers,omitempty"` // 可灵生成视频消耗算力
|
|
}
|
|
if err := c.ShouldBindJSON(&data); err != nil {
|
|
resp.ERROR(c, types.InvalidArgs)
|
|
return
|
|
}
|
|
|
|
h.sysConfig.Base.InitPower = data.InitPower
|
|
h.sysConfig.Base.DailyPower = data.DailyPower
|
|
h.sysConfig.Base.InvitePower = data.InvitePower
|
|
h.sysConfig.Base.MjPower = data.MjPower
|
|
h.sysConfig.Base.MjActionPower = data.MjActionPower
|
|
h.sysConfig.Base.MjUpscalePower = data.MjUpscalePower
|
|
h.sysConfig.Base.MjBlendPower = data.MjBlendPower
|
|
h.sysConfig.Base.MjSwapFacePower = data.MjSwapFacePower
|
|
h.sysConfig.Base.MjModalPower = data.MjModalPower
|
|
h.sysConfig.Base.SunoPower = data.SunoPower
|
|
h.sysConfig.Base.LumaPower = data.LumaPower
|
|
h.sysConfig.Base.KeLingPowers = data.KeLingPowers
|
|
|
|
err := h.Update(types.ConfigKeySystem, h.sysConfig.Base)
|
|
if err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
|
|
resp.SUCCESS(c, h.sysConfig.Base)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// UpdateAgreement 更新用户协议配置
|
|
func (h *ConfigHandler) UpdateAgreement(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.ConfigKeyAgreement, data)
|
|
if err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
|
|
resp.SUCCESS(c, data)
|
|
}
|
|
|
|
// UpdatePrivacy 更新隐私政策配置
|
|
func (h *ConfigHandler) UpdatePrivacy(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.ConfigKeyPrivacy, data)
|
|
if err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
|
|
resp.SUCCESS(c, data)
|
|
}
|
|
|
|
// UpdateMarkMap 更新思维导图配置
|
|
func (h *ConfigHandler) UpdateMarkMap(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.ConfigKeyMarkMap, 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
|
|
}
|
|
h.captchaService.UpdateConfig(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
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
if data.Stripe.Enabled {
|
|
h.stripeService.UpdateConfig(&data.Stripe)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
err := h.Update(types.ConfigKeySms, data)
|
|
if err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
|
|
// 更新服务配置
|
|
h.smsManager.UpdateConfig(data)
|
|
|
|
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
|
|
}
|
|
|
|
err := h.Update(types.ConfigKeyOss, data)
|
|
if err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
|
|
// 更新服务配置
|
|
h.uploaderManager.UpdateConfig(data)
|
|
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
|
|
}
|
|
if data.Host == "" || data.Port <= 0 {
|
|
resp.ERROR(c, "邮件服务器地址和端口不能为空")
|
|
return
|
|
}
|
|
|
|
err := h.Update(types.ConfigKeySmtp, data)
|
|
if err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
|
|
// 更新服务配置
|
|
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
|
|
}
|
|
|
|
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
|
|
err := h.DB.Where("name", name).First(&config).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
resp.SUCCESS(c, nil)
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
|
|
var value map[string]any
|
|
err = utils.JsonDecode(config.Value, &value)
|
|
if err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
|
|
resp.SUCCESS(c, value)
|
|
}
|
|
|
|
func (h *ConfigHandler) UpdateWxGzh(c *gin.Context) {
|
|
var data types.WxGzhConfig
|
|
if err := c.ShouldBindJSON(&data); err != nil {
|
|
resp.ERROR(c, types.InvalidArgs)
|
|
return
|
|
}
|
|
err := h.Update(types.ConfigKeyWxGzh, data)
|
|
if err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
|
|
h.wxGzhService.UpdateConfig(data)
|
|
h.sysConfig.WxGzh = data
|
|
resp.SUCCESS(c, data)
|
|
}
|
|
|
|
// UpdateWxGzhMenu 保存公众号自定义菜单草稿(不调用微信接口)。
|
|
func (h *ConfigHandler) UpdateWxGzhMenu(c *gin.Context) {
|
|
var data types.WxGzhMenuConfig
|
|
if err := c.ShouldBindJSON(&data); err != nil {
|
|
resp.ERROR(c, types.InvalidArgs)
|
|
return
|
|
}
|
|
if data.Button == nil {
|
|
data.Button = []types.WxGzhMenuButton{}
|
|
}
|
|
if err := h.Update(types.ConfigKeyWxGzhMenu, data); err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
resp.SUCCESS(c, data)
|
|
}
|
|
|
|
func (h *ConfigHandler) loadWxGzhCredentials() (appID, secret string, err error) {
|
|
var row model.Config
|
|
if err = h.DB.Where("name", types.ConfigKeyWxGzh).First(&row).Error; err != nil {
|
|
return "", "", err
|
|
}
|
|
var wx types.WxGzhConfig
|
|
if err = utils.JsonDecode(row.Value, &wx); err != nil {
|
|
return "", "", err
|
|
}
|
|
return strings.TrimSpace(wx.AppId), strings.TrimSpace(wx.Secret), nil
|
|
}
|
|
|
|
// PublishWxGzhMenu 将草稿菜单同步到微信公众平台(menu/create)。
|
|
func (h *ConfigHandler) PublishWxGzhMenu(c *gin.Context) {
|
|
appID, secret, err := h.loadWxGzhCredentials()
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
resp.ERROR(c, "请先配置微信公众号 AppID 与 AppSecret")
|
|
return
|
|
}
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
if appID == "" || secret == "" {
|
|
resp.ERROR(c, "请先配置微信公众号 AppID 与 AppSecret")
|
|
return
|
|
}
|
|
|
|
var menu types.WxGzhMenuConfig
|
|
var menuRow model.Config
|
|
if err := h.DB.Where("name", types.ConfigKeyWxGzhMenu).First(&menuRow).Error; err != nil {
|
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
} else {
|
|
if err := utils.JsonDecode(menuRow.Value, &menu); err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
}
|
|
if menu.Button == nil {
|
|
menu.Button = []types.WxGzhMenuButton{}
|
|
}
|
|
|
|
if err := h.wxGzhService.PublishCustomMenu(c.Request.Context(), appID, secret, menu); err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
resp.SUCCESS(c, gin.H{"message": "已同步到微信"})
|
|
}
|
|
|
|
// QueryWxGzhMenu 从微信拉取当前菜单并写入草稿 wx_gzh_menu。
|
|
func (h *ConfigHandler) QueryWxGzhMenu(c *gin.Context) {
|
|
appID, secret, err := h.loadWxGzhCredentials()
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
resp.ERROR(c, "请先配置微信公众号 AppID 与 AppSecret")
|
|
return
|
|
}
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
if appID == "" || secret == "" {
|
|
resp.ERROR(c, "请先配置微信公众号 AppID 与 AppSecret")
|
|
return
|
|
}
|
|
|
|
menuCfg, err := h.wxGzhService.GetCustomMenuFromWechat(c.Request.Context(), appID, secret)
|
|
if err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
if menuCfg.Button == nil {
|
|
menuCfg.Button = []types.WxGzhMenuButton{}
|
|
}
|
|
if err := h.Update(types.ConfigKeyWxGzhMenu, menuCfg); err != nil {
|
|
resp.ERROR(c, err.Error())
|
|
return
|
|
}
|
|
resp.SUCCESS(c, menuCfg)
|
|
}
|