Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
1fb29d59
Commit
1fb29d59
authored
Mar 21, 2026
by
Eilen6316
Browse files
fix(settings): prevent SMTP config overwrite and stabilize test after refresh
parent
a225a241
Changes
3
Hide whitespace changes
Inline
Side-by-side
backend/internal/handler/admin/setting_handler.go
View file @
1fb29d59
...
...
@@ -231,11 +231,27 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
if
req
.
DefaultBalance
<
0
{
req
.
DefaultBalance
=
0
}
req
.
SMTPHost
=
strings
.
TrimSpace
(
req
.
SMTPHost
)
req
.
SMTPUsername
=
strings
.
TrimSpace
(
req
.
SMTPUsername
)
req
.
SMTPPassword
=
strings
.
TrimSpace
(
req
.
SMTPPassword
)
req
.
SMTPFrom
=
strings
.
TrimSpace
(
req
.
SMTPFrom
)
req
.
SMTPFromName
=
strings
.
TrimSpace
(
req
.
SMTPFromName
)
if
req
.
SMTPPort
<=
0
{
req
.
SMTPPort
=
587
}
req
.
DefaultSubscriptions
=
normalizeDefaultSubscriptions
(
req
.
DefaultSubscriptions
)
// SMTP 配置保护:如果请求中 smtp_host 为空但数据库中已有配置,则保留已有 SMTP 配置
// 防止前端加载设置失败时空表单覆盖已保存的 SMTP 配置
if
req
.
SMTPHost
==
""
&&
previousSettings
.
SMTPHost
!=
""
{
req
.
SMTPHost
=
previousSettings
.
SMTPHost
req
.
SMTPPort
=
previousSettings
.
SMTPPort
req
.
SMTPUsername
=
previousSettings
.
SMTPUsername
req
.
SMTPFrom
=
previousSettings
.
SMTPFrom
req
.
SMTPFromName
=
previousSettings
.
SMTPFromName
req
.
SMTPUseTLS
=
previousSettings
.
SMTPUseTLS
}
// Turnstile 参数验证
if
req
.
TurnstileEnabled
{
// 检查必填字段
...
...
@@ -828,7 +844,7 @@ func equalDefaultSubscriptions(a, b []service.DefaultSubscriptionSetting) bool {
// TestSMTPRequest 测试SMTP连接请求
type
TestSMTPRequest
struct
{
SMTPHost
string
`json:"smtp_host"
binding:"required"
`
SMTPHost
string
`json:"smtp_host"`
SMTPPort
int
`json:"smtp_port"`
SMTPUsername
string
`json:"smtp_username"`
SMTPPassword
string
`json:"smtp_password"`
...
...
@@ -844,18 +860,35 @@ func (h *SettingHandler) TestSMTPConnection(c *gin.Context) {
return
}
if
req
.
SMTPPort
<=
0
{
req
.
SMTPPort
=
587
req
.
SMTPHost
=
strings
.
TrimSpace
(
req
.
SMTPHost
)
req
.
SMTPUsername
=
strings
.
TrimSpace
(
req
.
SMTPUsername
)
var
savedConfig
*
service
.
SMTPConfig
if
cfg
,
err
:=
h
.
emailService
.
GetSMTPConfig
(
c
.
Request
.
Context
());
err
==
nil
&&
cfg
!=
nil
{
savedConfig
=
cfg
}
// 如果未提供密码,从数据库获取已保存的密码
password
:=
req
.
SMTPPassword
if
password
==
""
{
savedConfig
,
err
:=
h
.
emailService
.
GetSMTPConfig
(
c
.
Request
.
Context
())
if
err
==
nil
&&
savedConfig
!=
nil
{
password
=
savedConfig
.
Password
if
req
.
SMTPHost
==
""
&&
savedConfig
!=
nil
{
req
.
SMTPHost
=
savedConfig
.
Host
}
if
req
.
SMTPPort
<=
0
{
if
savedConfig
!=
nil
&&
savedConfig
.
Port
>
0
{
req
.
SMTPPort
=
savedConfig
.
Port
}
else
{
req
.
SMTPPort
=
587
}
}
if
req
.
SMTPUsername
==
""
&&
savedConfig
!=
nil
{
req
.
SMTPUsername
=
savedConfig
.
Username
}
password
:=
strings
.
TrimSpace
(
req
.
SMTPPassword
)
if
password
==
""
&&
savedConfig
!=
nil
{
password
=
savedConfig
.
Password
}
if
req
.
SMTPHost
==
""
{
response
.
BadRequest
(
c
,
"SMTP host is required"
)
return
}
config
:=
&
service
.
SMTPConfig
{
Host
:
req
.
SMTPHost
,
...
...
@@ -877,7 +910,7 @@ func (h *SettingHandler) TestSMTPConnection(c *gin.Context) {
// SendTestEmailRequest 发送测试邮件请求
type
SendTestEmailRequest
struct
{
Email
string
`json:"email" binding:"required,email"`
SMTPHost
string
`json:"smtp_host"
binding:"required"
`
SMTPHost
string
`json:"smtp_host"`
SMTPPort
int
`json:"smtp_port"`
SMTPUsername
string
`json:"smtp_username"`
SMTPPassword
string
`json:"smtp_password"`
...
...
@@ -895,18 +928,43 @@ func (h *SettingHandler) SendTestEmail(c *gin.Context) {
return
}
if
req
.
SMTPPort
<=
0
{
req
.
SMTPPort
=
587
req
.
SMTPHost
=
strings
.
TrimSpace
(
req
.
SMTPHost
)
req
.
SMTPUsername
=
strings
.
TrimSpace
(
req
.
SMTPUsername
)
req
.
SMTPFrom
=
strings
.
TrimSpace
(
req
.
SMTPFrom
)
req
.
SMTPFromName
=
strings
.
TrimSpace
(
req
.
SMTPFromName
)
var
savedConfig
*
service
.
SMTPConfig
if
cfg
,
err
:=
h
.
emailService
.
GetSMTPConfig
(
c
.
Request
.
Context
());
err
==
nil
&&
cfg
!=
nil
{
savedConfig
=
cfg
}
// 如果未提供密码,从数据库获取已保存的密码
password
:=
req
.
SMTPPassword
if
password
==
""
{
savedConfig
,
err
:=
h
.
emailService
.
GetSMTPConfig
(
c
.
Request
.
Context
())
if
err
==
nil
&&
savedConfig
!=
nil
{
password
=
savedConfig
.
Password
if
req
.
SMTPHost
==
""
&&
savedConfig
!=
nil
{
req
.
SMTPHost
=
savedConfig
.
Host
}
if
req
.
SMTPPort
<=
0
{
if
savedConfig
!=
nil
&&
savedConfig
.
Port
>
0
{
req
.
SMTPPort
=
savedConfig
.
Port
}
else
{
req
.
SMTPPort
=
587
}
}
if
req
.
SMTPUsername
==
""
&&
savedConfig
!=
nil
{
req
.
SMTPUsername
=
savedConfig
.
Username
}
password
:=
strings
.
TrimSpace
(
req
.
SMTPPassword
)
if
password
==
""
&&
savedConfig
!=
nil
{
password
=
savedConfig
.
Password
}
if
req
.
SMTPFrom
==
""
&&
savedConfig
!=
nil
{
req
.
SMTPFrom
=
savedConfig
.
From
}
if
req
.
SMTPFromName
==
""
&&
savedConfig
!=
nil
{
req
.
SMTPFromName
=
savedConfig
.
FromName
}
if
req
.
SMTPHost
==
""
{
response
.
BadRequest
(
c
,
"SMTP host is required"
)
return
}
config
:=
&
service
.
SMTPConfig
{
Host
:
req
.
SMTPHost
,
...
...
backend/internal/service/email_service.go
View file @
1fb29d59
...
...
@@ -12,6 +12,7 @@ import (
"net/smtp"
"net/url"
"strconv"
"strings"
"time"
infraerrors
"github.com/Wei-Shaw/sub2api/internal/pkg/errors"
...
...
@@ -111,7 +112,7 @@ func (s *EmailService) GetSMTPConfig(ctx context.Context) (*SMTPConfig, error) {
return
nil
,
fmt
.
Errorf
(
"get smtp settings: %w"
,
err
)
}
host
:=
settings
[
SettingKeySMTPHost
]
host
:=
strings
.
TrimSpace
(
settings
[
SettingKeySMTPHost
]
)
if
host
==
""
{
return
nil
,
ErrEmailNotConfigured
}
...
...
@@ -128,10 +129,10 @@ func (s *EmailService) GetSMTPConfig(ctx context.Context) (*SMTPConfig, error) {
return
&
SMTPConfig
{
Host
:
host
,
Port
:
port
,
Username
:
settings
[
SettingKeySMTPUsername
],
Password
:
settings
[
SettingKeySMTPPassword
],
From
:
settings
[
SettingKeySMTPFrom
],
FromName
:
settings
[
SettingKeySMTPFromName
],
Username
:
strings
.
TrimSpace
(
settings
[
SettingKeySMTPUsername
]
)
,
Password
:
strings
.
TrimSpace
(
settings
[
SettingKeySMTPPassword
]
)
,
From
:
strings
.
TrimSpace
(
settings
[
SettingKeySMTPFrom
]
)
,
FromName
:
strings
.
TrimSpace
(
settings
[
SettingKeySMTPFromName
]
)
,
UseTLS
:
useTLS
,
},
nil
}
...
...
frontend/src/views/admin/SettingsView.vue
View file @
1fb29d59
...
...
@@ -1580,7 +1580,7 @@
<
button
type
=
"
button
"
@
click
=
"
testSmtpConnection
"
:
disabled
=
"
testingSmtp
"
:
disabled
=
"
testingSmtp
|| loadFailed
"
class
=
"
btn btn-secondary btn-sm
"
>
<
svg
v
-
if
=
"
testingSmtp
"
class
=
"
h-4 w-4 animate-spin
"
fill
=
"
none
"
viewBox
=
"
0 0 24 24
"
>
...
...
@@ -1650,6 +1650,11 @@
v
-
model
=
"
form.smtp_password
"
type
=
"
password
"
class
=
"
input
"
autocomplete
=
"
new-password
"
autocapitalize
=
"
off
"
spellcheck
=
"
false
"
@
keydown
=
"
smtpPasswordManuallyEdited = true
"
@
paste
=
"
smtpPasswordManuallyEdited = true
"
:
placeholder
=
"
form.smtp_password_configured
? t('admin.settings.smtp.passwordConfiguredPlaceholder')
...
...
@@ -1732,7 +1737,7 @@
<
button
type
=
"
button
"
@
click
=
"
sendTestEmail
"
:
disabled
=
"
sendingTestEmail || !testEmailAddress
"
:
disabled
=
"
sendingTestEmail || !testEmailAddress
|| loadFailed
"
class
=
"
btn btn-secondary
"
>
<
svg
...
...
@@ -1778,7 +1783,7 @@
<!--
Save
Button
-->
<
div
v
-
show
=
"
activeTab !== 'backup' && activeTab !== 'data'
"
class
=
"
flex justify-end
"
>
<
button
type
=
"
submit
"
:
disabled
=
"
saving
"
class
=
"
btn btn-primary
"
>
<
button
type
=
"
submit
"
:
disabled
=
"
saving
|| loadFailed
"
class
=
"
btn btn-primary
"
>
<
svg
v
-
if
=
"
saving
"
class
=
"
h-4 w-4 animate-spin
"
fill
=
"
none
"
viewBox
=
"
0 0 24 24
"
>
<
circle
class
=
"
opacity-25
"
...
...
@@ -1849,9 +1854,11 @@ const settingsTabs = [
const
{
copyToClipboard
}
=
useClipboard
()
const
loading
=
ref
(
true
)
const
loadFailed
=
ref
(
false
)
const
saving
=
ref
(
false
)
const
testingSmtp
=
ref
(
false
)
const
sendingTestEmail
=
ref
(
false
)
const
smtpPasswordManuallyEdited
=
ref
(
false
)
const
testEmailAddress
=
ref
(
''
)
const
registrationEmailSuffixWhitelistTags
=
ref
<
string
[]
>
([])
const
registrationEmailSuffixWhitelistDraft
=
ref
(
''
)
...
...
@@ -2116,6 +2123,7 @@ function moveMenuItem(index: number, direction: -1 | 1) {
async
function
loadSettings
()
{
loading
.
value
=
true
loadFailed
.
value
=
false
try
{
const
settings
=
await
adminAPI
.
settings
.
getSettings
()
Object
.
assign
(
form
,
settings
)
...
...
@@ -2133,9 +2141,11 @@ async function loadSettings() {
)
registrationEmailSuffixWhitelistDraft
.
value
=
''
form
.
smtp_password
=
''
smtpPasswordManuallyEdited
.
value
=
false
form
.
turnstile_secret_key
=
''
form
.
linuxdo_connect_client_secret
=
''
}
catch
(
error
:
any
)
{
loadFailed
.
value
=
true
appStore
.
showError
(
t
(
'
admin.settings.failedToLoad
'
)
+
'
:
'
+
(
error
.
message
||
t
(
'
common.unknownError
'
))
)
...
...
@@ -2257,6 +2267,7 @@ async function saveSettings() {
)
registrationEmailSuffixWhitelistDraft
.
value
=
''
form
.
smtp_password
=
''
smtpPasswordManuallyEdited
.
value
=
false
form
.
turnstile_secret_key
=
''
form
.
linuxdo_connect_client_secret
=
''
// Refresh cached settings so sidebar/header update immediately
...
...
@@ -2275,11 +2286,12 @@ async function saveSettings() {
async
function
testSmtpConnection
()
{
testingSmtp
.
value
=
true
try
{
const
smtpPasswordForTest
=
smtpPasswordManuallyEdited
.
value
?
form
.
smtp_password
:
''
const
result
=
await
adminAPI
.
settings
.
testSmtpConnection
({
smtp_host
:
form
.
smtp_host
,
smtp_port
:
form
.
smtp_port
,
smtp_username
:
form
.
smtp_username
,
smtp_password
:
form
.
smtp
_p
assword
,
smtp_password
:
smtp
P
assword
ForTest
,
smtp_use_tls
:
form
.
smtp_use_tls
}
)
// API returns
{
message
:
"
...
"
}
on
success
,
errors
are
thrown
as
exceptions
...
...
@@ -2301,12 +2313,13 @@ async function sendTestEmail() {
sendingTestEmail
.
value
=
true
try
{
const
smtpPasswordForSend
=
smtpPasswordManuallyEdited
.
value
?
form
.
smtp_password
:
''
const
result
=
await
adminAPI
.
settings
.
sendTestEmail
({
email
:
testEmailAddress
.
value
,
smtp_host
:
form
.
smtp_host
,
smtp_port
:
form
.
smtp_port
,
smtp_username
:
form
.
smtp_username
,
smtp_password
:
form
.
smtp
_p
assword
,
smtp_password
:
smtp
P
assword
ForSend
,
smtp_from_email
:
form
.
smtp_from_email
,
smtp_from_name
:
form
.
smtp_from_name
,
smtp_use_tls
:
form
.
smtp_use_tls
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment