2026-05-12 09:53:20 +08:00

337 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package http
import (
"hyapp/services/gateway-service/internal/transport/http/httpkit"
"net/http"
"strings"
userv1 "hyapp.local/api/proto/user/v1"
"hyapp/services/gateway-service/internal/auth"
)
type profileVisitRecordData struct {
VisitorUserID string `json:"visitor_user_id"`
TargetUserID string `json:"target_user_id"`
VisitCount int64 `json:"visit_count"`
LastVisitedAtMS int64 `json:"last_visited_at_ms"`
}
type followRecordData struct {
FollowerUserID string `json:"follower_user_id"`
FolloweeUserID string `json:"followee_user_id"`
FollowedAtMS int64 `json:"followed_at_ms"`
}
type friendRecordData struct {
UserID string `json:"user_id"`
FriendUserID string `json:"friend_user_id"`
FriendedAtMS int64 `json:"friended_at_ms"`
}
type friendApplicationData struct {
RequesterUserID string `json:"requester_user_id"`
TargetUserID string `json:"target_user_id"`
Status string `json:"status"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
// listMyProfileVisitors 返回访问我的主页的人;这是关系 read model不实时扫用户行为日志。
func (h *Handler) listMyProfileVisitors(writer http.ResponseWriter, request *http.Request) {
if h.userSocialClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
page, pageSize, ok := socialPage(request)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
resp, err := h.userSocialClient.ListProfileVisitors(request.Context(), &userv1.ListProfileVisitorsRequest{
Meta: authRequestMeta(request, ""),
UserId: auth.UserIDFromContext(request.Context()),
Page: page,
PageSize: pageSize,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
records := make([]profileVisitRecordData, 0, len(resp.GetRecords()))
for _, record := range resp.GetRecords() {
records = append(records, profileVisitData(record))
}
httpkit.WriteOK(writer, request, map[string]any{"records": records, "total": resp.GetTotal()})
}
// listMyFollowing 返回我关注的用户列表;关注我的列表后续单独建接口,避免语义混淆。
func (h *Handler) listMyFollowing(writer http.ResponseWriter, request *http.Request) {
if h.userSocialClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
page, pageSize, ok := socialPage(request)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
resp, err := h.userSocialClient.ListFollowing(request.Context(), &userv1.ListFollowingRequest{
Meta: authRequestMeta(request, ""),
UserId: auth.UserIDFromContext(request.Context()),
Page: page,
PageSize: pageSize,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
records := make([]followRecordData, 0, len(resp.GetRecords()))
for _, record := range resp.GetRecords() {
records = append(records, followData(record))
}
httpkit.WriteOK(writer, request, map[string]any{"records": records, "total": resp.GetTotal()})
}
// listMyFriends 返回双向好友列表。
func (h *Handler) listMyFriends(writer http.ResponseWriter, request *http.Request) {
if h.userSocialClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
page, pageSize, ok := socialPage(request)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
resp, err := h.userSocialClient.ListFriends(request.Context(), &userv1.ListFriendsRequest{
Meta: authRequestMeta(request, ""),
UserId: auth.UserIDFromContext(request.Context()),
Page: page,
PageSize: pageSize,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
records := make([]friendRecordData, 0, len(resp.GetRecords()))
for _, record := range resp.GetRecords() {
records = append(records, friendData(record))
}
httpkit.WriteOK(writer, request, map[string]any{"records": records, "total": resp.GetTotal()})
}
// listMyFriendApplications 返回待处理申请direction=incoming/outgoing。
func (h *Handler) listMyFriendApplications(writer http.ResponseWriter, request *http.Request) {
if h.userSocialClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
page, pageSize, ok := socialPage(request)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
resp, err := h.userSocialClient.ListFriendApplications(request.Context(), &userv1.ListFriendApplicationsRequest{
Meta: authRequestMeta(request, ""),
UserId: auth.UserIDFromContext(request.Context()),
Direction: strings.TrimSpace(request.URL.Query().Get("direction")),
Page: page,
PageSize: pageSize,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
applications := make([]friendApplicationData, 0, len(resp.GetApplications()))
for _, application := range resp.GetApplications() {
applications = append(applications, friendApplicationDataFromProto(application))
}
httpkit.WriteOK(writer, request, map[string]any{"applications": applications, "total": resp.GetTotal()})
}
// userSocialAction 处理 /users/{user_id}/visit|follow|friend-requests|friend-requests/accept|friend。
func (h *Handler) userSocialAction(writer http.ResponseWriter, request *http.Request) {
if h.userSocialClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
targetUserID, ok := httpkit.PositiveInt64PathValue(request, "user_id")
if !ok {
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
return
}
action := strings.Trim(request.PathValue("action"), "/")
switch action {
case "visit":
h.recordProfileVisit(writer, request, targetUserID)
case "follow":
h.handleFollowAction(writer, request, targetUserID)
case "friend-requests":
h.applyFriend(writer, request, targetUserID)
case "friend-requests/accept":
h.acceptFriendApplication(writer, request, targetUserID)
case "friend":
h.deleteFriend(writer, request, targetUserID)
default:
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
}
}
func (h *Handler) recordProfileVisit(writer http.ResponseWriter, request *http.Request, targetUserID int64) {
if request.Method != http.MethodPost {
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
return
}
resp, err := h.userSocialClient.RecordProfileVisit(request.Context(), &userv1.RecordProfileVisitRequest{
Meta: authRequestMeta(request, ""),
VisitorUserId: auth.UserIDFromContext(request.Context()),
TargetUserId: targetUserID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, map[string]any{"recorded": resp.GetRecorded(), "target_stats": overviewStatsFromProto(resp.GetTargetStats())})
}
func (h *Handler) handleFollowAction(writer http.ResponseWriter, request *http.Request, followeeUserID int64) {
userID := auth.UserIDFromContext(request.Context())
switch request.Method {
case http.MethodPost:
resp, err := h.userSocialClient.FollowUser(request.Context(), &userv1.FollowUserRequest{
Meta: authRequestMeta(request, ""),
FollowerUserId: userID,
FolloweeUserId: followeeUserID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, map[string]any{"following": resp.GetFollowing(), "follower_stats": overviewStatsFromProto(resp.GetFollowerStats())})
case http.MethodDelete:
resp, err := h.userSocialClient.UnfollowUser(request.Context(), &userv1.UnfollowUserRequest{
Meta: authRequestMeta(request, ""),
FollowerUserId: userID,
FolloweeUserId: followeeUserID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, map[string]any{"following": resp.GetFollowing(), "follower_stats": overviewStatsFromProto(resp.GetFollowerStats())})
default:
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
}
}
func (h *Handler) applyFriend(writer http.ResponseWriter, request *http.Request, targetUserID int64) {
if request.Method != http.MethodPost {
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
return
}
resp, err := h.userSocialClient.ApplyFriend(request.Context(), &userv1.ApplyFriendRequest{
Meta: authRequestMeta(request, ""),
RequesterUserId: auth.UserIDFromContext(request.Context()),
TargetUserId: targetUserID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, map[string]any{"application": friendApplicationDataFromProto(resp.GetApplication()), "already_friends": resp.GetAlreadyFriends()})
}
func (h *Handler) acceptFriendApplication(writer http.ResponseWriter, request *http.Request, requesterUserID int64) {
if request.Method != http.MethodPost {
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
return
}
resp, err := h.userSocialClient.AcceptFriendApplication(request.Context(), &userv1.AcceptFriendApplicationRequest{
Meta: authRequestMeta(request, ""),
AccepterUserId: auth.UserIDFromContext(request.Context()),
RequesterUserId: requesterUserID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, map[string]any{"friend": friendData(resp.GetFriend())})
}
func (h *Handler) deleteFriend(writer http.ResponseWriter, request *http.Request, friendUserID int64) {
if request.Method != http.MethodDelete {
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
return
}
resp, err := h.userSocialClient.DeleteFriend(request.Context(), &userv1.DeleteFriendRequest{
Meta: authRequestMeta(request, ""),
UserId: auth.UserIDFromContext(request.Context()),
FriendUserId: friendUserID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, map[string]any{"deleted": resp.GetDeleted()})
}
func socialPage(request *http.Request) (int32, int32, bool) {
page, ok := parsePositiveInt32Query(request, "page", 1)
if !ok {
return 0, 0, false
}
pageSize, ok := parsePositiveInt32Query(request, "page_size", 20)
if !ok {
return 0, 0, false
}
return page, pageSize, true
}
func profileVisitData(record *userv1.ProfileVisitRecord) profileVisitRecordData {
if record == nil {
return profileVisitRecordData{}
}
return profileVisitRecordData{
VisitorUserID: userIDString(record.GetVisitorUserId()),
TargetUserID: userIDString(record.GetTargetUserId()),
VisitCount: record.GetVisitCount(),
LastVisitedAtMS: record.GetLastVisitedAtMs(),
}
}
func followData(record *userv1.FollowRecord) followRecordData {
if record == nil {
return followRecordData{}
}
return followRecordData{
FollowerUserID: userIDString(record.GetFollowerUserId()),
FolloweeUserID: userIDString(record.GetFolloweeUserId()),
FollowedAtMS: record.GetFollowedAtMs(),
}
}
func friendData(record *userv1.FriendRecord) friendRecordData {
if record == nil {
return friendRecordData{}
}
return friendRecordData{
UserID: userIDString(record.GetUserId()),
FriendUserID: userIDString(record.GetFriendUserId()),
FriendedAtMS: record.GetFriendedAtMs(),
}
}
func friendApplicationDataFromProto(application *userv1.FriendApplication) friendApplicationData {
if application == nil {
return friendApplicationData{}
}
return friendApplicationData{
RequesterUserID: userIDString(application.GetRequesterUserId()),
TargetUserID: userIDString(application.GetTargetUserId()),
Status: application.GetStatus(),
CreatedAtMS: application.GetCreatedAtMs(),
UpdatedAtMS: application.GetUpdatedAtMs(),
}
}