宁波市网站建设_网站建设公司_Linux_seo优化
2025/12/25 20:01:47 网站建设 项目流程

一、优惠券需求分析

1.1.业务流程梳理

1.2.接口统计

1.3.表结构设计

  1.3.1.优惠券表结构设计

  1.3.2.兑换码表结构设计

1.4.代码生成

  1.4.1.创建新的模块 Promotion

  1.4.2.基础配置

1.4.2.1.POM文件

tjxtcom.tianji1.0.04.0.0tj-promotion1111com.tianjitj-auth-resource-sdk1.0.0com.tianjitj-api1.0.0org.springframework.bootspring-boot-starter-webcom.baomidoumybatis-plus-boot-startermysqlmysql-connector-javaorg.springframework.bootspring-boot-starter-data-rediscom.alibaba.cloudspring-cloud-starter-alibaba-nacos-discoverycom.alibaba.cloudspring-cloud-starter-alibaba-nacos-configcom.github.ben-manes.caffeinecaffeinecom.xuxuelixxl-job-coreorg.springframework.cloudspring-cloud-starter-loadbalancer${project.artifactId}org.springframework.bootspring-boot-maven-pluginbuild-infocom.tianji.promotion.PromotionApplication
    1.4.2.2.配置文件 bootstrap.yaml
server:port: 8091  #端口tomcat:uri-encoding: UTF-8   #服务编码
spring:profiles:active: devapplication:name: promotion-servicecloud:nacos:config:file-extension: yamlshared-configs: # 共享配置- data-id: shared-spring.yaml # 共享spring配置refresh: false- data-id: shared-redis.yaml # 共享redis配置refresh: false- data-id: shared-mybatis.yaml # 共享mybatis配置refresh: false- data-id: shared-logs.yaml # 共享日志配置refresh: false- data-id: shared-feign.yaml # 共享feign配置refresh: false
#          - data-id: shared-mq.yaml # 共享mq配置
#            refresh: false- data-id: shared-xxljob.yaml # 共享XxlJob配置refresh: false
tj:swagger:enable: trueenableResponseWrap: truepackage-path: com.tianji.promotion.controllertitle: 天机学堂 - 促销中心接口文档description: 该服务包含优惠券促销有关的功能contact-name: 传智教育·研究院contact-url: http://www.itcast.cn/contact-email: zhanghuyi@itcast.cnversion: v1.0jdbc:database: tj_promotionauth:resource:enable: true
    1.4.2.3.启动类 PromotionApplication
package com.tianji.promotion;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.net.InetAddress;
import java.net.UnknownHostException;
@SpringBootApplication
@EnableScheduling   // 开启定时任务
@MapperScan("com.tianji.promotion.mapper")
@Slf4j
public class PromotionApplication {public static void main(String[] args) throws UnknownHostException {SpringApplication app = new SpringApplicationBuilder(PromotionApplication.class).build(args);Environment env = app.run(args).getEnvironment();String protocol = "http";if (env.getProperty("server.ssl.key-store") != null) {protocol = "https";}log.info("--/\n---------------------------------------------------------------------------------------\n\t" +"Application '{}' is running! Access URLs:\n\t" +"Local: \t\t{}://localhost:{}\n\t" +"External: \t{}://{}:{}\n\t" +"Profile(s): \t{}" +"\n---------------------------------------------------------------------------------------",env.getProperty("spring.application.name"),protocol,env.getProperty("server.port"),protocol,InetAddress.getLocalHost().getHostAddress(),env.getProperty("server.port"),env.getActiveProfiles());}
}

  1.4.3.MyBatis-Plus生成基础代码

  1.4.4.引入枚举

二、优惠券管理

2.1.新增优惠券

  2.1.1.接口分析

  2.1.2.实体准备

  准备 CouponFormDTO

