ccpoad / internal /app /admin_settings.go
anyalerob's picture
Upload folder using huggingface_hub
2986042 verified
Raw
History Blame Contribute Delete
7.2 kB
package app
import (
"errors"
"fmt"
"log"
"net/http"
"strconv"
"ccLoad/internal/model"
"github.com/gin-gonic/gin"
)
// 配置验证常量
const (
LogRetentionDaysMin = 1
LogRetentionDaysMax = 365
LogRetentionDaysDisabled = -1 // 永久保留
)
// AdminListSettings 获取所有配置项
// GET /admin/settings
func (s *Server) AdminListSettings(c *gin.Context) {
settings, err := s.configService.ListAllSettings(c.Request.Context())
if err != nil {
log.Printf("[ERROR] AdminListSettings 失败: %v", err)
RespondError(c, http.StatusInternalServerError, err)
return
}
if settings == nil {
settings = make([]*model.SystemSetting, 0)
}
RespondJSON(c, http.StatusOK, settings)
}
// AdminGetSetting 获取单个配置项
// GET /admin/settings/:key
func (s *Server) AdminGetSetting(c *gin.Context) {
key := c.Param("key")
if key == "" {
RespondErrorMsg(c, http.StatusBadRequest, "missing setting key")
return
}
// 管理接口必须返回持久化后的最新值,不能复用等待重启的运行时缓存。
setting, err := s.configService.GetSettingFresh(c.Request.Context(), key)
if errors.Is(err, model.ErrSettingNotFound) {
RespondErrorMsg(c, http.StatusNotFound, fmt.Sprintf("setting not found: %s", key))
return
}
if err != nil {
log.Printf("[ERROR] AdminGetSetting key=%s 失败: %v", key, err)
RespondError(c, http.StatusInternalServerError, err)
return
}
RespondJSON(c, http.StatusOK, setting)
}
// AdminUpdateSetting 更新配置项
// PUT /admin/settings/:key
func (s *Server) AdminUpdateSetting(c *gin.Context) {
key := c.Param("key")
if key == "" {
RespondErrorMsg(c, http.StatusBadRequest, "missing setting key")
return
}
var req SettingUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
RespondErrorMsg(c, http.StatusBadRequest, fmt.Sprintf("invalid request: %v", err))
return
}
// 验证值的合法性
setting := s.configService.GetSetting(key)
if setting == nil {
RespondErrorMsg(c, http.StatusNotFound, fmt.Sprintf("setting not found: %s", key))
return
}
if err := validateSettingValue(key, setting.ValueType, req.Value); err != nil {
RespondErrorMsg(c, http.StatusBadRequest, fmt.Sprintf("invalid value for type %s: %v", setting.ValueType, err))
return
}
// 更新配置
if err := s.configService.UpdateSetting(c.Request.Context(), key, req.Value); err != nil {
log.Printf("[ERROR] AdminUpdateSetting key=%s 失败: %v", key, err)
RespondError(c, http.StatusInternalServerError, err)
return
}
// log.Printf("[INFO] Setting updated: %s = %s (restart required)", key, req.Value)
// 返回成功响应,告知需要重启
RespondJSON(c, http.StatusOK, gin.H{
"message": "配置已保存,程序将在2秒后重启",
"key": key,
"value": req.Value,
})
// 异步触发重启
go triggerRestart()
}
// AdminResetSetting 重置配置为默认值
// POST /admin/settings/:key/reset
func (s *Server) AdminResetSetting(c *gin.Context) {
key := c.Param("key")
if key == "" {
RespondErrorMsg(c, http.StatusBadRequest, "missing setting key")
return
}
// 获取默认值
setting := s.configService.GetSetting(key)
if setting == nil {
RespondErrorMsg(c, http.StatusNotFound, fmt.Sprintf("setting not found: %s", key))
return
}
// 重置为默认值
if err := s.configService.UpdateSetting(c.Request.Context(), key, setting.DefaultValue); err != nil {
log.Printf("[ERROR] AdminResetSetting key=%s 失败: %v", key, err)
RespondError(c, http.StatusInternalServerError, err)
return
}
// log.Printf("[INFO] Setting reset to default: %s = %s (restart required)", key, setting.DefaultValue)
RespondJSON(c, http.StatusOK, gin.H{
"message": "配置已重置为默认值,程序将在2秒后重启",
"key": key,
"value": setting.DefaultValue,
})
// 异步触发重启
go triggerRestart()
}
// AdminBatchUpdateSettings 批量更新配置(事务保护)
// POST /admin/settings/batch
func (s *Server) AdminBatchUpdateSettings(c *gin.Context) {
var req map[string]string
if err := c.ShouldBindJSON(&req); err != nil {
RespondErrorMsg(c, http.StatusBadRequest, fmt.Sprintf("invalid request: %v", err))
return
}
if len(req) == 0 {
RespondErrorMsg(c, http.StatusBadRequest, "no settings to update")
return
}
// 验证所有配置
for key, value := range req {
setting := s.configService.GetSetting(key)
if setting == nil {
RespondErrorMsg(c, http.StatusBadRequest, fmt.Sprintf("unknown setting: %s", key))
return
}
if err := validateSettingValue(key, setting.ValueType, value); err != nil {
RespondErrorMsg(c, http.StatusBadRequest, fmt.Sprintf("invalid value for %s: %v", key, err))
return
}
}
// 批量更新(事务保护)
if err := s.configService.BatchUpdateSettings(c.Request.Context(), req); err != nil {
log.Printf("[ERROR] AdminBatchUpdateSettings 失败: %v", err)
RespondError(c, http.StatusInternalServerError, err)
return
}
log.Printf("[INFO] 已批量更新 %d 项配置(需重启)", len(req))
RespondJSON(c, http.StatusOK, gin.H{
"message": fmt.Sprintf("已保存 %d 项配置,程序将在2秒后重启", len(req)),
})
// 异步触发重启
go triggerRestart()
}
// validateSettingValue 验证配置值的合法性
func validateSettingValue(key, valueType, value string) error {
switch valueType {
case "int":
intVal, err := strconv.Atoi(value)
if err != nil {
return fmt.Errorf("not a valid integer")
}
// 按配置项定义具体约束
switch key {
case "max_key_retries":
if intVal < 1 {
return fmt.Errorf("max_key_retries must be >= 1")
}
case "channel_check_interval_hours":
if intVal < 0 {
return fmt.Errorf("channel_check_interval_hours must be >= 0")
}
case "log_retention_days":
if intVal != LogRetentionDaysDisabled && (intVal < LogRetentionDaysMin || intVal > LogRetentionDaysMax) {
return fmt.Errorf("log_retention_days must be %d (永久) or %d-%d", LogRetentionDaysDisabled, LogRetentionDaysMin, LogRetentionDaysMax)
}
default:
if intVal < -1 {
return fmt.Errorf("value must be >= -1")
}
}
case "bool":
if value != "true" && value != "false" && value != "1" && value != "0" {
return fmt.Errorf("must be true/false or 1/0")
}
case "duration":
intVal, err := strconv.Atoi(value)
if err != nil {
return fmt.Errorf("duration must be an integer (seconds)")
}
if intVal < 0 {
return fmt.Errorf("duration must be >= 0 (0 = disabled)")
}
case "string":
switch key {
case "log_channel_click_action":
if value != "edit" && value != "navigate" {
return fmt.Errorf("log_channel_click_action must be edit or navigate")
}
}
default:
return fmt.Errorf("unknown value type: %s", valueType)
}
return nil
}
// RestartFunc 重启函数(由 main 包注入,避免循环依赖)
var RestartFunc func()
// triggerRestart 触发程序重启
// 依赖优雅关闭语义:触发 SIGTERM 后,HTTP 服务器应完成当前请求再退出。
func triggerRestart() {
log.Print("[INFO] 配置变更触发重启...")
if RestartFunc == nil {
log.Printf("[ERROR] RestartFunc 为空,重启已跳过")
return
}
RestartFunc()
}