390 lines
11 KiB
Go
390 lines
11 KiB
Go
package voiceroomrocket
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"chatapp3-golang/internal/integration"
|
|
"chatapp3-golang/internal/model"
|
|
)
|
|
|
|
func (s *Service) normalizeSysOrigin(sysOrigin string) string {
|
|
if strings.TrimSpace(sysOrigin) != "" {
|
|
return strings.ToUpper(strings.TrimSpace(sysOrigin))
|
|
}
|
|
if strings.TrimSpace(s.cfg.VoiceRoomRocket.DefaultSysOrigin) != "" {
|
|
return strings.ToUpper(strings.TrimSpace(s.cfg.VoiceRoomRocket.DefaultSysOrigin))
|
|
}
|
|
return defaultSysOrigin
|
|
}
|
|
|
|
func normalizeTimezone(value string) string {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return defaultTimezone
|
|
}
|
|
return value
|
|
}
|
|
|
|
func resolveLocation(value string) *time.Location {
|
|
location, err := time.LoadLocation(normalizeTimezone(value))
|
|
if err != nil {
|
|
return time.UTC
|
|
}
|
|
return location
|
|
}
|
|
|
|
func dayKey(t time.Time, location *time.Location) string {
|
|
if location == nil {
|
|
location = time.UTC
|
|
}
|
|
return t.In(location).Format("20060102")
|
|
}
|
|
|
|
func normalizePage(cursor, limit int) (int, int) {
|
|
if cursor <= 0 {
|
|
cursor = 1
|
|
}
|
|
if limit <= 0 {
|
|
limit = 20
|
|
}
|
|
if limit > 100 {
|
|
limit = 100
|
|
}
|
|
return cursor, limit
|
|
}
|
|
|
|
func formatDateTime(t time.Time) string {
|
|
if t.IsZero() {
|
|
return ""
|
|
}
|
|
return t.Format("2006-01-02 15:04:05")
|
|
}
|
|
|
|
func pointerTimeValue(value *time.Time) time.Time {
|
|
if value == nil {
|
|
return time.Time{}
|
|
}
|
|
return *value
|
|
}
|
|
|
|
func decimalPercent(current, need int64) (string, int) {
|
|
if need <= 0 || current <= 0 {
|
|
return "0.0000", 0
|
|
}
|
|
percent := float64(current) * 100 / float64(need)
|
|
display := int(math.Floor(percent))
|
|
if display > 100 {
|
|
display = 100
|
|
}
|
|
return fmt.Sprintf("%.4f", percent), display
|
|
}
|
|
|
|
func parsePercent(value string) float64 {
|
|
parsed, err := strconv.ParseFloat(strings.TrimSpace(value), 64)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return parsed
|
|
}
|
|
|
|
func countDistinctAcceptUsers(users []giftEventUser) int {
|
|
if len(users) == 0 {
|
|
return 0
|
|
}
|
|
set := make(map[int64]struct{}, len(users))
|
|
for _, user := range users {
|
|
if user.AcceptUserID.Int64() <= 0 {
|
|
continue
|
|
}
|
|
set[user.AcceptUserID.Int64()] = struct{}{}
|
|
}
|
|
return len(set)
|
|
}
|
|
|
|
func resolveMillisTime(raw int64, fallback time.Time) time.Time {
|
|
if raw <= 0 {
|
|
return fallback
|
|
}
|
|
if raw < 1_000_000_000_000 {
|
|
return time.Unix(raw, 0)
|
|
}
|
|
return time.UnixMilli(raw)
|
|
}
|
|
|
|
func truncateString(value string, limit int) string {
|
|
if limit <= 0 || len(value) <= limit {
|
|
return value
|
|
}
|
|
return value[:limit]
|
|
}
|
|
|
|
func isDuplicateKeyErr(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
message := strings.ToLower(err.Error())
|
|
return strings.Contains(message, "duplicate entry") ||
|
|
strings.Contains(message, "duplicate key") ||
|
|
strings.Contains(message, "unique constraint")
|
|
}
|
|
|
|
func levelPayload(row model.VoiceRoomRocketLevelConfig) LevelConfigPayload {
|
|
return LevelConfigPayload{
|
|
ID: row.ID,
|
|
Level: row.Level,
|
|
NeedEnergy: row.NeedEnergy,
|
|
ShakeThresholdPercent: row.ShakeThresholdPercent,
|
|
PreviewRocketURL: row.PreviewRocketURL,
|
|
RocketIconURL: row.RocketIconURL,
|
|
RocketAnimationURL: row.RocketAnimationURL,
|
|
ProgressBarURL: row.ProgressBarURL,
|
|
Enabled: row.Enabled,
|
|
Sort: row.Sort,
|
|
}
|
|
}
|
|
|
|
func rewardPayload(row model.VoiceRoomRocketRewardConfig) RewardConfigPayload {
|
|
return RewardConfigPayload{
|
|
ID: row.ID,
|
|
Level: row.Level,
|
|
RewardScene: row.RewardScene,
|
|
RewardType: row.RewardType,
|
|
RewardItemID: row.RewardItemID,
|
|
RewardName: row.RewardName,
|
|
RewardCover: row.RewardCover,
|
|
RewardAmount: row.RewardAmount,
|
|
ExpireDays: row.ExpireDays,
|
|
RewardCopies: positiveOrDefault(row.RewardCopies, 1),
|
|
Weight: row.Weight,
|
|
Sort: row.Sort,
|
|
Enabled: row.Enabled,
|
|
}
|
|
}
|
|
|
|
func rewardPayloads(rows []model.VoiceRoomRocketRewardConfig) []RewardConfigPayload {
|
|
sorted := sortedRewardRows(rows)
|
|
result := make([]RewardConfigPayload, 0, len(sorted))
|
|
for _, row := range sorted {
|
|
result = append(result, rewardPayload(row))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func rewardPreviewPayload(rows []model.VoiceRoomRocketRewardConfig) RewardPreviewResponse {
|
|
rows = sortedRewardRows(rows)
|
|
resp := RewardPreviewResponse{}
|
|
for _, row := range rows {
|
|
if strings.EqualFold(strings.TrimSpace(row.RewardType), rewardTypeNone) {
|
|
continue
|
|
}
|
|
payload := rewardPayload(row)
|
|
switch strings.ToUpper(strings.TrimSpace(row.RewardScene)) {
|
|
case rewardSceneTop1:
|
|
resp.Top1 = append(resp.Top1, payload)
|
|
case rewardSceneIgnite:
|
|
resp.Ignite = append(resp.Ignite, payload)
|
|
case rewardSceneInRoom:
|
|
resp.InRoom = append(resp.InRoom, payload)
|
|
}
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func sortedRewardRows(rows []model.VoiceRoomRocketRewardConfig) []model.VoiceRoomRocketRewardConfig {
|
|
sorted := append([]model.VoiceRoomRocketRewardConfig(nil), rows...)
|
|
sort.Slice(sorted, func(i, j int) bool {
|
|
if sorted[i].RewardScene != sorted[j].RewardScene {
|
|
return rewardSceneOrder(sorted[i].RewardScene) < rewardSceneOrder(sorted[j].RewardScene)
|
|
}
|
|
if sorted[i].Sort != sorted[j].Sort {
|
|
return sorted[i].Sort < sorted[j].Sort
|
|
}
|
|
return sorted[i].ID < sorted[j].ID
|
|
})
|
|
return sorted
|
|
}
|
|
|
|
func rewardSceneOrder(scene string) int {
|
|
switch strings.ToUpper(strings.TrimSpace(scene)) {
|
|
case rewardSceneTop1:
|
|
return 1
|
|
case rewardSceneIgnite:
|
|
return 2
|
|
case rewardSceneInRoom:
|
|
return 3
|
|
default:
|
|
return 100
|
|
}
|
|
}
|
|
|
|
func isSupportedRewardType(rewardType string) bool {
|
|
switch strings.ToUpper(strings.TrimSpace(rewardType)) {
|
|
case rewardTypeGold,
|
|
rewardTypeMount,
|
|
rewardTypeAvatarFrame,
|
|
rewardTypeChatBubble,
|
|
rewardTypeVIPCard,
|
|
rewardTypeGift,
|
|
rewardTypeBadge,
|
|
rewardTypeNone:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func levelContentsResponse(snapshot configSnapshot, levels []model.VoiceRoomRocketLevelConfig, rewards []model.VoiceRoomRocketRewardConfig) *LevelContentsResponse {
|
|
sort.Slice(levels, func(i, j int) bool {
|
|
if levels[i].Sort != levels[j].Sort {
|
|
return levels[i].Sort < levels[j].Sort
|
|
}
|
|
return levels[i].Level < levels[j].Level
|
|
})
|
|
rewardsByLevel := make(map[int][]model.VoiceRoomRocketRewardConfig)
|
|
for _, row := range rewards {
|
|
rewardsByLevel[row.Level] = append(rewardsByLevel[row.Level], row)
|
|
}
|
|
levelPayloads := make([]LevelContentPayload, 0, len(levels))
|
|
for _, row := range levels {
|
|
levelRewards := rewardsByLevel[row.Level]
|
|
levelPayloads = append(levelPayloads, LevelContentPayload{
|
|
LevelConfigPayload: levelPayload(row),
|
|
Rewards: rewardPayloads(levelRewards),
|
|
RewardPreview: rewardPreviewPayload(levelRewards),
|
|
})
|
|
}
|
|
return &LevelContentsResponse{
|
|
Configured: snapshot.Configured,
|
|
SysOrigin: snapshot.SysOrigin,
|
|
Enabled: snapshot.Enabled,
|
|
Timezone: snapshot.Timezone,
|
|
MaxLevel: snapshot.MaxLevel,
|
|
Levels: levelPayloads,
|
|
UpdateTime: formatDateTime(snapshot.UpdateTime),
|
|
}
|
|
}
|
|
|
|
func configResponse(snapshot configSnapshot, levels []model.VoiceRoomRocketLevelConfig, rewards []model.VoiceRoomRocketRewardConfig) *ConfigResponse {
|
|
levelPayloads := make([]LevelConfigPayload, 0, len(levels))
|
|
sort.Slice(levels, func(i, j int) bool {
|
|
if levels[i].Sort != levels[j].Sort {
|
|
return levels[i].Sort < levels[j].Sort
|
|
}
|
|
return levels[i].Level < levels[j].Level
|
|
})
|
|
for _, row := range levels {
|
|
levelPayloads = append(levelPayloads, levelPayload(row))
|
|
}
|
|
|
|
var ruleText json.RawMessage
|
|
if strings.TrimSpace(snapshot.RuleText) != "" {
|
|
ruleText = json.RawMessage(snapshot.RuleText)
|
|
}
|
|
var energyRuleJSON json.RawMessage
|
|
if strings.TrimSpace(snapshot.EnergyRuleJSON) != "" {
|
|
energyRuleJSON = json.RawMessage(snapshot.EnergyRuleJSON)
|
|
}
|
|
return &ConfigResponse{
|
|
Configured: snapshot.Configured,
|
|
ID: snapshot.ID,
|
|
SysOrigin: snapshot.SysOrigin,
|
|
Enabled: snapshot.Enabled,
|
|
Timezone: snapshot.Timezone,
|
|
MaxLevel: snapshot.MaxLevel,
|
|
RankingTopLimit: snapshot.RankingTopLimit,
|
|
RewardRecordKeepDays: snapshot.RewardRecordKeepDays,
|
|
BroadcastDurationSeconds: snapshot.BroadcastDurationSeconds,
|
|
InRoomRewardDelaySeconds: snapshot.InRoomRewardDelaySeconds,
|
|
EnergyRuleJSON: energyRuleJSON,
|
|
RuleText: ruleText,
|
|
Levels: levelPayloads,
|
|
RewardPreview: rewardPreviewPayload(rewards),
|
|
UpdateTime: formatDateTime(snapshot.UpdateTime),
|
|
}
|
|
}
|
|
|
|
func toRewardRecordView(row model.VoiceRoomRocketRewardRecord) RewardRecordView {
|
|
return RewardRecordView{
|
|
ID: row.ID,
|
|
LaunchNo: row.LaunchNo,
|
|
RoomID: row.RoomID,
|
|
DayKey: row.DayKey,
|
|
RoundNo: row.RoundNo,
|
|
Level: row.Level,
|
|
UserID: row.UserID,
|
|
RewardScene: row.RewardScene,
|
|
RewardType: row.RewardType,
|
|
RewardItemID: row.RewardItemID,
|
|
RewardName: row.RewardName,
|
|
RewardCover: row.RewardCover,
|
|
RewardAmount: row.RewardAmount,
|
|
ExpireDays: row.ExpireDays,
|
|
GrantStatus: row.GrantStatus,
|
|
PopupStatus: row.PopupStatus,
|
|
ErrorMessage: row.ErrorMessage,
|
|
GrantTime: formatDateTime(pointerTimeValue(row.GrantTime)),
|
|
CreateTime: formatDateTime(row.CreateTime),
|
|
}
|
|
}
|
|
|
|
func toRewardRecordViews(rows []model.VoiceRoomRocketRewardRecord, profiles map[int64]integration.UserProfile) []RewardRecordView {
|
|
records := make([]RewardRecordView, 0, len(rows))
|
|
for _, row := range rows {
|
|
view := toRewardRecordView(row)
|
|
if profile, ok := profiles[row.UserID]; ok {
|
|
applyRewardRecordProfile(&view, profile)
|
|
}
|
|
records = append(records, view)
|
|
}
|
|
return records
|
|
}
|
|
|
|
func applyRewardRecordProfile(view *RewardRecordView, profile integration.UserProfile) {
|
|
if strings.TrimSpace(profile.UserAvatar) != "" {
|
|
view.UserAvatar = profile.UserAvatar
|
|
}
|
|
if strings.TrimSpace(profile.UserNickname) != "" {
|
|
view.UserNickname = profile.UserNickname
|
|
}
|
|
if strings.TrimSpace(profile.Account) != "" {
|
|
view.Account = profile.Account
|
|
}
|
|
}
|
|
|
|
func toLaunchRecordView(row model.VoiceRoomRocketLaunch) LaunchRecordView {
|
|
return LaunchRecordView{
|
|
ID: row.ID,
|
|
LaunchNo: row.LaunchNo,
|
|
SysOrigin: row.SysOrigin,
|
|
RoomID: row.RoomID,
|
|
RoomAccount: row.RoomAccount,
|
|
DayKey: row.DayKey,
|
|
RoundNo: row.RoundNo,
|
|
Level: row.Level,
|
|
FinalEnergy: row.FinalEnergy,
|
|
Top1UserID: row.Top1UserID,
|
|
IgniteUserID: row.IgniteUserID,
|
|
InRoomSnapshotStatus: row.InRoomSnapshotStatus,
|
|
LaunchTime: formatDateTime(row.LaunchTime),
|
|
}
|
|
}
|
|
|
|
func userProfileToKingRecord(userID int64, rank int, score int64, profile integration.UserProfile) KingRecord {
|
|
return KingRecord{
|
|
Rank: rank,
|
|
UserID: userID,
|
|
UserAvatar: profile.UserAvatar,
|
|
UserNickname: profile.UserNickname,
|
|
Account: profile.Account,
|
|
CountryCode: profile.CountryCode,
|
|
CountryName: profile.CountryName,
|
|
ScoreEnergy: score,
|
|
}
|
|
}
|