package com.tianji.promotion.domain.dto;
import com.tianji.common.validate.annotations.EnumValid;
import com.tianji.promotion.enums.DiscountType;
import com.tianji.promotion.enums.ObtainType;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
@Data
@ApiModel(description = "优惠券表单数据")
public class CouponFormDTO {@ApiModelProperty("优惠券id,新增不需要添加,更新必填")private Long id;@ApiModelProperty("优惠券名称")@NotNull(message = "优惠券名称不能为空")@Size(max = 20, min = 4, message = "优惠券名称长度错误")private String name;@ApiModelProperty("是否添限定使用范围,true:限定了,false:没限定")private Boolean specific;@ApiModelProperty("优惠券使用范围")private List scopes;@ApiModelProperty("优惠券类型,1:每满减,2:折扣,3:无门槛,4:普通满减")@NotNull(message = "优惠券折扣类型不能为空")@EnumValid(enumeration = {1,2,3,4})private DiscountType discountType;@ApiModelProperty("折扣门槛,0代表无门槛")private Integer thresholdAmount;@ApiModelProperty("折扣值,满减填抵扣金额;打折填折扣值:80标示打8折")private Integer discountValue;@ApiModelProperty("最大优惠金额")private Integer maxDiscountAmount;@ApiModelProperty("优惠券总量")@Range(max = 5000, min = 1, message = "优惠券总量必须在1~5000")private Integer totalNum;@ApiModelProperty("每人领取的上限")@Range(max = 10, min = 1, message = "每人限领数量必须在1~10")private Integer userLimit;@ApiModelProperty("获取方式1:手动领取,2:指定发放(通过兑换码兑换)")@NotNull(message = "领取方式不能为空")@EnumValid(enumeration = {1, 2}, message = "领取方式不正确")private ObtainType obtainWay;
}

  准备 po Coupon

