From cd0167c6804ff96baed519ea387f7e6ce71bfcdd Mon Sep 17 00:00:00 2001 From: xc-yjs Date: Thu, 8 Aug 2024 17:58:45 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=20=E6=9B=B4=E6=96=B0=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coupon/controller/CouponController.java | 44 ++++++ .../controller/CouponRecordController.java | 21 +++ .../cn/nla/coupon/mapper/CouponMapper.java | 29 ++++ .../nla/coupon/mapper/CouponRecordMapper.java | 16 +++ .../nla/coupon/model/entity/CouponEntity.java | 100 +++++++++++++ .../model/entity/CouponRecordEntity.java | 86 ++++++++++++ .../coupon/service/CouponRecordService.java | 16 +++ .../cn/nla/coupon/service/CouponService.java | 30 ++++ .../service/impl/CouponRecordServiceImpl.java | 20 +++ .../service/impl/CouponServiceImpl.java | 132 ++++++++++++++++++ .../main/resources/mapper/CouponMapper.xml | 36 +++++ .../resources/mapper/CouponRecordMapper.xml | 26 ++++ 12 files changed, 556 insertions(+) create mode 100644 nla-coupon-service/src/main/java/cn/nla/coupon/controller/CouponController.java create mode 100644 nla-coupon-service/src/main/java/cn/nla/coupon/controller/CouponRecordController.java create mode 100644 nla-coupon-service/src/main/java/cn/nla/coupon/mapper/CouponMapper.java create mode 100644 nla-coupon-service/src/main/java/cn/nla/coupon/mapper/CouponRecordMapper.java create mode 100644 nla-coupon-service/src/main/java/cn/nla/coupon/model/entity/CouponEntity.java create mode 100644 nla-coupon-service/src/main/java/cn/nla/coupon/model/entity/CouponRecordEntity.java create mode 100644 nla-coupon-service/src/main/java/cn/nla/coupon/service/CouponRecordService.java create mode 100644 nla-coupon-service/src/main/java/cn/nla/coupon/service/CouponService.java create mode 100644 nla-coupon-service/src/main/java/cn/nla/coupon/service/impl/CouponRecordServiceImpl.java create mode 100644 nla-coupon-service/src/main/java/cn/nla/coupon/service/impl/CouponServiceImpl.java create mode 100644 nla-coupon-service/src/main/resources/mapper/CouponMapper.xml create mode 100644 nla-coupon-service/src/main/resources/mapper/CouponRecordMapper.xml diff --git a/nla-coupon-service/src/main/java/cn/nla/coupon/controller/CouponController.java b/nla-coupon-service/src/main/java/cn/nla/coupon/controller/CouponController.java new file mode 100644 index 0000000..961e385 --- /dev/null +++ b/nla-coupon-service/src/main/java/cn/nla/coupon/controller/CouponController.java @@ -0,0 +1,44 @@ +package cn.nla.coupon.controller; + +import cn.nla.common.enums.CouponCategoryEnum; +import cn.nla.common.util.JsonData; +import cn.nla.coupon.service.CouponService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + *

+ * 优惠券表 前端控制器 + *

+ * + * @author YJs + * @since 2024-08-08 + */ +@Api(tags = "优惠券控制器") +@RestController +@RequestMapping("/cop/coupon/v1") +public class CouponController { + @Resource + private CouponService couponService; + + @ApiOperation("分页查询优惠券") + @GetMapping("page_coupon") + public JsonData pageCouponList( + @ApiParam(value = "当前页") @RequestParam(value = "page", defaultValue = "1") int page, + @ApiParam(value = "每页显示多少条") @RequestParam(value = "size", defaultValue = "10") int size) { + return JsonData.buildSuccess(couponService.pageCouponActivity(page, size)); + } + + /** + * 领取优惠券 + */ + @ApiOperation("领取优惠券") + @GetMapping("/add/promotion/{coupon_id}") + public JsonData addPromotionCoupon(@ApiParam(value = "优惠券id", required = true) @PathVariable("coupon_id") long couponId) { + return couponService.addCoupon(couponId, CouponCategoryEnum.PROMOTION); + } +} diff --git a/nla-coupon-service/src/main/java/cn/nla/coupon/controller/CouponRecordController.java b/nla-coupon-service/src/main/java/cn/nla/coupon/controller/CouponRecordController.java new file mode 100644 index 0000000..16f929c --- /dev/null +++ b/nla-coupon-service/src/main/java/cn/nla/coupon/controller/CouponRecordController.java @@ -0,0 +1,21 @@ +package cn.nla.coupon.controller; + + +import org.springframework.web.bind.annotation.RequestMapping; + +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * 优惠券领劵记录 前端控制器 + *

