- 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
301 lines
7.5 KiB
Go
301 lines
7.5 KiB
Go
package config
|
||
|
||
import (
|
||
"encoding/json"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
)
|
||
|
||
// MailServiceConfig 邮箱服务配置
|
||
type MailServiceConfig struct {
|
||
Name string `yaml:"name" json:"name"`
|
||
APIBase string `yaml:"api_base" json:"api_base"`
|
||
APIToken string `yaml:"api_token" json:"api_token"`
|
||
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"`
|
||
AddUserAPI string `yaml:"add_user_api,omitempty" json:"add_user_api,omitempty"`
|
||
}
|
||
|
||
// Config 应用配置 (实时从数据库读取)
|
||
type Config struct {
|
||
// 服务器配置 (启动时固定)
|
||
Port int `json:"port"`
|
||
CorsOrigin string `json:"cors_origin"`
|
||
|
||
// S2A 配置 (可实时更新)
|
||
S2AApiBase string `json:"s2a_api_base"`
|
||
S2AAdminKey string `json:"s2a_admin_key"`
|
||
|
||
// 入库配置 (可实时更新)
|
||
Concurrency int `json:"concurrency"`
|
||
Priority int `json:"priority"`
|
||
GroupIDs []int `json:"group_ids"`
|
||
ProxyID *int `json:"proxy_id"`
|
||
|
||
// 代理配置 (可实时更新)
|
||
ProxyEnabled bool `json:"proxy_enabled"`
|
||
DefaultProxy string `json:"default_proxy"`
|
||
TeamRegProxy string `json:"team_reg_proxy"` // Team 注册使用的代理
|
||
|
||
// 授权方式配置
|
||
AuthMethod string `json:"auth_method"` // "api" 或 "browser"
|
||
|
||
// 自动化配置
|
||
AutoPauseOnExpired bool `json:"auto_pause_on_expired"`
|
||
AccountsPath string `json:"accounts_path"`
|
||
|
||
// 站点配置
|
||
SiteName string `json:"site_name"`
|
||
|
||
// 母号降级配置
|
||
DemoteAfterUse bool `json:"demote_after_use"` // 母号使用后自动降级
|
||
|
||
// 邮箱服务
|
||
MailServices []MailServiceConfig `json:"mail_services"`
|
||
}
|
||
|
||
// GetProxy 获取代理地址(如果启用)
|
||
func (c *Config) GetProxy() string {
|
||
if c.ProxyEnabled && c.DefaultProxy != "" {
|
||
return c.DefaultProxy
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// Account 账号结构 (保持 JSON 格式用于账号文件)
|
||
type Account struct {
|
||
Email string `json:"email,omitempty"`
|
||
Account string `json:"account,omitempty"`
|
||
Password string `json:"password"`
|
||
Name string `json:"name,omitempty"`
|
||
AccessToken string `json:"access_token,omitempty"`
|
||
Token string `json:"token,omitempty"`
|
||
RefreshToken string `json:"refresh_token,omitempty"`
|
||
CreatedAt string `json:"created_at,omitempty"`
|
||
Pooled bool `json:"pooled,omitempty"`
|
||
PooledAt string `json:"pooled_at,omitempty"`
|
||
PoolID int `json:"pool_id,omitempty"`
|
||
Used bool `json:"used,omitempty"`
|
||
UsedAt string `json:"used_at,omitempty"`
|
||
}
|
||
|
||
// GetEmail 获取邮箱
|
||
func (a *Account) GetEmail() string {
|
||
if a.Email != "" {
|
||
return a.Email
|
||
}
|
||
return a.Account
|
||
}
|
||
|
||
// GetAccessToken 获取 Token
|
||
func (a *Account) GetAccessToken() string {
|
||
if a.AccessToken != "" {
|
||
return a.AccessToken
|
||
}
|
||
return a.Token
|
||
}
|
||
|
||
// PoolingConfig 入库任务配置
|
||
type PoolingConfig struct {
|
||
Concurrency int `json:"concurrency"`
|
||
SerialAuthorize bool `json:"serial_authorize"`
|
||
BrowserType string `json:"browser_type"`
|
||
Headless bool `json:"headless"`
|
||
Proxy string `json:"proxy"`
|
||
}
|
||
|
||
// 全局配置实例
|
||
var (
|
||
Global *Config
|
||
configMu sync.RWMutex
|
||
)
|
||
|
||
// ConfigDB 配置数据库接口
|
||
type ConfigDB interface {
|
||
GetConfig(key string) (string, error)
|
||
SetConfig(key, value string) error
|
||
GetAllConfig() (map[string]string, error)
|
||
}
|
||
|
||
var configDB ConfigDB
|
||
|
||
// SetConfigDB 设置配置数据库
|
||
func SetConfigDB(db ConfigDB) {
|
||
configDB = db
|
||
}
|
||
|
||
// InitFromDB 从数据库初始化配置
|
||
func InitFromDB() *Config {
|
||
configMu.Lock()
|
||
defer configMu.Unlock()
|
||
|
||
cfg := &Config{
|
||
Port: 8848,
|
||
CorsOrigin: "*",
|
||
Concurrency: 2,
|
||
Priority: 0,
|
||
}
|
||
|
||
if configDB == nil {
|
||
Global = cfg
|
||
return cfg
|
||
}
|
||
|
||
// 从数据库加载配置
|
||
if v, _ := configDB.GetConfig("s2a_api_base"); v != "" {
|
||
cfg.S2AApiBase = v
|
||
}
|
||
if v, _ := configDB.GetConfig("s2a_admin_key"); v != "" {
|
||
cfg.S2AAdminKey = v
|
||
}
|
||
if v, _ := configDB.GetConfig("concurrency"); v != "" {
|
||
if n, err := strconv.Atoi(v); err == nil {
|
||
cfg.Concurrency = n
|
||
}
|
||
}
|
||
if v, _ := configDB.GetConfig("priority"); v != "" {
|
||
if n, err := strconv.Atoi(v); err == nil {
|
||
cfg.Priority = n
|
||
}
|
||
}
|
||
if v, _ := configDB.GetConfig("group_ids"); v != "" {
|
||
var ids []int
|
||
for _, s := range strings.Split(v, ",") {
|
||
if n, err := strconv.Atoi(strings.TrimSpace(s)); err == nil {
|
||
ids = append(ids, n)
|
||
}
|
||
}
|
||
cfg.GroupIDs = ids
|
||
}
|
||
if v, _ := configDB.GetConfig("proxy_enabled"); v == "true" {
|
||
cfg.ProxyEnabled = true
|
||
}
|
||
if v, _ := configDB.GetConfig("default_proxy"); v != "" {
|
||
cfg.DefaultProxy = v
|
||
}
|
||
if v, _ := configDB.GetConfig("team_reg_proxy"); v != "" {
|
||
cfg.TeamRegProxy = v
|
||
}
|
||
if v, _ := configDB.GetConfig("mail_services"); v != "" {
|
||
var services []MailServiceConfig
|
||
if err := json.Unmarshal([]byte(v), &services); err == nil {
|
||
cfg.MailServices = services
|
||
}
|
||
}
|
||
if v, _ := configDB.GetConfig("site_name"); v != "" {
|
||
cfg.SiteName = v
|
||
}
|
||
if v, _ := configDB.GetConfig("auth_method"); v != "" {
|
||
cfg.AuthMethod = v
|
||
} else {
|
||
cfg.AuthMethod = "browser" // 默认使用浏览器模式
|
||
}
|
||
if v, _ := configDB.GetConfig("demote_after_use"); v == "true" {
|
||
cfg.DemoteAfterUse = true
|
||
}
|
||
|
||
Global = cfg
|
||
return cfg
|
||
}
|
||
|
||
// SaveToDB 保存配置到数据库
|
||
func SaveToDB() error {
|
||
if configDB == nil || Global == nil {
|
||
return nil
|
||
}
|
||
|
||
configMu.RLock()
|
||
cfg := Global
|
||
configMu.RUnlock()
|
||
|
||
configDB.SetConfig("s2a_api_base", cfg.S2AApiBase)
|
||
configDB.SetConfig("s2a_admin_key", cfg.S2AAdminKey)
|
||
configDB.SetConfig("concurrency", strconv.Itoa(cfg.Concurrency))
|
||
configDB.SetConfig("priority", strconv.Itoa(cfg.Priority))
|
||
|
||
if len(cfg.GroupIDs) > 0 {
|
||
var ids []string
|
||
for _, id := range cfg.GroupIDs {
|
||
ids = append(ids, strconv.Itoa(id))
|
||
}
|
||
configDB.SetConfig("group_ids", strings.Join(ids, ","))
|
||
}
|
||
|
||
configDB.SetConfig("proxy_enabled", strconv.FormatBool(cfg.ProxyEnabled))
|
||
configDB.SetConfig("default_proxy", cfg.DefaultProxy)
|
||
configDB.SetConfig("team_reg_proxy", cfg.TeamRegProxy)
|
||
|
||
if len(cfg.MailServices) > 0 {
|
||
data, _ := json.Marshal(cfg.MailServices)
|
||
configDB.SetConfig("mail_services", string(data))
|
||
}
|
||
|
||
if cfg.SiteName != "" {
|
||
configDB.SetConfig("site_name", cfg.SiteName)
|
||
}
|
||
|
||
if cfg.AuthMethod != "" {
|
||
configDB.SetConfig("auth_method", cfg.AuthMethod)
|
||
}
|
||
|
||
configDB.SetConfig("demote_after_use", strconv.FormatBool(cfg.DemoteAfterUse))
|
||
|
||
return nil
|
||
}
|
||
|
||
// Update 更新配置 (实时生效)
|
||
func Update(cfg *Config) error {
|
||
configMu.Lock()
|
||
Global = cfg
|
||
configMu.Unlock()
|
||
return SaveToDB()
|
||
}
|
||
|
||
// Get 获取当前配置
|
||
func Get() *Config {
|
||
configMu.RLock()
|
||
defer configMu.RUnlock()
|
||
return Global
|
||
}
|
||
|
||
// FindPath 查找配置文件路径 (兼容)
|
||
func FindPath() string {
|
||
if envPath := os.Getenv("CONFIG_PATH"); envPath != "" {
|
||
return envPath
|
||
}
|
||
return "data/config.yaml"
|
||
}
|
||
|
||
// Load 加载配置 (兼容旧代码,现在直接从数据库加载)
|
||
func Load(path string) (*Config, error) {
|
||
return InitFromDB(), nil
|
||
}
|
||
|
||
// LoadAccounts 加载账号列表 (保持 JSON 格式)
|
||
func LoadAccounts(path string) ([]Account, error) {
|
||
data, err := os.ReadFile(path)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var accounts []Account
|
||
if err := json.Unmarshal(data, &accounts); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return accounts, nil
|
||
}
|
||
|
||
// SaveAccounts 保存账号列表 (保持 JSON 格式)
|
||
func SaveAccounts(path string, accounts []Account) error {
|
||
data, err := json.MarshalIndent(accounts, "", " ")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
return os.WriteFile(path, data, 0644)
|
||
}
|