Jelajahi Sumber

图片上传配置

liuchengsen 1 bulan lalu
induk
melakukan
07137cdedc

+ 86 - 0
zhijiayun-activity/src/main/java/com/xuekairui/activity/controller/ActivityController.java

@@ -1,9 +1,12 @@
 package com.xuekairui.activity.controller;
 
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.xuekairui.activity.dto.ActivityAvailableResponse;
 import com.xuekairui.activity.dto.ActivityCheckinCycleStatusResponse;
 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;
@@ -110,4 +113,87 @@ public class ActivityController {
         Long userId = (Long) request.getAttribute("userId");
         return Result.success(activityLotteryService.listMyRecords(userId, limit));
     }
+
+    // ========== 统一活动摘要(用户端) ==========
+
+    /**
+     * 获取当前用户可参加的所有活动摘要(三种活动状态一次返回)。
+     * GET /api/activity/available
+     */
+    @GetMapping("/available")
+    public Result<ActivityAvailableResponse> availableActivities(HttpServletRequest request) {
+        Long userId = (Long) request.getAttribute("userId");
+
+        ActivityInviteStatusResponse inviteStatus = activityInviteService.getStatus(userId);
+        ActivityCheckinCycleStatusResponse checkinStatus = activityCheckinCycleService.getStatus(userId);
+        ActivityLotteryStatusResponse lotteryStatus = activityLotteryService.getStatus(userId);
+
+        List<ActivityAvailableResponse.ActivityBrief> activities = List.of(
+                ActivityAvailableResponse.ActivityBrief.builder()
+                        .activityType("INVITE")
+                        .activityName("邀请有礼")
+                        .description("邀请好友注册并通过资质审核,双方各得会员天数奖励")
+                        .enabled(inviteStatus.getEnabled())
+                        .inWindow(inviteStatus.getInWindow())
+                        .rewardedCount(inviteStatus.getRewardedCount())
+                        .maxRewardCount(inviteStatus.getMaxInviterReward())
+                        .remainingCount(inviteStatus.getRemainingCount())
+                        .build(),
+                ActivityAvailableResponse.ActivityBrief.builder()
+                        .activityType("CHECKIN")
+                        .activityName("签到有礼")
+                        .description("每周期累计签到达标即可领取会员天数奖励")
+                        .enabled(checkinStatus.getEnabled())
+                        .inWindow(checkinStatus.getInWindow())
+                        .rewardedCount(checkinStatus.getRewardedCount())
+                        .maxRewardCount(checkinStatus.getMaxRewardCount())
+                        .remainingCount(checkinStatus.getRemainingCount())
+                        .cycleStart(checkinStatus.getCurrentCycleStart())
+                        .cycleEnd(checkinStatus.getCurrentCycleEnd())
+                        .checkinCount(checkinStatus.getCurrentCycleCheckinCount())
+                        .requiredDays(checkinStatus.getRequiredDays())
+                        .reachedTarget(checkinStatus.getReachedTarget())
+                        .rewardDays(checkinStatus.getRewardDays())
+                        .build(),
+                ActivityAvailableResponse.ActivityBrief.builder()
+                        .activityType("LOTTERY")
+                        .activityName("比价抽奖")
+                        .description("每日比价获得抽奖机会,赢取会员天数等好礼")
+                        .enabled(lotteryStatus.getEnabled())
+                        .inWindow(lotteryStatus.getInWindow())
+                        .eligible(lotteryStatus.getEligible())
+                        .membershipLevel(lotteryStatus.getMembershipLevel())
+                        .requireMembershipLevel(lotteryStatus.getRequireMembershipLevel())
+                        .availableChances(lotteryStatus.getAvailableChances())
+                        .usedChances(lotteryStatus.getUsedChances())
+                        .build()
+        );
+
+        return Result.success(ActivityAvailableResponse.builder().activities(activities).build());
+    }
+
+    // ========== 统一中奖/奖励记录查询(用户端) ==========
+
+    /**
+     * 按活动类型分页查询我的中奖/奖励记录(三种活动统一接口)。
+     * GET /api/activity/rewards?type=INVITE|CHECKIN|LOTTERY&page=1&size=20
+     *
+     * @param type 活动类型:INVITE-邀请有礼 / CHECKIN-签到有礼 / LOTTERY-比价抽奖
+     */
+    @GetMapping("/rewards")
+    public Result<Page<ActivityRewardRecordResponse>> myRewardRecords(
+            @RequestParam String type,
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size,
+            HttpServletRequest request) {
+        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 "LOTTERY" -> activityLotteryService.listLotteryRewards(userId, page, size);
+            default -> throw new IllegalArgumentException(
+                    "不支持的活动类型: " + type + ",可选值: INVITE, CHECKIN, LOTTERY");
+        };
+        return Result.success(result);
+    }
 }

+ 26 - 0
zhijiayun-activity/src/main/java/com/xuekairui/activity/controller/AdminActivityController.java

