12 changed files with 556 additions and 0 deletions
@ -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; |
|||
|
|||
/** |
|||
* <p> |
|||
* 优惠券表 前端控制器 |
|||
* </p> |
|||
* |
|||
* @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); |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
package cn.nla.coupon.controller; |
|||
|
|||
|
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
|
|||
import org.springframework.web.bind.annotation.RestController; |
|||
|
|||
/** |
|||
* <p> |
|||
* 优惠券领劵记录 前端控制器 |
|||
* </p> |
|||
* |
|||
* @author YJs |
|||
* @since 2024-08-08 |
|||
*/ |
|||
@RestController |
|||
@RequestMapping("/couponRecordEntity") |
|||
public class CouponRecordController { |
|||
|
|||
} |
|||
|
@ -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; |
|||
|
|||
/** |
|||
* <p> |
|||
* 优惠券表 Mapper 接口 |
|||
* </p> |
|||
* |
|||
* @author YJs |
|||
* @since 2024-08-08 |
|||
*/ |
|||
public interface CouponMapper extends BaseMapper<CouponEntity> { |
|||
|
|||
/** |
|||
* 扣减存储 |
|||
*/ |
|||
int reduceStock(@Param("couponId") long couponId); |
|||
|
|||
/** |
|||
* 扣减存储 |
|||
* |
|||
* @param couponId 优惠卷id |
|||
* @param oldVersion 版本号 |
|||
*/ |
|||
int reduceStockOpt(@Param("couponId") long couponId, @Param("oldVersion") long oldVersion); |
|||
} |
@ -0,0 +1,16 @@ |
|||
package cn.nla.coupon.mapper; |
|||
|
|||
import cn.nla.coupon.model.entity.CouponRecordEntity; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
|
|||
/** |
|||
* <p> |
|||
* 优惠券领劵记录 Mapper 接口 |
|||
* </p> |
|||
* |
|||
* @author YJs |
|||
* @since 2024-08-08 |
|||
*/ |
|||
public interface CouponRecordMapper extends BaseMapper<CouponRecordEntity> { |
|||
|
|||
} |
@ -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; |
|||
|
|||
/** |
|||
* <p> |
|||
* 优惠券表 |
|||
* </p> |
|||
* |
|||
* @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; |
|||
|
|||
} |
@ -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; |
|||
|
|||
/** |
|||
* <p> |
|||
* 优惠券领劵记录 |
|||
* </p> |
|||
* |
|||
* @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; |
|||
|
|||
|
|||
} |
@ -0,0 +1,16 @@ |
|||
package cn.nla.coupon.service; |
|||
|
|||
import cn.nla.coupon.model.entity.CouponRecordEntity; |
|||
import com.baomidou.mybatisplus.extension.service.IService; |
|||
|
|||
/** |
|||
* <p> |
|||
* 优惠券领劵记录 服务类 |
|||
* </p> |
|||
* |
|||
* @author YJs |
|||
* @since 2024-08-08 |
|||
*/ |
|||
public interface CouponRecordService extends IService<CouponRecordEntity> { |
|||
|
|||
} |
@ -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; |
|||
|
|||
/** |
|||
* <p> |
|||
* 优惠券表 服务类 |
|||
* </p> |
|||
* |
|||
* @author YJs |
|||
* @since 2024-08-08 |
|||
*/ |
|||
public interface CouponService extends IService<CouponEntity> { |
|||
|
|||
Map<String, Object> pageCouponActivity(int page, int size); |
|||
|
|||
/** |
|||
* 领劵接口 |
|||
* 1、获取优惠券是否存在 |
|||
* 2、校验优惠券是否可以领取:时间、库存、超过限制 |
|||
* 3、扣减库存 |
|||
* 4、保存领劵记录 |
|||
*/ |
|||
JsonData addCoupon(Long couponId, CouponCategoryEnum category); |
|||
} |
@ -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; |
|||
|
|||
/** |
|||
* <p> |
|||
* 优惠券领劵记录 服务实现类 |
|||
* </p> |
|||
* |
|||
* @author YJs |
|||
* @since 2024-08-08 |
|||
*/ |
|||
@Service |
|||
public class CouponRecordServiceImpl extends ServiceImpl<CouponRecordMapper, CouponRecordEntity> implements CouponRecordService { |
|||
|
|||
} |
@ -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; |
|||
|
|||
/** |
|||
* <p> |
|||
* 优惠券表 服务实现类 |
|||
* </p> |
|||
* |
|||
* @author YJs |
|||
* @since 2024-08-08 |
|||
*/ |
|||
@Slf4j |
|||
@Service |
|||
public class CouponServiceImpl extends ServiceImpl<CouponMapper, CouponEntity> implements CouponService { |
|||
|
|||
@Resource |
|||
private CouponRecordMapper couponRecordMapper; |
|||
|
|||
@Override |
|||
public Map<String, Object> pageCouponActivity(int page, int size) { |
|||
Page<CouponEntity> pageInfo = new Page<>(page, size); |
|||
IPage<CouponEntity> couponDOIPage = baseMapper.selectPage(pageInfo, |
|||
Wrappers.<CouponEntity>lambdaQuery().eq(CouponEntity::getPublish, CouponPublishEnum.PUBLISH) |
|||
.eq(CouponEntity::getCategory, CouponCategoryEnum.PROMOTION) |
|||
.orderByDesc(CouponEntity::getCreateTime)); |
|||
Map<String, Object> 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.<CouponEntity>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.<CouponRecordEntity>lambdaQuery().eq(CouponRecordEntity::getCouponId, coupon.getId()) |
|||
.eq(CouponRecordEntity::getUserId, userId)); |
|||
if (recordNum >= coupon.getUserLimit()) { |
|||
throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,36 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="cn.nla.coupon.mapper.CouponMapper"> |
|||
|
|||
<!-- 通用查询映射结果 --> |
|||
<resultMap id="BaseResultMap" type="cn.nla.coupon.model.entity.CouponEntity"> |
|||
<id column="id" property="id" /> |
|||
<result column="category" property="category" /> |
|||
<result column="publish" property="publish" /> |
|||
<result column="coupon_img" property="couponImg" /> |
|||
<result column="coupon_title" property="couponTitle" /> |
|||
<result column="price" property="price" /> |
|||
<result column="user_limit" property="userLimit" /> |
|||
<result column="start_time" property="startTime" /> |
|||
<result column="end_time" property="endTime" /> |
|||
<result column="publish_count" property="publishCount" /> |
|||
<result column="stock" property="stock" /> |
|||
<result column="create_time" property="createTime" /> |
|||
<result column="condition_price" property="conditionPrice" /> |
|||
</resultMap> |
|||
|
|||
<!-- 通用查询结果列 --> |
|||
<sql id="Base_Column_List"> |
|||
id, category, publish, coupon_img, coupon_title, price, user_limit, start_time, end_time, publish_count, stock, create_time, condition_price |
|||
</sql> |
|||
|
|||
<!--扣减库存--> |
|||
<update id="reduceStock"> |
|||
update coupon set stock=stock-1 where id = #{couponId} |
|||
</update> |
|||
|
|||
<!--扣减库存(加版本号,支持高并发下不超发问题)--> |
|||
<update id="reduceStockOpt"> |
|||
update coupon set stock=stock-1,version=version+1 where id = #{couponId} and stock>0 and version=#{oldVersion} |
|||
</update> |
|||
</mapper> |
@ -0,0 +1,26 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="cn.nla.coupon.mapper.CouponRecordMapper"> |
|||
|
|||
<!-- 通用查询映射结果 --> |
|||
<resultMap id="BaseResultMap" type="cn.nla.coupon.model.entity.CouponRecordEntity"> |
|||
<id column="id" property="id" /> |
|||
<result column="coupon_id" property="couponId" /> |
|||
<result column="create_time" property="createTime" /> |
|||
<result column="use_state" property="useState" /> |
|||
<result column="user_id" property="userId" /> |
|||
<result column="user_name" property="userName" /> |
|||
<result column="coupon_title" property="couponTitle" /> |
|||
<result column="start_time" property="startTime" /> |
|||
<result column="end_time" property="endTime" /> |
|||
<result column="order_id" property="orderId" /> |
|||
<result column="price" property="price" /> |
|||
<result column="condition_price" property="conditionPrice" /> |
|||
</resultMap> |
|||
|
|||
<!-- 通用查询结果列 --> |
|||
<sql id="Base_Column_List"> |
|||
id, coupon_id, create_time, use_state, user_id, user_name, coupon_title, start_time, end_time, order_id, price, condition_price |
|||
</sql> |
|||
|
|||
</mapper> |
Loading…
Reference in new issue