(w http.ResponseWriter, r *http.Request)
| 27 | } |
| 28 | |
| 29 | func (a *subAPI) handleSub(w http.ResponseWriter, r *http.Request) { |
| 30 | if r.Method != http.MethodGet { |
| 31 | http.NotFound(w, r) |
| 32 | return |
| 33 | } |
| 34 | |
| 35 | // 从路径提取 token:/sub/{token} |
| 36 | userID := strings.TrimPrefix(r.URL.Path, "/sub/") |
| 37 | userID = strings.TrimSuffix(userID, "/") |
| 38 | if userID == "" { |
| 39 | http.NotFound(w, r) |
| 40 | return |
| 41 | } |
| 42 | |
| 43 | user, err := a.users.GetUserBySubToken(userID) |
| 44 | if err != nil { |
| 45 | http.NotFound(w, r) |
| 46 | return |
| 47 | } |
| 48 | |
| 49 | // 异步记录订阅访问日志(无论用户状态如何,均记录) |
| 50 | go func() { |
| 51 | ip := realIP(r) |
| 52 | ua := r.Header.Get("User-Agent") |
| 53 | if err := a.users.LogSubAccess(user.ID, ip, ua); err != nil { |
| 54 | log.Printf("sub access log: %v", err) |
| 55 | } |
| 56 | }() |
| 57 | |
| 58 | // 非活跃用户(disabled / expired / limited / on_hold)返回空订阅。 |
| 59 | // 仍携带 Subscription-Userinfo header,让客户端能展示流量/到期信息。 |
| 60 | if !user.EffectiveEnabled() { |
| 61 | w.Header().Set("Subscription-Userinfo", buildUserinfo(user)) |
| 62 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") |
| 63 | w.WriteHeader(http.StatusOK) |
| 64 | _, _ = w.Write([]byte(base64.StdEncoding.EncodeToString(nil))) |
| 65 | return |
| 66 | } |
| 67 | |
| 68 | // 收集该用户所有节点的全部订阅链接(跳过已禁用节点) |
| 69 | accesses, err := a.users.ListActiveUserInboundsByUser(user.ID) |
| 70 | if err != nil { |
| 71 | http.Error(w, "internal error", http.StatusInternalServerError) |
| 72 | return |
| 73 | } |
| 74 | |
| 75 | // 构建用户排除的 host 集合 |
| 76 | excludedIDs, _ := a.users.ListHostExclusionsByUser(user.ID) |
| 77 | excluded := make(map[string]bool, len(excludedIDs)) |
| 78 | for _, id := range excludedIDs { |
| 79 | excluded[id] = true |
| 80 | } |
| 81 | |
| 82 | links := subscription.BuildLinks(accesses, a.inbounds, user, excluded) |
| 83 | |
| 84 | // Subscription-Userinfo header(客户端如 v2rayN 用于显示流量信息) |
| 85 | w.Header().Set("Subscription-Userinfo", buildUserinfo(user)) |
| 86 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") |
nothing calls this directly
no test coverage detected