package com.ylx.point.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ylx.common.core.domain.model.WxLoginUser; import com.ylx.common.exception.base.BaseException; import com.ylx.common.utils.DateUtils; import com.ylx.common.utils.SecurityUtils; import com.ylx.massage.domain.TWxUser; import com.ylx.massage.service.TWxUserService; import com.ylx.point.domain.*; import com.ylx.point.domain.dto.*; import com.ylx.point.domain.vo.*; import com.ylx.point.enums.PointActivityStatusEnum; import com.ylx.point.enums.PointActivityTypeEnum; import com.ylx.point.mapper.PointActivityMapper; import com.ylx.point.service.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.DateTimeException; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.*; import java.util.stream.Collectors; /** * 积分活动主Service业务层处理 * * @author wzj * @date 2026-03-25 */ @Slf4j @Service public class PointActivityServiceImpl extends ServiceImpl implements IPointActivityService { @Resource private PointActivityMapper pointActivityMapper; @Autowired private IPointActivityTaskService pointActivityTaskService; @Autowired private IPointSignTaskService pointSignTaskService; @Autowired private IPointSignRewardService pointSignRewardService; @Autowired private IPointUserActivityTaskCompletionService pointUserActivityTaskCompletionService; @Autowired private IPointActivityExpirePolicyService pointActivityExpirePolicyService; @Resource private TWxUserService wxUserService; private static final int BATCH_SIZE = 1000; private static final int TARGET_COUNT = 2; private static final String INVALID_EXPIRE_POLICY_TYPE_MESSAGE = "无效的过期策略类型"; private static final String EXPIRE_DAYS_REQUIRED_MESSAGE = "过期天数不能为空"; private static final String EXPIRE_YEAR_REQUIRED_MESSAGE = "过期年数不能为空"; private static final String UPDATE_FAILED_MESSAGE = "更新积分活动过期策略失败"; // 优先级顺序 private static final List PRIORITY_TYPES = Arrays.asList( PointActivityTypeEnum.NEW_USER_ACTIVITY.getCode(), PointActivityTypeEnum.DAILY_ACTIVITY.getCode(), PointActivityTypeEnum.MONTHLY_ACTIVITY.getCode()); // 新手, 每日, 每月 @Override public List list(PointActivityPageDTO dto) { return pointActivityMapper.list(dto); } @Override public PointActivityDetailsVo getDetailsInfo(Long id) { PointActivity pointActivity = this.pointActivityMapper.selectPointActivityById(id); if (ObjectUtil.isNull(pointActivity)) { throw new BaseException("参数有误,活动不存在"); } Integer activityType = pointActivity.getActivityType(); if (PointActivityTypeEnum.SIGN_TASK.getCode().equals(activityType)) { PointSignTaskVo vo = this.pointSignTaskService.selectPointSignTaskByActivityId(pointActivity.getId()); BeanUtil.copyProperties(pointActivity, vo); return PointActivityDetailsVo.of(activityType, vo); } else { PointActivityTaskVo vo = new PointActivityTaskVo(); BeanUtil.copyProperties(pointActivity, vo); List activityTaskList = this.pointActivityTaskService.selectPointActivityTaskByActivityId(pointActivity.getId()); vo.setActivityTaskList(activityTaskList); return PointActivityDetailsVo.of(activityType, vo); } } @Override @Transactional(rollbackFor = Exception.class) public void add(PointActivityDTO dto) { // 校验参数 validateActivityDTO(dto); // 保存活动信息 PointActivity entity = createActivityEntity(dto); saveActivity(entity); // 保存活动详情信息 saveActivityDetails(dto, entity.getId()); } @Override @Transactional(rollbackFor = Exception.class) public void edit(PointActivityDTO dto) { // 校验参数 Long id = dto.getId(); if (ObjectUtil.isNull(id)) { throw new BaseException("活动主键id不能为空"); } // 根据id获取活动详情 PointActivity entity = this.pointActivityMapper.selectPointActivityById(id); if (ObjectUtil.isNull(entity)) { throw new BaseException("参数有误,活动不存在"); } // 校验活动状态 if (PointActivityStatusEnum.IN_PROGRESS.getCode().equals(entity.getStatus())) { throw new BaseException("活动进行中,不可编辑"); } updateActivity(entity, dto); // 更新活动详情信息 updateActivityDetails(dto, id); } @Override public PointActivityStatVo getStatInfo(Long id) { // 根据id获取活动详情 PointActivity entity = this.pointActivityMapper.selectPointActivityById(id); if (ObjectUtil.isNull(entity)) { throw new BaseException("参数有误,活动不存在"); } PointActivityStatVo vo = new PointActivityStatVo(); BeanUtil.copyProperties(entity, vo); // 数据总览 PointActivityOverviewVO dataOverview = this.pointUserActivityTaskCompletionService.getPointActivityOverviewByActivityId(id); vo.setDataOverview(dataOverview); // 数据详情 List taskDetailList = pointActivityMapper.getTaskDetailListById(id); vo.setTaskDetailList(taskDetailList); return vo; } @Override @Transactional(rollbackFor = Exception.class) public void editValidity(PointActivityValidityDTO dto) { // 根据dto中activityType获取全部的活动list PointActivity pointActivity = new PointActivity(); pointActivity.setActivityType(dto.getActivityType()); List activityList = pointActivityMapper.selectPointActivityList(pointActivity); // 根据类型获取积分活动过期策略表数据 PointActivityExpirePolicy expirePolicy = pointActivityExpirePolicyService.selectPointActivityExpirePolicyByActivityType(dto.getActivityType()); if (CollectionUtil.isEmpty(activityList)) { // 更新积分活动过期策略表 this.saveOrUpdatePointActivityExpirePolicy(expirePolicy, dto); } else { // 判断活动list中只要有活动的状态为"进行中",修改就失败 boolean hasOngoingActivity = activityList.stream() .anyMatch(activity -> PointActivityStatusEnum.IN_PROGRESS.getCode().equals(activity.getStatus())); if (hasOngoingActivity) { throw new BaseException("存在进行中的活动,无法修改有效期"); } // 更新积分活动过期策略表 this.saveOrUpdatePointActivityExpirePolicy(expirePolicy, dto); // 批量更新活动的有效期策略 batchUpdateExpirePolicy(activityList, expirePolicy.getId()); } } @Override public List validityDetails() { try { // 查询所有过期策略 List expirePolicies = this.pointActivityExpirePolicyService.list(); // 如果没有数据,返回空列表 if (CollectionUtil.isEmpty(expirePolicies)) { log.debug("没有查询到积分活动过期策略数据"); return Collections.emptyList(); } // 转换为VO对象 List result = expirePolicies.stream() .map(this::convertToVo) .collect(Collectors.toList()); log.info("成功查询到 {} 条积分活动有效期详情", result.size()); return result; } catch (Exception e) { log.error("查询积分活动有效期详情失败", e); throw new BaseException("查询积分活动有效期详情失败"); } } @Override public Integer selectTotalActiveTasks(String cityCode) { return pointActivityMapper.selectTotalActiveTasks(cityCode); } @Override public List getUserPointActivityList(UserPointActivityPageDTO dto) { // 当前登录用户信息 WxLoginUser wxLoginUser = SecurityUtils.getWxLoginUser(); TWxUser user = wxUserService.getByOpenId(wxLoginUser.getCOpenid()); if (user == null) { throw new RuntimeException("用户不存在"); } String userId = user.getId(); dto.setUserId(userId); return pointActivityMapper.selectTaskWithProgress(dto); } @Override public List activityList() { // 当前登录用户信息 WxLoginUser wxLoginUser = SecurityUtils.getWxLoginUser(); TWxUser user = wxUserService.getByOpenId(wxLoginUser.getCOpenid()); if (user == null) { throw new RuntimeException("用户不存在"); } String userId = user.getId(); List resultList = new ArrayList<>(); // 2. 按优先级遍历活动类型 for (Integer activityType : PRIORITY_TYPES) { if (resultList.size() >= TARGET_COUNT) { break; } // 3. 查询该类型下 进行中 的任务 List taskList = pointActivityTaskService.selectTasksByActivityType(activityType); if (CollectionUtil.isEmpty(taskList)) { continue; } // 4. 批量查询用户完成记录 List taskIds = taskList.stream().map(PointActivityTask::getId).collect(Collectors.toList()); List completionList = pointUserActivityTaskCompletionService.selectCompletionsByUserAndTaskIds(userId, taskIds); Map completionMap = completionList.stream() .collect(Collectors.toMap(PointUserActivityTaskCompletion::getTaskId, c -> c)); // 5. 筛选【未完成】的任务 for (PointActivityTask task : taskList) { if (resultList.size() >= TARGET_COUNT) break; PointUserActivityTaskCompletion completion = completionMap.get(task.getId()); if (isTaskIncomplete(task, completion)) { resultList.add(convertToVo(task, completion)); } } } return resultList.stream().limit(2).collect(Collectors.toList()); } /** * 判断任务是否未完成 */ private boolean isTaskIncomplete(PointActivityTask task, PointUserActivityTaskCompletion completion) { // 1. 获取规则要求的次数 int requiredCount = 0; try { requiredCount = Integer.parseInt(task.getTriggerValue()); } catch (NumberFormatException e) { // 如果配置错误,为了安全起见,默认视为未完成(或根据业务抛出异常) return true; } // 2. 获取用户实际完成的次数 int actualCount = 0; if (ObjectUtil.isNotNull(completion) && ObjectUtil.isNotNull(completion.getCompletedCount())) { actualCount = completion.getCompletedCount(); } // 3. 核心判断逻辑 // 如果 实际次数 < 要求次数,则任务“未完成” (返回 true) return actualCount < requiredCount; } /** * 组装 VO 对象 */ private UserPointActivityVo convertToVo(PointActivityTask task, PointUserActivityTaskCompletion completion) { UserPointActivityVo vo = new UserPointActivityVo(); vo.setActivityId(task.getActivityId()); vo.setTaskName(task.getTaskName()); // 或者是从字典表查出的中文名称 vo.setRewardPoints(task.getRewardPoints()); // 完成次数:如果没有记录则为 "0" vo.setCompletedCount(completion != null ? String.valueOf(completion.getCompletedCount()) : "0"); // 触发条件:直接从任务配置中获取,或者根据 taskCode 查字典 vo.setTriggerValue(task.getTriggerValue()); return vo; } private PointActivityValidityVo convertToVo(PointActivityExpirePolicy policy) { PointActivityValidityVo vo = new PointActivityValidityVo(); BeanUtil.copyProperties(policy, vo); // 可以在这里添加额外的转换逻辑 return vo; } private void validateActivityDTO(PointActivityDTO dto) { if (ObjectUtil.isNull(dto)) { throw new BaseException("活动信息不能为空"); } PointActivityDTO.ActivityTimeConfig timeConfig = dto.getTimeConfig(); if (ObjectUtil.isNull(timeConfig) || (!timeConfig.getIsPermanent() && (ObjectUtil.isNull(timeConfig.getStartTime()) || ObjectUtil.isNull(timeConfig.getEndTime())))) { throw new BaseException("活动时间配置不能为空"); } } private PointActivity createActivityEntity(PointActivityDTO dto) { // 创建基础实体 PointActivity entity = new PointActivity(); BeanUtil.copyProperties(dto, entity); // 处理时间配置 PointActivityDTO.ActivityTimeConfig timeConfig = dto.getTimeConfig(); entity.setStartTime(DateUtil.beginOfDay(timeConfig.getStartTime())); entity.setEndTime(processEndTime(timeConfig.getEndTime())); entity.setIsPermanent(timeConfig.getIsPermanent()? 1 : 0); // 处理状态 if (PointActivityStatusEnum.PUBLISHED.getCode().equals(dto.getStatus())) { entity.setStatus(determineActivityStatus( entity.getStartTime(), entity.getEndTime(), timeConfig.getIsPermanent() )); } return entity; } private void saveActivity(PointActivity entity) { entity.setCreateBy(SecurityUtils.getUsername()); entity.setCreateTime(new Date()); int affectedRows = pointActivityMapper.insertPointActivity(entity); if (affectedRows <= 0) { throw new BaseException("保存积分活动失败"); } } private void saveOrUpdatePointActivityExpirePolicy(PointActivityExpirePolicy expirePolicy, PointActivityValidityDTO dto) { // 参数校验 if (ObjectUtil.isNull(dto)) { throw new IllegalArgumentException("参数不能为空"); } // 根据expirePolicy类型设置对应的过期时间 Integer expirePolicyCode = dto.getExpirePolicy(); if (ObjectUtil.isNull(expirePolicyCode)) { throw new IllegalArgumentException("过期策略类型不能为空"); } // 准备要保存或更新的实体 PointActivityExpirePolicy entity = expirePolicy != null ? expirePolicy : new PointActivityExpirePolicy(); // 设置基本信息 entity.setActivityType(dto.getActivityType()); entity.setExpirePolicy(dto.getExpirePolicy()); // 根据过期策略类型设置对应的过期时间 setExpireTimeByPolicyType(entity, dto, expirePolicyCode); // 保存或更新 saveOrUpdateExpirePolicy(entity, dto.getActivityType()); } private void setExpireTimeByPolicyType(PointActivityExpirePolicy entity, PointActivityValidityDTO dto, Integer expirePolicyCode) { // 先清空所有过期时间字段 entity.setExpireDays(null); entity.setExpireYear(null); // 根据策略类型设置对应的过期时间 switch (expirePolicyCode) { case 1: // PERMANENT_VALID - 永久有效 // 已经清空了过期天数和年数,无需额外操作 break; case 2: // TRANSACTION_EXPIRE - 按交易过期 if (dto.getExpireDays() == null) { throw new IllegalArgumentException(EXPIRE_DAYS_REQUIRED_MESSAGE); } entity.setExpireDays(dto.getExpireDays()); break; case 3: // YEARLY_EXPIRE - 按年过期 if (dto.getExpireYear() == null) { throw new IllegalArgumentException(EXPIRE_YEAR_REQUIRED_MESSAGE); } entity.setExpireYear(dto.getExpireYear()); break; default: throw new IllegalArgumentException(INVALID_EXPIRE_POLICY_TYPE_MESSAGE); } } private void saveOrUpdateExpirePolicy(PointActivityExpirePolicy entity, Integer activityType) { try { entity.setUpdateBy(SecurityUtils.getUsername()); entity.setUpdateTime(DateUtils.getNowDate()); boolean isSuccess = pointActivityExpirePolicyService.updateById(entity); if (!isSuccess) { log.error("保存或更新积分活动过期策略失败, activityType: {}", activityType); throw new BaseException(UPDATE_FAILED_MESSAGE); } } catch (Exception e) { log.error("保存或更新积分活动过期策略时发生异常, activityType: {}", activityType, e); throw new BaseException(UPDATE_FAILED_MESSAGE); } } private void saveActivityDetails(PointActivityDTO dto, Long activityId) { if (PointActivityTypeEnum.SIGN_TASK.getCode().equals(dto.getActivityType())) { saveSignTaskDetails(dto, activityId); } else { saveActivityTaskDetails(dto, activityId); } } private void updateActivityDetails(PointActivityDTO dto, Long activityId) { if (PointActivityTypeEnum.SIGN_TASK.getCode().equals(dto.getActivityType())) { updateSignTaskDetails(dto, activityId); } else { updateActivityTaskDetails(dto, activityId); } } private void updateActivity(PointActivity entity, PointActivityDTO dto) { // 更新活动信息 BeanUtil.copyProperties(dto, entity); // 处理时间配置 PointActivityDTO.ActivityTimeConfig timeConfig = dto.getTimeConfig(); if (timeConfig != null) { entity.setStartTime(DateUtil.beginOfDay(timeConfig.getStartTime())); entity.setEndTime(processEndTime(timeConfig.getEndTime())); entity.setIsPermanent(timeConfig.getIsPermanent()? 1 : 0); } // 处理状态 if (PointActivityStatusEnum.PUBLISHED.getCode().equals(dto.getStatus())) { entity.setStatus(determineActivityStatus( entity.getStartTime(), entity.getEndTime(), timeConfig.getIsPermanent() )); } entity.setUpdateBy(SecurityUtils.getUsername()); entity.setUpdateTime(DateUtils.getNowDate()); int affectedRows = pointActivityMapper.updatePointActivity(entity); if (affectedRows <= 0) { throw new BaseException("更新积分活动失败"); } } private void saveSignTaskDetails(PointActivityDTO dto, Long activityId) { PointSignTaskDTO signTaskDTO = dto.getSignTaskDTO(); if (ObjectUtil.isNull(signTaskDTO)) { throw new BaseException("签到任务配置不能为空"); } PointSignTask pointSignTask = new PointSignTask(); pointSignTask.setActivityId(activityId); pointSignTask.setBasePoints(signTaskDTO.getBasePoints()); pointSignTask.setBreakRule(signTaskDTO.getBreakRule()); int affectedRows = pointSignTaskService.insertPointSignTask(pointSignTask); if (affectedRows <= 0) { throw new BaseException("保存积分签到任务规则失败"); } saveSignRewards(signTaskDTO.getSignRewardList(), pointSignTask.getId()); } private void saveSignRewards(List rewardList, Long taskId) { if (CollectionUtil.isEmpty(rewardList)) { return; } List rewards = rewardList.stream() .map(dto -> { PointSignReward reward = new PointSignReward(); reward.setRewardPoints(dto.getRewardPoints()); reward.setContinueDays(dto.getContinueDays()); reward.setCreateBy(SecurityUtils.getUsername()); reward.setCreateTime(DateUtils.getNowDate()); reward.setUpdateTime(reward.getCreateTime()); reward.setIsDeleted(0); reward.setSignTaskId(taskId); return reward; }) .collect(Collectors.toList()); pointSignRewardService.batchInsertPointSignReward(rewards); } private void saveActivityTaskDetails(PointActivityDTO dto, Long activityId) { List tasks = dto.getTasks(); if (CollectionUtil.isEmpty(tasks)) { return; } List activityTasks = tasks.stream() .map(task -> { PointActivityTask activityTask = new PointActivityTask(); BeanUtil.copyProperties(task, activityTask); activityTask.setActivityId(activityId); activityTask.setIsDeleted(0); activityTask.setCreateBy(SecurityUtils.getUsername()); activityTask.setCreateTime(DateUtils.getNowDate()); activityTask.setUpdateTime(activityTask.getCreateTime()); return activityTask; }) .collect(Collectors.toList()); pointActivityTaskService.batchInsertPointActivityTask(activityTasks); } /** * 处理结束时间,设置为当天的23:59:59 */ private Date processEndTime(Date endTime) { if (endTime == null) { return null; } try { LocalDateTime localDateTime = LocalDateTime.ofInstant( endTime.toInstant(), ZoneId.systemDefault() ); LocalDateTime endOfDay = localDateTime.with( java.time.LocalTime.of(23, 59, 59)); return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant()); } catch (DateTimeException e) { throw new IllegalArgumentException("Invalid end time", e); } } /** * 确定活动状态 */ private Integer determineActivityStatus(Date startTime, Date endTime, Boolean isPermanent) { if (isPermanent) { return PointActivityStatusEnum.IN_PROGRESS.getCode(); } Date now = DateUtils.getNowDate(); if (startTime.after(now)) { return PointActivityStatusEnum.NOT_START.getCode(); } else if (endTime != null && endTime.before(now)) { return PointActivityStatusEnum.FINISHED.getCode(); } else { return PointActivityStatusEnum.IN_PROGRESS.getCode(); } } /** * 更新签到任务详情 */ private void updateSignTaskDetails(PointActivityDTO dto, Long activityId) { PointSignTaskDTO signTaskDTO = dto.getSignTaskDTO(); if (ObjectUtil.isNull(signTaskDTO)) { throw new BaseException("签到任务配置不能为空"); } // 获取现有的签到任务 PointSignTaskVo existingTask = pointSignTaskService.selectPointSignTaskByActivityId(activityId); // 更新签到任务基本信息 PointSignTask pointSignTask = new PointSignTask(); pointSignTask.setId(existingTask.getSignTaskId()); pointSignTask.setBasePoints(signTaskDTO.getBasePoints()); pointSignTask.setBreakRule(signTaskDTO.getBreakRule()); // 更新签到任务 int affectedRows = pointSignTaskService.updatePointSignTask(pointSignTask); if (affectedRows <= 0) { throw new BaseException("更新积分签到任务规则失败"); } // 更新签到奖励配置 updateSignRewards(signTaskDTO.getSignRewardList(), pointSignTask.getId()); } /** * 更新签到奖励配置 */ private void updateSignRewards(List rewardList, Long taskId) { // 先删除原有的奖励配置 PointSignReward query = new PointSignReward(); query.setSignTaskId(taskId); List existingRewards = pointSignRewardService.selectPointSignRewardList(query); if (CollectionUtil.isNotEmpty(existingRewards)) { String[] ids = existingRewards.stream() .map(reward -> String.valueOf(reward.getId())) .toArray(String[]::new); pointSignRewardService.deletePointSignRewardByIds(ids); } // 重新插入新的奖励配置 if (CollectionUtil.isEmpty(rewardList)) { return; } List rewards = rewardList.stream() .map(dto -> { PointSignReward reward = new PointSignReward(); reward.setRewardPoints(dto.getRewardPoints()); reward.setContinueDays(dto.getContinueDays()); reward.setCreateTime(DateUtils.getNowDate()); reward.setUpdateTime(reward.getCreateTime()); reward.setIsDeleted(0); reward.setSignTaskId(taskId); return reward; }) .collect(Collectors.toList()); pointSignRewardService.batchInsertPointSignReward(rewards); } /** * 更新活动任务详情 */ private void updateActivityTaskDetails(PointActivityDTO dto, Long activityId) { // 先删除原有的活动任务 pointActivityTaskService.deletePointActivityTaskByActivityId(activityId); // 重新插入新的活动任务 List tasks = dto.getTasks(); if (CollectionUtil.isEmpty(tasks)) { return; } List activityTasks = tasks.stream() .map(task -> { PointActivityTask activityTask = new PointActivityTask(); BeanUtil.copyProperties(task, activityTask); activityTask.setActivityId(activityId); activityTask.setIsDeleted(0); activityTask.setUpdateBy(SecurityUtils.getUsername()); activityTask.setUpdateTime(DateUtils.getNowDate()); return activityTask; }) .collect(Collectors.toList()); pointActivityTaskService.batchInsertPointActivityTask(activityTasks); } private void batchUpdateExpirePolicy(List activityList, Long expirePolicyId) { // 参数校验 if (CollectionUtil.isEmpty(activityList)) { log.debug("活动列表为空,无需更新过期策略"); return; } if (expirePolicyId == null) { throw new IllegalArgumentException("过期策略ID不能为空"); } try { // 使用Stream处理活动列表,设置过期策略ID List activitiesToUpdate = activityList.stream() .map(activity -> { activity.setExpirePolicyId(expirePolicyId); activity.setUpdateTime(DateUtils.getNowDate()); activity.setUpdateBy(SecurityUtils.getUsername()); return activity; }) .collect(Collectors.toList()); // 批量更新数据库 super.updateBatchById(activitiesToUpdate, BATCH_SIZE); log.info("成功更新 {} 个活动的过期策略为 {}", activitiesToUpdate.size(), expirePolicyId); } catch (Exception e) { log.error("批量更新活动过期策略失败, expirePolicyId: {}", expirePolicyId, e); throw new BaseException("批量更新活动过期策略失败"); } } }