|
|
@@ -2,6 +2,7 @@ package com.ylx.point.service.impl;
|
|
|
|
|
|
import cn.hutool.core.bean.BeanUtil;
|
|
|
import cn.hutool.core.collection.CollectionUtil;
|
|
|
+import cn.hutool.core.date.DateTime;
|
|
|
import cn.hutool.core.date.DateUtil;
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
@@ -61,6 +62,8 @@ public class PointActivityServiceImpl extends ServiceImpl<PointActivityMapper, P
|
|
|
private IPointUserSignLogService pointUserSignLogService;
|
|
|
@Resource
|
|
|
private IPointUserSignStatusService pointUserSignStatusService;
|
|
|
+ @Resource
|
|
|
+ private IPointAccountService pointAccountService;
|
|
|
|
|
|
private static final int BATCH_SIZE = 1000;
|
|
|
|
|
|
@@ -298,131 +301,216 @@ public class PointActivityServiceImpl extends ServiceImpl<PointActivityMapper, P
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public List<SignDayVo> getSignInfo(String cityName) {
|
|
|
-
|
|
|
+ public List<SignDayVo> getSignInfo(SignDTO dto) {
|
|
|
+ // 1. 获取用户信息
|
|
|
WxLoginUser wxLoginUser = SecurityUtils.getWxLoginUser();
|
|
|
- TWxUser user = wxUserService.getByOpenId(wxLoginUser.getCOpenid());
|
|
|
- if (user == null) {
|
|
|
+ if (ObjectUtil.isNull(wxLoginUser)) {
|
|
|
throw new RuntimeException("用户不存在");
|
|
|
}
|
|
|
+ String openId = wxLoginUser.getCOpenid();
|
|
|
|
|
|
- String userId = user.getId();
|
|
|
- // TODO 根据cityName换cityCode
|
|
|
- String cityCode = "110000";
|
|
|
- // 动态获取启用任务
|
|
|
- PointSignTask task = getEnabledTask(cityCode);
|
|
|
+ // 2. 获取任务ID (模拟城市逻辑)
|
|
|
+ PointSignTask task = getEnabledTask(dto.getCityCode());
|
|
|
+ if (task == null) {
|
|
|
+ throw new RuntimeException("当前城市暂无签到活动");
|
|
|
+ }
|
|
|
Long taskId = task.getId();
|
|
|
|
|
|
- List<SignDayVo> voList = new ArrayList<>();
|
|
|
- LocalDate today = LocalDate.now();
|
|
|
+ // 3. 查询奖励配置 (按天数正序查询)
|
|
|
+ LambdaQueryWrapper<PointSignReward> rewardQuery = new LambdaQueryWrapper<>();
|
|
|
+ rewardQuery.eq(PointSignReward::getSignTaskId, taskId);
|
|
|
+ rewardQuery.orderByAsc(PointSignReward::getContinueDays);
|
|
|
+ rewardQuery.last("LIMIT 7");
|
|
|
+ List<PointSignReward> rewardList = pointSignRewardService.list(rewardQuery);
|
|
|
|
|
|
- // 奖励配置
|
|
|
- LambdaQueryWrapper<PointSignReward> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
- queryWrapper.eq(PointSignReward::getSignTaskId, taskId);
|
|
|
- queryWrapper.orderByAsc(PointSignReward::getContinueDays);
|
|
|
- queryWrapper.last("LIMIT 7");
|
|
|
- List<PointSignReward> rewardList = this.pointSignRewardService.list(queryWrapper);
|
|
|
+ // 4. 获取用户当前状态
|
|
|
+ boolean signedToday = pointUserSignLogService.countTodaySign(openId, taskId) > 0;
|
|
|
|
|
|
- // 今日是否已签
|
|
|
- boolean signedToday = pointUserSignLogService.countTodaySign(userId, taskId) > 0;
|
|
|
+ LambdaQueryWrapper<PointUserSignStatus> statusQuery = new LambdaQueryWrapper<>();
|
|
|
+ statusQuery.eq(PointUserSignStatus::getOpenId, openId);
|
|
|
+ statusQuery.eq(PointUserSignStatus::getActivityId, taskId);
|
|
|
+ PointUserSignStatus status = pointUserSignStatusService.getOne(statusQuery);
|
|
|
|
|
|
- // 用户签到状态
|
|
|
- LambdaQueryWrapper<PointUserSignStatus> statusQueryWrapper = new LambdaQueryWrapper<>();
|
|
|
- statusQueryWrapper.eq(PointUserSignStatus::getUserId, userId);
|
|
|
- statusQueryWrapper.eq(PointUserSignStatus::getActivityId, taskId);
|
|
|
- PointUserSignStatus status = pointUserSignStatusService.getOne(statusQueryWrapper);
|
|
|
- int continuousDays = ObjectUtil.isNull(status) ? 0 : status.getCurrentContinuousDays();
|
|
|
+ // 修正点:处理空指针,如果没记录则默认为0
|
|
|
+ int continuousDays = (status == null) ? 0 : status.getCurrentContinuousDays();
|
|
|
|
|
|
- // 组装7天展示
|
|
|
+ // 5. 组装返回数据
|
|
|
+ List<SignDayVo> voList = new ArrayList<>();
|
|
|
+ LocalDate today = LocalDate.now();
|
|
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd");
|
|
|
- for (int i = 0; i < 7; i++) {
|
|
|
+
|
|
|
+ // 注意:UI通常要求从左到右是 [过去 -> 今天],所以我们需要倒序循环 (从第6天前 -> 今天)
|
|
|
+ for (int i = 6; i >= 0; i--) {
|
|
|
LocalDate date = today.minusDays(i);
|
|
|
+ int dayIndex = 7 - i; // 计算这是第几天 (1, 2, 3... 7)
|
|
|
+
|
|
|
SignDayVo vo = new SignDayVo();
|
|
|
+
|
|
|
+ // 修正点:格式化日期为字符串,避免前端处理麻烦
|
|
|
vo.setDate(date);
|
|
|
|
|
|
- // 奖励匹配
|
|
|
-// PointSignReward reward = rewardList.stream()
|
|
|
-// .filter(r -> r.getContinueDays() == (i + 1))
|
|
|
-// .findFirst()
|
|
|
-// .orElse(null);
|
|
|
-// vo.setPoints(reward == null ? 0 : reward.getRewardPoints());
|
|
|
+ // --- 核心逻辑修正:状态判断 ---
|
|
|
+ // 逻辑:
|
|
|
+ // 1. 如果是今天 (i==0):看 signedToday
|
|
|
+ // 2. 如果是过去 (i>0):看 continuousDays 是否覆盖了这一天
|
|
|
+ // 注意:只有当今天还没签时,continuousDays才代表截止到昨天的进度。
|
|
|
+ // 如果今天已签,continuousDays 包含了今天。
|
|
|
|
|
|
- // 状态 0未签 1已签 2今日可签
|
|
|
+ boolean isSigned;
|
|
|
if (i == 0) {
|
|
|
- vo.setStatus(signedToday ? 1 : 2);
|
|
|
+ // 今天的情况
|
|
|
+ isSigned = signedToday;
|
|
|
+ } else {
|
|
|
+ // 过去的情况
|
|
|
+ // 如果今天已签,continuousDays 包含了今天,所以 i <= continuousDays - 1
|
|
|
+ // 如果今天没签,continuousDays 截止到昨天,所以 i <= continuousDays
|
|
|
+ // 简化逻辑:只要 (i < continuousDays) 或者 (i == continuousDays 且 今天已签)
|
|
|
+ // 最通用的判断:当前的循环索引 i (倒数第几天) 小于 总连续天数,说明已签
|
|
|
+ // 特殊情况:如果今天没签,continuousDays 是昨天的进度。
|
|
|
+
|
|
|
+ // 简单写法:
|
|
|
+ if (signedToday) {
|
|
|
+ isSigned = i <= continuousDays; // 包含今天在内的已签天数
|
|
|
+ } else {
|
|
|
+ isSigned = i < continuousDays; // 不包含今天
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isSigned) {
|
|
|
+ vo.setStatus(1); // 已签
|
|
|
+ } else if (i == 0) {
|
|
|
+ vo.setStatus(2); // 今天未签 (可签到)
|
|
|
} else {
|
|
|
- vo.setStatus((i + 1) <= continuousDays ? 1 : 0);
|
|
|
+ vo.setStatus(0); // 未来/未签
|
|
|
}
|
|
|
+
|
|
|
+ // --- 奖励匹配 ---
|
|
|
+ // 根据 dayIndex (1-7) 去 rewardList 找对应的配置
|
|
|
+ PointSignReward reward = rewardList.stream()
|
|
|
+ .filter(r -> r.getContinueDays() == dayIndex)
|
|
|
+ .findFirst()
|
|
|
+ .orElse(null);
|
|
|
+
|
|
|
+ if (reward != null) {
|
|
|
+ vo.setPoints(reward.getRewardPoints());
|
|
|
+ // 如果有其他字段如图片,也可以在这里set
|
|
|
+ } else {
|
|
|
+ vo.setPoints(0);
|
|
|
+ }
|
|
|
+
|
|
|
voList.add(vo);
|
|
|
}
|
|
|
+
|
|
|
return voList;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public void sign() {
|
|
|
-// // 1. 检查今天是否已签
|
|
|
-// int count = pointUserSignLogService.countTodaySign(userId, DEFAULT_TASK_ID);
|
|
|
-// if (count > 0) {
|
|
|
-// throw new RuntimeException("今天已经签到过了");
|
|
|
-// }
|
|
|
-//
|
|
|
-// // 2. 锁定用户签到状态行 (悲观锁,防止并发重复签到)
|
|
|
-// PointUserSignStatus status = statusMapper.selectByUserAndTaskForUpdate(userId, DEFAULT_TASK_ID);
|
|
|
-//
|
|
|
-// int newContinuousDays = 1; // 默认第一天
|
|
|
-//
|
|
|
-// if (status != null) {
|
|
|
-// // 3. 判断是否断签
|
|
|
-// // 逻辑:如果上次签到时间不是昨天,且断签规则是"重置",则重置天数
|
|
|
-// boolean isBreak = !status.getLastSignTime().toLocalDate().equals(LocalDate.now().minusDays(1));
|
|
|
-//
|
|
|
-// PointSignTask task = taskMapper.selectById(DEFAULT_TASK_ID);
|
|
|
-// if (isBreak && task.getBreakRule() == 0) {
|
|
|
-// newContinuousDays = 1;
|
|
|
-// } else {
|
|
|
-// newContinuousDays = status.getCurrentContinuousDays() + 1;
|
|
|
-// }
|
|
|
-//
|
|
|
-// // 更新状态表
|
|
|
-// status.setCurrentContinuousDays(newContinuousDays);
|
|
|
-// status.setLastSignTime(LocalDateTime.now());
|
|
|
-// statusMapper.updateById(status);
|
|
|
-// } else {
|
|
|
-// // 第一次签到,插入状态
|
|
|
-// PointUserSignStatus newStatus = new PointUserSignStatus();
|
|
|
-// newStatus.setUserId(userId);
|
|
|
-// newStatus.setTaskId(DEFAULT_TASK_ID);
|
|
|
-// newStatus.setCurrentContinuousDays(1);
|
|
|
-// newStatus.setLastSignTime(LocalDateTime.now());
|
|
|
-// statusMapper.insert(newStatus);
|
|
|
-// }
|
|
|
-//
|
|
|
-// // 4. 计算奖励
|
|
|
-// PointSignReward reward = rewardMapper.selectOne(
|
|
|
-// new QueryWrapper<PointSignReward>()
|
|
|
-// .eq("task_id", DEFAULT_TASK_ID)
|
|
|
-// .eq("day", newContinuousDays)
|
|
|
-// );
|
|
|
-//
|
|
|
-// // 如果当前天数没有配置具体奖励,取最近的一个配置或者默认值
|
|
|
-// if (reward == null) {
|
|
|
-// // 这里可以写兜底逻辑,比如取最大天数的奖励
|
|
|
-// reward = rewardMapper.selectOne(new QueryWrapper<PointSignReward>().eq("task_id", DEFAULT_TASK_ID).orderByDesc("day").last("limit 1"));
|
|
|
-// }
|
|
|
-//
|
|
|
-// int points = (reward == null) ? 1 : reward.getPoints();
|
|
|
-//
|
|
|
-// // 5. 写入日志
|
|
|
-// PointUserSignLog log = new PointUserSignLog();
|
|
|
-// log.setUserId(userId);
|
|
|
-// log.setTaskId(DEFAULT_TASK_ID);
|
|
|
-// log.setPoints(points);
|
|
|
-// log.setCreateTime(LocalDateTime.now());
|
|
|
-// logMapper.insert(log);
|
|
|
-//
|
|
|
-// // 6. 增加用户总积分 (调用账户服务)
|
|
|
-// pointAccountService.addPoints(userId, points);
|
|
|
+ public void sign(SignDTO dto) {
|
|
|
+ // 1. 获取用户信息
|
|
|
+ WxLoginUser wxLoginUser = SecurityUtils.getWxLoginUser();
|
|
|
+ if (ObjectUtil.isNull(wxLoginUser)) {
|
|
|
+ throw new RuntimeException("用户未登录");
|
|
|
+ }
|
|
|
+ String openId = wxLoginUser.getCOpenid();
|
|
|
+
|
|
|
+ // 2. 获取当前城市有效的签到任务配置
|
|
|
+ // 建议:这里应该加缓存,避免每次签到都查库
|
|
|
+ PointSignTask task = getEnabledTask(dto.getCityCode());
|
|
|
+ if (ObjectUtil.isNull(task)) {
|
|
|
+ throw new RuntimeException("当前城市暂无签到活动");
|
|
|
+ }
|
|
|
+ Long activityId = task.getActivityId();
|
|
|
+ Long taskId = task.getId();
|
|
|
+
|
|
|
+ // 3. 防重复签到 (利用数据库唯一索引兜底,这里做快速失败)
|
|
|
+ // 优化:直接查库比 count 快,且逻辑更清晰
|
|
|
+ PointUserSignLog todayLog = pointUserSignLogService.getOne(new LambdaQueryWrapper<PointUserSignLog>()
|
|
|
+ .eq(PointUserSignLog::getOpenId, openId)
|
|
|
+ .eq(PointUserSignLog::getTaskId, taskId)
|
|
|
+ .eq(PointUserSignLog::getSignDate, DateUtils.getNowDate()) // 假设日志表有 sign_date 字段,如果没有用 create_time 转 date
|
|
|
+ .last("LIMIT 1"));
|
|
|
+
|
|
|
+ if (ObjectUtil.isNotNull(todayLog)) {
|
|
|
+ throw new RuntimeException("今日已签到");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 获取并锁定用户签到状态 (悲观锁)
|
|
|
+ // 注意:selectByOpenIdAndActivityIdForUpdate 必须使用 FOR UPDATE 语法
|
|
|
+ PointUserSignStatus status = pointUserSignStatusService.selectByOpenIdAndActivityIdForUpdate(openId, activityId);
|
|
|
+
|
|
|
+ int newContinuousDays = 1;
|
|
|
+ DateTime today = DateUtil.date();
|
|
|
+ DateTime yesterday = DateUtil.yesterday();
|
|
|
+
|
|
|
+ if (ObjectUtil.isNull(status)) {
|
|
|
+ // --- 首次签到 ---
|
|
|
+ status = new PointUserSignStatus();
|
|
|
+ status.setOpenId(openId);
|
|
|
+ status.setActivityId(activityId);
|
|
|
+ status.setLastSignDate(today);
|
|
|
+ status.setCurrentContinuousDays(1);
|
|
|
+ status.setLastRewardCycleDays(0);
|
|
|
+ pointUserSignStatusService.save(status);
|
|
|
+ } else {
|
|
|
+ // --- 非首次签到:计算连续天数 ---
|
|
|
+ DateTime lastSignDate = status.getLastSignDate();
|
|
|
+
|
|
|
+ // 判断是否断签:昨天不是最后签到日期,即为断签
|
|
|
+ boolean isBreak = !DateUtil.isSameDay(lastSignDate, yesterday);
|
|
|
+
|
|
|
+ if (isBreak) {
|
|
|
+ if (task.getBreakRule() == 0) {
|
|
|
+ // 规则0:断签重置
|
|
|
+ newContinuousDays = 1;
|
|
|
+ } else {
|
|
|
+ // 规则1:断签保留进度 (这里逻辑通常是 +1,除非你想做补签逻辑)
|
|
|
+ newContinuousDays = status.getCurrentContinuousDays() + 1;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 正常连续
|
|
|
+ newContinuousDays = status.getCurrentContinuousDays() + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新状态
|
|
|
+ status.setLastSignDate(today);
|
|
|
+ status.setCurrentContinuousDays(newContinuousDays);
|
|
|
+ pointUserSignStatusService.updateById(status);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 计算奖励积分
|
|
|
+ // 优化:匹配 <= 当前天数的最大奖励配置 (例如配置了3天、7天,第5天应该拿3天的奖,或者拿基础奖)
|
|
|
+ int rewardPoints = task.getBasePoints(); // 默认基础积分
|
|
|
+ PointSignReward reward = pointSignRewardService.getOne(new LambdaQueryWrapper<PointSignReward>()
|
|
|
+ .eq(PointSignReward::getSignTaskId, taskId)
|
|
|
+ .le(PointSignReward::getContinueDays, newContinuousDays) // 小于等于当前天数
|
|
|
+ .orderByDesc(PointSignReward::getContinueDays)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+
|
|
|
+ if (ObjectUtil.isNotNull(reward)) {
|
|
|
+ rewardPoints = reward.getRewardPoints();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 写入签到流水日志
|
|
|
+ PointUserSignLog signLog = new PointUserSignLog();
|
|
|
+ signLog.setOpenId(openId);
|
|
|
+ signLog.setActivityId(activityId);
|
|
|
+ signLog.setTaskId(taskId);
|
|
|
+ signLog.setSignDate(today); // 需确保数据库字段支持 Date 类型
|
|
|
+ signLog.setContinuousDays(newContinuousDays);
|
|
|
+ signLog.setPoints(rewardPoints);
|
|
|
+ signLog.setIsMakeUp(0);
|
|
|
+ pointUserSignLogService.save(signLog);
|
|
|
+
|
|
|
+ // 7. 增加用户积分账户余额
|
|
|
+ // 注意:这里假设 pointAccountService 内部会写入 point_user_log (积分总流水表)
|
|
|
+ try {
|
|
|
+ pointAccountService.addPoints(openId, rewardPoints, null, activityId, taskId, PointActivityTypeEnum.SIGN_TASK.getCode());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("签到发放积分失败", e);
|
|
|
+ throw new RuntimeException("签到成功,但积分发放失败,请联系客服");
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
/**
|