Ver Fonte

add 优惠卷代码

fix initial

fix

add 优惠卷接口

add 优惠卷接口

add 优惠卷服务端

add 优惠卷服务端

add 优惠卷服务端

fix 优惠卷
zoujiajian há 3 anos atrás
pai
commit
8be8f2c3a6
38 ficheiros alterados com 2559 adições e 11 exclusões
  1. 20 0
      netflix-dao/src/main/java/com/cyksj/mapper/manage/coupon/CouponActiveMapper.java
  2. 16 0
      netflix-dao/src/main/java/com/cyksj/mapper/manage/coupon/CouponDistributePopularizeMapper.java
  3. 15 0
      netflix-dao/src/main/java/com/cyksj/mapper/manage/coupon/CouponMapper.java
  4. 16 0
      netflix-dao/src/main/java/com/cyksj/mapper/manage/coupon/CouponRecommendMapper.java
  5. 17 0
      netflix-dao/src/main/java/com/cyksj/mapper/manage/coupon/CouponSkuMapper.java
  6. 20 0
      netflix-dao/src/main/java/com/cyksj/mapper/manage/coupon/CouponUserMapper.java
  7. 3 0
      netflix-dao/src/main/java/com/cyksj/model/dto/AuthRedirect.java
  8. 31 0
      netflix-dao/src/main/java/com/cyksj/model/dto/CouponPopularizeDto.java
  9. 138 0
      netflix-dao/src/main/java/com/cyksj/model/entity/Coupon.java
  10. 42 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CouponActive.java
  11. 30 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CouponDistributePopularize.java
  12. 25 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CouponRecommend.java
  13. 22 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CouponSku.java
  14. 79 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CouponUser.java
  15. 15 0
      netflix-dao/src/main/java/com/cyksj/model/entity/OrderDon.java
  16. 25 0
      netflix-dao/src/main/java/com/cyksj/model/request/CouponActiveReq.java
  17. 34 0
      netflix-dao/src/main/java/com/cyksj/model/request/CouponCollectReq.java
  18. 28 0
      netflix-dao/src/main/java/com/cyksj/model/request/CouponDistributeReq.java
  19. 10 0
      netflix-dao/src/main/java/com/cyksj/model/request/OrderPayRequest.java
  20. 161 0
      netflix-dao/src/main/java/com/cyksj/model/views/CouponActiveView.java
  21. 29 0
      netflix-dao/src/main/java/com/cyksj/model/views/CouponAvailableRecommendView.java
  22. 38 0
      netflix-dao/src/main/java/com/cyksj/model/views/CouponDistributePopularizeView.java
  23. 25 0
      netflix-dao/src/main/java/com/cyksj/model/views/CouponNewUserView.java
  24. 123 0
      netflix-dao/src/main/java/com/cyksj/model/views/CouponRecommendView.java
  25. 138 0
      netflix-dao/src/main/java/com/cyksj/model/views/CouponUserEnableView.java
  26. 144 0
      netflix-dao/src/main/java/com/cyksj/model/views/CouponUserView.java
  27. 104 0
      netflix-dao/src/main/java/com/cyksj/model/views/CouponView.java
  28. 17 0
      netflix-dao/src/main/resources/mapper/CouponMapper.xml
  29. 39 0
      netflix-dao/src/main/resources/mapper/CouponUserMapper.xml
  30. 1 0
      netflix-service/src/main/java/com/cyksj/redis/RedisService.java
  31. 46 0
      netflix-service/src/main/java/com/cyksj/service/coupon/CouponFontService.java
  32. 221 0
      netflix-service/src/main/java/com/cyksj/service/coupon/impl/CouponFontServiceImpl.java
  33. 56 0
      netflix-service/src/main/java/com/cyksj/service/mange/coupon/CouponCommonService.java
  34. 196 0
      netflix-service/src/main/java/com/cyksj/service/mange/coupon/impl/CouponCommonServiceImpl.java
  35. 1 1
      netflix-service/src/main/java/com/cyksj/service/order/OrderDonService.java
  36. 173 10
      netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java
  37. 189 0
      netflix-web/src/main/java/com/cyksj/web/controller/coupon/CouponFrontController.java
  38. 272 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/coupon/CouponController.java

+ 20 - 0
netflix-dao/src/main/java/com/cyksj/mapper/manage/coupon/CouponActiveMapper.java

@@ -0,0 +1,20 @@
+package com.cyksj.mapper.manage.coupon;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.CouponActive;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Update;
+
+/*
+ *项目名: netflix
+ *文件名: CouponActiveMapper
+ *创建者: JavaZou
+ *创建时间:2022/11/17 17:33
+ */
+public interface CouponActiveMapper extends BaseMapper<CouponActive> {
+	@Update("update coupon_active set deleted = false where coupon_id = #{id}")
+	void deleteByCouponId(Long id);
+
+	@Update("update coupon_active set remain_num = remain - 1 where id = #{id} and  remain_num = #{remainNum} and remain_num > 0")
+	Integer subRemainNum(@Param("id") Long id, @Param("remainNum") Integer remainNum);
+}

+ 16 - 0
netflix-dao/src/main/java/com/cyksj/mapper/manage/coupon/CouponDistributePopularizeMapper.java

@@ -0,0 +1,16 @@
+package com.cyksj.mapper.manage.coupon;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.CouponDistributePopularize;
+import org.apache.ibatis.annotations.Update;
+
+/*
+ *项目名: netflix
+ *文件名: CouponDistributePopularizeMapper
+ *创建者: JavaZou
+ *创建时间:2022/11/17 18:07
+ */
+public interface CouponDistributePopularizeMapper extends BaseMapper<CouponDistributePopularize> {
+	@Update("update coupon_distribute_popularize set deleted = false where coupon_id = #{id}")
+	void deleteByCouponId(Long id);
+}

+ 15 - 0
netflix-dao/src/main/java/com/cyksj/mapper/manage/coupon/CouponMapper.java

@@ -0,0 +1,15 @@
+package com.cyksj.mapper.manage.coupon;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.dto.CouponPopularizeDto;
+import com.cyksj.model.entity.Coupon;
+
+/*
+ *项目名: netflix
+ *文件名: CouponMapper
+ *创建者: JavaZou
+ *创建时间:2022/11/17 17:23
+ */
+public interface CouponMapper extends BaseMapper<Coupon> {
+	CouponPopularizeDto checkExCode(String couponExCode);
+}

+ 16 - 0
netflix-dao/src/main/java/com/cyksj/mapper/manage/coupon/CouponRecommendMapper.java

@@ -0,0 +1,16 @@
+package com.cyksj.mapper.manage.coupon;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.CouponRecommend;
+import org.apache.ibatis.annotations.Update;
+
+/*
+ *项目名: netflix
+ *文件名: CouponRecommendMapper
+ *创建者: JavaZou
+ *创建时间:2022/11/17 18:04
+ */
+public interface CouponRecommendMapper extends BaseMapper<CouponRecommend> {
+	@Update("update coupon_recommend set deleted = false where coupon_id = #{id}")
+	void deleteByCouponId(Long id);
+}

+ 17 - 0
netflix-dao/src/main/java/com/cyksj/mapper/manage/coupon/CouponSkuMapper.java

@@ -0,0 +1,17 @@
+package com.cyksj.mapper.manage.coupon;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.CouponSku;
+import org.apache.ibatis.annotations.Update;
+
+/*
+ *项目名: netflix
+ *文件名: CouponSkuMapper
+ *创建者: JavaZou
+ *创建时间:2022/11/21 12:10
+ */
+public interface CouponSkuMapper extends BaseMapper<CouponSku> {
+
+	@Update("update coupon_sku set deleted = false where coupon_id = #{id}")
+	void deleteByCid(Long id);
+}

+ 20 - 0
netflix-dao/src/main/java/com/cyksj/mapper/manage/coupon/CouponUserMapper.java

@@ -0,0 +1,20 @@
+package com.cyksj.mapper.manage.coupon;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.CouponUser;
+import com.cyksj.model.views.CouponUserView;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/*
+ *项目名: netflix
+ *文件名: CouponUserMapper
+ *创建者: JavaZou
+ *创建时间:2022/11/18 11:43
+ */
+public interface CouponUserMapper extends BaseMapper<CouponUser> {
+	List<CouponUserView> getAvailableCouponList(@Param("skuId") Long skuId, @Param("userId") Long userId);
+
+	Integer countAvailableCouponList(@Param("skuId") Long skuId, @Param("userId") Long userId);
+}

+ 3 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/AuthRedirect.java

