Commit b94a25e0 authored by 332784038@qq.com's avatar 332784038@qq.com

Merge remote-tracking branch 'origin/dev' into dev

parents 0f8d1df6 1e09e12b
......@@ -149,4 +149,22 @@ public class DateUtils {
public static String formatDateTime(Date date) {
return null == date ? "" : DateUtil.format(date, FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND);
}
public static Date getNextNDayStart(Date date, int days) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.DAY_OF_MONTH, days);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime();
}
public static Date addDays(Date date ,int days) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.DAY_OF_MONTH, days);
return calendar.getTime();
}
}
......@@ -6,8 +6,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import sun.util.locale.BaseLocale;
import sun.util.locale.LocaleUtils;
import java.io.IOException;
import java.util.Locale;
......
package cn.iocoder.yudao.framework.snowflake.config;
import cn.hutool.core.lang.generator.SnowflakeGenerator;
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
import cn.iocoder.yudao.framework.redis.helper.RedisHelper;
import io.micrometer.core.instrument.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author zhaobiyan
*/
@Slf4j
@Configuration
@AutoConfigureAfter(YudaoRedisAutoConfiguration.class)
public class SnowFlakeConfiguration {
@Resource
private RedisHelper redisHelper;
@Bean
public SnowflakeGenerator snowflakeGenerator() throws UnknownHostException {
String hostAddress = InetAddress.getLocalHost().getHostAddress();
log.info("snow flask configuration, host address:{}", hostAddress);
String workId = redisHelper.get("snowflake:work:id:" + hostAddress);
if (StringUtils.isBlank(workId)) {
Long nextWorkId = redisHelper.incrBy("incr:work:id", 1);
redisHelper.set("snowflake:work:id:" + hostAddress, String.valueOf(nextWorkId));
workId = String.valueOf(nextWorkId);
}
return new SnowflakeGenerator(Long.parseLong(workId), 0);
}
}
......@@ -25,4 +25,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.framework.pay.config.YudaoPayAutoConfiguration,\
cn.iocoder.yudao.framework.customizer.CustomizationAutoConfiguration,\
cn.iocoder.yudao.framework.i18n.config.LocaleAutoConfiguration,\
cn.iocoder.yudao.framework.limiter.RedisLimiterConfiguration
cn.iocoder.yudao.framework.limiter.RedisLimiterConfiguration,\
cn.iocoder.yudao.framework.snowflake.config.SnowFlakeConfiguration
......@@ -111,4 +111,7 @@ public class SupplierExternalBackVO {
@ApiModelProperty(value = "国家ID")
private Long countryId;
@ApiModelProperty(value = "TIN NO./RC NO.")
private String tinNoRcNo;
}
......@@ -14,6 +14,7 @@ public interface ErrorCodeConstants {
ErrorCode CONFIG_KEY_DUPLICATE = new ErrorCode(1001000002, "参数配置 key 重复");
ErrorCode CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE = new ErrorCode(1001000003, "不能删除类型为系统内置的参数配置");
ErrorCode CONFIG_GET_VALUE_ERROR_IF_SENSITIVE = new ErrorCode(1001000004, "不允许获取敏感配置到前端");
ErrorCode GET_LOCK_FAILED = new ErrorCode(1001000005, "get.lock.failed");
// ========== 定时任务 1001001000 ==========
ErrorCode JOB_NOT_EXISTS = new ErrorCode(1001001000, "定时任务不存在");
......
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 java.util.List;
public interface MemberUserScoreApi {
MemberUserScoreOperateRespDTO operateScore(MemberUserScoreOperateReqDTO req);
List<MemberUserScoreOperateRespDTO> batchOperateScore(MemberUserScoreBatchOperateReqDTO req);
}
package cn.iocoder.yudao.module.member.api.score.dto;
import cn.iocoder.yudao.module.member.enums.ScoreOperateTypeEnum;
import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import java.util.List;
import java.util.Map;
@Data
@Builder
@ToString
public class MemberUserScoreBatchOperateReqDTO {
/**
* 会员id
*/
private List<Long> memberIds;
/**
* 积分数量
*/
private Integer scoreCount;
/**
* 积分来源
*/
private ScoreSourceTypeEnum sourceType;
/**
* 人工操作时必传,其他来源不需要传
*/
private ScoreOperateTypeEnum operateType;
/**
* 积分规则id
*/
private Long ruleId;
/**
* 积分过期时间
*/
private Integer expireDays;
/**
* 扩展参数
*/
private Map<String, Object> extParam;
}
package cn.iocoder.yudao.module.member.api.score.dto;
import cn.iocoder.yudao.module.member.enums.ScoreOperateTypeEnum;
import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class MemberUserScoreDetailUpdateReqDto {
private Long memberId;
private Integer scoreCount;
private ScoreOperateTypeEnum operateType;
private ScoreSourceTypeEnum sourceType;
private Long scoreLogId;
private Integer expireDays;
}
package cn.iocoder.yudao.module.member.api.score.dto;
import cn.iocoder.yudao.module.member.enums.ScoreOperateTypeEnum;
import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import java.util.Map;
@Data
@Builder
@ToString
public class MemberUserScoreOperateReqDTO {
/**
* 会员id
*/
private Long memberId;
/**
* 积分数量
*/
private Integer scoreCount;
/**
* 积分来源
*/
private ScoreSourceTypeEnum sourceType;
/**
* 人工操作时必传,其他来源不需要传
*/
private ScoreOperateTypeEnum operateType;
/**
* 积分规则id
*/
private Long ruleId;
/**
* 积分过期时间
*/
private Integer expireDays;
/**
* 扩展参数
*/
private Map<String, Object> extParam;
}
package cn.iocoder.yudao.module.member.api.score.dto;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class MemberUserScoreOperateRespDTO {
private boolean success;
private int code;
private MemberUserScoreOperateReqDTO reqDTO;
public static MemberUserScoreOperateRespDTO success(MemberUserScoreOperateReqDTO req){
return MemberUserScoreOperateRespDTO.builder().success(true).code(200).reqDTO(req).build();
}
}
package cn.iocoder.yudao.module.member.api.score.dto;
import cn.iocoder.yudao.module.member.enums.ScoreOperateTypeEnum;
import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class MemberUserScoreUpdateReqDTO {
private Long memberId;
private Integer scoreCount;
private ScoreOperateTypeEnum operateType;
private ScoreSourceTypeEnum sourceType;
}
......@@ -46,4 +46,6 @@ public class UserRespDTO {
*/
private String controlPassword;
private Integer holdScore;
}
......@@ -40,4 +40,9 @@ public interface ErrorCodeConstants {
ErrorCode EMAIL_CODE_ERROR = new ErrorCode(1004007004, "验证码错误");
ErrorCode MEMBER_ID_IS_NULL = new ErrorCode(1004008001, "member.id.is.null");
ErrorCode SCORE_COUNT_ERROR = new ErrorCode(1004008002, "score.count.error");
ErrorCode MEMBER_SCORE_NOT_ENOUGH = new ErrorCode(1004008002, "member.score.not.enough");
}
package cn.iocoder.yudao.module.member.enums;
public enum MemberScoreStatueEnum {
AVAILABLE(1, "可用"),
PART_AVAILABLE(2, "部分可用"),
NOT_AVAILABLE(3, "不可用")
;
private final int value;
private final String name;
MemberScoreStatueEnum(int value, String name) {
this.value = value;
this.name = name;
}
public int getValue() {
return value;
}
public String getName() {
return name;
}
}
package cn.iocoder.yudao.module.member.enums;
public enum MemberUserScoreTypeEnum {
HOLD_SCORE(1, "持有积分"),
EXPIRED_SCORE(2, "过期积分"),
INVALID_SCORE(3, "已失效积分")
;
private final int value;
private final String name;
MemberUserScoreTypeEnum(int value, String name) {
this.value = value;
this.name = name;
}
public int getValue() {
return value;
}
public String getName() {
return name;
}
}
package cn.iocoder.yudao.module.member.enums;
import java.util.stream.Stream;
public enum ScoreOperateTypeEnum {
ADD(1, "增加"),
REDUCE(2, "减少");
private final int value;
private final String name;
ScoreOperateTypeEnum(int value, String name) {
this.value = value;
this.name = name;
}
public int getValue() {
return value;
}
public String getName() {
return name;
}
public static ScoreOperateTypeEnum parseByValue(int value) {
return Stream.of(values()).filter(e -> e.getValue() == value).findAny().orElse(null);
}
}
package cn.iocoder.yudao.module.member.enums;
import com.google.common.collect.Sets;
import java.util.Set;
public enum ScoreSourceTypeEnum {
MANUAL_OPERATE(1, "人工操作", null),
EXCHANGE_REWARD(2, "兑换礼品", ScoreOperateTypeEnum.REDUCE),
SYSTEM_EXPIRED(3, "系统失效", ScoreOperateTypeEnum.REDUCE),
;
private final int value;
private final String name;
private final ScoreOperateTypeEnum operateType;
ScoreSourceTypeEnum(int value, String name, ScoreOperateTypeEnum operateType) {
this.value = value;
this.name = name;
this.operateType = operateType;
}
public int getValue() {
return value;
}
public String getName() {
return name;
}
public ScoreOperateTypeEnum getOperateType() {
return operateType;
}
public static Set<ScoreSourceTypeEnum> getInvalidScoreStatusSourceType() {
return Sets.newHashSet(MANUAL_OPERATE, SYSTEM_EXPIRED);
}
}
package cn.iocoder.yudao.module.member.api.score;
import cn.iocoder.yudao.framework.redis.helper.RedisDistributedLock;
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.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.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.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.GET_LOCK_FAILED;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
@Slf4j
@Service
public class MemberUserScoreApiImpl implements MemberUserScoreApi{
@Resource
private MemberUserScoreLogService logService;
@Resource
private MemberUserScoreDetailService scoreDetailService;
@Resource
private MemberUserScoreService memberUserScoreService;
@Resource
private RedisDistributedLock redisDistributedLock;
@Override
@Transactional(rollbackFor = Exception.class)
public MemberUserScoreOperateRespDTO operateScore(MemberUserScoreOperateReqDTO req) {
if (req.getMemberId() == null) {
throw exception(MEMBER_ID_IS_NULL);
}
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);
}
try {
Long logId = saveScoreLog(req);
saveScoreDetail(req, logId);
updateUserScore(req);
} finally {
redisDistributedLock.releaseLock(lockKey);
}
return MemberUserScoreOperateRespDTO.success(req);
}
@Override
@Transactional
public List<MemberUserScoreOperateRespDTO> batchOperateScore(MemberUserScoreBatchOperateReqDTO req) {
if (CollectionUtils.isEmpty(req.getMemberIds())) {
throw exception(MEMBER_ID_IS_NULL);
}
if (req.getScoreCount() <= 0) {
throw exception(SCORE_COUNT_ERROR);
}
// 校验用户当前积分是否满足扣减要求
if (req.getOperateType() == ScoreOperateTypeEnum.REDUCE) {
LambdaQueryWrapper<MemberUserScoreDO> wrapper = Wrappers.lambdaQuery();
wrapper.in(MemberUserScoreDO::getMemberId, req.getMemberIds());
List<MemberUserScoreDO> userScoreDOList = memberUserScoreService.list(wrapper);
List<MemberUserScoreDO> notEnoughScoreList = userScoreDOList.stream()
.filter(item -> item.getHoldScore() < req.getScoreCount()).collect(Collectors.toList());
if (!notEnoughScoreList.isEmpty()) {
throw exception(MEMBER_SCORE_NOT_ENOUGH);
}
}
return req.getMemberIds().stream().map(memberId -> operateScore(MemberUserScoreOperateReqDTO.builder()
.memberId(memberId)
.scoreCount(req.getScoreCount())
.operateType(req.getOperateType())
.sourceType(req.getSourceType())
.extParam(req.getExtParam())
.build()))
.collect(Collectors.toList());
}
private void updateUserScore(MemberUserScoreOperateReqDTO req) {
memberUserScoreService.updateUserScore(MemberUserScoreUpdateReqDTO.builder()
.memberId(req.getMemberId())
.scoreCount(req.getScoreCount())
.operateType(req.getSourceType() == ScoreSourceTypeEnum.MANUAL_OPERATE ?
req.getOperateType() : req.getSourceType().getOperateType())
.sourceType(req.getSourceType())
.build());
}
private void saveScoreDetail(MemberUserScoreOperateReqDTO req, Long scoreLogId) {
scoreDetailService.updateScoreDetail(MemberUserScoreDetailUpdateReqDto.builder()
.memberId(req.getMemberId())
.scoreCount(req.getScoreCount())
.operateType(req.getSourceType() == ScoreSourceTypeEnum.MANUAL_OPERATE ?
req.getOperateType() : req.getSourceType().getOperateType())
.sourceType(req.getSourceType())
.scoreLogId(scoreLogId)
.expireDays(req.getExpireDays())
.build());
}
private Long saveScoreLog(MemberUserScoreOperateReqDTO req) {
return logService.createScoreLog(MemberUserScoreLogCreateReq.builder()
.memberId(req.getMemberId())
.scoreCount(req.getScoreCount())
.operateType(req.getSourceType() == ScoreSourceTypeEnum.MANUAL_OPERATE ?
req.getOperateType().getValue() : req.getSourceType().getOperateType().getValue())
.sourceType(req.getSourceType().getValue())
.extParam(req.getExtParam())
.build());
}
}
......@@ -21,6 +21,6 @@ public class MemberCodeFlushTask implements JobHandler {
@Override
public String execute(String param) throws Exception {
userService.historyCodeFlush();
return "";
return "success";
}
}
package cn.iocoder.yudao.module.member.controller.admin.job;
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;
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.MemberScoreStatueEnum;
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 com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
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
@Slf4j
public class MemberUserScoreExpireTask implements JobHandler {
/**
* 会员积分的过期时间为每日的0点整
* 任务每天0点整运行,扫描当天0点过期的积分进行状态修改
* @param param 参数
* @return
* @throws Exception
*/
@Resource
private MemberUserScoreDetailService scoreDetailService;
@Resource
private MemberUserScoreApi memberUserScoreApi;
@Override
public String execute(String param) throws Exception {
log.info("member user score expire task running");
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.orderByAsc(MemberUserScoreDetailDO::getCreateTime);
List<MemberUserScoreDetailDO> todoList = scoreDetailService.list(wrapper);
log.info("member user score expire task, to expire record count :{}", todoList.size());
if (CollectionUtils.isEmpty(todoList)) {
return "success";
}
for (MemberUserScoreDetailDO memberUserScoreDetailDO : todoList) {
try {
log.info("score expire, score detail id :{}", memberUserScoreDetailDO.getId());
List logIds = (List) memberUserScoreDetailDO.getExtParamByKey(MemberUserScoreDetailDO.MemberUserScoreDetailExtKey.LOG_IDS);
Map<String, Object> extParam = new HashMap<>();
extParam.put("scoreLogIds", logIds);
memberUserScoreApi.operateScore(MemberUserScoreOperateReqDTO.builder()
.memberId(memberUserScoreDetailDO.getMemberId())
.scoreCount(memberUserScoreDetailDO.getRemainCount())
.sourceType(ScoreSourceTypeEnum.SYSTEM_EXPIRED)
.extParam(extParam)
.build());
} catch (Exception e) {
log.error("member user score expire exception, data:{}", memberUserScoreDetailDO, e);
}
}
log.info("member user score expire task finished");
return "success";
}
}
package cn.iocoder.yudao.module.member.controller.admin.memberUserScore;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.api.score.MemberUserScoreApiImpl;
import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreBatchOperateReqDTO;
import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreOperateRespDTO;
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.vo.memberUserScore.MemberUserScoreBackVO;
import cn.iocoder.yudao.module.member.vo.memberUserScore.MemberUserScoreOperateQueryVO;
import cn.iocoder.yudao.module.member.vo.memberUserScore.MemberUserScoreQueryVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Validated
@RestController
@Api(tags = "管理后台 - 会员积分")
@RequestMapping("/member/user-score")
public class MemberUserScoreController {
@Resource
private MemberUserScoreService userScoreService;
@Resource
private MemberUserScoreApiImpl scoreApi;
@PostMapping("/page")
@ApiOperation("获得会员积分分页")
@PreAuthorize("@ss.hasPermission('member:user-score:query')")
public CommonResult<PageResult<MemberUserScoreBackVO>> getUserScorePage(@Valid @RequestBody MemberUserScoreQueryVO query) {
PageResult<MemberUserScoreBackVO> pageResult = userScoreService.getUserScorePage(query);
return success(pageResult);
}
@PostMapping("/operate")
@ApiOperation("操作积分")
@PreAuthorize("@ss.hasPermission('member:user-score:operate')")
public CommonResult<Boolean> operate(@Valid @RequestBody MemberUserScoreOperateQueryVO query) {
Map<String,Object> extParam = new HashMap<>();
extParam.put("comment", query.getComment());
List<MemberUserScoreOperateRespDTO> memberUserScoreOperateRespDTOS = scoreApi.batchOperateScore(MemberUserScoreBatchOperateReqDTO.builder()
.memberIds(query.getMemberIds())
.scoreCount(query.getScoreCount())
.operateType(ScoreOperateTypeEnum.parseByValue(query.getOperateType()))
.sourceType(ScoreSourceTypeEnum.MANUAL_OPERATE)
.extParam(extParam)
.build());
return success(null);
}
}
package cn.iocoder.yudao.module.member.controller.admin.memberUserScoreLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.service.memberUserScoreLog.MemberUserScoreLogService;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogBackVO;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogQueryVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Validated
@RestController
@Api(tags = "管理后台 - 会员积分记录")
@RequestMapping("/member/user-score-log")
public class MemberUserScoreLogController {
@Resource
private MemberUserScoreLogService memberUserScoreLogService;
@PostMapping("/page")
@ApiOperation("获得积分记录分页")
@PreAuthorize("@ss.hasPermission('member:user-score-log:query')")
public CommonResult<PageResult<MemberUserScoreLogBackVO>> page(@Valid @RequestBody MemberUserScoreLogQueryVO query) {
PageResult<MemberUserScoreLogBackVO> pageResult = memberUserScoreLogService.getPage(query);
return success(pageResult);
}
}
package cn.iocoder.yudao.module.member.convert.memberUserScore;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 会员积分 Convert
* @author 系统管理员
*/
@Mapper
public interface MemberUserScoreConvert {
/*****转换MapStruct*****/
MemberUserScoreConvert INSTANCE = Mappers.getMapper(MemberUserScoreConvert.class);
}
package cn.iocoder.yudao.module.member.convert.memberUserScoreLog;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 会员积分 Convert
* @author 系统管理员
*/
@Mapper
public interface MemberUserScoreLogConvert {
/*****转换MapStruct*****/
MemberUserScoreLogConvert INSTANCE = Mappers.getMapper(MemberUserScoreLogConvert.class);
}
package cn.iocoder.yudao.module.member.dal.dataobject.memberUserScore;
public class MemberUserScoreBackDO {
}
package cn.iocoder.yudao.module.member.dal.dataobject.memberUserScore;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 会员积分 DO
*
* @author 系统管理员
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("member_user_score")
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MemberUserScoreDO extends BaseDO {
/**
* 主键
*/
@TableId
private Long id;
/**
* 会员id
*/
private Long memberId;
/**
* 持有积分
*/
private Integer holdScore;
/**
* 已使用积分
*/
private Integer usedScore;
/**
* 过期积分
*/
private Integer expiredScore;
}
package cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.vavr.collection.List;
import lombok.*;
import org.apache.commons.lang.StringUtils;
import java.util.Date;
import java.util.Map;
/**
* 会员积分 DO
*
* @author 系统管理员
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("member_user_score_detail")
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MemberUserScoreDetailDO extends BaseDO {
@TableId
private Long id;
private Long memberId;
private Integer scoreCount;
private Integer status;
private Integer remainCount;
private Date expireTime;
private String extParam = "{}";
@Getter
public enum MemberUserScoreDetailExtKey {
LOG_IDS("scoreLogIds");
private final String key;
MemberUserScoreDetailExtKey(String key) {
this.key = key;
}
}
public Object getExtParamByKey(MemberUserScoreDetailExtKey key) {
JSONObject extParamJsonObject = JSONUtil.parseObj(extParam);
return extParamJsonObject.get(key.getKey(), List.class);
}
}
package cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreLog;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("member_user_score_log")
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MemberUserScoreLogDO extends BaseDO {
/**
* 主键
*/
@TableId
private Long id;
/**
* 会员id
*/
private Long memberId;
private Integer scoreCount;
private Integer operateType;
private Integer sourceType;
private Long ruleId;
private String extParam = "{}";
}
......@@ -236,4 +236,5 @@ public class MemberUserDO extends TenantBaseDO {
private String code;
private Integer holdScore;
}
package cn.iocoder.yudao.module.member.dal.mysql.memberUserScore;
import cn.iocoder.yudao.framework.mybatis.core.mapper.AbstractMapper;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScore.MemberUserScoreDO;
import cn.iocoder.yudao.module.member.vo.memberUserScore.MemberUserScoreBackVO;
import cn.iocoder.yudao.module.member.vo.memberUserScore.MemberUserScoreQueryVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 会员积分 Mapper
* @author 系统管理员
*/
@Mapper
public interface MemberUserScoreMapper extends AbstractMapper<MemberUserScoreDO> {
List<MemberUserScoreBackVO> getUserScoreList(@Param("start") int start,@Param("size") int size,@Param("query") MemberUserScoreQueryVO query);
int countUserScore(@Param("query") MemberUserScoreQueryVO query);
MemberUserScoreDO getByMemberId(@Param("memberId") Long memberId);
}
package cn.iocoder.yudao.module.member.dal.mysql.memberUserScoreDetail;
import cn.iocoder.yudao.framework.mybatis.core.mapper.AbstractMapper;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreDetail.MemberUserScoreDetailDO;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreLog.MemberUserScoreLogDO;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogBackVO;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogQueryVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* 会员积分详情 Mapper
* @author 系统管理员
*/
@Mapper
public interface MemberUserScoreDetailMapper extends AbstractMapper<MemberUserScoreDetailDO> {
Long getScoreTotalByStatus(@Param("memberId") Long memberId, @Param("statusList") Collection<Integer> statusList);
}
package cn.iocoder.yudao.module.member.dal.mysql.memberUserScoreLog;
import cn.iocoder.yudao.framework.mybatis.core.mapper.AbstractMapper;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreLog.MemberUserScoreLogDO;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogBackVO;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogQueryVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 会员积分 Mapper
* @author 系统管理员
*/
@Mapper
public interface MemberUserScoreLogMapper extends AbstractMapper<MemberUserScoreLogDO> {
List<MemberUserScoreLogBackVO> getPageRecordList(@Param("start") int start, @Param("size") int size, @Param("query") MemberUserScoreLogQueryVO query);
int getPageCount(@Param("query") MemberUserScoreLogQueryVO query);
}
......@@ -160,11 +160,13 @@ public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
" uea.`name` as enterprise_name,",
" uea.`create_time` as enterprise_audit_create_time,",
" uea.`audit_time` as enterprise_audit_time," ,
" uea.`audit_remark` as enterprise_audit_remark",
" uea.`audit_remark` as enterprise_audit_remark,",
" ifnull( mus.`hold_score`, 0) as hold_score",
"from member_user u ",
"left join member_user_enterprise e on e.user_id = u.id and e.deleted = 0 ",
"left join member_user_card_auth uca on u.id = uca.user_id and uca.deleted = 0 " ,
"left join member_user_enterprise_auth uea on u.id = uea.user_id and uea.deleted = 0 ",
"left join member_user_score mus on u.id = mus.member_id and mus.deleted = 0 ",
"where ",
"u.deleted = 0 ",
"AND u.id = #{id} ",
......
package cn.iocoder.yudao.module.member.service.memberUserScore;
import java.util.*;
import cn.iocoder.yudao.framework.mybatis.core.service.IService;
import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreUpdateReqDTO;
import cn.iocoder.yudao.module.member.vo.memberUserScore.*;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScore.MemberUserScoreDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
/**
* 会员积分 Service 接口
*
* @author 系统管理员
*/
public interface MemberUserScoreService extends IService<MemberUserScoreDO> {
/**
* 删除会员积分
* @param id 编号
*/
void deleteUserScore(Long id);
/**
* 获得会员积分
* @param id 编号
* @return 会员积分
*/
MemberUserScoreDO getUserScore(Long id);
/**
* 获得会员积分列表
* @param ids 编号
* @return 会员积分列表
*/
List<MemberUserScoreDO> getUserScoreList(Collection<Long> ids);
/**
* 获得会员积分分页
* @param query 查询
* @return 会员积分分页
*/
PageResult<MemberUserScoreBackVO> getUserScorePage(MemberUserScoreQueryVO query);
/**
* 获得会员积分列表, 用于 Excel 导出
* @param query 查询
* @return 会员积分列表
*/
List<MemberUserScoreDO> getUserScoreList(MemberUserScoreQueryVO query);
void updateUserScore(MemberUserScoreUpdateReqDTO query);
}
package cn.iocoder.yudao.module.member.service.memberUserScore;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.module.member.api.score.dto.MemberUserScoreUpdateReqDTO;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScore.MemberUserScoreDO;
import cn.iocoder.yudao.module.member.dal.mysql.memberUserScore.MemberUserScoreMapper;
import cn.iocoder.yudao.module.member.enums.ScoreOperateTypeEnum;
import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum;
import cn.iocoder.yudao.module.member.vo.memberUserScore.MemberUserScoreBackVO;
import cn.iocoder.yudao.module.member.vo.memberUserScore.MemberUserScoreQueryVO;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.MEMBER_ID_IS_NULL;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.MEMBER_SCORE_NOT_ENOUGH;
/**
* 会员积分 Service 实现类
*
* @author 系统管理员
*/
@Service
@Validated
@Lazy
public class MemberUserScoreServiceImpl extends AbstractService<MemberUserScoreMapper, MemberUserScoreDO> implements MemberUserScoreService {
@Resource
private MemberUserScoreMapper userScoreMapper;
@Override
public void deleteUserScore(Long id) {
// 删除
userScoreMapper.deleteById(id);
}
@Override
public MemberUserScoreDO getUserScore(Long id) {
return userScoreMapper.selectById(id);
}
@Override
public List<MemberUserScoreDO> getUserScoreList(Collection<Long> ids) {
return userScoreMapper.selectBatchIds(ids);
}
@Override
public PageResult<MemberUserScoreBackVO> getUserScorePage(MemberUserScoreQueryVO query) {
int start = (query.getPageNo() - 1) * query.getPageSize();
int size = query.getPageSize();
List<MemberUserScoreBackVO> list = userScoreMapper.getUserScoreList(start, size, query);
int total = userScoreMapper.countUserScore(query);
return new PageResult<>(list, total, query.getPageSize(), query.getPageNo(), (total + query.getPageSize() - 1) / query.getPageSize());
}
@Override
public List<MemberUserScoreDO> getUserScoreList(MemberUserScoreQueryVO query) {
return userScoreMapper.selectList(query);
}
@Override
public void updateUserScore(MemberUserScoreUpdateReqDTO query) {
// 增加积分,只需要增加持有积分即可
// 减少积分,持有积分减少, 已兑换或者已失效积分增加
if (query.getMemberId() == null) {
throw exception(MEMBER_ID_IS_NULL);
}
MemberUserScoreDO memberUserScoreDO = userScoreMapper.getByMemberId(query.getMemberId());
if (memberUserScoreDO == null) {
memberUserScoreDO = initMemberUserScore(query.getMemberId());
}
if (query.getOperateType() == ScoreOperateTypeEnum.ADD) {
memberUserScoreDO.setHoldScore(memberUserScoreDO.getHoldScore() + query.getScoreCount());
this.saveOrUpdate(memberUserScoreDO);
}
if (query.getOperateType() == ScoreOperateTypeEnum.REDUCE) {
if (memberUserScoreDO.getHoldScore() < query.getScoreCount()) {
throw exception(MEMBER_SCORE_NOT_ENOUGH);
}
memberUserScoreDO.setHoldScore(memberUserScoreDO.getHoldScore() - query.getScoreCount());
if (ScoreSourceTypeEnum.getInvalidScoreStatusSourceType().contains(query.getSourceType())) {
memberUserScoreDO.setExpiredScore(memberUserScoreDO.getExpiredScore() + query.getScoreCount());
} else {
memberUserScoreDO.setUsedScore(memberUserScoreDO.getUsedScore() + query.getScoreCount());
}
this.saveOrUpdate(memberUserScoreDO);
}
}
private MemberUserScoreDO initMemberUserScore(Long memberId) {
MemberUserScoreDO memberUserScoreDO = new MemberUserScoreDO();
memberUserScoreDO.setMemberId(memberId);
memberUserScoreDO.setHoldScore(0);
memberUserScoreDO.setExpiredScore(0);
memberUserScoreDO.setUsedScore(0);
memberUserScoreDO.setCreateTime(new Date());
return memberUserScoreDO;
}
}
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;
/**
* 会员积分日志 Service 接口
*
* @author 系统管理员
*/
public interface MemberUserScoreDetailService extends IService<MemberUserScoreDetailDO> {
void updateScoreDetail(MemberUserScoreDetailUpdateReqDto reqDto);
}
package cn.iocoder.yudao.module.member.service.memberUserScoreDetail;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
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.dal.dataobject.memberUserScoreDetail.MemberUserScoreDetailDO;
import cn.iocoder.yudao.module.member.dal.mysql.memberUserScoreDetail.MemberUserScoreDetailMapper;
import cn.iocoder.yudao.module.member.enums.MemberScoreStatueEnum;
import cn.iocoder.yudao.module.member.enums.ScoreOperateTypeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.*;
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.getNextNDayStart;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.MEMBER_ID_IS_NULL;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.MEMBER_SCORE_NOT_ENOUGH;
/**
* 会员积分详情 Service 实现类
*
* @author 系统管理员
*/
@Service
@Validated
public class MemberUserScoreDetailServiceImpl extends AbstractService<MemberUserScoreDetailMapper, MemberUserScoreDetailDO> implements MemberUserScoreDetailService {
@Resource
private MemberUserScoreDetailMapper detailMapper;
@Override
public void updateScoreDetail(MemberUserScoreDetailUpdateReqDto reqDto) {
// 增加积分,只需要新增一条记录
// 减少积分,使已有记录失效
if (reqDto.getMemberId() == null) {
throw exception(MEMBER_ID_IS_NULL);
}
if (reqDto.getOperateType() == ScoreOperateTypeEnum.ADD) {
update4Add(reqDto);
}
if (reqDto.getOperateType() == ScoreOperateTypeEnum.REDUCE) {
update4Reduce(reqDto);
}
}
private void update4Reduce(MemberUserScoreDetailUpdateReqDto reqDto) {
Long scoreCount = detailMapper.getScoreTotalByStatus(reqDto.getMemberId(), Lists.newArrayList(MemberScoreStatueEnum.AVAILABLE.getValue(), MemberScoreStatueEnum.PART_AVAILABLE.getValue()));
if (scoreCount == null || scoreCount < reqDto.getScoreCount()) {
throw exception(MEMBER_SCORE_NOT_ENOUGH);
}
LambdaQueryWrapper<MemberUserScoreDetailDO> wrappers = Wrappers.lambdaQuery();
wrappers.eq(MemberUserScoreDetailDO::getMemberId, reqDto.getMemberId());
wrappers.in(MemberUserScoreDetailDO::getStatus, Lists.newArrayList(MemberScoreStatueEnum.AVAILABLE.getValue(), MemberScoreStatueEnum.PART_AVAILABLE.getValue()));
wrappers.orderByAsc(MemberUserScoreDetailDO::getExpireTime);
List<MemberUserScoreDetailDO> detailList = this.list(wrappers);
int needCount = reqDto.getScoreCount();
List<MemberUserScoreDetailDO> updateDetailList = new ArrayList<>();
for (MemberUserScoreDetailDO memberUserScoreDetailDO : detailList) {
if (needCount == 0) {
break;
}
if (memberUserScoreDetailDO.getRemainCount() <= needCount) {
needCount -= memberUserScoreDetailDO.getRemainCount();
memberUserScoreDetailDO.setRemainCount(0);
memberUserScoreDetailDO.setStatus(MemberScoreStatueEnum.NOT_AVAILABLE.getValue());
memberUserScoreDetailDO.setExtParam(addScoreLogId(memberUserScoreDetailDO.getExtParam(), reqDto.getScoreLogId()));
updateDetailList.add(memberUserScoreDetailDO);
} else {
memberUserScoreDetailDO.setRemainCount(memberUserScoreDetailDO.getRemainCount() - needCount);
needCount = 0;
memberUserScoreDetailDO.setStatus(MemberScoreStatueEnum.PART_AVAILABLE.getValue());
memberUserScoreDetailDO.setExtParam(addScoreLogId(memberUserScoreDetailDO.getExtParam(), reqDto.getScoreLogId()));
updateDetailList.add(memberUserScoreDetailDO);
}
}
this.updateBatch(updateDetailList);
}
private void update4Add(MemberUserScoreDetailUpdateReqDto reqDto) {
MemberUserScoreDetailDO memberUserScoreDetailDO = new MemberUserScoreDetailDO();
memberUserScoreDetailDO.setMemberId(reqDto.getMemberId());
memberUserScoreDetailDO.setScoreCount(reqDto.getScoreCount());
memberUserScoreDetailDO.setStatus(MemberScoreStatueEnum.AVAILABLE.getValue());
memberUserScoreDetailDO.setRemainCount(reqDto.getScoreCount());
memberUserScoreDetailDO.setCreateTime(new Date());
if (reqDto.getExpireDays() != null) {
memberUserScoreDetailDO.setExpireTime(addDays(getNextNDayStart(memberUserScoreDetailDO.getCreateTime(), 1), reqDto.getExpireDays()));
}
Map<String, Object> extParma = new HashMap<>();
extParma.put(MemberUserScoreDetailDO.MemberUserScoreDetailExtKey.LOG_IDS.getKey(), Lists.newArrayList(reqDto.getScoreLogId()));
memberUserScoreDetailDO.setExtParam(JSONUtil.toJsonStr(extParma));
this.saveOrUpdate(memberUserScoreDetailDO);
}
private String addScoreLogId(String extParam, Long scoreLogId) {
JSONObject extParamJson = JSONUtil.parseObj(extParam);
if (extParam == null ){
extParamJson = new JSONObject();
}
List<Long> logIds = extParamJson.get(MemberUserScoreDetailDO.MemberUserScoreDetailExtKey.LOG_IDS.getKey(), List.class);
if (logIds == null) {
logIds = new ArrayList<>();
}
logIds.add(scoreLogId);
extParamJson.set(MemberUserScoreDetailDO.MemberUserScoreDetailExtKey.LOG_IDS.getKey(), logIds);
return extParamJson.toStringPretty();
}
}
package cn.iocoder.yudao.module.member.service.memberUserScoreLog;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.service.IService;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreLog.MemberUserScoreLogDO;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogBackVO;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogCreateReq;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogQueryVO;
/**
* 会员积分日志 Service 接口
*
* @author 系统管理员
*/
public interface MemberUserScoreLogService extends IService<MemberUserScoreLogDO> {
PageResult<MemberUserScoreLogBackVO> getPage(MemberUserScoreLogQueryVO query);
Long createScoreLog(MemberUserScoreLogCreateReq createReq);
}
package cn.iocoder.yudao.module.member.service.memberUserScoreLog;
import cn.hutool.core.lang.generator.SnowflakeGenerator;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.module.member.dal.dataobject.memberUserScoreLog.MemberUserScoreLogDO;
import cn.iocoder.yudao.module.member.dal.mysql.memberUserScoreLog.MemberUserScoreLogMapper;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogBackVO;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogCreateReq;
import cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogQueryVO;
import cn.iocoder.yudao.module.system.service.dict.DictTypeService;
import com.alibaba.excel.util.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.MEMBER_ID_IS_NULL;
/**
* 会员积分日志 Service 实现类
*
* @author 系统管理员
*/
@Service
@Validated
public class MemberUserScoreLogServiceImpl extends AbstractService<MemberUserScoreLogMapper, MemberUserScoreLogDO> implements MemberUserScoreLogService {
@Resource
private MemberUserScoreLogMapper userScoreLogMapper;
@Resource
private DictTypeService dictTypeService;
@Resource
private SnowflakeGenerator snowflakeGenerator;
@Override
public PageResult<MemberUserScoreLogBackVO> getPage(MemberUserScoreLogQueryVO query) {
int start = (query.getPageNo() - 1) * query.getPageSize();
int size = query.getPageSize();
List<MemberUserScoreLogBackVO> list = userScoreLogMapper.getPageRecordList(start, size, query);
int total = userScoreLogMapper.getPageCount(query);
return new PageResult<>(list, total, query.getPageSize(), query.getPageNo(), (total + query.getPageSize() - 1) / query.getPageSize());
}
@Override
public Long createScoreLog(MemberUserScoreLogCreateReq createReq) {
if (createReq.getMemberId() == null) {
throw exception(MEMBER_ID_IS_NULL);
}
MemberUserScoreLogDO userScoreLogDO = new MemberUserScoreLogDO();
userScoreLogDO.setId(snowflakeGenerator.next());
userScoreLogDO.setMemberId(createReq.getMemberId());
userScoreLogDO.setScoreCount(createReq.getScoreCount());
userScoreLogDO.setOperateType(createReq.getOperateType());
userScoreLogDO.setSourceType(createReq.getSourceType());
userScoreLogDO.setRuleId(createReq.getRuleId());
if (createReq.getExtParam() != null) {
userScoreLogDO.setExtParam(JSONUtil.toJsonStr(createReq.getExtParam()));
}
this.saveOrUpdate(userScoreLogDO);
return userScoreLogDO.getId();
}
}
......@@ -12,6 +12,7 @@ import cn.iocoder.yudao.framework.i18n.core.I18nMessage;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.redis.helper.RedisDistributedLock;
import cn.iocoder.yudao.framework.redis.helper.RedisHelper;
import cn.iocoder.yudao.module.ecw.api.customer.CustomerApi;
import cn.iocoder.yudao.module.ecw.api.internalMessage.ClientInternalMessageApi;
......@@ -63,6 +64,7 @@ import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.GET_LOCK_FAILED;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
/**
......@@ -111,6 +113,9 @@ public class MemberUserServiceImpl implements MemberUserService {
@Resource
private RedisHelper redisHelper;
@Resource
private RedisDistributedLock redisDistributedLock;
@Override
public MemberUserDO getUserByMobile(String mobile) {
......@@ -580,7 +585,11 @@ public class MemberUserServiceImpl implements MemberUserService {
if (nextMemberCodeNumber != null) {
return MemberUserCodeUtils.generateMemberCode(nextMemberCodeNumber);
}
synchronized(this) {
boolean lock = redisDistributedLock.lock("next:member:code:lock", 2000, 3, 500);
if (!lock) {
throw exception(GET_LOCK_FAILED);
}
try {
nextMemberCodeNumber = redisHelper.execute4Long(redisScript, Collections.singletonList(key));
if (nextMemberCodeNumber != null) {
return MemberUserCodeUtils.generateMemberCode(nextMemberCodeNumber);
......@@ -589,6 +598,8 @@ public class MemberUserServiceImpl implements MemberUserService {
Long memberCodeMaxNumber = MemberUserCodeUtils.getMemberCodeNumber(currentMaxMemberCode);
redisHelper.set(key, String.valueOf(memberCodeMaxNumber), 5, TimeUnit.MINUTES);
return MemberUserCodeUtils.generateMemberCode(redisHelper.incrBy(key, 1));
} finally {
redisDistributedLock.releaseLock("next:member:code:lock");
}
}
......
package cn.iocoder.yudao.module.member.vo.memberUserScore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
/**
* @author zhaobiyan
*/
@Data
@ApiModel("管理后台 - 会员积分查询 VO")
public class MemberUserScoreBackVO {
@ApiModelProperty(value = "会员id")
private String id;
@ApiModelProperty(value = "会员昵称")
private String nickname;
@ApiModelProperty(value = "联系方式")
private String areaCode;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "国家(中文)")
private String countryNameZh;
@ApiModelProperty(value = "国家(英文)")
private String countryNameEn;
@ApiModelProperty(value = "当前积分")
private Integer holdScore;
@ApiModelProperty(value = "已兑换积分")
private Integer usedScore;
@ApiModelProperty(value = "已失效积分")
private Integer expiredScore;
@ApiModelProperty(value = "会员注册时间")
private Date createTime;
}
package cn.iocoder.yudao.module.member.vo.memberUserScore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* @author zhaobiyan
*/
@Data
@ApiModel("管理后台 - 操作积分 VO")
public class MemberUserScoreOperateQueryVO {
@ApiModelProperty(value = "会员id列表")
private List<Long> memberIds;
@ApiModelProperty(value = "操作类型 1:新增 2:删除")
private Integer operateType;
@ApiModelProperty(value = "积分数量")
private Integer scoreCount;
@ApiModelProperty(value = "备注")
private String comment;
}
package cn.iocoder.yudao.module.member.vo.memberUserScore;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
/**
* @author zhaobiyan
*/
@Data
@ApiModel("管理后台 - 会员积分查询 VO")
public class MemberUserScoreQueryVO extends PageParam {
@ApiModelProperty(value = "关键词")
private String key;
@ApiModelProperty(value = "国家")
private Integer country;
@ApiModelProperty(value = "当前积分")
private Integer holdScore;
@ApiModelProperty(value = "当前积分操作")
private Integer holdScoreOperate;
@ApiModelProperty(value = "已使用积分")
private Integer usedScore;
@ApiModelProperty(value = "已使用积分操作")
private Integer usedScoreOperate;
@ApiModelProperty(value = "会员创建时间开始")
private Date startTime;
@ApiModelProperty(value = "会员创建时间结束")
private Date endTime;
}
package cn.iocoder.yudao.module.member.vo.memberUserScoreLog;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.apache.commons.lang.StringUtils;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
/**
* @author zhaobiyan
*/
@Data
@ApiModel("管理后台 - 积分记录查询 VO")
public class MemberUserScoreLogBackVO {
@ApiModelProperty(value = "积分记录id")
private Long id;
@ApiModelProperty(value = "会员id")
private Long memberId;
@ApiModelProperty(value = "会员昵称")
private String memberName;
@ApiModelProperty(value = "积分数量")
private Integer scoreCount;
@ApiModelProperty(value = "操作类别")
private Integer operateType;
@ApiModelProperty(value = "积分来源")
private Integer sourceType;
@ApiModelProperty(value = "规则标题(中文)")
private String ruleTitleZh;
@ApiModelProperty(value = "规则标题(英文)")
private String ruleTitleEn;
@ApiModelProperty(value = "规则说明(中文)")
private String ruleDescZh;
@ApiModelProperty(value = "规则说明(英文)")
private String ruleDescEn;
@ApiModelProperty(value = "规则封面(中文)")
private String coverImageZh;
@ApiModelProperty(value = "规则封面(英文)")
private String coverImageEn;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "扩展参数")
private String extParam;
public JSONObject getExtParamJson() {
if (StringUtils.isBlank(extParam)) {
return new JSONObject();
}
return JSONUtil.parseObj(extParam);
}
}
package cn.iocoder.yudao.module.member.vo.memberUserScoreLog;
import lombok.Builder;
import lombok.Data;
import java.util.Map;
/**
* @author zhaobiyan
*/
@Data
@Builder
public class MemberUserScoreLogCreateReq {
private Long memberId;
private Integer scoreCount;
private Integer operateType;
private Integer sourceType;
private Long ruleId;
private Map<String, Object> extParam;
}
package cn.iocoder.yudao.module.member.vo.memberUserScoreLog;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
/**
* @author zhaobiyan
*/
@Data
@ApiModel("管理后台 - 积分记录查询 VO")
public class MemberUserScoreLogQueryVO extends PageParam {
@ApiModelProperty(value = "关键词")
private String key;
@ApiModelProperty(value = "积分来源")
private Integer sourceType;
@ApiModelProperty(value = "规则标题")
private String ruleTitle;
@ApiModelProperty(value = "规则说明")
private String ruleDesc;
@ApiModelProperty(value = "积分数值")
private Integer scoreCount;
@ApiModelProperty(value = "积分数值操作")
private Integer scoreCountOperate;
@ApiModelProperty(value = "开始时间")
private Date startTime;
@ApiModelProperty(value = "结束时间")
private Date endTime;
}
<?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.memberUserScoreDetail.MemberUserScoreDetailMapper">
<select id="getScoreTotalByStatus" resultType="java.lang.Long">
select sum(remain_count)
from member_user_score_detail
where member_id = #{memberId}
<if test="statusList != null and statusList.size >0">
and status in
<foreach collection="statusList" item="status" open="(" close=")" separator=",">
#{status}
</foreach>
</if>
</select>
</mapper>
<?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.memberUserScoreLog.MemberUserScoreLogMapper">
<select id="getPageRecordList"
resultType="cn.iocoder.yudao.module.member.vo.memberUserScoreLog.MemberUserScoreLogBackVO">
select
musl.id as id,
musl.member_id as memberId,
mu.nickname as memberName,
musl.score_count as scoreCount,
musl.operate_type as operateType,
musl.source_type as sourceType,
sr.title_zh as ruleTitleZh,
sr.title_en as ruleTitleEn,
sr.desc_zh as ruleDescZh,
sr.desc_en as ruleDescEn,
sr.cover_image_zh as coverImageZh,
sr.cover_image_en as coverImageEn,
musl.create_time as createTime,
musl.ext_param as extParam
from member_user_score_log musl
left join member_user mu on mu.id = musl.member_id
left join score_rule sr on sr.id = musl.rule_id
where 1=1
<include refid="pageCondition"/>
order by musl.create_time desc
limit #{start}, #{size}
</select>
<select id="getPageCount" resultType="java.lang.Integer">
select count(*)
from member_user_score_log musl
left join member_user mu on mu.id = musl.member_id
left join score_rule sr on sr.id = musl.rule_id
where 1=1
<include refid="pageCondition"/>
</select>
<sql id="pageCondition">
<if test="query.key !=null and query.key != ''">
and (mu.nickname like '%${query.key}%' or mu.mobile like '%${query.key}%')
</if>
<if test="query.sourceType !=null">
and musl.source_type = #{query.sourceType}
</if>
<if test="query.ruleTitle !=null and query.ruleTitle !=''">
and (sr.title_zh like '%${query.ruleTitle}%' or sr.title_en like '%${query.ruleTitle}%')
</if>
<if test="query.ruleDesc !=null and query.ruleDesc !=''">
and (sr.desc_zh like '%${query.ruleDesc}%' or sr.desc_en like '%${query.ruleDesc}%')
</if>
<if test="query.startTime != null ">
and musl.create_time &gt;= #{query.startTime}
</if>
<if test="query.endTime != null ">
and musl.create_time &lt;= #{query.endTime}
</if>
<if test="query.scoreCount != null and query.scoreCountOperate != null">
<!--1:大于 2:等于 3:小于-->
<if test="query.scoreCountOperate == 1">
and musl.score_count &gt; #{query.scoreCount}
</if>
<if test="query.scoreCountOperate == 2">
and musl.score_count = #{query.scoreCount}
</if>
<if test="query.scoreCountOperate == 3">
and musl.score_count &lt; #{query.scoreCount}
</if>
</if>
</sql>
</mapper>
<?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.memberUserScore.MemberUserScoreMapper">
<select id="getUserScoreList"
resultType="cn.iocoder.yudao.module.member.vo.memberUserScore.MemberUserScoreBackVO">
select
mu.id,
mu.nickname,
mu.area_code as areaCode,
mu.mobile,
re.title_zh as countryNameZh,
re.title_en as countryNameEn,
ifnull(mus.hold_score, 0) as holdScore,
ifnull(mus.used_score, 0) as usedScore,
ifnull(mus.expired_score, 0) as expiredScore,
mu.create_time as createTime
from member_user mu
left join member_user_score mus on mus.member_id = mu.id
left join ecw_region re on re.id = mu.country
where 1 = 1
<include refid="scoreCondition"/>
order by mu.id
limit #{start}, #{size}
</select>
<select id="countUserScore" resultType="java.lang.Integer">
select
count(*)
from member_user mu
left join member_user_score mus on mus.member_id = mu.id
left join ecw_region re on re.id = mu.country
where 1 = 1
<include refid="scoreCondition"/>
</select>
<select id="getByMemberId"
resultType="cn.iocoder.yudao.module.member.dal.dataobject.memberUserScore.MemberUserScoreDO">
select * from member_user_score where member_id = #{memberId} and deleted = 0
</select>
<sql id="scoreCondition">
<if test="query.key != null and query.key != ''">
and (mu.nickname like '%${query.key}%' or mu.mobile like '%${query.key}%')
</if>
<if test="query.country != null">
and mu.country = #{query.country}
</if>
<if test="query.startTime != null ">
and mu.create_time &gt;= #{query.startTime}
</if>
<if test="query.endTime != null ">
and mu.create_time &lt;= #{query.endTime}
</if>
<if test="query.usedScore != null and query.usedScoreOperate != null">
<!--1:大于 2:等于 3:小于-->
<if test="query.usedScoreOperate == 1">
and mus.used_score &gt; #{query.usedScore}
</if>
<if test="query.usedScoreOperate == 2">
and mus.used_score = #{query.usedScore}
</if>
<if test="query.usedScoreOperate == 3">
and mus.used_score &lt; #{query.usedScore}
</if>
</if>
<if test="query.holdScore != null and query.holdScoreOperate != null">
<!--1:大于 2:等于 3:小于-->
<if test="query.holdScoreOperate == 1">
and mus.hold_score &gt; #{query.holdScore}
</if>
<if test="query.holdScoreOperate == 2">
and mus.hold_score = #{query.holdScore}
</if>
<if test="query.holdScoreOperate == 3">
and mus.hold_score &lt; #{query.holdScore}
</if>
</if>
</sql>
</mapper>
......@@ -20,7 +20,6 @@ import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.sun.org.apache.xpath.internal.operations.Or;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
......
package cn.iocoder.yudao.module.reward.api.reward;
import cn.iocoder.yudao.module.reward.api.reward.dto.RedeemRewardReqVO;
import cn.iocoder.yudao.module.reward.api.reward.dto.RedeemRewardRespDTO;
import java.util.List;
/**
* 礼品兑换 api 接口
*/
public interface RedeemRewardApi {
RedeemRewardRespDTO redeemReward(RedeemRewardReqVO redeemRewardReqVO);
List<RedeemRewardRespDTO> redeemRewards(List<RedeemRewardReqVO> redeemRewardReqVOList);
}
package cn.iocoder.yudao.module.reward.api.reward.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Data
@ApiModel("管理后台 - 礼品兑换 Request VO")
public class RedeemRewardReqVO {
@ApiModelProperty(value = "会员id")
private Long memberId;
@ApiModelProperty(value = "礼品id")
private Long rewardId;
@ApiModelProperty(value = "兑换数量")
private Integer rewardCount;
@ApiModelProperty(value = "兑换方式,同领取方式(1上门领取,2包邮到家,3邮寄到付)")
private Integer redeemType;
@ApiModelProperty(value = "兑换入口(后台,app,web)")
private Integer entrance;
@ApiModelProperty(value = "费用数字(两位小数)")
private BigDecimal expenses;
@ApiModelProperty(value = "费用币种(字典配置)")
private Integer currency;
@ApiModelProperty(value = "收件人姓名")
private String recipientName;
@ApiModelProperty(value = "收件人电话")
private String recipientPhoneNum;
@ApiModelProperty(value = "收件人地址")
private String recipientAddress;
@ApiModelProperty(value = "兑换人")
private String redeemer;
@ApiModelProperty(value = "兑换时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date redemptionTime;
@ApiModelProperty(value = "快递公司")
private String courierCompany;
@ApiModelProperty(value = "快递单号")
private String expressNo;
@ApiModelProperty(value = "快递日期")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date expressDate;
@ApiModelProperty(value = "快递寄出人")
private String expressSender;
@ApiModelProperty(value = "上传附件")
private String annex;
@ApiModelProperty(value = "备注")
private String remark;
}
package cn.iocoder.yudao.module.reward.api.reward.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("管理后台 - 礼品兑换 Response VO")
public class RedeemRewardRespDTO {
@ApiModelProperty(value = "兑换操作结果")
private Boolean exchangeResult;
@ApiModelProperty(value = "兑换操作详情")
private String msg;
}
......@@ -6,6 +6,22 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
// TODO 待办:请将下面的错误码复制到 yudao-module-reward-api 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!!
// ========== 礼品 TODO 补充编号 ==========
public interface ErrorCodeConstants {
ErrorCode _NOT_EXISTS = new ErrorCode(1009011000, "礼品不存在");
ErrorCode REWARD_NOT_EXISTS = new ErrorCode(1010011001, "reward.do.not.exist");
ErrorCode REWARD_ENDTIME_ERROR = new ErrorCode(1001011002, "reward.end.time.less.than.now");
ErrorCode REWARD_STATUS_NOT_ALLOW_DELETE = new ErrorCode(1001011003, "reward.status.not.allow.delete");
ErrorCode REWARD_STATUS_NOT_ALLOW_ClOSE = new ErrorCode(1001011004, "reward.status.not.allow.close");
ErrorCode REWARD_STATUS_CHANGE_ERROR = new ErrorCode(1001011005, "reward.status.change.error");
ErrorCode REWARD_STATUS_NOT_ALLOW_DELAY = new ErrorCode(1001011006, "reward.status.not.allow.delay");
ErrorCode REWARD_STATUS_NOT_ALLOW_CREATE = new ErrorCode(1001011007, "reward.status.not.allow.create");
ErrorCode REWARD_START_OR_END_TIME_NOT_ALLOW_CREATE = new ErrorCode(1001011008, "reward.time.not.allow");
ErrorCode REWARD_PICK_METHOD_NOT_ALLOW_CREATE = new ErrorCode(1001011009, "领取方式不合法");
ErrorCode REWARD_STATUS_NOT_ALLOW_UPDATE = new ErrorCode(1001011010, "礼物状态不允许编辑");
ErrorCode REWARD_STATUS_NOT_ALLOW_ENABLE = new ErrorCode(1001011011, "礼物不能启用");
ErrorCode REWARD_NOT_ENABLE = new ErrorCode(1001011012, "礼物未启用");
ErrorCode REWARD_SCORE_NOT_ENOUGH = new ErrorCode(1001011013, "会员积分不够");
ErrorCode REWARD_COUNT_NOT_ENOUGH = new ErrorCode(1001011014, "礼品数量不够");
ErrorCode REWARD_REDEEM_FAIL = new ErrorCode(1001011015, "批量兑换失败");
ErrorCode REWARD_REDEEM_COUNT_NOT_ALLOW = new ErrorCode(1001011016, "批量兑换每次最多十条");
ErrorCode REWARD_REDEEM_ALLOW_COUNT_ERROR = new ErrorCode(1001011017, "超出允许兑换次数");
}
package cn.iocoder.yudao.module.reward.enums;
public enum RewardRedeemStatusEnum {
REDEEMING(1, "兑换中"),
REDEEMED(2, "已兑换"),
CANCELED(3, "已取消")
;
private final int value;
private final String name;
RewardRedeemStatusEnum(int value, String name) {
this.value = value;
this.name = name;
}
public int getValue() {
return value;
}
public String getName() {
return name;
}
}
package cn.iocoder.yudao.module.reward.vo.reward;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
@ApiModel("管理后台 - 礼品更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class RewardUpdateReqVO extends RewardBaseVO {
@ApiModelProperty(value = "", required = true)
@NotNull(message = "不能为空")
private Long id;
}
import cn.hutool.core.lang.UUID;
import org.apache.commons.lang3.RandomStringUtils;
public class GenRewardCodeTest {
public static void main(String[] args) {
String format = String.format("%012d", 2);
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
System.out.println(format);
System.out.println(uuid);
String string = RandomStringUtils.randomAlphanumeric(12).toUpperCase();
System.out.println(string);
}
}
......@@ -26,7 +26,16 @@
<artifactId>yudao-module-reward-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-infra-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-member-api</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package cn.iocoder.yudao.module.reward.api.reward;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.generator.SnowflakeGenerator;
import cn.iocoder.yudao.framework.redis.helper.RedisDistributedLock;
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.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
import cn.iocoder.yudao.module.member.enums.ScoreSourceTypeEnum;
import cn.iocoder.yudao.module.reward.api.reward.dto.RedeemRewardReqVO;
import cn.iocoder.yudao.module.reward.api.reward.dto.RedeemRewardRespDTO;
import cn.iocoder.yudao.module.reward.dal.dataobject.redeem.RewardRedeemDO;
import cn.iocoder.yudao.module.reward.dal.dataobject.reward.RewardDO;
import cn.iocoder.yudao.module.reward.dal.mysql.redeem.RewardRedeemMapper;
import cn.iocoder.yudao.module.reward.dal.mysql.reward.RewardMapper;
import cn.iocoder.yudao.module.reward.enums.RewardRedeemStatusEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.GET_LOCK_FAILED;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.USER_NOT_EXISTS;
import static cn.iocoder.yudao.module.reward.enums.ErrorCodeConstants.*;
@Service
@Validated
public class RedeemRewardApiImpl implements RedeemRewardApi {
@Resource
private RewardMapper rewardMapper;
@Resource
private RewardRedeemMapper rewardRedeemMapper;
@Resource
private MemberUserApi memberUserApi;
@Resource
private RedisDistributedLock redisDistributedLock;
@Resource
private MemberUserScoreApi memberUserScoreApi;
@Resource
private SnowflakeGenerator snowflakeGenerator;
@Override
@Transactional(rollbackFor = Exception.class)
public RedeemRewardRespDTO redeemReward(RedeemRewardReqVO redeemRewardReqVO) {
// 查询礼品
RewardDO rewardDO = rewardMapper.selectById(redeemRewardReqVO.getRewardId());
if (rewardDO == null) {
throw exception(REWARD_NOT_EXISTS);
}
// 礼品未开启
if (rewardDO.getStatus() != 1) {
throw exception(REWARD_NOT_ENABLE);
}
// 礼品数量不够
if (rewardDO.getQuantityRemain() < redeemRewardReqVO.getRewardCount()) {
throw exception(REWARD_COUNT_NOT_ENOUGH);
}
// 兑换方式不匹配
if (!Objects.equals(rewardDO.getPickMethod(), redeemRewardReqVO.getRedeemType())) {
throw exception(REWARD_PICK_METHOD_NOT_ALLOW_CREATE);
}
verifyMemberUser(redeemRewardReqVO, rewardDO);
boolean lock = false;
try {
lock = redisDistributedLock.lock("reward:redeem:lock:" + redeemRewardReqVO.getRewardId());
if (!lock) {
throw exception(GET_LOCK_FAILED);
}
// 更新礼品
redeemReward(rewardDO, redeemRewardReqVO.getRewardCount());
// 添加兑换记录
Long redeemId = addRedeemRecord(redeemRewardReqVO);
// 更新会员积分
updateMemberScore(redeemRewardReqVO, rewardDO, redeemId);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
redisDistributedLock.releaseLock("reward:redeem:lock:" + redeemRewardReqVO.getRewardId());
}
return null;
}
private void redeemReward(RewardDO rewardDO, Integer rewardCount) {
// 更新时校验礼品数量是否足够
RewardDO rewardDO1 = rewardMapper.selectById(rewardDO.getId());
if (rewardDO1.getQuantityRemain() < rewardCount) {
throw exception(REWARD_COUNT_NOT_ENOUGH);
}
rewardDO.setExchangeCount(rewardDO.getExchangeCount() + 1);
rewardDO.setQuantityRemain(rewardDO.getQuantityRemain() - rewardCount);
rewardMapper.updateById(rewardDO);
}
private void updateMemberScore(RedeemRewardReqVO redeemRewardReqVO, RewardDO rewardDO, Long redeemId) {
Map<String, Object> extParam = new HashMap<>();
extParam.put("redeemId", redeemId);
memberUserScoreApi.operateScore(MemberUserScoreOperateReqDTO.builder()
.memberId(redeemRewardReqVO.getMemberId())
.sourceType(ScoreSourceTypeEnum.EXCHANGE_REWARD)
.scoreCount(redeemRewardReqVO.getRewardCount() * rewardDO.getPointsRequire())
.extParam(extParam)
.build());
}
private Long addRedeemRecord(RedeemRewardReqVO redeemRewardReqVO) {
RewardRedeemDO rewardRedeemDO = BeanUtil.copyProperties(redeemRewardReqVO, RewardRedeemDO.class);
rewardRedeemDO.setId(snowflakeGenerator.next());
rewardRedeemDO.setStatus(RewardRedeemStatusEnum.REDEEMING.getValue());
rewardRedeemMapper.insert(rewardRedeemDO);
return rewardRedeemDO.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public List<RedeemRewardRespDTO> redeemRewards(List<RedeemRewardReqVO> redeemRewardReqVOList) {
// 批量兑换每次最多十条
if (redeemRewardReqVOList.size() > 10) {
throw exception(REWARD_REDEEM_COUNT_NOT_ALLOW);
}
Long rewardId = redeemRewardReqVOList.get(0).getRewardId();
// 查询礼品
RewardDO rewardDO = rewardMapper.selectById(rewardId);
// 礼品未开启
if (rewardDO.getStatus() != 1) {
throw exception(REWARD_NOT_ENABLE);
}
int totalCount = 0;
for (RedeemRewardReqVO redeemRewardReqVO : redeemRewardReqVOList) {
// 每个兑换VO校验一遍
// 兑换方式不匹配
if (!Objects.equals(rewardDO.getPickMethod(), redeemRewardReqVO.getRedeemType())) {
throw exception(REWARD_PICK_METHOD_NOT_ALLOW_CREATE);
}
verifyMemberUser(redeemRewardReqVO, rewardDO);
// 记录兑换总数
totalCount += redeemRewardReqVO.getRewardCount();
// 判断兑换总数是否大于礼物数量
if (totalCount > rewardDO.getQuantityRemain()) {
throw exception(REWARD_COUNT_NOT_ENOUGH);
}
}
boolean lock = false;
try {
lock = redisDistributedLock.lock("reward:redeem:lock:" + rewardId);
if (!lock) {
throw exception(GET_LOCK_FAILED);
}
for (RedeemRewardReqVO redeemRewardReqVO : redeemRewardReqVOList) {
// 更新礼品
redeemReward(rewardDO, redeemRewardReqVO.getRewardCount());
// 添加兑换记录
Long redeemId = addRedeemRecord(redeemRewardReqVO);
// 更新会员积分
updateMemberScore(redeemRewardReqVO, rewardDO, redeemId);
}
} catch (Exception e) {
throw exception(REWARD_REDEEM_FAIL);
} finally {
redisDistributedLock.releaseLock("reward:redeem:lock:" + rewardId);
}
return null;
}
private void verifyMemberUser(RedeemRewardReqVO redeemRewardReqVO, RewardDO rewardDO) {
// 查询会员积分
UserRespDTO memberUser = memberUserApi.getUser(redeemRewardReqVO.getMemberId());
if (memberUser == null) {
throw exception(USER_NOT_EXISTS);
}
Integer holdScore = memberUser.getHoldScore();
// 会员积分不够
if (holdScore < rewardDO.getPointsRequire() * redeemRewardReqVO.getRewardCount()) {
throw exception(REWARD_SCORE_NOT_ENOUGH);
}
// 校验兑换次数
LambdaQueryWrapper<RewardRedeemDO> rewardRedeemDOWrapper = new LambdaQueryWrapper<>();
rewardRedeemDOWrapper.eq(RewardRedeemDO::getRewardId, redeemRewardReqVO.getRewardId())
.eq(RewardRedeemDO::getMemberId, redeemRewardReqVO.getMemberId());
Long count = rewardRedeemMapper.selectCount(rewardRedeemDOWrapper);
if (count > rewardDO.getAllowCount()) {
throw exception(REWARD_REDEEM_ALLOW_COUNT_ERROR);
}
}
}
package cn.iocoder.yudao.module.reward.controller.admin.reward;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.reward.api.reward.RedeemRewardApi;
import cn.iocoder.yudao.module.reward.api.reward.dto.RedeemRewardReqVO;
import cn.iocoder.yudao.module.reward.api.reward.dto.RedeemRewardRespDTO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Validated
@RestController
@Api(tags = "管理后台 - 礼品管理")
@RequestMapping("/reward/redeem")
public class RedeemRewardController {
@Resource
private RedeemRewardApi redeemRewardApi;
@PostMapping("/single")
@ApiOperation("兑换礼品")
//@PreAuthorize("@ss.hasPermission('reward::redeem')")
public CommonResult<RedeemRewardRespDTO> redeemReward(@Valid @RequestBody RedeemRewardReqVO redeemRewardReqVO) {
return success(redeemRewardApi.redeemReward(redeemRewardReqVO));
}
@PostMapping("/batch")
@ApiOperation("批量兑换礼品")
//@PreAuthorize("@ss.hasPermission('reward::redeem')")
public CommonResult<List<RedeemRewardRespDTO>> redeemRewards(@Valid @RequestBody List<RedeemRewardReqVO> redeemRewardReqVOList) {
return success(redeemRewardApi.redeemRewards(redeemRewardReqVOList));
}
}
......@@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
......@@ -41,7 +42,7 @@ public class RewardController {
return success(rewardService.create(createReqVO));
}
@PutMapping("/update")
@PostMapping("/update")
@ApiOperation("更新礼品")
//@PreAuthorize("@ss.hasPermission('reward::update')")
public CommonResult<Boolean> update(@Valid @RequestBody RewardUpdateReqVO updateReqVO) {
......@@ -88,7 +89,20 @@ public class RewardController {
@ApiOperation("礼品状态变更")
//@PreAuthorize("@ss.hasPermission('reward::query')")
public CommonResult<Boolean> updateStatus(@Valid @RequestBody RewardStatusReqVO statusVO) {
rewardService.updateById(RewardConvert.INSTANCE.convertStatusReqVO(statusVO));
rewardService.updateStatus(statusVO);
return success(true);
}
@PostMapping("/copy/{id}")
@ApiOperation("复制礼品")
public CommonResult<Long> copyReward(@NotNull @PathVariable Long id) {
return success(rewardService.copyReward(id));
}
@PostMapping("/delay")
@ApiOperation("礼品延期")
public CommonResult<Boolean> delayReward(@Valid @RequestBody RewardDelayReqVO delayVO) {
rewardService.delayReward(delayVO);
return success(true);
}
......
......@@ -58,4 +58,11 @@ public interface RewardConvert {
* @return
*/
RewardDO convertStatusReqVO(RewardStatusReqVO bean);
/***
* 礼品延期VO转实体
* @param bean
* @return
*/
RewardDO convertDelayReqVO(RewardDelayReqVO bean);
}
package cn.iocoder.yudao.module.reward.dal.dataobject.redeem;
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.math.BigDecimal;
import java.util.Date;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("ecw_reward_redeem")
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class RewardRedeemDO extends BaseDO {
/**
* 主键
*/
@TableId
private Long id;
/**
* 会员id
*/
private Long memberId;
/**
* 礼品id
*/
private Long rewardId;
/**
* 兑换状态
*/
private Integer status;
/**
* 兑换数量
*/
private Integer rewardCount;
/**
* 兑换方式
*/
private Integer redeemType;
/**
* 兑换入口
*/
private Integer entrance;
/**
* 费用
*/
private BigDecimal expenses;
/**
* 币种
*/
private Integer currency;
/**
* 收件人姓名
*/
private String recipientName;
/**
* 收件人电话
*/
private String recipientPhoneNum;
/**
* 收件人地址
*/
private String recipientAddress;
/**
* 兑换人
*/
private String redeemer;
/**
* 兑换时间
*/
private Date redemptionTime;
/**
* 快递公司
*/
private String courierCompany;
/**
* 快递单号
*/
private String expressNo;
/**
* 快递日期
*/
private String expressDate;
/**
* 快递寄出人
*/
private String expressSender;
/**
* 上传附件
*/
private String annex;
/**
* 备注
*/
private String remark;
}
package cn.iocoder.yudao.module.reward.dal.dataobject.reward;
import lombok.*;
import java.util.*;
import com.baomidou.mybatisplus.annotation.*;
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
......@@ -78,6 +80,7 @@ public class RewardDO extends BaseDO {
private Date endTime;
/**
* 领取方式(1上门领取,2包邮到家,3邮寄到付)
* TODO :改为枚举
*/
private Integer pickMethod;
/**
......@@ -98,6 +101,7 @@ public class RewardDO extends BaseDO {
private String remarkFr;
/**
* 礼品状态(1已启用,2未启用,3已关闭,4已过期)
* TODO :改为枚举
*/
private Integer status;
......
package cn.iocoder.yudao.module.reward.dal.mysql.redeem;
import cn.iocoder.yudao.framework.mybatis.core.mapper.AbstractMapper;
import cn.iocoder.yudao.module.reward.dal.dataobject.redeem.RewardRedeemDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 礼品 Mapper
* @author 系统管理员
*/
@Mapper
public interface RewardRedeemMapper extends AbstractMapper<RewardRedeemDO> {
}
package cn.iocoder.yudao.module.reward.dal.mysql.reward;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQuery;
import cn.iocoder.yudao.framework.mybatis.core.mapper.AbstractMapper;
import cn.iocoder.yudao.module.reward.dal.dataobject.reward.RewardDO;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQuery;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.reward.dal.dataobject.reward.RewardDO;
import cn.iocoder.yudao.module.reward.vo.reward.RewardQueryVO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.reward.vo.reward.*;
import java.util.List;
/**
* 礼品 Mapper
......
package cn.iocoder.yudao.module.reward.service.redeem;
import cn.iocoder.yudao.framework.mybatis.core.service.IService;
import cn.iocoder.yudao.module.reward.dal.dataobject.redeem.RewardRedeemDO;
/**
* 礼品兑换 Service 接口
*
* @author 系统管理员
*/
public interface RewardRedeemService extends IService<RewardRedeemDO> {
}
package cn.iocoder.yudao.module.reward.service.redeem;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.mybatis.core.service.IService;
import cn.iocoder.yudao.module.reward.dal.dataobject.redeem.RewardRedeemDO;
import cn.iocoder.yudao.module.reward.dal.dataobject.reward.RewardDO;
import cn.iocoder.yudao.module.reward.dal.mysql.redeem.RewardRedeemMapper;
import cn.iocoder.yudao.module.reward.dal.mysql.reward.RewardMapper;
import cn.iocoder.yudao.module.reward.service.reward.RewardService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 礼品兑换 Service
*
* @author 系统管理员
*/
@Service
@Slf4j
public class RewardRedeemServiceImpl extends AbstractService<RewardRedeemMapper, RewardRedeemDO> implements RewardRedeemService {
}
package cn.iocoder.yudao.module.reward.service.reward;
import java.util.*;
import javax.validation.*;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.service.IService;
import cn.iocoder.yudao.module.reward.vo.reward.*;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.reward.dal.dataobject.reward.RewardDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.reward.vo.reward.*;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
* 礼品 Service 接口
......@@ -62,4 +64,15 @@ public interface RewardService extends IService<RewardDO> {
* @return 礼品列表
*/
List<RewardDO> getList(RewardQueryVO query);
/**
* 复制礼品
* @param id 编号
* @return 新的礼品id
*/
Long copyReward(Long id);
void delayReward(RewardDelayReqVO delayVO);
void updateStatus(RewardStatusReqVO statusVO);
}
package cn.iocoder.yudao.module.reward.service.reward;
import java.util.*;
import javax.annotation.Resource;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQuery;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.module.reward.vo.reward.*;
import cn.iocoder.yudao.module.reward.dal.dataobject.reward.RewardDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.reward.convert.reward.RewardConvert;
import cn.iocoder.yudao.module.reward.dal.dataobject.reward.RewardDO;
import cn.iocoder.yudao.module.reward.dal.mysql.reward.RewardMapper;
import cn.iocoder.yudao.module.reward.vo.reward.*;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.Instant;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.reward.enums.ErrorCodeConstants.*;
import static com.baomidou.mybatisplus.core.toolkit.IdWorker.getId;
/**
* 礼品 Service 实现类
......@@ -32,45 +36,113 @@ public class RewardServiceImpl extends AbstractService<RewardMapper, RewardDO> i
@Override
public Long create(RewardCreateReqVO createReqVO) {
//活动开始时间不能比活动结束时间晚,活动结束时间不能比当前时间早
if (ObjectUtils.allNotNull(createReqVO.getStartTime(), createReqVO.getEndTime())) {
if (createReqVO.getStartTime().after((createReqVO.getEndTime())) || createReqVO.getEndTime().before(Date.from(Instant.now()))) {
throw exception(REWARD_START_OR_END_TIME_NOT_ALLOW_CREATE);
}
} else if (ObjectUtils.allNull(createReqVO.getStartTime(), createReqVO.getEndTime())) {
} else {
throw exception(REWARD_START_OR_END_TIME_NOT_ALLOW_CREATE);
}
// 创建的礼品状态只能是启用或未启用
if (createReqVO.getStatus() != 1 && createReqVO.getStatus() != 2) {
throw exception(REWARD_STATUS_NOT_ALLOW_CREATE);
}
//领取方式只能是1,2,3
if (createReqVO.getPickMethod() != 1 && createReqVO.getPickMethod() != 2 && createReqVO.getPickMethod() != 3) {
throw exception(REWARD_PICK_METHOD_NOT_ALLOW_CREATE);
}
// 插入
RewardDO rewardDO = RewardConvert.INSTANCE.convert(createReqVO);
rewardDO.setCode(generateRewardCode());
// 剩余数量若没传给默认值0
if (rewardDO.getQuantityRemain() == null) {
rewardDO.setQuantityRemain(0);
}
//如果插入失败,重新生成code再次插入
try {
rewardMapper.insert(rewardDO);
} catch (Exception e) {
rewardDO.setCode(generateRewardCode());
rewardMapper.insert(rewardDO);
}
// 返回
return rewardDO.getId();
}
@Override
// TODO :完善校验
public void update(RewardUpdateReqVO updateReqVO) {
// 校验存在
this.validateExists(updateReqVO.getId());
RewardDO rewardDO = rewardMapper.selectById(updateReqVO.getId());
if (rewardDO == null) {
throw exception(REWARD_NOT_EXISTS);
}
RewardDO updateObj = new RewardDO();
//未启用可编辑所有内容
if (rewardDO.getStatus() == 2) {
if (ObjectUtils.allNotNull(updateReqVO.getStartTime(), updateReqVO.getEndTime())) {
if (updateReqVO.getStartTime().after((updateReqVO.getEndTime())) || updateReqVO.getEndTime().before(Date.from(Instant.now()))) {
throw exception(REWARD_START_OR_END_TIME_NOT_ALLOW_CREATE);
}
} else if (ObjectUtils.allNull(updateReqVO.getStartTime(), updateReqVO.getEndTime())) {
} else {
throw exception(REWARD_START_OR_END_TIME_NOT_ALLOW_CREATE);
}
//领取方式只能是1,2,3
if (updateReqVO.getPickMethod() != 1 && updateReqVO.getPickMethod() != 2 && updateReqVO.getPickMethod() != 3) {
throw exception(REWARD_PICK_METHOD_NOT_ALLOW_CREATE);
}
updateObj = RewardConvert.INSTANCE.convert(updateReqVO);
} else if (rewardDO.getStatus() == 1) { //已启用可编辑剩余数量和备注
updateObj.setQuantityRemain(updateReqVO.getQuantityRemain());
updateObj.setRemarkEn(updateReqVO.getRemarkEn());
updateObj.setRemarkFr(updateReqVO.getRemarkFr());
updateObj.setRemarkZh(updateReqVO.getRemarkZh());
} else {
throw exception(REWARD_STATUS_NOT_ALLOW_UPDATE);
}
// 更新
RewardDO updateObj = RewardConvert.INSTANCE.convert(updateReqVO);
rewardMapper.updateById(updateObj);
}
@Override
public void delete(Long id) {
// 校验存在
this.validateExists(id);
RewardDO rewardDO = rewardMapper.selectById(id);
if (rewardDO == null) {
throw exception(REWARD_NOT_EXISTS);
}
//只有未启用可以删除
if (rewardDO.getStatus() != 2) {
throw exception(REWARD_STATUS_NOT_ALLOW_DELETE);
}
// 删除
rewardMapper.deleteById(id);
}
private void validateExists(Long id) {
if (rewardMapper.selectById(id) == null) {
throw exception(_NOT_EXISTS);
throw exception(REWARD_NOT_EXISTS);
}
}
@Override
public RewardDO get(Long id) {
return rewardMapper.selectById(id);
RewardDO rewardDO = rewardMapper.selectById(id);
//校验是否过期
if (rewardDO != null) {
validateExpire(rewardDO);
}
return rewardDO;
}
@Override
public List<RewardDO> getList(Collection<Long> ids) {
return rewardMapper.selectBatchIds(ids);
List<RewardDO> rewardDOS = rewardMapper.selectBatchIds(ids);
//校验是否过期
rewardDOS.forEach(this::validateExpire);
return rewardDOS;
}
@Override
......@@ -118,7 +190,10 @@ public class RewardServiceImpl extends AbstractService<RewardMapper, RewardDO> i
//创建时间
lambdaQuery.betweenIfPresent(RewardDO::getCreateTime, query.getBeginCreateTime(), query.getEndCreateTime())
.orderByDesc(RewardDO::getId);
return rewardMapper.selectPage(page, lambdaQuery);
PageResult<RewardDO> rewardDOPageResult = rewardMapper.selectPage(page, lambdaQuery);
//校验是否过期
rewardDOPageResult.getList().forEach(this::validateExpire);
return rewardDOPageResult;
}
@Override
......@@ -126,9 +201,90 @@ public class RewardServiceImpl extends AbstractService<RewardMapper, RewardDO> i
return rewardMapper.selectList(query);
}
@Override
public Long copyReward(Long id) {
RewardDO reward = rewardMapper.selectById(id);
if (reward == null) {
throw exception(REWARD_NOT_EXISTS);
}
//重新生成礼品ID
reward.setCode(generateRewardCode());
//设置为未启用
reward.setStatus(2);
reward.setId(null);
reward.setCreateTime(null);
reward.setUpdateTime(null);
reward.setDeleted(null);
try {
rewardMapper.insert(reward);
} catch (Exception e) {
reward.setCode(generateRewardCode());
rewardMapper.insert(reward);
}
return reward.getId();
}
@Override
public void delayReward(RewardDelayReqVO delayVO) {
RewardDO reward = rewardMapper.selectById(delayVO.getId());
if (reward == null) {
throw exception(REWARD_NOT_EXISTS);
}
//只允许延期已启用状态礼品
if (reward.getStatus() != 1) {
throw exception(REWARD_STATUS_NOT_ALLOW_DELAY);
}
Instant now = Instant.now();
//结束时间不能小于当前时间
if (!delayVO.getEndTime().toInstant().isAfter(now)) {
throw exception(REWARD_ENDTIME_ERROR);
}
rewardMapper.updateById(RewardConvert.INSTANCE.convertDelayReqVO(delayVO));
}
@Override
public void updateStatus(RewardStatusReqVO statusVO) {
this.validateExists(statusVO.getId());
RewardDO rewardDO = rewardMapper.selectById(statusVO.getId());
Integer oldStatus = rewardDO.getStatus();
Integer newStatus = statusVO.getStatus();
switch (newStatus) {
case 3://关闭:未启用情况下,不可进行关闭;点击关闭需要有二次弹窗提示;关闭后状态为已关闭,不支持兑换
if (oldStatus == 2) {
throw exception(REWARD_STATUS_NOT_ALLOW_ClOSE);
} else {
rewardMapper.updateById(RewardConvert.INSTANCE.convertStatusReqVO(statusVO));
}
break;
case 1://启用:有效期有效且未启用可进行点击启用,需要有二次确认弹窗
if (oldStatus == 2) {
if (rewardDO.getEndTime().toInstant().isBefore(Instant.now())) {
throw exception(REWARD_STATUS_NOT_ALLOW_ENABLE);
}
rewardMapper.updateById(RewardConvert.INSTANCE.convertStatusReqVO(statusVO));
}
break;
default:
throw exception(REWARD_STATUS_CHANGE_ERROR);
}
}
//生成礼品ID
private String generateRewardCode() {
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
return uuid.substring(0, 12).toUpperCase();
return RandomStringUtils.randomAlphanumeric(12).toUpperCase();
}
//校验礼品是否过期并修改礼品状态
//TODO:异步更新礼品状态
private void validateExpire(RewardDO rewardDO) {
if (rewardDO.getEndTime() != null){
if (rewardDO.getStatus() == 1 && rewardDO.getEndTime().toInstant().isBefore(Instant.now())) {
RewardDO expireReward = new RewardDO();
expireReward.setId(rewardDO.getId());
expireReward.setStatus(4);
rewardMapper.updateById(expireReward);
}
}
}
}
package cn.iocoder.yudao.module.reward.vo.reward;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
......
package cn.iocoder.yudao.module.reward.vo.reward;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
......
package cn.iocoder.yudao.module.reward.vo.reward;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.*;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
......@@ -15,12 +19,15 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
public class RewardCreateReqVO {
@ApiModelProperty(value = "中文名称")
@Size(max = 50, message = "名称长度不能超过50")
private String titleZh;
@ApiModelProperty(value = "英文名称")
@Size(max = 50, message = "名称长度不能超过50")
private String titleEn;
@ApiModelProperty(value = "法文名称")
@Size(max = 50, message = "名称长度不能超过50")
private String titleFr;
@ApiModelProperty(value = "中文礼品图片")
......@@ -34,6 +41,7 @@ public class RewardCreateReqVO {
@ApiModelProperty(value = "兑换所需积分", required = true)
@NotNull(message = "兑换所需积分不能为空")
@Min(value = 0)
private Integer pointsRequire;
@ApiModelProperty(value = "兑换网点", required = true)
......@@ -42,6 +50,7 @@ public class RewardCreateReqVO {
@ApiModelProperty(value = "剩余数量", required = true)
@NotNull(message = "剩余数量不能为空")
@Min(value = 0)
private Integer quantityRemain;
@ApiModelProperty(value = "活动开始时间")
......@@ -58,6 +67,7 @@ public class RewardCreateReqVO {
@ApiModelProperty(value = "允许兑换次数", required = true)
@NotNull(message = "允许兑换次数不能为空")
@Min(value = 0)
private Integer allowCount;
@ApiModelProperty(value = "中文备注")
......
package cn.iocoder.yudao.module.reward.vo.reward;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Data
@ApiModel("管理后台 - 礼品延期 VO")
public class RewardDelayReqVO {
@ApiModelProperty(value = "", required = true)
@NotNull(message = "不能为空")
private Long id;
@ApiModelProperty(value = "活动结束时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@NotNull(message = "结束时间不能为空")
private Date endTime;
}
package cn.iocoder.yudao.module.reward.vo.reward;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Data
......@@ -40,7 +41,7 @@ public class RewardQueryVO {
private Integer status;
@ApiModelProperty(value = "备注(不区分语言)")
private Integer remark;
private String remark;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "开始创建时间")
......
package cn.iocoder.yudao.module.reward.vo.reward;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel("管理后台 - 礼品更新 Request VO")
@Data
@ToString(callSuper = true)
public class RewardUpdateReqVO {
@ApiModelProperty(value = "", required = true)
@NotNull(message = "不能为空")
private Long id;
@ApiModelProperty(value = "中文名称")
@Size(max = 50, message = "名称长度不能超过50")
private String titleZh;
@ApiModelProperty(value = "英文名称")
@Size(max = 50, message = "名称长度不能超过50")
private String titleEn;
@ApiModelProperty(value = "法文名称")
@Size(max = 50, message = "名称长度不能超过50")
private String titleFr;
@ApiModelProperty(value = "中文礼品图片")
private String imgZh;
@ApiModelProperty(value = "英文礼品图片")
private String imgEn;
@ApiModelProperty(value = "法文礼品图片")
private String imgFr;
@ApiModelProperty(value = "兑换所需积分")
@Min(value = 0)
private Integer pointsRequire;
@ApiModelProperty(value = "兑换网点")
private Integer nodeId;
@ApiModelProperty(value = "剩余数量")
@NotNull(message = "剩余数量不能为空")
@Min(value = 0)
private Integer quantityRemain;
@ApiModelProperty(value = "活动开始时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date startTime;
@ApiModelProperty(value = "活动结束时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date endTime;
@ApiModelProperty(value = "领取方式(1上门领取,2包邮到家,3邮寄到付)")
private Integer pickMethod;
@ApiModelProperty(value = "允许兑换次数")
@Min(value = 0)
private Integer allowCount;
@ApiModelProperty(value = "中文备注")
@Size(max = 500, message = "中文备注长度不能超过500")
private String remarkZh;
@ApiModelProperty(value = "英文备注")
@Size(max = 500, message = "英文备注长度不能超过500")
private String remarkEn;
@ApiModelProperty(value = "法文备注")
@Size(max = 500, message = "法文备注长度不能超过500")
private String remarkFr;
}
<?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.reward.dal.mysql.redeem.RewardRedeemMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>
......@@ -114,6 +114,7 @@ yudao:
- cn.iocoder.yudao.module.wealth.enums.ErrorCodeConstants
- cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants
- cn.iocoder.yudao.module.system.enums.ErrorCodeConstants
- cn.iocoder.yudao.module.reward.enums.ErrorCodeConstants
tenant: # 多租户相关配置项
enable: false
ignore-urls:
......
......@@ -262,3 +262,5 @@ order.already.in.merge.pkg=
customer.is.new.or.old.no.change=
order.is.pre.installed=
order.is.not.pre.installed=
member.id.is.null=
score.count.error=
\ No newline at end of file
......@@ -998,4 +998,16 @@ order.already.in.merge.pkg=The order has been placed under the packaging box
customer.is.new.or.old.no.change=customer current business type is {}, it's no change
order.is.pre.installed=Order pre installed
customer.delay.approval.times.more.then.one=delay approval times more then one
member.id.is.null=no member ids
score.count.error= score count must > 0
member.score.not.enough = member score not enough
reward.do.not.exist=The reward does not exist
reward.end.time.less.than.now=The reward end time is less than the current time
reward.status.not.allow.delete=Only not enable can delete
reward.status.not.allow.close=Not enable reward can not close
reward.status.change.error=The reward status change error
reward.status.not.allow.delay=Only enable status can delay
reward.status.not.allow.create=Only enabled or disabled reward status can be created
reward.time.not.allow=The reward time is not allow
get.lock.failed = The service is busy, please try again later
\ No newline at end of file
......@@ -1002,3 +1002,16 @@ order.already.in.merge.pkg=\u8BA2\u5355\u5DF2\u5728\u5408\u5305\u7BB1\u4E0B
customer.is.new.or.old.no.change=\u5BA2\u6237\u5F53\u524D\u4E1A\u7EE9\u7C7B\u578B\u662F{}\u5BA2\u6237\uFF0C\u4E0D\u9700\u8981\u66F4\u65B0
order.is.pre.installed=\u8BA2\u5355\u5DF2\u9884\u88C5
order.is.not.pre.installed =\u8BA2\u5355\u672A\u88C5\u7BB1\uFF0C\u4E0D\u80FD\u5408\u5305
member.id.is.null=\u7F3A\u5C11\u4F1A\u5458id
score.count.error=\u79EF\u5206\u5FC5\u987B > 0
member.score.not.enough = \u4F1A\u5458\u79EF\u5206\u4E0D\u8DB3
reward.do.not.exist=\u793C\u54C1\u4E0D\u5B58\u5728
reward.end.time.less.than.now=\u793C\u54C1\u7ED3\u675F\u65F6\u95F4\u4E0D\u80FD\u65E9\u4E8E\u5F53\u524D\u65F6\u95F4
reward.status.not.allow.delete=\u53EA\u6709\u672A\u542F\u7528\u53EF\u5220\u9664
reward.status.not.allow.close=\u672A\u542F\u7528\u793C\u54C1\u4E0D\u53EF\u5173\u95ED
reward.status.change.error=\u793C\u54C1\u72B6\u6001\u64CD\u4F5C\u4E0D\u7B26\u5408\u89C4\u5219
reward.status.not.allow.delay=\u53EA\u5141\u8BB8\u5EF6\u671F\u542F\u7528\u72B6\u6001\u793C\u54C1
reward.status.not.allow.create=\u521B\u5EFA\u7684\u793C\u54C1\u72B6\u6001\u53EA\u80FD\u662F\u542F\u7528\u6216\u672A\u542F\u7528
reward.time.not.allow=\u6D3B\u52A8\u65F6\u95F4\u4E0D\u5408\u6CD5
get.lock.failed = \u670D\u52A1\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5
\ 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