@@ -5,6 +5,7 @@ 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.entity.ActivityInviteConfig;
 import com.xuekairui.activity.entity.ActivityLotteryConfig;
@@ -122,6 +123,31 @@ public class AdminActivityController {
         return Result.success(activityLotteryService.listAllRecords(userId, page, size));
     }
 
+    // ========== 统一中奖/奖励记录查询(运营端) ==========
+
+    /**
+     * 按活动类型分页查询中奖/奖励记录(三种活动统一接口)。
+     * GET /api/admin/activity/rewards?type=INVITE|CHECKIN|LOTTERY&page=1&size=20&userId=1
+     *
+     * @param type   活动类型:INVITE-邀请有礼 / CHECKIN-签到有礼 / LOTTERY-比价抽奖
+     * @param userId 可选,用户ID筛选(邀请类型为邀请人ID)
+     */
+    @GetMapping("/rewards")
+    public Result<Page<ActivityRewardRecordResponse>> listRewardRecords(
+            @RequestParam String type,
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size,
+            @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 "LOTTERY" -> activityLotteryService.listLotteryRewards(userId, page, size);
+            default -> throw new IllegalArgumentException(
+                    "不支持的活动类型: " + type + ",可选值: INVITE, CHECKIN, LOTTERY");
+        };
+        return Result.success(result);
+    }
+
     // ========== 反作弊:撤销邀请奖励 ==========
 
     @PostMapping("/invite/revoke")

+ 7 - 7
zhijiayun-activity/src/main/java/com/xuekairui/activity/controller/CheckInController.java

@@ -1,5 +1,6 @@
 package com.xuekairui.activity.controller;
 
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.xuekairui.activity.dto.CheckInCalendarResponse;
 import com.xuekairui.activity.dto.CheckInResultResponse;
 import com.xuekairui.activity.dto.CheckInStatusResponse;
@@ -10,8 +11,6 @@ import jakarta.servlet.http.HttpServletRequest;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.List;
-
 /**
  * 签到接口(用户端)
  * <p>
@@ -58,13 +57,14 @@ public class CheckInController {
     }
 
     /**
-     * 我的签到记录
-     * GET /api/checkin/records?limit=30
+     * 我的签到记录(分页)
+     * GET /api/checkin/records?page=1&size=30
      */
     @GetMapping("/records")
-    public Result<List<CheckInRecord>> listRecords(HttpServletRequest request,
-                                                    @RequestParam(required = false) Integer limit) {
+    public Result<Page<CheckInRecord>> listRecords(HttpServletRequest request,
+                                                    @RequestParam(defaultValue = "1") int page,
+                                                    @RequestParam(defaultValue = "30") int size) {
         Long userId = (Long) request.getAttribute("userId");
-        return Result.success(checkInService.listRecords(userId, limit));
+        return Result.success(checkInService.listRecords(userId, page, size));
     }
 }

+ 84 - 0
zhijiayun-activity/src/main/java/com/xuekairui/activity/dto/ActivityAvailableResponse.java

@@ -0,0 +1,84 @@
+package com.xuekairui.activity.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 用户可参加的活动列表响应。
+ * <p>
+ * 聚合三种活动(邀请有礼 / 签到有礼 / 比价抽奖)的基本状态,
+ * 供前端在"我可参加的活动"页面一次性展示。
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ActivityAvailableResponse {
+
+    /** 活动列表 */
+    private List<ActivityBrief> activities;
+
+    /**
+     * 单个活动摘要信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class ActivityBrief {
+
+        /** 活动类型: INVITE / CHECKIN / LOTTERY */
+        private String activityType;
+
+        /** 活动名称 */
+        private String activityName;
+
+        /** 活动描述 */
+        private String description;
+
+        /** 是否启用 */
+        private Boolean enabled;
+
+        /** 是否在活动时间窗内 */
+        private Boolean inWindow;
+
+        // ===== 通用统计 =====
+        /** 已获奖励次数 */
+        private Long rewardedCount;
+        /** 最多可获奖励次数 */
+        private Integer maxRewardCount;
+        /** 剩余可获奖励次数 */
+        private Long remainingCount;
+
+        // ===== 签到特有 =====
+        /** 当前周期起始日 */
+        private LocalDate cycleStart;
+        /** 当前周期截止日 */
+        private LocalDate cycleEnd;
+        /** 当前周期已签到天数 */
+        private Integer checkinCount;
+        /** 周期需达签到天数 */
+        private Integer requiredDays;
+        /** 当前周期是否已达标 */
+        private Boolean reachedTarget;
+        /** 达标可获会员天数 */
+        private Integer rewardDays;
+
+        // ===== 抽奖特有 =====
+        /** 是否满足参与资格 */
+        private Boolean eligible;
+        /** 当前会员等级 */
+        private String membershipLevel;
+        /** 所需会员等级 */
+        private String requireMembershipLevel;
+        /** 今日可用抽奖机会 */
+        private Integer availableChances;
+        /** 今日已用抽奖机会 */
+        private Integer usedChances;
+    }
+}

