package handler import ( "context" "database/sql" "encoding/json" "fmt" "io" "net/http" "net/url" "sort" "strconv" "strings" "sync" "time" "go-backend/internal/auth" "go-backend/internal/http/middleware" "go-backend/internal/http/response" "go-backend/internal/security" "go-backend/internal/store/sqlite" "go-backend/internal/ws" ) type Handler struct { repo *sqlite.Repository jwtSecret string wsServer *ws.Server jobsMu sync.Mutex jobsCancel context.CancelFunc jobsStarted bool jobsWG sync.WaitGroup } type loginRequest struct { Username string `json:"username"` Password string `json:"password"` CaptchaID string `json:"captchaId"` } type nameRequest struct { Name string `json:"name"` } type configSingleRequest struct { Name string `json:"name"` Value string `json:"value"` } type changePasswordRequest struct { NewUsername string `json:"newUsername"` CurrentPassword string `json:"currentPassword"` NewPassword string `json:"newPassword"` ConfirmPassword string `json:"confirmPassword"` } type flowItem struct { N string `json:"n"` U int64 `json:"u"` D int64 `json:"d"` } func New(repo *sqlite.Repository, jwtSecret string) *Handler { return &Handler{ repo: repo, jwtSecret: jwtSecret, wsServer: ws.NewServer(repo, jwtSecret), } } func (h *Handler) WebSocketHandler() http.Handler { return h.wsServer } func (h *Handler) Register(mux *http.ServeMux) { mux.HandleFunc("/api/v1/user/login", h.login) mux.HandleFunc("/api/v1/user/list", h.userList) mux.HandleFunc("/api/v1/user/create", h.userCreate) mux.HandleFunc("/api/v1/user/update", h.userUpdate) mux.HandleFunc("/api/v1/user/delete", h.userDelete) mux.HandleFunc("/api/v1/user/reset", h.userResetFlow) mux.HandleFunc("/api/v1/config/get", h.getConfigByName) mux.HandleFunc("/api/v1/config/list", h.getConfigs) mux.HandleFunc("/api/v1/config/update", h.updateConfigs) mux.HandleFunc("/api/v1/config/update-single", h.updateSingleConfig) mux.HandleFunc("/api/v1/captcha/check", h.checkCaptcha) mux.HandleFunc("/api/v1/user/package", h.userPackage) mux.HandleFunc("/api/v1/user/updatePassword", h.updatePassword) mux.HandleFunc("/api/v1/node/list", h.nodeList) mux.HandleFunc("/api/v1/node/create", h.nodeCreate) mux.HandleFunc("/api/v1/node/update", h.nodeUpdate) mux.HandleFunc("/api/v1/node/delete", h.nodeDelete) mux.HandleFunc("/api/v1/node/install", h.nodeInstall) mux.HandleFunc("/api/v1/node/update-order", h.nodeUpdateOrder) mux.HandleFunc("/api/v1/node/batch-delete", h.nodeBatchDelete) mux.HandleFunc("/api/v1/node/check-status", h.nodeCheckStatus) mux.HandleFunc("/api/v1/tunnel/list", h.tunnelList) mux.HandleFunc("/api/v1/tunnel/create", h.tunnelCreate) mux.HandleFunc("/api/v1/tunnel/get", h.tunnelGet) mux.HandleFunc("/api/v1/tunnel/update", h.tunnelUpdate) mux.HandleFunc("/api/v1/tunnel/delete", h.tunnelDelete) mux.HandleFunc("/api/v1/tunnel/diagnose", h.tunnelDiagnose) mux.HandleFunc("/api/v1/tunnel/update-order", h.tunnelUpdateOrder) mux.HandleFunc("/api/v1/tunnel/batch-delete", h.tunnelBatchDelete) mux.HandleFunc("/api/v1/tunnel/batch-redeploy", h.tunnelBatchRedeploy) mux.HandleFunc("/api/v1/tunnel/user/assign", h.userTunnelAssign) mux.HandleFunc("/api/v1/tunnel/user/batch-assign", h.userTunnelBatchAssign) mux.HandleFunc("/api/v1/tunnel/user/remove", h.userTunnelRemove) mux.HandleFunc("/api/v1/tunnel/user/update", h.userTunnelUpdate) mux.HandleFunc("/api/v1/forward/list", h.forwardList) mux.HandleFunc("/api/v1/forward/create", h.forwardCreate) mux.HandleFunc("/api/v1/forward/update", h.forwardUpdate) mux.HandleFunc("/api/v1/forward/delete", h.forwardDelete) mux.HandleFunc("/api/v1/forward/force-delete", h.forwardForceDelete) mux.HandleFunc("/api/v1/forward/pause", h.forwardPause) mux.HandleFunc("/api/v1/forward/resume", h.forwardResume) mux.HandleFunc("/api/v1/forward/diagnose", h.forwardDiagnose) mux.HandleFunc("/api/v1/forward/update-order", h.forwardUpdateOrder) mux.HandleFunc("/api/v1/forward/batch-delete", h.forwardBatchDelete) mux.HandleFunc("/api/v1/forward/batch-pause", h.forwardBatchPause) mux.HandleFunc("/api/v1/forward/batch-resume", h.forwardBatchResume) mux.HandleFunc("/api/v1/forward/batch-redeploy", h.forwardBatchRedeploy) mux.HandleFunc("/api/v1/forward/batch-change-tunnel", h.forwardBatchChangeTunnel) mux.HandleFunc("/api/v1/speed-limit/list", h.speedLimitList) mux.HandleFunc("/api/v1/speed-limit/create", h.speedLimitCreate) mux.HandleFunc("/api/v1/speed-limit/update", h.speedLimitUpdate) mux.HandleFunc("/api/v1/speed-limit/delete", h.speedLimitDelete) mux.HandleFunc("/api/v1/speed-limit/tunnels", h.tunnelList) mux.HandleFunc("/api/v1/tunnel/user/tunnel", h.userTunnelVisibleList) mux.HandleFunc("/api/v1/tunnel/user/list", h.userTunnelList) mux.HandleFunc("/api/v1/group/tunnel/list", h.tunnelGroupList) mux.HandleFunc("/api/v1/group/tunnel/create", h.groupTunnelCreate) mux.HandleFunc("/api/v1/group/tunnel/update", h.groupTunnelUpdate) mux.HandleFunc("/api/v1/group/tunnel/delete", h.groupTunnelDelete) mux.HandleFunc("/api/v1/group/tunnel/assign", h.groupTunnelAssign) mux.HandleFunc("/api/v1/group/user/list", h.userGroupList) mux.HandleFunc("/api/v1/group/user/create", h.groupUserCreate) mux.HandleFunc("/api/v1/group/user/update", h.groupUserUpdate) mux.HandleFunc("/api/v1/group/user/delete", h.groupUserDelete) mux.HandleFunc("/api/v1/group/user/assign", h.groupUserAssign) mux.HandleFunc("/api/v1/group/permission/list", h.groupPermissionList) mux.HandleFunc("/api/v1/group/permission/assign", h.groupPermissionAssign) mux.HandleFunc("/api/v1/group/permission/remove", h.groupPermissionRemove) mux.HandleFunc("/api/v1/open_api/sub_store", h.openAPISubStore) mux.HandleFunc("/api/v1/federation/share/list", h.federationShareList) mux.HandleFunc("/api/v1/federation/share/create", h.federationShareCreate) mux.HandleFunc("/api/v1/federation/share/delete", h.federationShareDelete) mux.HandleFunc("/api/v1/federation/connect", h.authPeer(h.federationConnect)) mux.HandleFunc("/api/v1/federation/tunnel/create", h.authPeer(h.federationTunnelCreate)) mux.HandleFunc("/api/v1/federation/node/import", h.nodeImport) mux.HandleFunc("/flow/test", h.flowTest) mux.HandleFunc("/flow/config", h.flowConfig) mux.HandleFunc("/flow/upload", h.flowUpload) mux.HandleFunc("/error", h.errorPage) } func (h *Handler) login(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { response.WriteJSON(w, response.ErrDefault("请求失败")) return } var req loginRequest if err := decodeJSON(r.Body, &req); err != nil { response.WriteJSON(w, response.Err(500, "请求参数错误")) return } if strings.TrimSpace(req.Username) == "" { response.WriteJSON(w, response.Err(500, "用户名不能为空")) return } if strings.TrimSpace(req.Password) == "" { response.WriteJSON(w, response.Err(500, "密码不能为空")) return } captchaEnabled, err := h.captchaEnabled() if err != nil { response.WriteJSON(w, response.Err(-2, err.Error())) return } if captchaEnabled { if strings.TrimSpace(req.CaptchaID) == "" { response.WriteJSON(w, response.ErrDefault("验证码校验失败")) return } secretCfg, err := h.repo.GetConfigByName("cloudflare_secret_key") if err != nil || secretCfg == nil || secretCfg.Value == "" { response.WriteJSON(w, response.ErrDefault("验证码配置错误:未配置Secret Key")) return } if !h.verifyCloudflareTurnstile(req.CaptchaID, secretCfg.Value) { response.WriteJSON(w, response.ErrDefault("验证码校验失败")) return } } user, err := h.repo.GetUserByUsername(req.Username) if err != nil { response.WriteJSON(w, response.Err(-2, err.Error())) return } if user == nil { response.WriteJSON(w, response.ErrDefault("账号或密码错误")) return } if user.Pwd != security.MD5(req.Password) { response.WriteJSON(w, response.ErrDefault("账号或密码错误")) return } if user.Status == 0 { response.WriteJSON(w, response.ErrDefault("账号被停用")) return } token, err := auth.GenerateToken(user.ID, user.User, user.RoleID, h.jwtSecret) if err != nil { response.WriteJSON(w, response.Err(-2, err.Error())) return } requirePasswordChange := req.Username == "admin_user" || req.Password == "admin_user" response.WriteJSON(w, response.OK(map[string]interface{}{ "token": token, "name": user.User, "role_id": user.RoleID, "requirePasswordChange": requirePasswordChange, })) } func (h *Handler) getConfigByName(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { response.WriteJSON(w, response.ErrDefault("请求失败")) return } var req nameRequest if err := decodeJSON(r.Body, &req); err != nil { response.WriteJSON(w, response.ErrDefault("配置名称不能为空")) return } if strings.TrimSpace(req.Name) == "" { response.WriteJSON(w, response.ErrDefault("配置名称不能为空")) return } cfg, err := h.repo.GetConfigByName(req.Name) if err != nil { response.WriteJSON(w, response.Err(-2, err.Error())) return } if cfg == nil { response.WriteJSON(w, response.ErrDefault("配置不存在")) return } response.WriteJSON(w, response.OK(cfg)) } func (h *Handler) getConfigs(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { response.WriteJSON(w, response.ErrDefault("请求失败")) return } cfgMap, err := h.repo.ListConfigs() if err != nil { response.WriteJSON(w, response.Err(-2, err.Error())) return } response.WriteJSON(w, response.OK(cfgMap)) } func (h *Handler) userList(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { response.WriteJSON(w, response.ErrDefault("请求失败")) return } users, err := h.repo.ListUsers() if err != nil { response.WriteJSON(w, response.Err(-2, err.Error())) return } response.WriteJSON(w, response.OK(users)) } func (h *Handler) nodeList(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { response.WriteJSON(w, response.ErrDefault("请求失败")) return } items, err := h.repo.ListNodes() if err != nil { response.WriteJSON(w, response.Err(-2, err.Error())) return } response.WriteJSON(w, response.OK(items)) } func (h *Handler) tunnelList(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { response.WriteJSON(w, response.ErrDefault("请求失败")) return } items, err := h.repo.ListTunnels() if err != nil { response.WriteJSON(w, response.Err(-2, err.Error())) return } response.WriteJSON(w, response.OK(items)) } func (h *Handler) forwardList(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { response.WriteJSON(w, response.ErrDefault("请求失败")) return } userID, roleID, err := userRoleFromRequest(r) if err != nil { response.WriteJSON(w, response.Err(401, "无效的token或token已过期")) return } items, err := h.repo.ListForwards() if err != nil { response.WriteJSON(w, response.Err(-2, err.Error())) return } if roleID != 0 { filtered := make([]map[string]interface{}, 0, len(items)) for _, item := range items { if asInt64(item["userId"], 0) == userID { filtered = append(filtered, item) } } items = filtered } response.WriteJSON(w, response.OK(items)) } func (h *Handler) speedLimitList(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { response.WriteJSON(w, response.ErrDefault("请求失败")) return } items, err := h.repo.ListSpeedLimits() if err != nil { response.WriteJSON(w, response.Err(-2, err.Error())) return } response.WriteJSON(w, response.OK(items)) } func (h *Handler) openAPISubStore(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { response.WriteJSON(w, response.ErrDefault("请求失败")) return } if h == nil || h.repo == nil || h.repo.DB() == nil { response.WriteJSON(w, response.Err(-2, "database unavailable")) return } username := strings.TrimSpace(r.URL.Query().Get("user")) password := strings.TrimSpace(r.URL.Query().Get("pwd")) tunnel := strings.TrimSpace(r.URL.Query().Get("tunnel")) if tunnel == "" { tunnel = "-1" } if username == "" { response.WriteJSON(w, response.ErrDefault("用户不能为空")) return } if password == "" { response.WriteJSON(w, response.ErrDefault("密码不能为空")) return } user, err := h.repo.GetUserByUsername(username) if err != nil { response.WriteJSON(w, response.Err(-2, err.Error())) return } if user == nil || user.Pwd != security.MD5(password) { response.WriteJSON(w, response.ErrDefault("鉴权失败")) return } const giga = int64(1024 * 1024 * 1024) headerValue := "" if tunnel == "-1" { headerValue = buildSubscriptionHeader(user.OutFlow, user.InFlow, user.Flow*giga, user.ExpTime/1000) } else { tunnelID, parseErr := strconv.ParseInt(tunnel, 10, 64) if parseErr != nil || tunnelID <= 0 { response.WriteJSON(w, response.ErrDefault("隧道不存在")) return } var userID int64 var inFlow int64 var outFlow int64 var flow int64 var expTime int64 err = h.repo.DB().QueryRow(`SELECT user_id, in_flow, out_flow, flow, exp_time FROM user_tunnel WHERE id = ? LIMIT 1`, tunnelID). Scan(&userID, &inFlow, &outFlow, &flow, &expTime) if err != nil { if err == sql.ErrNoRows { response.WriteJSON(w, response.ErrDefault("隧道不存在")) return } response.WriteJSON(w, response.Err(-2, err.Error())) return } if userID != user.ID { response.WriteJSON(w, response.ErrDefault("隧道不存在")) return } headerValue = buildSubscriptionHeader(outFlow, inFlow, flow*giga, expTime/1000) } w.Header().Set("subscription-userinfo", headerValue) w.Header().Set("Content-Type", "text/plain; charset=utf-8") _, _ = w.Write([]byte(headerValue)) } func (h *Handler) errorPage(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "text/html; charset=UTF-8") w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte("