package voiceroomredpacket import ( "context" "errors" "fmt" "net/http" "strings" "time" "chatapp3-golang/internal/model" "gorm.io/gorm" ) // Detail 返回红包详情和当前用户领取状态。 func (s *Service) Detail(ctx context.Context, user AuthUser, packetNo string) (*DetailResponse, error) { packetNo = strings.TrimSpace(packetNo) if packetNo == "" { return nil, NewAppError(http.StatusBadRequest, "missing_packet_no", "packetNo is required") } packet, err := s.loadPacketByNo(ctx, packetNo) if err != nil { return nil, err } if packet == nil { return nil, NewAppError(http.StatusNotFound, "voice_room_red_packet_not_found", "red packet not found") } claims, err := s.loadClaims(ctx, packet.ID) if err != nil { return nil, err } resp := &DetailResponse{ Packet: packetPayload(*packet), Claims: make([]ClaimPayload, 0, len(claims)), NowMillis: time.Now().UnixMilli(), } for _, claim := range claims { payload := ClaimPayload{ ID: claim.ID, UserID: claim.UserID, UserRegionCode: claim.UserRegionCode, Amount: claim.Amount, WalletStatus: claim.WalletStatus, ClaimTime: formatTime(claim.ClaimTime), } resp.Claims = append(resp.Claims, payload) if claim.UserID == user.UserID { resp.Grabbed = true resp.MyAmount = claim.Amount } } region, _ := s.resolveCachedUserRegion(ctx, normalizeSysOrigin(user.SysOrigin), user.UserID) resp.Claimable = validateClaimable(*packet, region) == nil return resp, nil } // ListClaimRecords 返回某个红包的成功领取列表,包含领取人资料、领取金额和领取时间。 func (s *Service) ListClaimRecords(ctx context.Context, _ AuthUser, packetNo string) (*ClaimRecordListResponse, error) { packetNo = strings.TrimSpace(packetNo) if packetNo == "" { return nil, NewAppError(http.StatusBadRequest, "missing_packet_no", "packetNo is required") } packet, err := s.loadPacketByNo(ctx, packetNo) if err != nil { return nil, err } if packet == nil { return nil, NewAppError(http.StatusNotFound, "voice_room_red_packet_not_found", "red packet not found") } claims, err := s.loadSuccessfulClaims(ctx, packet.ID) if err != nil { return nil, err } profiles, err := s.mapClaimUserProfiles(ctx, claims) if err != nil { return nil, err } resp := &ClaimRecordListResponse{ PacketNo: packet.PacketNo, TotalCount: packet.TotalCount, ClaimedCount: len(claims), Records: make([]ClaimRecordPayload, 0, len(claims)), } for _, claim := range claims { profile := profiles[claim.UserID] resp.ClaimedAmount += claim.Amount resp.Records = append(resp.Records, ClaimRecordPayload{ ID: claim.ID, UserID: claim.UserID, Account: defaultIfBlank(profile.Account, fmt.Sprint(claim.UserID)), UserNickname: defaultIfBlank(profile.UserNickname, defaultIfBlank(profile.Account, fmt.Sprint(claim.UserID))), UserAvatar: profile.UserAvatar, CountryCode: profile.CountryCode, CountryName: profile.CountryName, UserRegionCode: claim.UserRegionCode, Amount: claim.Amount, WalletStatus: claim.WalletStatus, ClaimTime: formatTime(claim.ClaimTime), }) } return resp, nil } // PageSentRecords 返回当前用户发出的红包记录。 func (s *Service) PageSentRecords(ctx context.Context, user AuthUser, cursor, limit int) (*SentRecordPageResponse, error) { cursor, limit = normalizePage(cursor, limit) var rows []model.VoiceRoomRedPacket query := s.db.WithContext(ctx). Where("sys_origin = ? AND sender_user_id = ?", normalizeSysOrigin(user.SysOrigin), user.UserID). Order("create_time desc"). Order("id desc") var total int64 if err := query.Model(&model.VoiceRoomRedPacket{}).Count(&total).Error; err != nil { return nil, err } if err := query.Offset((cursor - 1) * limit).Limit(limit).Find(&rows).Error; err != nil { return nil, err } return &SentRecordPageResponse{Records: packetPayloads(rows), Total: total}, nil } // PageAdminRecords 返回后台红包记录。 func (s *Service) PageAdminRecords(ctx context.Context, sysOrigin, status string, userID int64, cursor, limit int) (*SentRecordPageResponse, error) { cursor, limit = normalizePage(cursor, limit) query := s.db.WithContext(ctx).Model(&model.VoiceRoomRedPacket{}) if strings.TrimSpace(sysOrigin) != "" { query = query.Where("sys_origin = ?", normalizeSysOrigin(sysOrigin)) } if strings.TrimSpace(status) != "" { query = query.Where("status = ?", strings.ToUpper(strings.TrimSpace(status))) } if userID > 0 { query = query.Where("sender_user_id = ?", userID) } var total int64 if err := query.Count(&total).Error; err != nil { return nil, err } var rows []model.VoiceRoomRedPacket if err := query.Order("create_time desc").Order("id desc").Offset((cursor - 1) * limit).Limit(limit).Find(&rows).Error; err != nil { return nil, err } return &SentRecordPageResponse{Records: packetPayloads(rows), Total: total}, nil } func (s *Service) loadClaims(ctx context.Context, packetID int64) ([]model.VoiceRoomRedPacketClaim, error) { var rows []model.VoiceRoomRedPacketClaim if err := s.db.WithContext(ctx). Where("packet_id = ?", packetID). Order("claim_time asc"). Find(&rows).Error; err != nil { return nil, err } return rows, nil } func (s *Service) loadSuccessfulClaims(ctx context.Context, packetID int64) ([]model.VoiceRoomRedPacketClaim, error) { var rows []model.VoiceRoomRedPacketClaim if err := s.db.WithContext(ctx). Where("packet_id = ? AND wallet_status = ?", packetID, walletStatusSuccess). Order("claim_time asc"). Order("id asc"). Find(&rows).Error; err != nil { return nil, err } return rows, nil } func (s *Service) mapClaimUserProfiles(ctx context.Context, claims []model.VoiceRoomRedPacketClaim) (map[int64]model.UserBaseInfo, error) { userIDs := make([]int64, 0, len(claims)) seen := map[int64]struct{}{} for _, claim := range claims { if claim.UserID <= 0 { continue } if _, exists := seen[claim.UserID]; exists { continue } seen[claim.UserID] = struct{}{} userIDs = append(userIDs, claim.UserID) } if len(userIDs) == 0 { return map[int64]model.UserBaseInfo{}, nil } var users []model.UserBaseInfo if err := s.db.WithContext(ctx).Where("id IN ?", userIDs).Find(&users).Error; err != nil { return nil, err } result := make(map[int64]model.UserBaseInfo, len(users)) for _, user := range users { result[user.ID] = user } return result, nil } func packetPayloads(rows []model.VoiceRoomRedPacket) []PacketPayload { result := make([]PacketPayload, 0, len(rows)) for _, row := range rows { result = append(result, packetPayload(row)) } return result } func defaultIfBlank(value string, fallback string) string { if strings.TrimSpace(value) == "" { return fallback } return value } func ParseUserID(raw string) int64 { raw = strings.TrimSpace(raw) if raw == "" { return 0 } var userID int64 _, _ = fmt.Sscan(raw, &userID) return userID } func ignoreRecordNotFound(err error) error { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } return err }