diff --git a/backend/config/config.go b/backend/config/config.go index 7bb4079..afc2dda 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -155,7 +155,7 @@ func (l *logFormatter) Format(entry *log.Entry) ([]byte, error) { } } if entry.Caller != nil { - b.WriteString(fmt.Sprintf("[%s:%d]", entry.Caller.File, entry.Caller.Line)) + b.WriteString(fmt.Sprintf("[%s:%d]", runtimepath.CleanCallerPath(entry.Caller.File), entry.Caller.Line)) } b.WriteString(entry.Message) // 错误字段也是有效内容,不能在消息为空时被格式器丢弃。 diff --git a/backend/config/manager_v2.go b/backend/config/manager_v2.go index e669049..28ee8bd 100644 --- a/backend/config/manager_v2.go +++ b/backend/config/manager_v2.go @@ -883,6 +883,13 @@ func applySectionPayload(current RuntimeConfig, section Section, payload json.Ra return RuntimeConfig{}, ErrInvalidSecretChange } value.SMTPSectionConfig.Relay.Password = current.SMTP.Relay.Password + if smtpRelayConfigured(value.SMTPSectionConfig.Relay) && value.SMTPSectionConfig.Relay.Security == "" { + if ResolveMailSecurity(value.SMTPSectionConfig.Listener) == MailSecurityImplicitTLS { + value.SMTPSectionConfig.Relay.Security = MailSecurityImplicitTLS + } else { + value.SMTPSectionConfig.Relay.Security = MailSecuritySTARTTLS + } + } candidate.SMTP = value.SMTPSectionConfig candidate.Email.Policy.LoginVerificationPolicy = candidate.SMTP.Relay.LoginPolicy case SectionEmail: @@ -904,6 +911,13 @@ func applySectionPayload(current RuntimeConfig, section Section, payload json.Ra return RuntimeConfig{}, ErrInvalidSecretChange } value.MailSectionConfig.Relay.Password = current.SMTP.Relay.Password + if smtpRelayConfigured(value.Relay) && value.Relay.Security == "" { + if ResolveMailSecurity(value.Listener) == MailSecurityImplicitTLS { + value.Relay.Security = MailSecurityImplicitTLS + } else { + value.Relay.Security = MailSecuritySTARTTLS + } + } candidate.SMTP = SMTPSectionConfig{ Meta: value.Meta, Listener: value.Listener, diff --git a/backend/hooks/framework/framework.go b/backend/hooks/framework/framework.go index 156eac2..99ae370 100644 --- a/backend/hooks/framework/framework.go +++ b/backend/hooks/framework/framework.go @@ -1,6 +1,7 @@ package framework import ( + "git.pcux.cn/xuans/Smail/utils/runtimepath" "bytes" "crypto/subtle" "encoding/json" @@ -141,7 +142,7 @@ func (l *logFormatter) Format(entry *log.Entry) ([]byte, error) { b.WriteString(fmt.Sprintf("[%s]", ctx.GetValue(context.LogID))) } } - b.WriteString(fmt.Sprintf("[%s:%d]", entry.Caller.File, entry.Caller.Line)) + b.WriteString(fmt.Sprintf("[%s:%d]", runtimepath.CleanCallerPath(entry.Caller.File), entry.Caller.Line)) b.WriteString(entry.Message) b.WriteString("\n") diff --git a/backend/utils/runtimepath/layout.go b/backend/utils/runtimepath/layout.go index fb4123e..fee9125 100644 --- a/backend/utils/runtimepath/layout.go +++ b/backend/utils/runtimepath/layout.go @@ -270,3 +270,26 @@ func migrate(root string, rename func(string, string) error) ([]Move, error) { } return completed, nil } + +// CleanCallerPath 清洗调用者代码路径,去除本地盘符和宿主机绝对目录,只保留模块相对路径。 +func CleanCallerPath(path string) string { + if strings.TrimSpace(path) == "" { + return "" + } + normalized := strings.ReplaceAll(path, string(filepath.Separator), "/") + normalized = strings.ReplaceAll(normalized, "\\", "/") + for _, target := range []string{"backend/", "frontend/", "plugins/"} { + if index := strings.Index(normalized, target); index != -1 { + return normalized[index:] + } + } + marker := "smail/" + lower := strings.ToLower(normalized) + if index := strings.Index(lower, marker); index != -1 { + return normalized[index+len(marker):] + } + if len(normalized) >= 2 && normalized[1] == ':' { + return strings.TrimLeft(normalized[2:], "/") + } + return normalized +} diff --git a/frontend/src/views/components/ConfigSettings/ConfigForm/ConfigForm.js b/frontend/src/views/components/ConfigSettings/ConfigForm/ConfigForm.js index e296ba1..082ddc6 100644 --- a/frontend/src/views/components/ConfigSettings/ConfigForm/ConfigForm.js +++ b/frontend/src/views/components/ConfigSettings/ConfigForm/ConfigForm.js @@ -11,10 +11,10 @@ const mailSecurity = config => { const value = normalizedMailSecurity(config.listener?.security) return value || 'both' } -const relaySecurityForMail = config => mailSecurity(config) === 'implicit_tls' ? 'implicit_tls' : 'starttls' +export const relaySecurityForMail = config => mailSecurity(config) === 'implicit_tls' ? 'implicit_tls' : 'starttls' const startTLSMail = config => mailSecurity(config) === 'starttls' const implicitTLSMail = config => mailSecurity(config) === 'implicit_tls' -const relayConfigured = config => !!( +export const relayConfigured = config => !!( config.relay?.relayHost || config.relay?.username || config.relay?.password || config.relay?.from ) diff --git a/frontend/src/views/components/ConfigSettings/ConfigSettings.js b/frontend/src/views/components/ConfigSettings/ConfigSettings.js index c451de2..9b2bc24 100644 --- a/frontend/src/views/components/ConfigSettings/ConfigSettings.js +++ b/frontend/src/views/components/ConfigSettings/ConfigSettings.js @@ -3,7 +3,7 @@ import { ElMessage, ElMessageBox } from 'element-plus' import { http } from '@/utils/axios' import { useGlobalStatusStore } from '@/stores/useGlobalStatusStore' import { isRecentAuthenticationCancelled, requestWithRecentAuthentication } from '@/utils/recentAuthentication' -import { readFormConfig, updateFormConfig, validateFormConfig } from './ConfigForm/ConfigForm.js' +import { readFormConfig, updateFormConfig, validateFormConfig, relayConfigured, relaySecurityForMail } from './ConfigForm/ConfigForm.js' const sections = [ { value: 'base', label: '基础配置' }, @@ -493,6 +493,9 @@ export function usePage(pageKey = 'base') { delete payload.relay } } + if (payload.relay && relayConfigured(payload) && !payload.relay.security) { + payload.relay.security = relaySecurityForMail(payload) + } } if (apiSection() === 'plugins' && payload.plugins && typeof payload.plugins === 'object') { Object.values(payload.plugins).forEach(plugin => {