@@ -16,6 +16,9 @@ public class AuthRedirect {
 
     private String authType;
 
+    /**
+     * 分销用户id
+     */
     private Long sharedId;
 
     /**

+ 31 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/CouponPopularizeDto.java

@@ -0,0 +1,31 @@
+package com.cyksj.model.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: CouponPopularizeDto
+ *创建者: JavaZou
+ *创建时间:2022/11/21 15:03
+ */
+@Getter
+@Setter
+public class CouponPopularizeDto {
+	/**
+	 * 优惠卷id
+	 */
+	private Long couponId;
+
+	private String skuIds;
+
+	/**
+	 * 优惠卷推广者关联id
+	 */
+	private Long popularizeId;
+
+	/**
+	 * 优惠卷推广者关联优惠卷id
+	 */
+	private Long popularizeCouponId;
+}

+ 138 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/Coupon.java

@@ -0,0 +1,138 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/*
+ *项目名: netflix
+ *文件名: Coupon
+ *创建者: JavaZou
+ *创建时间:2022/11/17 15:49
+ */
+@Getter
+@Setter
+public class Coupon extends BaseEntity {
+	/**
+	 * 优惠卷名称
+	 */
+	@NotEmpty(message = "优惠卷名称不能为空")
+	private String name;
+
+	/**
+	 * 优惠卷平台类型
+	 */
+	@NotNull(message = "优惠卷平台类型不能为空")
+	private GoodsType goodsType;
+
+	/**
+	 * 优惠卷类型
+	 */
+	@NotNull(message = "优惠卷类型不能为空")
+	private Type type;
+
+	/**
+	 * 抵扣金额
+	 */
+	private BigDecimal reduceMoney;
+
+	/**
+	 * 折扣
+	 */
+	private BigDecimal discount;
+
+	/**
+	 * 领取人范围
+	 */
+	@NotNull(message = "领取人不能为空")
+	private Scope scope;
+
+	/**
+	 * 平台规格集合
+	 */
+	private String skuIds;
+
+	/**
+	 * 是否是有效时间
+	 */
+	private Boolean isValidTime;
+
+	/**
+	 * 起始有效时间
+	 */
+	private Date validStartTime;
+
+	/**
+	 * 截止有效时间
+	 */
+	private Date validEndTime;
+
+	/**
+	 * 领取有效天数
+	 */
+	private Integer validDays;
+
+	/**
+	 * 是否可在续费时使用
+	 */
+	private Boolean isRenew;
+
+	/**
+	 * 兑换码
+	 */
+	private String exCode;
+
+	/**
+	 * 发放状态
+	 */
+	private Boolean sendStatus;
+
+	/**
+	 * 推荐状态
+	 */
+	private Boolean hotStatus;
+
+	@TableLogic(value = "1", delval = "0")
+	private Boolean deleted;
+
+	@Getter
+	public enum GoodsType {
+		realGoods("实物"),
+		virtualGoods("虚物"),
+		;
+		String type;
+
+		GoodsType(String type) {
+			this.type = type;
+		}
+	}
+
+	@Getter
+	public enum Type {
+		reduce("立减卷"),
+		discount("折扣卷"),
+		;
+		String desc;
+
+		Type(String desc) {
+			this.desc = desc;
+		}
+	}
+
+	@Getter
+	public enum Scope {
+		everyone("所有人"),
+		newUser("新用户"),
+		;
+		String desc;
+
+		Scope(String desc) {
+			this.desc = desc;
+		}
+	}
+}

+ 42 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CouponActive.java

@@ -0,0 +1,42 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: CouponActive
+ *创建者: JavaZou
+ *创建时间:2022/11/17 17:30
+ */
+@Getter
+@Setter
+public class CouponActive extends BaseEntity{
+	/**
+	 * 优惠卷id
+	 */
+	private Long couponId;
+	/**
+	 * 发放数量
+	 */
+	private Integer num;
+
+	/**
+	 * 剩余数量
+	 */
+	private Integer remainNum;
+
+	/**
+	 * 状态(默认启用)
+	 */
+	private Boolean status;
+
+	/**
+	 * 有效状态(默认有效)
+	 */
+	private Boolean expiryStatus;
+
+	@TableLogic(value = "1", delval = "0")
+	private Boolean deleted;
+}

+ 30 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CouponDistributePopularize.java

@@ -0,0 +1,30 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: CouponDistributePopularize
+ *创建者: JavaZou
+ *创建时间:2022/11/17 18:07
+ */
+@Getter
+@Setter
+public class CouponDistributePopularize extends BaseEntity {
+	private Long couponId;
+
+	/**
+	 * 固定推广者id
+	 */
+	private Long distributeId;
+
+	/**
+	 * 兑换码
+	 */
+	private String exCode;
+
+	@TableLogic(value = "1", delval = "0")
+	private Boolean deleted;
+}

+ 25 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CouponRecommend.java

@@ -0,0 +1,25 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: CouponRecommend
+ *创建者: JavaZou
+ *创建时间:2022/11/17 18:01
+ */
+@Getter
+@Setter
+public class CouponRecommend extends BaseEntity {
+	private Long couponId;
+
+	/**
+	 * 默认推荐
+	 */
+	private Boolean status;
+
+	@TableLogic(value = "1", delval = "0")
+	private Boolean deleted;
+}

+ 22 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CouponSku.java

@@ -0,0 +1,22 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: CouponSku
+ *创建者: JavaZou
+ *创建时间:2022/11/21 12:07
+ */
+@Getter
+@Setter
+public class CouponSku extends BaseEntity{
+	private Long couponId;
+
+	private Long skuId;
+
+	@TableLogic(value = "1", delval = "0")
+	private Boolean deleted;
+}

+ 79 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CouponUser.java

@@ -0,0 +1,79 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: CouponUser
+ *创建者: JavaZou
+ *创建时间:2022/11/18 11:43
+ */
+@Getter
+@Setter
+public class CouponUser extends BaseEntity {
+	private Long userId;
+
+	/**
+	 * 优惠卷id
+	 */
+	private Long couponId;
+
+	/**
+	 * 发放id
+	 */
+	private Long couponActiveId;
+
+	/**
+	 * 推荐id
+	 */
+	private Long recommendId;
+
+	/**
+	 * 关联推广id
+	 */
+	private Long popularizeId;
+
+	/**
+	 * 获取渠道
+	 */
+	private Channel channel;
+
+	/**
+	 * 优惠卷使用状态
+	 */
+	private Status status;
+
+	@Getter
+	public enum Channel {
+		recommend("recommend", "推荐渠道"),
+		center("unused", "领卷中心"),
+		exCode("exCode", "兑换码"),
+		;
+		private String channel;
+		private String desc;
+
+		Channel(String channel, String desc) {
+			this.channel = channel;
+			this.desc = desc;
+		}
+	}
+
+	@Getter
+	public enum Status {
+		used("used", "已使用"),
+		unused("unused", "未使用"),
+		expired("expired", "已过期"),
+		stop("stop", "未启用"),
+		invalid("invalid", "已失效"),
+		notEffective("notEffective", "未生效"),
+		;
+		private String status;
+		private String desc;
+
+		Status(String status, String desc) {
+			this.status = status;
+			this.desc = desc;
+		}
+	}
+}

+ 15 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/OrderDon.java

@@ -52,6 +52,21 @@ public class OrderDon extends BaseEntity implements Serializable {
      */
     private BigDecimal refundMoney;
 
+    /**
+     * 优惠金额
+     */
+    private BigDecimal realMoney;
+
+    /**
+     * 优惠金额
+     */
+    private BigDecimal couponMoney;
+
+    /**
+     * 优惠卷用户领取id
+     */
+    private Long couponUserId;
+
     /**
      * 购买数量
      */

+ 25 - 0
netflix-dao/src/main/java/com/cyksj/model/request/CouponActiveReq.java

@@ -0,0 +1,25 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+/*
+ *项目名: netflix
+ *文件名: CouponActiveReq
+ *创建者: JavaZou
+ *创建时间:2022/11/17 17:37
+ */
+@Getter
+@Setter
+public class CouponActiveReq {
+	@NotNull(message = "优惠卷id不能为空")
+	@Min(value = 1)
+	private Long couponId;
+
+	@NotNull(message = "发放数量不能为空")
+	@Min(value = 1)
+	private Integer num;
+}

+ 34 - 0
netflix-dao/src/main/java/com/cyksj/model/request/CouponCollectReq.java

@@ -0,0 +1,34 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+/*
+ *项目名: netflix
+ *文件名: CouponCollectReq
+ *创建者: JavaZou
+ *创建时间:2022/11/18 12:29
+ */
+@Getter
+@Setter
+public class CouponCollectReq {
+	/**
+	 * 优惠卷id
+	 */
+	@NotNull(message = "优惠卷id不能为空")
+	@Min(value = 1)
+	private Long couponId;
+
+	/**
+	 * 优惠卷发放id
+	 */
+	private Long couponActiveId;
+
+	/**
+	 * 优惠卷推荐id
+	 */
+	private Long couponRecommendId;
+}

+ 28 - 0
netflix-dao/src/main/java/com/cyksj/model/request/CouponDistributeReq.java

@@ -0,0 +1,28 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+/*
+ *项目名: netflix
+ *文件名: CouponDistributeReq
+ *创建者: JavaZou
+ *创建时间:2022/11/17 18:16
+ */
+@Getter
+@Setter
+public class CouponDistributeReq {
+	@NotNull(message = "固定推广者id不能为空")
+	@Min(value = 1)
+	private Long couponId;
+
+	/**
+	 * 固定推广者id
+	 */
+	@NotNull(message = "固定推广者id不能为空")
+	@Min(value = 1)
+	private Long distributeId;
+}

+ 10 - 0
netflix-dao/src/main/java/com/cyksj/model/request/OrderPayRequest.java

@@ -56,4 +56,14 @@ public class OrderPayRequest {
     private Long addressId;
 
     private String type;
+
+    /**
+     * 优惠卷id
+     */
+    private Long couponId;
+
+    /**
+     * 优惠卷兑换码
+     */
+    private String couponExCode;
 }

+ 161 - 0
netflix-dao/src/main/java/com/cyksj/model/views/CouponActiveView.java

@@ -0,0 +1,161 @@
+package com.cyksj.model.views;
+
+import com.cyksj.model.entity.Coupon;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/*
+ *项目名: netflix
+ *文件名: CouponActiveView
+ *创建者: JavaZou
+ *创建时间:2022/11/18 10:00
+ */
+@Getter
+@Setter
+@SearchBean(tables = "coupon_active ca left join coupon c on c.id = ca.coupon_id",
+		where = "ca.deleted is true and c.deleted is true")
+public class CouponActiveView {
+	@DbField("ca.id")
+	private Long couponActiveId;
+
+	/**
+	 * 优惠卷id
+	 */
+	@DbField("c.id")
+	private Long couponId;
+
+	/**
+	 * 优惠卷名称
+	 */
+	@DbField("c.name")
+	private String name;
+
+	/**
+	 * 优惠卷平台类型
+	 */
+	@DbField("c.goods_type")
+	private Coupon.GoodsType goodsType;
+
+	/**
+	 * 优惠卷类型
+	 */
+	@DbField("c.type")
+	private Coupon.Type type;
+
+	/**
+	 * 抵扣金额
+	 */
+	@DbField("c.reduce_money")
+	private BigDecimal reduceMoney;
+
+	/**
+	 * 折扣
+	 */
+	@DbField("c.discount")
+	private BigDecimal discount;
+
+	/**
+	 * 领取人范围
+	 */
+	@DbField("c.scope")
+	private Coupon.Scope scope;
+
+	/**
+	 * 平台规格集合
+	 */
+	@DbField("c.sku_ids")
+	private String skuIds;
+
+	/**
+	 * 是否是有效时间
+	 */
+	@DbField("c.is_valid_time")
+	private Boolean isValidTime;
+
+	/**
+	 * 起始有效时间
+	 */
+	@DbField("c.valid_start_time")
+	private Date validStartTime;
+
+	/**
+	 * 截止有效时间
+	 */
+	@DbField("c.valid_end_time")
+	private Date validEndTime;
+
+	/**
+	 * 领取有效天数
+	 */
+	@DbField("c.valid_days")
+	private Integer validDays;
+
+	/**
+	 * 是否可在续费时使用
+	 */
+	@DbField("c.is_renew")
+	private Boolean isRenew;
+
+	/**
+	 * 发放数量
+	 */
+	@DbField("ca.num")
+	private Integer num;
+
+	/**
+	 * 状态(默认启用)
+	 */
+	@DbField("ca.status")
+	private Boolean status;
+
+	/**
+	 * 有效状态(默认有效)
+	 */
+	@DbField("ca.expiry_status")
+	private Boolean expiryStatus;
+
+	/**
+	 * 剩余数量
+	 */
+	@DbField("ca.remain_num")
+	private Integer remainNum;
+
+	/**
+	 * 领取人数
+	 */
+	@DbIgnore
+	private Integer receiptNum;
+
+	/**
+	 * 使用人数
+	 */
+	@DbIgnore
+	private Integer usedNum;
+
+	@DbIgnore
+	private CouponStatus couponStatus;
+
+	@DbField("ca.created_time")
+	private Date createdTime;
+
+	@Getter
+	public enum CouponStatus {
+		collecting("collecting", "待领取"),
+		received("received", "已领取"),
+		over("over", "已领完"),
+		;
+		private String status;
+		private String desc;
+
+		CouponStatus(String status, String desc) {
+			this.status = status;
+			this.desc = desc;
+		}
+	}
+}

+ 29 - 0
netflix-dao/src/main/java/com/cyksj/model/views/CouponAvailableRecommendView.java

@@ -0,0 +1,29 @@
+package com.cyksj.model.views;
+
+import com.cyksj.model.entity.Coupon;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: CouponAvailableRecommenView
+ *创建者: JavaZou
+ *创建时间:2022/11/18 15:19
+ */
+@Setter
+@Getter
+@SearchBean(tables = "coupon_recommend cr left join coupon c on c.id = cr.coupon_id" +
+		" left join coupon_user cu on cu.coupon_id = c.id",
+		where = "cr.deleted is true and c.deleted is true")
+public class CouponAvailableRecommendView {
+	@DbField("cr.id")
+	private Long recommendId;
+
+	@DbField("c.id")
+	private Long couponId;
+
+	@DbField("c.scope")
+	private Coupon.Scope scope;
+}

+ 38 - 0
netflix-dao/src/main/java/com/cyksj/model/views/CouponDistributePopularizeView.java

@@ -0,0 +1,38 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: CouponDistributePopularizeView
+ *创建者: JavaZou
+ *创建时间:2022/11/18 10:37
+ */
+@Getter
+@Setter
+@SearchBean(tables = "coupon_distribute_popularize cp left join user_distribute ub on ub.id = cp.distribute_id" +
+		" left join user u on u.id = ub.user_id",
+		where = "cp.deleted is true")
+public class CouponDistributePopularizeView {
+	@DbField("cp.id")
+	private Long id;
+
+	@DbField("u.nickname")
+	private String nickname;
+
+	@DbField("u.headimgurl")
+	private String headImgUrl;
+
+	@DbField("u.id")
+	private Long userId;
+
+	@DbField("cp.ex_code")
+	private String exCode;
+
+	@DbIgnore
+	private Integer usedNum;
+}

+ 25 - 0
netflix-dao/src/main/java/com/cyksj/model/views/CouponNewUserView.java

@@ -0,0 +1,25 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: CouponNewUserView
+ *创建者: JavaZou
+ *创建时间:2022/11/18 13:44
+ */
+@Getter
+@Setter
+@SearchBean(tables = "order_don od left join goods_don_sku gdk on gdk.id = od.sku_id" +
+		" left join goods_don gd on gd.id = gdk.goods_id",
+		where = "gd.type = 1 and od.status not in ('close','noPayment')")
+public class CouponNewUserView {
+	@DbField("od.id")
+	private Long orderId;
+
+	@DbField("od.user_id")
+	private Long userId;
+}

+ 123 - 0
netflix-dao/src/main/java/com/cyksj/model/views/CouponRecommendView.java

@@ -0,0 +1,123 @@
+package com.cyksj.model.views;
+
+import com.cyksj.model.entity.Coupon;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/*
+ *项目名: netflix
+ *文件名: CouponRecommendView
+ *创建者: JavaZou
+ *创建时间:2022/11/18 10:07
+ */
+@Setter
+@Getter
+@SearchBean(tables = "coupon_recommend cr left join coupon c on c.id = cr.coupon_id",
+		where = "cr.deleted is true and c.deleted is true")
+public class CouponRecommendView {
+	@DbField("cr.id")
+	private Long id;
+
+	@DbField("cr.coupon_id")
+	private Long couponId;
+
+	/**
+	 * 优惠卷名称
+	 */
+	@DbField("c.name")
+	private String name;
+
+	/**
+	 * 优惠卷平台类型
+	 */
+	@DbField("c.goods_type")
+	private Coupon.GoodsType goodsType;
+
+	/**
+	 * 优惠卷类型
+	 */
+	@DbField("c.type")
+	private Coupon.Type type;
+
+	/**
+	 * 抵扣金额
+	 */
+	@DbField("c.reduce_money")
+	private BigDecimal reduceMoney;
+
+	/**
+	 * 折扣
+	 */
+	@DbField("c.discount")
+	private BigDecimal discount;
+
+	/**
+	 * 领取人范围
+	 */
+	@DbField("c.scope")
+	private Coupon.Scope scope;
+
+	/**
+	 * 是否是有效时间
+	 */
+	@DbField("c.is_valid_time")
+	private Boolean isValidTime;
+
+	/**
+	 * 起始有效时间
+	 */
+	@DbField("c.valid_start_time")
+	private Date validStartTime;
+
+	/**
+	 * 截止有效时间
+	 */
+	@DbField("c.valid_end_time")
+	private Date validEndTime;
+
+	/**
+	 * 领取有效天数
+	 */
+	@DbField("c.valid_days")
+	private Integer validDays;
+
+	/**
+	 * 是否可在续费时使用
+	 */
+	@DbField("c.is_renew")
+	private Boolean isRenew;
+
+	/**
+	 * 推荐状态
+	 */
+	@DbField("cr.status")
+	private Boolean status;
+
+	/**
+	 * 推荐时间
+	 */
+	@DbField("cr.created_time")
+	private Date createdTime;
+
+	/**
+	 * 推荐领取人数
+	 */
+	@DbIgnore
+	private Integer receiptNum;
+
+	/**
+	 * 使用人数
+	 */
+	@DbIgnore
+	private Integer usedNum;
+
+	@DbIgnore
+	private CouponActiveView.CouponStatus couponStatus;
+
+}

+ 138 - 0
netflix-dao/src/main/java/com/cyksj/model/views/CouponUserEnableView.java

@@ -0,0 +1,138 @@
+package com.cyksj.model.views;
+
+import com.cyksj.model.entity.Coupon;
+import com.cyksj.model.entity.CouponUser;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/*
+ *项目名: netflix
+ *文件名: CouponUserEnableView
+ *创建者: JavaZou
+ *创建时间:2022/11/21 10:33
+ */
+@Getter
+@Setter
+@SearchBean(tables = "coupon_user cu left join coupon_active ca on ca.id = cu.coupon_active_id" +
+		" left join coupon c on c.id = cu.coupon_id",
+		where = "order by cu.id desc")
+public class CouponUserEnableView {
+	@DbField("cu.id")
+	private Long id;
+
+	/**
+	 * 获取渠道
+	 */
+	@DbField("cu.channel")
+	private CouponUser.Channel channel;
+
+	/**
+	 * 优惠卷使用状态
+	 */
+	@DbField("cu.status")
+	private CouponUser.Status couponUserStatus;
+
+	/**
+	 * 用户id
+	 */
+	@DbField("cu.user_id")
+	private Long userId;
+
+	/**
+	 * 优惠卷id
+	 */
+	@DbField("c.id")
+	private Long couponId;
+
+	/**
+	 * 优惠卷发放id
+	 */
+	@DbField("ca.id")
+	private Long couponActiveId;
+
+	/**
+	 * 优惠卷名称
+	 */
+	@DbField("ca.name")
+	private String name;
+
+	/**
+	 * 优惠卷平台类型
+	 */
+	@DbField("ca.goods_type")
+	private Coupon.GoodsType goodsType;
+
+	/**
+	 * 优惠卷类型
+	 */
+	@DbField("ca.type")
+	private Coupon.Type type;
+
+	/**
+	 * 抵扣金额
+	 */
+	@DbField("ca.reduce_money")
+	private BigDecimal reduceMoney;
+
+	/**
+	 * 折扣
+	 */
+	@DbField("ca.discount")
+	private BigDecimal discount;
+
+	/**
+	 * 平台规格集合
+	 */
+	@DbField("ca.sku_ids")
+	private String skuIds;
+
+	/**
+	 * 是否是有效时间
+	 */
+	@DbField("ca.is_valid_time")
+	private Boolean isValidTime;
+
+	/**
+	 * 起始有效时间
+	 */
+	@DbField("ca.valid_start_time")
+	private Date validStartTime;
+
+	/**
+	 * 领取有效天数
+	 */
+	@DbField("ca.valid_days")
+	private Integer validDays;
+
+	/**
+	 * 截止有效时间
+	 */
+	@DbField("ca.valid_end_time")
+	private Date validEndTime;
+
+	/**
+	 * 是否可在续费时使用
+	 */
+	@DbField("ca.is_renew")
+	private Boolean isRenew;
+
+	/**
+	 * 发放优惠卷状态(默认启用)
+	 */
+	@DbField("ca.status")
+	private Boolean status;
+
+	/**
+	 * 有效状态(默认有效)
+	 */
+	@DbField("ca.expiry_status")
+	private Boolean expiryStatus;
+
+	@DbField("ca.created_time")
+	private Date createdTime;
+}

+ 144 - 0
netflix-dao/src/main/java/com/cyksj/model/views/CouponUserView.java

@@ -0,0 +1,144 @@
+package com.cyksj.model.views;
+
+import com.cyksj.model.entity.Coupon;
+import com.cyksj.model.entity.CouponUser;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/*
+ *项目名: netflix
+ *文件名: CouponUserView
+ *创建者: JavaZou
+ *创建时间:2022/11/18 12:12
+ */
+@Getter
+@Setter
+@SearchBean(tables = "coupon_user cu left join coupon_active ca on ca.id = cu.coupon_active_id" +
+		" left join coupon c on c.id = cu.coupon_id")
+public class CouponUserView {
+	@DbField("cu.id")
+	private Long id;
+
+	/**
+	 * 获取渠道
+	 */
+	@DbField("cu.channel")
+	private CouponUser.Channel channel;
+
+	/**
+	 * 优惠卷使用状态
+	 */
+	@DbField("cu.status")
+	private CouponUser.Status couponUserStatus;
+
+	/**
+	 * 用户id
+	 */
+	@DbField("cu.user_id")
+	private Long userId;
+
+	/**
+	 * 优惠卷id
+	 */
+	@DbField("c.id")
+	private Long couponId;
+
+	/**
+	 * 优惠卷发放id
+	 */
+	@DbField("ca.id")
+	private Long couponActiveId;
+
+	/**
+	 * 优惠卷名称
+	 */
+	@DbField("c.name")
+	private String name;
+
+	/**
+	 * 优惠卷平台类型
+	 */
+	@DbField("c.goods_type")
+	private Coupon.GoodsType goodsType;
+
+	/**
+	 * 优惠卷类型
+	 */
+	@DbField("c.type")
+	private Coupon.Type type;
+
+	/**
+	 * 抵扣金额
+	 */
+	@DbField("c.reduce_money")
+	private BigDecimal reduceMoney;
+
+	/**
+	 * 折扣
+	 */
+	@DbField("c.discount")
+	private BigDecimal discount;
+
+	/**
+	 * 平台规格集合
+	 */
+	@DbField("c.sku_ids")
+	private String skuIds;
+
+	/**
+	 * 是否是有效时间
+	 */
+	@DbField("c.is_valid_time")
+	private Boolean isValidTime;
+
+	/**
+	 * 起始有效时间
+	 */
+	@DbField("c.valid_start_time")
+	private Date validStartTime;
+
+	/**
+	 * 领取有效天数
+	 */
+	@DbField("c.valid_days")
+	private Integer validDays;
+
+	/**
+	 * 截止有效时间
+	 */
+	@DbField("c.valid_end_time")
+	private Date validEndTime;
+
+	/**
+	 * 是否可在续费时使用
+	 */
+	@DbField("c.is_renew")
+	private Boolean isRenew;
+
+	/**
+	 * 发放优惠卷状态(默认启用)
+	 */
+	@DbField("ca.status")
+	private Boolean status;
+
+	/**
+	 * 有效状态(默认有效)
+	 */
+	@DbField("ca.expiry_status")
+	private Boolean expiryStatus;
+
+	/**
+	 * 0不可用/1可用
+	 */
+	@DbIgnore
+	private Boolean isAvailable;
+
+	@DbField("cu.created_time")
+	private Date createdTime;
+}

+ 104 - 0
netflix-dao/src/main/java/com/cyksj/model/views/CouponView.java

@@ -0,0 +1,104 @@
+package com.cyksj.model.views;
+
+import com.cyksj.model.entity.Coupon;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/*
+ *项目名: netflix
+ *文件名: CouponView
+ *创建者: JavaZou
+ *创建时间:2022/11/17 18:32
+ */
+@Getter
+@Setter
+@SearchBean(tables = "coupon c",
+		where = "c.deleted is true")
+public class CouponView {
+	private Long id;
+
+	/**
+	 * 优惠卷名称
+	 */
+	private String name;
+
+	/**
+	 * 优惠卷平台类型
+	 */
+	private Coupon.GoodsType goodsType;
+
+	/**
+	 * 优惠卷类型
+	 */
+	private Coupon.Type type;
+
+	/**
+	 * 抵扣金额
+	 */
+	private BigDecimal reduceMoney;
+
+	/**
+	 * 折扣
+	 */
+	private BigDecimal discount;
+
+	/**
+	 * 领取人范围
+	 */
+	private Coupon.Scope scope;
+
+	/**
+	 * 平台规格集合
+	 */
+	private String skuIds;
+
+	/**
+	 * 是否是有效时间
+	 */
+	private Boolean isValidTime;
+
+	/**
+	 * 起始有效时间
+	 */
+	private Date validStartTime;
+
+	/**
+	 * 截止有效时间
+	 */
+	private Date validEndTime;
+
+	/**
+	 * 领取有效天数
+	 */
+	private Integer validDays;
+
+	/**
+	 * 是否可在续费时使用
+	 */
+	private Boolean isRenew;
+
+	/**
+	 * 兑换码
+	 */
+	private String exCode;
+
+	/**
+	 * 发放状态
+	 */
+	private Boolean sendStatus;
+
+	/**
+	 * 推荐状态
+	 */
+	private Boolean hotStatus;
+
+	@DbIgnore
+	private Integer relationNum;
+
+	private Date createdTime;
+}

+ 17 - 0
netflix-dao/src/main/resources/mapper/CouponMapper.xml

@@ -0,0 +1,17 @@
+<?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="com.cyksj.mapper.manage.coupon.CouponMapper">
+
+    <select id="checkExCode" resultType="com.cyksj.model.dto.CouponPopularizeDto">
+        select cp.id as couponId,
+               cdp.id as popularizeId,
+               cp.sku_ids,
+               cdp.coupon_id as popularizeCouponId
+        from `coupon` cp
+                 left join `coupon_distribute_popularize` cdp on cdp.coupon_id = cp.id
+        where cp.deleted is true
+          and cdp.deleted is true
+          and (cp.ex_code = #{couponExCode} or cdp.ex_code = #{couponExCode}) limit 1
+    </select>
+</mapper>

+ 39 - 0
netflix-dao/src/main/resources/mapper/CouponUserMapper.xml

@@ -0,0 +1,39 @@
+<?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="com.cyksj.mapper.manage.coupon.CouponUserMapper">
+
+    <select id="getAvailableCouponList" resultType="com.cyksj.model.views.CouponUserView">
+        select distinct cu.id,
+                        cp.name,
+                        cp.goods_type,
+                        cp.type,
+                        cp.sku_ids,
+                        cp.discount,
+                        cp.reduce_money,
+                        cp.is_valid_time,
+                        cp.valid_start_time,
+                        cp.valid_end_time,
+                        cp.valid_days,
+                        cu.created_time,
+                        if(ck.id is null, 0, 1) as 'isAvailable'
+        from `coupon_user` cu
+                 left join `coupon` cp on cp.id = cu.coupon_id
+                 left join `coupon_sku` ck on ck.coupon_id = cp.id
+        where ck.id = #{skuId}
+          and cu.user_id = #{userId}
+          and cu.status = 'unused'
+        group by cu.id, cp.name, cp.goods_type, cp.type, cp.sku_ids, cp.discount, cp.reduce_money, cp.is_valid_time,
+                 cp.valid_start_time, cp.valid_end_time, cp.valid_days, cu.created_time
+        order by isAvailable desc
+    </select>
+
+    <select id="countAvailableCouponList" resultType="java.lang.Integer">
+        select count(distinct cu.id)
+        from `coupon_user` cu
+                 left join `coupon` cp on cp.id = cu.coupon_id
+                 left join `coupon_sku` ck on ck.coupon_id = cp.id
+        where ck.id = #{skuId} and cu.user_id = #{userId}
+          and cu.status = 'unused'
+    </select>
+</mapper>

+ 1 - 0
netflix-service/src/main/java/com/cyksj/redis/RedisService.java

@@ -591,6 +591,7 @@ public class RedisService {
 	    CORP_AGENT_JS_TICKET_KEY("corp_wechat:js_ticket:agent:", "js_ticket", 60 * 30L),
 	    MAPPING_INDEX_KEY("mapper:index:", "授权跳转", 60 * 10L),
         WX_LOGIN_AUTH_URI("wx_login_auth_uri:%s", "微信网页授权登录uri", 60 * 60 * 24L),
+        RECOMMEND_COUPON_DAY("recommend_coupon_day:%s", "推荐优惠卷弹窗", 60 * 60 * 24L)
         ;
 
         private String name;

+ 46 - 0
netflix-service/src/main/java/com/cyksj/service/coupon/CouponFontService.java

@@ -0,0 +1,46 @@
+package com.cyksj.service.coupon;
+
+import cn.hutool.core.date.DateTime;
+import com.cyksj.model.request.CouponCollectReq;
+import com.cyksj.model.views.CouponUserView;
+import com.ejlchina.searcher.SearchResult;
+
+import java.util.List;
+
+/*
+ *项目名: netflix
+ *文件名: CouponFontService
+ *创建者: JavaZou
+ *创建时间:2022/11/18 13:34
+ */
+public interface CouponFontService {
+	/**
+	 * 领取优惠卷
+	 */
+	void collectCoupon(CouponCollectReq couponCollectReq, Long userId);
+
+	/**
+	 * 领取推荐优惠卷
+	 */
+	void collectRecommendCoupon(CouponCollectReq couponCollectReq, Long userId);
+
+	/**
+	 * 一键领取推荐优惠卷
+	 */
+	void collectWholeRecommendCoupon(Long userId);
+
+	/**
+	 * 处理优惠卷状态
+	 */
+	void handleCouponStatus(SearchResult<CouponUserView> search);
+
+	/**
+	 * 可用/不可用优惠卷
+	 */
+	List<CouponUserView> getAvailableCouponList(Long skuId, Long userId);
+
+	/**
+	 * 优惠卷状态
+	 */
+	void setCouponUserStatus(CouponUserView data, DateTime now);
+}

+ 221 - 0
netflix-service/src/main/java/com/cyksj/service/coupon/impl/CouponFontServiceImpl.java

@@ -0,0 +1,221 @@
+package com.cyksj.service.coupon.impl;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.annotation.NoSubmit;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
+import com.cyksj.mapper.manage.coupon.CouponActiveMapper;
+import com.cyksj.mapper.manage.coupon.CouponMapper;
+import com.cyksj.mapper.manage.coupon.CouponUserMapper;
+import com.cyksj.model.entity.Coupon;
+import com.cyksj.model.entity.CouponActive;
+import com.cyksj.model.entity.CouponUser;
+import com.cyksj.model.request.CouponCollectReq;
+import com.cyksj.model.views.CouponAvailableRecommendView;
+import com.cyksj.model.views.CouponNewUserView;
+import com.cyksj.model.views.CouponUserView;
+import com.cyksj.service.coupon.CouponFontService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.param.Operator;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Optional;
+
+/*
+ *项目名: netflix
+ *文件名: CouponFontServiceImpl
+ *创建者: JavaZou
+ *创建时间:2022/11/18 13:34
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class CouponFontServiceImpl implements CouponFontService {
+	private final CouponMapper couponMapper;
+
+	private final CouponActiveMapper couponActiveMapper;
+
+	private final CouponUserMapper couponUserMapper;
+
+	private final BeanSearcher beanSearcher;
+
+	private static final GlobalThreadPoolTaskExecutor TASK_EXECUTOR = GlobalThreadPoolTaskExecutor.getInstance();
+
+	@Override
+	@Transactional(rollbackFor = Throwable.class)
+	@NoSubmit
+	public void collectCoupon(CouponCollectReq couponCollectReq, Long userId) {
+		collectCommonCheck(couponCollectReq, userId);
+		CouponActive couponActive = couponActiveMapper.selectById(couponCollectReq.getCouponActiveId());
+		if (couponActive == null) {
+			throw BusinessRuntimeException.getInstance("该发放优惠卷不存在");
+		}
+		if (couponActive.getRemainNum() <= 0) {
+			throw BusinessRuntimeException.getInstance("该优惠卷已领取完");
+		}
+		CouponUser couponUser = new CouponUser();
+		couponUser.setUserId(userId);
+		couponUser.setCouponId(couponCollectReq.getCouponId());
+		//领卷中心
+		couponUser.setChannel(CouponUser.Channel.center);
+		couponUser.setStatus(CouponUser.Status.unused);
+		couponUser.setCouponActiveId(couponCollectReq.getCouponActiveId());
+		try {
+			couponUserMapper.insert(couponUser);
+		} catch (DuplicateKeyException e) {
+			log.info("用户:{}重复领取优惠卷{}插入数据错误", userId, couponCollectReq.getCouponId());
+			throw BusinessRuntimeException.getInstance("已领取成功");
+		}
+		if (couponActive != null) {
+			//发放优惠卷记录剩余数量
+			Integer remainNum = couponActive.getRemainNum();
+			Integer update = couponActiveMapper.subRemainNum(couponActive.getId(), remainNum);
+			if (update != 1) {
+				throw BusinessRuntimeException.getInstance("系统繁忙,请稍后再试..");
+			}
+		}
+	}
+
+	@Override
+	@Transactional(rollbackFor = Throwable.class)
+	@NoSubmit
+	@Deprecated
+	public void collectRecommendCoupon(CouponCollectReq couponCollectReq, Long userId) {
+		collectCommonCheck(couponCollectReq, userId);
+		CouponUser couponUser = new CouponUser();
+		couponUser.setUserId(userId);
+		couponUser.setCouponId(couponCollectReq.getCouponId());
+		//推荐渠道
+		couponUser.setChannel(CouponUser.Channel.recommend);
+		couponUser.setStatus(CouponUser.Status.unused);
+		try {
+			couponUserMapper.insert(couponUser);
+		} catch (DuplicateKeyException e) {
+			log.info("用户:{}重复领取推荐优惠卷{}插入数据错误", userId, couponCollectReq.getCouponId());
+			throw BusinessRuntimeException.getInstance("已领取成功");
+		}
+	}
+
+	@Override
+	@Transactional(rollbackFor = Throwable.class)
+	@NoSubmit
+	public void collectWholeRecommendCoupon(Long userId) {
+		CouponNewUserView couponNewUserView = beanSearcher.searchFirst(CouponNewUserView.class, MapUtils.builder().field(CouponNewUserView::getUserId, userId).op(Operator.Equal).build());
+		//查询所有可领取的推荐优惠卷
+		List<CouponAvailableRecommendView> couponAvailableRecommendViews = beanSearcher.searchAll(CouponAvailableRecommendView.class, null);
+		couponAvailableRecommendViews.stream().filter(data->{
+			if (data.getScope() == Coupon.Scope.newUser && couponNewUserView != null) {
+				return false;
+			}
+			return true;
+		}).forEach(data -> {
+			//领取优惠卷
+			CouponUser couponUser = new CouponUser();
+			couponUser.setUserId(userId);
+			couponUser.setCouponId(data.getCouponId());
+			//推荐渠道
+			couponUser.setChannel(CouponUser.Channel.recommend);
+			couponUser.setStatus(CouponUser.Status.unused);
+			try {
+				couponUserMapper.insert(couponUser);
+			} catch (DuplicateKeyException e) {
+				//插入失败继续插入 不影响
+				log.info("用户:{}一键领取推荐优惠卷{}插入数据错误", userId, data.getCouponId());
+			}
+		});
+	}
+
+	@Override
+	public void handleCouponStatus(SearchResult<CouponUserView> search) {
+		DateTime now = DateTime.now();
+		search.getDataList().forEach(data -> {
+			if (!data.getIsValidTime()) {
+				data.setValidStartTime(data.getCreatedTime());
+				data.setValidEndTime(DateUtil.offsetDay(data.getValidStartTime(), data.getValidDays()));
+			}
+			this.setCouponUserStatus(data, now);
+		});
+	}
+
+	@Override
+	public List<CouponUserView> getAvailableCouponList(Long skuId, Long userId) {
+		DateTime now = DateTime.now();
+		List<CouponUserView> availableCouponList = couponUserMapper.getAvailableCouponList(skuId, userId);
+		Optional.ofNullable(availableCouponList).ifPresent(list->{
+			list.stream().forEach(data->{
+				if (!data.getIsValidTime()) {
+					data.setValidStartTime(data.getCreatedTime());
+					data.setValidEndTime(DateUtil.offsetDay(data.getValidStartTime(), data.getValidDays()));
+				}
+				this.setCouponUserStatus(data, now);
+			});
+		});
+		return availableCouponList;
+	}
+
+	/**
+	 * 设置用户优惠卷状态
+	 */
+	@Override
+	public void setCouponUserStatus(CouponUserView data, DateTime now) {
+		Boolean isFlag = false;
+		if (data.getCouponUserStatus() == CouponUser.Status.unused) {
+			if (data.getExpiryStatus() != null && !data.getExpiryStatus()) {
+				data.setCouponUserStatus(CouponUser.Status.invalid);
+				data.setIsAvailable(false);
+				isFlag = true;
+			}else if (data.getExpiryStatus() != null && !data.getStatus()) {
+				data.setCouponUserStatus(CouponUser.Status.stop);
+				data.setIsAvailable(false);
+				isFlag = true;
+			} else if (now.isAfter(data.getValidEndTime())) {
+				data.setCouponUserStatus(CouponUser.Status.expired);
+				data.setIsAvailable(false);
+				isFlag = true;
+			} else if (now.isBefore(data.getValidStartTime())) {
+				data.setCouponUserStatus(CouponUser.Status.notEffective);
+				data.setIsAvailable(false);
+				isFlag = true;
+			}
+		}
+		if (isFlag) {
+			TASK_EXECUTOR.execute(() -> {
+				CouponUser couponUser = new CouponUser();
+				couponUser.setId(data.getId());
+				couponUser.setStatus(data.getCouponUserStatus());
+				couponUserMapper.updateById(couponUser);
+			});
+		}
+	}
+
+	public void collectCommonCheck(CouponCollectReq couponCollectReq, Long userId) {
+		Coupon coupon = couponMapper.selectById(couponCollectReq.getCouponId());
+		if (coupon == null || !coupon.getDeleted()) {
+			throw BusinessRuntimeException.getInstance("该优惠卷不存在");
+		}
+		//是否可领取
+		if (coupon.getScope() == Coupon.Scope.newUser) {
+			//注册后从没产生过虚拟订单的用户,产生过退款后也不是新用户
+			CouponNewUserView couponNewUserView = beanSearcher.searchFirst(CouponNewUserView.class, MapUtils.builder().field(CouponNewUserView::getUserId, userId).op(Operator.Equal).build());
+			if (couponNewUserView != null) {
+				throw BusinessRuntimeException.getInstance("该优惠卷只能新用户才能领取");
+			}
+		}
+		CouponUser couponUser = couponUserMapper.selectOne(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getCouponId, couponCollectReq.getCouponId()));
+		if (couponUser != null) {
+			if (couponUser.getChannel() == CouponUser.Channel.exCode) {
+				throw BusinessRuntimeException.getInstance("您已兑换过该优惠卷..");
+			}
+			throw BusinessRuntimeException.getInstance("您已领取该优惠卷..");
+		}
+	}
+}

+ 56 - 0
netflix-service/src/main/java/com/cyksj/service/mange/coupon/CouponCommonService.java

@@ -0,0 +1,56 @@
+package com.cyksj.service.mange.coupon;
+
+import com.cyksj.model.entity.Coupon;
+import com.cyksj.model.entity.CouponDistributePopularize;
+import com.cyksj.model.request.CouponActiveReq;
+import com.cyksj.model.request.CouponDistributeReq;
+
+/*
+ *项目名: netflix
+ *文件名: CouponCommonService
+ *创建者: JavaZou
+ *创建时间:2022/11/17 16:59
+ */
+public interface CouponCommonService {
+	/**
+	 * 新增优惠卷
+	 *
+	 * @param coupon
+	 */
+	void saveCoupon(Coupon coupon) throws Exception;
+
+	/**
+	 * 更新优惠卷
+	 *
+	 * @param coupon
+	 */
+	void updateCoupon(Coupon coupon) throws Exception;
+
+	/**
+	 * 发放优惠卷
+	 *
+	 * @param couponActiveReq
+	 */
+	void sendCoupon(CouponActiveReq couponActiveReq);
+
+	/**
+	 * 设置为推荐优惠卷
+	 *
+	 * @param couponId
+	 */
+	void setHotCoupon(Long couponId);
+
+	/**
+	 * 关联推广者
+	 *
+	 * @param couponDistributeReq
+	 */
+	void setPopularizeRelation(CouponDistributeReq couponDistributeReq);
+
+	/**
+	 * 设置推广者优惠卷兑换码
+	 *
+	 * @param couponDistributePopularize
+	 */
+	void setPopularizeRelationCode(CouponDistributePopularize couponDistributePopularize);
+}

+ 196 - 0
netflix-service/src/main/java/com/cyksj/service/mange/coupon/impl/CouponCommonServiceImpl.java

@@ -0,0 +1,196 @@
+package com.cyksj.service.mange.coupon.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.util.Jsons;
+import com.cyksj.mapper.GoodsDonSkuMapper;
+import com.cyksj.mapper.manage.coupon.*;
+import com.cyksj.model.entity.*;
+import com.cyksj.model.request.CouponActiveReq;
+import com.cyksj.model.request.CouponDistributeReq;
+import com.cyksj.service.mange.coupon.CouponCommonService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.Set;
+
+/*
+ *项目名: netflix
+ *文件名: CouponCommonServiceImpl
+ *创建者: JavaZou
+ *创建时间:2022/11/17 17:00
+ */
+@Service
+@RequiredArgsConstructor
+public class CouponCommonServiceImpl implements CouponCommonService {
+	private final GoodsDonSkuMapper goodsDonSkuMapper;
+
+	private final CouponMapper couponMapper;
+
+	private final CouponActiveMapper couponActiveMapper;
+
+	private final CouponRecommendMapper couponRecommendMapper;
+
+	private final CouponDistributePopularizeMapper couponDistributePopularizeMapper;
+
+	private final CouponSkuMapper couponSkuMapper;
+
+	@Override
+	@Transactional(rollbackFor = Throwable.class)
+	public void saveCoupon(Coupon coupon) throws Exception {
+		checkCouponParamsAndSaveOrUpdate(coupon);
+	}
+
+	@Override
+	public void updateCoupon(Coupon coupon) throws Exception {
+		if (coupon.getId() == null || coupon.getId() < 1) {
+			throw BusinessRuntimeException.getInstance("请输入正确的优惠卷id");
+		}
+		Coupon entity = couponMapper.selectById(coupon.getId());
+		if (entity == null || !entity.getDeleted()) {
+			throw BusinessRuntimeException.getInstance("该优惠卷不存在");
+		}
+		checkCouponParamsAndSaveOrUpdate(coupon);
+	}
+
+	@Override
+	public void sendCoupon(CouponActiveReq couponActiveReq) {
+		Coupon coupon = couponMapper.selectById(couponActiveReq.getCouponId());
+		if (coupon == null || !coupon.getDeleted()) {
+			throw BusinessRuntimeException.getInstance("该优惠卷不存在");
+		}
+		if (!coupon.getSendStatus()) {
+			coupon.setSendStatus(!coupon.getSendStatus());
+			couponMapper.updateById(coupon);
+		}
+		CouponActive couponActive = new CouponActive();
+		couponActive.setCouponId(coupon.getId());
+		couponActive.setNum(couponActiveReq.getNum());
+		//发放优惠剩余数量
+		couponActive.setRemainNum(couponActiveReq.getNum());
+		couponActiveMapper.insert(couponActive);
+	}
+
+	@Override
+	public void setHotCoupon(Long couponId) {
+		Coupon coupon = couponMapper.selectById(couponId);
+		if (coupon == null || !coupon.getDeleted()) {
+			throw BusinessRuntimeException.getInstance("该优惠卷不存在");
+		}
+		if (!coupon.getHotStatus()) {
+			coupon.setHotStatus(!coupon.getHotStatus());
+		}
+		CouponRecommend couponRecommend = new CouponRecommend();
+		couponRecommend.setCouponId(coupon.getId());
+		couponRecommendMapper.insert(couponRecommend);
+		couponMapper.updateById(coupon);
+	}
+
+	@Override
+	public void setPopularizeRelation(CouponDistributeReq couponDistributeReq) {
+		Coupon coupon = couponMapper.selectById(couponDistributeReq.getCouponId());
+		if (coupon == null || !coupon.getDeleted()) {
+			throw BusinessRuntimeException.getInstance("该优惠卷不存在");
+		}
+		CouponDistributePopularize exists = couponDistributePopularizeMapper.selectOne(Wrappers.lambdaQuery(CouponDistributePopularize.class).eq(CouponDistributePopularize::getCouponId, coupon.getId()).eq(CouponDistributePopularize::getDistributeId, couponDistributeReq.getDistributeId()).eq(CouponDistributePopularize::getDeleted, true).last("limit 1"));
+		if (exists != null) {
+			throw BusinessRuntimeException.getInstance("该优惠卷已关联该固定推广者");
+		}
+		CouponDistributePopularize couponDistributePopularize = new CouponDistributePopularize();
+		couponDistributePopularize.setCouponId(coupon.getId());
+		couponDistributePopularize.setDistributeId(couponDistributeReq.getDistributeId());
+		couponDistributePopularizeMapper.insert(couponDistributePopularize);
+	}
+
+	@Override
+	public void setPopularizeRelationCode(CouponDistributePopularize couponDistributePopularize) {
+		if (couponDistributePopularize.getId() == null || couponDistributePopularize.getId() < 1) {
+			throw BusinessRuntimeException.getInstance("优惠卷推广者关联id为空");
+		}
+		CouponDistributePopularize entity = couponDistributePopularizeMapper.selectById(couponDistributePopularize.getId());
+		if (entity == null) {
+			throw BusinessRuntimeException.getInstance("该优惠卷推广关联记录不存在");
+		}
+		if (StrUtil.isEmpty(couponDistributePopularize.getExCode())) {
+			throw BusinessRuntimeException.getInstance("优惠卷兑换码为空");
+		}
+		entity.setExCode(couponDistributePopularize.getExCode());
+		CouponDistributePopularize exists = couponDistributePopularizeMapper.selectOne(Wrappers.lambdaQuery(CouponDistributePopularize.class).eq(CouponDistributePopularize::getExCode, couponDistributePopularize.getExCode()).eq(CouponDistributePopularize::getDeleted, true).ne(CouponDistributePopularize::getId, entity.getId()).select(CouponDistributePopularize::getId).last("limit 1"));
+		if (exists != null) {
+			throw BusinessRuntimeException.getInstance("推荐官兑换码不能重复");
+		} else {
+			Coupon coupon = couponMapper.selectOne(Wrappers.lambdaQuery(Coupon.class).eq(Coupon::getExCode, couponDistributePopularize.getExCode()).eq(Coupon::getDeleted, true).select(Coupon::getId).last("limit 1"));
+			if (coupon != null) {
+				throw BusinessRuntimeException.getInstance("推荐官兑换码与优惠卷兑换码设置重复");
+			}
+		}
+		couponDistributePopularizeMapper.updateById(entity);
+	}
+
+
+	public void checkCouponParamsAndSaveOrUpdate(Coupon coupon) throws Exception {
+		Set<Integer> skuIds = null;
+		if (coupon.getType() == Coupon.Type.discount) {
+			if (coupon.getDiscount() == null || (coupon.getDiscount().compareTo(BigDecimal.ZERO) < 0 && coupon.getDiscount().compareTo(BigDecimal.ZERO) > 0)) {
+				throw BusinessRuntimeException.getInstance("请输入正确的折扣");
+			}
+		}
+		if (coupon.getType() == Coupon.Type.reduce) {
+			if (StrUtil.isEmpty(coupon.getSkuIds())) {
+				throw BusinessRuntimeException.getInstance("立减优惠卷,请配置其平台及规格");
+			}
+			if (coupon.getReduceMoney() == null) {
+				throw BusinessRuntimeException.getInstance("请配置其立减金额");
+			}
+			skuIds = Jsons.parseObject(coupon.getSkuIds(), Set.class);
+			GoodsDonSku sku = goodsDonSkuMapper.selectOne(Wrappers.lambdaQuery(GoodsDonSku.class).in(GoodsDonSku::getId, skuIds).orderByAsc(GoodsDonSku::getPrice).select(GoodsDonSku::getPrice).last("limit 1"));
+			if (sku == null) {
+				throw BusinessRuntimeException.getInstance("所选规格不存在");
+			}
+			if (coupon.getReduceMoney().compareTo(sku.getPrice()) > 0) {
+				throw BusinessRuntimeException.getInstance("可减金额必须小于规格金额");
+			}
+			if (coupon.getIsValidTime()) {
+				if (coupon.getValidStartTime() == null || coupon.getValidEndTime() == null || (coupon.getValidStartTime().getTime() > coupon.getValidEndTime().getTime())) {
+					throw BusinessRuntimeException.getInstance("请输入正确的有效起始和截止时间");
+				}
+			} else {
+				if (coupon.getValidDays() == null || coupon.getValidDays() < 0) {
+					throw BusinessRuntimeException.getInstance("请输入正确的有效天数");
+				}
+			}
+		}
+		if (StrUtil.isNotEmpty(coupon.getExCode())) {
+			Coupon exists = couponMapper.selectOne(Wrappers.lambdaQuery(Coupon.class).eq(Coupon::getExCode, coupon.getExCode()).eq(Coupon::getDeleted, true).ne(coupon.getId() != null, Coupon::getId, coupon.getId()).select(Coupon::getId).last("limit 1"));
+			if (exists != null) {
+				throw BusinessRuntimeException.getInstance("优惠卷兑换码重复");
+			} else {
+				CouponDistributePopularize couponDistributePopularize = couponDistributePopularizeMapper.selectOne(Wrappers.lambdaQuery(CouponDistributePopularize.class).eq(CouponDistributePopularize::getExCode, coupon.getExCode()).eq(CouponDistributePopularize::getDeleted, true).select(CouponDistributePopularize::getId).last("limit 1"));
+				if (couponDistributePopularize != null) {
+					throw BusinessRuntimeException.getInstance("优惠卷兑换码与推荐管兑换码设置重复");
+				}
+			}
+		}
+		if (coupon.getId() == null) {
+			couponMapper.insert(coupon);
+			skuIds.forEach(skuId->{
+				CouponSku couponSku = new CouponSku();
+				couponSku.setCouponId(coupon.getId());
+				couponSku.setSkuId(Long.valueOf(skuId));
+				couponSkuMapper.insert(couponSku);
+			});
+		} else {
+			couponMapper.updateById(coupon);
+			couponSkuMapper.delete(Wrappers.lambdaQuery(CouponSku.class).eq(CouponSku::getCouponId, coupon.getId()));
+			skuIds.forEach(skuId->{
+				CouponSku couponSku = new CouponSku();
+				couponSku.setCouponId(coupon.getId());
+				couponSku.setSkuId(Long.valueOf(skuId));
+				couponSkuMapper.insert(couponSku);
+			});
+		}
+	}
+}

+ 1 - 1
netflix-service/src/main/java/com/cyksj/service/order/OrderDonService.java

@@ -23,7 +23,7 @@ import java.util.Map;
  */
 public interface OrderDonService extends IService<OrderDon> {
 
-    OrderDon submit(OrderPayRequest request);
+    OrderDon submit(OrderPayRequest request) throws Exception;
 
     /**
      * 订单支付

+ 173 - 10
netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java

@@ -34,10 +34,9 @@ import com.cyksj.dto.RedisKey;
 import com.cyksj.dto.Result;
 import com.cyksj.enums.GatewayResponse;
 import com.cyksj.mapper.*;
-import com.cyksj.model.dto.AliPayParams;
-import com.cyksj.model.dto.H5JsPayParams;
-import com.cyksj.model.dto.WxMpTemplateData;
-import com.cyksj.model.dto.WxMpTemplateMessage;
+import com.cyksj.mapper.manage.coupon.CouponMapper;
+import com.cyksj.mapper.manage.coupon.CouponUserMapper;
+import com.cyksj.model.dto.*;
 import com.cyksj.model.entity.*;
 import com.cyksj.model.manage.views.OrderDonView;
 import com.cyksj.model.request.OrderAttach;
@@ -45,24 +44,25 @@ import com.cyksj.model.request.OrderPayRequest;
 import com.cyksj.model.request.WxOrderRefundReq;
 import com.cyksj.model.request.ZfbOrderRefundReq;
 import com.cyksj.model.response.AliPayRefundRep;
+import com.cyksj.model.views.CouponUserView;
 import com.cyksj.redis.RedisService;
+import com.cyksj.service.coupon.CouponFontService;
 import com.cyksj.service.order.OrderDonService;
 import com.cyksj.service.wechat.WeChatService;
 import com.cyksj.task.JobManager;
 import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.param.Operator;
 import com.ejlchina.searcher.util.MapUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 
 import static com.cyksj.enums.GatewayApiCode.GROUP_INDEX_ALREADY_BE_USER;
 import static com.cyksj.enums.GatewayApiCode.GROUP_INDEX_NOT_FIND;
@@ -120,10 +120,22 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 
 	private final RefundOrderDonMapper refundOrderDonMapper;
 
+	private final CouponMapper couponMapper;
+
+	private final CouponFontService couponFontService;
+
+	private final CouponUserMapper couponUserMapper;
+
 
 	@Transactional(rollbackFor = Throwable.class)
 	@Override
-	public OrderDon submit(OrderPayRequest payRequest) {
+	public OrderDon submit(OrderPayRequest payRequest) throws Exception {
+		CouponUser couponUser = null;
+		if (StrUtil.isNotBlank(payRequest.getCouponExCode()) || payRequest.getCouponId() != null) {
+			checkCouponStatus(payRequest.getSkuId(), payRequest.getCouponExCode(), payRequest.getCouponId(), payRequest.getUserId());
+			//记录优惠卷使用状态
+			couponUser = couponStatusRecord(payRequest.getCouponExCode(), payRequest.getCouponId(), payRequest.getUserId());
+		}
 		User user = userMapper.selectById(payRequest.getUserId());
 		GoodsDonSku sku = goodsDonSkuMapper.selectById(payRequest.getSkuId());
 		GoodsDon goodsDon = goodsDonMapper.selectById(sku.getGoodsId());
@@ -203,6 +215,24 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 		orderDon.setOrderNo(donNo);
 		orderDon.setOpenId(payOpenId);
 		orderDon.setMoney(payRequest.getDonMoney());
+		orderDon.setRealMoney(payRequest.getDonMoney());
+		//存在优惠卷
+		if (couponUser != null) {
+			Long couponId = couponUser.getCouponId();
+			Coupon coupon = couponMapper.selectById(couponId);
+			BigDecimal couponMoney = null;
+			if (coupon.getType() == Coupon.Type.discount) {
+				BigDecimal discountMoney = payRequest.getDonMoney().multiply(coupon.getDiscount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN);
+				orderDon.setMoney(discountMoney);
+				couponMoney = payRequest.getDonMoney().subtract(discountMoney);
+			} else if (coupon.getType() == Coupon.Type.reduce) {
+				BigDecimal subtractMoney = payRequest.getDonMoney().subtract(coupon.getReduceMoney());
+				orderDon.setMoney(subtractMoney);
+				couponMoney = payRequest.getDonMoney().subtract(subtractMoney);
+			}
+			orderDon.setCouponMoney(couponMoney);
+			orderDon.setCouponUserId(couponUser.getId());
+		}
 		orderDon.setSkuId(payRequest.getSkuId());
 		orderDon.setRelationId(relationId);
 		orderDon.setNum(payRequest.getNum());
@@ -212,7 +242,6 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 		orderDon.setType(OrderDon.Type.getType(payRequest.getType()));
 		this.saveOrUpdate(orderDon);
 
-
 		if (goodsDon.getType() == 2) {
 			saveTransport(payRequest.getAddressId(), orderDon.getId());
 		}
@@ -391,6 +420,9 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 						});
 					}
 				}
+				if (orderDon.getCouponUserId() != 0) {
+					updateOrderDonCouponStatus(orderDon);
+				}
 			}
 		}
 	}
@@ -586,6 +618,11 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 		});
 		//虚物订单退款,清出车队,车票变为已过期
 		clearUnrealGoodsDetail(orderDon, goodsDon);
+		//若存在优惠卷使用,退还优惠卷
+		if (orderDon.getCouponUserId() != 0) {
+			redisService.set(String.format("%s-%s", orderDon.getId(), orderDon.getCouponUserId()), orderDon.getId(), 120l);
+			updateOrderDonCouponStatus(orderDon);
+		}
 		return GatewayResponse.SUCCESS.newBuilder().toResult("退款成功");
 	}
 
@@ -650,6 +687,10 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 			});
 			//虚物订单退款,清出车队,车票变为已过期
 			clearUnrealGoodsDetail(orderDon, goodsDon);
+			if (orderDon.getCouponUserId() != 0) {
+				//zfb订单退款,返还优惠卷
+				updateOrderDonCouponStatus(orderDon);
+			}
 			return GatewayResponse.SUCCESS.newBuilder().toResult("退款成功");
 		}
 		throw BusinessRuntimeException.getInstance("退款失败,e:{0}", response.getBody());
@@ -765,6 +806,12 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 			orderDon.setRefundMoney(refundMoney);
 			orderDonMapper.updateById(orderDon);
 		}
+		if (orderDon.getCouponUserId() != 0) {
+			Object obj = redisService.get(String.format("%s-%s", orderDon.getId(), orderDon.getCouponUserId()));
+			if (obj == null) {
+				updateOrderDonCouponStatus(orderDon);
+			}
+		}
 		log.info("微信退款: 退款单号[{}] 退款成功.", outRefundNo);
 	}
 
@@ -936,4 +983,120 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 			}
 		});
 	}
+
+	public void checkCouponStatus(Long skuId, String couponExCode, Long couponId, Long userId) throws Exception {
+		log.info("开始校验兑换码:{},优惠卷id:{}状态", couponExCode, couponId);
+		if (StrUtil.isNotBlank(couponExCode) && couponId != null) {
+			throw BusinessRuntimeException.getInstance("优惠券与优惠码不可同时使用..");
+		}
+		if (StrUtil.isNotBlank(couponExCode)) {
+			CouponPopularizeDto exists = couponMapper.checkExCode(couponExCode);
+			if (exists == null) {
+				throw BusinessRuntimeException.getInstance("该优惠码不存在");
+			}
+			Set<Integer> skuIds = Jsons.parseObject(exists.getSkuIds(), Set.class);
+			if (!skuIds.contains(Integer.valueOf(String.valueOf(skuId)))) {
+				throw BusinessRuntimeException.getInstance("该优惠码不适用于该规格");
+			}
+		} else if (couponId != null) {
+			CouponUserView couponUserView = beanSearcher.searchFirst(CouponUserView.class, MapUtils.builder()
+					.field(CouponUserView::getCouponId, couponId).op(Operator.Equal)
+					.field(CouponUserView::getUserId, userId).op(Operator.Equal)
+					.build());
+			if (couponUserView == null) {
+				throw BusinessRuntimeException.getInstance("该优惠卷不存在");
+			}
+			if (couponUserView.getCouponUserStatus() == CouponUser.Status.used) {
+				throw BusinessRuntimeException.getInstance("该优惠卷已使用");
+			}
+			if (!couponUserView.getIsValidTime()) {
+				couponUserView.setValidStartTime(couponUserView.getCreatedTime());
+				couponUserView.setValidEndTime(DateUtil.offsetDay(couponUserView.getValidStartTime(), couponUserView.getValidDays()));
+			}
+			couponFontService.setCouponUserStatus(couponUserView, DateTime.now());
+			if (couponUserView.getCouponUserStatus() != CouponUser.Status.unused) {
+				throw BusinessRuntimeException.getInstance("该优惠卷不可使用");
+			}
+			Set<Integer> skuIds = Jsons.parseObject(couponUserView.getSkuIds(), Set.class);
+			if (!skuIds.contains(Integer.valueOf(String.valueOf(skuId)))) {
+				throw BusinessRuntimeException.getInstance("该优惠码不适用于该规格");
+			}
+		}
+	}
+
+	private CouponUser couponStatusRecord(String couponExCode, Long couponId, Long userId) {
+		log.info("兑换码:{},优惠卷id:{},更新其优惠卷状态", couponExCode, couponId);
+		CouponUser couponUser = null;
+		if (StrUtil.isNotBlank(couponExCode)) {
+			CouponPopularizeDto exists = couponMapper.checkExCode(couponExCode);
+			if (exists == null) {
+				throw BusinessRuntimeException.getInstance("该优惠码不存在");
+			}
+			couponUser = new CouponUser();
+			couponUser.setCouponId(exists.getCouponId());
+			if (exists.getCouponId() == null) {
+				couponUser.setCouponId(exists.getPopularizeCouponId());
+				couponUser.setPopularizeId(exists.getPopularizeId());
+			}
+			CouponUser received = couponUserMapper.selectOne(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getUserId, userId).eq(CouponUser::getCouponId, couponUser.getCouponId()).last("limit 1"));
+			if (received != null && received.getStatus() != CouponUser.Status.unused) {
+				throw BusinessRuntimeException.getInstance("该优惠卷已使用");
+			}
+			couponUser.setUserId(userId);
+			couponUser.setStatus(CouponUser.Status.used);
+			couponUser.setChannel(CouponUser.Channel.exCode);
+			try {
+				couponUserMapper.insert(couponUser);
+			} catch (DuplicateKeyException e) {
+				log.info("用户已领取该{}优惠卷", couponUser.getCouponId());
+				throw BusinessRuntimeException.getInstance("该优惠卷已使用");
+			}
+		} else if (couponId != null) {
+			CouponUserView couponUserView = beanSearcher.searchFirst(CouponUserView.class, MapUtils.builder()
+					.field(CouponUserView::getCouponId, couponId).op(Operator.Equal)
+					.field(CouponUserView::getUserId, userId).op(Operator.Equal)
+					.build());
+			if (couponUserView == null) {
+				throw BusinessRuntimeException.getInstance("该优惠卷不存在");
+			}
+			if (couponUserView.getCouponUserStatus() == CouponUser.Status.used) {
+				throw BusinessRuntimeException.getInstance("该优惠卷已使用");
+			}
+			if (!couponUserView.getIsValidTime()) {
+				couponUserView.setValidStartTime(couponUserView.getCreatedTime());
+				couponUserView.setValidEndTime(DateUtil.offsetDay(couponUserView.getValidStartTime(), couponUserView.getValidDays()));
+			}
+			couponFontService.setCouponUserStatus(couponUserView, DateTime.now());
+			if (couponUserView.getCouponUserStatus() != CouponUser.Status.unused) {
+				throw BusinessRuntimeException.getInstance("该优惠卷不可使用");
+			}
+			couponUser = couponUserMapper.selectById(couponUserView.getId());
+			couponUser.setStatus(CouponUser.Status.used);
+			couponUserMapper.updateById(couponUser);
+		}
+		return couponUser;
+	}
+
+	/**
+	 * 退款时,归还优惠卷
+	 */
+	private void updateOrderDonCouponStatus(OrderDon orderDon) {
+		log.info("订单:{}退款,优惠卷返还start...", orderDon.getId());
+		CouponUser couponUser = couponUserMapper.selectById(orderDon.getCouponUserId());
+		if (CouponUser.Channel.exCode == couponUser.getChannel()) {
+			//兑换码兑换的优惠卷退款时,直接删除
+			couponUserMapper.deleteById(couponUser.getId());
+		}else {
+			CouponUserView couponUserView = beanSearcher.searchFirst(CouponUserView.class, MapUtils.builder().field(CouponUserView::getCouponId, couponUser.getCouponId()).build());
+			if (!couponUserView.getIsValidTime()) {
+				couponUserView.setValidStartTime(couponUserView.getCreatedTime());
+				couponUserView.setValidEndTime(DateUtil.offsetDay(couponUserView.getValidStartTime(), couponUserView.getValidDays()));
+			}
+			couponUserView.setCouponUserStatus(CouponUser.Status.unused);
+			couponFontService.setCouponUserStatus(couponUserView, DateTime.now());
+			couponUser.setStatus(couponUserView.getCouponUserStatus());
+			couponUserMapper.updateById(couponUser);
+		}
+		log.info("订单:{}退款,优惠卷返还end...", orderDon.getId());
+	}
 }

+ 189 - 0
netflix-web/src/main/java/com/cyksj/web/controller/coupon/CouponFrontController.java

@@ -0,0 +1,189 @@
+package com.cyksj.web.controller.coupon;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.manage.coupon.CouponUserMapper;
+import com.cyksj.model.entity.Coupon;
+import com.cyksj.model.entity.CouponUser;
+import com.cyksj.model.request.CouponCollectReq;
+import com.cyksj.model.views.CouponActiveView;
+import com.cyksj.model.views.CouponNewUserView;
+import com.cyksj.model.views.CouponRecommendView;
+import com.cyksj.model.views.CouponUserView;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.coupon.CouponFontService;
+import com.cyksj.web.util.StpUserUtil;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.param.Operator;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+
+/*
+ *项目名: netflix
+ *文件名: CouponFrontController
+ *创建者: JavaZou
+ *创建时间:2022/11/18 10:56
+ */
+@RestController
+@RequestMapping("/applet/coupon")
+@RequiredArgsConstructor
+public class CouponFrontController {
+	private final CouponUserMapper couponUserMapper;
+
+	private final BeanSearcher beanSearcher;
+
+	private final CouponFontService couponFontService;
+
+	private final RedisService redisService;
+
+	/**
+	 * 优惠卷领取中心列表
+	 */
+	@GetMapping("/center/list")
+	public Result<SearchResult<CouponActiveView>> couponCenterList(HttpServletRequest request) {
+		setToken(8116l);
+		Long userId = StpUserUtil.getLoginIdAsLong();
+		SearchResult<CouponActiveView> search = beanSearcher.search(CouponActiveView.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		search.getDataList().forEach(data -> {
+			CouponUser couponUser = couponUserMapper.selectOne(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getCouponId, data.getCouponId()).eq(CouponUser::getUserId, userId).select(CouponUser::getId).last("limit 1"));
+			if (couponUser == null) {
+				if (data.getRemainNum() <= 0) {
+					//已领完
+					data.setCouponStatus(CouponActiveView.CouponStatus.over);
+				} else {
+					//待领取
+					data.setCouponStatus(CouponActiveView.CouponStatus.collecting);
+				}
+			} else {
+				//已领取
+				data.setCouponStatus(CouponActiveView.CouponStatus.received);
+			}
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 个人优惠卷列表
+	 */
+	@GetMapping("/personal/list")
+	public Result<SearchResult<CouponUserView>> couponPersonalList(HttpServletRequest request) {
+		setToken(8116l);
+		Long userId = StpUserUtil.getLoginIdAsLong();
+		SearchResult<CouponUserView> search = beanSearcher.search(CouponUserView.class,
+				MapUtils.flatBuilder(request.getParameterMap())
+						.field(CouponUserView::getUserId, userId)
+						.orderBy(CouponUserView::getId).desc()
+						.build());
+		couponFontService.handleCouponStatus(search);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 可用优惠卷
+	 */
+	@GetMapping("/available/list")
+	public Result<SearchResult<CouponUserView>> availableCouponList(Long skuId) {
+		setToken(8116l);
+		Long userId = StpUserUtil.getLoginIdAsLong();
+		Integer count = couponUserMapper.countAvailableCouponList(skuId, userId);
+		SearchResult<CouponUserView> couponUserViews = new SearchResult<>();
+		if (count != 0) {
+			List<CouponUserView> couponActiveViewList = couponFontService.getAvailableCouponList(skuId, userId);
+			couponUserViews = new SearchResult<>(couponActiveViewList);
+		}
+		couponUserViews.setTotalCount(count);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(couponUserViews);
+	}
+
+	/**
+	 * 推荐优惠卷列表
+	 */
+	@GetMapping("/recommend/list")
+	public Result<SearchResult<CouponRecommendView>> getRecommendList(HttpServletRequest request) {
+		setToken(8116l);
+		Long userId = StpUserUtil.getLoginIdAsLong();
+		CouponNewUserView couponNewUserView = beanSearcher.searchFirst(CouponNewUserView.class, MapUtils.builder().field(CouponNewUserView::getUserId, userId).op(Operator.Equal).build());
+		SearchResult<CouponRecommendView> search = beanSearcher.search(CouponRecommendView.class,
+				MapUtils.flatBuilder(request.getParameterMap())
+						.orderBy(CouponRecommendView::getId).desc()
+						.build());
+		search.getDataList().stream().filter(data -> {
+			//过滤新用户优惠卷
+			if (data.getScope() == Coupon.Scope.newUser && couponNewUserView != null) {
+				return false;
+			}
+			return true;
+		}).forEach(data -> {
+			CouponUser couponUser = couponUserMapper.selectOne(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getCouponId, data.getCouponId()).select(CouponUser::getId).last("limit 1"));
+			if (couponUser == null) {
+				//待领取
+				data.setCouponStatus(CouponActiveView.CouponStatus.collecting);
+			} else {
+				//已领取
+				data.setCouponStatus(CouponActiveView.CouponStatus.received);
+			}
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 一键领取推荐优惠卷
+	 */
+	@PostMapping("/collect/recommend/whole")
+	public Result<String> collectWholeRecommendCoupon() {
+		setToken(8116l);
+		Long userId = StpUserUtil.getLoginIdAsLong();
+		couponFontService.collectWholeRecommendCoupon(userId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("领取成功");
+	}
+
+	/**
+	 * 今日推荐优惠卷弹窗已弹
+	 */
+	@GetMapping("/get/recommend/appear")
+	public Result<Boolean> recommendIsAppear() {
+		setToken(8116l);
+		Long userId = StpUserUtil.getLoginIdAsLong();
+		String zero = DateUtil.beginOfDay(DateTime.now()).toString();
+		Object exists = redisService.get(RedisService.key.RECOMMEND_COUPON_DAY.getNameFormat(String.format("%s-%s", zero, userId)));
+		if (exists == null) {
+			redisService.set(RedisService.key.RECOMMEND_COUPON_DAY.getNameFormat(String.format("%s-%s", zero, userId)), userId);
+		}
+		return GatewayResponse.SUCCESS.newBuilder().toResult(exists == null ? false : true);
+	}
+
+	/**
+	 * 领取优惠卷
+	 */
+	@PostMapping("/collect")
+	public Result<String> collectCoupon(@RequestBody CouponCollectReq couponCollectReq) {
+		setToken(8116l);
+		Long userId = StpUserUtil.getLoginIdAsLong();
+		couponFontService.collectCoupon(couponCollectReq, userId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("领取优惠卷成功");
+	}
+
+	/**
+	 * 领取推荐优惠卷
+	 */
+	@PostMapping("/recommend/collect")
+	@Deprecated
+	public Result<String> collectRecommendCoupon(@RequestBody CouponCollectReq couponCollectReq) {
+		setToken(8116l);
+		Long userId = StpUserUtil.getLoginIdAsLong();
+		couponFontService.collectRecommendCoupon(couponCollectReq, userId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("领取优惠卷成功");
+	}
+
+	public void setToken(Long userId) {
+		StpUserUtil.login(userId);
+	}
+}

+ 272 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/coupon/CouponController.java

@@ -0,0 +1,272 @@
+package com.cyksj.web.controller.manage.coupon;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.manage.coupon.*;
+import com.cyksj.model.entity.*;
+import com.cyksj.model.request.CouponActiveReq;
+import com.cyksj.model.request.CouponDistributeReq;
+import com.cyksj.model.views.CouponActiveView;
+import com.cyksj.model.views.CouponDistributePopularizeView;
+import com.cyksj.model.views.CouponRecommendView;
+import com.cyksj.model.views.CouponView;
+import com.cyksj.service.mange.coupon.CouponCommonService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.constraints.Min;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/*
+ *项目名: netflix
+ *文件名: CouponController
+ *创建者: JavaZou
+ *创建时间:2022/11/17 13:44
+ */
+@RestController
+@RequestMapping("/manage/coupon")
+@RequiredArgsConstructor
+public class CouponController {
+	private final CouponCommonService couponCommonService;
+
+	private final CouponMapper couponMapper;
+
+	private final BeanSearcher beanSearcher;
+
+	private final CouponRecommendMapper couponRecommendMapper;
+
+	private final CouponDistributePopularizeMapper couponDistributePopularizeMapper;
+
+	private final CouponActiveMapper couponActiveMapper;
+
+	private final CouponUserMapper couponUserMapper;
+
+	private final CouponSkuMapper couponSkuMapper;
+
+	/**
+	 * 新增优惠卷
+	 */
+	@PostMapping
+	public Result<String> couponSave(@RequestBody @Validated Coupon coupon) throws Exception {
+		couponCommonService.saveCoupon(coupon);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("新增优惠卷成功");
+	}
+
+	/**
+	 * 删除优惠卷
+	 */
+	@DeleteMapping("/{id}")
+	@Transactional(rollbackFor = Throwable.class)
+	public Result<String> couponSave(@PathVariable Long id) throws Exception {
+		couponMapper.deleteById(id);
+		couponSkuMapper.deleteByCid(id);
+		couponActiveMapper.deleteByCouponId(id);
+		couponRecommendMapper.deleteByCouponId(id);
+		couponDistributePopularizeMapper.deleteByCouponId(id);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("删除优惠卷成功");
+	}
+
+	/**
+	 * 修改优惠卷
+	 */
+	@PutMapping
+	public Result<String> putCoupon(@RequestBody @Validated Coupon coupon) throws Exception {
+		couponCommonService.updateCoupon(coupon);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("修改优惠卷成功");
+	}
+
+	/**
+	 * 发放优惠卷
+	 */
+	@PostMapping("/send")
+	public Result<String> senCoupon(@RequestBody @Validated CouponActiveReq couponActiveReq) throws Exception {
+		couponCommonService.sendCoupon(couponActiveReq);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("发放优惠卷成功");
+	}
+
+	/**
+	 * 优惠卷 设置为推荐优惠卷
+	 */
+	@PutMapping("/set/hot/{couponId}")
+	public Result<String> setHotCoupon(@PathVariable @Min(value = 1) Long couponId) {
+		couponCommonService.setHotCoupon(couponId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("推荐优惠卷成功");
+	}
+
+	/**
+	 * 优惠卷关联固定推广者
+	 */
+	@PostMapping("/set/relation")
+	public Result<String> setPopularizeRelation(@RequestBody CouponDistributeReq couponDistributeReq) {
+		couponCommonService.setPopularizeRelation(couponDistributeReq);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("优惠卷关联固定推广者成功");
+	}
+
+	/**
+	 * 设置固定推广者兑换码
+	 */
+	@PutMapping("/set/relation/exCode")
+	public Result<String> setPopularizeRelationCode(@RequestBody CouponDistributePopularize couponDistributePopularize) {
+		couponCommonService.setPopularizeRelationCode(couponDistributePopularize);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("设置固定推广者兑换码成功");
+	}
+
+	/**
+	 * 关联某优惠卷列表
+	 */
+	@GetMapping("/get/relation/list")
+	public Result<SearchResult<CouponDistributePopularizeView>> getRelationList(HttpServletRequest request) {
+		SearchResult<CouponDistributePopularizeView> search = beanSearcher.search(CouponDistributePopularizeView.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		search.getDataList().forEach(data -> {
+			List<CouponUser> couponUsers = couponUserMapper.selectList(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getPopularizeId, data.getId()).select(CouponUser::getId, CouponUser::getStatus));
+			Optional.ofNullable(couponUsers).ifPresentOrElse(list -> {
+				Map<CouponUser.Status, List<CouponUser>> listMap = list.stream().collect(Collectors.groupingBy(CouponUser::getStatus));
+				List<CouponUser> used = listMap.get(CouponUser.Status.used);
+				data.setUsedNum(used == null ? 0 : used.size());
+			}, () -> {
+				data.setUsedNum(0);
+			});
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 删除固定推广者关联
+	 */
+	@DeleteMapping("/relation/del/{relationId}")
+	public Result<String> deleteRelationById(@PathVariable Long relationId) {
+		couponDistributePopularizeMapper.deleteById(relationId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("删除推广者关联成功");
+	}
+
+	/**
+	 * 优惠卷列表
+	 */
+	@GetMapping
+	public Result<SearchResult<CouponView>> couponList(HttpServletRequest request) {
+		SearchResult<CouponView> search = beanSearcher.search(CouponView.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		search.getDataList().forEach(data -> {
+			data.setRelationNum(couponDistributePopularizeMapper.selectCount(Wrappers.lambdaQuery(CouponDistributePopularize.class).eq(CouponDistributePopularize::getCouponId, data.getId())));
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 优惠卷发放列表
+	 */
+	@GetMapping("/active/list")
+	public Result<SearchResult<CouponActiveView>> couponActiveList(HttpServletRequest request) {
+		SearchResult<CouponActiveView> search = beanSearcher.search(CouponActiveView.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		search.getDataList().forEach(data -> {
+			List<CouponUser> couponUsers = couponUserMapper.selectList(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getCouponActiveId, data.getCouponActiveId()).select(CouponUser::getId, CouponUser::getStatus));
+			Optional.ofNullable(couponUsers).ifPresentOrElse(list -> {
+				data.setReceiptNum(list.size());
+				Map<CouponUser.Status, List<CouponUser>> listMap = list.stream().collect(Collectors.groupingBy(CouponUser::getStatus));
+				List<CouponUser> used = listMap.get(CouponUser.Status.used);
+				data.setUsedNum(used == null ? 0 : used.size());
+			}, () -> {
+				data.setReceiptNum(0);
+				data.setUsedNum(0);
+			});
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 发放优惠卷停用
+	 */
+	@PutMapping("/active/putStatus/{couponActiveId}")
+	public Result<String> activePutStatus(@PathVariable Long couponActiveId) {
+		CouponActive couponActive = couponActiveMapper.selectById(couponActiveId);
+		if (couponActive == null || !couponActive.getDeleted()) {
+			throw BusinessRuntimeException.getInstance("发放记录已不存在");
+		}
+		if (!couponActive.getStatus()) {
+			throw BusinessRuntimeException.getInstance("该发放优惠卷已停用");
+		}
+		couponActive.setStatus(false);
+		couponActiveMapper.updateById(couponActive);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("停用发放优惠卷成功");
+	}
+
+	/**
+	 * 发放优惠卷失效
+	 */
+	@PutMapping("/active/putExpiryStatus/{couponActiveId}")
+	public Result<String> activePutExpiryStatus(@PathVariable Long couponActiveId) {
+		CouponActive couponActive = couponActiveMapper.selectById(couponActiveId);
+		if (couponActive == null || !couponActive.getDeleted()) {
+			throw BusinessRuntimeException.getInstance("发放记录已不存在");
+		}
+		if (!couponActive.getExpiryStatus()) {
+			throw BusinessRuntimeException.getInstance("该发放优惠卷已失效");
+		}
+		couponActive.setExpiryStatus(false);
+		couponActive.setStatus(false);
+		couponActiveMapper.updateById(couponActive);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("失效发放优惠卷成功");
+	}
+
+
+	/**
+	 * 推荐发放列表
+	 */
+	@GetMapping("/recommend/list")
+	public Result<SearchResult<CouponRecommendView>> couponRecommendList(HttpServletRequest request) {
+		SearchResult<CouponRecommendView> search = beanSearcher.search(CouponRecommendView.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		search.getDataList().forEach(data -> {
+			List<CouponUser> couponUsers = couponUserMapper.selectList(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getRecommendId, data.getId()).select(CouponUser::getId, CouponUser::getStatus));
+			Optional.ofNullable(couponUsers).ifPresentOrElse(list -> {
+				data.setReceiptNum(list.size());
+				Map<CouponUser.Status, List<CouponUser>> listMap = list.stream().collect(Collectors.groupingBy(CouponUser::getStatus));
+				List<CouponUser> used = listMap.get(CouponUser.Status.used);
+				data.setUsedNum(used == null ? 0 : used.size());
+			}, () -> {
+				data.setReceiptNum(0);
+				data.setUsedNum(0);
+			});
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 推荐优惠卷设置/取消推荐
+	 */
+	@PutMapping("/recommend/putStatus/{recommendId}")
+	public Result<String> putRecommendStatusById(@PathVariable Long recommendId) {
+		CouponRecommend couponRecommend = couponRecommendMapper.selectById(recommendId);
+		if (couponRecommend == null || !couponRecommend.getDeleted()) {
+			throw BusinessRuntimeException.getInstance("该推荐优惠卷不存在");
+		}
+		couponRecommend.setStatus(!couponRecommend.getStatus());
+		couponRecommendMapper.updateById(couponRecommend);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("操作成功");
+	}
+
+	/**
+	 * 删除推荐优惠卷
+	 */
+	@DeleteMapping("/recommend/del/{recommendId}")
+	public Result<String> deleteRecommendById(@PathVariable Long recommendId) {
+		CouponRecommend couponRecommend = couponRecommendMapper.selectById(recommendId);
+		if (couponRecommend == null || !couponRecommend.getDeleted()) {
+			throw BusinessRuntimeException.getInstance("该推荐优惠卷不存在");
+		}
+		if (couponRecommend.getStatus()) {
+			throw BusinessRuntimeException.getInstance("取消推荐优惠券,才可删除");
+		}
+		couponRecommendMapper.deleteById(recommendId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("删除推荐优惠卷成功");
+	}
+}