package com.tianji.promotion.domain.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.DiscountType;
import com.tianji.promotion.enums.ObtainType;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/*** 

* 优惠券的规则信息*

** @author 文涛* @since 2025-11-24*/ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("coupon") public class Coupon implements Serializable {private static final long serialVersionUID = 1L;/*** 优惠券id*/@TableId(value = "id", type = IdType.ASSIGN_ID)private Long id;/*** 优惠券名称,可以和活动名称保持一致*/@TableField("`name`") //与数据库关键字冲突,需要添加转义字符private String name;/*** 优惠券类型,1:普通券。目前就一种,保留字段*/private Integer type;/*** 折扣类型,1:满减,2:每满减,3:折扣,4:无门槛*/private DiscountType discountType;/*** 是否限定作用范围,false:不限定,true:限定。默认false*/@TableField("`specific`") //与数据库关键字冲突,需要添加转义字符private Boolean specific;/*** 折扣值,如果是满减则存满减金额,如果是折扣,则存折扣率,8折就是存80*/private Integer discountValue;/*** 使用门槛,0:表示无门槛,其他值:最低消费金额*/private Integer thresholdAmount;/*** 最高优惠金额,满减最大,0:表示没有限制,不为0,则表示该券有金额的限制*/private Integer maxDiscountAmount;/*** 获取方式:1:手动领取,2:兑换码*/private ObtainType obtainWay;/*** 开始发放时间*/private LocalDateTime issueBeginTime;/*** 结束发放时间*/private LocalDateTime issueEndTime;/*** 优惠券有效期天数,0:表示有效期是指定有效期的*/private Integer termDays;/*** 优惠券有效期开始时间*/private LocalDateTime termBeginTime;/*** 优惠券有效期结束时间*/private LocalDateTime termEndTime;/*** 优惠券配置状态,1:待发放,2:未开始 3:进行中,4:已结束,5:暂停*/private CouponStatus status;/*** 总数量,不超过5000*/private Integer totalNum;/*** 已发行数量,用于判断是否超发*/private Integer issueNum;/*** 已使用数量*/private Integer usedNum;/*** 每个人限领的数量,默认1*/private Integer userLimit;/*** 拓展参数字段,保留字段*/private String extParam;/*** 创建时间*/private LocalDateTime createTime;/*** 更新时间*/private LocalDateTime updateTime;/*** 创建人*/private Long creater;/*** 更新人*/private Long updater; }

  2.1.3.接口实现

    2.1.3.1.新增优惠券 CouponController
package com.tianji.promotion.controller;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.service.ICouponService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
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.validation.Valid;
/*** 

* 优惠券的规则信息 前端控制器*

** @author 文涛* @since 2025-11-24*/ @RestController @RequestMapping("/coupons") @Api(tags = "优惠券相关接口") @RequiredArgsConstructor public class CouponController {private final ICouponService couponService;@PostMapping()@ApiOperation("新增优惠券")public void saveCoupon(@RequestBody @Valid CouponFormDTO dto) {couponService.saveCoupon(dto);} }
    2.1.3.2.新增优惠券 ICouponService
package com.tianji.promotion.service;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.baomidou.mybatisplus.extension.service.IService;
/*** 

* 优惠券的规则信息 服务类*

** @author 文涛* @since 2025-11-24*/ public interface ICouponService extends IService {void saveCoupon(CouponFormDTO dto); }
    2.1.3.3.新增优惠券 CouponServiceImpl
package com.tianji.promotion.service.impl;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
/*** 

* 优惠券的规则信息 服务实现类*

** @author 文涛* @since 2025-11-24*/ @Service @RequiredArgsConstructor public class CouponServiceImpl extends ServiceImpl implements ICouponService {private final ICouponScopeService scopeService;@Override@Transactionalpublic void saveCoupon(CouponFormDTO dto) {//1.保存优惠券//1.1.转为POCoupon coupon = BeanUtils.copyBean(dto, Coupon.class);//1.2.保存save(coupon);//判断是否有保存范围限制if (!dto.getSpecific()) {//没有范围限制return;}Long couponId = coupon.getId();//2.保存限定范围List scopes = dto.getScopes();if (CollUtils.isEmpty(scopes)) {throw new BadRequestException("优惠券范围不能为空");}//2.1.转为POList list = scopes.stream().map(bizId -> new CouponScope().setBizId(bizId).setCouponId(couponId)).collect(Collectors.toList());//2.2.保存scopeService.saveBatch(list);} }
    2.1.3.4.测试

天机学堂管理端        账号:13500010002         密码:admin

天机学堂管理端地址:http://manage.tianji.com/

测试前需要修改 coupon_scope type 字段默认值为1,不然会报错

2.2.分页查询优惠券

  2.1.1.接口分析

  2.1.2.实体准备

  QUERY实体:CouponQuery

package com.tianji.promotion.domain.query;
import com.tianji.common.domain.query.PageQuery;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(description = "优惠券查询参数")
@Accessors(chain = true)
public class CouponQuery extends PageQuery {@ApiModelProperty("优惠券折扣类型:1:每满减,2:折扣,3:无门槛,4:满减")private Integer type;@ApiModelProperty("优惠券状态,1:待发放,2:发放中,3:已结束, 4:取消/终止")private Integer status;@ApiModelProperty("优惠券名称")private String name;
}

  VO实体:CouponPageVO

package com.tianji.promotion.domain.vo;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.DiscountType;
import com.tianji.promotion.enums.ObtainType;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@ApiModel(description = "优惠券分页数据")
public class CouponPageVO {@ApiModelProperty("优惠券id,新增不需要添加,更新必填")private Long id;@ApiModelProperty("优惠券名称")private String name;@ApiModelProperty("是否限定使用范围")private Boolean specific;@ApiModelProperty("优惠券类型,1:每满减,2:折扣,3:无门槛,4:普通满减")private DiscountType discountType;@ApiModelProperty("折扣门槛,0代表无门槛")private Integer thresholdAmount;@ApiModelProperty("折扣值,满减填抵扣金额;打折填折扣值:80标示打8折")private Integer discountValue;@ApiModelProperty("最大优惠金额")private Integer maxDiscountAmount;@ApiModelProperty("获取方式1:手动领取,2:指定发放(通过兑换码兑换)")private ObtainType obtainWay;@ApiModelProperty("已使用")private Integer usedNum;@ApiModelProperty("已发放数量")private Integer issueNum;@ApiModelProperty("优惠券总量")private Integer totalNum;@ApiModelProperty("优惠券创建时间")private LocalDateTime createTime;@ApiModelProperty("发放开始时间")private LocalDateTime issueBeginTime;@ApiModelProperty("发放结束时间")private LocalDateTime issueEndTime;@ApiModelProperty("有效天数")private Integer termDays;@ApiModelProperty("使用有效期开始时间")private LocalDateTime termBeginTime;@ApiModelProperty("使用有效期结束时间")private LocalDateTime termEndTime;@ApiModelProperty("状态")private CouponStatus status;
}

  2.1.3.接口实现

    2.1.3.1.分页查询优惠券 CouponController
package com.tianji.promotion.controller;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.domain.query.PageQuery;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.service.ICouponService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/*** 

* 优惠券的规则信息 前端控制器*

** @author 文涛* @since 2025-11-24*/ @RestController @RequestMapping("/coupons") @Api(tags = "优惠券相关接口") @RequiredArgsConstructor public class CouponController {private final ICouponService couponService;@GetMapping("page")@ApiOperation("分页查询优惠券接口")public PageDTO queryCouponByPage(CouponQuery query) {return couponService.queryCouponByPage(query);} }
    2.1.3.2.分页查询优惠券 ICouponService
PageDTO queryCouponByPage(CouponQuery query);
    2.1.3.3.分页查询优惠券 CouponServiceImpl
@Override
public PageDTO queryCouponByPage(CouponQuery query) {//1.分页查询Integer status = query.getStatus();String name = query.getName();Integer type = query.getType();Page page = lambdaQuery().eq(status != null, Coupon::getStatus, status).eq(type != null, Coupon::getDiscountType, type).like(StringUtils.isNotBlank(name), Coupon::getName, name).page(query.toMpPageDefaultSortByCreateTimeDesc());//2.处理VOList records = page.getRecords();if (CollUtils.isEmpty(records)) {return PageDTO.empty(page);}List list = BeanUtils.copyList(records, CouponPageVO.class);//3.返回return PageDTO.of(page, list);
}
    2.1.3.4.测试

三、优惠券发放

3.1.发放优惠券

  3.1.1.接口分析

  3.1.2.实体准备

  DTO实体 CouponIssueFormDTO

package com.tianji.promotion.domain.dto;
import com.tianji.common.utils.DateUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.Future;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
@Data
@ApiModel(description = "优惠券发放的表单实体")
public class CouponIssueFormDTO {@ApiModelProperty("优惠券id")private Long id;@ApiModelProperty("发放开始时间")@DateTimeFormat(pattern = DateUtils.DEFAULT_DATE_TIME_FORMAT)@Future(message = "发放开始时间必须晚于当前时间")private LocalDateTime issueBeginTime;@ApiModelProperty("发放结束时间")@Future(message = "发放结束时间必须晚于当前时间")@NotNull(message = "发放结束时间不能为空")@DateTimeFormat(pattern = DateUtils.DEFAULT_DATE_TIME_FORMAT)private LocalDateTime issueEndTime;@ApiModelProperty("有效天数")private Integer termDays;@ApiModelProperty("使用有效期开始时间")@DateTimeFormat(pattern = DateUtils.DEFAULT_DATE_TIME_FORMAT)private LocalDateTime termBeginTime;@ApiModelProperty("使用有效期结束时间")@DateTimeFormat(pattern = DateUtils.DEFAULT_DATE_TIME_FORMAT)private LocalDateTime termEndTime;
}

  3.1.3.接口实现

    3.1.3.1.发放优惠券 CouponController
package com.tianji.promotion.controller;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.domain.query.PageQuery;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.service.ICouponService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/*** 

* 优惠券的规则信息 前端控制器*

** @author 文涛* @since 2025-11-24*/ @RestController @RequestMapping("/coupons") @Api(tags = "优惠券相关接口") @RequiredArgsConstructor public class CouponController {private final ICouponService couponService;@ApiOperation("发放优惠券接口")@PutMapping("/{id}/issue")public void beginIssue(@RequestBody @Valid CouponIssueFormDTO dto) {couponService.beginIssue(dto);} }
    3.1.3.2.发放优惠券 ICouponService
void beginIssue(@Valid CouponIssueFormDTO dto);
    3.1.3.3.发放优惠券 ICouponServiceImpl
@Transactional
@Override
public void beginIssue(CouponIssueFormDTO dto) {// 1.查询优惠券Coupon coupon = getById(dto.getId());if (coupon == null) {throw new BadRequestException("优惠券不存在!");}// 2.判断优惠券状态,是否是暂停或待发放if(coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != PAUSE){throw new BizIllegalException("优惠券状态错误!");}// 3.判断是否是立刻发放LocalDateTime issueBeginTime = dto.getIssueBeginTime();LocalDateTime now = LocalDateTime.now();boolean isBegin = issueBeginTime == null || !issueBeginTime.isAfter(now);// 4.更新优惠券// 4.1.拷贝属性到POCoupon c = BeanUtils.copyBean(dto, Coupon.class);// 4.2.更新状态if (isBegin) {c.setStatus(ISSUING);c.setIssueBeginTime(now);}else{c.setStatus(UN_ISSUE);}// 4.3.写入数据库updateById(c);// TODO 兑换码生成// 5.判断是否需要生成兑换码,优惠券类型必须是兑换码,优惠券状态必须是待发放if(coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT){coupon.setIssueEndTime(c.getIssueEndTime());codeService.asyncGenerateCode(coupon);}
}
    3.1.3.4.测试

3.2.兑换码生成算法

  3.2.1.兑换码的需求

  3.2.2.算法分析

  3.2.3.算法实现

3.3.异步生成兑换码

  3.3.1.思路分析

  3.3.2.代码实现

    3.3.2.1. PromotionConfig 中定义线程池

    启动类添加@EnableAsync注解,开启异步功能

package com.tianji.promotion.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
@Configuration
public class PromotionConfig {@Beanpublic Executor generateExchangeCodeExecutor(){ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 1.核心线程池大小executor.setCorePoolSize(2);// 2.最大线程池大小executor.setMaxPoolSize(5);// 3.队列大小executor.setQueueCapacity(200);// 4.线程名称executor.setThreadNamePrefix("exchange-code-handler-");// 5.拒绝策略executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}
}
    3.3.2.2. 改造 CouponServiceImpl 中的 beginIssue 方法
@Transactional
@Override
public void beginIssue(CouponIssueFormDTO dto) {// 1.查询优惠券Coupon coupon = getById(dto.getId());if (coupon == null) {throw new BadRequestException("优惠券不存在!");}// 2.判断优惠券状态,是否是暂停或待发放if(coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != PAUSE){throw new BizIllegalException("优惠券状态错误!");}// 3.判断是否是立刻发放LocalDateTime issueBeginTime = dto.getIssueBeginTime();LocalDateTime now = LocalDateTime.now();boolean isBegin = issueBeginTime == null || !issueBeginTime.isAfter(now);// 4.更新优惠券// 4.1.拷贝属性到POCoupon c = BeanUtils.copyBean(dto, Coupon.class);// 4.2.更新状态if (isBegin) {c.setStatus(ISSUING);c.setIssueBeginTime(now);}else{c.setStatus(UN_ISSUE);}// 4.3.写入数据库updateById(c);// 新增 兑换码生成// 5.判断是否需要生成兑换码,优惠券类型必须是兑换码,优惠券状态必须是待发放if(coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT){coupon.setIssueEndTime(c.getIssueEndTime());codeService.asyncGenerateCode(coupon);}
}
    3.3.2.3. 在 IExchangeCodeService 声明 asyncGenerateCode
package com.tianji.promotion.service;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.baomidou.mybatisplus.extension.service.IService;
/*** 

* 兑换码 服务类*

** @author 文涛* @since 2025-11-24*/ public interface IExchangeCodeService extends IService {void asyncGenerateCode(Coupon coupon); }
    3.3.2.4. ExchangeCodeServiceImpl 中实现 asyncGenerateCode
package com.tianji.promotion.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.utils.CollUtils;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.mapper.ExchangeCodeMapper;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.utils.CodeUtil;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static com.tianji.promotion.constans.PromotionConstants.COUPON_CODE_SERIAL_KEY;
/*** 

* 兑换码 服务实现类*

** @author 虎哥*/ @Service public class ExchangeCodeServiceImpl extends ServiceImpl implements IExchangeCodeService {private final StringRedisTemplate redisTemplate;private final BoundValueOperations serialOps;public ExchangeCodeServiceImpl(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;this.serialOps = redisTemplate.boundValueOps(COUPON_CODE_SERIAL_KEY);}@Override@Async("generateExchangeCodeExecutor")public void asyncGenerateCode(Coupon coupon) {// 发放数量Integer totalNum = coupon.getTotalNum();// 1.获取Redis自增序列号Long result = serialOps.increment(totalNum);if (result == null) {return;}int maxSerialNum = result.intValue();List list = new ArrayList<>(totalNum);for (int serialNum = maxSerialNum - totalNum + 1; serialNum <= maxSerialNum; serialNum++) {// 2.生成兑换码String code = CodeUtil.generateCode(serialNum, coupon.getId());ExchangeCode e = new ExchangeCode();e.setCode(code);e.setId(serialNum);e.setExchangeTargetId(coupon.getId());e.setExpiredTime(coupon.getIssueEndTime());list.add(e);}// 3.保存数据库saveBatch(list); // // 4.写入Redis缓存,member:couponId,score:兑换码的最大序列号 // redisTemplate.opsForZSet().add(COUPON_RANGE_KEY, coupon.getId().toString(), maxSerialNum);} }
    3.3.2.5.测试

四、练习

4.1.修改优惠券

  4.1.1. 修改优惠券 CouponController

// TODO 修改优惠券
@PutMapping("/{id}")
@ApiOperation("修改优惠券")
public void updateCouponById(@Valid @RequestBody CouponFormDTO dto){couponService.updateCouponById(dto);
}

  4.1.1. 修改优惠券 CouponServiceImpl(CouponService省略)

// 修改优惠券
@Override
public void updateCouponById(CouponFormDTO dto) {// 1.属性拷贝Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);// 2.更新表数据updateById(coupon);
}

4.2.删除优惠券

  4.2.1. 删除优惠券 CouponController

// TODO 删除优惠券
@ApiOperation("删除优惠券--管理端")
@DeleteMapping("/{id}")
public void removeCouponById(@PathVariable("id") Long id) {couponService.removeCouponById(id);
}

  4.2.2. 删除优惠券 CouponServiceImpl

/*** 删除优惠券--管理端** @param id*/
@Override
public void removeCouponById(Long id) {//1、校验if (id == null) {throw new BadRequestException("非法参数!");}//2、查询优惠券表Coupon coupon = this.getById(id);if (coupon == null) {throw new BadRequestException("优惠券不存在!");}//3、判断优惠券配置状态if (coupon.getStatus() != CouponStatus.DRAFT) {throw new BadRequestException("该优惠券不处于待发放状态,不能删除!");}//4、删除优惠券this.removeById(id);
}

4.3.根据id查询优惠券

  4.3.1. 根据id查询优惠券 CouponController

// TODO 根据id查询优惠券
@GetMapping("/{id}")
@ApiOperation("根据id查询优惠券")
public Coupon getCouponById(@PathVariable Long id){return couponService.getCouponById(id);
}

  4.3.2. 根据id查询优惠券 CouponServiceImpl

// 根据id查询优惠券
@Override
public Coupon getCouponById(Long id) {// 返回return lambdaQuery().eq(Coupon::getId, id).one();
}

4.4.定时开始发放优惠券(同结束)

4.5.定时结束发放优惠券

  4.5.1.创建CouponIssueJobHandler

package com.tianji.promotion.handler;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.common.utils.CollUtils;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.service.ICouponService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
/*** 定时任务*/
@Slf4j
@RequiredArgsConstructor
@Component
public class CouponIssueJobHandler {private final ICouponService couponService;/*** 定时开始或结束发放优惠券*/@XxlJob("couponIssueJobHandler")public void IssueCoupon() {// 1.获取分片信息,作为页码,每页最多查询 20条int index = XxlJobHelper.getShardIndex() + 1;int size = Integer.parseInt(XxlJobHelper.getJobParam());// 2.查询未开始的优惠券Page page = couponService.lambdaQuery().eq(Coupon::getStatus, CouponStatus.UN_ISSUE).le(Coupon::getTermBeginTime, LocalDateTime.now()).page(new Page<>(index, size));// 3.发放优惠券List coupons = page.getRecords();if (CollUtils.isEmpty(coupons)) {return;}couponService.beginIssueBatch(coupons);// 4.查询发放中的优惠券Page pageEnd = couponService.lambdaQuery().eq(Coupon::getStatus, CouponStatus.ISSUING).ge(Coupon::getIssueEndTime, LocalDateTime.now()).page(new Page<>(index, size));List couponsEnd = pageEnd.getRecords();//5、结束发放优惠券if (CollUtils.isEmpty(couponsEnd)) {return;}couponService.endIssueBatch(couponsEnd);}
}

  4.5.2.定时发放优惠券 ICouponService

/*** 定时开始发放优惠券* @param coupons*/
void beginIssueBatch(List coupons);
/*** 定时结束发放优惠券* @param couponsEnd*/
void endIssueBatch(List couponsEnd);
void pauseCoupon(Long id);

  4.5.3.定时发放优惠券 CouponServiceImpl

/*** 定时结束发放优惠券** @param couponsEnd*/
@Override
public void endIssueBatch(List couponsEnd) {//更新券状态为发放结束for (Coupon coupon : couponsEnd) {coupon.setStatus(CouponStatus.FINISHED);}this.updateBatchById(couponsEnd);
}

4.6.暂停发放优惠券

  4.6.1. 暂停发放优惠券 CouponController

/*** 暂停发放优惠券*/
@ApiOperation("暂停发放优惠券")
@PutMapping("/{id}/pause")
public void pauseCoupon(@PathVariable("id") Long id) {couponService.pauseCoupon(id);
}

  4.6.2. 暂停发放优惠券 CouponServiceImpl

/*** 暂停发放优惠券** @param id*/
@Override
public void pauseCoupon(Long id) {//1、检验参数if (id == null) {throw new BadRequestException("非法参数!");}//2、校验优惠券id是否存在Coupon coupon = this.getById(id);if (coupon == null) {throw new BadRequestException("优惠券不存在!");}//3、校验优惠券状态  只有发放中的状态才能暂停if (coupon.getStatus() != CouponStatus.ISSUING) {throw new BizIllegalException("该优惠券不处于发放中的状态,不能暂停!");}//4、修改优惠券状态为暂停this.lambdaUpdate().eq(Coupon::getId, id).set(Coupon::getStatus, CouponStatus.PAUSE).update();
}

4.7.查询兑换码

  4.7.1. 查询兑换码 CouponController

package com.tianji.promotion.controller;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.promotion.domain.query.CodeQuery;
import com.tianji.promotion.domain.vo.ExchangeCodeVO;
import com.tianji.promotion.service.IExchangeCodeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/*** 

* 兑换码 前端控制器*

** @author wyy* @since 2025-04-23*/ @Api(tags = "兑换码相关接口") @RequiredArgsConstructor @RestController @RequestMapping("/codes") public class ExchangeCodeController {private final IExchangeCodeService exchangeCodeService;/*** 分页查询兑换码* @param query* @return*/@ApiOperation("分页查询兑换码")@GetMapping("/page")public PageDTO queryExchangeCodePage(CodeQuery query) {return exchangeCodeService.queryExchangeCodePage(query);} }

  4.7.2. 查询兑换码 ExchangeCodeServiceImpl

/*** 分页查询兑换码* @param query* @return*/
@Override
public PageDTO queryExchangeCodePage(CodeQuery query) {//1、分页查询兑换码表Page page = this.lambdaQuery().eq(ExchangeCode::getExchangeTargetId, query.getCouponId()).eq(ExchangeCode::getStatus, query.getStatus()).page(query.toMpPageDefaultSortByCreateTimeDesc());List records = page.getRecords();if (CollUtils.isEmpty(records)) {return PageDTO.empty(page);}//2、封装vo,返回结果List voList = BeanUtils.copyList(records, ExchangeCodeVO.class);return PageDTO.of(page, voList);
}

  4.7.3.ExchangeCodeVO

package com.tianji.promotion.domain.vo;
import com.tianji.promotion.enums.DiscountType;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@ApiModel(description = "优惠券分页数据")
@NoArgsConstructor
@AllArgsConstructor
public class ExchangeCodeVO {@ApiModelProperty("兑换码id")private Integer id;@ApiModelProperty("兑换码")private String code;@ApiModelProperty("兑换码状态")private DiscountType status;@ApiModelProperty("兑换人")private Long userId;@ApiModelProperty("兑换码目标id")private Long exchangeTargetId;
}

4.7.4.测试

                                都看到这了,给文涛点个赞支持一下呗!

                                你的‘赞’,是给与文涛最大的动力鸭

                                有问题,可以评论区大家一起讨论

                                后续会在此更新,相关问题及解决方案

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

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

立即咨询