704 lines
20 KiB
Go
704 lines
20 KiB
Go
package controller
|
||
|
||
import (
|
||
"fmt"
|
||
"net/http"
|
||
"strings"
|
||
|
||
"github.com/QuantumNous/new-api/common"
|
||
"github.com/QuantumNous/new-api/model"
|
||
"github.com/QuantumNous/new-api/service"
|
||
"github.com/QuantumNous/new-api/setting"
|
||
"github.com/QuantumNous/new-api/setting/console_setting"
|
||
"github.com/QuantumNous/new-api/setting/operation_setting"
|
||
"github.com/QuantumNous/new-api/setting/ratio_setting"
|
||
"github.com/QuantumNous/new-api/setting/system_setting"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
var completionRatioMetaOptionKeys = []string{
|
||
"ModelPrice",
|
||
"ModelRatio",
|
||
"CompletionRatio",
|
||
"CacheRatio",
|
||
"CreateCacheRatio",
|
||
"ImageRatio",
|
||
"AudioRatio",
|
||
"AudioCompletionRatio",
|
||
"VideoRatio",
|
||
"VideoCompletionRatio",
|
||
"VideoPrice",
|
||
"VideoPricingRules",
|
||
"ChannelVideoPricingRules",
|
||
"ImagePrice",
|
||
"ImagePricingRules",
|
||
"ChannelImagePricingRules",
|
||
}
|
||
|
||
// modelPricingOptionKeys 包含值格式为模型名→价格/倍率 JSON 对象的选项 key。
|
||
// 对这些 key 的更新会按模型粒度比较新旧值,仅记录实际变化的条目。
|
||
var modelPricingOptionKeys = map[string]bool{
|
||
"ModelPrice": true,
|
||
"ModelRatio": true,
|
||
"CompletionRatio": true,
|
||
"CacheRatio": true,
|
||
"CreateCacheRatio": true,
|
||
"ImageRatio": true,
|
||
"AudioRatio": true,
|
||
"AudioCompletionRatio": true,
|
||
"VideoRatio": true,
|
||
"VideoCompletionRatio": true,
|
||
"VideoPrice": true,
|
||
"VideoPricingRules": true,
|
||
"ChannelVideoPricingRules": true,
|
||
"ImagePrice": true,
|
||
"ImagePricingRules": true,
|
||
"ChannelImagePricingRules": true,
|
||
"GroupRatio": true,
|
||
"ChannelModelPrice": true,
|
||
"ChannelModelRatio": true,
|
||
"GroupModelPrice": true,
|
||
}
|
||
|
||
// computeModelPricingDiff 比较模型定价类选项的新旧 JSON 值,返回仅包含变更条目的 FieldChange 列表。
|
||
// oldJSON 和 newJSON 应为形如 {"model-a": 1.5, "model-b": 0.8} 的 JSON 字符串。
|
||
func computeModelPricingDiff(oldJSON, newJSON string) []service.FieldChange {
|
||
if oldJSON == "" {
|
||
oldJSON = "{}"
|
||
}
|
||
if newJSON == "" {
|
||
newJSON = "{}"
|
||
}
|
||
if strings.TrimSpace(oldJSON) == strings.TrimSpace(newJSON) {
|
||
return nil
|
||
}
|
||
|
||
var oldMap, newMap map[string]any
|
||
if err := common.UnmarshalJsonStr(oldJSON, &oldMap); err != nil {
|
||
return nil
|
||
}
|
||
if err := common.UnmarshalJsonStr(newJSON, &newMap); err != nil {
|
||
return nil
|
||
}
|
||
|
||
var changes []service.FieldChange
|
||
// 检测修改和新增的条目
|
||
for k, newV := range newMap {
|
||
oldV, existed := oldMap[k]
|
||
newStr := formatPricingValue(newV)
|
||
if !existed {
|
||
changes = append(changes, service.FieldChange{
|
||
Field: k,
|
||
OldValue: "",
|
||
NewValue: newStr,
|
||
})
|
||
} else {
|
||
oldStr := formatPricingValue(oldV)
|
||
if oldStr != newStr {
|
||
changes = append(changes, service.FieldChange{
|
||
Field: k,
|
||
OldValue: oldStr,
|
||
NewValue: newStr,
|
||
})
|
||
}
|
||
}
|
||
}
|
||
// 检测删除的条目
|
||
for k, oldV := range oldMap {
|
||
if _, stillExists := newMap[k]; !stillExists {
|
||
changes = append(changes, service.FieldChange{
|
||
Field: k,
|
||
OldValue: formatPricingValue(oldV),
|
||
NewValue: "",
|
||
})
|
||
}
|
||
}
|
||
return changes
|
||
}
|
||
|
||
// formatPricingValue 将定价值格式化为可读字符串。
|
||
func formatPricingValue(v any) string {
|
||
switch val := v.(type) {
|
||
case float64:
|
||
if val == float64(int64(val)) {
|
||
return fmt.Sprintf("%d", int64(val))
|
||
}
|
||
return fmt.Sprintf("%g", val)
|
||
case string:
|
||
return val
|
||
default:
|
||
return fmt.Sprintf("%v", val)
|
||
}
|
||
}
|
||
|
||
func collectModelNamesFromOptionValue(raw string, modelNames map[string]struct{}) {
|
||
if strings.TrimSpace(raw) == "" {
|
||
return
|
||
}
|
||
|
||
var parsed map[string]any
|
||
if err := common.UnmarshalJsonStr(raw, &parsed); err != nil {
|
||
return
|
||
}
|
||
|
||
for modelName := range parsed {
|
||
modelNames[modelName] = struct{}{}
|
||
}
|
||
}
|
||
|
||
func buildCompletionRatioMetaValue(optionValues map[string]string) string {
|
||
modelNames := make(map[string]struct{})
|
||
for _, key := range completionRatioMetaOptionKeys {
|
||
collectModelNamesFromOptionValue(optionValues[key], modelNames)
|
||
}
|
||
|
||
meta := make(map[string]ratio_setting.CompletionRatioInfo, len(modelNames))
|
||
for modelName := range modelNames {
|
||
meta[modelName] = ratio_setting.GetCompletionRatioInfo(modelName)
|
||
}
|
||
|
||
jsonBytes, err := common.Marshal(meta)
|
||
if err != nil {
|
||
return "{}"
|
||
}
|
||
return string(jsonBytes)
|
||
}
|
||
|
||
func GetOptions(c *gin.Context) {
|
||
// 已审核供应商仅返回其自有模型相关配置项,避免读取全局敏感配置。
|
||
if c.GetInt("role") < common.RoleAdminUser {
|
||
ownedModels, err := collectSupplierOwnedModelNames(c.GetInt("id"))
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
options := make([]*model.Option, 0, len(supplierEditableModelOptionKeys))
|
||
common.OptionMapRWMutex.Lock()
|
||
for key := range supplierEditableModelOptionKeys {
|
||
value := strings.TrimSpace(common.Interface2String(common.OptionMap[key]))
|
||
filteredValue, filterErr := filterModelJSONByOwnedModels(value, ownedModels)
|
||
if filterErr != nil {
|
||
continue
|
||
}
|
||
options = append(options, &model.Option{
|
||
Key: key,
|
||
Value: filteredValue,
|
||
})
|
||
}
|
||
common.OptionMapRWMutex.Unlock()
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "",
|
||
"data": options,
|
||
})
|
||
return
|
||
}
|
||
|
||
var options []*model.Option
|
||
optionValues := make(map[string]string)
|
||
common.OptionMapRWMutex.Lock()
|
||
for k, v := range common.OptionMap {
|
||
// YipayAppSecret 在循环结束后单独追加,以 operation_setting 为准并避免与 OptionMap 不同步
|
||
if k == "YipayAppSecret" {
|
||
continue
|
||
}
|
||
// OSS AccessKey 与 Secret 在循环结束后单独追加(脱敏)
|
||
if k == "oss_setting.access_key_id" || k == "oss_setting.access_key_secret" {
|
||
continue
|
||
}
|
||
// 阿里云短信 AccessKey 在循环结束后单独追加(脱敏)。
|
||
if k == "SMSAccessKeyID" || k == "SMSAccessKeySecret" {
|
||
continue
|
||
}
|
||
value := common.Interface2String(v)
|
||
if strings.HasSuffix(k, "Token") ||
|
||
strings.HasSuffix(k, "Secret") ||
|
||
strings.HasSuffix(k, "Key") ||
|
||
strings.HasSuffix(k, "secret") ||
|
||
strings.HasSuffix(k, "api_key") {
|
||
continue
|
||
}
|
||
options = append(options, &model.Option{
|
||
Key: k,
|
||
Value: value,
|
||
})
|
||
for _, optionKey := range completionRatioMetaOptionKeys {
|
||
if optionKey == k {
|
||
optionValues[k] = value
|
||
break
|
||
}
|
||
}
|
||
}
|
||
rawYipay := strings.TrimSpace(operation_setting.YipayAppSecret)
|
||
if rawYipay == "" {
|
||
if v, ok := common.OptionMap["YipayAppSecret"]; ok {
|
||
rawYipay = strings.TrimSpace(common.Interface2String(v))
|
||
}
|
||
}
|
||
yipayDisp := ""
|
||
if rawYipay != "" {
|
||
yipayDisp = common.MaskCredentialForAdminDisplay(rawYipay)
|
||
}
|
||
options = append(options, &model.Option{
|
||
Key: "YipayAppSecret",
|
||
Value: yipayDisp,
|
||
})
|
||
rawOssID := strings.TrimSpace(operation_setting.GetOssSetting().AccessKeyID)
|
||
if rawOssID == "" {
|
||
if v, ok := common.OptionMap["oss_setting.access_key_id"]; ok {
|
||
rawOssID = strings.TrimSpace(common.Interface2String(v))
|
||
}
|
||
}
|
||
ossIDDisp := ""
|
||
if rawOssID != "" {
|
||
ossIDDisp = common.MaskCredentialForAdminDisplay(rawOssID)
|
||
}
|
||
options = append(options, &model.Option{
|
||
Key: "oss_setting.access_key_id",
|
||
Value: ossIDDisp,
|
||
})
|
||
rawOssSecret := strings.TrimSpace(operation_setting.GetOssSetting().AccessKeySecret)
|
||
if rawOssSecret == "" {
|
||
if v, ok := common.OptionMap["oss_setting.access_key_secret"]; ok {
|
||
rawOssSecret = strings.TrimSpace(common.Interface2String(v))
|
||
}
|
||
}
|
||
ossSecretDisp := ""
|
||
if rawOssSecret != "" {
|
||
ossSecretDisp = common.MaskCredentialForAdminDisplay(rawOssSecret)
|
||
}
|
||
options = append(options, &model.Option{
|
||
Key: "oss_setting.access_key_secret",
|
||
Value: ossSecretDisp,
|
||
})
|
||
rawSMSID := strings.TrimSpace(common.SMSAccessKeyID)
|
||
if rawSMSID == "" {
|
||
if v, ok := common.OptionMap["SMSAccessKeyID"]; ok {
|
||
rawSMSID = strings.TrimSpace(common.Interface2String(v))
|
||
}
|
||
}
|
||
smsIDDisp := ""
|
||
if rawSMSID != "" {
|
||
smsIDDisp = common.MaskCredentialForAdminDisplay(rawSMSID)
|
||
}
|
||
options = append(options, &model.Option{
|
||
Key: "SMSAccessKeyID",
|
||
Value: smsIDDisp,
|
||
})
|
||
rawSMSSecret := strings.TrimSpace(common.SMSAccessKeySecret)
|
||
if rawSMSSecret == "" {
|
||
if v, ok := common.OptionMap["SMSAccessKeySecret"]; ok {
|
||
rawSMSSecret = strings.TrimSpace(common.Interface2String(v))
|
||
}
|
||
}
|
||
smsSecretDisp := ""
|
||
if rawSMSSecret != "" {
|
||
smsSecretDisp = common.MaskCredentialForAdminDisplay(rawSMSSecret)
|
||
}
|
||
options = append(options, &model.Option{
|
||
Key: "SMSAccessKeySecret",
|
||
Value: smsSecretDisp,
|
||
})
|
||
common.OptionMapRWMutex.Unlock()
|
||
options = append(options, &model.Option{
|
||
Key: "CompletionRatioMeta",
|
||
Value: buildCompletionRatioMetaValue(optionValues),
|
||
})
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "",
|
||
"data": options,
|
||
})
|
||
}
|
||
|
||
type OptionUpdateRequest struct {
|
||
Key string `json:"key"`
|
||
Value any `json:"value"`
|
||
}
|
||
|
||
func UpdateOption(c *gin.Context) {
|
||
var option OptionUpdateRequest
|
||
err := common.DecodeJson(c.Request.Body, &option)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"success": false,
|
||
"message": "无效的参数",
|
||
})
|
||
return
|
||
}
|
||
switch option.Value.(type) {
|
||
case bool:
|
||
option.Value = common.Interface2String(option.Value.(bool))
|
||
case float64:
|
||
option.Value = common.Interface2String(option.Value.(float64))
|
||
case int:
|
||
option.Value = common.Interface2String(option.Value.(int))
|
||
default:
|
||
option.Value = fmt.Sprintf("%v", option.Value)
|
||
}
|
||
valStr := strings.TrimSpace(option.Value.(string))
|
||
// 已审核供应商仅可更新自己模型范围内的倍率相关配置,不可修改其他全局设置。
|
||
if c.GetInt("role") < common.RoleAdminUser {
|
||
if _, ok := supplierEditableModelOptionKeys[option.Key]; !ok {
|
||
c.JSON(http.StatusForbidden, gin.H{
|
||
"success": false,
|
||
"message": "供应商仅可修改模型倍率相关配置",
|
||
})
|
||
return
|
||
}
|
||
ownedModels, err := collectSupplierOwnedModelNames(c.GetInt("id"))
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
common.OptionMapRWMutex.Lock()
|
||
currentValue := strings.TrimSpace(common.Interface2String(common.OptionMap[option.Key]))
|
||
common.OptionMapRWMutex.Unlock()
|
||
mergedValue, err := mergeModelJSONByOwnedModels(currentValue, valStr, ownedModels)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"success": false,
|
||
"message": "配置格式错误,仅支持 JSON 对象",
|
||
})
|
||
return
|
||
}
|
||
if err := model.UpdateOption(option.Key, mergedValue); err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "",
|
||
})
|
||
return
|
||
}
|
||
|
||
if option.Key == "YipayAppSecret" && strings.TrimSpace(operation_setting.YipayAppSecret) != "" {
|
||
if valStr == common.MaskCredentialForAdminDisplay(operation_setting.YipayAppSecret) {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "",
|
||
})
|
||
return
|
||
}
|
||
}
|
||
if option.Key == "oss_setting.access_key_id" && strings.TrimSpace(operation_setting.GetOssSetting().AccessKeyID) != "" {
|
||
if valStr == common.MaskCredentialForAdminDisplay(operation_setting.GetOssSetting().AccessKeyID) {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "",
|
||
})
|
||
return
|
||
}
|
||
}
|
||
if option.Key == "oss_setting.access_key_secret" && strings.TrimSpace(operation_setting.GetOssSetting().AccessKeySecret) != "" {
|
||
if valStr == common.MaskCredentialForAdminDisplay(operation_setting.GetOssSetting().AccessKeySecret) {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "",
|
||
})
|
||
return
|
||
}
|
||
}
|
||
if option.Key == "SMSAccessKeySecret" && strings.TrimSpace(common.SMSAccessKeySecret) != "" {
|
||
if valStr == common.MaskCredentialForAdminDisplay(common.SMSAccessKeySecret) {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "",
|
||
})
|
||
return
|
||
}
|
||
}
|
||
if option.Key == "SMSAccessKeyID" && strings.TrimSpace(common.SMSAccessKeyID) != "" {
|
||
if valStr == common.MaskCredentialForAdminDisplay(common.SMSAccessKeyID) {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "",
|
||
})
|
||
return
|
||
}
|
||
}
|
||
switch option.Key {
|
||
case "GitHubOAuthEnabled":
|
||
if option.Value == "true" && common.GitHubClientId == "" {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "无法启用 GitHub OAuth,请先填入 GitHub Client Id 以及 GitHub Client Secret!",
|
||
})
|
||
return
|
||
}
|
||
case "discord.enabled":
|
||
if option.Value == "true" && system_setting.GetDiscordSettings().ClientId == "" {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "无法启用 Discord OAuth,请先填入 Discord Client Id 以及 Discord Client Secret!",
|
||
})
|
||
return
|
||
}
|
||
case "oidc.enabled":
|
||
if option.Value == "true" && system_setting.GetOIDCSettings().ClientId == "" {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "无法启用 OIDC 登录,请先填入 OIDC Client Id 以及 OIDC Client Secret!",
|
||
})
|
||
return
|
||
}
|
||
case "LinuxDOOAuthEnabled":
|
||
if option.Value == "true" && common.LinuxDOClientId == "" {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "无法启用 LinuxDO OAuth,请先填入 LinuxDO Client Id 以及 LinuxDO Client Secret!",
|
||
})
|
||
return
|
||
}
|
||
case "EmailDomainRestrictionEnabled":
|
||
if option.Value == "true" && len(common.EmailDomainWhitelist) == 0 {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "无法启用邮箱域名限制,请先填入限制的邮箱域名!",
|
||
})
|
||
return
|
||
}
|
||
case "WeChatAuthEnabled":
|
||
if option.Value == "true" && common.WeChatServerAddress == "" {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "无法启用微信登录,请先填入微信登录相关配置信息!",
|
||
})
|
||
return
|
||
}
|
||
case "TurnstileCheckEnabled":
|
||
if option.Value == "true" && common.TurnstileSiteKey == "" {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "无法启用 Turnstile 校验,请先填入 Turnstile 校验相关配置信息!",
|
||
})
|
||
|
||
return
|
||
}
|
||
case "TelegramOAuthEnabled":
|
||
if option.Value == "true" && common.TelegramBotToken == "" {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "无法启用 Telegram OAuth,请先填入 Telegram Bot Token!",
|
||
})
|
||
return
|
||
}
|
||
case "GroupRatio":
|
||
err = ratio_setting.CheckGroupRatio(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "ImageRatio":
|
||
err = ratio_setting.UpdateImageRatioByJSONString(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "图片倍率设置失败: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "AudioRatio":
|
||
err = ratio_setting.UpdateAudioRatioByJSONString(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "音频倍率设置失败: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "AudioCompletionRatio":
|
||
err = ratio_setting.UpdateAudioCompletionRatioByJSONString(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "音频补全倍率设置失败: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "VideoRatio":
|
||
err = ratio_setting.UpdateVideoRatioByJSONString(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "视频倍率设置失败: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "VideoCompletionRatio":
|
||
err = ratio_setting.UpdateVideoCompletionRatioByJSONString(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "视频输出倍率设置失败: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "VideoPrice":
|
||
err = ratio_setting.UpdateVideoPriceByJSONString(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "视频按次价格设置失败: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "VideoPricingRules":
|
||
err = ratio_setting.UpdateVideoPricingRulesByJSONString(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "视频规则价格设置失败: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "ChannelVideoPricingRules":
|
||
err = ratio_setting.UpdateChannelVideoPricingRulesByJSONString(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "渠道视频规则价格设置失败: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "ImagePrice":
|
||
err = ratio_setting.UpdateImagePriceByJSONString(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "图片按张价格设置失败: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "ImagePricingRules":
|
||
err = ratio_setting.UpdateImagePricingRulesByJSONString(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "图片规则价格设置失败: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "ChannelImagePricingRules":
|
||
err = ratio_setting.UpdateChannelImagePricingRulesByJSONString(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "渠道图片规则价格设置失败: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "CreateCacheRatio":
|
||
err = ratio_setting.UpdateCreateCacheRatioByJSONString(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": "缓存创建倍率设置失败: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "ModelRequestRateLimitGroup":
|
||
err = setting.CheckModelRequestRateLimitGroup(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "RateLimitUserWhitelist":
|
||
err = setting.CheckRateLimitUserWhitelistJSON(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "AutomaticDisableStatusCodes":
|
||
_, err = operation_setting.ParseHTTPStatusCodeRanges(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "AutomaticRetryStatusCodes":
|
||
_, err = operation_setting.ParseHTTPStatusCodeRanges(option.Value.(string))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "console_setting.api_info":
|
||
err = console_setting.ValidateConsoleSettings(option.Value.(string), "ApiInfo")
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "console_setting.announcements":
|
||
err = console_setting.ValidateConsoleSettings(option.Value.(string), "Announcements")
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "console_setting.faq":
|
||
err = console_setting.ValidateConsoleSettings(option.Value.(string), "FAQ")
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
case "console_setting.uptime_kuma_groups":
|
||
err = console_setting.ValidateConsoleSettings(option.Value.(string), "UptimeKumaGroups")
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": false,
|
||
"message": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
}
|
||
// 捕获旧值,用于计算模型定价类选项的字段级变更
|
||
common.OptionMapRWMutex.Lock()
|
||
oldVal := strings.TrimSpace(common.Interface2String(common.OptionMap[option.Key]))
|
||
common.OptionMapRWMutex.Unlock()
|
||
|
||
err = model.UpdateOption(option.Key, option.Value.(string))
|
||
if err != nil {
|
||
common.ApiError(c, err)
|
||
return
|
||
}
|
||
|
||
// 对模型定价类选项,仅记录实际发生变化的模型条目
|
||
if modelPricingOptionKeys[option.Key] {
|
||
changes := computeModelPricingDiff(oldVal, valStr)
|
||
if len(changes) > 0 {
|
||
service.RecordUpdateWithDiff(c, "setting", 0, option.Key, "更新系统设置: "+option.Key, changes, "")
|
||
}
|
||
} else {
|
||
service.RecordUpdateOperation(c, "setting", 0, option.Key, "更新系统设置: "+option.Key, "")
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "",
|
||
})
|
||
}
|