2026-05-14 00:25:02 +08:00

86 lines
2.7 KiB
Go

package service
import (
"context"
"strings"
roomv1 "hyapp.local/api/proto/room/v1"
"hyapp/pkg/roomid"
"hyapp/pkg/xerr"
)
// FollowRoom 建立当前用户对房间的关注关系。
// 关注关系是用户个性化读模型事实,不改变 Room Cell 核心状态,也不需要 Redis lease 串行化。
func (s *Service) FollowRoom(ctx context.Context, req *roomv1.FollowRoomRequest) (*roomv1.FollowRoomResponse, error) {
ctx = contextFromMeta(ctx, req.GetMeta())
roomID, userID, err := normalizeRoomFollowInput(req.GetMeta(), req.GetRoomId(), req.GetUserId())
if err != nil {
return nil, err
}
meta, exists, err := s.repository.GetRoomMeta(ctx, roomID)
if err != nil {
return nil, err
}
if !exists {
return nil, xerr.New(xerr.NotFound, "room not found")
}
if isClosedRoomStatus(meta.Status) {
// 已关闭房间不再建立新的关注关系;历史关注仍可通过 UnfollowRoom 幂等取消。
return nil, xerr.New(xerr.RoomClosed, "room closed")
}
nowMS := s.clock.Now().UnixMilli()
record, err := s.repository.FollowRoom(ctx, userID, roomID, nowMS)
if err != nil {
return nil, err
}
return &roomv1.FollowRoomResponse{
RoomId: roomID,
Followed: true,
FollowedAtMs: record.FollowedAtMS,
ServerTimeMs: nowMS,
}, nil
}
// UnfollowRoom 取消当前用户对房间的关注关系。
// 未关注或历史房间已关闭都按幂等成功处理,方便客户端在本地状态不确定时直接收敛。
func (s *Service) UnfollowRoom(ctx context.Context, req *roomv1.UnfollowRoomRequest) (*roomv1.UnfollowRoomResponse, error) {
ctx = contextFromMeta(ctx, req.GetMeta())
roomID, userID, err := normalizeRoomFollowInput(req.GetMeta(), req.GetRoomId(), req.GetUserId())
if err != nil {
return nil, err
}
nowMS := s.clock.Now().UnixMilli()
if err := s.repository.UnfollowRoom(ctx, userID, roomID, nowMS); err != nil {
return nil, err
}
return &roomv1.UnfollowRoomResponse{
RoomId: roomID,
Followed: false,
ServerTimeMs: nowMS,
}, nil
}
func normalizeRoomFollowInput(meta *roomv1.RequestMeta, rawRoomID string, userID int64) (string, int64, error) {
roomID := strings.TrimSpace(rawRoomID)
metaRoomID := strings.TrimSpace(meta.GetRoomId())
if roomID == "" {
roomID = metaRoomID
}
if metaRoomID != "" && roomID != metaRoomID {
// meta.room_id 是链路和审计字段;与 body/path room_id 不一致时必须拒绝,避免写错关系。
return "", 0, xerr.New(xerr.InvalidArgument, "room_id is invalid")
}
if !roomid.ValidStringID(roomID) {
return "", 0, xerr.New(xerr.InvalidArgument, "room_id is invalid")
}
if userID <= 0 {
return "", 0, xerr.New(xerr.InvalidArgument, "user_id is required")
}
return roomID, userID, nil
}