From e61430b60d09e898dbf4323b1da337ecc834dce8 Mon Sep 17 00:00:00 2001 From: kyx236 Date: Fri, 30 Jan 2026 16:03:07 +0800 Subject: [PATCH] feat: Implement core backend API server with configuration, S2A proxy, and mail service APIs, and introduce EmailConfig frontend page. --- backend/cmd/main.go | 38 +++-- frontend/src/pages/EmailConfig.tsx | 222 ++++++++++++++++------------- 2 files changed, 151 insertions(+), 109 deletions(-) diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 7e92838..17e7af1 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -344,16 +344,19 @@ func handleMailServices(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": services := mail.GetServices() - safeServices := make([]map[string]interface{}, len(services)) + // 返回完整配置(包括 token)供前端加载 + result := make([]map[string]interface{}, len(services)) for i, s := range services { - safeServices[i] = map[string]interface{}{ - "name": s.Name, - "api_base": s.APIBase, - "has_token": s.APIToken != "", - "domain": s.Domain, + result[i] = map[string]interface{}{ + "name": s.Name, + "apiBase": s.APIBase, + "apiToken": s.APIToken, + "domain": s.Domain, + "emailPath": s.EmailPath, + "addUserApi": s.AddUserAPI, } } - api.Success(w, safeServices) + api.Success(w, result) case "POST": var req struct { Services []struct { @@ -393,7 +396,7 @@ func handleMailServices(w http.ResponseWriter, r *http.Request) { }) } - // 更新邮箱服务配置 + // 更新邮箱服务配置(内存) mail.Init(services) // 保存到全局配置 @@ -401,6 +404,14 @@ func handleMailServices(w http.ResponseWriter, r *http.Request) { config.Global.MailServices = services } + // 持久化到数据库 + if database.Instance != nil { + jsonData, _ := json.Marshal(services) + if err := database.Instance.SetConfig("mail_services", string(jsonData)); err != nil { + logger.Error(fmt.Sprintf("保存邮箱配置到数据库失败: %v", err), "", "mail") + } + } + logger.Success(fmt.Sprintf("邮箱服务配置已保存: %d 个服务", len(services)), "", "mail") for _, s := range services { logger.Info(fmt.Sprintf(" - %s (%s) @ %s", s.Name, s.Domain, s.APIBase), "", "mail") @@ -483,8 +494,15 @@ func handleTestMailService(w http.ResponseWriter, r *http.Request) { } // 判断结果 - if result.Code == 200 || strings.Contains(result.Message, "exist") { - logger.Success(fmt.Sprintf("邮箱服务测试成功: %s", req.Name), "", "mail") + // code 200 = 创建成功 + // code 501 且消息包含"已存在" = 邮箱已存在,说明连接正常 + isSuccess := result.Code == 200 || + strings.Contains(result.Message, "exist") || + strings.Contains(result.Message, "已存在") || + (result.Code == 501 && strings.Contains(result.Message, "邮箱")) + + if isSuccess { + logger.Success(fmt.Sprintf("邮箱服务测试成功: %s (邮箱已存在或创建成功)", req.Name), "", "mail") api.Success(w, map[string]interface{}{ "connected": true, "message": "邮箱服务连接成功", diff --git a/frontend/src/pages/EmailConfig.tsx b/frontend/src/pages/EmailConfig.tsx index dc000dd..0efff70 100644 --- a/frontend/src/pages/EmailConfig.tsx +++ b/frontend/src/pages/EmailConfig.tsx @@ -5,21 +5,36 @@ import { useConfig } from '../hooks/useConfig' import type { MailServiceConfig } from '../types' export default function EmailConfig() { - const { config, updateEmailConfig } = useConfig() + const { updateEmailConfig } = useConfig() const toast = useToast() const [saved, setSaved] = useState(false) const [saving, setSaving] = useState(false) - const [services, setServices] = useState(config.email?.services || []) + const [loading, setLoading] = useState(true) + const [services, setServices] = useState([]) const [testingIndex, setTestingIndex] = useState(null) const [testResults, setTestResults] = useState>({}) - // 同步配置变化 + // 从后端加载配置 useEffect(() => { - if (config.email?.services) { - setServices(config.email.services) + const loadConfig = async () => { + try { + const res = await fetch('/api/mail/services') + if (res.ok) { + const data = await res.json() + if (data.code === 0 && Array.isArray(data.data)) { + setServices(data.data) + updateEmailConfig({ services: data.data }) + } + } + } catch (e) { + console.error('加载邮箱配置失败:', e) + } + setLoading(false) } - }, [config.email?.services]) + loadConfig() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) const handleSave = async () => { if (services.length === 0) { @@ -146,106 +161,115 @@ export default function EmailConfig() { {/* Service Cards */} -
- {services.map((service, index) => ( - - -
- - - {service.name || `服务 ${index + 1}`} - - (@{service.domain || '未设置域名'}) - - -
- {testResults[index] && ( - - {testResults[index].message} + {loading ? ( + + + + 加载中... + + + ) : ( +
+ {services.map((service, index) => ( + + +
+ + + {service.name || `服务 ${index + 1}`} + + (@{service.domain || '未设置域名'}) - )} - - + +
+ {testResults[index] && ( + + {testResults[index].message} + + )} + + +
+
+
+ +
+ handleUpdateService(index, { name: e.target.value })} + hint="用于识别不同的邮箱服务" + /> + handleUpdateService(index, { domain: e.target.value })} + hint="生成邮箱地址的域名后缀" + />
-
- - -
handleUpdateService(index, { name: e.target.value })} - hint="用于识别不同的邮箱服务" + label="API 地址" + placeholder="https://mail.example.com" + value={service.apiBase} + onChange={(e) => handleUpdateService(index, { apiBase: e.target.value })} + hint="邮箱服务 API 地址" /> handleUpdateService(index, { domain: e.target.value })} - hint="生成邮箱地址的域名后缀" + label="API Token" + type="password" + placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + value={service.apiToken} + onChange={(e) => handleUpdateService(index, { apiToken: e.target.value })} + hint="邮箱服务的 API 认证令牌" /> -
- handleUpdateService(index, { apiBase: e.target.value })} - hint="邮箱服务 API 地址" - /> - handleUpdateService(index, { apiToken: e.target.value })} - hint="邮箱服务的 API 认证令牌" - /> - {/* Advanced Settings (Collapsed by default) */} -
- - - 高级设置 - -
- handleUpdateService(index, { emailPath: e.target.value })} - hint="获取邮件列表的 API 路径" - /> - handleUpdateService(index, { addUserApi: e.target.value })} - hint="创建邮箱用户的 API 路径" - /> -
-
-
- - ))} -
+ {/* Advanced Settings (Collapsed by default) */} +
+ + + 高级设置 + +
+ handleUpdateService(index, { emailPath: e.target.value })} + hint="获取邮件列表的 API 路径" + /> + handleUpdateService(index, { addUserApi: e.target.value })} + hint="创建邮箱用户的 API 路径" + /> +
+
+ + + ))} +
+ )} {/* Help Info */}