+ 75 - 0
zhijiayun-activity/src/main/java/com/xuekairui/activity/dto/ActivityRewardRecordResponse.java

@@ -0,0 +1,75 @@
+package com.xuekairui.activity.dto;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+/**
+ * 统一活动奖励/中奖记录响应(运营端三种活动统一查询)
+ * <p>
+ * 覆盖邀请有礼、签到有礼、比价抽奖三类活动的中奖/奖励记录。
+ * 类型特有字段在非对应类型时为 null。
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class ActivityRewardRecordResponse {
+
+    /* ====== 通用字段 ====== */
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long id;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long userId;
+
+    /** 活动类型:INVITE-邀请有礼 / CHECKIN-签到有礼 / LOTTERY-比价抽奖 */
+    private String activityType;
+
+    /** 奖励名称(奖品名 / 奖励描述) */
+    private String rewardName;
+
+    /** 奖励天数 */
+    private Integer rewardDays;
+
+    /** 奖励会员等级 */
+    private String rewardLevel;
+
+    /** 奖励类型:MEMBERSHIP / EMPTY */
+    private String rewardType;
+
+    /** 中奖/奖励时间 */
+    private LocalDateTime createTime;
+
+    /* ====== 邀请有礼特有 ====== */
+
+    /** 邀请人ID */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long inviterId;
+
+    /** 被邀请人ID */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long inviteeId;
+
+    /** 状态:0-正常 1-已撤销 */
+    private Integer status;
+
+    /* ====== 签到有礼特有 ====== */
+
+    /** 周期内签到天数 */
+    private Integer checkinCount;
+
+    /** 周期描述,如 "2026-07-04~2026-07-10" */
+    private String cycleDesc;
+
+    /* ====== 比价抽奖特有 ====== */
+
+    /** 抽中奖品名称 */
+    private String prizeName;
+}

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

@@ -1,8 +1,10 @@
 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;
@@ -220,6 +222,48 @@ public class ActivityCheckinCycleService {
                         .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;
+    }
+
     // ==========================================
     // 私有辅助
     // ==========================================

+ 56 - 0
zhijiayun-activity/src/main/java/com/xuekairui/activity/service/ActivityInviteService.java

@@ -1,7 +1,10 @@
 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.ActivityInviteConfigRequest;
 import com.xuekairui.activity.dto.ActivityInviteStatusResponse;
+import com.xuekairui.activity.dto.ActivityRewardRecordResponse;
 import com.xuekairui.activity.entity.ActivityInviteConfig;
 import com.xuekairui.activity.entity.ActivityInviteRewardRecord;
 import com.xuekairui.activity.mapper.ActivityInviteConfigMapper;
@@ -175,6 +178,59 @@ public class ActivityInviteService implements ActivityInviteGate {
                 .build();
     }
 
+    // ==========================================
+    // 运营端:分页查询邀请奖励记录
+    // ==========================================
+
+    /**
+     * 分页查询邀请有礼奖励记录(运营端),支持按邀请人ID筛选。
+     * 按创建时间倒序,转换为统一 DTO。
+     */
+    public Page<ActivityRewardRecordResponse> listInviteRewardRecords(
+            Long userId, int pageNum, int pageSize) {
+        Page<ActivityInviteRewardRecord> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<ActivityInviteRewardRecord> wrapper = new LambdaQueryWrapper<>();
+        if (userId != null) {
+            wrapper.eq(ActivityInviteRewardRecord::getInviterId, userId);
+        }
+        wrapper.orderByDesc(ActivityInviteRewardRecord::getCreateTime);
+        Page<ActivityInviteRewardRecord> recordPage = rewardRecordMapper.selectPage(page, wrapper);
+
+        Page<ActivityRewardRecordResponse> resultPage = new Page<>(pageNum, pageSize, recordPage.getTotal());
+        List<ActivityRewardRecordResponse> list = toRewardResponseList(recordPage.getRecords());
+        resultPage.setRecords(list);
+        return resultPage;
+    }
+
+    /**
+     * 用户端:查询我作为邀请人获得的奖励记录。
+     */
+    public List<ActivityRewardRecordResponse> listMyInviteRewards(Long userId) {
+        LambdaQueryWrapper<ActivityInviteRewardRecord> wrapper = new LambdaQueryWrapper<ActivityInviteRewardRecord>()
+                .eq(ActivityInviteRewardRecord::getInviterId, userId)
+                .orderByDesc(ActivityInviteRewardRecord::getCreateTime);
+        List<ActivityInviteRewardRecord> records = rewardRecordMapper.selectList(wrapper);
+        return toRewardResponseList(records);
+    }
+
+    private List<ActivityRewardRecordResponse> toRewardResponseList(List<ActivityInviteRewardRecord> records) {
+        return records.stream()
+                .map(r -> ActivityRewardRecordResponse.builder()
+                        .id(r.getId())
+                        .userId(r.getInviterId())
+                        .activityType("INVITE")
+                        .rewardName("邀请有礼奖励")
+                        .rewardDays(r.getRewardDays())
+                        .rewardLevel("PRO")
+                        .rewardType("MEMBERSHIP")
+                        .inviterId(r.getInviterId())
+                        .inviteeId(r.getInviteeId())
+                        .status(r.getStatus())
+                        .createTime(r.getCreateTime())
+                        .build())
+                .toList();
+    }
+
     // ==========================================
     // 私有辅助
     // ==========================================

