package com.xuekairui.activity.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.xuekairui.activity.dto.ActivityCheckinCycleConfigRequest; import com.xuekairui.activity.dto.ActivityCheckinCycleStatusResponse; import com.xuekairui.activity.entity.ActivityCheckinCycleConfig; import com.xuekairui.activity.entity.ActivityCheckinCycleRecord; import com.xuekairui.activity.entity.CheckInRecord; import com.xuekairui.activity.mapper.ActivityCheckinCycleConfigMapper; import com.xuekairui.activity.mapper.ActivityCheckinCycleRecordMapper; import com.xuekairui.activity.mapper.CheckInRecordMapper; import com.xuekairui.user.enums.MembershipLevel; import com.xuekairui.user.service.MembershipService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; /** * 签到送高级会员活动服务(周期制)。 *
* 规则: * 1. 活动时间窗 2026-07-10 ~ 2026-07-31,所有用户可参加; * 2. 每周五至下周四为一个周期,累计 4 天签到即获得 7 天高级会员; * 3. 周期结束后的第一天(即下一周期首日周五)发放奖励; * 4. 每用户最多领取 3 次。 *
* 数据复用现有 t_checkin_record 签到记录统计周期内签到天数,不改动每日签到奖励逻辑。 */ @Slf4j @Service @RequiredArgsConstructor public class ActivityCheckinCycleService { private static final Long CONFIG_ID = 1L; /** 周期模式:周五至下周四 */ private static final String CYCLE_MODE_FRI_THU = "FRI_THU"; private final ActivityCheckinCycleConfigMapper configMapper; private final ActivityCheckinCycleRecordMapper cycleRecordMapper; private final CheckInRecordMapper checkInRecordMapper; private final MembershipService membershipService; // ========================================== // 配置管理 // ========================================== public ActivityCheckinCycleConfig getConfig() { ActivityCheckinCycleConfig config = configMapper.selectById(CONFIG_ID); if (config == null) { return ActivityCheckinCycleConfig.builder() .id(CONFIG_ID).enabled(0) .startTime(LocalDateTime.now()).endTime(LocalDateTime.now()) .cycleMode(CYCLE_MODE_FRI_THU).requiredDays(4).rewardDays(7).maxRewardCount(3) .build(); } return config; } @Transactional public ActivityCheckinCycleConfig updateConfig(ActivityCheckinCycleConfigRequest request) { ActivityCheckinCycleConfig config = getConfig(); config.setEnabled(request.getEnabled()); config.setStartTime(request.getStartTime()); config.setEndTime(request.getEndTime()); if (request.getCycleMode() != null) config.setCycleMode(request.getCycleMode()); if (request.getRequiredDays() != null) config.setRequiredDays(request.getRequiredDays()); if (request.getRewardDays() != null) config.setRewardDays(request.getRewardDays()); if (request.getMaxRewardCount() != null) config.setMaxRewardCount(request.getMaxRewardCount()); if (request.getRemark() != null) config.setRemark(request.getRemark()); if (config.getId() == null) { config.setId(CONFIG_ID); configMapper.insert(config); } else { configMapper.updateById(config); } log.info("签到送会员活动配置已更新: enabled={}, required={}, reward={}, max={}", config.getEnabled(), config.getRequiredDays(), config.getRewardDays(), config.getMaxRewardCount()); return config; } // ========================================== // 周期奖励发放(签到或查询状态时触发) // ========================================== /** * 尝试发放最近一个已结束周期的奖励。 *
* 在用户每日签到成功后、或查询周期状态时调用。
* 仅当活动开启、该周期累计签到达标、未发放过、且用户未达领取上限时发放。
*/
@Transactional
public boolean tryGrantCycleReward(Long userId) {
ActivityCheckinCycleConfig config = getConfig();
if (!isActive(config)) {
return false;
}
LocalDate today = LocalDate.now();
LocalDate currentCycleStart = cycleStartOf(today);
LocalDate prevCycleStart = currentCycleStart.minusDays(7);
LocalDate prevCycleEnd = prevCycleStart.plusDays(6);
// 仅处理已完整结束的周期(周期截止日 < 今天)
if (!prevCycleEnd.isBefore(today)) {
return false;
}
int checkinCount = countCheckinInCycle(userId, prevCycleStart, prevCycleEnd);
int requiredDays = config.getRequiredDays() != null ? config.getRequiredDays() : 4;
if (checkinCount < requiredDays) {
return false;
}
// 校验领取上限
long rewarded = cycleRecordMapper.countRewardedByUser(userId);
int maxReward = config.getMaxRewardCount() != null ? config.getMaxRewardCount() : 3;
if (rewarded >= maxReward) {
log.info("用户{}已达签到周期奖励上限{}次", userId, maxReward);
return false;
}
// 幂等:插入周期记录(uk_user_cycle 兜底),已存在则更新为已发放
ActivityCheckinCycleRecord exist = cycleRecordMapper.selectOne(
new LambdaQueryWrapper