Sfoglia il codice sorgente

活动管理更新

liuchengsen 1 mese fa
parent
commit
a023a88c2d
15 ha cambiato i file con 249 aggiunte e 484 eliminazioni
  1. 13 10
      zhijiayun-activity/src/main/java/com/xuekairui/activity/controller/ActivityController.java
  2. 12 10
      zhijiayun-activity/src/main/java/com/xuekairui/activity/controller/AdminActivityController.java
  3. 0 31
      zhijiayun-activity/src/main/java/com/xuekairui/activity/dto/ActivityCheckinCycleConfigRequest.java
  4. 0 50
      zhijiayun-activity/src/main/java/com/xuekairui/activity/entity/ActivityCheckinCycleConfig.java
  5. 1 0
      zhijiayun-activity/src/main/java/com/xuekairui/activity/entity/CheckInPeriod.java
  6. 11 15
      zhijiayun-activity/src/main/java/com/xuekairui/activity/entity/CheckInRewardRecord.java
  7. 0 9
      zhijiayun-activity/src/main/java/com/xuekairui/activity/mapper/ActivityCheckinCycleConfigMapper.java
  8. 0 15
      zhijiayun-activity/src/main/java/com/xuekairui/activity/mapper/ActivityCheckinCycleRecordMapper.java
  9. 1 2
      zhijiayun-activity/src/main/java/com/xuekairui/activity/mapper/CheckInPeriodMapper.java
  10. 15 0
      zhijiayun-activity/src/main/java/com/xuekairui/activity/mapper/CheckInRewardRecordMapper.java
  11. 0 321
      zhijiayun-activity/src/main/java/com/xuekairui/activity/service/ActivityCheckinCycleService.java
  12. 2 2
      zhijiayun-activity/src/main/java/com/xuekairui/activity/service/CheckInConfigService.java
  13. 140 19
      zhijiayun-activity/src/main/java/com/xuekairui/activity/service/CheckInService.java
  14. 31 0
      zhijiayun-gateway/src/main/resources/db/migration-v17.sql
  15. 23 0
      zhijiayun-gateway/src/main/resources/db/migration-v18.sql

+ 13 - 10
zhijiayun-activity/src/main/java/com/xuekairui/activity/controller/ActivityController.java

@@ -7,12 +7,12 @@ import com.xuekairui.activity.dto.ActivityInviteStatusResponse;
 import com.xuekairui.activity.dto.ActivityLotteryResultResponse;
 import com.xuekairui.activity.dto.ActivityLotteryStatusResponse;
 import com.xuekairui.activity.dto.ActivityRewardRecordResponse;
-import com.xuekairui.activity.entity.ActivityCheckinCycleRecord;
 import com.xuekairui.activity.entity.ActivityLotteryPrize;
 import com.xuekairui.activity.entity.ActivityLotteryRecord;
-import com.xuekairui.activity.service.ActivityCheckinCycleService;
+import com.xuekairui.activity.entity.CheckInPeriod;
 import com.xuekairui.activity.service.ActivityInviteService;
 import com.xuekairui.activity.service.ActivityLotteryService;
+import com.xuekairui.activity.service.CheckInService;
 import com.xuekairui.common.Result;
 import com.xuekairui.common.ValidateUtil;
 import jakarta.servlet.http.HttpServletRequest;