+ 26 - 0
zhijiayun-activity/src/main/java/com/xuekairui/activity/service/ActivityLotteryService.java

@@ -7,6 +7,7 @@ import com.xuekairui.activity.dto.ActivityLotteryConfigRequest;
 import com.xuekairui.activity.dto.ActivityLotteryPrizeRequest;
 import com.xuekairui.activity.dto.ActivityLotteryResultResponse;
 import com.xuekairui.activity.dto.ActivityLotteryStatusResponse;
+import com.xuekairui.activity.dto.ActivityRewardRecordResponse;
 import com.xuekairui.activity.entity.ActivityLotteryChance;
 import com.xuekairui.activity.entity.ActivityLotteryConfig;
 import com.xuekairui.activity.entity.ActivityLotteryPrize;
@@ -270,6 +271,31 @@ public class ActivityLotteryService {
         return recordMapper.selectPage(page, wrapper);
     }
 
+    /**
+     * 运营端:分页查询比价抽奖记录,转换为统一 DTO。
+     */
+    public Page<ActivityRewardRecordResponse> listLotteryRewards(
+            Long userId, int pageNum, int pageSize) {
+        Page<ActivityLotteryRecord> records = listAllRecords(userId, pageNum, pageSize);
+        Page<ActivityRewardRecordResponse> resultPage =
+                new Page<>(pageNum, pageSize, records.getTotal());
+        List<ActivityRewardRecordResponse> list = records.getRecords().stream()
+                .map(r -> ActivityRewardRecordResponse.builder()
+                        .id(r.getId())
+                        .userId(r.getUserId())
+                        .activityType("LOTTERY")
+                        .rewardName(r.getPrizeName())
+                        .rewardDays(r.getRewardDays())
+                        .rewardLevel(r.getRewardLevel())
+                        .rewardType(r.getRewardType())
+                        .prizeName(r.getPrizeName())
+                        .createTime(r.getCreateTime())
+                        .build())
+                .toList();
+        resultPage.setRecords(list);
+        return resultPage;
+    }
+
     // ==========================================
     // 奖品管理(运营端)
     // ==========================================

+ 3 - 5
zhijiayun-activity/src/main/java/com/xuekairui/activity/service/CheckInService.java

@@ -190,14 +190,12 @@ public class CheckInService {
                 .build();
     }
 
