fix(settings): preserve turnstile and omitted config fields

This commit is contained in:
Sora39831 2026-04-04 14:45:31 +08:00
parent 37c184aa45
commit 4a5d8aa702
4 changed files with 106 additions and 5 deletions

View file

@ -77,6 +77,14 @@ class AllSetting {
this.ldapDefaultExpiryDays = 0;
this.ldapDefaultLimitIP = 0;
this.dbType = "sqlite";
this.dbHost = "127.0.0.1";
this.dbPort = "3306";
this.dbUser = "";
this.dbName = "3xui";
this.turnstileSiteKey = "";
if (data == null) {
return
}
@ -86,4 +94,4 @@ class AllSetting {
equals(other) {
return ObjectUtil.equals(this, other);
}
}
}

View file

@ -1,7 +1,9 @@
package controller
import (
"encoding/json"
"errors"
"io"
"time"
"github.com/mhsanaei/3x-ui/v2/util/crypto"
@ -66,15 +68,47 @@ func (a *SettingController) getDefaultSettings(c *gin.Context) {
jsonObj(c, result, nil)
}
func bindSettingUpdate(c *gin.Context) (*entity.AllSetting, map[string]struct{}, error) {
allSetting := &entity.AllSetting{}
presentKeys := map[string]struct{}{}
if c.ContentType() == "application/json" {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
return nil, nil, err
}
if err := json.Unmarshal(body, allSetting); err != nil {
return nil, nil, err
}
var raw map[string]json.RawMessage
if err := json.Unmarshal(body, &raw); err != nil {
return nil, nil, err
}
for key := range raw {
presentKeys[key] = struct{}{}
}
return allSetting, presentKeys, nil
}
if err := c.ShouldBind(allSetting); err != nil {
return nil, nil, err
}
if err := c.Request.ParseForm(); err == nil {
for key := range c.Request.PostForm {
presentKeys[key] = struct{}{}
}
}
return allSetting, presentKeys, nil
}
// updateSetting updates all settings with the provided data.
func (a *SettingController) updateSetting(c *gin.Context) {
allSetting := &entity.AllSetting{}
err := c.ShouldBind(allSetting)
allSetting, presentKeys, err := bindSettingUpdate(c)
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
return
}
err = a.settingService.UpdateAllSetting(allSetting)
err = a.settingService.UpdateAllSetting(allSetting, presentKeys)
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
}

View file

@ -977,7 +977,7 @@ func (s *SettingService) SetTurnstileSecretKey(value string) error {
return s.setString("turnstileSecretKey", value)
}
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting, presentKeys map[string]struct{}) error {
if err := allSetting.CheckValid(); err != nil {
return err
}
@ -995,6 +995,11 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
if key == "-" || key == "" {
continue
}
if presentKeys != nil {
if _, ok := presentKeys[key]; !ok {
continue
}
}
fieldV := v.FieldByName(field.Name)
settings[key] = fmt.Sprint(fieldV.Interface())
}

View file

@ -295,3 +295,57 @@ func TestRoundTripNestedFormat(t *testing.T) {
t.Error("expected 'tgBot' group in nested JSON")
}
}
func TestUpdateAllSettingPreservesOmittedFields(t *testing.T) {
setupTestSettings(t)
svc := &SettingService{}
if err := svc.setString("turnstileSiteKey", "site-key-123"); err != nil {
t.Fatalf("setString turnstileSiteKey error: %v", err)
}
if err := svc.setString("dbType", "mariadb"); err != nil {
t.Fatalf("setString dbType error: %v", err)
}
if err := svc.setString("dbHost", "10.0.0.8"); err != nil {
t.Fatalf("setString dbHost error: %v", err)
}
allSetting, err := svc.GetAllSetting()
if err != nil {
t.Fatalf("GetAllSetting error: %v", err)
}
allSetting.WebPort = 9443
presentKeys := map[string]struct{}{}
for _, key := range []string{
"webListen", "webDomain", "webPort", "webCertFile", "webKeyFile", "webBasePath", "sessionMaxAge",
"pageSize", "expireDiff", "trafficDiff", "remarkModel", "datepicker",
"tgBotEnable", "tgBotToken", "tgBotProxy", "tgBotAPIServer", "tgBotChatId", "tgRunTime", "tgBotBackup", "tgBotLoginNotify", "tgCpu", "tgLang",
"timeLocation", "twoFactorEnable", "twoFactorToken",
"subEnable", "subJsonEnable", "subTitle", "subSupportUrl", "subProfileUrl", "subAnnounce", "subEnableRouting", "subRoutingRules", "subListen", "subPort", "subPath", "subDomain", "subCertFile", "subKeyFile", "subUpdates", "externalTrafficInformEnable", "externalTrafficInformURI", "subEncrypt", "subShowInfo", "subURI", "subJsonPath", "subJsonURI", "subJsonFragment", "subJsonNoises", "subJsonMux", "subJsonRules",
"ldapEnable", "ldapHost", "ldapPort", "ldapUseTLS", "ldapBindDN", "ldapPassword", "ldapBaseDN", "ldapUserFilter", "ldapUserAttr", "ldapVlessField", "ldapSyncCron", "ldapFlagField", "ldapTruthyValues", "ldapInvertFlag", "ldapInboundTags", "ldapAutoCreate", "ldapAutoDelete", "ldapDefaultTotalGB", "ldapDefaultExpiryDays", "ldapDefaultLimitIP",
} {
presentKeys[key] = struct{}{}
}
if err := svc.UpdateAllSetting(allSetting, presentKeys); err != nil {
t.Fatalf("UpdateAllSetting error: %v", err)
}
settings, err := loadSettings()
if err != nil {
t.Fatalf("loadSettings error: %v", err)
}
if got := settings["turnstileSiteKey"]; got != "site-key-123" {
t.Fatalf("expected turnstileSiteKey to be preserved, got %q", got)
}
if got := settings["dbType"]; got != "mariadb" {
t.Fatalf("expected dbType to be preserved, got %q", got)
}
if got := settings["dbHost"]; got != "10.0.0.8" {
t.Fatalf("expected dbHost to be preserved, got %q", got)
}
if got := settings["webPort"]; got != "9443" {
t.Fatalf("expected webPort to be updated, got %q", got)
}
}