+ * + * @author YJs + * @since 2024-08-08 + */ +@RestController +@RequestMapping("/couponRecordEntity") +public class CouponRecordController { + +} + diff --git a/nla-coupon-service/src/main/java/cn/nla/coupon/mapper/CouponMapper.java b/nla-coupon-service/src/main/java/cn/nla/coupon/mapper/CouponMapper.java new file mode 100644 index 0000000..09bc4ff --- /dev/null +++ b/nla-coupon-service/src/main/java/cn/nla/coupon/mapper/CouponMapper.java @@ -0,0 +1,29 @@ +package cn.nla.coupon.mapper; + +import cn.nla.coupon.model.entity.CouponEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +/** + *

+ * 优惠券表 Mapper 接口 + *

+ * + * @author YJs + * @since 2024-08-08 + */ +public interface CouponMapper extends BaseMapper { + + /** + * 扣减存储 + */ + int reduceStock(@Param("couponId") long couponId); + + /** + * 扣减存储 + * + * @param couponId 优惠卷id + * @param oldVersion 版本号 + */ + int reduceStockOpt(@Param("couponId") long couponId, @Param("oldVersion") long oldVersion); +} diff --git a/nla-coupon-service/src/main/java/cn/nla/coupon/mapper/CouponRecordMapper.java b/nla-coupon-service/src/main/java/cn/nla/coupon/mapper/CouponRecordMapper.java new file mode 100644 index 0000000..4f2e1ef --- /dev/null +++ b/nla-coupon-service/src/main/java/cn/nla/coupon/mapper/CouponRecordMapper.java @@ -0,0 +1,16 @@ +package cn.nla.coupon.mapper; + +import cn.nla.coupon.model.entity.CouponRecordEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 优惠券领劵记录 Mapper 接口 + *

+ * + * @author YJs + * @since 2024-08-08 + */ +public interface CouponRecordMapper extends BaseMapper { + +} diff --git a/nla-coupon-service/src/main/java/cn/nla/coupon/model/entity/CouponEntity.java b/nla-coupon-service/src/main/java/cn/nla/coupon/model/entity/CouponEntity.java new file mode 100644 index 0000000..5aebbb1 --- /dev/null +++ b/nla-coupon-service/src/main/java/cn/nla/coupon/model/entity/CouponEntity.java @@ -0,0 +1,100 @@ +package cn.nla.coupon.model.entity; + +import java.math.BigDecimal; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; + +import java.util.Date; + +import com.baomidou.mybatisplus.annotation.TableId; + +import java.io.Serializable; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 优惠券表 + *

+ * + * @author YJs + * @since 2024-08-08 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("coupon") +public class CouponEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 优惠卷类型[NEW_USER注册赠券,TASK任务卷,PROMOTION促销劵] + */ + private String category; + + /** + * 发布状态, PUBLISH发布,DRAFT草稿,OFFLINE下线 + */ + private String publish; + + /** + * 优惠券图片 + */ + private String couponImg; + + /** + * 优惠券标题 + */ + private String couponTitle; + + /** + * 抵扣价格 + */ + private BigDecimal price; + + /** + * 每人限制张数 + */ + private Integer userLimit; + + /** + * 优惠券开始有效时间 + */ + private Date startTime; + + /** + * 优惠券失效时间 + */ + private Date endTime; + + /** + * 优惠券总量 + */ + private Integer publishCount; + + /** + * 库存 + */ + private Integer stock; + + private Date createTime; + + /** + * 满多少才可以使用 + */ + private BigDecimal conditionPrice; + + /** + * 版本 + */ + private Long version; + +} diff --git a/nla-coupon-service/src/main/java/cn/nla/coupon/model/entity/CouponRecordEntity.java b/nla-coupon-service/src/main/java/cn/nla/coupon/model/entity/CouponRecordEntity.java new file mode 100644 index 0000000..74f76f7 --- /dev/null +++ b/nla-coupon-service/src/main/java/cn/nla/coupon/model/entity/CouponRecordEntity.java @@ -0,0 +1,86 @@ +package cn.nla.coupon.model.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import java.util.Date; +import com.baomidou.mybatisplus.annotation.TableId; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 优惠券领劵记录 + *