-    public List<CheckInRecord> listRecords(Long userId, Integer limit) {
+    public Page<CheckInRecord> listRecords(Long userId, int pageNum, int pageSize) {
+        Page<CheckInRecord> page = new Page<>(pageNum, pageSize);
         LambdaQueryWrapper<CheckInRecord> wrapper = new LambdaQueryWrapper<CheckInRecord>()
                 .eq(CheckInRecord::getUserId, userId)
                 .orderByDesc(CheckInRecord::getCheckinDate);
-        if (limit != null && limit > 0) {
-            wrapper.last("LIMIT " + limit);
-        }
-        return checkinRecordMapper.selectList(wrapper);
+        return checkinRecordMapper.selectPage(page, wrapper);
     }
 
     // ==========================================

+ 102 - 22
zhijiayun-test/src/main/resources/static/activity-test.html

@@ -187,8 +187,10 @@
     <div class="module-tabs" id="moduleTabs">
         <div class="module-tab active" data-module="checkin">1.签到有礼</div>
         <div class="module-tab" data-module="activity-invite">2.邀请有礼</div>
-        <div class="module-tab" data-module="activity-checkin">3.签到送会员</div>
-        <div class="module-tab" data-module="activity-lottery">4.比价抽奖</div>
+        <div class="module-tab" data-module="activity-lottery">3.比价抽奖</div>
+        <div class="module-tab" data-module="business-license">4.营业执照管理</div>
+        <div class="module-tab" data-module="activity-available">5.我可参加的活动</div>
+        <div class="module-tab" data-module="activity-rewards-tab">6.各种活动奖励</div>
     </div>
 
     <!-- 运营侧模块标签栏 -->
@@ -214,7 +216,7 @@ const SMS_CODE = () => document.getElementById('smsCode')?.value || '123456';
 
 let stats = { pass: 0, fail: 0, pending: 0, total: 0 };
 
-const USER_MODULES = ['checkin', 'activity-invite', 'activity-checkin', 'activity-lottery'];
+const USER_MODULES = ['checkin', 'activity-invite', 'activity-lottery', 'business-license', 'activity-available', 'activity-rewards-tab'];
 const ADMIN_MODULES = ['admin-checkin', 'admin-activity-invite', 'admin-activity-checkin', 'admin-activity-lottery'];
 
 // ===================== 大整数安全JSON解析 =====================
@@ -431,8 +433,10 @@ function renderModule(module) {
     switch (module) {
         case 'checkin': html = renderCheckinModule(); break;
         case 'activity-invite': html = renderActivityInviteModule(); break;
-        case 'activity-checkin': html = renderActivityCheckinModule(); break;
         case 'activity-lottery': html = renderActivityLotteryModule(); break;
+        case 'business-license': html = renderBusinessLicenseModule(); break;
+        case 'activity-available': html = renderActivityAvailableModule(); break;
+        case 'activity-rewards-tab': html = renderActivityRewardsModule(); break;
         case 'admin-checkin': html = renderAdminCheckinModule(); break;
         case 'admin-activity-invite': html = renderAdminActivityInviteModule(); break;
         case 'admin-activity-checkin': html = renderAdminActivityCheckinModule(); break;
@@ -631,6 +635,26 @@ const FIELD_DESC = {
         ['data[].chanceId', 'Long', '抽奖机会ID'],
         ['data[].createTime', 'DateTime', '抽奖时间']
     ],
+    'activityRewards': [
+        ['data.records[].id', 'Long', '记录ID'],
+        ['data.records[].userId', 'Long', '用户ID'],
+        ['data.records[].activityType', 'String', '活动类型:INVITE/CHECKIN/LOTTERY'],
+        ['data.records[].rewardName', 'String', '奖励名称'],
+        ['data.records[].rewardDays', 'Integer', '奖励会员天数'],
+        ['data.records[].rewardLevel', 'String', '奖励会员等级'],
+        ['data.records[].rewardType', 'String', '奖励类型:MEMBERSHIP/EMPTY'],
+        ['data.records[].createTime', 'DateTime', '中奖/奖励时间'],
+        ['data.records[].inviterId', 'Long', '邀请人ID(INVITE类型)'],
+        ['data.records[].inviteeId', 'Long', '被邀请人ID(INVITE类型)'],
+        ['data.records[].status', 'Integer', '状态:0正常/1已撤销(INVITE)'],
+        ['data.records[].checkinCount', 'Integer', '签到天数(CHECKIN类型)'],
+        ['data.records[].cycleDesc', 'String', '周期描述(CHECKIN类型)'],
+        ['data.records[].prizeName', 'String', '奖品名称(LOTTERY类型)'],
+        ['data.total', 'Long', '总记录数'],
+        ['data.current', 'Long', '当前页'],
+        ['data.size', 'Long', '每页条数'],
+        ['data.pages', 'Long', '总页数']
+    ],
     'adminActivityInviteConfig': [
         ['data.id', 'Long', '配置ID(固定1)'],
         ['data.enabled', 'Integer', '是否启用:0-停用 1-启用'],
@@ -697,6 +721,26 @@ const FIELD_DESC = {
         ['data.size', 'Long', '每页条数'],
         ['data.pages', 'Long', '总页数']
     ],
+    'adminActivityRewards': [
+        ['data.records[].id', 'Long', '记录ID'],
+        ['data.records[].userId', 'Long', '用户ID(邀请类型为邀请人ID)'],
+        ['data.records[].activityType', 'String', '活动类型:INVITE-邀请/CHECKIN-签到/LOTTERY-比价抽奖'],
+        ['data.records[].rewardName', 'String', '奖励名称'],
+        ['data.records[].rewardDays', 'Integer', '奖励会员天数'],
+        ['data.records[].rewardLevel', 'String', '奖励会员等级'],
+        ['data.records[].rewardType', 'String', '奖励类型:MEMBERSHIP/EMPTY'],
+        ['data.records[].createTime', 'DateTime', '中奖/奖励时间'],
+        ['data.records[].inviterId', 'Long', '邀请人ID(INVITE类型)'],
+        ['data.records[].inviteeId', 'Long', '被邀请人ID(INVITE类型)'],
+        ['data.records[].status', 'Integer', '状态:0-正常/1-已撤销(INVITE类型)'],
+        ['data.records[].checkinCount', 'Integer', '周期内签到天数(CHECKIN类型)'],
+        ['data.records[].cycleDesc', 'String', '周期描述(CHECKIN类型)'],
+        ['data.records[].prizeName', 'String', '奖品名称(LOTTERY类型)'],
+        ['data.total', 'Long', '总记录数'],
+        ['data.current', 'Long', '当前页'],
+        ['data.size', 'Long', '每页条数'],
+        ['data.pages', 'Long', '总页数']
+    ],
     'businessLicenseInfo': [
         ['data.id', 'Long', '执照ID(null=未上传)'],
         ['data.userId', 'Long', '用户ID'],
@@ -827,7 +871,7 @@ function toNumberIfSafe(v) {
     return num;
 }
 
-// ==================== 用户端:签到模块 ====================
+// ==================== 用户端:签到有礼模块(含基础签到 + 周期签到送会员) ====================
 function renderCheckinModule() {
     return `
     <div class="batch-buttons">
@@ -839,8 +883,11 @@ function renderCheckinModule() {
     ${testSection('checkin-calendar', '当月签到日历', 'GET', 'get', '/api/checkin/calendar?year=2026&month=7', '需认证', 'auth-required',
         [{label:'年份(year,可选)', name:'year', placeholder:'2026', default:'2026'},
          {label:'月份(month,可选)', name:'month', placeholder:'7', default:'7'}], true, 'checkinCalendar', '获取指定月份的签到日历,不传默认当月')}
-    ${testSection('checkin-records', '我的签到记录', 'GET', 'get', '/api/checkin/records?limit=30', '需认证', 'auth-required',
-        [{label:'条数(limit,可选)', name:'limit', placeholder:'30', default:'30'}], true, null, '获取当前用户的签到历史记录,按签到日期倒序')}`;
+    ${testSection('checkin-records', '我的签到记录(分页)', 'GET', 'get', '/api/checkin/records?page=1&size=20', '需认证', 'auth-required',
+        [{label:'页码(page)', name:'page', placeholder:'1', default:'1'},
+         {label:'每页(size)', name:'size', placeholder:'20', default:'20'}], true, null, '获取当前用户的签到记录,分页返回,按签到日期倒序')}
+    ${testSection('activity-checkin-status', '周期签到进度', 'GET', 'get', '/api/activity/checkin-cycle/status', '需认证', 'auth-required', null, true, 'activityCheckinStatus', '查询当前周期签到进度:周期起止日、已签到天数、是否达标、累计/剩余可领取次数')}
+    ${testSection('activity-checkin-rewards', '周期奖励记录', 'GET', 'get', '/api/activity/checkin-cycle/rewards', '需认证', 'auth-required', null, true, 'activityCheckinCycleRewards', '获取签到周期达标奖励记录:每个周期的签到天数、是否已发放、发放时间')}`;
 }
 
 // ==================== 用户端:邀请有礼模块 ====================
@@ -853,16 +900,6 @@ function renderActivityInviteModule() {
     ${testSection('activity-invite-rewards', '我的邀请奖励记录', 'GET', 'get', '/api/invite/rewards', '需认证', 'auth-required', null, true, 'activityInviteRewards', '获取邀请奖励记录列表,包含被邀请人信息、注册状态、奖励月数')}`;
 }
 
-// ==================== 用户端:签到送会员模块(旧版独立接口,周期信息已整合到签到状态接口) ====================
-function renderActivityCheckinModule() {
-    return `
-    <div class="batch-buttons">
-        <button class="btn btn-warning btn-sm" onclick="runActivityCheckinTests()">运行全部签到送会员测试</button>
-    </div>
-    ${testSection('activity-checkin-status', '签到送会员周期状态(旧版)', 'GET', 'get', '/api/activity/checkin-cycle/status', '需认证', 'auth-required', null, true, 'activityCheckinStatus', '【旧版】查询当前周期签到进度。新版签到状态已整合周期信息到 /api/checkin/status')}
-    ${testSection('activity-checkin-rewards', '我的签到周期奖励记录', 'GET', 'get', '/api/activity/checkin-cycle/rewards', '需认证', 'auth-required', null, true, 'activityCheckinCycleRewards', '获取签到周期达标奖励记录:每个周期的签到天数、是否已发放、发放时间')}`;
-}
-
 // ==================== 用户端:比价抽奖模块 ====================
 function renderActivityLotteryModule() {
     return `
@@ -873,8 +910,16 @@ function renderActivityLotteryModule() {
     ${testSection('activity-lottery-status', '比价抽奖活动状态', 'GET', 'get', '/api/activity/lottery/status', '需认证', 'auth-required', null, true, 'activityLotteryStatus', '查询抽奖活动状态:是否开启、是否满足资格、今日可用机会')}
     ${testSection('activity-lottery-draw', '执行抽奖', 'POST', 'post', '/api/activity/lottery/draw', '需认证', 'auth-required', null, true, 'activityLotteryDraw', '消耗一次抽奖机会,随机抽取奖品(会员/爬虫次数/谢谢参与)')}
     ${testSection('activity-lottery-records', '我的抽奖记录', 'GET', 'get', '/api/activity/lottery/records?limit=20', '需认证', 'auth-required',
-        [{label:'条数(limit,可选)', name:'limit', placeholder:'20', default:'20'}], true, 'activityLotteryRecords', '获取当前用户的抽奖历史记录,包含奖品名称、奖励类型、会员天数、是否中奖等完整中奖信息')}
-    ${testSection('business-license-info', '我的营业执照信息', 'GET', 'get', '/api/business-license/info', '需认证', 'auth-required', null, true, 'businessLicenseInfo', '查询当前用户的营业执照提交状态和审核结果。审核通过后可在比价抽奖中获得额外抽奖机会')}
+        [{label:'条数(limit,可选)', name:'limit', placeholder:'20', default:'20'}], true, 'activityLotteryRecords', '获取当前用户的抽奖历史记录,包含奖品名称、奖励类型、会员天数、是否中奖等完整中奖信息')}`;
+}
+
+// ==================== 用户端:营业执照管理(公用模块) ====================
+function renderBusinessLicenseModule() {
+    return `
+    <div class="batch-buttons">
+        <button class="btn btn-warning btn-sm" onclick="runBusinessLicenseTests()">运行营业执照测试</button>
+    </div>
+    ${testSection('business-license-info', '我的营业执照信息', 'GET', 'get', '/api/business-license/info', '需认证', 'auth-required', null, true, 'businessLicenseInfo', '查询当前用户的营业执照提交状态和审核结果。审核通过后可在多种活动中获得权益:比价抽奖额外机会、邀请有礼资格认定等')}
     ${testSection('business-license-upload', '提交营业执照(JSON)', 'POST', 'post', '/api/business-license/upload', '需认证', 'auth-required',
         [
          {label:'执照图片URL(licenseImageUrl)', name:'licenseImageUrl', placeholder:'https://example.com/license.jpg', default:''},
@@ -884,6 +929,29 @@ function renderActivityLotteryModule() {
         ], true, 'businessLicenseInfo', '上传营业执照参与资质审核。审核通过后可获认证标识、解锁邀请有礼奖励、获取比价抽奖资格')}`;
 }
 
+// ==================== 用户端:我可参加的活动(统一摘要) ====================
+function renderActivityAvailableModule() {
+    return `
+    <div class="batch-buttons">
+        <button class="btn btn-warning btn-sm" onclick="runActivityAvailableTests()">运行活动摘要测试</button>
+    </div>
+    ${testSection('activity-available', '我可参加的活动列表', 'GET', 'get', '/api/activity/available', '需认证', 'auth-required', null, true, 'activityAvailable', '一次性获取三种活动(邀请有礼、签到有礼、比价抽奖)的当前状态摘要:是否启用、时间窗内、奖励统计、签到进度、抽奖资格')}`;
+}
+
+// ==================== 用户端:各种活动奖励(统一查询) ====================
+function renderActivityRewardsModule() {
+    return `
+    <div class="batch-buttons">
+        <button class="btn btn-warning btn-sm" onclick="runActivityRewardsTabTests()">运行奖励查询测试</button>
+    </div>
+    ${testSection('activity-rewards', '我的中奖/奖励记录(三种活动按类型分页)', 'GET', 'get', '/api/activity/rewards?type=LOTTERY&page=1&size=20', '需认证', 'auth-required',
+        [
+         {label:'活动类型(type)', name:'type', placeholder:'INVITE/CHECKIN/LOTTERY', default:'LOTTERY'},
+         {label:'页码(page)', name:'page', placeholder:'1', default:'1'},
+         {label:'每页(size)', name:'size', placeholder:'20', default:'20'}
+        ], true, 'activityRewards', '按活动类型分页查询当前用户的三种活动中奖/奖励记录。type: INVITE-邀请有礼 / CHECKIN-签到有礼 / LOTTERY-比价抽奖')}`;
+}
+
 // ==================== 运营端:签到管理模块 ====================
 function renderAdminCheckinModule() {
     return `
@@ -1037,7 +1105,14 @@ function renderAdminActivityLotteryModule() {
          {label:'页码(page)', name:'page', placeholder:'1', default:'1'},
          {label:'每页(size)', name:'size', placeholder:'20', default:'20'},
          {label:'用户ID(userId,可选)', name:'userId', placeholder:'1', default:''}
-        ], true, 'adminLotteryRecord', '分页查询所有用户的抽奖中奖记录,支持按用户ID筛选,按时间倒序')}`;
+        ], true, 'adminLotteryRecord', '分页查询所有用户的抽奖中奖记录,支持按用户ID筛选,按时间倒序')}
+    ${testSection('admin-activity-rewards', '统一中奖/奖励记录(三种活动)', 'GET', 'get', '/api/admin/activity/rewards?type=LOTTERY&page=1&size=20&userId=1', '需认证', 'auth-required',
+        [
+         {label:'活动类型(type)', name:'type', placeholder:'INVITE/CHECKIN/LOTTERY', default:'LOTTERY'},
+         {label:'页码(page)', name:'page', placeholder:'1', default:'1'},
+         {label:'每页(size)', name:'size', placeholder:'20', default:'20'},
+         {label:'用户ID(userId,可选)', name:'userId', placeholder:'1', default:''}
+        ], true, 'adminActivityRewards', '按活动类型统一查询三种活动的中奖/奖励记录。type: INVITE-邀请有礼 / CHECKIN-签到有礼 / LOTTERY-比价抽奖')}`;
 }
 
 // ===================== 批量测试 ====================
@@ -1140,8 +1215,10 @@ function clearAllResults() {
 // 用户侧/运营侧快捷批量运行
 function runCheckinTests() { runBatchTests('checkin'); }
 function runActivityInviteTests() { runBatchTests('activity-invite'); }
-function runActivityCheckinTests() { runBatchTests('activity-checkin'); }
 function runActivityLotteryTests() { runBatchTests('activity-lottery'); }
+function runBusinessLicenseTests() { runBatchTests('business-license'); }
+function runActivityAvailableTests() { runBatchTests('activity-available'); }
+function runActivityRewardsTabTests() { runBatchTests('activity-rewards'); }
 function runAdminCheckinTests() { runBatchTests('admin-checkin'); }
 function runAdminActivityInviteTests() { runBatchTests('admin-activity-invite'); }
 function runAdminActivityCheckinTests() { runBatchTests('admin-activity-checkin'); }
@@ -1151,7 +1228,7 @@ function runAdminActivityLotteryTests() { runBatchTests('admin-activity-lottery'
 function validate_checkinStatus(result) { return result.data?.code === 200 && result.data?.data?.checkedToday !== undefined; }
 function validate_checkinDo(result) { return result.data?.code === 200 && result.data?.data?.id; }
 function validate_checkinCalendar(result) { return result.data?.code === 200 && result.data?.data?.checkedDates !== undefined; }
-function validate_checkinRecords(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
+function validate_checkinRecords(result) { return result.data?.code === 200 && result.data?.data?.records !== undefined; }
 function validate_adminCheckinConfig(result) { return result.data?.code === 200 && result.data?.data?.id; }
 function validate_adminCheckinConfigUpdate(result) { return result.data?.code === 200 && result.data?.data?.id; }
 function validate_adminCheckinPeriods(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
@@ -1169,6 +1246,8 @@ function validate_activityCheckinRewards(result) { return result.data?.code ===
 function validate_activityLotteryStatus(result) { return result.data?.code === 200 && result.data?.data?.enabled !== undefined; }
 function validate_activityLotteryPrizes(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
 function validate_activityLotteryRecords(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
+function validate_activityRewards(result) { return result.data?.code === 200 && result.data?.data?.records !== undefined; }
+function validate_activityAvailable(result) { return result.data?.code === 200 && Array.isArray(result.data?.data?.activities); }
 function validate_activityLotteryDraw(result) { return result.data?.code === 200 && result.data?.data?.prizeName !== undefined; }
 function validate_adminActivityInviteConfig(result) { return result.data?.code === 200 && result.data?.data?.id; }
 function validate_adminActivityInviteRevoke(result) { return result.data?.code === 200 && result.data?.data !== undefined; }
@@ -1180,6 +1259,7 @@ function validate_adminActivityLotteryPrizeUpdate(result) { return result.data?.
 function validate_adminActivityLotteryPrizeDelete(result) { return result.data?.code === 200; }
 
 function validate_adminActivityLotteryRecords(result) { return result.data?.code === 200 && result.data?.data?.records !== undefined; }
+function validate_adminActivityRewards(result) { return result.data?.code === 200 && result.data?.data?.records !== undefined; }
 function validate_businessLicenseInfo(result) { return result.data?.code === 200 && result.data?.data !== undefined; }
 function validate_businessLicenseUpload(result) { return result.data?.code === 200; }
 

+ 4 - 4
zhijiayun-user/src/main/java/com/xuekairui/user/config/OssProperties.java

@@ -13,16 +13,16 @@ import org.springframework.stereotype.Component;
 public class OssProperties {
 
     /** OSS 地域节点(如 oss-cn-hangzhou.aliyuncs.com) */
-    private String endpoint = "oss-cn-hangzhou.aliyuncs.com";
+    private String endpoint = "oss-cn-shenzhen.aliyuncs.com";
 
     /** 阿里云 AccessKey ID */
-    private String accessKeyId;
+    private String accessKeyId="LTAI5tDwjfteBvivYN41r8sJ";
 
     /** 阿里云 AccessKey Secret */
-    private String accessKeySecret;
+    private String accessKeySecret="yowuOGi2nYYnrqGpO3qcz94C4brcPp";
 
     /** Bucket 名称 */
-    private String bucketName;
+    private String bucketName= "kailin-saas";
 
     /** OSS 存储基础路径(如 uploads/) */
     private String basePath = "uploads/";