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;
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.MemberUserScoreOperateRespDTO;
import cn.iocoder.yudao.module.member.api.score.dto.ReleationScoreExpireInfoDTO;
import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum;
import java.util.List;
......@@ -12,4 +14,5 @@ public interface MemberUserScoreApi {
List<MemberUserScoreOperateRespDTO> batchOperateScore(MemberUserScoreBatchOperateReqDTO req);
List<ReleationScoreExpireInfoDTO> getScoreExpireInfo(String releation, ScoreSourceTypeEnum scoreSourceTypeEnum);
}
......@@ -14,4 +14,5 @@ public class MemberUserScoreDetailUpdateReqDto {
private ScoreSourceTypeEnum sourceType;
private Long scoreLogId;
private Integer expireDays;
private String releationId;
}
......@@ -28,6 +28,10 @@ public class MemberUserScoreOperateReqDTO {
* 人工操作时必传,其他来源不需要传
*/
private ScoreOperateTypeEnum operateType;
/**
* 若操作的积分后续存在回退逻辑时必传, 目前存在 礼品兑换后, 兑换取消,扣除积分的回退
*/
private String releationId;
/**
* 积分规则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 {
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;
import java.util.Set;
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;
......@@ -18,16 +20,23 @@ public enum ScoreSourceTypeEnum {
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.name = name;
this.operateType = operateType;
this.reverseSource = reverseSource;
}
public int getValue() {
return value;
}
public ScoreSourceTypeEnum getReverseSource() {
return reverseSource;
}
public String getName() {
return name;
}
......
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.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.ScoreSourceTypeEnum;
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.memberUserScoreDetailReleation.MemberUserScoreDetailReleationService;
import cn.iocoder.yudao.module.member.service.memberUserScoreLog.MemberUserScoreLogService;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogCreateReq;
import com.alibaba.excel.util.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
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.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Collections;
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 static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
......@@ -36,7 +49,12 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{
@Resource
private MemberUserScoreService memberUserScoreService;
@Resource
private RedisDistributedLock redisDistributedLock;
private MemberUserScoreDetailReleationService memberUserScoreDetailReleationService;
@Resource
private ApplicationContext applicationContext;
@Resource
private RedissonClient redissonClient;
@Override
@Transactional(rollbackFor = Exception.class)
public MemberUserScoreOperateRespDTO operateScore(MemberUserScoreOperateReqDTO req) {
......@@ -46,21 +64,43 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{
if (req.getScoreCount() <= 0) {
throw exception(SCORE_COUNT_ERROR);
}
String lockKey = "member:operate:socre:" + req.getMemberId();
boolean lock = redisDistributedLock.lock(lockKey, 5000, 3, 100);
if (!lock) {
throw exception(GET_LOCK_FAILED);
}
String lockKey = "member:operate:score:" + req.getMemberId();
RLock lock = redissonClient.getLock(lockKey);
try {
boolean lockSuccess = lock.tryLock(2, 2, TimeUnit.MINUTES);
if (!lockSuccess) {
throw exception(GET_LOCK_FAILED);
}
Long logId = saveScoreLog(req);
saveScoreDetail(req, logId);
List<ScoreDetailChangeDto> detailInfos = saveScoreDetail(req, logId);
saveScoreDetailReleation(req, detailInfos);
updateUserScore(req);
publishDetailExpireEvent(req,detailInfos);
} catch (InterruptedException e) {
throw exception(GET_LOCK_FAILED);
} finally {
redisDistributedLock.releaseLock(lockKey);
lock.unlock();
}
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
@Transactional
public List<MemberUserScoreOperateRespDTO> batchOperateScore(MemberUserScoreBatchOperateReqDTO req) {
......@@ -91,6 +131,31 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{
.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) {
memberUserScoreService.updateUserScore(MemberUserScoreUpdateReqDTO.builder()
.memberId(req.getMemberId())
......@@ -101,8 +166,8 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{
.build());
}
private void saveScoreDetail(MemberUserScoreOperateReqDTO req, Long scoreLogId) {
scoreDetailService.updateScoreDetail(MemberUserScoreDetailUpdateReqDto.builder()
private List<ScoreDetailChangeDto> saveScoreDetail(MemberUserScoreOperateReqDTO req, Long scoreLogId) {
return scoreDetailService.updateScoreDetail(MemberUserScoreDetailUpdateReqDto.builder()
.memberId(req.getMemberId())
.scoreCount(req.getScoreCount())
.operateType(req.getSourceType() == ScoreSourceTypeEnum.MANUAL_OPERATE ?
......@@ -110,6 +175,7 @@ public class MemberUserScoreApiImpl implements MemberUserScoreApi{
.sourceType(req.getSourceType())
.scoreLogId(scoreLogId)
.expireDays(req.getExpireDays())
.releationId(req.getReleationId())
.build());
}
......
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.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.module.member.api.score.MemberUserScoreApi;
......@@ -46,7 +47,7 @@ public class MemberUserScoreExpireTask implements JobHandler {
LambdaQueryWrapper<MemberUserScoreDetailDO> wrapper = Wrappers.lambdaQuery();
wrapper.in(MemberUserScoreDetailDO::getStatus, Lists.newArrayList(MemberScoreStatueEnum.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);
List<MemberUserScoreDetailDO> todoList = scoreDetailService.list(wrapper);
log.info("member user score expire task, to expire record count :{}", todoList.size());
......@@ -56,9 +57,9 @@ public class MemberUserScoreExpireTask implements JobHandler {
for (MemberUserScoreDetailDO memberUserScoreDetailDO : todoList) {
try {
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<>();
extParam.put("scoreLogIds", logIds);
extParam.put("scoreLogIds", logIds.toList(Long.class));
memberUserScoreApi.operateScore(MemberUserScoreOperateReqDTO.builder()
.memberId(memberUserScoreDetailDO.getMemberId())
.scoreCount(memberUserScoreDetailDO.getRemainCount())
......
package cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.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);
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
if (memberUserScoreDO == null) {
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) {
memberUserScoreDO.setHoldScore(memberUserScoreDO.getHoldScore() + query.getScoreCount());
this.saveOrUpdate(memberUserScoreDO);
......
......@@ -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.module.member.api.score.dto.MemberUserScoreDetailUpdateReqDto;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail.MemberUserScoreDetailDO;
import cn.iocoder.yudao.module.member.dto.ScoreDetailChangeDto;
import java.util.List;
/**
* 会员积分日志 Service 接口
......@@ -11,5 +14,5 @@ import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail.Membe
*/
public interface MemberUserScoreDetailService extends IService<MemberUserScoreDetailDO> {
void updateScoreDetail(MemberUserScoreDetailUpdateReqDto reqDto);
List<ScoreDetailChangeDto> updateScoreDetail(MemberUserScoreDetailUpdateReqDto reqDto);
}
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 {
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 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 {
if (!lock) {
throw exception(GET_LOCK_FAILED);
}
// 更新礼品
redeemReward(rewardDO, redeemRewardReqVO.getRewardCount());
// 添加兑换记录
Long redeemId = addRedeemRecord(redeemRewardReqVO);
Long redeemId = addRedeemRecord(redeemRewardReqVO, rewardDO);
// 更新会员积分
updateMemberScore(redeemRewardReqVO, rewardDO, redeemId);
// 更新礼品
redeemReward(rewardDO, redeemRewardReqVO.getRewardCount());
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
......@@ -111,14 +111,16 @@ public class RedeemRewardApiImpl implements RedeemRewardApi {
.memberId(redeemRewardReqVO.getMemberId())
.sourceType(ScoreSourceTypeEnum.EXCHANGE_REWARD)
.scoreCount(redeemRewardReqVO.getRewardCount() * rewardDO.getPointsRequire())
.releationId(String.valueOf(redeemId))
.extParam(extParam)
.build());
}
private Long addRedeemRecord(RedeemRewardReqVO redeemRewardReqVO) {
private Long addRedeemRecord(RedeemRewardReqVO redeemRewardReqVO, RewardDO rewardDO) {
RewardRedeemDO rewardRedeemDO = BeanUtil.copyProperties(redeemRewardReqVO, RewardRedeemDO.class);
rewardRedeemDO.setId(snowflakeGenerator.next());
rewardRedeemDO.setStatus(RewardRedeemStatusEnum.REDEEMING.getValue());
rewardRedeemDO.setScoreCount(redeemRewardReqVO.getRewardCount()* rewardDO.getPointsRequire());
rewardRedeemMapper.insert(rewardRedeemDO);
return rewardRedeemDO.getId();
}
......@@ -164,12 +166,12 @@ public class RedeemRewardApiImpl implements RedeemRewardApi {
throw exception(GET_LOCK_FAILED);
}
for (RedeemRewardReqVO redeemRewardReqVO : redeemRewardReqVOList) {
// 更新礼品
redeemReward(rewardDO, redeemRewardReqVO.getRewardCount());
// 添加兑换记录
Long redeemId = addRedeemRecord(redeemRewardReqVO);
Long redeemId = addRedeemRecord(redeemRewardReqVO, rewardDO);
// 更新会员积分
updateMemberScore(redeemRewardReqVO, rewardDO, redeemId);
// 更新礼品
redeemReward(rewardDO, redeemRewardReqVO.getRewardCount());
}
} catch (Exception e) {
log.error("redeem reward exception",e);
......
......@@ -81,6 +81,18 @@ public class RedeemRewardController {
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")
@ApiOperation("导入模板下载")
public void importTemplate(HttpServletResponse response) throws IOException {
......
......@@ -33,6 +33,7 @@ public class RewardRedeemDO extends BaseDO {
* 兑换状态
*/
private Integer status;
private Integer scoreCount;
/**
* 兑换数量
*/
......
......@@ -33,4 +33,8 @@ public interface RewardRedeemService extends IService<RewardRedeemDO> {
Integer exportCount(RewardRedeemPageReqVO request);
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;
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.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.mysql.redeem.RewardRedeemMapper;
import cn.iocoder.yudao.module.reward.dto.RewardRedeemVerifyDTO;
......@@ -29,6 +33,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.function.Function;
......@@ -53,13 +58,15 @@ public class RewardRedeemServiceImpl extends AbstractService<RewardRedeemMapper,
private final FileMakeApi fileMakeApi;
private final ExpressApi expressApi;
private final CurrencyApi currencyApi;
private final MemberUserScoreApi memberUserScoreApi;
public RewardRedeemServiceImpl(RewardRedeemMapper rewardRedeemMapper, FileMakeApi fileMakeApi,
ExpressApi expressApi, CurrencyApi currencyApi) {
ExpressApi expressApi, CurrencyApi currencyApi, MemberUserScoreApi memberUserScoreApi) {
this.rewardRedeemMapper = rewardRedeemMapper;
this.fileMakeApi = fileMakeApi;
this.expressApi = expressApi;
this.currencyApi = currencyApi;
this.memberUserScoreApi = memberUserScoreApi;
}
@Override
......@@ -237,6 +244,59 @@ public class RewardRedeemServiceImpl extends AbstractService<RewardRedeemMapper,
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,
Map<String, CurrencyRespDTO> titleEnCurrencyMap,
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 @@
er.title_fr as rewardTitleFr,
en.title_zh as nodeTitleZh,
en.title_en as nodeTitleEn,
er.points_require*err.reward_count as totalCount,
err.score_count as totalCount,
suc.username as creatorName,
suu.username as updaterName,
cc.title_zh as currencyTitleZh,
......
......@@ -1020,4 +1020,5 @@ redeem.import.max.count = allow maximum number of imports is {}
dict.unknown.error = Not in dict {0}: {1}
express.not.exist = express not exist
currency.not.exist = currency not exist
date.format.error = date format error, for example : 2024-01-01 12:11:11
\ No newline at end of file
date.format.error = date format error, for example : 2024-01-01 12:11:11
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
dict.unknown.error = \u4E0D\u5728{0}\u5B57\u5178\u4E2D: {1}
express.not.exist = \u5FEB\u9012\u516C\u53F8\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
\ No newline at end of file
date.format.error = \u65E5\u671F\u683C\u5F0F\u4E0D\u6B63\u786E, \u6B63\u786E\u683C\u5F0F\u53C2\u8003: 2024-01-01 12:11:11
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