实用指南:天机学堂day09学习

一、优惠券需求分析

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文件


    
        tjxt
        com.tianji
        1.0.0
    
    4.0.0
    tj-promotion
    
        11
        11
    
    
        
        
            com.tianji
            tj-auth-resource-sdk
            1.0.0
        
        
        
            com.tianji
            tj-api
            1.0.0
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            com.baomidou
            mybatis-plus-boot-starter
        
        
            mysql
            mysql-connector-java
        
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-config
        
        
        
            com.github.ben-manes.caffeine
            caffeine
        
        
        
            com.xuxueli
            xxl-job-core
        
        
        
            org.springframework.cloud
            spring-cloud-starter-loadbalancer
        
    
    
        ${project.artifactId}
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                        
                            build-info
                        
                    
                
                
                    com.tianji.promotion.PromotionApplication
                
            
        
    
    1.4.2.2.配置文件 bootstrap.yaml
server:
  port: 8091  #端口
  tomcat:
    uri-encoding: UTF-8   #服务编码
spring:
  profiles:
    active: dev
  application:
    name: promotion-service
  cloud:
    nacos:
      config:
        file-extension: yaml
        shared-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: true
    enableResponseWrap: true
    package-path: com.tianji.promotion.controller
    title: 天机学堂 - 促销中心接口文档
    description: 该服务包含优惠券促销有关的功能
    contact-name: 传智教育·研究院
    contact-url: http://www.itcast.cn/
    contact-email: zhanghuyi@itcast.cn
    version: v1.0
  jdbc:
    database: tj_promotion
  auth:
    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 @Transactional public void saveCoupon(CouponFormDTO dto) { //1.保存优惠券 //1.1.转为PO Coupon 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.转为PO List 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.处理VO
    List 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.拷贝属性到PO
    Coupon 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 {
    @Bean
    public 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.拷贝属性到PO
    Coupon 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.测试

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

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

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

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

posted @ 2025-12-25 20:00  yangykaifa  阅读(3)  评论(0)    收藏  举报