萍乡市网站建设_网站建设公司_MongoDB_seo优化
2025/12/17 21:08:06 网站建设 项目流程

day09

实现新增优惠卷的功能

/*** ***思路分析:根据前端传入的dto,可以将coupon信息直接存入数据库当中,但是对于其限定信息 ***还需要进行判断(也就是标签)若是有呢,还需要加入到coupon_scope表当中,而在本方法中 ***转化成po使用stream流进行映射注入相关的scope信息,是因为CouponScopes ***实体类上有这个注解能实现链式注入@Accessors(chain = true) ***/ @Transactional public void saveCoupon(CouponFormDTO dto) { //1.保存优惠卷信息 //转成po Coupon coupon = BeanUtils.copyBean(dto, Coupon.class); //保存 save(coupon); if (!dto.getSpecific()){ //没有范围限定 return; } //2.保存限定范围 Long couponId = coupon.getId(); List<Long> scopes = dto.getScopes(); if (CollUtils.isEmpty(scopes)){ throw new BadRequestException("限定范围不能为空"); } //转换po List<CouponScope> list = scopes.stream().map(bizId -> new CouponScope().setBizId(bizId).setCouponId(couponId)).collect(Collectors.toList()); //保存 scopeService.saveBatch(list); }

实现分页查询优惠券功能

/*** ****思路分析:该代码的思路也就是跟分页查询差不多,根据筛选条件从数据库中查出page ***拿到record,最后返回即可 ***/ public PageDTO<CouponPageVO> queryCouponByPage(CouponQuery query) { // 从查询条件中获取过滤参数 Integer status = query.getStatus(); // 优惠券状态 Integer type = query.getType(); // 优惠券类型 String name = query.getName(); // 优惠券名称 // 执行分页查询,使用LambdaQueryWrapper构建查询条件 // 根据传入的参数动态添加查询条件,参数不为null时才添加对应条件 Page<Coupon> page = lambdaQuery() .eq(type != null, Coupon::getType, type) // 按类型查询 .eq(status != null, Coupon::getStatus, status) // 按状态查询 .like(StrUtil.isNotBlank(name), Coupon::getName, name) // 按名称模糊查询 .page(query.toMpPageDefaultSortByCreateTimeDesc()); // 执行分页查询,按创建时间降序排序 // 获取查询结果记录 List<Coupon> records = page.getRecords(); // 如果结果为空,返回空分页对象 if (CollUtils.isEmpty(records)) return PageDTO.empty(page); // 将实体对象列表转换为VO对象列表 List<CouponPageVO> list = BeanUtils.copyList(records, CouponPageVO.class); // 返回分页结果 return PageDTO.of(page,list); }

实现优惠券的发放功能

/*** ****思路分析:该功能的实现就是相当于一个更新操作,将优惠券的状态改为发放。拿到优惠券信息 ***只有暂停发放的优惠券或者是待发放的优惠券才能发放,同时判断是不是立刻发放或这发放时间小于 ***等于当前时间才发放,最后更新优惠券状态为发放,并且设置发放时间。注:此处还未时间定时发放功能 ***推断后续应该会有定时任务,同时,指定发放还需要生成验证码,后续实现 ***/ public void beginIssue(CouponIssueFormDTO dto) { //查询优惠券 Coupon coupon = getById(dto.getId()); if (coupon == null){ throw new BadRequestException("优惠券不存在"); } //判断优惠券状态,是否是暂停还是待发放 if (!CouponStatus.PAUSE.equals(coupon.getStatus()) && !CouponStatus.DRAFT.equals(coupon.getStatus())){ throw new BizIllegalException("优惠券状态错误"); } //判断是否是立刻发放 LocalDateTime issueBeginTime = dto.getIssueBeginTime(); LocalDateTime now = LocalDateTime.now(); //如果发放时间为null或者当前时间小于等于发放时间 boolean isBegin = issueBeginTime == null || !issueBeginTime.isAfter(now); //更新优惠券 Coupon c = BeanUtils.copyBean(dto, Coupon.class); if (isBegin){ c.setStatus(CouponStatus.ISSUING); c.setIssueBeginTime(now); }else { c.setStatus(CouponStatus.UN_ISSUE); } updateById(c); }

实现生成兑换码功能

思路分析:

说一下该算法生成兑换码思路:由于唯一性要求,很容易想到使用id技术,而id技术又有自增id,雪花算法,uuid这三个。而我们的兑换码是26个英文字母再加上2-9个数字组成,也就是32个,也就是base32算(将五个二进字字符视为一组,将该组的二进制计算为10进组对应其角标),最后又由于要求兑换码不能超过十个字符,而每个字符由5个字节(也就是一组)确定。因此,不能兑换码不能超过50个字节,其中uuid占用128个字节,雪花算法占用68个字节,因此,最终选定自增id(自增id从1增加到Integer的最大值,可以达到40亿以上个数字,而占用的字节仅仅4个字节,也就是32个bit位,距离50个bit位的限制还有很大的剩余,符合要求!)。这样就解决了唯一性,可读性,数据量大这三个要求。考虑到兑换码的状态只是兑换和没兑换,这跟前面的签到功能是一样的。因此很容易想到使用redis中的位图去解决。将兑换码的唯一标识,也就是自增id去对应位图上的位置,当该位置为1时就表名已经兑换,解决了重兑和高效的问题。而如何检验重兑呢,也很简单,只要使用base32将兑换码解码等操作,解析出原来的自增id去位图比对即可。最后就剩一个防止爆刷(也就是为了防止别人能够猜出来),这里采用的是加权算法将自增id(32位)每4位分为一组,共8组,都转为10进制​每一组给不同权重​把每一组数加权求和,得到的结果就是签名。当然,为了避免秘钥被人猜测出规律,我们可以准备16组秘钥。在兑换码自增id前拼接一个4位的新鲜值,可以是随机的。这个值是多少,就取第几组秘钥。

这样就进一步增加了兑换码的复杂度。​最后,把加权和,也就是签名也转二进制,拼接到最前面,最终的兑换码就是这样:

因此,在解码的时候,因为有50个字节,需要找到自增长id对应的序列号,也就是最后32个字节

功能实现:

1.引入base32工具类和签名工具:

package com.tianji.promotion.utils; import cn.hutool.core.text.StrBuilder; /** * 将整数转为base32字符的工具,因为是32进制,所以每5个bit位转一次 */ public class Base32 { private final static String baseChars = "6CSB7H8DAKXZF3N95RTMVUQG2YE4JWPL"; public static String encode(long raw) { StrBuilder sb = new StrBuilder(); while (raw != 0) { int i = (int) (raw & 0b11111); sb.append(baseChars.charAt(i)); raw = raw >>> 5; } return sb.toString(); } public static long decode(String code) { long r = 0; char[] chars = code.toCharArray(); for (int i = chars.length - 1; i >= 0; i--) { long n = baseChars.indexOf(chars[i]); r = r | (n << (5*i)); } return r; } public static String encode(byte[] raw) { StrBuilder sb = new StrBuilder(); int size = 0; int temp = 0; for (byte b : raw) { if (size == 0) { // 取5个bit int index = (b >>> 3) & 0b11111; sb.append(baseChars.charAt(index)); // 还剩下3位 size = 3; temp = b & 0b111; } else { int index = temp << (5 - size) | (b >>> (3 + size) & ((1 << 5 - size) - 1)) ; sb.append(baseChars.charAt(index)); int left = 3 + size; size = 0; if(left >= 5){ index = b >>> (left - 5) & ((1 << 5) - 1); sb.append(baseChars.charAt(index)); left = left - 5; } if(left == 0){ continue; } temp = b & ((1 << left) - 1); size = left; } } if(size > 0){ sb.append(baseChars.charAt(temp)); } return sb.toString(); } public static byte[] decode2Byte(String code) { char[] chars = code.toCharArray(); byte[] bytes = new byte[(code.length() * 5 )/ 8]; byte tmp = 0; byte byteSize = 0; int index = 0; int i = 0; for (char c : chars) { byte n = (byte) baseChars.indexOf(c); i++; if (byteSize == 0) { tmp = n; byteSize = 5; } else { int left = Math.min(8 - byteSize, 5); if(i == chars.length){ bytes[index] =(byte) (tmp << left | (n & ((1 << left) - 1))); break; } tmp = (byte) (tmp << left | (n >>> (5 - left))); byteSize += left; if (byteSize >= 8) { bytes[index++] = tmp; byteSize = (byte) (5 - left); if (byteSize == 0) { tmp = 0; } else { tmp = (byte) (n & ((1 << byteSize) - 1)); } } } } return bytes; } }
package com.tianji.promotion.utils; import com.tianji.common.constants.RegexConstants; import com.tianji.common.exceptions.BadRequestException; /** * <h1 style='font-weight:500'>1.兑换码算法说明:</h1> * <p>兑换码分为明文和密文,明文是50位二进制数,密文是长度为10的Base32编码的字符串 </p> * <h1 style='font-weight:500'>2.兑换码的明文结构:</h1> * <p style='padding: 0 15px'>14(校验码) + 4 (新鲜值) + 32(序列号) </p> * <ul style='padding: 0 15px'> * <li>序列号:一个单调递增的数字,可以通过Redis来生成</li> * <li>新鲜值:可以是优惠券id的最后4位,同一张优惠券的兑换码就会有一个相同标记</li> * <li>载荷:将新鲜值(4位)拼接序列号(32位)得到载荷</li> * <li>校验码:将载荷4位一组,每组乘以加权数,最后累加求和,然后对2^14求余得到</li> * </ul> * <h1 style='font-weight:500'>3.兑换码的加密过程:</h1> * <ol type='a' style='padding: 0 15px'> * <li>首先利用优惠券id计算新鲜值 f</li> * <li>将f和序列号s拼接,得到载荷payload</li> * <li>然后以f为角标,从提前准备好的16组加权码表中选一组</li> * <li>对payload做加权计算,得到校验码 c </li> * <li>利用c的后4位做角标,从提前准备好的异或密钥表中选择一个密钥:key</li> * <li>将payload与key做异或,作为新payload2</li> * <li>然后拼接兑换码明文:f (4位) + payload2(36位)</li> * <li>利用Base32对密文转码,生成兑换码</li> * </ol> * <h1 style='font-weight:500'>4.兑换码的解密过程:</h1> * <ol type='a' style='padding: 0 15px'> * <li>首先利用Base32解码兑换码,得到明文数值num</li> * <li>取num的高14位得到c1,取num低36位得payload </li> * <li>利用c1的后4位做角标,从提前准备好的异或密钥表中选择一个密钥:key</li> * <li>将payload与key做异或,作为新payload2</li> * <li>利用加密时的算法,用payload2和s1计算出新校验码c2,把c1和c2比较,一致则通过 </li> * </ol> */ public class CodeUtil { /** * 异或密钥表,用于最后的数据混淆 */ private final static long[] XOR_TABLE = { 61261925471L, 61261925523L, 58169127203L, 64169927267L, 64169927199L, 61261925629L, 58169127227L, 64169927363L, 59169127063L, 64169927359L, 58169127291L, 61261925739L, 59169127133L, 55139281911L, 56169127077L, 59169127167L }; /** * fresh值的偏移位数 */ private final static int FRESH_BIT_OFFSET = 32; /** * 校验码的偏移位数 */ private final static int CHECK_CODE_BIT_OFFSET = 36; /** * fresh值的掩码,4位 */ private final static int FRESH_MASK = 0xF; /** * 验证码的掩码,14位 */ private final static int CHECK_CODE_MASK = 0b11111111111111; /** * 载荷的掩码,36位 */ private final static long PAYLOAD_MASK = 0xFFFFFFFFFL; /** * 序列号掩码,32位 */ private final static long SERIAL_NUM_MASK = 0xFFFFFFFFL; /** * 序列号加权运算的秘钥表 */ private final static int[][] PRIME_TABLE = { {23, 59, 241, 61, 607, 67, 977, 1217, 1289, 1601}, {79, 83, 107, 439, 313, 619, 911, 1049, 1237}, {173, 211, 499, 673, 823, 941, 1039, 1213, 1429, 1259}, {31, 293, 311, 349, 431, 577, 757, 883, 1009, 1657}, {353, 23, 367, 499, 599, 661, 719, 929, 1301, 1511}, {103, 179, 353, 467, 577, 691, 811, 947, 1153, 1453}, {213, 439, 257, 313, 571, 619, 743, 829, 983, 1103}, {31, 151, 241, 349, 607, 677, 769, 823, 967, 1049}, {61, 83, 109, 137, 151, 521, 701, 827, 1123}, {23, 61, 199, 223, 479, 647, 739, 811, 947, 1019}, {31, 109, 311, 467, 613, 743, 821, 881, 1031, 1171}, {41, 173, 367, 401, 569, 683, 761, 883, 1009, 1181}, {127, 283, 467, 577, 661, 773, 881, 967, 1097, 1289}, {59, 137, 257, 347, 439, 547, 641, 839, 977, 1009}, {61, 199, 313, 421, 613, 739, 827, 941, 1087, 1307}, {19, 127, 241, 353, 499, 607, 811, 919, 1031, 1301} }; /** * 生成兑换码 * * @param serialNum 递增序列号 * @return 兑换码 */ public static String generateCode(long serialNum, long fresh) { // 1.计算新鲜值 fresh = fresh & FRESH_MASK; //取最后4位,确保新鲜值在0-15之间 // 2.拼接payload,fresh(4位) + serialNum(32位) long payload = fresh << FRESH_BIT_OFFSET | serialNum;//将新鲜值左移32位并拼接 // 3.计算验证码 long checkCode = calcCheckCode(payload, (int) fresh); System.out.println("checkCode = " + checkCode); // 4.payload做大质数异或运算,混淆数据 payload ^= XOR_TABLE[(int) (checkCode & FRESH_MASK)]; // 5.拼接兑换码明文: 校验码(14位) + payload(36位) long code = checkCode << CHECK_CODE_BIT_OFFSET | payload; // 6.转码 return Base32.encode(code); } private static long calcCheckCode(long payload, int fresh) { // 1.获取码表 int[] table = PRIME_TABLE[fresh]; // 2.生成校验码,payload每4位乘加权数,求和,取最后13位结果 long sum = 0; int index = 0; while (payload > 0) { sum += (payload & 0xf) * table[index++]; payload >>>= 4; } return sum & CHECK_CODE_MASK; } public static long parseCode(String code) { if (code == null || !code.matches(RegexConstants.COUPON_CODE_PATTERN)) { // 兑换码格式错误 throw new BadRequestException("无效兑换码"); } // 1.Base32解码 long num = Base32.decode(code); // 2.获取低36位,payload long payload = num & PAYLOAD_MASK; // 3.获取高14位,校验码 int checkCode = (int) (num >>> CHECK_CODE_BIT_OFFSET); // 4.载荷异或大质数,解析出原来的payload payload ^= XOR_TABLE[(checkCode & FRESH_MASK)]; // 5.获取高4位,fresh int fresh = (int) (payload >>> FRESH_BIT_OFFSET & FRESH_MASK); // 6.验证格式: if (calcCheckCode(payload, fresh) != checkCode) { throw new BadRequestException("无效兑换码"); } return payload & SERIAL_NUM_MASK; } }

2.完善优惠券发放功能

/*** ****思路分析:这里说一下为什么是异步方法,因为当优惠券第一次发放的时候同步生成兑换码,而由于 ****兑换码的生成数量可能较多,因此发放的时候可能会拖慢发放的进度,因此这里采用线程池去异步 ****处理 ****/ public void beginIssue(CouponIssueFormDTO dto) { //查询优惠券 Coupon coupon = getById(dto.getId()); if (coupon == null){ throw new BadRequestException("优惠券不存在"); } //判断优惠券状态,是否是暂停还是待发放 if (!CouponStatus.PAUSE.equals(coupon.getStatus()) && !CouponStatus.DRAFT.equals(coupon.getStatus())){ throw new BizIllegalException("优惠券状态错误"); } //判断是否是立刻发放 LocalDateTime issueBeginTime = dto.getIssueBeginTime(); LocalDateTime now = LocalDateTime.now(); //如果发放时间为null或者当前时间小于等于发放时间 boolean isBegin = issueBeginTime == null || !issueBeginTime.isAfter(now); //更新优惠券 Coupon c = BeanUtils.copyBean(dto, Coupon.class); if (isBegin){ c.setStatus(CouponStatus.ISSUING); c.setIssueBeginTime(now); }else { c.setStatus(CouponStatus.UN_ISSUE); } updateById(c); //是否需要生成兑换码 //判断优惠券的类型是兑换码的类型,且状态必须为待发放的状态 if(coupon.getObtainWay() == ObtainType.ISSUE && CouponStatus.DRAFT.equals(coupon.getStatus())){ coupon.setIssueEndTime(c.getIssueEndTime()); codeService.asynCenerateCode(coupon); }

添加的代码块

启动类上添加启动异步的注解:

/** ***注册线程池 ***/ @Configuration @Slf4j public class PromotionConfig { @Bean public Executor generateExchangeCodeExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); // 核心线程数 executor.setMaxPoolSize(5); // 最大线程数 executor.setQueueCapacity(200); // 队列容量 executor.setThreadNamePrefix("exchange-code-handler-"); // 线程名称前缀 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略 executor.initialize(); return executor; } }
/** ***常量类 **/ public interface PromotionConstants { String COUPON_CODE_SERIAL_KEY = "coupon:code:serial"; }
/*** ****思路分析:首先通过构造器,将redis和key进行绑定,后续调用不在需要注入key ***然后,通过id自长的方式得到序列号,由于兑换码要生成多条,因此直接得到最后一个自增长的 ****序列号,再通过for循环去拿到第一个自增长的序列号,并生成兑换码,同时将exchangeCode缺少的属性 ***补上,最后放入list中打包一起存入数据库当中 ***/ private BoundValueOperations<String, String> serialOps; //该构造器的作用就是将这个redis与key绑定,为了后续调用不再需要注入key public ExchangeCodeServiceImpl(StringRedisTemplate redisTemplate) { this.serialOps = redisTemplate.boundValueOps(COUPON_CODE_SERIAL_KEY); } /** * 异步生成优惠券代码的方法重写 * 该方法用于异步生成优惠券的特定代码,可能是为了提高系统性能和响应速度 * * @param coupon 优惠券对象,包含需要生成代码的相关信息 */ @Override @Async("generateExchangeCodeExecutor") public void asynCenerateCode(Coupon coupon) { Integer totalNum = coupon.getTotalNum(); //获取redis自增序列号 Long result = serialOps.increment(totalNum); if (result == null) return; List<ExchangeCode> list = new ArrayList<>(totalNum); int maxSerialNum = result.intValue(); for (int serialNum = maxSerialNum - totalNum + 1; serialNum <= maxSerialNum; serialNum++) { //生成兑换码 String code = CodeUtil.generateCode(serialNum, coupon.getId()); ExchangeCode e = new ExchangeCode(); e.setId(serialNum); e.setCode(code); e.setExchangeTargetId(coupon.getId()); e.setExpiredTime(coupon.getIssueEndTime()); list.add(e); } //保存数据库 saveBatch(list); }

练习:

1.实现修改优惠券功能

/*** ****思路分析:由于修改是另类的新增,因此可以按照新增的操作去写,只需要在操作数据库的时候 ****将所需要修改的优惠券的coupon扔进去即可。如果是限定,还需要删除旧数据,再更新新数据 ****/ @Transactional public void updateCouponById(CouponFormDTO dto) { Coupon coupon = lambdaQuery().eq(Coupon::getId, dto.getId()).one(); if (coupon == null){ throw new BadRequestException("优惠券不存在"); } if (!CouponStatus.DRAFT.equals(coupon.getStatus())){ throw new BadRequestException("优惠券状态不正确"); } Coupon updateCoupon = BeanUtils.copyBean(dto, Coupon.class); //更新 lambdaUpdate().eq(Coupon::getId, dto.getId()) .update(updateCoupon); if (!dto.getSpecific()){ //没有范围限定 return; } //2.保存限定范围 Long couponId = coupon.getId(); List<Long> scopes = dto.getScopes(); if (CollUtils.isEmpty(scopes)) { throw new BadRequestException("限定范围不能为空"); } //转换po List<CouponScope> list = scopes.stream().map(bizId -> new CouponScope().setBizId(bizId).setCouponId(couponId)).collect(Collectors.toList()); //保存 scopeService.updateBatchById(list); }

2.实现删除优惠券功能

/** * 根据优惠券ID删除优惠券 * @param id 优惠券ID * @throws BadRequestException 当优惠券不存在或状态不正确时抛出 */ @Override @Transactional // 声明事务,确保方法执行的事务性 public void deleteCouponById(Long id) { // 根据ID查询优惠券 Coupon coupon = lambdaQuery().eq(Coupon::getId, id).one(); // 如果优惠券不存在,抛出异常 if (coupon == null){ throw new BadRequestException("优惠券不存在"); } // 检查优惠券状态 if (!CouponStatus.DRAFT.equals(coupon.getStatus())){ throw new BadRequestException("优惠券状态不正确"); } // 执行删除操作 lambdaUpdate() .eq(Coupon::getId, id) .remove(); // 如果优惠券不是限定类型,直接返回 if (!coupon.getSpecific()) return; //如果有限定还需要去scope里面删 scopeService.lambdaUpdate().eq(CouponScope::getCouponId, id).remove(); }

3.根据id查询优惠券

private final CategoryClient categoryClient; /*** ****思路分析:由于只传入了id,因此需要去数据库查询到对应的优惠券信息,然后将其转化成vo ****而对应scope的处理就需要去数据库拿到bizId,如果通过远程调用否方式拿到所有的分类 ***拿到分类后还需要将其转化为map,key就是bizId,值就是分类的名称。拿到这个后,在通过 ***遍历bizId的方式,将对应的标签注入到scopeList中,最后填充到vo中返回 ***/ @Transactional public CouponDetailVO queryCouponById(Long id) { Optional<Coupon> coupon = lambdaQuery().eq(Coupon::getId, id).oneOpt(); if (coupon.isEmpty()){ throw new BadRequestException("优惠券不存在"); } //将po属性赋值给vo CouponDetailVO vo = BeanUtils.copyBean(coupon.get(), CouponDetailVO.class); //补全缺失的scope List<CouponScope> list = scopeService.lambdaQuery().eq(CouponScope::getCouponId, id).list(); List<Long> bizId = list.stream().map(CouponScope::getBizId).collect(Collectors.toList()); List<CategoryBasicDTO> category = categoryClient.getAllOfOneLevel(); Map<Long, CouponScopeVO> map = category.stream().collect(Collectors.toMap(CategoryBasicDTO::getId, c -> new CouponScopeVO(c.getId(), c.getName()))); List<CouponScopeVO> scope = new ArrayList<>(); bizId.forEach(b -> { CouponScopeVO couponScopeVO = map.get(b); if (couponScopeVO != null){ scope.add(couponScopeVO); } }); vo.setScopes(scope); return vo; }

4.实现定时发放优惠券功能

/*** ****思路分析:该功能和昨天写的定时任务类似,只需要将数据从数据库中分页查出来,然后将 ****数据的状态和发放时间修改,并且存入集合中,最后批处理即可 ***/ @XxlJob("timedCouponIssuanceTask") public void timedCouponIssuanceTask() { //根据筛选条件拿到对应的待发放的优惠券的分页查询列表 int index = XxlJobHelper.getShardIndex() ; int shardTotal = XxlJobHelper.getShardTotal(); int pageNo = index + 1; int pageSize = 1000; // 查询待发放的优惠券 List<Coupon> list = new ArrayList<>(); while (true){ List<Coupon> coupons = couponService.queryByStatusAndIssueBeginTime(pageNo , pageSize); if (CollUtils.isEmpty(coupons)) break; //分片轮播修改状态 coupons.forEach(e -> { e.setStatus(CouponStatus.ISSUING); e.setIssueBeginTime(LocalDateTime.now()); }); list.addAll(coupons); pageNo += shardTotal; } // 修改优惠券状态为已发放 couponService.updateBatchById(list); }
/*** ****其中分页查询的代码块 ***/ public List<Coupon> queryByStatusAndIssueBeginTime(Integer pageNo ,Integer pageSize) { LocalDateTime now = LocalDateTime.now(); Page<Coupon> page = lambdaQuery().eq(Coupon::getStatus, CouponStatus.UN_ISSUE) .le(Coupon::getIssueBeginTime, now).page(new Page<>(pageNo, pageSize)); List<Coupon> records = page.getRecords(); if (CollUtils.isEmpty(records)) return null; return records; }

xxl-job的配置

5.实现定时结束发放优惠券功能

@XxlJob("timedCouponSuspendTask") public void timedCouponSuspendTask() { //根据筛选条件拿到对应的待发放的优惠券的分页查询列表 int index = XxlJobHelper.getShardIndex() ; int shardTotal = XxlJobHelper.getShardTotal(); int pageNo = index + 1; int pageSize = 1000; // 查询待发放的优惠券 List<Coupon> list = new ArrayList<>(); while (true){ List<Coupon> coupons = couponService.queryByStatusAndIssueEndTime(pageNo , pageSize); if (CollUtils.isEmpty(coupons)) break; //分片轮播修改状态 coupons.forEach(e -> { e.setStatus(CouponStatus.PAUSE); e.setIssueEndTime(LocalDateTime.now()); }); list.addAll(coupons); pageNo += shardTotal; } // 修改优惠券状态为已暂停 couponService.updateBatchById(list); }
public List<Coupon> queryByStatusAndIssueEndTime(int pageNo, int pageSize) { LocalDateTime now = LocalDateTime.now(); Page<Coupon> page = lambdaQuery().eq(Coupon::getStatus, CouponStatus.ISSUING) .le(Coupon::getIssueEndTime, now).page(new Page<>(pageNo, pageSize)); List<Coupon> records = page.getRecords(); if (CollUtils.isEmpty(records)) return null; return records; }

6.实现暂停发放优惠券功能

public void pauseIssue(Long id) { // 根据ID获取优惠券信息 Coupon coupon = getById(id); // 如果优惠券不存在,抛出异常 if (coupon == null){ throw new BadRequestException("优惠券不存在"); } //判断优惠券状态,只有发放中才能暂停 if (!CouponStatus.ISSUING.equals(coupon.getStatus())){ throw new BizIllegalException("优惠券状态错误"); } //修改优惠券状态 Coupon update = new Coupon(); update.setId(id); update.setStatus(CouponStatus.PAUSE); updateById(update);

7.实现分页查询兑换码功能

/*** ****定义一个返回参数的类 ***/ @Data @ApiModel(description = "兑换码分页数据") @NoArgsConstructor @AllArgsConstructor public class ExchangeCodeVo { // 兑换码码 String code; // 兑换码对应的ID Integer id; }
private final IExchangeCodeService exchangeCodeService; @GetMapping("/page") @ApiOperation("分页查询兑换码") public PageDTO<ExchangeCodeVo> queryExchangeCodePage(@Valid CodeQuery query){ return exchangeCodeService.queryExchangeCodePage(query); }
public PageDTO<ExchangeCodeVo> queryExchangeCodePage(CodeQuery query) { Page<ExchangeCode> page = lambdaQuery().eq(ExchangeCode::getExchangeTargetId, query.getCouponId()) .eq(ExchangeCode::getStatus, query.getStatus()) .page(query.toMpPage()); List<ExchangeCode> records = page.getRecords(); if (CollUtils.isEmpty(records)) return PageDTO.empty(page); List<ExchangeCodeVo> vo = BeanUtils.copyList(records, ExchangeCodeVo.class); return PageDTO.of(page, vo); }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询