@@ -36,7 +36,7 @@ import java.util.List;
 public class ActivityController {
 
     private final ActivityInviteService activityInviteService;
-    private final ActivityCheckinCycleService activityCheckinCycleService;
+    private final CheckInService checkInService;
     private final ActivityLotteryService activityLotteryService;
 
     // ========== 邀请有礼 ==========
@@ -60,17 +60,20 @@ public class ActivityController {
     @GetMapping("/checkin-cycle/status")
     public Result<ActivityCheckinCycleStatusResponse> checkinCycleStatus(HttpServletRequest request) {
         Long userId = (Long) request.getAttribute("userId");
-        return Result.success(activityCheckinCycleService.getStatus(userId));
+        return Result.success(checkInService.getCheckinCycleStatus(userId));
     }
 
     /**
-     * 我的签到周期奖励记录
-     * GET /api/activity/checkin-cycle/rewards
+     * 我的签到周期奖励记录(分页)
+     * GET /api/activity/checkin-cycle/rewards?page=1&size=20
      */
     @GetMapping("/checkin-cycle/rewards")
-    public Result<List<ActivityCheckinCycleRecord>> checkinCycleRewards(HttpServletRequest request) {
+    public Result<Page<ActivityRewardRecordResponse>> checkinCycleRewards(
+            HttpServletRequest request,
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
         Long userId = (Long) request.getAttribute("userId");
-        return Result.success(activityCheckinCycleService.listMyCycleRewards(userId));
+        return Result.success(checkInService.listMyCycleRewards(userId, page, size));
     }
 
     // ========== 比价抽奖 ==========
@@ -127,7 +130,7 @@ public class ActivityController {
         Long userId = (Long) request.getAttribute("userId");
 
         ActivityInviteStatusResponse inviteStatus = activityInviteService.getStatus(userId);
-        ActivityCheckinCycleStatusResponse checkinStatus = activityCheckinCycleService.getStatus(userId);
+        ActivityCheckinCycleStatusResponse checkinStatus = checkInService.getCheckinCycleStatus(userId);
         ActivityLotteryStatusResponse lotteryStatus = activityLotteryService.getStatus(userId);
 
         List<ActivityAvailableResponse.ActivityBrief> activities = List.of(
@@ -198,7 +201,7 @@ public class ActivityController {
         Long userId = (Long) request.getAttribute("userId");
         Page<ActivityRewardRecordResponse> result = switch (type.toUpperCase()) {
             case "INVITE" -> activityInviteService.listInviteRewardRecords(userId, page, size);
-            case "CHECKIN" -> activityCheckinCycleService.listCycleRewards(userId, page, size);
+            case "CHECKIN" -> checkInService.listCycleRewards(userId, page, size);
             case "LOTTERY" -> activityLotteryService.listLotteryRewards(userId, page, size);
             default -> throw new IllegalArgumentException(
                     "不支持的活动类型: " + type + ",可选值: INVITE, CHECKIN, LOTTERY");

+ 12 - 10
zhijiayun-activity/src/main/java/com/xuekairui/activity/controller/AdminActivityController.java

@@ -1,19 +1,20 @@
 package com.xuekairui.activity.controller;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.xuekairui.activity.dto.ActivityCheckinCycleConfigRequest;
 import com.xuekairui.activity.dto.ActivityInviteConfigRequest;
 import com.xuekairui.activity.dto.ActivityLotteryConfigRequest;
 import com.xuekairui.activity.dto.ActivityLotteryPrizeRequest;
 import com.xuekairui.activity.dto.ActivityRewardRecordResponse;
-import com.xuekairui.activity.entity.ActivityCheckinCycleConfig;
+import com.xuekairui.activity.dto.CheckInConfigRequest;
 import com.xuekairui.activity.entity.ActivityInviteConfig;
 import com.xuekairui.activity.entity.ActivityLotteryConfig;
 import com.xuekairui.activity.entity.ActivityLotteryPrize;
 import com.xuekairui.activity.entity.ActivityLotteryRecord;
-import com.xuekairui.activity.service.ActivityCheckinCycleService;
+import com.xuekairui.activity.entity.CheckInConfig;
 import com.xuekairui.activity.service.ActivityInviteService;
 import com.xuekairui.activity.service.ActivityLotteryService;
+import com.xuekairui.activity.service.CheckInConfigService;
+import com.xuekairui.activity.service.CheckInService;
 import com.xuekairui.common.Result;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.validation.Valid;
@@ -47,7 +48,8 @@ import java.util.Map;
 public class AdminActivityController {
 
     private final ActivityInviteService activityInviteService;
-    private final ActivityCheckinCycleService activityCheckinCycleService;
+    private final CheckInConfigService checkInConfigService;
+    private final CheckInService checkInService;
     private final ActivityLotteryService activityLotteryService;
 
     // ========== 邀请有礼配置 ==========
@@ -65,14 +67,14 @@ public class AdminActivityController {
     // ========== 签到送会员配置 ==========
 
     @GetMapping("/checkin-cycle/config")
-    public Result<ActivityCheckinCycleConfig> getCheckinCycleConfig() {
-        return Result.success(activityCheckinCycleService.getConfig());
+    public Result<CheckInConfig> getCheckinCycleConfig() {
+        return Result.success(checkInConfigService.getConfig());
     }
 
     @PutMapping("/checkin-cycle/config")
-    public Result<ActivityCheckinCycleConfig> updateCheckinCycleConfig(
-            @Valid @RequestBody ActivityCheckinCycleConfigRequest request) {
-        return Result.success(activityCheckinCycleService.updateConfig(request));
+    public Result<CheckInConfig> updateCheckinCycleConfig(
+            @Valid @RequestBody CheckInConfigRequest request) {
+        return Result.success(checkInConfigService.updateConfig(request));
     }
 
     // ========== 比价抽奖配置 ==========
@@ -143,7 +145,7 @@ public class AdminActivityController {
             @RequestParam(required = false) Long userId) {
         Page<ActivityRewardRecordResponse> result = switch (type.toUpperCase()) {
             case "INVITE" -> activityInviteService.listInviteRewardRecords(userId, page, size);
-            case "CHECKIN" -> activityCheckinCycleService.listCycleRewards(userId, page, size);
+            case "CHECKIN" -> checkInService.listCycleRewards(userId, page, size);
             case "LOTTERY" -> activityLotteryService.listLotteryRewards(userId, page, size);
             default -> throw new IllegalArgumentException(
                     "不支持的活动类型: " + type + ",可选值: INVITE, CHECKIN, LOTTERY");

+ 0 - 31
zhijiayun-activity/src/main/java/com/xuekairui/activity/dto/ActivityCheckinCycleConfigRequest.java

@@ -1,31 +0,0 @@
-package com.xuekairui.activity.dto;
-
-import jakarta.validation.constraints.NotNull;
-import lombok.Data;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import java.time.LocalDateTime;
-
-/**
- * 签到送会员活动配置更新请求(运营端)
- */
-@Data
-public class ActivityCheckinCycleConfigRequest {
-
-    @NotNull(message = "启用状态不能为空")
-    private Integer enabled;
-
-    @NotNull(message = "开始时间不能为空")
-    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
-    private LocalDateTime startTime;
-
-    @NotNull(message = "结束时间不能为空")
-    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
-    private LocalDateTime endTime;
-
-    private String cycleMode;
-    private Integer requiredDays;
-    private Integer rewardDays;
-    private Integer maxRewardCount;
-    private String remark;
-}

+ 0 - 50
zhijiayun-activity/src/main/java/com/xuekairui/activity/entity/ActivityCheckinCycleConfig.java

@@ -1,50 +0,0 @@
-package com.xuekairui.activity.entity;
-
-import com.baomidou.mybatisplus.annotation.*;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-
-import java.time.LocalDateTime;
-
-/**
- * 签到送会员活动配置(全局唯一,id=1)
- */
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@TableName("t_activity_checkin_cycle_config")
-public class ActivityCheckinCycleConfig {
-
-    @TableId(type = IdType.AUTO)
-    private Long id;
-
-    @Builder.Default
-    private Integer enabled = 0;
-
-    private LocalDateTime startTime;
-    private LocalDateTime endTime;
-
-    /** 周期模式:FRI_THU(周五至周四)/ MON_SUN(周一至周日) */
-    @Builder.Default
-    private String cycleMode = "FRI_THU";
-
-    @Builder.Default
-    private Integer requiredDays = 4;
-
-    @Builder.Default
-    private Integer rewardDays = 7;
-
-    @Builder.Default
-    private Integer maxRewardCount = 3;
-
-    private String remark;
-
-    @TableField(fill = FieldFill.INSERT)
-    private LocalDateTime createTime;
-
-    @TableField(fill = FieldFill.INSERT_UPDATE)
-    private LocalDateTime updateTime;
-}

+ 1 - 0
zhijiayun-activity/src/main/java/com/xuekairui/activity/entity/CheckInPeriod.java

@@ -15,6 +15,7 @@ import java.time.LocalDateTime;
  * 周期模式 cycleMode 决定周期的星期范围(如 FRI_THU=周五到下周四),
  * 每条记录定义该周期的起止日期、所需签到天数、达标奖励。
  * 活动全局开关和默认值在 t_checkin_config 中。
+ * 用户奖励发放记录在 t_checkin_reward_record 中。
  */
 @Data
 @NoArgsConstructor

+ 11 - 15
zhijiayun-activity/src/main/java/com/xuekairui/activity/entity/ActivityCheckinCycleRecord.java → zhijiayun-activity/src/main/java/com/xuekairui/activity/entity/CheckInRewardRecord.java

@@ -1,43 +1,39 @@
 package com.xuekairui.activity.entity;
 
 import com.baomidou.mybatisplus.annotation.*;
-import lombok.Data;
-import lombok.NoArgsConstructor;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
 
-import java.time.LocalDate;
 import java.time.LocalDateTime;
 
 /**
- * 签到周期奖励记录(每用户每周期一条)
+ * 签到周期奖励发放记录(每用户每周期一条,唯一索引 uk_user_period 防重
  */
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
-@TableName("t_activity_checkin_cycle_record")
-public class ActivityCheckinCycleRecord {
+@TableName("t_checkin_reward_record")
+public class CheckInRewardRecord {
 
     @TableId(type = IdType.AUTO)
     private Long id;
 
+    /** 用户ID */
     private Long userId;
 
-    /** 周期起始日(周五) */
-    private LocalDate cycleStart;
-
-    /** 周期截止日(周四) */
-    private LocalDate cycleEnd;
+    /** 周期ID(关联 t_checkin_period.id) */
+    private Long periodId;
 
+    /** 该周期内用户签到天数 */
     @Builder.Default
     private Integer checkinCount = 0;
 
-    @Builder.Default
-    private Integer rewardGranted = 0;
-
+    /** 奖励发放时间 */
     private LocalDateTime grantTime;
 
     @TableField(fill = FieldFill.INSERT)
     private LocalDateTime createTime;
-}
+}

+ 0 - 9
zhijiayun-activity/src/main/java/com/xuekairui/activity/mapper/ActivityCheckinCycleConfigMapper.java

@@ -1,9 +0,0 @@
-package com.xuekairui.activity.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.xuekairui.activity.entity.ActivityCheckinCycleConfig;
-import org.apache.ibatis.annotations.Mapper;
-
-@Mapper
-public interface ActivityCheckinCycleConfigMapper extends BaseMapper<ActivityCheckinCycleConfig> {
-}

+ 0 - 15
zhijiayun-activity/src/main/java/com/xuekairui/activity/mapper/ActivityCheckinCycleRecordMapper.java

@@ -1,15 +0,0 @@
-package com.xuekairui.activity.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.xuekairui.activity.entity.ActivityCheckinCycleRecord;
-import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Param;
-import org.apache.ibatis.annotations.Select;
-
-@Mapper
-public interface ActivityCheckinCycleRecordMapper extends BaseMapper<ActivityCheckinCycleRecord> {
-
-    /** 统计用户已领取的周期奖励次数(用于3次上限校验) */
-    @Select("SELECT COUNT(*) FROM t_activity_checkin_cycle_record WHERE user_id = #{userId} AND reward_granted = 1")
-    long countRewardedByUser(@Param("userId") Long userId);
-}

+ 1 - 2
zhijiayun-activity/src/main/java/com/xuekairui/activity/mapper/CheckInPeriodMapper.java

@@ -7,7 +7,6 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
 import java.time.LocalDate;
-import java.util.Optional;
 
 @Mapper
 public interface CheckInPeriodMapper extends BaseMapper<CheckInPeriod> {
@@ -19,4 +18,4 @@ public interface CheckInPeriodMapper extends BaseMapper<CheckInPeriod> {
     /** 查询最近一个已结束的周期(已结束但可能未发奖) */
     @Select("SELECT * FROM t_checkin_period WHERE end_date < #{date} AND status = 'CLOSED' ORDER BY end_date DESC LIMIT 1")
     CheckInPeriod findLastClosedByDate(@Param("date") LocalDate date);
-}
+}

+ 15 - 0
zhijiayun-activity/src/main/java/com/xuekairui/activity/mapper/CheckInRewardRecordMapper.java

@@ -0,0 +1,15 @@
+package com.xuekairui.activity.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.xuekairui.activity.entity.CheckInRewardRecord;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+@Mapper
+public interface CheckInRewardRecordMapper extends BaseMapper<CheckInRewardRecord> {
+
+    /** 统计用户已领取的周期奖励次数(用于上限校验,按 periodId 去重) */
+    @Select("SELECT COUNT(*) FROM t_checkin_reward_record WHERE user_id = #{userId}")
+    long countRewardedByUser(@Param("userId") Long userId);
+}

+ 0 - 321
zhijiayun-activity/src/main/java/com/xuekairui/activity/service/ActivityCheckinCycleService.java

@@ -1,321 +0,0 @@
-package com.xuekairui.activity.service;
-
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.xuekairui.activity.dto.ActivityCheckinCycleConfigRequest;
-import com.xuekairui.activity.dto.ActivityCheckinCycleStatusResponse;
-import com.xuekairui.activity.dto.ActivityRewardRecordResponse;
-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;
-
-/**
- * 签到送高级会员活动服务(周期制)。
- * <p>
- * 规则:
- * 1. 活动时间窗可配置,所有用户可参加;
- * 2. 周期模式可配置:MON_SUN(周一至周日)或 FRI_THU(周五至下周四);
- * 3. 周期内累计签到达标天数即获得奖励;
- * 4. 周期结束后的第一天发放奖励;
- * 5. 每用户最多领取指定次数。
- * <p>
- * 数据复用现有 t_checkin_record 签到记录统计周期内签到天数,不改动每日签到奖励逻辑。
- */
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class ActivityCheckinCycleService {
-
-    private static final Long CONFIG_ID = 1L;
-    /** 周期模式:周一至周日 */
-    private static final String CYCLE_MODE_MON_SUN = "MON_SUN";
-    /** 周期模式:周五至下周四 */
-    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;
-    }
-
-    // ==========================================
-    // 周期奖励发放(签到或查询状态时触发)
-    // ==========================================
-
-    /**
-     * 尝试发放最近一个已结束周期的奖励。
-     * <p>
-     * 在用户每日签到成功后、或查询周期状态时调用。
-     * 仅当活动开启、该周期累计签到达标、未发放过、且用户未达领取上限时发放。
-     */
-    @Transactional
-    public boolean tryGrantCycleReward(Long userId) {
-        ActivityCheckinCycleConfig config = getConfig();
-        if (!isActive(config)) {
-            return false;
-        }
-
-        String cycleMode = config.getCycleMode();
-        LocalDate today = LocalDate.now();
-        LocalDate currentCycleStart = cycleStartOf(today, cycleMode);
-        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<ActivityCheckinCycleRecord>()
-                        .eq(ActivityCheckinCycleRecord::getUserId, userId)
-                        .eq(ActivityCheckinCycleRecord::getCycleStart, prevCycleStart));
-        if (exist != null && exist.getRewardGranted() == 1) {
-            return false;
-        }
-
-        int rewardDays = config.getRewardDays() != null ? config.getRewardDays() : 7;
-        membershipService.grantOrExtendMembership(userId, MembershipLevel.PRO, rewardDays,
-                "ACTIVITY_CHECKIN_CYCLE", "签到送会员活动:周期" + prevCycleStart + "~" + prevCycleEnd + "累计签到" + checkinCount + "天奖励" + rewardDays + "天高级会员");
-
-        if (exist == null) {
-            ActivityCheckinCycleRecord record = ActivityCheckinCycleRecord.builder()
-                    .userId(userId)
-                    .cycleStart(prevCycleStart)
-                    .cycleEnd(prevCycleEnd)
-                    .checkinCount(checkinCount)
-                    .rewardGranted(1)
-                    .grantTime(LocalDateTime.now())
-                    .build();
-            try {
-                cycleRecordMapper.insert(record);
-            } catch (DuplicateKeyException e) {
-                // 并发兜底:已被其他线程处理
-                log.info("签到周期奖励并发处理,已跳过: userId={}, cycle={}", userId, prevCycleStart);
-                return false;
-            }
-        } else {
-            exist.setCheckinCount(checkinCount);
-            exist.setRewardGranted(1);
-            exist.setGrantTime(LocalDateTime.now());
-            cycleRecordMapper.updateById(exist);
-        }
-
-        log.info("签到周期奖励已发放: userId={}, cycle={}~{}, checkinCount={}, days={}",
-                userId, prevCycleStart, prevCycleEnd, checkinCount, rewardDays);
-        return true;
-    }
-
-    // ==========================================
-    // 用户端状态查询
-    // ==========================================
-
-    public ActivityCheckinCycleStatusResponse getStatus(Long userId) {
-        ActivityCheckinCycleConfig config = getConfig();
-
-        // 查询时顺带尝试发放上一周期奖励(用户若未在发放日签到,查询时补发)
-        if (userId != null && isActive(config)) {
-            try {
-                tryGrantCycleReward(userId);
-            } catch (Exception e) {
-                log.warn("查询签到周期状态时尝试发奖失败,不影响查询: userId={}", userId, e);
-            }
-        }
-
-        LocalDate today = LocalDate.now();
-        String cycleMode = config.getCycleMode();
-        LocalDate currentCycleStart = cycleStartOf(today, cycleMode);
-        LocalDate currentCycleEnd = currentCycleStart.plusDays(6);
-
-        int currentCount = userId != null ? countCheckinInCycle(userId, currentCycleStart, currentCycleEnd) : 0;
-        int requiredDays = config.getRequiredDays() != null ? config.getRequiredDays() : 4;
-        int rewardDays = config.getRewardDays() != null ? config.getRewardDays() : 7;
-        long rewarded = userId != null ? cycleRecordMapper.countRewardedByUser(userId) : 0L;
-        int maxReward = config.getMaxRewardCount() != null ? config.getMaxRewardCount() : 3;
-        long remaining = Math.max(0, maxReward - rewarded);
-
-        return ActivityCheckinCycleStatusResponse.builder()
-                .enabled(isActive(config))
-                .inWindow(inWindow(config))
-                .startTime(config.getStartTime())
-                .endTime(config.getEndTime())
-                .currentCycleStart(currentCycleStart)
-                .currentCycleEnd(currentCycleEnd)
-                .currentCycleCheckinCount(currentCount)
-                .requiredDays(requiredDays)
-                .reachedTarget(currentCount >= requiredDays)
-                .rewardedCount(rewarded)
-                .maxRewardCount(maxReward)
-                .remainingCount(remaining)
-                .rewardDays(rewardDays)
-                .build();
-    }
-
-    /**
-     * 用户端:查询我的签到周期奖励记录
-     * 返回已发放的周期奖励列表,按发放时间倒序
-     */
-    public List<ActivityCheckinCycleRecord> listMyCycleRewards(Long userId) {
-        return cycleRecordMapper.selectList(
-                new LambdaQueryWrapper<ActivityCheckinCycleRecord>()
-                        .eq(ActivityCheckinCycleRecord::getUserId, userId)
-                        .eq(ActivityCheckinCycleRecord::getRewardGranted, 1)
-                        .orderByDesc(ActivityCheckinCycleRecord::getGrantTime));
-    }
-
-    // ==========================================
-    // 运营端:分页查询签到周期奖励记录
-    // ==========================================
-
-    /**
-     * 分页查询签到周期奖励记录(运营端),支持按用户ID筛选。
-     * 只返回已发放奖励的周期记录,按发放时间倒序,转换为统一 DTO。
-     */
-    public Page<ActivityRewardRecordResponse> listCycleRewards(
-            Long userId, int pageNum, int pageSize) {
-        Page<ActivityCheckinCycleRecord> page = new Page<>(pageNum, pageSize);
-        LambdaQueryWrapper<ActivityCheckinCycleRecord> wrapper = new LambdaQueryWrapper<>();
-        if (userId != null) {
-            wrapper.eq(ActivityCheckinCycleRecord::getUserId, userId);
-        }
-        wrapper.eq(ActivityCheckinCycleRecord::getRewardGranted, 1)
-                .orderByDesc(ActivityCheckinCycleRecord::getGrantTime);
-        Page<ActivityCheckinCycleRecord> recordPage = cycleRecordMapper.selectPage(page, wrapper);
-
-        // 读取奖励天数(来自配置)
-        ActivityCheckinCycleConfig config = getConfig();
-        int rewardDays = config.getRewardDays() != null ? config.getRewardDays() : 7;
-
-        Page<ActivityRewardRecordResponse> resultPage = new Page<>(pageNum, pageSize, recordPage.getTotal());
-        List<ActivityRewardRecordResponse> list = recordPage.getRecords().stream()
-                .map(r -> ActivityRewardRecordResponse.builder()
-                        .id(r.getId())
-                        .userId(r.getUserId())
-                        .activityType("CHECKIN")
-                        .rewardName("签到有礼奖励")
-                        .rewardDays(rewardDays)
-                        .rewardLevel("PRO")
-                        .rewardType("MEMBERSHIP")
-                        .checkinCount(r.getCheckinCount())
-                        .cycleDesc(r.getCycleStart() + "~" + r.getCycleEnd())
-                        .createTime(r.getGrantTime())
-                        .build())
-                .toList();
-        resultPage.setRecords(list);
-        return resultPage;
-    }
-
-    // ==========================================
-    // 私有辅助
-    // ==========================================
-
-    private boolean isActive(ActivityCheckinCycleConfig config) {
-        return config.getEnabled() != null && config.getEnabled() == 1 && inWindow(config);
-    }
-
-    private boolean inWindow(ActivityCheckinCycleConfig config) {
-        LocalDateTime now = LocalDateTime.now();
-        return !now.isBefore(config.getStartTime()) && !now.isAfter(config.getEndTime());
-    }
-
-    /**
-     * 计算日期所属周期的起始日。
-     * 根据配置的周期模式计算:
-     * - MON_SUN:周一至周日,以周一为起点
-     * - FRI_THU:周五至下周四,以周五为起点
-     *
-     * @param date      目标日期
-     * @param cycleMode 周期模式(MON_SUN 或 FRI_THU)
-     * @return 周期起始日
-     */
-    private LocalDate cycleStartOf(LocalDate date, String cycleMode) {
-        int dow = date.getDayOfWeek().getValue(); // MON=1 ... SUN=7
-        int offset;
-        if (CYCLE_MODE_MON_SUN.equalsIgnoreCase(cycleMode)) {
-            // 周一为起点:offset = (dow - 1 + 7) % 7
-            offset = (dow - 1 + 7) % 7;
-        } else {
-            // 默认周五为起点:offset = (dow - 5 + 7) % 7
-            offset = (dow - 5 + 7) % 7;
-        }
-        return date.minusDays(offset);
-    }
-
-    private int countCheckinInCycle(Long userId, LocalDate start, LocalDate end) {
-        if (userId == null) {
-            return 0;
-        }
-        Long count = checkInRecordMapper.selectCount(
-                new LambdaQueryWrapper<CheckInRecord>()
-                        .eq(CheckInRecord::getUserId, userId)
-                        .ge(CheckInRecord::getCheckinDate, start)
-                        .le(CheckInRecord::getCheckinDate, end));
-        return count != null ? count.intValue() : 0;
-    }
-}

+ 2 - 2
zhijiayun-activity/src/main/java/com/xuekairui/activity/service/CheckInConfigService.java

@@ -77,7 +77,7 @@ public class CheckInConfigService {
         if (request.getMaxRewardCount() != null) config.setMaxRewardCount(request.getMaxRewardCount());
         if (request.getRemark() != null) config.setRemark(request.getRemark());
         checkinConfigMapper.updateById(config);
-        
+
         if (timeChanged || modeChanged) {
             regeneratePeriods(config);
         }
@@ -86,7 +86,7 @@ public class CheckInConfigService {
                 config.getEnabled(), config.getCycleMode(), config.getRequiredDays(), config.getRewardDays());
         return config;
     }
-    
+
     /**
      * 根据活动时间和周期模式重新生成周期
      * 规则:删除所有PENDING周期,保留ACTIVE/CLOSED周期(已有用户签到数据)

+ 140 - 19
zhijiayun-activity/src/main/java/com/xuekairui/activity/service/CheckInService.java

@@ -9,13 +9,15 @@ import com.xuekairui.activity.dto.CheckInPeriodStatsResponse;
 import com.xuekairui.activity.dto.CheckInResultResponse;
 import com.xuekairui.activity.dto.CheckInStatsResponse;
 import com.xuekairui.activity.dto.CheckInStatusResponse;
-import com.xuekairui.activity.entity.ActivityCheckinCycleRecord;
+import com.xuekairui.activity.dto.ActivityCheckinCycleStatusResponse;
+import com.xuekairui.activity.dto.ActivityRewardRecordResponse;
 import com.xuekairui.activity.entity.CheckInConfig;
 import com.xuekairui.activity.entity.CheckInPeriod;
 import com.xuekairui.activity.entity.CheckInRecord;
-import com.xuekairui.activity.mapper.ActivityCheckinCycleRecordMapper;
+import com.xuekairui.activity.entity.CheckInRewardRecord;
 import com.xuekairui.activity.mapper.CheckInPeriodMapper;
 import com.xuekairui.activity.mapper.CheckInRecordMapper;
+import com.xuekairui.activity.mapper.CheckInRewardRecordMapper;
 import com.xuekairui.common.BusinessException;
 import com.xuekairui.common.ErrorCode;
 import com.xuekairui.user.enums.MembershipLevel;
@@ -50,7 +52,7 @@ public class CheckInService {
     private final CheckInConfigService checkinConfigService;
     private final CheckInRecordMapper checkinRecordMapper;
     private final CheckInPeriodMapper periodMapper;
-    private final ActivityCheckinCycleRecordMapper cycleRecordMapper;
+    private final CheckInRewardRecordMapper rewardRecordMapper;
     private final MembershipService membershipService;
 
     /**
@@ -95,7 +97,7 @@ public class CheckInService {
             periodReached = isPeriodTargetReached(userId, currentPeriod);
         }
 
-        long rewardedCount = cycleRecordMapper.countRewardedByUser(userId);
+        long rewardedCount = rewardRecordMapper.countRewardedByUser(userId);
         int maxReward = config.getMaxRewardCount() != null ? config.getMaxRewardCount() : 3;
         long remaining = Math.max(0, maxReward - rewardedCount);
 
@@ -238,10 +240,9 @@ public class CheckInService {
             Long reachedUsers = checkinRecordMapper.countReachedUsersByPeriod(
                     period.getStartDate(), period.getEndDate(), 
                     period.getRequiredDays(), period.getRewardMode());
-            Long rewardedUsers = cycleRecordMapper.selectCount(
-                    new LambdaQueryWrapper<ActivityCheckinCycleRecord>()
-                            .eq(ActivityCheckinCycleRecord::getCycleStart, period.getStartDate())
-                            .eq(ActivityCheckinCycleRecord::getRewardGranted, 1));
+            Long rewardedUsers = rewardRecordMapper.selectCount(
+                    new LambdaQueryWrapper<CheckInRewardRecord>()
+                            .eq(CheckInRewardRecord::getPeriodId, period.getId()));
             
             double reachRate = totalUsers > 0 ? (reachedUsers * 100.0 / totalUsers) : 0.0;
             
@@ -299,7 +300,7 @@ public class CheckInService {
             return;
         }
 
-        long rewarded = cycleRecordMapper.countRewardedByUser(userId);
+        long rewarded = rewardRecordMapper.countRewardedByUser(userId);
         CheckInConfig config = checkinConfigService.getConfig();
         int maxReward = config.getMaxRewardCount() != null ? config.getMaxRewardCount() : 3;
         if (rewarded >= maxReward) {
@@ -320,15 +321,13 @@ public class CheckInService {
                         + " 累计签到" + checkinCount + "天 获" + rewardDays + "天" + rewardLevelStr + "会员");
 
         try {
-            ActivityCheckinCycleRecord cycleRecord = ActivityCheckinCycleRecord.builder()
+            CheckInRewardRecord rewardRecord = CheckInRewardRecord.builder()
                     .userId(userId)
-                    .cycleStart(lastClosed.getStartDate())
-                    .cycleEnd(lastClosed.getEndDate())
+                    .periodId(lastClosed.getId())
                     .checkinCount(checkinCount)
-                    .rewardGranted(1)
                     .grantTime(LocalDateTime.now())
                     .build();
-            cycleRecordMapper.insert(cycleRecord);
+            rewardRecordMapper.insert(rewardRecord);
         } catch (DuplicateKeyException e) {
             log.info("周期奖励并发处理: userId={}, period={}", userId, lastClosed.getStartDate());
         }
@@ -411,10 +410,132 @@ public class CheckInService {
     }
 
     private boolean isPeriodRewardGranted(Long userId, CheckInPeriod period) {
-        return cycleRecordMapper.selectCount(
-                new LambdaQueryWrapper<ActivityCheckinCycleRecord>()
-                        .eq(ActivityCheckinCycleRecord::getUserId, userId)
-                        .eq(ActivityCheckinCycleRecord::getCycleStart, period.getStartDate())
-                        .eq(ActivityCheckinCycleRecord::getRewardGranted, 1)) > 0;
+        return rewardRecordMapper.selectCount(
+                new LambdaQueryWrapper<CheckInRewardRecord>()
+                        .eq(CheckInRewardRecord::getUserId, userId)
+                        .eq(CheckInRewardRecord::getPeriodId, period.getId())) > 0;
+    }
+
+    // ==========================================
+    // 活动中心:签到送会员周期状态(供 ActivityController 使用)
+    // ==========================================
+
+    /**
+     * 签到送会员周期状态(当前周期签到天数、是否达标、累计领取次数、剩余次数)
+     */
+    public ActivityCheckinCycleStatusResponse getCheckinCycleStatus(Long userId) {
+        CheckInConfig config = checkinConfigService.getConfig();
+
+        // 查询时顺带尝试发放上一周期奖励
+        if (userId != null && checkinConfigService.isActive(config)) {
+            try {
+                tryGrantCycleReward(userId);
+            } catch (Exception e) {
+                log.warn("查询签到周期状态时尝试发奖失败,不影响查询: userId={}", userId, e);
+            }
+        }
+
+        LocalDate today = LocalDate.now();
+        CheckInPeriod currentPeriod = periodMapper.findActiveByDate(today);
+        LocalDate currentCycleStart;
+        LocalDate currentCycleEnd;
+        int requiredDays;
+        int rewardDays;
+
+        if (currentPeriod != null) {
+            currentCycleStart = currentPeriod.getStartDate();
+            currentCycleEnd = currentPeriod.getEndDate();
+            requiredDays = currentPeriod.getRequiredDays() != null ? currentPeriod.getRequiredDays() : 4;
+            rewardDays = currentPeriod.getRewardDays() != null ? currentPeriod.getRewardDays() : 7;
+        } else {
+            String cycleMode = config.getCycleMode();
+            currentCycleStart = cycleStartOf(today, cycleMode);
+            currentCycleEnd = currentCycleStart.plusDays(6);
+            requiredDays = config.getRequiredDays() != null ? config.getRequiredDays() : 4;
+            rewardDays = config.getRewardDays() != null ? config.getRewardDays() : 7;
+        }
+
+        int currentCount = userId != null ? countCheckinsInRange(userId, currentCycleStart, currentCycleEnd) : 0;
+        long rewarded = userId != null ? rewardRecordMapper.countRewardedByUser(userId) : 0L;
+        int maxReward = config.getMaxRewardCount() != null ? config.getMaxRewardCount() : 3;
+        long remaining = Math.max(0, maxReward - rewarded);
+
+        return ActivityCheckinCycleStatusResponse.builder()
+                .enabled(checkinConfigService.isActive(config))
+                .inWindow(checkinConfigService.isActive(config))
+                .startTime(config.getStartTime())
+                .endTime(config.getEndTime())
+                .currentCycleStart(currentCycleStart)
+                .currentCycleEnd(currentCycleEnd)
+                .currentCycleCheckinCount(currentCount)
+                .requiredDays(requiredDays)
+                .reachedTarget(currentCount >= requiredDays)
+                .rewardedCount(rewarded)
+                .maxRewardCount(maxReward)
+                .remainingCount(remaining)
+                .rewardDays(rewardDays)
+                .build();
+    }
+
+    // ==========================================
+    // 签到周期奖励记录查询(用户端 + 运营端)
+    // ==========================================
+
+    /**
+     * 用户端:分页查询我的签到周期奖励记录
+     */
+    public Page<ActivityRewardRecordResponse> listMyCycleRewards(Long userId, int pageNum, int pageSize) {
+        return listCycleRewards(userId, pageNum, pageSize);
+    }
+
+    /**
+     * 运营端:分页查询签到周期奖励记录,支持按用户ID筛选
+     */
+    public Page<ActivityRewardRecordResponse> listCycleRewards(Long userId, int pageNum, int pageSize) {
+        Page<CheckInRewardRecord> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<CheckInRewardRecord> wrapper = new LambdaQueryWrapper<>();
+        if (userId != null) {
+            wrapper.eq(CheckInRewardRecord::getUserId, userId);
+        }
+        wrapper.orderByDesc(CheckInRewardRecord::getGrantTime);
+        Page<CheckInRewardRecord> recordPage = rewardRecordMapper.selectPage(page, wrapper);
+
+        CheckInConfig config = checkinConfigService.getConfig();
+        int rewardDays = config.getRewardDays() != null ? config.getRewardDays() : 7;
+
+        Page<ActivityRewardRecordResponse> resultPage = new Page<>(pageNum, pageSize, recordPage.getTotal());
+        List<ActivityRewardRecordResponse> list = recordPage.getRecords().stream()
+                .map(r -> ActivityRewardRecordResponse.builder()
+                        .id(r.getId())
+                        .userId(r.getUserId())
+                        .activityType("CHECKIN")
+                        .rewardName("签到有礼奖励")
+                        .rewardDays(rewardDays)
+                        .rewardLevel("PRO")
+                        .rewardType("MEMBERSHIP")
+                        .checkinCount(r.getCheckinCount())
+                        .createTime(r.getGrantTime())
+                        .build())
+                .collect(Collectors.toList());
+        resultPage.setRecords(list);
+        return resultPage;
+    }
+
+    // ==========================================
+    // 私有辅助
+    // ==========================================
+
+    /**
+     * 计算日期所属周期的起始日(FRI_THU 或 MON_SUN)
+     */
+    private LocalDate cycleStartOf(LocalDate date, String cycleMode) {
+        int dow = date.getDayOfWeek().getValue(); // MON=1 ... SUN=7
+        int offset;
+        if ("MON_SUN".equalsIgnoreCase(cycleMode)) {
+            offset = (dow - 1 + 7) % 7;
+        } else {
+            offset = (dow - 5 + 7) % 7;
+        }
+        return date.minusDays(offset);
     }
 }

+ 31 - 0
zhijiayun-gateway/src/main/resources/db/migration-v17.sql

@@ -0,0 +1,31 @@
+-- ============================================
+-- 迁移 v17:合并签到配置表
+-- 说明:
+--   废弃 t_activity_checkin_cycle_config,统一使用 t_checkin_config 作为唯一签到配置表
+--   将 t_activity_checkin_cycle_config 中的数据同步到 t_checkin_config(取最新值)
+--   t_checkin_config 补充 reward_days 字段(如不存在)
+-- ============================================
+
+-- 1. t_checkin_config 补充 reward_days 字段(周期达标奖励天数)
+SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
+    WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_checkin_config' AND COLUMN_NAME = 'reward_days');
+SET @sql = IF(@col_exists = 0,
+    "ALTER TABLE `t_checkin_config` ADD COLUMN `reward_days` INT NOT NULL DEFAULT 7 COMMENT '周期达标奖励天数' AFTER `reward_mode`",
+    'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- 2. 将 t_activity_checkin_cycle_config 的数据同步到 t_checkin_config(取活动配置表的值覆盖)
+--    仅当 t_activity_checkin_cycle_config 存在数据时执行
+SET @row_count = (SELECT COUNT(*) FROM `t_activity_checkin_cycle_config` WHERE `id` = 1);
+SET @sql = IF(@row_count > 0,
+    "UPDATE `t_checkin_config` c INNER JOIN `t_activity_checkin_cycle_config` a ON c.id = 1 AND a.id = 1 SET c.enabled = a.enabled, c.start_time = a.start_time, c.end_time = a.end_time, c.cycle_mode = a.cycle_mode, c.required_days = a.required_days, c.reward_days = a.reward_days, c.max_reward_count = a.max_reward_count, c.remark = a.remark WHERE c.id = 1",
+    'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- 3. 如果 t_checkin_config 没有记录,从 t_activity_checkin_cycle_config 插入
+SET @cfg_count = (SELECT COUNT(*) FROM `t_checkin_config` WHERE `id` = 1);
+SET @src_count = (SELECT COUNT(*) FROM `t_activity_checkin_cycle_config` WHERE `id` = 1);
+SET @sql = IF(@cfg_count = 0 AND @src_count > 0,
+    "INSERT INTO `t_checkin_config` (`id`, `enabled`, `start_time`, `end_time`, `reward_type`, `reward_level`, `cycle_mode`, `required_days`, `reward_mode`, `reward_days`, `max_reward_count`, `remark`) SELECT 1, `enabled`, `start_time`, `end_time`, 'MEMBERSHIP', 'PRO', `cycle_mode`, `required_days`, 'CUMULATIVE', `reward_days`, `max_reward_count`, `remark` FROM `t_activity_checkin_cycle_config` WHERE `id` = 1",
+    'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

+ 23 - 0
zhijiayun-gateway/src/main/resources/db/migration-v18.sql

@@ -0,0 +1,23 @@
+-- ============================================
+-- 迁移 v18:签到奖励记录独立 + 废弃旧签到周期表
+-- 说明:
+--   废弃 t_activity_checkin_cycle_config(v17 已将数据同步到 t_checkin_config)
+--   废弃 t_activity_checkin_cycle_record,新建 t_checkin_reward_record 替代
+--   t_checkin_period 保持纯周期定义表,不承载用户奖励记录
+-- ============================================
+
+-- 1. 创建签到周期奖励发放记录表
+CREATE TABLE IF NOT EXISTS `t_checkin_reward_record` (
+    `id`            BIGINT   NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `user_id`       BIGINT   NOT NULL                COMMENT '用户ID',
+    `period_id`     BIGINT   NOT NULL                COMMENT '周期ID(关联 t_checkin_period.id)',
+    `checkin_count` INT      NOT NULL DEFAULT 0      COMMENT '该周期内用户签到天数',
+    `grant_time`    DATETIME DEFAULT NULL            COMMENT '奖励发放时间',
+    `create_time`   DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_user_period` (`user_id`, `period_id`) COMMENT '同一用户同一周期仅一条奖励记录'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='签到周期奖励发放记录';
+
+-- 2. 废弃旧签到周期相关表
+DROP TABLE IF EXISTS `t_activity_checkin_cycle_record`;
+DROP TABLE IF EXISTS `t_activity_checkin_cycle_config`;