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
+
+
+