Commit e3e82a56 authored by zhaobiyan's avatar zhaobiyan

兑换撤销接口

parent 879a2da6
package cn.iocoder.yudao.framework.apollo.core.event.export;
import lombok.Data;
import java.util.List;
@Data
public class MemberScoreDetailExpireEvent {
private List<Long> detailIds;
}
...@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.member.api.score; ...@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.member.api.score;
import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreBatchOperateReqDTO; import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreBatchOperateReqDTO;
import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreOperateReqDTO; import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreOperateReqDTO;
import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreOperateRespDTO; import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreOperateRespDTO;
import cn.iocoder.yudao.module.member.api.score.dto.ReleationScoreExpireInfoDTO;
import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum;
import java.util.List; import java.util.List;
...@@ -12,4 +14,5 @@ public interface MemberUserScoreApi { ...@@ -12,4 +14,5 @@ public interface MemberUserScoreApi {
List<MemberUserScoreOperateRespDTO> batchOperateScore(MemberUserScoreBatchOperateReqDTO req); List<MemberUserScoreOperateRespDTO> batchOperateScore(MemberUserScoreBatchOperateReqDTO req);
List<ReleationScoreExpireInfoDTO> getScoreExpireInfo(String releation, ScoreSourceTypeEnum scoreSourceTypeEnum);
} }
...@@ -14,4 +14,5 @@ public class MemberUserScoreDetailUpdateReqDto { ...@@ -14,4 +14,5 @@ public class MemberUserScoreDetailUpdateReqDto {
private ScoreSourceTypeEnum sourceType; private ScoreSourceTypeEnum sourceType;
private Long scoreLogId; private Long scoreLogId;
private Integer expireDays; private Integer expireDays;
private String releationId;
} }
...@@ -28,6 +28,10 @@ public class MemberUserScoreOperateReqDTO { ...@@ -28,6 +28,10 @@ public class MemberUserScoreOperateReqDTO {
* 人工操作时必传,其他来源不需要传 * 人工操作时必传,其他来源不需要传
*/ */
private ScoreOperateTypeEnum operateType; private ScoreOperateTypeEnum operateType;
/**
* 若操作的积分后续存在回退逻辑时必传, 目前存在 礼品兑换后, 兑换取消,扣除积分的回退
*/
private String releationId;
/** /**
* 积分规则id * 积分规则id
*/ */
......
package cn.iocoder.yudao.module.member.api.score.dto;
import lombok.Data;
import java.util.Date;
@Data
public class ReleationScoreExpireInfoDTO {
private Long detailId;
private Date expireDate;
private Integer scoreCount;
}
...@@ -44,5 +44,6 @@ public interface ErrorCodeConstants { ...@@ -44,5 +44,6 @@ public interface ErrorCodeConstants {
ErrorCode SCORE_COUNT_ERROR = new ErrorCode(1004008002, "score.count.error"); ErrorCode SCORE_COUNT_ERROR = new ErrorCode(1004008002, "score.count.error");
ErrorCode MEMBER_SCORE_NOT_ENOUGH = new ErrorCode(1004008002, "member.score.not.enough"); ErrorCode MEMBER_SCORE_NOT_ENOUGH = new ErrorCode(1004008003, "member.score.not.enough");
ErrorCode REVERSE_SOURCE_NO_RELEATION_ID = new ErrorCode(1004008004, "reverse.source.no.releation.id");
} }
package cn.iocoder.yudao.module.member.enums;
public enum MemberScoreDetailReleationStatueEnum {
AVAILABLE(1, "有效"),
NOT_AVAILABLE(2, "失效")
;
private final int value;
private final String name;
MemberScoreDetailReleationStatueEnum(int value, String name) {
this.value = value;
this.name = name;
}
public int getValue() {
return value;
}
public String getName() {
return name;
}
}
...@@ -5,11 +5,13 @@ import com.google.common.collect.Sets; ...@@ -5,11 +5,13 @@ import com.google.common.collect.Sets;
import java.util.Set; import java.util.Set;
public enum ScoreSourceTypeEnum { public enum ScoreSourceTypeEnum {
MANUAL_OPERATE(1, "人工操作", null), MANUAL_OPERATE(1, "人工操作", null, null),
EXCHANGE_REWARD(2, "兑换礼品", ScoreOperateTypeEnum.REDUCE), EXCHANGE_REWARD(2, "兑换礼品", ScoreOperateTypeEnum.REDUCE, null),
SYSTEM_EXPIRED(3, "系统失效", ScoreOperateTypeEnum.REDUCE), SYSTEM_EXPIRED(3, "系统失效", ScoreOperateTypeEnum.REDUCE, null),
EXCHANGE_REWARD_CANCEL(4, "兑换礼品撤销", ScoreOperateTypeEnum.ADD, EXCHANGE_REWARD),
; ;
private final int value; private final int value;
...@@ -18,16 +20,23 @@ public enum ScoreSourceTypeEnum { ...@@ -18,16 +20,23 @@ public enum ScoreSourceTypeEnum {
private final ScoreOperateTypeEnum operateType; private final ScoreOperateTypeEnum operateType;
ScoreSourceTypeEnum(int value, String name, ScoreOperateTypeEnum operateType) { private final ScoreSourceTypeEnum reverseSource;
ScoreSourceTypeEnum(int value, String name, ScoreOperateTypeEnum operateType, ScoreSourceTypeEnum reverseSource) {
this.value = value; this.value = value;
this.name = name; this.name = name;
this.operateType = operateType; this.operateType = operateType;
this.reverseSource = reverseSource;
} }
public int getValue() { public int getValue() {
return value; return value;
} }
public ScoreSourceTypeEnum getReverseSource() {
return reverseSource;
}
public String getName() { public String getName() {
return name; return name;
} }
......
package cn.iocoder.yudao.module.member.api.score; package cn.iocoder.yudao.module.member.api.score;
import cn.iocoder.yudao.framework.redis.helper.RedisDistributedLock; import cn.iocoder.yudao.framework.apollo.core.event.export.MemberScoreDetailExpireEvent;
import cn.iocoder.yudao.module.member.api.score.dto.*; import cn.iocoder.yudao.module.member.api.score.dto.*;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScore.MemberUserScoreDO; import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScore.MemberUserScoreDO;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail.MemberUserScoreDetailDO;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetailReleation.MemberUserScoreDetailReleationDO;
import cn.iocoder.yudao.module.member.dto.ScoreDetailChangeDto;
import cn.iocoder.yudao.module.member.enums.MemberScoreDetailReleationStatueEnum;
import cn.iocoder.yudao.module.member.enums.ScoreOperateTypeEnum; import cn.iocoder.yudao.module.member.enums.ScoreOperateTypeEnum;
import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum; import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum;
import cn.iocoder.yudao.module.member.service.memberUserScore.MemberUserScoreService; import cn.iocoder.yudao.module.member.service.memberUserScore.MemberUserScoreService;
import cn.iocoder.yudao.module.member.service.memberUserScoreDetail.MemberUserScoreDetailService; import cn.iocoder.yudao.module.member.service.memberUserScoreDetail.MemberUserScoreDetailService;
import cn.iocoder.yudao.module.member.service.memberUserScoreDetailReleation.MemberUserScoreDetailReleationService;
import cn.iocoder.yudao.module.member.service.memberUserScoreLog.MemberUserScoreLogService; import cn.iocoder.yudao.module.member.service.memberUserScoreLog.MemberUserScoreLogService;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogCreateReq; import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogCreateReq;
import com.alibaba.excel.util.CollectionUtils; import com.alibaba.excel.util.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
...@@ -36,7 +49,12 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{ ...@@ -36,7 +49,12 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{
@Resource @Resource
private MemberUserScoreService memberUserScoreService; private MemberUserScoreService memberUserScoreService;
@Resource @Resource
private RedisDistributedLock redisDistributedLock; private MemberUserScoreDetailReleationService memberUserScoreDetailReleationService;
@Resource
private ApplicationContext applicationContext;
@Resource
private RedissonClient redissonClient;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public MemberUserScoreOperateRespDTO operateScore(MemberUserScoreOperateReqDTO req) { public MemberUserScoreOperateRespDTO operateScore(MemberUserScoreOperateReqDTO req) {
...@@ -46,21 +64,43 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{ ...@@ -46,21 +64,43 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{
if (req.getScoreCount() <= 0) { if (req.getScoreCount() <= 0) {
throw exception(SCORE_COUNT_ERROR); throw exception(SCORE_COUNT_ERROR);
} }
String lockKey = "member:operate:socre:" + req.getMemberId(); String lockKey = "member:operate:score:" + req.getMemberId();
boolean lock = redisDistributedLock.lock(lockKey, 5000, 3, 100); RLock lock = redissonClient.getLock(lockKey);
if (!lock) {
throw exception(GET_LOCK_FAILED);
}
try { try {
boolean lockSuccess = lock.tryLock(2, 2, TimeUnit.MINUTES);
if (!lockSuccess) {
throw exception(GET_LOCK_FAILED);
}
Long logId = saveScoreLog(req); Long logId = saveScoreLog(req);
saveScoreDetail(req, logId); List<ScoreDetailChangeDto> detailInfos = saveScoreDetail(req, logId);
saveScoreDetailReleation(req, detailInfos);
updateUserScore(req); updateUserScore(req);
publishDetailExpireEvent(req,detailInfos);
} catch (InterruptedException e) {
throw exception(GET_LOCK_FAILED);
} finally { } finally {
redisDistributedLock.releaseLock(lockKey); lock.unlock();
} }
return MemberUserScoreOperateRespDTO.success(req); return MemberUserScoreOperateRespDTO.success(req);
} }
private void publishDetailExpireEvent(MemberUserScoreOperateReqDTO req, List<ScoreDetailChangeDto> detailInfos) {
if (req.getSourceType().getReverseSource() == null || req.getOperateType() ==ScoreOperateTypeEnum.REDUCE) {
return;
}
//判断回退流程,回退的积分是否过期.如果过期再走正常的过期扣积分流程
MemberScoreDetailExpireEvent memberScoreDetailExpireEvent = new MemberScoreDetailExpireEvent();
memberScoreDetailExpireEvent.setDetailIds(detailInfos.stream().map(ScoreDetailChangeDto::getDetailId).collect(Collectors.toList()));
applicationContext.publishEvent(memberScoreDetailExpireEvent);
}
private void saveScoreDetailReleation(MemberUserScoreOperateReqDTO req, List<ScoreDetailChangeDto> detailInfos) {
if (StringUtils.isBlank(req.getReleationId())) {
return;
}
memberUserScoreDetailReleationService.updateReleation(req.getReleationId(), req.getSourceType(), detailInfos);
}
@Override @Override
@Transactional @Transactional
public List<MemberUserScoreOperateRespDTO> batchOperateScore(MemberUserScoreBatchOperateReqDTO req) { public List<MemberUserScoreOperateRespDTO> batchOperateScore(MemberUserScoreBatchOperateReqDTO req) {
...@@ -91,6 +131,31 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{ ...@@ -91,6 +131,31 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@Override
public List<ReleationScoreExpireInfoDTO> getScoreExpireInfo(String releationId, ScoreSourceTypeEnum scoreSourceTypeEnum) {
if (StringUtils.isBlank(releationId)) {
return Collections.emptyList();
}
LambdaQueryWrapper<MemberUserScoreDetailReleationDO> wrapper = Wrappers.lambdaQuery();
wrapper.eq(MemberUserScoreDetailReleationDO::getReleationId, releationId);
wrapper.eq(MemberUserScoreDetailReleationDO::getSourceType, scoreSourceTypeEnum.getValue());
wrapper.eq(MemberUserScoreDetailReleationDO::getStatus, MemberScoreDetailReleationStatueEnum.AVAILABLE.getValue());
List<MemberUserScoreDetailReleationDO> releationDOList = memberUserScoreDetailReleationService.list(wrapper);
Set<Long> detailIds = releationDOList.stream().map(MemberUserScoreDetailReleationDO::getDetailId).collect(Collectors.toSet());
LambdaQueryWrapper<MemberUserScoreDetailDO> detailWrappers = Wrappers.lambdaQuery();
detailWrappers.in(MemberUserScoreDetailDO::getId, detailIds);
Map<Long, MemberUserScoreDetailDO> detailIdInfoMap = scoreDetailService.list(detailWrappers).stream()
.collect(Collectors.toMap(MemberUserScoreDetailDO::getId, Function.identity(), (c1, c2) -> c1));
return releationDOList.stream().map(releation -> {
ReleationScoreExpireInfoDTO expireInfoDTO = new ReleationScoreExpireInfoDTO();
MemberUserScoreDetailDO detailDO = detailIdInfoMap.getOrDefault(releation.getDetailId(), new MemberUserScoreDetailDO());
expireInfoDTO.setDetailId(detailDO.getId());
expireInfoDTO.setExpireDate(detailDO.getExpireTime());
expireInfoDTO.setScoreCount(releation.getScoreCount());
return expireInfoDTO;
}).collect(Collectors.toList());
}
private void updateUserScore(MemberUserScoreOperateReqDTO req) { private void updateUserScore(MemberUserScoreOperateReqDTO req) {
memberUserScoreService.updateUserScore(MemberUserScoreUpdateReqDTO.builder() memberUserScoreService.updateUserScore(MemberUserScoreUpdateReqDTO.builder()
.memberId(req.getMemberId()) .memberId(req.getMemberId())
...@@ -101,8 +166,8 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{ ...@@ -101,8 +166,8 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{
.build()); .build());
} }
private void saveScoreDetail(MemberUserScoreOperateReqDTO req, Long scoreLogId) { private List<ScoreDetailChangeDto> saveScoreDetail(MemberUserScoreOperateReqDTO req, Long scoreLogId) {
scoreDetailService.updateScoreDetail(MemberUserScoreDetailUpdateReqDto.builder() return scoreDetailService.updateScoreDetail(MemberUserScoreDetailUpdateReqDto.builder()
.memberId(req.getMemberId()) .memberId(req.getMemberId())
.scoreCount(req.getScoreCount()) .scoreCount(req.getScoreCount())
.operateType(req.getSourceType() == ScoreSourceTypeEnum.MANUAL_OPERATE ? .operateType(req.getSourceType() == ScoreSourceTypeEnum.MANUAL_OPERATE ?
...@@ -110,6 +175,7 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{ ...@@ -110,6 +175,7 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{
.sourceType(req.getSourceType()) .sourceType(req.getSourceType())
.scoreLogId(scoreLogId) .scoreLogId(scoreLogId)
.expireDays(req.getExpireDays()) .expireDays(req.getExpireDays())
.releationId(req.getReleationId())
.build()); .build());
} }
......
package cn.iocoder.yudao.module.member.controller.admin.job; package cn.iocoder.yudao.module.member.controller.admin.job;
import cn.hutool.json.JSONArray;
import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.module.member.api.score.MemberUserScoreApi; import cn.iocoder.yudao.module.member.api.score.MemberUserScoreApi;
...@@ -46,7 +47,7 @@ public class MemberUserScoreExpireTask implements JobHandler { ...@@ -46,7 +47,7 @@ public class MemberUserScoreExpireTask implements JobHandler {
LambdaQueryWrapper<MemberUserScoreDetailDO> wrapper = Wrappers.lambdaQuery(); LambdaQueryWrapper<MemberUserScoreDetailDO> wrapper = Wrappers.lambdaQuery();
wrapper.in(MemberUserScoreDetailDO::getStatus, Lists.newArrayList(MemberScoreStatueEnum.AVAILABLE.getValue(), wrapper.in(MemberUserScoreDetailDO::getStatus, Lists.newArrayList(MemberScoreStatueEnum.AVAILABLE.getValue(),
MemberScoreStatueEnum.PART_AVAILABLE.getValue())); MemberScoreStatueEnum.PART_AVAILABLE.getValue()));
wrapper.eq(MemberUserScoreDetailDO::getExpireTime, DateUtils.getNextNDayStart(new Date(), 0)); wrapper.le(MemberUserScoreDetailDO::getExpireTime, DateUtils.getNextNDayStart(new Date(), 0));
wrapper.orderByAsc(MemberUserScoreDetailDO::getCreateTime); wrapper.orderByAsc(MemberUserScoreDetailDO::getCreateTime);
List<MemberUserScoreDetailDO> todoList = scoreDetailService.list(wrapper); List<MemberUserScoreDetailDO> todoList = scoreDetailService.list(wrapper);
log.info("member user score expire task, to expire record count :{}", todoList.size()); log.info("member user score expire task, to expire record count :{}", todoList.size());
...@@ -56,9 +57,9 @@ public class MemberUserScoreExpireTask implements JobHandler { ...@@ -56,9 +57,9 @@ public class MemberUserScoreExpireTask implements JobHandler {
for (MemberUserScoreDetailDO memberUserScoreDetailDO : todoList) { for (MemberUserScoreDetailDO memberUserScoreDetailDO : todoList) {
try { try {
log.info("score expire, score detail id :{}", memberUserScoreDetailDO.getId()); log.info("score expire, score detail id :{}", memberUserScoreDetailDO.getId());
List logIds = (List) memberUserScoreDetailDO.getExtParamByKey(MemberUserScoreDetailDO.MemberUserScoreDetailExtKey.LOG_IDS); JSONArray logIds = memberUserScoreDetailDO.getExtParamByKey(MemberUserScoreDetailDO.MemberUserScoreDetailExtKey.LOG_IDS);
Map<String, Object> extParam = new HashMap<>(); Map<String, Object> extParam = new HashMap<>();
extParam.put("scoreLogIds", logIds); extParam.put("scoreLogIds", logIds.toList(Long.class));
memberUserScoreApi.operateScore(MemberUserScoreOperateReqDTO.builder() memberUserScoreApi.operateScore(MemberUserScoreOperateReqDTO.builder()
.memberId(memberUserScoreDetailDO.getMemberId()) .memberId(memberUserScoreDetailDO.getMemberId())
.scoreCount(memberUserScoreDetailDO.getRemainCount()) .scoreCount(memberUserScoreDetailDO.getRemainCount())
......
package cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail; package cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
...@@ -55,8 +56,8 @@ public class MemberUserScoreDetailDO extends BaseDO { ...@@ -55,8 +56,8 @@ public class MemberUserScoreDetailDO extends BaseDO {
} }
public Object getExtParamByKey(MemberUserScoreDetailExtKey key) { public JSONArray getExtParamByKey(MemberUserScoreDetailExtKey key) {
JSONObject extParamJsonObject = JSONUtil.parseObj(extParam); JSONObject extParamJsonObject = JSONUtil.parseObj(extParam);
return extParamJsonObject.get(key.getKey(), List.class); return extParamJsonObject.get(key.getKey(), JSONArray.class);
} }
} }
package cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetailReleation;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.util.Date;
/**
* 会员积分 DO
*
* @author 系统管理员
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("member_user_score_detail_releation")
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MemberUserScoreDetailReleationDO extends BaseDO {
@TableId
private Long id;
private String releationId;
private Integer sourceType;
private Long detailId;
private Integer scoreCount;
private Integer status;
}
package cn.iocoder.yudao.module.member.dal.mysql.memberUserScoreDetailReleation;
import cn.iocoder.yudao.framework.mybatis.core.mapper.AbstractMapper;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetailReleation.MemberUserScoreDetailReleationDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
/**
* 会员积分详情 Mapper
* @author 系统管理员
*/
@Mapper
public interface MemberUserScoreDetailReleationMapper extends AbstractMapper<MemberUserScoreDetailReleationDO> {
}
package cn.iocoder.yudao.module.member.dto;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class ScoreDetailChangeDto {
private Long detailId;
private Integer scoreCount;
}
package cn.iocoder.yudao.module.member.listener;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.apollo.core.event.export.MemberScoreDetailExpireEvent;
import cn.iocoder.yudao.module.member.api.score.MemberUserScoreApi;
import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreOperateReqDTO;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail.MemberUserScoreDetailDO;
import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum;
import cn.iocoder.yudao.module.member.service.memberUserScoreDetail.MemberUserScoreDetailService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
@AllArgsConstructor
@Slf4j
public class MemberUserScoreDetailExpireListener {
@Resource
private MemberUserScoreDetailService scoreDetailService;
@Resource
private MemberUserScoreApi memberUserScoreApi;
@Async
@EventListener(MemberScoreDetailExpireEvent.class)
public void listen(MemberScoreDetailExpireEvent event) {
log.info("member user score expire listen,event:{}", JSONUtil.toJsonStr(event));
if (CollectionUtils.isEmpty(event.getDetailIds())) {
return;
}
//暂停一会确保那边事务执行完成
ThreadUtil.safeSleep(3000L);
LambdaQueryWrapper<MemberUserScoreDetailDO> wrapper = Wrappers.lambdaQuery();
wrapper.in(MemberUserScoreDetailDO::getId, event.getDetailIds());
wrapper.le(MemberUserScoreDetailDO::getExpireTime, new Date());
List<MemberUserScoreDetailDO> todoList = scoreDetailService.list(wrapper);
todoList.forEach(detail -> {
try {
log.info("score expire, score detail id :{}", detail.getId());
JSONArray jsonArray = detail.getExtParamByKey(MemberUserScoreDetailDO.MemberUserScoreDetailExtKey.LOG_IDS);
Map<String, Object> extParam = new HashMap<>();
extParam.put("scoreLogIds", jsonArray.toList(Long.class));
memberUserScoreApi.operateScore(MemberUserScoreOperateReqDTO.builder()
.memberId(detail.getMemberId())
.scoreCount(detail.getRemainCount())
.sourceType(ScoreSourceTypeEnum.SYSTEM_EXPIRED)
.extParam(extParam)
.build());
} catch (Exception e) {
log.error("member user score expire exception, data:{}", detail, e);
}
});
log.info("member user score expire listen finished");
}
}
...@@ -76,6 +76,15 @@ public class MemberUserScoreServiceImpl extends AbstractService<MemberUserScoreM ...@@ -76,6 +76,15 @@ public class MemberUserScoreServiceImpl extends AbstractService<MemberUserScoreM
if (memberUserScoreDO == null) { if (memberUserScoreDO == null) {
memberUserScoreDO = initMemberUserScore(query.getMemberId()); memberUserScoreDO = initMemberUserScore(query.getMemberId());
} }
// 回退流程增加积分
if (query.getSourceType().getReverseSource() != null && query.getOperateType() == ScoreOperateTypeEnum.ADD) {
// 回退流程增加积分,不仅需要新增持有积分,还需要减少使用积分
memberUserScoreDO.setHoldScore(memberUserScoreDO.getHoldScore() + query.getScoreCount());
memberUserScoreDO.setUsedScore(memberUserScoreDO.getUsedScore() - query.getScoreCount());
this.saveOrUpdate(memberUserScoreDO);
return;
}
//正常流程 增加积分 减少积分
if (query.getOperateType() == ScoreOperateTypeEnum.ADD) { if (query.getOperateType() == ScoreOperateTypeEnum.ADD) {
memberUserScoreDO.setHoldScore(memberUserScoreDO.getHoldScore() + query.getScoreCount()); memberUserScoreDO.setHoldScore(memberUserScoreDO.getHoldScore() + query.getScoreCount());
this.saveOrUpdate(memberUserScoreDO); this.saveOrUpdate(memberUserScoreDO);
......
...@@ -3,6 +3,9 @@ package cn.iocoder.yudao.module.member.service.memberUserScoreDetail; ...@@ -3,6 +3,9 @@ package cn.iocoder.yudao.module.member.service.memberUserScoreDetail;
import cn.iocoder.yudao.framework.mybatis.core.service.IService; import cn.iocoder.yudao.framework.mybatis.core.service.IService;
import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreDetailUpdateReqDto; import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreDetailUpdateReqDto;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail.MemberUserScoreDetailDO; import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail.MemberUserScoreDetailDO;
import cn.iocoder.yudao.module.member.dto.ScoreDetailChangeDto;
import java.util.List;
/** /**
* 会员积分日志 Service 接口 * 会员积分日志 Service 接口
...@@ -11,5 +14,5 @@ import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail.Membe ...@@ -11,5 +14,5 @@ import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail.Membe
*/ */
public interface MemberUserScoreDetailService extends IService<MemberUserScoreDetailDO> { public interface MemberUserScoreDetailService extends IService<MemberUserScoreDetailDO> {
void updateScoreDetail(MemberUserScoreDetailUpdateReqDto reqDto); List<ScoreDetailChangeDto> updateScoreDetail(MemberUserScoreDetailUpdateReqDto reqDto);
} }
package cn.iocoder.yudao.module.member.service.memberUserScoreDetail; package cn.iocoder.yudao.module.member.service.memberUserScoreDetail;
import cn.hutool.core.lang.generator.SnowflakeGenerator;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService; import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreDetailUpdateReqDto; import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreDetailUpdateReqDto;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail.MemberUserScoreDetailDO; import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail.MemberUserScoreDetailDO;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetailReleation.MemberUserScoreDetailReleationDO;
import cn.iocoder.yudao.module.member.dal.mysql.memberUserScoreDetail.MemberUserScoreDetailMapper; import cn.iocoder.yudao.module.member.dal.mysql.memberUserScoreDetail.MemberUserScoreDetailMapper;
import cn.iocoder.yudao.module.member.dto.ScoreDetailChangeDto;
import cn.iocoder.yudao.module.member.enums.MemberScoreStatueEnum; import cn.iocoder.yudao.module.member.enums.MemberScoreStatueEnum;
import cn.iocoder.yudao.module.member.enums.ScoreOperateTypeEnum; import cn.iocoder.yudao.module.member.enums.ScoreOperateTypeEnum;
import cn.iocoder.yudao.module.member.service.memberUserScoreDetailReleation.MemberUserScoreDetailReleationService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.*; import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addDays; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addDays;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.getNextNDayStart; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.getNextNDayStart;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.MEMBER_ID_IS_NULL; import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.MEMBER_SCORE_NOT_ENOUGH; import static cn.iocoder.yudao.module.member.enums.MemberScoreStatueEnum.AVAILABLE;
import static cn.iocoder.yudao.module.member.enums.MemberScoreStatueEnum.PART_AVAILABLE;
/** /**
* 会员积分详情 Service 实现类 * 会员积分详情 Service 实现类
...@@ -31,62 +39,124 @@ import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.MEMBER_SCO ...@@ -31,62 +39,124 @@ import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.MEMBER_SCO
@Service @Service
@Validated @Validated
public class MemberUserScoreDetailServiceImpl extends AbstractService<MemberUserScoreDetailMapper, MemberUserScoreDetailDO> implements MemberUserScoreDetailService { public class MemberUserScoreDetailServiceImpl extends AbstractService<MemberUserScoreDetailMapper, MemberUserScoreDetailDO> implements MemberUserScoreDetailService {
@Resource @Autowired
private MemberUserScoreDetailMapper detailMapper; private MemberUserScoreDetailMapper detailMapper;
@Autowired
private MemberUserScoreDetailReleationService memberUserScoreDetailReleationService;
@Autowired
private SnowflakeGenerator snowflakeGenerator;
@Override @Override
public void updateScoreDetail(MemberUserScoreDetailUpdateReqDto reqDto) { public List<ScoreDetailChangeDto> updateScoreDetail(MemberUserScoreDetailUpdateReqDto reqDto) {
// 增加积分,只需要新增一条记录 // 增加积分,只需要新增一条记录
// 减少积分,使已有记录失效 // 减少积分,使已有记录失效
if (reqDto.getMemberId() == null) { if (reqDto.getMemberId() == null) {
throw exception(MEMBER_ID_IS_NULL); throw exception(MEMBER_ID_IS_NULL);
} }
if (reqDto.getSourceType().getReverseSource() != null && reqDto.getOperateType() == ScoreOperateTypeEnum.ADD) {
//说明是回退流程新增加分 根据releationId 和source 获取之前操作过的detail
//如果是回退流程减少积分 则走正常的扣减逻辑
return dealReverseSourceDetail(reqDto);
}
if (reqDto.getOperateType() == ScoreOperateTypeEnum.ADD) { if (reqDto.getOperateType() == ScoreOperateTypeEnum.ADD) {
update4Add(reqDto); return update4Add(reqDto);
} }
if (reqDto.getOperateType() == ScoreOperateTypeEnum.REDUCE) { if (reqDto.getOperateType() == ScoreOperateTypeEnum.REDUCE) {
update4Reduce(reqDto); return update4Reduce(reqDto);
}
return Collections.emptyList();
}
private List<ScoreDetailChangeDto> dealReverseSourceDetail(MemberUserScoreDetailUpdateReqDto reqDto) {
if (StringUtils.isBlank(reqDto.getReleationId())) {
throw exception(REVERSE_SOURCE_NO_RELEATION_ID);
}
List<MemberUserScoreDetailReleationDO> releationDOList = memberUserScoreDetailReleationService.listByReleationIdAndSource(reqDto.getReleationId(),
reqDto.getSourceType().getReverseSource().getValue());
if (reqDto.getOperateType() == ScoreOperateTypeEnum.ADD) {
//积分恢复
dealReverseSourceAdd(releationDOList, reqDto);
} }
return releationDOList.stream().map(e ->ScoreDetailChangeDto.builder().detailId(e.getDetailId()).scoreCount(e.getScoreCount()).build())
.collect(Collectors.toList());
} }
private void update4Reduce(MemberUserScoreDetailUpdateReqDto reqDto) { private void dealReverseSourceAdd(List<MemberUserScoreDetailReleationDO> releationDOList, MemberUserScoreDetailUpdateReqDto reqDto) {
Long scoreCount = detailMapper.getScoreTotalByStatus(reqDto.getMemberId(), Lists.newArrayList(MemberScoreStatueEnum.AVAILABLE.getValue(), MemberScoreStatueEnum.PART_AVAILABLE.getValue())); List<Long> detailIds = releationDOList.stream().map(MemberUserScoreDetailReleationDO::getDetailId).collect(Collectors.toList());
Map<Long, MemberUserScoreDetailReleationDO> detailIdReleationDoMap = releationDOList.stream().collect(Collectors.toMap(MemberUserScoreDetailReleationDO::getDetailId, Function.identity(), (c1, c2) -> c1));
LambdaQueryWrapper<MemberUserScoreDetailDO> wrappers = Wrappers.lambdaQuery();
wrappers.in(MemberUserScoreDetailDO::getId, detailIds);
List<MemberUserScoreDetailDO> detailDOList = this.list(wrappers);
List<MemberUserScoreDetailDO> saveDetailList = new ArrayList<>();
detailDOList.forEach(detail -> {
MemberUserScoreDetailReleationDO releationDO = detailIdReleationDoMap.get(detail.getId());
if (releationDO == null) {
return;
}
MemberUserScoreDetailDO updateData = new MemberUserScoreDetailDO();
updateData.setId(detail.getId());
updateData.setRemainCount(detail.getRemainCount() + releationDO.getScoreCount());
updateData.setStatus(Objects.equals(detail.getScoreCount(), updateData.getRemainCount()) ? AVAILABLE.getValue() : PART_AVAILABLE.getValue());
updateData.setExtParam(addScoreLogId(detail.getExtParam(), reqDto.getScoreLogId()));
saveDetailList.add(updateData);
});
this.saveOrUpdateBatch(saveDetailList);
}
private List<ScoreDetailChangeDto> update4Reduce(MemberUserScoreDetailUpdateReqDto reqDto) {
Long scoreCount = detailMapper.getScoreTotalByStatus(reqDto.getMemberId(), Lists.newArrayList(AVAILABLE.getValue(), MemberScoreStatueEnum.PART_AVAILABLE.getValue()));
if (scoreCount == null || scoreCount < reqDto.getScoreCount()) { if (scoreCount == null || scoreCount < reqDto.getScoreCount()) {
throw exception(MEMBER_SCORE_NOT_ENOUGH); throw exception(MEMBER_SCORE_NOT_ENOUGH);
} }
LambdaQueryWrapper<MemberUserScoreDetailDO> wrappers = Wrappers.lambdaQuery(); LambdaQueryWrapper<MemberUserScoreDetailDO> wrappers = Wrappers.lambdaQuery();
wrappers.eq(MemberUserScoreDetailDO::getMemberId, reqDto.getMemberId()); wrappers.eq(MemberUserScoreDetailDO::getMemberId, reqDto.getMemberId());
wrappers.in(MemberUserScoreDetailDO::getStatus, Lists.newArrayList(MemberScoreStatueEnum.AVAILABLE.getValue(), MemberScoreStatueEnum.PART_AVAILABLE.getValue())); wrappers.in(MemberUserScoreDetailDO::getStatus, Lists.newArrayList(AVAILABLE.getValue(), MemberScoreStatueEnum.PART_AVAILABLE.getValue()));
wrappers.orderByAsc(MemberUserScoreDetailDO::getExpireTime);
List<MemberUserScoreDetailDO> detailList = this.list(wrappers); List<MemberUserScoreDetailDO> detailList = this.list(wrappers);
detailList = sort(detailList);
int needCount = reqDto.getScoreCount(); int needCount = reqDto.getScoreCount();
List<MemberUserScoreDetailDO> updateDetailList = new ArrayList<>(); List<MemberUserScoreDetailDO> updateDetailList = new ArrayList<>();
List<ScoreDetailChangeDto> scoreDetailChangeDtos = new ArrayList<>();
for (MemberUserScoreDetailDO memberUserScoreDetailDO : detailList) { for (MemberUserScoreDetailDO memberUserScoreDetailDO : detailList) {
if (needCount == 0) { if (needCount <= 0) {
break; break;
} }
Integer usedScoreCount;
if (memberUserScoreDetailDO.getRemainCount() <= needCount) { if (memberUserScoreDetailDO.getRemainCount() <= needCount) {
needCount -= memberUserScoreDetailDO.getRemainCount(); usedScoreCount = memberUserScoreDetailDO.getRemainCount();
needCount -= usedScoreCount;
memberUserScoreDetailDO.setRemainCount(0); memberUserScoreDetailDO.setRemainCount(0);
memberUserScoreDetailDO.setStatus(MemberScoreStatueEnum.NOT_AVAILABLE.getValue()); memberUserScoreDetailDO.setStatus(MemberScoreStatueEnum.NOT_AVAILABLE.getValue());
memberUserScoreDetailDO.setExtParam(addScoreLogId(memberUserScoreDetailDO.getExtParam(), reqDto.getScoreLogId())); memberUserScoreDetailDO.setExtParam(addScoreLogId(memberUserScoreDetailDO.getExtParam(), reqDto.getScoreLogId()));
updateDetailList.add(memberUserScoreDetailDO); updateDetailList.add(memberUserScoreDetailDO);
} else { } else {
memberUserScoreDetailDO.setRemainCount(memberUserScoreDetailDO.getRemainCount() - needCount); usedScoreCount = needCount;
memberUserScoreDetailDO.setRemainCount(memberUserScoreDetailDO.getRemainCount() - usedScoreCount);
needCount = 0; needCount = 0;
memberUserScoreDetailDO.setStatus(MemberScoreStatueEnum.PART_AVAILABLE.getValue()); memberUserScoreDetailDO.setStatus(MemberScoreStatueEnum.PART_AVAILABLE.getValue());
memberUserScoreDetailDO.setExtParam(addScoreLogId(memberUserScoreDetailDO.getExtParam(), reqDto.getScoreLogId())); memberUserScoreDetailDO.setExtParam(addScoreLogId(memberUserScoreDetailDO.getExtParam(), reqDto.getScoreLogId()));
updateDetailList.add(memberUserScoreDetailDO); updateDetailList.add(memberUserScoreDetailDO);
} }
scoreDetailChangeDtos.add(ScoreDetailChangeDto.builder().detailId(memberUserScoreDetailDO.getId()).scoreCount(usedScoreCount).build());
} }
this.updateBatch(updateDetailList); this.updateBatch(updateDetailList);
return scoreDetailChangeDtos;
}
private List<MemberUserScoreDetailDO> sort(List<MemberUserScoreDetailDO> detailList) {
// 使用优先级 expire time asc > 没有过期时间的积分
Map<Boolean, List<MemberUserScoreDetailDO>> groupDetailMap = detailList.stream().collect(Collectors.groupingBy(e -> e.getExpireTime() != null));
List<MemberUserScoreDetailDO> expireTimeDetail = groupDetailMap.getOrDefault(true, new ArrayList<>()).stream()
.sorted(Comparator.comparing(MemberUserScoreDetailDO::getExpireTime)).collect(Collectors.toList());
expireTimeDetail.addAll(groupDetailMap.getOrDefault(false, new ArrayList<>()));
return expireTimeDetail;
} }
private void update4Add(MemberUserScoreDetailUpdateReqDto reqDto) { private List<ScoreDetailChangeDto> update4Add(MemberUserScoreDetailUpdateReqDto reqDto) {
MemberUserScoreDetailDO memberUserScoreDetailDO = new MemberUserScoreDetailDO(); MemberUserScoreDetailDO memberUserScoreDetailDO = new MemberUserScoreDetailDO();
memberUserScoreDetailDO.setId(snowflakeGenerator.next());
memberUserScoreDetailDO.setMemberId(reqDto.getMemberId()); memberUserScoreDetailDO.setMemberId(reqDto.getMemberId());
memberUserScoreDetailDO.setScoreCount(reqDto.getScoreCount()); memberUserScoreDetailDO.setScoreCount(reqDto.getScoreCount());
memberUserScoreDetailDO.setStatus(MemberScoreStatueEnum.AVAILABLE.getValue()); memberUserScoreDetailDO.setStatus(AVAILABLE.getValue());
memberUserScoreDetailDO.setRemainCount(reqDto.getScoreCount()); memberUserScoreDetailDO.setRemainCount(reqDto.getScoreCount());
memberUserScoreDetailDO.setCreateTime(new Date()); memberUserScoreDetailDO.setCreateTime(new Date());
if (reqDto.getExpireDays() != null) { if (reqDto.getExpireDays() != null) {
...@@ -96,6 +166,7 @@ public class MemberUserScoreDetailServiceImpl extends AbstractService<MemberUser ...@@ -96,6 +166,7 @@ public class MemberUserScoreDetailServiceImpl extends AbstractService<MemberUser
extParma.put(MemberUserScoreDetailDO.MemberUserScoreDetailExtKey.LOG_IDS.getKey(), Lists.newArrayList(reqDto.getScoreLogId())); extParma.put(MemberUserScoreDetailDO.MemberUserScoreDetailExtKey.LOG_IDS.getKey(), Lists.newArrayList(reqDto.getScoreLogId()));
memberUserScoreDetailDO.setExtParam(JSONUtil.toJsonStr(extParma)); memberUserScoreDetailDO.setExtParam(JSONUtil.toJsonStr(extParma));
this.saveOrUpdate(memberUserScoreDetailDO); this.saveOrUpdate(memberUserScoreDetailDO);
return Lists.newArrayList(ScoreDetailChangeDto.builder().detailId(memberUserScoreDetailDO.getId()).scoreCount(reqDto.getScoreCount()).build());
} }
private String addScoreLogId(String extParam, Long scoreLogId) { private String addScoreLogId(String extParam, Long scoreLogId) {
......
package cn.iocoder.yudao.module.member.service.memberUserScoreDetailReleation;
import cn.iocoder.yudao.framework.mybatis.core.service.IService;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetailReleation.MemberUserScoreDetailReleationDO;
import cn.iocoder.yudao.module.member.dto.ScoreDetailChangeDto;
import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum;
import java.util.List;
/**
* 会员积分日志 Service 接口
*
* @author 系统管理员
*/
public interface MemberUserScoreDetailReleationService extends IService<MemberUserScoreDetailReleationDO> {
void updateReleation(String releationId, ScoreSourceTypeEnum sourceType, List<ScoreDetailChangeDto> detailInfos);
List<MemberUserScoreDetailReleationDO> listByReleationIdAndSource(String releationId, int value);
}
package cn.iocoder.yudao.module.member.service.memberUserScoreDetailReleation;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetailReleation.MemberUserScoreDetailReleationDO;
import cn.iocoder.yudao.module.member.dal.mysql.memberUserScoreDetailReleation.MemberUserScoreDetailReleationMapper;
import cn.iocoder.yudao.module.member.dto.ScoreDetailChangeDto;
import cn.iocoder.yudao.module.member.enums.MemberScoreDetailReleationStatueEnum;
import cn.iocoder.yudao.module.member.enums.ScoreOperateTypeEnum;
import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* 会员积分详情 Service 实现类
*
* @author 系统管理员
*/
@Service
@Validated
public class MemberUserScoreDetailReleationServiceImpl extends AbstractService<MemberUserScoreDetailReleationMapper, MemberUserScoreDetailReleationDO> implements MemberUserScoreDetailReleationService {
@Override
public void updateReleation(String releationId, ScoreSourceTypeEnum sourceType, List<ScoreDetailChangeDto> detailInfos) {
if (StringUtils.isBlank(releationId) || CollectionUtils.isEmpty(detailInfos)) {
return;
}
if (sourceType.getReverseSource() != null) {
//回退流程, 失效掉之前的关联关系
LambdaUpdateWrapper<MemberUserScoreDetailReleationDO> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.eq(MemberUserScoreDetailReleationDO::getReleationId, releationId);
updateWrapper.eq(MemberUserScoreDetailReleationDO::getSourceType, sourceType.getReverseSource().getValue());
updateWrapper.in(MemberUserScoreDetailReleationDO::getDetailId, detailInfos.stream().map(ScoreDetailChangeDto::getDetailId).collect(Collectors.toList()));
updateWrapper.set(MemberUserScoreDetailReleationDO::getStatus, MemberScoreDetailReleationStatueEnum.NOT_AVAILABLE.getValue());
this.update(updateWrapper);
return;
}
List<MemberUserScoreDetailReleationDO> dos = detailInfos.stream().map(scoreDetailChangeDto -> {
MemberUserScoreDetailReleationDO releationDO = new MemberUserScoreDetailReleationDO();
releationDO.setReleationId(releationId);
releationDO.setSourceType(sourceType.getValue());
releationDO.setDetailId(scoreDetailChangeDto.getDetailId());
releationDO.setScoreCount(scoreDetailChangeDto.getScoreCount());
releationDO.setStatus(MemberScoreDetailReleationStatueEnum.AVAILABLE.getValue());
return releationDO;
}).collect(Collectors.toList());
this.saveOrUpdateBatch(dos);
}
@Override
public List<MemberUserScoreDetailReleationDO> listByReleationIdAndSource(String releationId, int sourceType) {
if (StringUtils.isBlank(releationId)) {
return Collections.emptyList();
}
LambdaQueryWrapper<MemberUserScoreDetailReleationDO> wrapper = Wrappers.lambdaQuery();
wrapper.eq(MemberUserScoreDetailReleationDO::getReleationId, releationId);
wrapper.eq(MemberUserScoreDetailReleationDO::getSourceType, sourceType);
wrapper.eq(MemberUserScoreDetailReleationDO::getStatus, MemberScoreDetailReleationStatueEnum.AVAILABLE.getValue());
return this.list(wrapper);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.member.dal.mysql.memberUserScoreDetailReleation.MemberUserScoreDetailReleationMapper">
</mapper>
...@@ -29,5 +29,6 @@ public interface ErrorCodeConstants { ...@@ -29,5 +29,6 @@ public interface ErrorCodeConstants {
ErrorCode REWARD_REDEEM_BATCH_VERIFY_ERROR = new ErrorCode(1001011021, "reward.redeem.batch.verify.error"); ErrorCode REWARD_REDEEM_BATCH_VERIFY_ERROR = new ErrorCode(1001011021, "reward.redeem.batch.verify.error");
ErrorCode REWARD_REDEEM_VERIFY_BACK_STATUS = new ErrorCode(1001011022, "reward.redeem.verify.back.status"); ErrorCode REWARD_REDEEM_VERIFY_BACK_STATUS = new ErrorCode(1001011022, "reward.redeem.verify.back.status");
ErrorCode REDEEM_IMPORT_MAX_COUNT = new ErrorCode(1001011023, "redeem.import.max.count"); ErrorCode REDEEM_IMPORT_MAX_COUNT = new ErrorCode(1001011023, "redeem.import.max.count");
ErrorCode REDEEM_CANCEL_STATUS_ERROR = new ErrorCode(1001011024, "redeem.cancel.status.error");
} }
...@@ -79,12 +79,12 @@ public class RedeemRewardApiImpl implements RedeemRewardApi { ...@@ -79,12 +79,12 @@ public class RedeemRewardApiImpl implements RedeemRewardApi {
if (!lock) { if (!lock) {
throw exception(GET_LOCK_FAILED); throw exception(GET_LOCK_FAILED);
} }
// 更新礼品
redeemReward(rewardDO, redeemRewardReqVO.getRewardCount());
// 添加兑换记录 // 添加兑换记录
Long redeemId = addRedeemRecord(redeemRewardReqVO); Long redeemId = addRedeemRecord(redeemRewardReqVO, rewardDO);
// 更新会员积分 // 更新会员积分
updateMemberScore(redeemRewardReqVO, rewardDO, redeemId); updateMemberScore(redeemRewardReqVO, rewardDO, redeemId);
// 更新礼品
redeemReward(rewardDO, redeemRewardReqVO.getRewardCount());
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} finally { } finally {
...@@ -111,14 +111,16 @@ public class RedeemRewardApiImpl implements RedeemRewardApi { ...@@ -111,14 +111,16 @@ public class RedeemRewardApiImpl implements RedeemRewardApi {
.memberId(redeemRewardReqVO.getMemberId()) .memberId(redeemRewardReqVO.getMemberId())
.sourceType(ScoreSourceTypeEnum.EXCHANGE_REWARD) .sourceType(ScoreSourceTypeEnum.EXCHANGE_REWARD)
.scoreCount(redeemRewardReqVO.getRewardCount() * rewardDO.getPointsRequire()) .scoreCount(redeemRewardReqVO.getRewardCount() * rewardDO.getPointsRequire())
.releationId(String.valueOf(redeemId))
.extParam(extParam) .extParam(extParam)
.build()); .build());
} }
private Long addRedeemRecord(RedeemRewardReqVO redeemRewardReqVO) { private Long addRedeemRecord(RedeemRewardReqVO redeemRewardReqVO, RewardDO rewardDO) {
RewardRedeemDO rewardRedeemDO = BeanUtil.copyProperties(redeemRewardReqVO, RewardRedeemDO.class); RewardRedeemDO rewardRedeemDO = BeanUtil.copyProperties(redeemRewardReqVO, RewardRedeemDO.class);
rewardRedeemDO.setId(snowflakeGenerator.next()); rewardRedeemDO.setId(snowflakeGenerator.next());
rewardRedeemDO.setStatus(RewardRedeemStatusEnum.REDEEMING.getValue()); rewardRedeemDO.setStatus(RewardRedeemStatusEnum.REDEEMING.getValue());
rewardRedeemDO.setScoreCount(redeemRewardReqVO.getRewardCount()* rewardDO.getPointsRequire());
rewardRedeemMapper.insert(rewardRedeemDO); rewardRedeemMapper.insert(rewardRedeemDO);
return rewardRedeemDO.getId(); return rewardRedeemDO.getId();
} }
...@@ -164,12 +166,12 @@ public class RedeemRewardApiImpl implements RedeemRewardApi { ...@@ -164,12 +166,12 @@ public class RedeemRewardApiImpl implements RedeemRewardApi {
throw exception(GET_LOCK_FAILED); throw exception(GET_LOCK_FAILED);
} }
for (RedeemRewardReqVO redeemRewardReqVO : redeemRewardReqVOList) { for (RedeemRewardReqVO redeemRewardReqVO : redeemRewardReqVOList) {
// 更新礼品
redeemReward(rewardDO, redeemRewardReqVO.getRewardCount());
// 添加兑换记录 // 添加兑换记录
Long redeemId = addRedeemRecord(redeemRewardReqVO); Long redeemId = addRedeemRecord(redeemRewardReqVO, rewardDO);
// 更新会员积分 // 更新会员积分
updateMemberScore(redeemRewardReqVO, rewardDO, redeemId); updateMemberScore(redeemRewardReqVO, rewardDO, redeemId);
// 更新礼品
redeemReward(rewardDO, redeemRewardReqVO.getRewardCount());
} }
} catch (Exception e) { } catch (Exception e) {
log.error("redeem reward exception",e); log.error("redeem reward exception",e);
......
...@@ -81,6 +81,18 @@ public class RedeemRewardController { ...@@ -81,6 +81,18 @@ public class RedeemRewardController {
return success(rewardRedeemService.export(reqVO)); return success(rewardRedeemService.export(reqVO));
} }
@PostMapping("record/cancel/check")
@ApiOperation("撤销检查")
public CommonResult<RedeemCancelCheckRespVO> cancelCheck(@Valid @RequestBody RewardRedeemUpdateReqVO reqVO) {
return success(rewardRedeemService.cancelCheck(reqVO));
}
@PostMapping("record/cancel")
@ApiOperation("撤销")
public CommonResult<Boolean> cancel(@Valid @RequestBody RewardRedeemUpdateReqVO reqVO) {
return success(rewardRedeemService.cancel(reqVO));
}
@PostMapping("record/import/template") @PostMapping("record/import/template")
@ApiOperation("导入模板下载") @ApiOperation("导入模板下载")
public void importTemplate(HttpServletResponse response) throws IOException { public void importTemplate(HttpServletResponse response) throws IOException {
......
...@@ -33,6 +33,7 @@ public class RewardRedeemDO extends BaseDO { ...@@ -33,6 +33,7 @@ public class RewardRedeemDO extends BaseDO {
* 兑换状态 * 兑换状态
*/ */
private Integer status; private Integer status;
private Integer scoreCount;
/** /**
* 兑换数量 * 兑换数量
*/ */
......
...@@ -33,4 +33,8 @@ public interface RewardRedeemService extends IService<RewardRedeemDO> { ...@@ -33,4 +33,8 @@ public interface RewardRedeemService extends IService<RewardRedeemDO> {
Integer exportCount(RewardRedeemPageReqVO request); Integer exportCount(RewardRedeemPageReqVO request);
RecordInfoImportRespVO recordImport(List<RedeemInfoImportExcelVO> dataList); RecordInfoImportRespVO recordImport(List<RedeemInfoImportExcelVO> dataList);
Boolean cancel(RewardRedeemUpdateReqVO reqVO);
RedeemCancelCheckRespVO cancelCheck(RewardRedeemUpdateReqVO reqVO);
} }
...@@ -12,6 +12,10 @@ import cn.iocoder.yudao.module.ecw.api.currency.CurrencyApi; ...@@ -12,6 +12,10 @@ import cn.iocoder.yudao.module.ecw.api.currency.CurrencyApi;
import cn.iocoder.yudao.module.ecw.api.currency.dto.CurrencyRespDTO; import cn.iocoder.yudao.module.ecw.api.currency.dto.CurrencyRespDTO;
import cn.iocoder.yudao.module.ecw.api.express.ExpressApi; import cn.iocoder.yudao.module.ecw.api.express.ExpressApi;
import cn.iocoder.yudao.module.ecw.api.express.dto.ExpressRespDTO; import cn.iocoder.yudao.module.ecw.api.express.dto.ExpressRespDTO;
import cn.iocoder.yudao.module.member.api.score.MemberUserScoreApi;
import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreOperateReqDTO;
import cn.iocoder.yudao.module.member.api.score.dto.ReleationScoreExpireInfoDTO;
import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum;
import cn.iocoder.yudao.module.reward.dal.dataobject.redeem.RewardRedeemDO; import cn.iocoder.yudao.module.reward.dal.dataobject.redeem.RewardRedeemDO;
import cn.iocoder.yudao.module.reward.dal.mysql.redeem.RewardRedeemMapper; import cn.iocoder.yudao.module.reward.dal.mysql.redeem.RewardRedeemMapper;
import cn.iocoder.yudao.module.reward.dto.RewardRedeemVerifyDTO; import cn.iocoder.yudao.module.reward.dto.RewardRedeemVerifyDTO;
...@@ -29,6 +33,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; ...@@ -29,6 +33,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
...@@ -53,13 +58,15 @@ public class RewardRedeemServiceImpl extends AbstractService<RewardRedeemMapper, ...@@ -53,13 +58,15 @@ public class RewardRedeemServiceImpl extends AbstractService<RewardRedeemMapper,
private final FileMakeApi fileMakeApi; private final FileMakeApi fileMakeApi;
private final ExpressApi expressApi; private final ExpressApi expressApi;
private final CurrencyApi currencyApi; private final CurrencyApi currencyApi;
private final MemberUserScoreApi memberUserScoreApi;
public RewardRedeemServiceImpl(RewardRedeemMapper rewardRedeemMapper, FileMakeApi fileMakeApi, public RewardRedeemServiceImpl(RewardRedeemMapper rewardRedeemMapper, FileMakeApi fileMakeApi,
ExpressApi expressApi, CurrencyApi currencyApi) { ExpressApi expressApi, CurrencyApi currencyApi, MemberUserScoreApi memberUserScoreApi) {
this.rewardRedeemMapper = rewardRedeemMapper; this.rewardRedeemMapper = rewardRedeemMapper;
this.fileMakeApi = fileMakeApi; this.fileMakeApi = fileMakeApi;
this.expressApi = expressApi; this.expressApi = expressApi;
this.currencyApi = currencyApi; this.currencyApi = currencyApi;
this.memberUserScoreApi = memberUserScoreApi;
} }
@Override @Override
...@@ -237,6 +244,59 @@ public class RewardRedeemServiceImpl extends AbstractService<RewardRedeemMapper, ...@@ -237,6 +244,59 @@ public class RewardRedeemServiceImpl extends AbstractService<RewardRedeemMapper,
return RecordInfoImportRespVO.builder().build(); return RecordInfoImportRespVO.builder().build();
} }
@Override
@Transactional
public Boolean cancel(RewardRedeemUpdateReqVO reqVO) {
//取消兑换 状态改为已取消 回退扣减的积分 ,如果存在已过期的积分,则直接过期
if (reqVO.getId() == null) {
throw exception(ErrorCodeConstants.REWARD_REDEEM_NOT_EXIST);
}
RewardRedeemDO rewardRedeemDO = this.getById(reqVO.getId());
if (rewardRedeemDO == null) {
throw exception(ErrorCodeConstants.REWARD_REDEEM_NOT_EXIST);
}
if (rewardRedeemDO.getStatus() != RewardRedeemStatusEnum.REDEEMING.getValue()) {
throw exception(ErrorCodeConstants.REDEEM_CANCEL_STATUS_ERROR);
}
LambdaUpdateWrapper<RewardRedeemDO> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.eq(RewardRedeemDO::getId, reqVO.getId());
updateWrapper.eq(RewardRedeemDO::getStatus, RewardRedeemStatusEnum.REDEEMING.getValue());
updateWrapper.set(RewardRedeemDO::getStatus, RewardRedeemStatusEnum.CANCELED.getValue());
boolean updateSuccess = this.update(null, updateWrapper);
if (!updateSuccess) {
throw exception(ErrorCodeConstants.REDEEM_CANCEL_STATUS_ERROR);
}
Map<String, Object> extParam = new HashMap<>();
extParam.put("redeemId", reqVO.getId());
memberUserScoreApi.operateScore(MemberUserScoreOperateReqDTO.builder()
.memberId(rewardRedeemDO.getMemberId())
.sourceType(ScoreSourceTypeEnum.EXCHANGE_REWARD_CANCEL)
.scoreCount(rewardRedeemDO.getScoreCount())
.extParam(extParam)
.releationId(String.valueOf(reqVO.getId()))
.build());
return true;
}
@Override
public RedeemCancelCheckRespVO cancelCheck(RewardRedeemUpdateReqVO reqVO) {
if (reqVO.getId() == null) {
throw exception(ErrorCodeConstants.REWARD_REDEEM_NOT_EXIST);
}
RewardRedeemDO rewardRedeemDO = this.getById(reqVO.getId());
if (rewardRedeemDO == null) {
throw exception(ErrorCodeConstants.REWARD_REDEEM_NOT_EXIST);
}
if (rewardRedeemDO.getStatus() != RewardRedeemStatusEnum.REDEEMING.getValue()) {
throw exception(ErrorCodeConstants.REDEEM_CANCEL_STATUS_ERROR);
}
List<ReleationScoreExpireInfoDTO> scoreExpireInfo = memberUserScoreApi
.getScoreExpireInfo(String.valueOf(reqVO.getId()), ScoreSourceTypeEnum.EXCHANGE_REWARD);
int totalExpireScore = scoreExpireInfo.stream().filter(e -> e.getExpireDate() != null && e.getExpireDate().compareTo(new Date()) < 0)
.mapToInt(ReleationScoreExpireInfoDTO::getScoreCount).sum();
return RedeemCancelCheckRespVO.builder().expireCount(totalExpireScore).build();
}
private Map<String, String> validate(List<RedeemInfoImportExcelVO> dataList, Map<String, CurrencyRespDTO> titleZhCurrencyMap, private Map<String, String> validate(List<RedeemInfoImportExcelVO> dataList, Map<String, CurrencyRespDTO> titleZhCurrencyMap,
Map<String, CurrencyRespDTO> titleEnCurrencyMap, Map<String, CurrencyRespDTO> titleEnCurrencyMap,
Map<String, ExpressRespDTO> nameExpressMap) { Map<String, ExpressRespDTO> nameExpressMap) {
......
package cn.iocoder.yudao.module.reward.vo.reward;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class RedeemCancelCheckRespVO {
@ExcelProperty("过期积分数量")
private int expireCount;
}
...@@ -102,7 +102,7 @@ ...@@ -102,7 +102,7 @@
er.title_fr as rewardTitleFr, er.title_fr as rewardTitleFr,
en.title_zh as nodeTitleZh, en.title_zh as nodeTitleZh,
en.title_en as nodeTitleEn, en.title_en as nodeTitleEn,
er.points_require*err.reward_count as totalCount, err.score_count as totalCount,
suc.username as creatorName, suc.username as creatorName,
suu.username as updaterName, suu.username as updaterName,
cc.title_zh as currencyTitleZh, cc.title_zh as currencyTitleZh,
......
...@@ -1020,4 +1020,5 @@ redeem.import.max.count = allow maximum number of imports is {} ...@@ -1020,4 +1020,5 @@ redeem.import.max.count = allow maximum number of imports is {}
dict.unknown.error = Not in dict {0}: {1} dict.unknown.error = Not in dict {0}: {1}
express.not.exist = express not exist express.not.exist = express not exist
currency.not.exist = currency not exist currency.not.exist = currency not exist
date.format.error = date format error, for example : 2024-01-01 12:11:11 date.format.error = date format error, for example : 2024-01-01 12:11:11
\ No newline at end of file redeem.cancel.status.error = record status must be redeeming
\ No newline at end of file
...@@ -1024,4 +1024,5 @@ redeem.import.max.count = \u5141\u8BB8\u7684\u6700\u5927\u5BFC\u5165\u6761\u6570 ...@@ -1024,4 +1024,5 @@ redeem.import.max.count = \u5141\u8BB8\u7684\u6700\u5927\u5BFC\u5165\u6761\u6570
dict.unknown.error = \u4E0D\u5728{0}\u5B57\u5178\u4E2D: {1} dict.unknown.error = \u4E0D\u5728{0}\u5B57\u5178\u4E2D: {1}
express.not.exist = \u5FEB\u9012\u516C\u53F8\u4E0D\u5B58\u5728 express.not.exist = \u5FEB\u9012\u516C\u53F8\u4E0D\u5B58\u5728
currency.not.exist = \u5E01\u79CD\u4E0D\u5B58\u5728 currency.not.exist = \u5E01\u79CD\u4E0D\u5B58\u5728
date.format.error = \u65E5\u671F\u683C\u5F0F\u4E0D\u6B63\u786E, \u6B63\u786E\u683C\u5F0F\u53C2\u8003: 2024-01-01 12:11:11 date.format.error = \u65E5\u671F\u683C\u5F0F\u4E0D\u6B63\u786E, \u6B63\u786E\u683C\u5F0F\u53C2\u8003: 2024-01-01 12:11:11
\ No newline at end of file redeem.cancel.status.error = \u5151\u6362\u4E2D\u72B6\u6001\u7684\u8BB0\u5F55\u624D\u80FD\u64A4\u9500
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment