package http import ( "net" "net/http" "strings" "time" userv1 "hyapp/api/proto/user/v1" "hyapp/services/gateway-service/internal/auth" ) // authTokenData 是 gateway 对外 auth API 的扁平响应格式。 type authTokenData struct { UserID int64 `json:"user_id,omitempty"` DisplayUserID string `json:"display_user_id,omitempty"` DefaultDisplayUserID string `json:"default_display_user_id,omitempty"` DisplayUserIDKind string `json:"display_user_id_kind,omitempty"` DisplayUserIDExpiresAtMs int64 `json:"display_user_id_expires_at_ms"` SessionID string `json:"session_id"` AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` ExpiresInSec int64 `json:"expires_in_sec"` TokenType string `json:"token_type"` IsNewUser *bool `json:"is_new_user,omitempty"` } // loginPassword 处理 display_user_id + 密码登录。 func (h *Handler) loginPassword(writer http.ResponseWriter, request *http.Request) { var body struct { DisplayUserID string `json:"display_user_id"` Password string `json:"password"` DeviceID string `json:"device_id"` } if !decode(writer, request, &body) { return } if h.userClient == nil { writeError(writer, request, http.StatusBadGateway, codeUpstreamError, "upstream service error") return } resp, err := h.userClient.LoginPassword(request.Context(), &userv1.LoginPasswordRequest{ Meta: authRequestMeta(request, body.DeviceID), DisplayUserId: body.DisplayUserID, Password: body.Password, }) if err != nil { writeRPCError(writer, request, err) return } writeOK(writer, request, authData(resp.GetToken(), nil)) } // loginThirdParty 处理三方登录即注册。 func (h *Handler) loginThirdParty(writer http.ResponseWriter, request *http.Request) { var body struct { Provider string `json:"provider"` Credential string `json:"credential"` DeviceID string `json:"device_id"` } if !decode(writer, request, &body) { return } if h.userClient == nil { writeError(writer, request, http.StatusBadGateway, codeUpstreamError, "upstream service error") return } resp, err := h.userClient.LoginThirdParty(request.Context(), &userv1.LoginThirdPartyRequest{ Meta: authRequestMeta(request, body.DeviceID), Provider: body.Provider, Credential: body.Credential, }) if err != nil { writeRPCError(writer, request, err) return } isNewUser := resp.GetIsNewUser() writeOK(writer, request, authData(resp.GetToken(), &isNewUser)) } // setPassword 处理已登录用户首次设置密码。 func (h *Handler) setPassword(writer http.ResponseWriter, request *http.Request) { var body struct { Password string `json:"password"` } if !decode(writer, request, &body) { return } if h.userClient == nil { writeError(writer, request, http.StatusBadGateway, codeUpstreamError, "upstream service error") return } resp, err := h.userClient.SetPassword(request.Context(), &userv1.SetPasswordRequest{ Meta: authRequestMeta(request, ""), UserId: auth.UserIDFromContext(request.Context()), Password: body.Password, }) if err != nil { writeRPCError(writer, request, err) return } writeOK(writer, request, map[string]bool{"password_set": resp.GetPasswordSet()}) } // refreshToken 处理 refresh token 轮换。 func (h *Handler) refreshToken(writer http.ResponseWriter, request *http.Request) { var body struct { RefreshToken string `json:"refresh_token"` DeviceID string `json:"device_id"` } if !decode(writer, request, &body) { return } if h.userClient == nil { writeError(writer, request, http.StatusBadGateway, codeUpstreamError, "upstream service error") return } resp, err := h.userClient.RefreshToken(request.Context(), &userv1.RefreshTokenRequest{ Meta: authRequestMeta(request, body.DeviceID), RefreshToken: body.RefreshToken, }) if err != nil { writeRPCError(writer, request, err) return } writeOK(writer, request, authData(resp.GetToken(), nil)) } // logout 处理 refresh session 失效。 func (h *Handler) logout(writer http.ResponseWriter, request *http.Request) { var body struct { RefreshToken string `json:"refresh_token"` SessionID string `json:"session_id"` } if !decode(writer, request, &body) { return } if h.userClient == nil { writeError(writer, request, http.StatusBadGateway, codeUpstreamError, "upstream service error") return } resp, err := h.userClient.Logout(request.Context(), &userv1.LogoutRequest{ Meta: authRequestMeta(request, ""), SessionId: body.SessionID, RefreshToken: body.RefreshToken, }) if err != nil { writeRPCError(writer, request, err) return } writeOK(writer, request, map[string]bool{"revoked": resp.GetRevoked()}) } // authRequestMeta 生成 user-service auth RPC 的统一追踪和审计元信息。 func authRequestMeta(request *http.Request, deviceID string) *userv1.RequestMeta { return &userv1.RequestMeta{ RequestId: requestIDFromContext(request.Context()), Caller: "gateway-service", GatewayNodeId: "gateway-local", SentAtMs: time.Now().UnixMilli(), DeviceId: deviceID, ClientIp: clientIP(request), UserAgent: request.UserAgent(), } } // authData 把 user-service 的 token response 转成外部 HTTP 扁平 data。 func authData(token *userv1.AuthToken, isNewUser *bool) authTokenData { if token == nil { return authTokenData{IsNewUser: isNewUser} } return authTokenData{ UserID: token.GetUserId(), DisplayUserID: token.GetDisplayUserId(), DefaultDisplayUserID: token.GetDefaultDisplayUserId(), DisplayUserIDKind: token.GetDisplayUserIdKind(), DisplayUserIDExpiresAtMs: token.GetDisplayUserIdExpiresAtMs(), SessionID: token.GetSessionId(), AccessToken: token.GetAccessToken(), RefreshToken: token.GetRefreshToken(), ExpiresInSec: token.GetExpiresInSec(), TokenType: token.GetTokenType(), IsNewUser: isNewUser, } } // clientIP 提取 gateway 看到的客户端地址。 func clientIP(request *http.Request) string { forwarded := strings.TrimSpace(request.Header.Get("X-Forwarded-For")) if forwarded != "" { parts := strings.Split(forwarded, ",") return strings.TrimSpace(parts[0]) } host, _, err := net.SplitHostPort(request.RemoteAddr) if err != nil { return request.RemoteAddr } return host }