554 lines
19 KiB
Go
554 lines
19 KiB
Go
package weekstar
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"chatapp3-golang/internal/model"
|
|
"chatapp3-golang/internal/utils"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// GetConfig 返回指定配置详情;未传 ID 时默认返回当前生效配置,若当前周不存在则返回最近未来周配置。
|
|
func (s *WeekStarService) GetConfig(ctx context.Context, sysOrigin string, id int64) (*WeekStarConfigResponse, error) {
|
|
var (
|
|
configRow *model.WeekStarActivityConfig
|
|
giftRows []model.WeekStarActivityGiftConfig
|
|
rewardRows []model.WeekStarActivityRewardConfig
|
|
err error
|
|
)
|
|
// 显式传了配置 ID 时按 ID 查;否则优先取当前生效配置,再回退未来周配置。
|
|
if id > 0 {
|
|
configRow, giftRows, rewardRows, err = s.loadConfigBundleByID(ctx, id, sysOrigin)
|
|
} else {
|
|
configRow, giftRows, rewardRows, err = s.loadPreferredAdminConfigBundle(ctx, sysOrigin, time.Now().In(s.location))
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if configRow == nil {
|
|
return &WeekStarConfigResponse{
|
|
Configured: false,
|
|
SysOrigin: s.normalizeSysOrigin(sysOrigin),
|
|
Timezone: s.location.String(),
|
|
GiftConfigs: []WeekStarGiftConfigPayload{},
|
|
RewardConfigs: []WeekStarRewardConfigPayload{},
|
|
}, nil
|
|
}
|
|
return s.buildConfigResponse(ctx, configRow, giftRows, rewardRows, true)
|
|
}
|
|
|
|
// PageConfigs 按系统分页返回多周活动配置列表,供后台查看和选择未来周配置。
|
|
func (s *WeekStarService) PageConfigs(ctx context.Context, sysOrigin string, cursor, limit int) (map[string]any, error) {
|
|
sysOrigin = s.normalizeSysOrigin(sysOrigin)
|
|
page, size := normalizePage(cursor, limit)
|
|
|
|
query := s.repo.DB.WithContext(ctx).
|
|
Model(&model.WeekStarActivityConfig{}).
|
|
Where("sys_origin = ?", sysOrigin)
|
|
|
|
var total int64
|
|
if err := query.Count(&total).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var rows []model.WeekStarActivityConfig
|
|
if total > 0 {
|
|
if err := query.
|
|
Order("start_at desc").
|
|
Order("id desc").
|
|
Offset((page - 1) * size).
|
|
Limit(size).
|
|
Find(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
records := make([]WeekStarConfigResponse, 0, len(rows))
|
|
for index := range rows {
|
|
gifts, rewards, err := s.loadConfigChildren(ctx, rows[index].ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
record, err := s.buildConfigResponse(ctx, &rows[index], gifts, rewards, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if record != nil {
|
|
records = append(records, *record)
|
|
}
|
|
}
|
|
|
|
return map[string]any{
|
|
"records": records,
|
|
"total": total,
|
|
}, nil
|
|
}
|
|
|
|
// SaveConfig 创建或更新未来周配置,并校验礼物、奖励和启用配置时间窗不重叠。
|
|
func (s *WeekStarService) SaveConfig(ctx context.Context, req SaveWeekStarConfigRequest) (*WeekStarConfigResponse, error) {
|
|
requestID := req.ID.Int64()
|
|
sysOrigin := s.normalizeSysOrigin(req.SysOrigin)
|
|
timezone := strings.TrimSpace(req.Timezone)
|
|
if timezone == "" {
|
|
timezone = s.cfg.WeekStar.Timezone
|
|
}
|
|
location, err := time.LoadLocation(timezone)
|
|
if err != nil {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_timezone", err.Error())
|
|
}
|
|
|
|
if len(req.GiftConfigs) != 3 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_gift_configs", "giftConfigs must contain exactly 3 gifts")
|
|
}
|
|
if len(req.RewardConfigs) != 3 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_reward_configs", "rewardConfigs must contain exactly 3 rewards")
|
|
}
|
|
|
|
startAt, err := parseDateTimeInLocation(req.StartAt, location)
|
|
if err != nil {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_start_at", err.Error())
|
|
}
|
|
endAt, err := parseDateTimeInLocation(req.EndAt, location)
|
|
if err != nil {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_end_at", err.Error())
|
|
}
|
|
if !endAt.After(startAt) {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_time_range", "endAt must be later than startAt")
|
|
}
|
|
now := time.Now().In(location)
|
|
if requestID > 0 {
|
|
if !startAt.After(now) {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_start_at", "only future-week configs can be updated")
|
|
}
|
|
} else if !startAt.After(now) {
|
|
currentCycleStart, _ := s.cycleBounds(now.In(s.location))
|
|
if startAt.In(s.location).Before(currentCycleStart) {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_start_at", "current-week config cannot start before this week")
|
|
}
|
|
if !endAt.After(now) {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_time_range", "current-week config must end in the future")
|
|
}
|
|
}
|
|
|
|
giftIDs := make(map[int64]struct{}, len(req.GiftConfigs))
|
|
// 先把礼物和奖励配置标准化,避免后续事务里还掺杂输入清洗逻辑。
|
|
normalizedGifts := make([]model.WeekStarActivityGiftConfig, 0, len(req.GiftConfigs))
|
|
for index, item := range req.GiftConfigs {
|
|
giftID := item.GiftID.Int64()
|
|
if giftID <= 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_gift_id", "giftId is required")
|
|
}
|
|
if _, exists := giftIDs[giftID]; exists {
|
|
return nil, NewAppError(http.StatusBadRequest, "duplicate_gift_id", "giftConfigs must not contain duplicate gifts")
|
|
}
|
|
giftIDs[giftID] = struct{}{}
|
|
id, idErr := utils.NextID()
|
|
if idErr != nil {
|
|
return nil, idErr
|
|
}
|
|
normalizedGifts = append(normalizedGifts, model.WeekStarActivityGiftConfig{
|
|
ID: id,
|
|
GiftID: giftID,
|
|
GiftName: strings.TrimSpace(item.GiftName),
|
|
GiftPhoto: strings.TrimSpace(item.GiftPhoto),
|
|
GiftCandy: item.GiftCandy,
|
|
Sort: index + 1,
|
|
})
|
|
}
|
|
|
|
rewardRanks := map[int]struct{}{1: {}, 2: {}, 3: {}}
|
|
seenRanks := make(map[int]struct{}, len(req.RewardConfigs))
|
|
normalizedRewards := make([]model.WeekStarActivityRewardConfig, 0, len(req.RewardConfigs))
|
|
for _, item := range req.RewardConfigs {
|
|
rewardGroupID := item.RewardGroupID.Int64()
|
|
if _, ok := rewardRanks[item.Rank]; !ok {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_reward_rank", "reward rank must be 1, 2, or 3")
|
|
}
|
|
if rewardGroupID <= 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_reward_group_id", "rewardGroupId is required")
|
|
}
|
|
if _, exists := seenRanks[item.Rank]; exists {
|
|
return nil, NewAppError(http.StatusBadRequest, "duplicate_reward_rank", "rewardConfigs must not contain duplicate ranks")
|
|
}
|
|
rewardItems, rewardGroupName, err := s.loadRewardPreviewItems(ctx, rewardGroupID)
|
|
if err != nil || len(rewardItems) == 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_reward_group_id", "reward group detail is empty or unavailable")
|
|
}
|
|
seenRanks[item.Rank] = struct{}{}
|
|
id, idErr := utils.NextID()
|
|
if idErr != nil {
|
|
return nil, idErr
|
|
}
|
|
if strings.TrimSpace(rewardGroupName) == "" {
|
|
rewardGroupName = item.RewardGroupName
|
|
}
|
|
normalizedRewards = append(normalizedRewards, model.WeekStarActivityRewardConfig{
|
|
ID: id,
|
|
Rank: item.Rank,
|
|
RewardGroupID: rewardGroupID,
|
|
RewardGroupName: strings.TrimSpace(rewardGroupName),
|
|
})
|
|
}
|
|
|
|
sort.Slice(normalizedRewards, func(i, j int) bool {
|
|
return normalizedRewards[i].Rank < normalizedRewards[j].Rank
|
|
})
|
|
|
|
var savedConfigID int64
|
|
// 主配置与子配置在同一个事务中写入,避免出现半保存状态。
|
|
if err := s.repo.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
now := time.Now()
|
|
storedStartAt := s.toStorageWallClock(startAt)
|
|
storedEndAt := s.toStorageWallClock(endAt)
|
|
var configRow model.WeekStarActivityConfig
|
|
if requestID > 0 {
|
|
if err := tx.Where("id = ?", requestID).First(&configRow).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return NewAppError(http.StatusNotFound, "week_star_config_not_found", "config not found")
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
if !s.isConfigEditable(&configRow, time.Now().In(s.location)) {
|
|
return NewAppError(http.StatusBadRequest, "week_star_config_readonly", "only future-week configs can be edited")
|
|
}
|
|
if strings.TrimSpace(req.SysOrigin) == "" {
|
|
sysOrigin = configRow.SysOrigin
|
|
}
|
|
} else {
|
|
configID, idErr := utils.NextID()
|
|
if idErr != nil {
|
|
return idErr
|
|
}
|
|
configRow = model.WeekStarActivityConfig{
|
|
ID: configID,
|
|
SysOrigin: sysOrigin,
|
|
Enabled: req.Enabled,
|
|
StartAt: storedStartAt,
|
|
EndAt: storedEndAt,
|
|
Timezone: timezone,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
}
|
|
|
|
if req.Enabled {
|
|
if err := s.ensureNoEnabledOverlap(ctx, tx, sysOrigin, configRow.ID, storedStartAt, storedEndAt); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
configRow.SysOrigin = sysOrigin
|
|
configRow.Enabled = req.Enabled
|
|
configRow.StartAt = storedStartAt
|
|
configRow.EndAt = storedEndAt
|
|
configRow.Timezone = timezone
|
|
configRow.UpdateTime = now
|
|
|
|
if requestID > 0 {
|
|
if err := tx.Model(&model.WeekStarActivityConfig{}).
|
|
Where("id = ?", configRow.ID).
|
|
Updates(map[string]any{
|
|
"sys_origin": configRow.SysOrigin,
|
|
"enabled": configRow.Enabled,
|
|
"start_at": configRow.StartAt,
|
|
"end_at": configRow.EndAt,
|
|
"timezone": configRow.Timezone,
|
|
"update_time": configRow.UpdateTime,
|
|
}).Error; err != nil {
|
|
return err
|
|
}
|
|
} else if err := tx.Create(&configRow).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tx.Where("config_id = ?", configRow.ID).Delete(&model.WeekStarActivityGiftConfig{}).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Where("config_id = ?", configRow.ID).Delete(&model.WeekStarActivityRewardConfig{}).Error; err != nil {
|
|
return err
|
|
}
|
|
for index := range normalizedGifts {
|
|
normalizedGifts[index].ConfigID = configRow.ID
|
|
normalizedGifts[index].CreateTime = now
|
|
normalizedGifts[index].UpdateTime = now
|
|
}
|
|
for index := range normalizedRewards {
|
|
normalizedRewards[index].ConfigID = configRow.ID
|
|
normalizedRewards[index].CreateTime = now
|
|
normalizedRewards[index].UpdateTime = now
|
|
}
|
|
if err := tx.Create(&normalizedGifts).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Create(&normalizedRewards).Error; err != nil {
|
|
return err
|
|
}
|
|
savedConfigID = configRow.ID
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s.GetConfig(ctx, sysOrigin, savedConfigID)
|
|
}
|
|
|
|
// buildConfigResponse 把配置主表和子表组装成后台接口需要的展示结构。
|
|
func (s *WeekStarService) buildConfigResponse(ctx context.Context, configRow *model.WeekStarActivityConfig, giftRows []model.WeekStarActivityGiftConfig, rewardRows []model.WeekStarActivityRewardConfig, includeItems bool) (*WeekStarConfigResponse, error) {
|
|
rewards := make([]WeekStarRewardConfigPayload, 0, len(rewardRows))
|
|
sort.Slice(rewardRows, func(i, j int) bool {
|
|
return rewardRows[i].Rank < rewardRows[j].Rank
|
|
})
|
|
for _, row := range rewardRows {
|
|
payload := WeekStarRewardConfigPayload{
|
|
Rank: row.Rank,
|
|
RewardGroupID: row.RewardGroupID,
|
|
RewardGroupName: row.RewardGroupName,
|
|
}
|
|
if includeItems && row.RewardGroupID > 0 {
|
|
items, groupName, err := s.loadRewardPreviewItems(ctx, row.RewardGroupID)
|
|
if err == nil {
|
|
payload.Items = items
|
|
if strings.TrimSpace(groupName) != "" {
|
|
payload.RewardGroupName = groupName
|
|
}
|
|
}
|
|
}
|
|
rewards = append(rewards, payload)
|
|
}
|
|
now := time.Now().In(s.location)
|
|
return &WeekStarConfigResponse{
|
|
Configured: true,
|
|
ID: configRow.ID,
|
|
SysOrigin: configRow.SysOrigin,
|
|
Enabled: configRow.Enabled,
|
|
Status: s.resolveActivityStatus(configRow, now),
|
|
Editable: s.isConfigEditable(configRow, now),
|
|
StartAt: formatDateTime(s.fromStorageWallClock(configRow.StartAt)),
|
|
EndAt: formatDateTime(s.fromStorageWallClock(configRow.EndAt)),
|
|
Timezone: configRow.Timezone,
|
|
UpdateTime: formatDateTime(configRow.UpdateTime),
|
|
GiftConfigs: buildGiftPayloads(giftRows),
|
|
RewardConfigs: rewards,
|
|
}, nil
|
|
}
|
|
|
|
// buildRewardPreview 把奖励配置组装成 H5 可直接渲染的奖励预览数据。
|
|
func (s *WeekStarService) buildRewardPreview(ctx context.Context, rewardRows []model.WeekStarActivityRewardConfig) ([]WeekStarRewardConfigPayload, error) {
|
|
sort.Slice(rewardRows, func(i, j int) bool {
|
|
return rewardRows[i].Rank < rewardRows[j].Rank
|
|
})
|
|
result := make([]WeekStarRewardConfigPayload, 0, len(rewardRows))
|
|
for _, row := range rewardRows {
|
|
payload := WeekStarRewardConfigPayload{
|
|
Rank: row.Rank,
|
|
RewardGroupID: row.RewardGroupID,
|
|
RewardGroupName: row.RewardGroupName,
|
|
}
|
|
if row.RewardGroupID > 0 {
|
|
items, groupName, err := s.loadRewardPreviewItems(ctx, row.RewardGroupID)
|
|
if err == nil {
|
|
payload.Items = items
|
|
if strings.TrimSpace(groupName) != "" {
|
|
payload.RewardGroupName = groupName
|
|
}
|
|
}
|
|
}
|
|
result = append(result, payload)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// loadRewardPreviewItems 从 Java 奖励组详情接口拉取预览物品列表。
|
|
func (s *WeekStarService) loadRewardPreviewItems(ctx context.Context, rewardGroupID int64) ([]WeekStarRewardPreviewItem, string, error) {
|
|
detail, err := s.java.GetRewardGroupDetail(ctx, rewardGroupID)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
items := make([]WeekStarRewardPreviewItem, 0, len(detail.RewardConfigList))
|
|
for _, item := range detail.RewardConfigList {
|
|
items = append(items, WeekStarRewardPreviewItem{
|
|
ID: int64(item.ID),
|
|
Type: item.Type,
|
|
Name: item.Name,
|
|
Content: item.Content,
|
|
Quantity: int64(item.Quantity),
|
|
Cover: item.Cover,
|
|
Remark: item.Remark,
|
|
})
|
|
}
|
|
sort.Slice(items, func(i, j int) bool {
|
|
return items[i].ID < items[j].ID
|
|
})
|
|
return items, detail.Name, nil
|
|
}
|
|
|
|
// loadConfigBundleByID 按配置 ID 读取完整配置,并校验它是否属于目标系统。
|
|
func (s *WeekStarService) loadConfigBundleByID(ctx context.Context, id int64, sysOrigin string) (*model.WeekStarActivityConfig, []model.WeekStarActivityGiftConfig, []model.WeekStarActivityRewardConfig, error) {
|
|
if id <= 0 {
|
|
return nil, nil, nil, nil
|
|
}
|
|
var configRow model.WeekStarActivityConfig
|
|
err := s.repo.DB.WithContext(ctx).Where("id = ?", id).First(&configRow).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, nil, nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if strings.TrimSpace(sysOrigin) != "" && configRow.SysOrigin != s.normalizeSysOrigin(sysOrigin) {
|
|
return nil, nil, nil, nil
|
|
}
|
|
gifts, rewards, err := s.loadConfigChildren(ctx, configRow.ID)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
return &configRow, gifts, rewards, nil
|
|
}
|
|
|
|
// loadPreferredAdminConfigBundle 优先选择当前周配置,其次选择最近未来周配置,再回退到最新一条配置。
|
|
func (s *WeekStarService) loadPreferredAdminConfigBundle(ctx context.Context, sysOrigin string, at time.Time) (*model.WeekStarActivityConfig, []model.WeekStarActivityGiftConfig, []model.WeekStarActivityRewardConfig, error) {
|
|
if configRow, gifts, rewards, err := s.loadActiveConfigBundle(ctx, sysOrigin, at); err != nil || configRow != nil {
|
|
return configRow, gifts, rewards, err
|
|
}
|
|
if configRow, gifts, rewards, err := s.loadUpcomingConfigBundle(ctx, sysOrigin, at); err != nil || configRow != nil {
|
|
return configRow, gifts, rewards, err
|
|
}
|
|
sysOrigin = s.normalizeSysOrigin(sysOrigin)
|
|
var configRow model.WeekStarActivityConfig
|
|
err := s.repo.DB.WithContext(ctx).
|
|
Where("sys_origin = ?", sysOrigin).
|
|
Order("start_at desc").
|
|
Order("id desc").
|
|
First(&configRow).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, nil, nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
gifts, rewards, err := s.loadConfigChildren(ctx, configRow.ID)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
return &configRow, gifts, rewards, nil
|
|
}
|
|
|
|
// loadActiveConfigBundle 根据事件时间或当前时间查找正在生效的启用配置,时间窗语义为 [startAt, endAt)。
|
|
func (s *WeekStarService) loadActiveConfigBundle(ctx context.Context, sysOrigin string, at time.Time) (*model.WeekStarActivityConfig, []model.WeekStarActivityGiftConfig, []model.WeekStarActivityRewardConfig, error) {
|
|
sysOrigin = s.normalizeSysOrigin(sysOrigin)
|
|
storedAt := s.toStorageWallClock(at)
|
|
var configRow model.WeekStarActivityConfig
|
|
err := s.repo.DB.WithContext(ctx).
|
|
Where(
|
|
"sys_origin = ? AND enabled = ? AND start_at <= ? AND end_at > ?",
|
|
sysOrigin,
|
|
true,
|
|
storedAt,
|
|
storedAt,
|
|
).
|
|
Order("start_at asc").
|
|
Order("id asc").
|
|
First(&configRow).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, nil, nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
gifts, rewards, err := s.loadConfigChildren(ctx, configRow.ID)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
return &configRow, gifts, rewards, nil
|
|
}
|
|
|
|
// loadUpcomingConfigBundle 查找最近的未来周启用配置,用于当前周结束后的自动切换展示。
|
|
func (s *WeekStarService) loadUpcomingConfigBundle(ctx context.Context, sysOrigin string, at time.Time) (*model.WeekStarActivityConfig, []model.WeekStarActivityGiftConfig, []model.WeekStarActivityRewardConfig, error) {
|
|
sysOrigin = s.normalizeSysOrigin(sysOrigin)
|
|
var configRow model.WeekStarActivityConfig
|
|
err := s.repo.DB.WithContext(ctx).
|
|
Where("sys_origin = ? AND enabled = ? AND start_at > ?", sysOrigin, true, s.toStorageWallClock(at)).
|
|
Order("start_at asc").
|
|
Order("id asc").
|
|
First(&configRow).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, nil, nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
gifts, rewards, err := s.loadConfigChildren(ctx, configRow.ID)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
return &configRow, gifts, rewards, nil
|
|
}
|
|
|
|
// ensureNoEnabledOverlap 校验同一系统下启用状态的配置时间窗不重叠。
|
|
func (s *WeekStarService) ensureNoEnabledOverlap(ctx context.Context, tx *gorm.DB, sysOrigin string, excludeID int64, startAt, endAt time.Time) error {
|
|
query := tx.WithContext(ctx).
|
|
Model(&model.WeekStarActivityConfig{}).
|
|
Where("sys_origin = ? AND enabled = ?", sysOrigin, true).
|
|
Where("start_at < ? AND end_at > ?", endAt, startAt)
|
|
if excludeID > 0 {
|
|
query = query.Where("id <> ?", excludeID)
|
|
}
|
|
var count int64
|
|
if err := query.Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count > 0 {
|
|
return NewAppError(http.StatusBadRequest, "week_star_config_overlap", "enabled configs must not overlap in time range")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// isConfigEditable 判断配置是否仍处于未开始状态,只有未来周配置允许后台编辑。
|
|
func (s *WeekStarService) isConfigEditable(configRow *model.WeekStarActivityConfig, now time.Time) bool {
|
|
if configRow == nil {
|
|
return false
|
|
}
|
|
return s.fromStorageWallClock(configRow.StartAt).After(now.In(s.location))
|
|
}
|
|
|
|
// loadConfigChildren 读取某个活动配置下的指定礼物和奖励组子表。
|
|
func (s *WeekStarService) loadConfigChildren(ctx context.Context, configID int64) ([]model.WeekStarActivityGiftConfig, []model.WeekStarActivityRewardConfig, error) {
|
|
var gifts []model.WeekStarActivityGiftConfig
|
|
if err := s.repo.DB.WithContext(ctx).
|
|
Where("config_id = ?", configID).
|
|
Order("sort asc").
|
|
Find(&gifts).Error; err != nil {
|
|
return nil, nil, err
|
|
}
|
|
var rewards []model.WeekStarActivityRewardConfig
|
|
if err := s.repo.DB.WithContext(ctx).
|
|
Where("config_id = ?", configID).
|
|
Order("`rank` asc").
|
|
Find(&rewards).Error; err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return gifts, rewards, nil
|
|
}
|
|
|
|
// resolveActivityStatus 根据当前时间判断配置处于未开始、进行中、已结束或已关闭状态。
|
|
func (s *WeekStarService) resolveActivityStatus(configRow *model.WeekStarActivityConfig, now time.Time) string {
|
|
if configRow == nil || !configRow.Enabled {
|
|
return weekStarActivityStatusDisabled
|
|
}
|
|
startAt := s.fromStorageWallClock(configRow.StartAt)
|
|
endAt := s.fromStorageWallClock(configRow.EndAt)
|
|
switch {
|
|
case now.Before(startAt):
|
|
return weekStarActivityStatusNotStarted
|
|
case !now.Before(endAt):
|
|
return weekStarActivityStatusEnded
|
|
default:
|
|
return weekStarActivityStatusOngoing
|
|
}
|
|
}
|