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:
@@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 := ¤tMailServices[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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 路径
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user