+ * + * @author YJs + * @since 2024-08-08 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("coupon_record") +public class CouponRecordEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 优惠券id + */ + private Long couponId; + + /** + * 创建时间获得时间 + */ + private Date createTime; + + /** + * 使用状态: 可用NEW,已使用USED,过期EXPIRED; + */ + private String useState; + + /** + * 用户id + */ + private Long userId; + + /** + * 用户昵称 + */ + private String userName; + + /** + * 优惠券标题 + */ + private String couponTitle; + + /** + * 开始时间 + */ + private Date startTime; + + /** + * 结束时间 + */ + private Date endTime; + + /** + * 订单id + */ + private Long orderId; + + /** + * 抵扣价格 + */ + private BigDecimal price; + + /** + * 满多少才可以使用 + */ + private BigDecimal conditionPrice; + + +} diff --git a/nla-coupon-service/src/main/java/cn/nla/coupon/service/CouponRecordService.java b/nla-coupon-service/src/main/java/cn/nla/coupon/service/CouponRecordService.java new file mode 100644 index 0000000..595258d --- /dev/null +++ b/nla-coupon-service/src/main/java/cn/nla/coupon/service/CouponRecordService.java @@ -0,0 +1,16 @@ +package cn.nla.coupon.service; + +import cn.nla.coupon.model.entity.CouponRecordEntity; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 优惠券领劵记录 服务类 + *

+ * + * @author YJs + * @since 2024-08-08 + */ +public interface CouponRecordService extends IService { + +} diff --git a/nla-coupon-service/src/main/java/cn/nla/coupon/service/CouponService.java b/nla-coupon-service/src/main/java/cn/nla/coupon/service/CouponService.java new file mode 100644 index 0000000..749f79d --- /dev/null +++ b/nla-coupon-service/src/main/java/cn/nla/coupon/service/CouponService.java @@ -0,0 +1,30 @@ +package cn.nla.coupon.service; + +import cn.nla.common.enums.CouponCategoryEnum; +import cn.nla.common.util.JsonData; +import cn.nla.coupon.model.entity.CouponEntity; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.Map; + +/** + *

+ * 优惠券表 服务类 + *

+ * + * @author YJs + * @since 2024-08-08 + */ +public interface CouponService extends IService { + + Map pageCouponActivity(int page, int size); + + /** + * 领劵接口 + * 1、获取优惠券是否存在 + * 2、校验优惠券是否可以领取:时间、库存、超过限制 + * 3、扣减库存 + * 4、保存领劵记录 + */ + JsonData addCoupon(Long couponId, CouponCategoryEnum category); +} diff --git a/nla-coupon-service/src/main/java/cn/nla/coupon/service/impl/CouponRecordServiceImpl.java b/nla-coupon-service/src/main/java/cn/nla/coupon/service/impl/CouponRecordServiceImpl.java new file mode 100644 index 0000000..4c1e467 --- /dev/null +++ b/nla-coupon-service/src/main/java/cn/nla/coupon/service/impl/CouponRecordServiceImpl.java @@ -0,0 +1,20 @@ +package cn.nla.coupon.service.impl; + +import cn.nla.coupon.model.entity.CouponRecordEntity; +import cn.nla.coupon.mapper.CouponRecordMapper; +import cn.nla.coupon.service.CouponRecordService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 优惠券领劵记录 服务实现类 + *

+ * + * @author YJs + * @since 2024-08-08 + */ +@Service +public class CouponRecordServiceImpl extends ServiceImpl implements CouponRecordService { + +} diff --git a/nla-coupon-service/src/main/java/cn/nla/coupon/service/impl/CouponServiceImpl.java b/nla-coupon-service/src/main/java/cn/nla/coupon/service/impl/CouponServiceImpl.java new file mode 100644 index 0000000..d42afbf --- /dev/null +++ b/nla-coupon-service/src/main/java/cn/nla/coupon/service/impl/CouponServiceImpl.java @@ -0,0 +1,132 @@ +package cn.nla.coupon.service.impl; + +import cn.nla.common.enums.BizCodeEnum; +import cn.nla.common.enums.CouponCategoryEnum; +import cn.nla.common.enums.CouponPublishEnum; +import cn.nla.common.enums.CouponStateEnum; +import cn.nla.common.exception.BizException; +import cn.nla.common.interceptor.LoginInterceptor; +import cn.nla.common.model.LoginUser; +import cn.nla.common.util.CommonUtil; +import cn.nla.common.util.JsonData; +import cn.nla.coupon.mapper.CouponRecordMapper; +import cn.nla.coupon.model.VO.CouponVO; +import cn.nla.coupon.model.entity.CouponEntity; +import cn.nla.coupon.mapper.CouponMapper; +import cn.nla.coupon.model.entity.CouponRecordEntity; +import cn.nla.coupon.service.CouponService; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + *

+ * 优惠券表 服务实现类 + *

