feat: Implement core backend API server with configuration, S2A proxy, and mail service APIs, and introduce EmailConfig frontend page.
This commit is contained in:
@@ -344,16 +344,19 @@ func handleMailServices(w http.ResponseWriter, r *http.Request) {
|
|||||||
switch r.Method {
|
switch r.Method {
|
||||||
case "GET":
|
case "GET":
|
||||||
services := mail.GetServices()
|
services := mail.GetServices()
|
||||||
safeServices := make([]map[string]interface{}, len(services))
|
// 返回完整配置(包括 token)供前端加载
|
||||||
|
result := make([]map[string]interface{}, len(services))
|
||||||
for i, s := range services {
|
for i, s := range services {
|
||||||
safeServices[i] = map[string]interface{}{
|
result[i] = map[string]interface{}{
|
||||||
"name": s.Name,
|
"name": s.Name,
|
||||||
"api_base": s.APIBase,
|
"apiBase": s.APIBase,
|
||||||
"has_token": s.APIToken != "",
|
"apiToken": s.APIToken,
|
||||||
"domain": s.Domain,
|
"domain": s.Domain,
|
||||||
|
"emailPath": s.EmailPath,
|
||||||
|
"addUserApi": s.AddUserAPI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
api.Success(w, safeServices)
|
api.Success(w, result)
|
||||||
case "POST":
|
case "POST":
|
||||||
var req struct {
|
var req struct {
|
||||||
Services []struct {
|
Services []struct {
|
||||||
@@ -393,7 +396,7 @@ func handleMailServices(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新邮箱服务配置
|
// 更新邮箱服务配置(内存)
|
||||||
mail.Init(services)
|
mail.Init(services)
|
||||||
|
|
||||||
// 保存到全局配置
|
// 保存到全局配置
|
||||||
@@ -401,6 +404,14 @@ func handleMailServices(w http.ResponseWriter, r *http.Request) {
|
|||||||
config.Global.MailServices = services
|
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")
|
logger.Success(fmt.Sprintf("邮箱服务配置已保存: %d 个服务", len(services)), "", "mail")
|
||||||
for _, s := range services {
|
for _, s := range services {
|
||||||
logger.Info(fmt.Sprintf(" - %s (%s) @ %s", s.Name, s.Domain, s.APIBase), "", "mail")
|
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") {
|
// code 200 = 创建成功
|
||||||
logger.Success(fmt.Sprintf("邮箱服务测试成功: %s", req.Name), "", "mail")
|
// 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{}{
|
api.Success(w, map[string]interface{}{
|
||||||
"connected": true,
|
"connected": true,
|
||||||
"message": "邮箱服务连接成功",
|
"message": "邮箱服务连接成功",
|
||||||
|
|||||||
@@ -5,21 +5,36 @@ import { useConfig } from '../hooks/useConfig'
|
|||||||
import type { MailServiceConfig } from '../types'
|
import type { MailServiceConfig } from '../types'
|
||||||
|
|
||||||
export default function EmailConfig() {
|
export default function EmailConfig() {
|
||||||
const { config, updateEmailConfig } = useConfig()
|
const { updateEmailConfig } = useConfig()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const [saved, setSaved] = useState(false)
|
const [saved, setSaved] = useState(false)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [services, setServices] = useState<MailServiceConfig[]>(config.email?.services || [])
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [services, setServices] = useState<MailServiceConfig[]>([])
|
||||||
const [testingIndex, setTestingIndex] = useState<number | null>(null)
|
const [testingIndex, setTestingIndex] = useState<number | null>(null)
|
||||||
const [testResults, setTestResults] = useState<Record<number, { success: boolean; message: string }>>({})
|
const [testResults, setTestResults] = useState<Record<number, { success: boolean; message: string }>>({})
|
||||||
|
|
||||||
// 同步配置变化
|
// 从后端加载配置
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (config.email?.services) {
|
const loadConfig = async () => {
|
||||||
setServices(config.email.services)
|
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 })
|
||||||
}
|
}
|
||||||
}, [config.email?.services])
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('加载邮箱配置失败:', e)
|
||||||
|
}
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
loadConfig()
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (services.length === 0) {
|
if (services.length === 0) {
|
||||||
@@ -146,6 +161,14 @@ export default function EmailConfig() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Service Cards */}
|
{/* Service Cards */}
|
||||||
|
{loading ? (
|
||||||
|
<Card>
|
||||||
|
<CardContent className="flex items-center justify-center py-8">
|
||||||
|
<Loader2 className="h-6 w-6 animate-spin text-slate-400" />
|
||||||
|
<span className="ml-2 text-slate-500">加载中...</span>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{services.map((service, index) => (
|
{services.map((service, index) => (
|
||||||
<Card key={index}>
|
<Card key={index}>
|
||||||
@@ -246,6 +269,7 @@ export default function EmailConfig() {
|
|||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Help Info */}
|
{/* Help Info */}
|
||||||
<Card>
|
<Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user