feat(mail): Add support for multiple domains per mail service

- Add `Domains` field to MailServiceConfig for managing additional domains under single API
- Implement `matchDomain` helper function for precise and subdomain matching logic
- Update `GetServiceByDomain` to check both primary domain and additional domains list
- Enhance EmailConfig UI to display domain count and allow comma-separated domain input
- Add domains field to mail service request/response structures in API handlers
- Update frontend types to include domains array in MailService interface
- Improve documentation with clarification on primary vs additional domains usage
- Allows single mail service API to manage multiple email domains for verification and operations
This commit is contained in:
2026-02-08 02:57:19 +08:00
parent 847574e89e
commit 2eb4a57639
5 changed files with 56 additions and 20 deletions

View File

@@ -739,6 +739,7 @@ func handleMailServices(w http.ResponseWriter, r *http.Request) {
"apiBase": s.APIBase, "apiBase": s.APIBase,
"apiToken": s.APIToken, "apiToken": s.APIToken,
"domain": s.Domain, "domain": s.Domain,
"domains": s.Domains,
"emailPath": s.EmailPath, "emailPath": s.EmailPath,
"addUserApi": s.AddUserAPI, "addUserApi": s.AddUserAPI,
} }
@@ -751,6 +752,7 @@ func handleMailServices(w http.ResponseWriter, r *http.Request) {
APIBase string `json:"apiBase"` APIBase string `json:"apiBase"`
APIToken string `json:"apiToken"` APIToken string `json:"apiToken"`
Domain string `json:"domain"` Domain string `json:"domain"`
Domains []string `json:"domains"`
EmailPath string `json:"emailPath"` EmailPath string `json:"emailPath"`
AddUserAPI string `json:"addUserApi"` AddUserAPI string `json:"addUserApi"`
} `json:"services"` } `json:"services"`
@@ -778,6 +780,7 @@ func handleMailServices(w http.ResponseWriter, r *http.Request) {
APIBase: s.APIBase, APIBase: s.APIBase,
APIToken: s.APIToken, APIToken: s.APIToken,
Domain: s.Domain, Domain: s.Domain,
Domains: s.Domains,
EmailPath: emailPath, EmailPath: emailPath,
AddUserAPI: addUserAPI, AddUserAPI: addUserAPI,
}) })

View File

@@ -14,6 +14,7 @@ type MailServiceConfig struct {
APIBase string `yaml:"api_base" json:"api_base"` APIBase string `yaml:"api_base" json:"api_base"`
APIToken string `yaml:"api_token" json:"api_token"` APIToken string `yaml:"api_token" json:"api_token"`
Domain string `yaml:"domain" json:"domain"` Domain string `yaml:"domain" json:"domain"`
Domains []string `yaml:"domains,omitempty" json:"domains,omitempty"` // 附加域名列表同一个API管理的多个域名
EmailPath string `yaml:"email_path,omitempty" json:"email_path,omitempty"` EmailPath string `yaml:"email_path,omitempty" json:"email_path,omitempty"`
AddUserAPI string `yaml:"add_user_api,omitempty" json:"add_user_api,omitempty"` AddUserAPI string `yaml:"add_user_api,omitempty" json:"add_user_api,omitempty"`
} }

View File

@@ -100,14 +100,31 @@ func GetRandomService() config.MailServiceConfig {
return currentMailServices[rand.Intn(len(currentMailServices))] return currentMailServices[rand.Intn(len(currentMailServices))]
} }
// matchDomain 检查邮箱域名是否匹配服务域名(精确匹配或子域名匹配)
func matchDomain(emailDomain, serviceDomain string) bool {
if serviceDomain == "" {
return false
}
return emailDomain == serviceDomain || strings.HasSuffix(emailDomain, "."+serviceDomain)
}
// GetServiceByDomain 根据域名获取对应的邮箱服务 // GetServiceByDomain 根据域名获取对应的邮箱服务
// 会同时检查 Domain主域名和 Domains附加域名列表
func GetServiceByDomain(domain string) *config.MailServiceConfig { func GetServiceByDomain(domain string) *config.MailServiceConfig {
mailServicesMutex.RLock() mailServicesMutex.RLock()
defer mailServicesMutex.RUnlock() defer mailServicesMutex.RUnlock()
for _, s := range currentMailServices { for i := range currentMailServices {
if s.Domain == domain || strings.HasSuffix(domain, "."+s.Domain) { s := &currentMailServices[i]
return &s // 检查主域名
if matchDomain(domain, s.Domain) {
return s
}
// 检查附加域名列表
for _, d := range s.Domains {
if matchDomain(domain, d) {
return s
}
} }
} }
return nil return nil

View File

@@ -78,6 +78,7 @@ export default function EmailConfig() {
apiBase: '', apiBase: '',
apiToken: '', apiToken: '',
domain: '', domain: '',
domains: [],
}, },
]) ])
} }
@@ -178,7 +179,7 @@ export default function EmailConfig() {
<Server className="h-5 w-5 text-purple-500" /> <Server className="h-5 w-5 text-purple-500" />
<span>{service.name || `服务 ${index + 1}`}</span> <span>{service.name || `服务 ${index + 1}`}</span>
<span className="text-sm font-normal text-slate-500"> <span className="text-sm font-normal text-slate-500">
(@{service.domain || '未设置域名'}) (@{service.domain || '未设置域名'}{service.domains && service.domains.length > 0 ? ` +${service.domains.length}域名` : ''})
</span> </span>
</CardTitle> </CardTitle>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -249,6 +250,19 @@ export default function EmailConfig() {
</summary> </summary>
<div className="mt-4 space-y-4 pl-5 border-l-2 border-slate-200 dark:border-slate-700"> <div className="mt-4 space-y-4 pl-5 border-l-2 border-slate-200 dark:border-slate-700">
<Input
label="附加域名"
placeholder="如loopkit.de5.net, kyyx.us.ci, 114514222.de"
value={(service.domains || []).join(', ')}
onChange={(e) => {
const domains = e.target.value
.split(/[,]/)
.map(d => d.trim())
.filter(d => d !== '')
handleUpdateService(index, { domains })
}}
hint="同一个 API 管理的其他域名(逗号分隔),可用于截取验证码等操作"
/>
<Input <Input
label="邮件列表 API 路径" label="邮件列表 API 路径"
placeholder="/api/public/emailList (默认)" placeholder="/api/public/emailList (默认)"
@@ -279,8 +293,8 @@ export default function EmailConfig() {
<ul className="list-disc list-inside space-y-1"> <ul className="list-disc list-inside space-y-1">
<li>使</li> <li>使</li>
<li> API Token </li> <li> API Token </li>
<li> xxx@esyteam.edu.kg</li> <li> xxx@esyteam.edu.kg</li>
<li></li> <li> API </li>
<li>使</li> <li>使</li>
</ul> </ul>
</div> </div>

View File

@@ -165,7 +165,8 @@ export interface MailServiceConfig {
name: string // 服务名称 name: string // 服务名称
apiBase: string // API 地址 apiBase: string // API 地址
apiToken: string // API Token apiToken: string // API Token
domain: string // 邮箱域名 domain: string // 邮箱域名(用于生成邮箱)
domains?: string[] // 附加域名列表同一API管理的多个域名
emailPath?: string // 获取邮件列表的 API 路径 emailPath?: string // 获取邮件列表的 API 路径
addUserApi?: string // 创建用户的 API 路径 addUserApi?: string // 创建用户的 API 路径
} }