+ * + * @author YJs + * @since 2024-08-08 + */ +@Slf4j +@Service +public class CouponServiceImpl extends ServiceImpl implements CouponService { + + @Resource + private CouponRecordMapper couponRecordMapper; + + @Override + public Map pageCouponActivity(int page, int size) { + Page pageInfo = new Page<>(page, size); + IPage couponDOIPage = baseMapper.selectPage(pageInfo, + Wrappers.lambdaQuery().eq(CouponEntity::getPublish, CouponPublishEnum.PUBLISH) + .eq(CouponEntity::getCategory, CouponCategoryEnum.PROMOTION) + .orderByDesc(CouponEntity::getCreateTime)); + Map pageMap = new HashMap<>(3); + //总条数 + pageMap.put("total_record", couponDOIPage.getTotal()); + //总页数 + pageMap.put("total_page", couponDOIPage.getPages()); + pageMap.put("current_data", couponDOIPage.getRecords().stream().map(this::beanProcess).collect(Collectors.toList())); + return pageMap; + } + +@Override +public JsonData addCoupon(Long couponId, CouponCategoryEnum category) { + LoginUser loginUser = LoginInterceptor.threadLocal.get(); + CouponEntity coupon = baseMapper.selectOne( + Wrappers.lambdaQuery().eq(CouponEntity::getId, couponId) + .eq(CouponEntity::getCategory, category.name())); + //优惠券是否可以领取 + this.checkCoupon(coupon, loginUser.getId()); + //构建领劵记录 + CouponRecordEntity couponRecord = new CouponRecordEntity(); + BeanUtils.copyProperties(coupon, couponRecord); + couponRecord.setCreateTime(new Date()); + couponRecord.setUseState(CouponStateEnum.NEW.name()); + couponRecord.setUserId(loginUser.getId()); + couponRecord.setUserName(loginUser.getName()); + couponRecord.setCouponId(couponId); + couponRecord.setId(null); + //扣减库存 + // int rows = baseMapper.reduceStock(couponId); + //⾼并发下扣减劵库存,采⽤乐观锁,当前stock做版本号,延伸多种防⽌超卖的问题,⼀次只能领取1张 + // 数据库添加字段: version INT DEFAULT 1, 根据当前的版本号更新 + int rows = baseMapper.reduceStockOpt(couponId, coupon.getVersion()); + if (rows == 1) { + //库存扣减成功才保存 + couponRecordMapper.insert(couponRecord); + } else { + log.warn("发放优惠券失败:{},⽤ 户:{}", coupon, loginUser); + throw new BizException(BizCodeEnum.COUPON_NO_STOCK); + } + return JsonData.buildSuccess(); +} + + + private CouponVO beanProcess(CouponEntity entity) { + CouponVO couponVO = new CouponVO(); + BeanUtils.copyProperties(entity, couponVO); + return couponVO; + } + + /** + * 校验是否可以领取 + */ + private void checkCoupon(CouponEntity coupon, Long userId) { + if (coupon == null) { + throw new BizException(BizCodeEnum.COUPON_NO_EXITS); + } + //库存是否足够 + if (coupon.getStock() <= 0) { + throw new BizException(BizCodeEnum.COUPON_NO_STOCK); + } + //判断是否是否发布状态 + if (!coupon.getPublish().equals(CouponPublishEnum.PUBLISH.name())) { + throw new BizException(BizCodeEnum.COUPON_GET_FAIL); + } + //是否在领取时间范围 + long time = CommonUtil.getCurrentTimestamp(); + long start = coupon.getStartTime().getTime(); + long end = coupon.getEndTime().getTime(); + if (time < start || time > end) { + throw new BizException(BizCodeEnum.COUPON_OUT_OF_TIME); + } + //用户是否超过限制 + int recordNum = couponRecordMapper.selectCount( + Wrappers.lambdaQuery().eq(CouponRecordEntity::getCouponId, coupon.getId()) + .eq(CouponRecordEntity::getUserId, userId)); + if (recordNum >= coupon.getUserLimit()) { + throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT); + } + } +} diff --git a/nla-coupon-service/src/main/resources/mapper/CouponMapper.xml b/nla-coupon-service/src/main/resources/mapper/CouponMapper.xml new file mode 100644 index 0000000..196278d --- /dev/null +++ b/nla-coupon-service/src/main/resources/mapper/CouponMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + id, category, publish, coupon_img, coupon_title, price, user_limit, start_time, end_time, publish_count, stock, create_time, condition_price + + + + + update coupon set stock=stock-1 where id = #{couponId} + + + + + update coupon set stock=stock-1,version=version+1 where id = #{couponId} and stock>0 and version=#{oldVersion} + + diff --git a/nla-coupon-service/src/main/resources/mapper/CouponRecordMapper.xml b/nla-coupon-service/src/main/resources/mapper/CouponRecordMapper.xml new file mode 100644 index 0000000..487e4da --- /dev/null +++ b/nla-coupon-service/src/main/resources/mapper/CouponRecordMapper.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + id, coupon_id, create_time, use_state, user_id, user_name, coupon_title, start_time, end_time, order_id, price, condition_price + + +