172 lines
5.1 KiB
Go
172 lines
5.1 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"hyapp/pkg/appcode"
|
|
"hyapp/pkg/roomid"
|
|
"hyapp/pkg/xerr"
|
|
|
|
roomv1 "hyapp.local/api/proto/room/v1"
|
|
)
|
|
|
|
const (
|
|
roomContributionRankPeriodDay = "day"
|
|
roomContributionRankPeriodWeek = "week"
|
|
roomContributionRankPeriodMonth = "month"
|
|
|
|
defaultRoomContributionRankLimit = 20
|
|
maxRoomContributionRankLimit = 100
|
|
)
|
|
|
|
type RoomContributionRankQuery struct {
|
|
AppCode string
|
|
RoomID string
|
|
Period string
|
|
PeriodStartMS int64
|
|
Limit int
|
|
}
|
|
|
|
type RoomContributionRankPage struct {
|
|
Items []RoomContributionRankEntry
|
|
Total int64
|
|
Period string
|
|
StartAtMS int64
|
|
EndAtMS int64
|
|
ServerTimeMS int64
|
|
}
|
|
|
|
type RoomContributionRankEntry struct {
|
|
Rank int64
|
|
UserID int64
|
|
Score int64
|
|
GiftValue int64
|
|
UpdatedAtMS int64
|
|
}
|
|
|
|
// ListRoomContributionRank 读取单房间用户贡献周期榜。
|
|
// 查询只访问 MySQL 周期读模型,不扫描 Room Cell、command log 或 outbox。
|
|
func (s *Service) ListRoomContributionRank(ctx context.Context, req *roomv1.ListRoomContributionRankRequest) (*roomv1.ListRoomContributionRankResponse, error) {
|
|
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
|
roomID := strings.TrimSpace(req.GetRoomId())
|
|
viewerUserID := req.GetViewerUserId()
|
|
if !roomid.ValidStringID(roomID) {
|
|
return nil, xerr.New(xerr.InvalidArgument, "room_id is invalid")
|
|
}
|
|
if viewerUserID <= 0 {
|
|
return nil, xerr.New(xerr.InvalidArgument, "viewer_user_id is required")
|
|
}
|
|
period := normalizeRoomContributionRankPeriod(req.GetPeriod())
|
|
if period == "" {
|
|
return nil, xerr.New(xerr.InvalidArgument, "period is invalid")
|
|
}
|
|
|
|
// 周期榜是房间内面板数据,必须确认 viewer 仍在该房间,避免用公开接口枚举任意房间用户贡献。
|
|
presence, exists, err := s.repository.GetCurrentRoomPresence(ctx, viewerUserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !exists || presence.RoomID != roomID || presence.Status != roomPresenceStatusActive {
|
|
return nil, xerr.New(xerr.PermissionDenied, "viewer is not in room")
|
|
}
|
|
|
|
limit := int(req.GetLimit())
|
|
if limit <= 0 {
|
|
limit = defaultRoomContributionRankLimit
|
|
}
|
|
if limit > maxRoomContributionRankLimit {
|
|
limit = maxRoomContributionRankLimit
|
|
}
|
|
now := s.clock.Now().UTC()
|
|
start, end := roomContributionRankWindow(period, now)
|
|
result, err := s.repository.ListRoomContributionRank(ctx, RoomContributionRankQuery{
|
|
AppCode: appcode.FromContext(ctx),
|
|
RoomID: roomID,
|
|
Period: period,
|
|
PeriodStartMS: start.UnixMilli(),
|
|
Limit: limit,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
items := make([]*roomv1.RoomContributionRankItem, 0, len(result.Items))
|
|
for _, item := range result.Items {
|
|
items = append(items, &roomv1.RoomContributionRankItem{
|
|
Rank: item.Rank,
|
|
UserId: item.UserID,
|
|
Score: item.Score,
|
|
GiftValue: item.GiftValue,
|
|
UpdatedAtMs: item.UpdatedAtMS,
|
|
})
|
|
}
|
|
return &roomv1.ListRoomContributionRankResponse{
|
|
RoomId: roomID,
|
|
Period: period,
|
|
StartAtMs: start.UnixMilli(),
|
|
EndAtMs: end.UnixMilli(),
|
|
Items: items,
|
|
Total: result.Total,
|
|
ServerTimeMs: now.UnixMilli(),
|
|
}, nil
|
|
}
|
|
|
|
func roomContributionPeriodStatIncrements(app string, roomID string, userID int64, giftValue int64, occurredMS int64) []RoomUserContributionPeriodStatIncrement {
|
|
if userID <= 0 || giftValue <= 0 || strings.TrimSpace(roomID) == "" {
|
|
return nil
|
|
}
|
|
occurredAt := time.UnixMilli(occurredMS).UTC()
|
|
if occurredMS <= 0 {
|
|
occurredAt = time.Now().UTC()
|
|
occurredMS = occurredAt.UnixMilli()
|
|
}
|
|
stats := make([]RoomUserContributionPeriodStatIncrement, 0, 3)
|
|
for _, period := range []string{roomContributionRankPeriodDay, roomContributionRankPeriodWeek, roomContributionRankPeriodMonth} {
|
|
start, end := roomContributionRankWindow(period, occurredAt)
|
|
stats = append(stats, RoomUserContributionPeriodStatIncrement{
|
|
AppCode: app,
|
|
RoomID: roomID,
|
|
Period: period,
|
|
PeriodStartMS: start.UnixMilli(),
|
|
PeriodEndMS: end.UnixMilli(),
|
|
UserID: userID,
|
|
GiftValue: giftValue,
|
|
LastGiftAtMS: occurredMS,
|
|
})
|
|
}
|
|
return stats
|
|
}
|
|
|
|
func normalizeRoomContributionRankPeriod(raw string) string {
|
|
switch strings.ToLower(strings.TrimSpace(raw)) {
|
|
case "", roomContributionRankPeriodDay, "today", "daily":
|
|
return roomContributionRankPeriodDay
|
|
case roomContributionRankPeriodWeek, "weekly":
|
|
return roomContributionRankPeriodWeek
|
|
case roomContributionRankPeriodMonth, "monthly":
|
|
return roomContributionRankPeriodMonth
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func roomContributionRankWindow(period string, now time.Time) (time.Time, time.Time) {
|
|
dayStart := time.Date(now.UTC().Year(), now.UTC().Month(), now.UTC().Day(), 0, 0, 0, 0, time.UTC)
|
|
switch period {
|
|
case roomContributionRankPeriodWeek:
|
|
weekday := int(dayStart.Weekday())
|
|
if weekday == 0 {
|
|
weekday = 7
|
|
}
|
|
start := dayStart.AddDate(0, 0, 1-weekday)
|
|
return start, start.AddDate(0, 0, 7)
|
|
case roomContributionRankPeriodMonth:
|
|
start := time.Date(dayStart.Year(), dayStart.Month(), 1, 0, 0, 0, 0, time.UTC)
|
|
return start, start.AddDate(0, 1, 0)
|
|
default:
|
|
return dayStart, dayStart.AddDate(0, 0, 1)
|
|
}
|
|
}
|