feat: Implement system configuration page for site settings and proxy management, and add team registration functionality.

This commit is contained in:
2026-02-01 07:50:28 +08:00
parent 247bfb336e
commit 995af8e19a
4 changed files with 135 additions and 67 deletions

View File

@@ -160,6 +160,7 @@ func startServer(cfg *config.Config) {
mux.HandleFunc("/api/team-reg/status", api.CORS(api.HandleTeamRegStatus))
mux.HandleFunc("/api/team-reg/logs", api.HandleTeamRegLogs) // SSE
mux.HandleFunc("/api/team-reg/import", api.CORS(api.HandleTeamRegImport))
mux.HandleFunc("/api/team-reg/clear-logs", api.CORS(api.HandleTeamRegClearLogs))
// 嵌入的前端静态文件
if web.IsEmbedded() {
@@ -216,21 +217,23 @@ func handleConfig(w http.ResponseWriter, r *http.Request) {
return
}
api.Success(w, map[string]interface{}{
"port": config.Global.Port,
"s2a_api_base": config.Global.S2AApiBase,
"s2a_admin_key": config.Global.S2AAdminKey,
"has_admin_key": config.Global.S2AAdminKey != "",
"concurrency": config.Global.Concurrency,
"priority": config.Global.Priority,
"group_ids": config.Global.GroupIDs,
"proxy_enabled": config.Global.ProxyEnabled,
"default_proxy": config.Global.DefaultProxy,
"team_reg_proxy": config.Global.TeamRegProxy,
"proxy_test_status": getProxyTestStatus(),
"proxy_test_ip": getProxyTestIP(),
"site_name": config.Global.SiteName,
"mail_services_count": len(config.Global.MailServices),
"mail_services": config.Global.MailServices,
"port": config.Global.Port,
"s2a_api_base": config.Global.S2AApiBase,
"s2a_admin_key": config.Global.S2AAdminKey,
"has_admin_key": config.Global.S2AAdminKey != "",
"concurrency": config.Global.Concurrency,
"priority": config.Global.Priority,
"group_ids": config.Global.GroupIDs,
"proxy_enabled": config.Global.ProxyEnabled,
"default_proxy": config.Global.DefaultProxy,
"team_reg_proxy": config.Global.TeamRegProxy,
"proxy_test_status": getProxyTestStatus(),
"proxy_test_ip": getProxyTestIP(),
"team_reg_proxy_test_status": getTeamRegProxyTestStatus(),
"team_reg_proxy_test_ip": getTeamRegProxyTestIP(),
"site_name": config.Global.SiteName,
"mail_services_count": len(config.Global.MailServices),
"mail_services": config.Global.MailServices,
})
case http.MethodPut:
@@ -1007,7 +1010,8 @@ func handleProxyTest(w http.ResponseWriter, r *http.Request) {
}
var req struct {
ProxyURL string `json:"proxy_url"`
ProxyURL string `json:"proxy_url"`
ProxyType string `json:"proxy_type"` // "default" 或 "team_reg"
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
api.Error(w, http.StatusBadRequest, "请求格式错误")
@@ -1020,6 +1024,14 @@ func handleProxyTest(w http.ResponseWriter, r *http.Request) {
return
}
// 确定保存状态的 key 前缀
statusKey := "proxy_test_status"
ipKey := "proxy_test_ip"
if req.ProxyType == "team_reg" {
statusKey = "team_reg_proxy_test_status"
ipKey = "team_reg_proxy_test_ip"
}
logger.Info(fmt.Sprintf("测试代理连接: %s", proxyURL), "", "proxy")
// 解析代理 URL
@@ -1052,8 +1064,8 @@ func handleProxyTest(w http.ResponseWriter, r *http.Request) {
logger.Error(fmt.Sprintf("代理测试失败: HTTP %d", resp.StatusCode), "", "proxy")
// 保存失败状态
if database.Instance != nil {
database.Instance.SetConfig("proxy_test_status", "error")
database.Instance.SetConfig("proxy_test_ip", "")
database.Instance.SetConfig(statusKey, "error")
database.Instance.SetConfig(ipKey, "")
}
api.Error(w, http.StatusBadGateway, fmt.Sprintf("代理测试失败: HTTP %d", resp.StatusCode))
return
@@ -1071,8 +1083,8 @@ func handleProxyTest(w http.ResponseWriter, r *http.Request) {
// 保存成功状态到数据库
if database.Instance != nil {
database.Instance.SetConfig("proxy_test_status", "success")
database.Instance.SetConfig("proxy_test_ip", ipResp.Origin)
database.Instance.SetConfig(statusKey, "success")
database.Instance.SetConfig(ipKey, ipResp.Origin)
}
api.Success(w, map[string]interface{}{
@@ -1102,6 +1114,26 @@ func getProxyTestIP() string {
return val
}
// getTeamRegProxyTestStatus 获取注册代理测试状态
func getTeamRegProxyTestStatus() string {
if database.Instance == nil {
return "unknown"
}
if val, _ := database.Instance.GetConfig("team_reg_proxy_test_status"); val != "" {
return val
}
return "unknown"
}
// getTeamRegProxyTestIP 获取注册代理测试出口IP
func getTeamRegProxyTestIP() string {
if database.Instance == nil {
return ""
}
val, _ := database.Instance.GetConfig("team_reg_proxy_test_ip")
return val
}
// parseProxyURL 解析代理 URL
func parseProxyURL(proxyURL string) (*url.URL, error) {
// 如果没有协议前缀,默认添加 http://

View File

@@ -375,26 +375,26 @@ func runTeamRegProcess(config TeamRegConfig) {
addTeamRegLog("[系统] 进程正常完成")
}
// 查找输出文件
outputFile := findLatestOutputFile(workDir)
if outputFile != "" {
teamRegState.mu.Lock()
teamRegState.OutputFile = outputFile
teamRegState.mu.Unlock()
addTeamRegLog(fmt.Sprintf("[系统] 输出文件: %s", filepath.Base(outputFile)))
// 查找输出文件
outputFile := findLatestOutputFile(workDir)
if outputFile != "" {
teamRegState.mu.Lock()
teamRegState.OutputFile = outputFile
teamRegState.mu.Unlock()
addTeamRegLog(fmt.Sprintf("[系统] 输出文件: %s", filepath.Base(outputFile)))
// 自动导入
if config.AutoImport {
addTeamRegLog("[系统] 自动导入账号到数据库...")
tryAutoImport(outputFile, config)
}
// 自动导入
if config.AutoImport {
addTeamRegLog("[系统] 自动导入账号到数据库...")
tryAutoImport(outputFile, config)
}
// 发送回车退出程序(如果还在运行)
time.Sleep(500 * time.Millisecond)
signalTeamRegExit()
}
// 发送回车退出程序(如果还在运行)
time.Sleep(500 * time.Millisecond)
signalTeamRegExit()
}
// readOutput 读取进程输出
func readOutput(reader io.Reader, workDir string, config TeamRegConfig) {
scanner := bufio.NewScanner(reader)
@@ -405,26 +405,26 @@ func readOutput(reader io.Reader, workDir string, config TeamRegConfig) {
if trimmed != "" {
addTeamRegLog(trimmed)
// 检测输出文件名(例如:结果已保存到: accounts-2-20260201-071558.json
if strings.Contains(trimmed, "结果已保存到") || strings.Contains(trimmed, "accounts-") && strings.Contains(trimmed, ".json") {
// 尝试提取文件名
if idx := strings.Index(trimmed, "accounts-"); idx >= 0 {
endIdx := strings.Index(trimmed[idx:], ".json")
if endIdx > 0 {
fileName := trimmed[idx : idx+endIdx+5] // 包含 .json
// 构建完整路径
fullPath := filepath.Join(workDir, fileName)
teamRegState.mu.Lock()
teamRegState.OutputFile = fullPath
teamRegState.mu.Unlock()
if config.AutoImport {
go tryAutoImport(fullPath, config)
}
// 发送回车提示退出(有些程序会在完成后等待回车)
signalTeamRegExit()
// 检测输出文件名(例如:结果已保存到: accounts-2-20260201-071558.json
if strings.Contains(trimmed, "结果已保存到") || strings.Contains(trimmed, "accounts-") && strings.Contains(trimmed, ".json") {
// 尝试提取文件名
if idx := strings.Index(trimmed, "accounts-"); idx >= 0 {
endIdx := strings.Index(trimmed[idx:], ".json")
if endIdx > 0 {
fileName := trimmed[idx : idx+endIdx+5] // 包含 .json
// 构建完整路径
fullPath := filepath.Join(workDir, fileName)
teamRegState.mu.Lock()
teamRegState.OutputFile = fullPath
teamRegState.mu.Unlock()
if config.AutoImport {
go tryAutoImport(fullPath, config)
}
// 发送回车提示退出(有些程序会在完成后等待回车)
signalTeamRegExit()
}
}
}
// 检测 403 错误
if strings.Contains(trimmed, "403") {
@@ -573,6 +573,21 @@ func addTeamRegLog(log string) {
logger.Info(fmt.Sprintf("[TeamReg] %s", cleanLog), "", "team-reg")
}
// HandleTeamRegClearLogs POST /api/team-reg/clear-logs - 清除日志
func HandleTeamRegClearLogs(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
Error(w, http.StatusMethodNotAllowed, "仅支持 POST")
return
}
teamRegState.mu.Lock()
teamRegState.Logs = make([]string, 0)
teamRegState.mu.Unlock()
logger.Info("[TeamReg] 日志已清除", "", "team-reg")
Success(w, map[string]string{"message": "日志已清除"})
}
// findTeamRegExecutable 查找 team-reg 可执行文件
func findTeamRegExecutable() string {
// 可能的文件名