Browse Source

feat: coupon

wanghongkai 5 years ago
parent
commit
25dec49dc4
40 changed files with 932 additions and 383 deletions
  1. 27 5
      sql/courselearning.sql
  2. 20 41
      src/main/java/cn/seecoder/courselearning/controller/CouponController.java
  3. 9 0
      src/main/java/cn/seecoder/courselearning/controller/CourseOrderController.java
  4. 7 6
      src/main/java/cn/seecoder/courselearning/dto/CouponDTO.java
  5. 0 24
      src/main/java/cn/seecoder/courselearning/dto/CourseCouponDTO.java
  6. 33 0
      src/main/java/cn/seecoder/courselearning/dto/DeliverCouponDTO.java
  7. 0 25
      src/main/java/cn/seecoder/courselearning/dto/UserCouponDTO.java
  8. 31 0
      src/main/java/cn/seecoder/courselearning/enums/CouponDeliver.java
  9. 42 0
      src/main/java/cn/seecoder/courselearning/enums/CouponScope.java
  10. 25 2
      src/main/java/cn/seecoder/courselearning/enums/CouponType.java
  11. 3 3
      src/main/java/cn/seecoder/courselearning/mapperservice/CouponMapper.java
  12. 16 0
      src/main/java/cn/seecoder/courselearning/mapperservice/CourseOrderCouponMapper.java
  13. 19 0
      src/main/java/cn/seecoder/courselearning/mapperservice/UserCouponMapper.java
  14. 14 29
      src/main/java/cn/seecoder/courselearning/po/Coupon.java
  15. 24 0
      src/main/java/cn/seecoder/courselearning/po/CourseOrderCoupon.java
  16. 32 0
      src/main/java/cn/seecoder/courselearning/po/UserCoupon.java
  17. 21 0
      src/main/java/cn/seecoder/courselearning/service/CouponDeliverStrategy.java
  18. 11 15
      src/main/java/cn/seecoder/courselearning/service/CouponService.java
  19. 7 23
      src/main/java/cn/seecoder/courselearning/service/CouponStrategy.java
  20. 4 0
      src/main/java/cn/seecoder/courselearning/service/CourseOrderService.java
  21. 111 101
      src/main/java/cn/seecoder/courselearning/serviceimpl/CouponServiceImpl.java
  22. 155 5
      src/main/java/cn/seecoder/courselearning/serviceimpl/CourseOrderServiceImpl.java
  23. 14 0
      src/main/java/cn/seecoder/courselearning/serviceimpl/coupondeliverstrategy/AllUserDeliverStrategy.java
  24. 18 0
      src/main/java/cn/seecoder/courselearning/serviceimpl/coupondeliverstrategy/CourseLikeDeliverStrategy.java
  25. 27 0
      src/main/java/cn/seecoder/courselearning/serviceimpl/coupondeliverstrategy/SingleUserDeliverStrategy.java
  26. 50 0
      src/main/java/cn/seecoder/courselearning/serviceimpl/couponstrategy/AbstractCouponStrategy.java
  27. 0 14
      src/main/java/cn/seecoder/courselearning/serviceimpl/couponstrategy/CourseCouponStrategyImpl.java
  28. 35 0
      src/main/java/cn/seecoder/courselearning/serviceimpl/couponstrategy/CutDownCouponStrategy.java
  29. 29 0
      src/main/java/cn/seecoder/courselearning/serviceimpl/couponstrategy/DiscountCouponStrategy.java
  30. 0 14
      src/main/java/cn/seecoder/courselearning/serviceimpl/couponstrategy/UniversalCouponStrategyImpl.java
  31. 0 14
      src/main/java/cn/seecoder/courselearning/serviceimpl/couponstrategy/UserCouponStrategyImpl.java
  32. 25 0
      src/main/java/cn/seecoder/courselearning/util/AsyncConfig.java
  33. 1 5
      src/main/java/cn/seecoder/courselearning/util/CouponValidator.java
  34. 28 0
      src/main/java/cn/seecoder/courselearning/util/JSONHelper.java
  35. 10 27
      src/main/java/cn/seecoder/courselearning/vo/CouponVO.java
  36. 4 0
      src/main/java/cn/seecoder/courselearning/vo/CourseOrderVO.java
  37. 20 28
      src/main/resources/mapper/CouponMapper.xml
  38. 26 0
      src/main/resources/mapper/CourseOrderCouponMapper.xml
  39. 2 2
      src/main/resources/mapper/CourseOrderMapper.xml
  40. 32 0
      src/main/resources/mapper/UserCouponMapper.xml

+ 27 - 5
sql/courselearning.sql

@@ -321,13 +321,10 @@ DROP TABLE IF EXISTS `coupon`;
 CREATE TABLE `coupon` (
   `id` int NOT NULL AUTO_INCREMENT,
   `type` varchar(255) NOT NULL,
+  `scope` varchar(255) NOT NULL,
   `name` varchar(255) NOT NULL,
   `description` varchar(255) NOT NULL,
-  `course_id` int DEFAULT '-1',
-  `valid_user_id` int DEFAULT '-1',
-  `threshold` int DEFAULT '0',
-  `discount` double DEFAULT '1.0',
-  `cut_down` int DEFAULT '0',
+  `metadata` text NOT NULL ,
   `start_time` datetime DEFAULT NULL,
   `end_time` datetime DEFAULT NULL,
   `is_valid` tinyint(1) NOT NULL DEFAULT '1',
@@ -347,4 +344,29 @@ CREATE TABLE `course_likes` (
   PRIMARY KEY (`course_id`, `user_id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
+-- ----------------------------
+-- Table structure for `course_order_coupon`
+-- ----------------------------
+
+DROP TABLE IF EXISTS `course_order_coupon`;
+CREATE TABLE `course_order_coupon` (
+  `order_id` int NOT NULL,
+  `coupon_id` int NOT NULL,
+  PRIMARY KEY (`order_id`, `coupon_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+
+-- ----------------------------
+-- Table structure for `user_coupon`
+-- ----------------------------
+
+DROP TABLE IF EXISTS `user_coupon`;
+CREATE TABLE `user_coupon` (
+  `user_id` int NOT NULL,
+  `coupon_id` int NOT NULL,
+  `nums` int NOT NULL,
+  PRIMARY KEY (`user_id`, `coupon_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
 SET FOREIGN_KEY_CHECKS=1;
+

+ 20 - 41
src/main/java/cn/seecoder/courselearning/controller/CouponController.java

@@ -1,11 +1,9 @@
 package cn.seecoder.courselearning.controller;
 
-import cn.seecoder.courselearning.dto.CourseCouponDTO;
-import cn.seecoder.courselearning.dto.UniversalCouponDTO;
-import cn.seecoder.courselearning.dto.UserCouponDTO;
+import cn.seecoder.courselearning.dto.CouponDTO;
+import cn.seecoder.courselearning.dto.DeliverCouponDTO;
 import cn.seecoder.courselearning.service.CouponService;
 import cn.seecoder.courselearning.vo.CouponVO;
-import cn.seecoder.courselearning.vo.CourseOrderVO;
 import cn.seecoder.courselearning.vo.ResultVO;
 import org.springframework.web.bind.annotation.*;
 
@@ -18,46 +16,27 @@ public class CouponController {
     @Resource
     CouponService couponService;
 
-    // 创建通用型优惠券
-    @PostMapping("/create/universal")
-    ResultVO<CouponVO> createUniversalCoupon(@RequestBody UniversalCouponDTO universalCouponDTO) {
-        return couponService.createUniversalCoupon(universalCouponDTO);
+    // 创建优惠券
+    @PostMapping("/create")
+    ResultVO<CouponVO> createCoupon(@RequestBody CouponDTO universalCouponDTO) {
+        return couponService.createCoupon(universalCouponDTO);
     }
 
-//    // 创建课程型优惠券
-//    @PostMapping("/create/course")
-//    ResultVO<CouponVO> createCourseCoupon(@RequestBody CourseCouponDTO courseCouponDTO) {
-//        return couponService.createCourseCoupon(courseCouponDTO);
-//    }
-//
-//    // 创建用户定制化优惠券
-//    @PostMapping("/create/user")
-//    ResultVO<CouponVO> createUserCoupon(@RequestBody UserCouponDTO userCouponDTO) {
-//        return couponService.createUserCoupon(userCouponDTO);
-//    }
-
-    // 查看所有的网站通用型优惠券
-    @GetMapping("/universal")
-    List<CouponVO> getUniversalCoupons() {
-        return couponService.getUniversalCoupons();
+    // 查看所有的优惠券
+    @GetMapping("/getAll")
+    ResultVO<List<CouponVO>> getAll() {
+        return couponService.getAllAvailableCoupons();
     }
 
+    // 获取订单可用的优惠券
+    @GetMapping("/getForOrder")
+    ResultVO<List<CouponVO>> getForOrder(@RequestParam Integer orderId) {
+        return couponService.getAllAvailableCouponsForOrder(orderId);
+    }
 
-//    // 查看当前课程所有的课程优惠券(不含网站通用优惠券、用户定制化优惠券) 包含已经失效了的优惠活动
-//    @GetMapping("/course/{courseId}")
-//    List<CouponVO> getCourseCoupons(@PathVariable Integer courseId) {
-//        return couponService.getCourseCoupons(courseId);
-//    }
-//
-//    // 查询 userId 对应的用户 目前可以使用的有效的优惠券
-//    @GetMapping("/user/{userId}")
-//    List<CouponVO> getAllEffectiveCouponsForUser(@PathVariable Integer userId) {
-//        return couponService.getAllEffectiveUserCoupons(userId);
-//    }
-//
-//    // 查看当前用户购买此课程可生效的所有优惠
-//    @GetMapping("/check_for_order")
-//    List<CouponVO> getAllEffectiveCouponsForOrder(@RequestBody CourseOrderVO orderVO) {
-//        return couponService.getAllEffectiveCouponsForOrder(orderVO);
-//    }
+    // 发放优惠券
+    @PostMapping("/deliver")
+    ResultVO<Boolean> deliver(@RequestBody DeliverCouponDTO deliverCouponDTO) {
+        return couponService.deliverCoupon(deliverCouponDTO);
+    }
 }

+ 9 - 0
src/main/java/cn/seecoder/courselearning/controller/CourseOrderController.java

@@ -35,4 +35,13 @@ public class CourseOrderController {
         return orderService.updateCourseOrder(orderId, orderStatus);
     }
 
+    @PostMapping("/use_coupon")
+    public ResultVO<Boolean> useCoupon(@RequestBody CourseOrderVO orderVO, @RequestParam Integer couponId){
+        return orderService.useCoupon(orderVO, couponId);
+    }
+
+    @PostMapping("/cancel_coupon")
+    public ResultVO<Boolean> cancelCoupon(@RequestBody CourseOrderVO orderVO, @RequestParam Integer couponId){
+        return orderService.cancelCoupon(orderVO, couponId);
+    }
 }

+ 7 - 6
src/main/java/cn/seecoder/courselearning/dto/UniversalCouponDTO.java → src/main/java/cn/seecoder/courselearning/dto/CouponDTO.java

@@ -1,23 +1,24 @@
 package cn.seecoder.courselearning.dto;
 
+import cn.seecoder.courselearning.enums.CouponScope;
 import cn.seecoder.courselearning.enums.CouponType;
 import lombok.Data;
 
 import java.time.LocalDateTime;
 
 /**
- * 网站优惠活动
- * 对所有课程、所有用户均有效的、可重复使用的优惠券
+ * 优惠券/优惠活动DTO类
  */
 @Data
-public class UniversalCouponDTO {
+public class CouponDTO {
     private CouponType type;
+    private CouponScope scope;
     private String name;
     private String description;
-    private Double discount;
-    private Integer threshold;
-    private Integer cutDown;
+    private String metadata;
     private LocalDateTime startTime;
     private LocalDateTime endTime;
     private Boolean sharable;
+    // 发放策略
+    private DeliverCouponDTO deliverCouponDTO;
 }

+ 0 - 24
src/main/java/cn/seecoder/courselearning/dto/CourseCouponDTO.java

@@ -1,24 +0,0 @@
-package cn.seecoder.courselearning.dto;
-
-import cn.seecoder.courselearning.enums.CouponType;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-/**
- * 课程优惠活动
- * 所有用户在优惠券生效期间购买当前课程均可享受优惠
- */
-@Data
-public class CourseCouponDTO {
-    private CouponType type;
-    private String name;
-    private String description;
-    private Integer courseId;
-    private Double discount;
-    private Integer threshold;
-    private Integer cutDown;
-    private LocalDateTime startTime;
-    private LocalDateTime endTime;
-    private Boolean sharable;
-}

+ 33 - 0
src/main/java/cn/seecoder/courselearning/dto/DeliverCouponDTO.java

@@ -0,0 +1,33 @@
+package cn.seecoder.courselearning.dto;
+
+import cn.seecoder.courselearning.enums.CouponDeliver;
+import lombok.Data;
+
+import java.util.Map;
+
+@Data
+public class DeliverCouponDTO {
+    /**
+     * 要发放的优惠券id
+     */
+    private Integer couponId;
+
+    /**
+     * 优惠券发放对象类型
+     */
+    private CouponDeliver deliver;
+
+    /**
+     * 判断发放类型时额外的判断数据
+     * 如发放类型为基于某课程的点赞数,这里则传递课程id,以及该点赞数的发放阈值
+     */
+    private Map<String, String> extra;
+
+    /**
+     * 是否可以发放给已经发放过该优惠券的用户
+     */
+    private Boolean canDuplicate;
+    
+    // 发放数量
+    private Integer nums;
+}

+ 0 - 25
src/main/java/cn/seecoder/courselearning/dto/UserCouponDTO.java

@@ -1,25 +0,0 @@
-package cn.seecoder.courselearning.dto;
-
-import cn.seecoder.courselearning.enums.CouponType;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-/**
- * 对用户发放的定制化优惠券
- * 仅对某个用户生效的单次优惠券
- */
-@Data
-public class UserCouponDTO {
-    private CouponType type;
-    private String name;
-    private String description;
-    private Integer courseId;
-    private Integer validUserId;
-    private Double discount;
-    private Integer threshold;
-    private Integer cutDown;
-    private LocalDateTime startTime;
-    private LocalDateTime endTime;
-    private Boolean sharable;
-}

+ 31 - 0
src/main/java/cn/seecoder/courselearning/enums/CouponDeliver.java

@@ -0,0 +1,31 @@
+package cn.seecoder.courselearning.enums;
+
+import cn.seecoder.courselearning.service.CouponDeliverStrategy;
+import cn.seecoder.courselearning.serviceimpl.coupondeliverstrategy.AllUserDeliverStrategy;
+import cn.seecoder.courselearning.serviceimpl.coupondeliverstrategy.CourseLikeDeliverStrategy;
+import cn.seecoder.courselearning.serviceimpl.coupondeliverstrategy.SingleUserDeliverStrategy;
+import lombok.Getter;
+
+/**
+ * 优惠券发放对象类型
+ */
+@Getter
+public enum CouponDeliver {
+    SINGLE("发放给单个用户", new SingleUserDeliverStrategy()),
+    COURSE_LIKE("发放给某课程点赞超过一定数量的用户", new CourseLikeDeliverStrategy()),
+    ALL("发放给全体用户", new AllUserDeliverStrategy());
+
+    private final String value;
+
+    private CouponDeliverStrategy deliverStrategy;
+
+    CouponDeliver(String value, CouponDeliverStrategy deliverStrategy) {
+        this.deliverStrategy = deliverStrategy;
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+}

+ 42 - 0
src/main/java/cn/seecoder/courselearning/enums/CouponScope.java

@@ -0,0 +1,42 @@
+package cn.seecoder.courselearning.enums;
+
+import cn.seecoder.courselearning.po.Coupon;
+import cn.seecoder.courselearning.po.Course;
+import cn.seecoder.courselearning.util.JSONHelper;
+import java.util.function.BiFunction;
+
+/**
+ * 优惠券使用范围
+ * 可以分为对某一个课程使用SINGLE
+ * 也可以分为对某一类课程使用如TEACHER,表明优惠券可以用在老师上
+ */
+public enum CouponScope {
+
+
+    SINGLE("单个课程", (course,coupon) -> course.getId().equals((Integer)JSONHelper.getByProperty(coupon.getMetadata(), "courseId"))),
+    TEACHER("某个老师的课程", (course, coupon) -> course.getId().equals((Integer)JSONHelper.getByProperty(coupon.getMetadata(), "teacherId"))),
+    SCHOOL("某个学校的课程", (course, coupon) -> course.getId().equals((Integer)JSONHelper.getByProperty(coupon.getMetadata(), "schoolId"))),
+    COMMON("通用,全站课程皆可使用", (course, coupon) -> true);
+
+    private final String value;
+
+    /**
+     * 用来判断course是否满足coupon的使用范围
+     */
+    private final BiFunction<Course, Coupon, Boolean> couponScopeStrategy;
+
+    CouponScope(String value, BiFunction<Course, Coupon, Boolean> couponScopeStrategy) {
+        this.value = value;
+        this.couponScopeStrategy = couponScopeStrategy;
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+
+
+    public boolean canUse(Course course, Coupon coupon) {
+        return this.couponScopeStrategy.apply(course, coupon);
+    }
+}

+ 25 - 2
src/main/java/cn/seecoder/courselearning/enums/CouponType.java

@@ -1,16 +1,39 @@
 package cn.seecoder.courselearning.enums;
 
+import cn.seecoder.courselearning.service.CouponStrategy;
+import cn.seecoder.courselearning.serviceimpl.couponstrategy.CutDownCouponStrategy;
+import cn.seecoder.courselearning.serviceimpl.couponstrategy.DiscountCouponStrategy;
+import lombok.Getter;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+@Getter
 public enum CouponType {
-    DISCOUNT("折扣型"), CUT_DOWN("减价型");
+    DISCOUNT("折扣型", ApplicationContextInjector.applicationContext.getBean(DiscountCouponStrategy.class)),
+    CUT_DOWN("减价型", ApplicationContextInjector.applicationContext.getBean(CutDownCouponStrategy.class));
 
     private final String value;
 
-    CouponType(String value) {
+    private final CouponStrategy couponStrategy;
+
+    CouponType(String value, CouponStrategy couponStrategy) {
         this.value = value;
+        this.couponStrategy = couponStrategy;
     }
 
     @Override
     public String toString() {
         return value;
     }
+
+    @Component
+    public static class ApplicationContextInjector implements ApplicationContextAware {
+        public static ApplicationContext applicationContext;
+        @Override
+        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+            this.applicationContext = applicationContext;
+        }
+    }
 }

+ 3 - 3
src/main/java/cn/seecoder/courselearning/mapperservice/CouponMapper.java

@@ -14,9 +14,9 @@ public interface CouponMapper {
 
     int updateByPrimaryKey(Coupon record);
 
-    // 根据课程Id和用户Id查找优惠券
-    List<Coupon> selectByCourseIdAndUserId(Integer courseId, Integer userId);
-
     // 根据用户Id查找优惠券
     List<Coupon> selectByUserId(Integer userId);
+
+    // 根据订单id查找订单已使用优惠券
+    List<Coupon> selectByOrderId(Integer orderId);
 }

+ 16 - 0
src/main/java/cn/seecoder/courselearning/mapperservice/CourseOrderCouponMapper.java

@@ -0,0 +1,16 @@
+package cn.seecoder.courselearning.mapperservice;
+
+import cn.seecoder.courselearning.po.CourseOrderCoupon;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface CourseOrderCouponMapper {
+    int insert(CourseOrderCoupon courseOrderCoupon);
+
+    List<CourseOrderCoupon> selectByOrderId(Integer orderId);
+
+    CourseOrderCoupon selectByOrderIdAndCouponId(@Param(value = "orderId") Integer orderId, @Param(value = "couponId") Integer couponId);
+
+    int deleteByOrderIdAndCouponId(@Param(value = "orderId") Integer orderId, @Param(value = "couponId") Integer couponId);
+}

+ 19 - 0
src/main/java/cn/seecoder/courselearning/mapperservice/UserCouponMapper.java

@@ -0,0 +1,19 @@
+package cn.seecoder.courselearning.mapperservice;
+
+import cn.seecoder.courselearning.po.UserCoupon;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface UserCouponMapper {
+
+    int insert(UserCoupon userCoupon);
+
+    int update(UserCoupon userCoupon);
+
+    UserCoupon selectByCouponIdAndUserId(@Param(value = "couponId") Integer couponId, @Param(value = "userId")  Integer userId);
+
+    List<UserCoupon> selectByUserId(Integer userId);
+
+    int deleteByUserIdAndCouponId(@Param(value = "userId") Integer userId, @Param(value = "couponId") Integer couponId);
+}

+ 14 - 29
src/main/java/cn/seecoder/courselearning/po/Coupon.java

@@ -1,5 +1,6 @@
 package cn.seecoder.courselearning.po;
 
+import cn.seecoder.courselearning.enums.CouponScope;
 import cn.seecoder.courselearning.enums.CouponType;
 import cn.seecoder.courselearning.vo.CouponVO;
 import lombok.AccessLevel;
@@ -22,6 +23,11 @@ public class Coupon {
      */
     private CouponType type;
 
+    /**
+     * 优惠券使用范围
+     */
+    private CouponScope scope;
+
     /**
      * 优惠券名称
      */
@@ -33,31 +39,13 @@ public class Coupon {
     private String description;
 
     /**
-     * 如果为-1,表示对所有课程均有效
-     */
-    private Integer courseId;
-
-    /**
-     * 如果为-1,表示对所有用户均有效
-     * 当前优惠券对 userId 对应的用户生效
-     */
-    private Integer validUserId;
-
-    /**
-     * 使用门槛
-     */
-    private Integer threshold;
-
-    /**
-     * 折扣型 - 折扣(介于0~1之间)
+     * 优惠券元数据,对不同的优惠券类型,优惠券发放策略,优惠券使用范围不同,格式为JSON字符串
+     * 如优惠券类型为满减,这里的元数据可以是{"threshold": 100, "cutDown": 20}表示满100减20的优惠券
+     * 如优惠券类型为折扣,这里的元数据可以是{"discount": 0.8}表示打八折
+     * 格式很自由,可以自定义数据,如自定义优惠券为满200减50,低于200打95折,这里则存储着优惠券类型会使用到的数据
      */
-    private double discount;
-
-    /**
-     * 减价金额
-     */
-    private Integer cutDown;
-
+    private String metadata;
+    
     /**
      * 生效时间
      */
@@ -91,13 +79,10 @@ public class Coupon {
     public Coupon(CouponVO couponVO) {
         this.id = couponVO.getId();
         this.type = CouponType.valueOf(couponVO.getType());
+        this.scope = CouponScope.valueOf(couponVO.getScope());
         this.name = couponVO.getName();
         this.description = couponVO.getDescription();
-        this.courseId = couponVO.getCourseId();
-        this.validUserId = couponVO.getValidUserId();
-        this.threshold = couponVO.getThreshold();
-        this.discount = couponVO.getDiscount();
-        this.cutDown = couponVO.getCutDown();
+        this.metadata = couponVO.getMetadata();
         this.startTime = couponVO.getStartTime();
         this.endTime = couponVO.getEndTime();
         this.valid = couponVO.isValid();

+ 24 - 0
src/main/java/cn/seecoder/courselearning/po/CourseOrderCoupon.java

@@ -0,0 +1,24 @@
+package cn.seecoder.courselearning.po;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 表明某优惠券被哪个订单使用
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class CourseOrderCoupon {
+    /**
+     * 订单id
+     */
+    private Integer orderId;
+
+    /**
+     * 优惠券id
+     */
+    private Integer couponId;
+
+}

+ 32 - 0
src/main/java/cn/seecoder/courselearning/po/UserCoupon.java

@@ -0,0 +1,32 @@
+package cn.seecoder.courselearning.po;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 表明某优惠券被哪个用户拥有
+ * 这里不把userId放到Coupon中,是因为防止重复发放
+ * 试思考一个场景,假如网站经理批量发放某一类优惠券到某一类用户,这时候倘若有新用户也满足了发放范围
+ * 网站经理或定时想发放优惠券给这些新用户时,而老用户不要重复发放时,怎么知道该优惠券已经发放给用户?
+ * 用Coupon中的其他属性判断显然是不现实的,并不能保证唯一
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class UserCoupon {
+    /**
+     * 优惠券id
+     */
+    private Integer couponId;
+
+    /**
+     * 用户id
+     */
+    private Integer userId;
+
+    /**
+     * 优惠券数量
+     */
+    private Integer nums;
+}

+ 21 - 0
src/main/java/cn/seecoder/courselearning/service/CouponDeliverStrategy.java

@@ -0,0 +1,21 @@
+package cn.seecoder.courselearning.service;
+
+import cn.seecoder.courselearning.po.Coupon;
+import cn.seecoder.courselearning.po.User;
+
+import java.util.Map;
+
+/**
+ * 优惠券发放策略
+ */
+public interface CouponDeliverStrategy {
+
+    /**
+     * 判断该用户是否是优惠券发放对象
+     * @param user 用户
+     * @param coupon 优惠券
+     * @param extra 额外的判断数据
+     * @return
+     */
+    boolean canDeliver(User user, Coupon coupon, Map<String, String> extra);
+}

+ 11 - 15
src/main/java/cn/seecoder/courselearning/service/CouponService.java

@@ -1,24 +1,20 @@
 package cn.seecoder.courselearning.service;
 
-import cn.seecoder.courselearning.dto.CourseCouponDTO;
-import cn.seecoder.courselearning.dto.UniversalCouponDTO;
-import cn.seecoder.courselearning.dto.UserCouponDTO;
+import cn.seecoder.courselearning.dto.CouponDTO;
+import cn.seecoder.courselearning.dto.DeliverCouponDTO;
 import cn.seecoder.courselearning.vo.CouponVO;
-import cn.seecoder.courselearning.vo.CourseOrderVO;
 import cn.seecoder.courselearning.vo.ResultVO;
 
 import java.util.List;
 
 public interface CouponService {
-    ResultVO<CouponVO> createUniversalCoupon(UniversalCouponDTO universalCouponDTO);
-    ResultVO<CouponVO> createCourseCoupon(CourseCouponDTO courseCouponDTO);
-    ResultVO<CouponVO> createUserCoupon(UserCouponDTO userCouponDTO);
-    // 查看所有的网站通用型优惠券
-    List<CouponVO> getUniversalCoupons();
-//    // 查看当前课程所有的课程优惠券(不含网站通用优惠券、用户定制化优惠券) 包含已经失效了的优惠活动
-//    List<CouponVO> getCourseCoupons(Integer courseId);
-//    // 查询 userId 对应的用户 目前可以使用的有效的优惠券
-//    List<CouponVO> getAllEffectiveUserCoupons(Integer userId);
-//    // 查看当前用户购买此课程可生效的所有优惠
-//    List<CouponVO> getAllEffectiveCouponsForOrder(CourseOrderVO orderVO);
+    // 创建并发放优惠券
+    ResultVO<CouponVO> createCoupon(CouponDTO couponDTO);
+    // 发放现有的优惠券
+    ResultVO<Boolean> deliverCoupon(DeliverCouponDTO courseCouponDTO);
+    // 网站经理获取所有可发放优惠券(用于选择优惠券并发放)
+    ResultVO<List<CouponVO>> getAllAvailableCoupons();
+    // 查看某用户购买此课程可用的所有优惠
+    ResultVO<List<CouponVO>> getAllAvailableCouponsForOrder(Integer orderId);
+
 }

+ 7 - 23
src/main/java/cn/seecoder/courselearning/service/CouponStrategy.java

@@ -3,38 +3,22 @@ package cn.seecoder.courselearning.service;
 import cn.seecoder.courselearning.po.Coupon;
 import cn.seecoder.courselearning.vo.CourseOrderVO;
 
-import java.time.LocalDateTime;
 
 // 优惠券匹配策略
 public interface CouponStrategy {
 
     /**
-     * 根据当前优惠券匹配策略
-     *   判断当前优惠活动 或 优惠券是否适用于此课程订单
-     * 例如,coupon1 是网站优惠活动产生的通用型优惠券,那么它在通用优惠活动策略下 就适用于任何课程
+     * 判断该订单能否使用该优惠券
      * @param orderVO 课程订单
      * @param coupon 优惠活动 或 优惠券
      */
-    boolean isMatch(CourseOrderVO orderVO, Coupon coupon);
+    boolean canUse(CourseOrderVO orderVO, Coupon coupon);
 
     /**
-     * 判断优惠券是否对此订单有效
-     * @param orderVO 课程订单
-     * @param coupon 优惠活动 或 优惠券
-     * @return 优惠券是否有效
+     * 使用优惠券
+     * @param orderVO
+     * @param coupon
+     * @return 返回优惠价格
      */
-    default boolean isEffective(CourseOrderVO orderVO, Coupon coupon) {
-        // 获取当前时间
-        LocalDateTime now = LocalDateTime.now();
-        if (isMatch(orderVO, coupon)) {
-            return coupon.isValid() && coupon.getThreshold()>=orderVO.getCost()
-                    && (coupon.getStartTime()==null || now.isAfter(coupon.getStartTime()))
-                    && (coupon.getEndTime()==null || now.isBefore(coupon.getEndTime()));
-        }
-        return false;
-    }
-
-    default void useCoupon(CourseOrderVO orderVO, Coupon coupon) {
-
-    }
+    int useCoupon(CourseOrderVO orderVO, Coupon coupon);
 }

+ 4 - 0
src/main/java/cn/seecoder/courselearning/service/CourseOrderService.java

@@ -14,4 +14,8 @@ public interface CourseOrderService {
     ResultVO<CourseOrderVO> updateCourseOrder(Integer orderId, Integer orderStatus);
     // 根据用户id获取该用户的所有订单
     List<CourseOrderVO> getCourseOrders(Integer uid);
+    // 使用优惠券/活动
+    ResultVO<Boolean> useCoupon(CourseOrderVO orderVO, Integer couponId);
+    // 取消使用优惠券/活动
+    ResultVO<Boolean> cancelCoupon(CourseOrderVO orderVO, Integer couponId);
 }

+ 111 - 101
src/main/java/cn/seecoder/courselearning/serviceimpl/CouponServiceImpl.java

@@ -1,138 +1,148 @@
 package cn.seecoder.courselearning.serviceimpl;
 
-import cn.seecoder.courselearning.dto.CourseCouponDTO;
-import cn.seecoder.courselearning.dto.UniversalCouponDTO;
-import cn.seecoder.courselearning.dto.UserCouponDTO;
-import cn.seecoder.courselearning.mapperservice.CouponMapper;
-import cn.seecoder.courselearning.po.Coupon;
-import cn.seecoder.courselearning.service.CouponStrategy;
+import cn.seecoder.courselearning.dto.CouponDTO;
+import cn.seecoder.courselearning.dto.DeliverCouponDTO;
+import cn.seecoder.courselearning.mapperservice.*;
+import cn.seecoder.courselearning.po.*;
+import cn.seecoder.courselearning.service.CouponDeliverStrategy;
 import cn.seecoder.courselearning.service.CouponService;
-import cn.seecoder.courselearning.serviceimpl.couponstrategy.CourseCouponStrategyImpl;
-import cn.seecoder.courselearning.serviceimpl.couponstrategy.UniversalCouponStrategyImpl;
-import cn.seecoder.courselearning.serviceimpl.couponstrategy.UserCouponStrategyImpl;
 import cn.seecoder.courselearning.util.Constant;
 import cn.seecoder.courselearning.util.CouponValidator;
 import cn.seecoder.courselearning.vo.CouponVO;
 import cn.seecoder.courselearning.vo.CourseOrderVO;
 import cn.seecoder.courselearning.vo.ResultVO;
 import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
-import java.util.Date;
+import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 @Service
 public class CouponServiceImpl implements CouponService {
     @Resource
     private CouponMapper couponMapper;
 
-//    private final List<CouponStrategy> couponStrategyList;
-//
-//    @Autowired
-//    public CouponServiceImpl(UniversalCouponStrategyImpl universalCouponMatchStrategy, CourseCouponStrategyImpl courseCouponMatchStrategy, UserCouponStrategyImpl userCouponMatchStrategy) {
-//        couponStrategyList = new ArrayList<>();
-//        couponStrategyList.add(universalCouponMatchStrategy);
-//        couponStrategyList.add(courseCouponMatchStrategy);
-//        couponStrategyList.add(userCouponMatchStrategy);
-//    }
+    @Resource
+    private UserMapper userMapper;
 
-    @Override
-    public ResultVO<CouponVO> createUniversalCoupon(UniversalCouponDTO universalCouponDTO) {
-        System.out.println(universalCouponDTO);
+    @Resource
+    private UserCouponMapper userCouponMapper;
+
+    @Resource
+    private CourseMapper courseMapper;
+
+    @Resource
+    private CourseOrderMapper orderMapper;
+    /**
+     * 直接创建优惠券并发放
+     * @param couponDTO
+     * @return
+     */
+    public ResultVO<CouponVO> createCoupon(CouponDTO couponDTO) {
+        System.out.println(couponDTO);
         Coupon coupon = new Coupon();
-        BeanUtils.copyProperties(universalCouponDTO, coupon);
-        // 网站优惠活动生成的优惠券一定是对所有用户、所有课程均有效的
-        coupon.setValidUserId(-1);
-        coupon.setCourseId(-1);
+        BeanUtils.copyProperties(couponDTO, coupon);
         coupon.setValid(true);
-        if(CouponValidator.isInvalid(coupon))
+        if (CouponValidator.isInvalid(coupon)) {
             return new ResultVO<>(Constant.REQUEST_FAIL, "优惠信息填写有误!");
-        if(couponMapper.insert(coupon) > 0) {
-            System.out.println(coupon);
-            return new ResultVO<>(Constant.REQUEST_SUCCESS, "网站优惠活动创建成功", new CouponVO(coupon));
         }
-        return new ResultVO<>(Constant.REQUEST_FAIL, "网站优惠活动创建失败");
+        if (couponMapper.insert(coupon) <= 0) {
+            return new ResultVO<>(Constant.REQUEST_FAIL, "优惠活动创建失败");
+        }
+        ResultVO<CouponVO> ret = new ResultVO<>(Constant.REQUEST_SUCCESS, "优惠活动创建成功", new CouponVO(coupon));
+        if (couponDTO.getDeliverCouponDTO() != null) {
+            // 异步发放优惠券
+            DeliverCouponDTO deliverCouponDTO = couponDTO.getDeliverCouponDTO();
+            deliverCouponDTO.setCouponId(coupon.getId());
+            deliverCouponToUsers(deliverCouponDTO);
+        }
+        return ret;
     }
 
     @Override
-    public ResultVO<CouponVO> createCourseCoupon(CourseCouponDTO courseCouponDTO) {
-        Coupon coupon = new Coupon();
-        BeanUtils.copyProperties(courseCouponDTO, coupon);
-        // 课程优惠活动生成的优惠券是对所有用户均有效的
-        coupon.setValidUserId(-1);
-        coupon.setValid(true);
-        if(CouponValidator.isInvalid(coupon) || coupon.getCourseId() == -1)
-            return new ResultVO<>(Constant.REQUEST_FAIL, "优惠信息填写有误!");
-        if(couponMapper.insert(coupon) > 0)
-            return new ResultVO<>(Constant.REQUEST_SUCCESS, "课程优惠活动创建成功", new CouponVO(coupon));
-        return new ResultVO<>(Constant.REQUEST_FAIL, "课程优惠活动创建失败");
+    public ResultVO<Boolean> deliverCoupon(DeliverCouponDTO courseCouponDTO) {
+        deliverCouponToUsers(courseCouponDTO);
+        return new ResultVO<>(Constant.REQUEST_SUCCESS, "发放成功");
     }
 
     @Override
-    public ResultVO<CouponVO> createUserCoupon(UserCouponDTO userCouponDTO) {
-        Coupon coupon = new Coupon();
-        BeanUtils.copyProperties(userCouponDTO, coupon);
-        coupon.setValid(true);
-        // 用户定制化优惠活动生成的优惠券仅对某个用户有效
-        if(CouponValidator.isInvalid(coupon) || coupon.getValidUserId() == -1)
-            return new ResultVO<>(Constant.REQUEST_FAIL, "优惠信息填写有误!");
-        if(couponMapper.insert(coupon) > 0)
-            return new ResultVO<>(Constant.REQUEST_SUCCESS, "用户优惠券发放成功", new CouponVO(coupon));
-        return new ResultVO<>(Constant.REQUEST_FAIL, "用户优惠券发放失败");
+    public ResultVO<List<CouponVO>> getAllAvailableCoupons() {
+        List<Coupon> coupons = couponMapper.selectAll();
+        LocalDateTime now = LocalDateTime.now();
+        // 获取还未结束或还未开始的可发放的优惠券
+        List<CouponVO> result = coupons.stream().filter(coupon -> coupon.getEndTime().isAfter(now) && coupon.isValid()).map(CouponVO::new).collect(Collectors.toList());
+        return new ResultVO<>(Constant.REQUEST_SUCCESS, "获取成功", result);
     }
 
     @Override
-    public List<CouponVO> getUniversalCoupons() {
-        List<Coupon> couponList = couponMapper.selectByCourseIdAndUserId(-1, -1);
-        List<CouponVO> ret = new ArrayList<>();
-        couponList.forEach(coupon -> ret.add(new CouponVO(coupon)));
-        return ret;
+    public ResultVO<List<CouponVO>> getAllAvailableCouponsForOrder(Integer orderId) {
+        CourseOrder courseOrder = orderMapper.selectByPrimaryKey(orderId);
+        if (orderId == null) {
+            return new ResultVO<>(Constant.REQUEST_FAIL, "查询失败,订单不存在!", Collections.EMPTY_LIST);
+        }
+        CourseOrderVO orderVO = new CourseOrderVO(courseOrder);
+        // 获取已经使用的优惠券
+        List<Coupon> usedCoupons = couponMapper.selectByOrderId(orderVO.getId());
+        boolean sharable = true;
+        // 获取对应的优惠券
+        for (Coupon usedCoupon : usedCoupons) {
+            if (!usedCoupon.isSharable()) {
+                sharable = false;
+                break;
+            }
+        }
+        if (sharable) {
+            // 查询可用优惠券
+            List<Coupon> userCoupons = couponMapper.selectByUserId(orderVO.getUserId());
+            List<Coupon> availableCoupons = new ArrayList<>();
+            Course course = courseMapper.selectByPrimaryKey(orderVO.getCourseId());
+            for (Coupon coupon : userCoupons) {
+                if (coupon.getType().getCouponStrategy().canUse(orderVO, coupon) && coupon.getScope().canUse(course, coupon)) {
+                    // 再去除当前订单使用的
+                    Coupon usedCoupon = usedCoupons.stream().filter(used -> used.getId().equals(coupon.getId())).findFirst().orElse(null);
+                    if (usedCoupon == null) {
+                        availableCoupons.add(coupon);
+                    }
+                }
+            }
+            return new ResultVO<>(Constant.REQUEST_SUCCESS, "查询成功", availableCoupons.stream().map(CouponVO::new).collect(Collectors.toList()));
+        }
+        return new ResultVO<>(Constant.REQUEST_SUCCESS, "查询成功", Collections.EMPTY_LIST);
     }
 
-//    @Override
-//    public List<CouponVO> getCourseCoupons(Integer courseId) {
-//        List<Coupon> couponList = couponMapper.selectByCourseIdAndUserId(courseId, -1);
-//        List<CouponVO> ret = new ArrayList<>();
-//        couponList.forEach(coupon -> ret.add(new CouponVO(coupon)));
-//        return ret;
-//    }
-
-//    @Override
-//    public List<CouponVO> getAllEffectiveUserCoupons(Integer userId) {
-//        List<Coupon> couponList = couponMapper.selectByUserId(userId);
-//        List<CouponVO> ret = new ArrayList<>();
-//        couponList.forEach(coupon -> {
-//            CourseOrderVO orderVO = new CourseOrderVO();
-//            orderVO.setCourseId(coupon.getCourseId());
-//            orderVO.setUserId(userId);
-//            orderVO.setCost(0);
-//            orderVO.setCreateTime(new Date());
-//            if (couponStrategyList.get(2).isEffective(orderVO, coupon))
-//                ret.add(new CouponVO(coupon));
-//        });
-//        return ret;
-//    }
-//
-//    @Override
-//    public List<CouponVO> getAllEffectiveCouponsForOrder(CourseOrderVO orderVO) {
-//        List<Coupon> universalCoupons = couponMapper.selectByCourseIdAndUserId(-1, -1);
-//        List<Coupon> courseCoupons = couponMapper.selectByCourseIdAndUserId(orderVO.getCourseId(), -1);
-//        List<Coupon> userCoupons = couponMapper.selectByUserId(orderVO.getUserId());
-//        List<Coupon> temp = new ArrayList<>(universalCoupons);
-//        temp.addAll(courseCoupons);
-//        temp.addAll(userCoupons);
-//        List<CouponVO> ret = new ArrayList<>();
-//        temp.forEach(coupon -> {
-//            for(CouponStrategy strategy: couponStrategyList) {
-//                if (strategy.isEffective(orderVO, coupon)) {
-//                    ret.add(new CouponVO(coupon));
-//                    break;
-//                }
-//            }
-//        });
-//        return ret;
-//    }
+    @Async("taskExecutor")
+    protected void deliverCouponToUsers(DeliverCouponDTO deliverCouponDTO) {
+        List<User> users = userMapper.selectAll();
+        Coupon coupon = couponMapper.selectByPrimaryKey(deliverCouponDTO.getCouponId());
+        if (coupon == null || users.isEmpty()) return;
+        List<User> targetUsers = new ArrayList<>();
+        CouponDeliverStrategy deliverStrategy = deliverCouponDTO.getDeliver().getDeliverStrategy();
+        for (User user : users) {
+            if (deliverStrategy.canDeliver(user, coupon, deliverCouponDTO.getExtra())) {
+                if (deliverCouponDTO.getCanDuplicate()) {
+                    targetUsers.add(user);
+                } else {
+                    UserCoupon userCoupon = userCouponMapper.selectByCouponIdAndUserId(deliverCouponDTO.getCouponId(), user.getId());
+                    if (userCoupon == null) {
+                        targetUsers.add(user);
+                    }
+                }
+            }
+        }
+        for (User user : targetUsers) {
+            UserCoupon userCoupon = userCouponMapper.selectByCouponIdAndUserId(deliverCouponDTO.getCouponId(), user.getId());
+            if (userCoupon != null) {
+                userCoupon.setNums(userCoupon.getNums() + deliverCouponDTO.getNums());
+                userCouponMapper.update(userCoupon);
+            } else {
+                userCoupon = new UserCoupon(deliverCouponDTO.getCouponId(), user.getId(), deliverCouponDTO.getNums());
+                userCouponMapper.insert(userCoupon);
+            }
+        }
+    }
 }

+ 155 - 5
src/main/java/cn/seecoder/courselearning/serviceimpl/CourseOrderServiceImpl.java

@@ -1,18 +1,21 @@
 package cn.seecoder.courselearning.serviceimpl;
 
-import cn.seecoder.courselearning.mapperservice.CourseOrderMapper;
-import cn.seecoder.courselearning.mapperservice.UserMapper;
-import cn.seecoder.courselearning.po.CourseOrder;
-import cn.seecoder.courselearning.po.User;
+import cn.seecoder.courselearning.mapperservice.*;
+import cn.seecoder.courselearning.po.*;
 import cn.seecoder.courselearning.service.CourseOrderService;
 import cn.seecoder.courselearning.util.Constant;
+import cn.seecoder.courselearning.vo.CouponVO;
 import cn.seecoder.courselearning.vo.CourseOrderVO;
 import cn.seecoder.courselearning.vo.ResultVO;
+import org.omg.CORBA.IntHolder;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 @Service
 public class CourseOrderServiceImpl implements CourseOrderService {
@@ -20,6 +23,14 @@ public class CourseOrderServiceImpl implements CourseOrderService {
     CourseOrderMapper orderMapper;
     @Resource
     UserMapper userMapper;
+    @Resource
+    CourseOrderCouponMapper courseOrderCouponMapper;
+    @Resource
+    CouponMapper couponMapper;
+    @Resource
+    UserCouponMapper userCouponMapper;
+    @Resource
+    CourseMapper courseMapper;
 
     @Override
     public ResultVO<CourseOrderVO> insertCourseOrder(CourseOrderVO orderVO) {
@@ -39,6 +50,7 @@ public class CourseOrderServiceImpl implements CourseOrderService {
                     // 支付成功,将订单存入数据库
                     order.setStatus(Constant.ORDER_STATUS_SUCCESS);
                     orderMapper.insert(order);
+                    updateUserCoupon(order.getId(), user.getId());
                 }else {
                     // 将订单存入数据库
                     orderMapper.insert(order);
@@ -48,6 +60,7 @@ public class CourseOrderServiceImpl implements CourseOrderService {
             }else if(status.equals(Constant.ORDER_STATUS_SUCCESS) || status.equals(Constant.ORDER_STATUS_WAIT)) {
                 // 若订单状态为已支付或等待发货,则直接存入数据库
                 orderMapper.insert(order);
+                updateUserCoupon(order.getId(), order.getUserId());
                 return new ResultVO<>(Constant.REQUEST_SUCCESS, "订单保存成功", new CourseOrderVO(order));
             }
         }catch (Exception e){
@@ -68,6 +81,7 @@ public class CourseOrderServiceImpl implements CourseOrderService {
             User user = userMapper.selectByPrimaryKey(order.getUserId());
             if(user.getBalance()>=order.getCost()){
                 userMapper.decreaseBalance(user.getId(), order.getCost());
+                updateUserCoupon(orderId, user.getId());
             }else
                 return new ResultVO<>(Constant.REQUEST_FAIL, "用户余额不足");
         }
@@ -76,13 +90,149 @@ public class CourseOrderServiceImpl implements CourseOrderService {
         return new ResultVO<>(Constant.REQUEST_SUCCESS, "课程购买成功", new CourseOrderVO(order));
     }
 
+    /**
+     * 订单完成时更新用户优惠券
+     * @param orderId
+     * @param userId
+     */
+    public void updateUserCoupon(Integer orderId, Integer userId) {
+        // 更新用户优惠券状态
+        List<Coupon> orderCoupons = couponMapper.selectByOrderId(orderId);
+        List<UserCoupon> userCouponList = userCouponMapper.selectByUserId(userId);
+        for (UserCoupon userCoupon : userCouponList) {
+            if (orderCoupons.stream().anyMatch(coupon -> coupon.getId().equals(userCoupon.getCouponId()))) {
+                if (userCoupon.getNums() == 1) {
+                    userCouponMapper.deleteByUserIdAndCouponId(userId, userCoupon.getCouponId());
+                } else if (userCoupon.getNums() > 1) {
+                    userCoupon.setNums(userCoupon.getNums() - 1);
+                    userCouponMapper.update(userCoupon);
+                }
+            }
+        }
+    }
+
     @Override
     public List<CourseOrderVO> getCourseOrders(Integer uid) {
         List<CourseOrderVO> ret = new ArrayList<>();
         List<CourseOrder> orderList = orderMapper.selectByUserId(uid);
+        LocalDateTime now = LocalDateTime.now();
         for(CourseOrder order: orderList){
-            ret.add(new CourseOrderVO(order));
+            // 获取订单对应的优惠券
+            List<Coupon> courseOrderCoupons = couponMapper.selectByOrderId(order.getId());
+            List<Coupon> coupons = new ArrayList<>();
+            boolean updated = false;
+            // 查询的时候更新一下,以防优惠券在这个时候过期了
+            for (Coupon courseOrderCoupon : courseOrderCoupons) {
+                if (!(courseOrderCoupon.isValid() && courseOrderCoupon.getStartTime().isBefore(now) && courseOrderCoupon.getEndTime().isAfter(now))) {
+                    updated = true;
+                    courseOrderCouponMapper.deleteByOrderIdAndCouponId(order.getId(), courseOrderCoupon.getId());
+                } else {
+                    coupons.add(courseOrderCoupon);
+                }
+            }
+            if (updated) {
+                updateBestCost(new CourseOrderVO(order));
+                order = orderMapper.selectByPrimaryKey(order.getId());
+            }
+            CourseOrderVO orderVO = new CourseOrderVO(order);
+            orderVO.setUsedCoupons(coupons.stream().map(CouponVO::new).collect(Collectors.toList()));
+            ret.add(orderVO);
         }
         return ret;
     }
+
+    /**
+     * 当前订单使用该优惠券
+     * @param orderVO
+     * @param couponId
+     * @return
+     */
+    @Override
+    public ResultVO<Boolean> useCoupon(CourseOrderVO orderVO, Integer couponId) {
+        Coupon coupon = couponMapper.selectByPrimaryKey(couponId);
+        if (coupon == null) {
+            return new ResultVO<>(Constant.REQUEST_FAIL, "错误的优惠券");
+        }
+        LocalDateTime now = LocalDateTime.now();
+        if (!(coupon.isValid() && coupon.getStartTime().isBefore(now) && coupon.getEndTime().isAfter(now))) {
+            return new ResultVO<>(Constant.REQUEST_FAIL, "优惠券过期或还未能使用");
+        }
+        // 判断用户有没有该优惠券
+        UserCoupon userCoupon = userCouponMapper.selectByCouponIdAndUserId(couponId, orderVO.getUserId());
+        if (userCoupon == null) {
+            return new ResultVO<>(Constant.REQUEST_FAIL, "尚未拥有该优惠券");
+        }
+        // 判断能否使用
+        Course course = courseMapper.selectByPrimaryKey(orderVO.getCourseId());
+        if (!coupon.getScope().canUse(course, coupon)) {
+            return new ResultVO<>(Constant.REQUEST_FAIL, "该课程无法使用该优惠券");
+        }
+        // 判断有没有达到使用的门槛
+        if (!coupon.getType().getCouponStrategy().canUse(orderVO, coupon)) {
+            return new ResultVO<>(Constant.REQUEST_FAIL, "没有达到使用门槛!");
+        }
+        // 判断是否已经用过一张了
+        CourseOrderCoupon courseOrderCoupon = courseOrderCouponMapper.selectByOrderIdAndCouponId(orderVO.getId(), couponId);
+        if (courseOrderCoupon != null) {
+            return new ResultVO<>(Constant.REQUEST_FAIL, "已经使用过优惠券!");
+        }
+        courseOrderCouponMapper.insert(new CourseOrderCoupon(orderVO.getId(), couponId));
+        updateBestCost(orderVO);
+        return new ResultVO<>(Constant.REQUEST_SUCCESS, "使用成功");
+    }
+
+    @Override
+    public ResultVO<Boolean> cancelCoupon(CourseOrderVO orderVO, Integer couponId) {
+        courseOrderCouponMapper.deleteByOrderIdAndCouponId(orderVO.getId(), couponId);
+        updateBestCost(orderVO);
+        return new ResultVO<>(Constant.REQUEST_SUCCESS, "取消使用成功!");
+    }
+
+    public void updateBestCost(CourseOrderVO orderVO) {
+        List<Coupon> usedCoupons = couponMapper.selectByOrderId(orderVO.getId());
+        boolean[] used = new boolean[usedCoupons.size()];
+        Arrays.fill(used, false);
+        // 这里通过dfs算法计算最优价格,而IntHolder为可变的int,用于记录最小值
+        // 这里Integer不能使用,因为是Immutable的,每次会创建一个新对象,效果等同于值传递
+        IntHolder minCost = new IntHolder(orderVO.getOrigin());
+        orderVO.setCost(null);
+        getBestCost(orderVO, usedCoupons, used, orderVO.getOrigin(), minCost);
+        orderVO.setCost(minCost.value);
+        // 保存
+        CourseOrder courseOrder = orderMapper.selectByPrimaryKey(orderVO.getId());
+        courseOrder.setCost(orderVO.getCost());
+        orderMapper.updateByPrimaryKey(courseOrder);
+    }
+
+    /**
+     * 获得最优的cost
+     * @param courseOrderVO
+     * @param usedCoupons
+     */
+    public void getBestCost(CourseOrderVO courseOrderVO, List<Coupon> usedCoupons, boolean[] used, int currentCost, IntHolder minCost) {
+        boolean allUsed = true;
+        for (int i = 0; i < used.length; i++) {
+            if (!used[i]) {
+                allUsed = false;
+                break;
+            }
+        }
+        if (allUsed) {
+            if ( minCost.value > currentCost) {
+                minCost.value = currentCost;
+            }
+        } else {
+            for (int i = 0; i < used.length; i++) {
+                if (!used[i]) {
+                    used[i] = true;
+                    Integer oldCost = courseOrderVO.getCost();
+                    int newCost = usedCoupons.get(i).getType().getCouponStrategy().useCoupon(courseOrderVO, usedCoupons.get(i));
+                    courseOrderVO.setCost(newCost);
+                    getBestCost(courseOrderVO, usedCoupons, used, newCost, minCost);
+                    courseOrderVO.setCost(oldCost);
+                    used[i] = false;
+                }
+            }
+        }
+    }
 }

+ 14 - 0
src/main/java/cn/seecoder/courselearning/serviceimpl/coupondeliverstrategy/AllUserDeliverStrategy.java

@@ -0,0 +1,14 @@
+package cn.seecoder.courselearning.serviceimpl.coupondeliverstrategy;
+
+import cn.seecoder.courselearning.po.Coupon;
+import cn.seecoder.courselearning.po.User;
+import cn.seecoder.courselearning.service.CouponDeliverStrategy;
+
+import java.util.Map;
+
+public class AllUserDeliverStrategy implements CouponDeliverStrategy {
+    @Override
+    public boolean canDeliver(User user, Coupon coupon, Map<String, String> extra) {
+        return true;
+    }
+}

+ 18 - 0
src/main/java/cn/seecoder/courselearning/serviceimpl/coupondeliverstrategy/CourseLikeDeliverStrategy.java

@@ -0,0 +1,18 @@
+package cn.seecoder.courselearning.serviceimpl.coupondeliverstrategy;
+
+import cn.seecoder.courselearning.po.Coupon;
+import cn.seecoder.courselearning.po.User;
+import cn.seecoder.courselearning.service.CouponDeliverStrategy;
+
+import java.util.Map;
+
+/**
+ * 用户点赞数发放策略
+ */
+public class CourseLikeDeliverStrategy implements CouponDeliverStrategy {
+    @Override
+    public boolean canDeliver(User user, Coupon coupon, Map<String, String> extra) {
+        // 获取用户点赞数
+        return false;
+    }
+}

+ 27 - 0
src/main/java/cn/seecoder/courselearning/serviceimpl/coupondeliverstrategy/SingleUserDeliverStrategy.java

@@ -0,0 +1,27 @@
+package cn.seecoder.courselearning.serviceimpl.coupondeliverstrategy;
+
+import cn.seecoder.courselearning.po.Coupon;
+import cn.seecoder.courselearning.po.User;
+import cn.seecoder.courselearning.service.CouponDeliverStrategy;
+
+import java.util.Map;
+
+/**
+ * 只发放给单个用户的
+ */
+public class SingleUserDeliverStrategy implements CouponDeliverStrategy {
+
+    @Override
+    public boolean canDeliver(User user, Coupon coupon, Map<String, String> extra) {
+        String userIdStr = extra.get("userId");
+        if (userIdStr == null) {
+            return false;
+        }
+        try {
+            Integer userId = Integer.valueOf(userIdStr);
+            return user.getId().equals(userId);
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}

+ 50 - 0
src/main/java/cn/seecoder/courselearning/serviceimpl/couponstrategy/AbstractCouponStrategy.java

@@ -0,0 +1,50 @@
+package cn.seecoder.courselearning.serviceimpl.couponstrategy;
+
+import cn.seecoder.courselearning.mapperservice.CourseMapper;
+import cn.seecoder.courselearning.mapperservice.UserCouponMapper;
+import cn.seecoder.courselearning.po.Coupon;
+import cn.seecoder.courselearning.po.Course;
+import cn.seecoder.courselearning.po.UserCoupon;
+import cn.seecoder.courselearning.service.CouponStrategy;
+import cn.seecoder.courselearning.vo.CourseOrderVO;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.time.LocalDateTime;
+
+/**
+ * 基础的优惠策略,在这里可以进行优惠能否使用的基础判断
+ */
+public abstract class AbstractCouponStrategy implements CouponStrategy {
+
+    @Autowired
+    protected UserCouponMapper userCouponMapper;
+
+    @Autowired
+    protected CourseMapper courseMapper;
+
+    @Override
+    public boolean canUse(CourseOrderVO orderVO, Coupon coupon) {
+        // 判断用户有没有该优惠券
+        UserCoupon userCoupon = userCouponMapper.selectByCouponIdAndUserId(coupon.getId(),orderVO.getUserId());
+        if (userCoupon == null) {
+            return false;
+        }
+        // 判断是否过期,合法以及在使用时间内
+        LocalDateTime now = LocalDateTime.now();
+        if ((coupon.isValid()
+                && (coupon.getStartTime()==null || now.isAfter(coupon.getStartTime()))
+                && (coupon.getEndTime()==null || now.isBefore(coupon.getEndTime()))) == false) {
+            return false;
+        }
+        // 根据类型判断是否能使用在课程上
+        Course course = courseMapper.selectByPrimaryKey(orderVO.getCourseId());
+        if (course == null) {
+            // 无效订单
+            return false;
+        }
+        if (!coupon.getScope().canUse(course, coupon)) {
+            return false;
+        }
+        return true;
+    }
+}

+ 0 - 14
src/main/java/cn/seecoder/courselearning/serviceimpl/couponstrategy/CourseCouponStrategyImpl.java

@@ -1,14 +0,0 @@
-package cn.seecoder.courselearning.serviceimpl.couponstrategy;
-
-import cn.seecoder.courselearning.po.Coupon;
-import cn.seecoder.courselearning.service.CouponStrategy;
-import cn.seecoder.courselearning.vo.CourseOrderVO;
-import org.springframework.stereotype.Service;
-
-@Service
-public class CourseCouponStrategyImpl implements CouponStrategy {
-    @Override
-    public boolean isMatch(CourseOrderVO orderVO, Coupon coupon) {
-        return coupon.getCourseId().equals(orderVO.getCourseId()) && coupon.getValidUserId() == -1;
-    }
-}

+ 35 - 0
src/main/java/cn/seecoder/courselearning/serviceimpl/couponstrategy/CutDownCouponStrategy.java

@@ -0,0 +1,35 @@
+package cn.seecoder.courselearning.serviceimpl.couponstrategy;
+
+import cn.seecoder.courselearning.po.Coupon;
+import cn.seecoder.courselearning.util.JSONHelper;
+import cn.seecoder.courselearning.vo.CourseOrderVO;
+import org.springframework.stereotype.Component;
+
+/**
+ * 满减策略
+ * 注意这里的满减策略的使用判断是按原价
+ * 即有多张优惠券叠加使用的话,只要都过了门槛,都能使用
+ * 而如果需求是使用过一张优惠券之后再按使用后的金额进行满减计算,则可以再定义一个满减策略实现这样的逻辑
+ */
+@Component
+public class CutDownCouponStrategy extends AbstractCouponStrategy {
+    @Override
+    public boolean canUse(CourseOrderVO orderVO, Coupon coupon) {
+        Object threshold = JSONHelper.getByProperty(coupon.getMetadata(), "threshold");
+        if (threshold == null) {
+            // 无效优惠券
+            return false;
+        }
+        // 判断满减策略
+        return super.canUse(orderVO, coupon) && orderVO.getOrigin() >= (Integer)threshold;
+    }
+
+    @Override
+    public int useCoupon(CourseOrderVO orderVO, Coupon coupon) {
+        Integer cutDown = (Integer)JSONHelper.getByProperty(coupon.getMetadata(), "cutDown");
+        int initialCost = orderVO.getCost() == null ? orderVO.getOrigin() : orderVO.getCost();
+        if (initialCost - cutDown > 0) {
+            return initialCost - cutDown;
+        } else return 0;
+    }
+}

+ 29 - 0
src/main/java/cn/seecoder/courselearning/serviceimpl/couponstrategy/DiscountCouponStrategy.java

@@ -0,0 +1,29 @@
+package cn.seecoder.courselearning.serviceimpl.couponstrategy;
+
+import cn.seecoder.courselearning.po.Coupon;
+import cn.seecoder.courselearning.util.JSONHelper;
+import cn.seecoder.courselearning.vo.CourseOrderVO;
+import org.springframework.stereotype.Component;
+
+/**
+ * 这里的折扣策略不需要满一定数额才能用,可以根据业务需求调整
+ */
+@Component
+public class DiscountCouponStrategy extends AbstractCouponStrategy {
+    @Override
+    public boolean canUse(CourseOrderVO orderVO, Coupon coupon) {
+        Object discount = JSONHelper.getByProperty(coupon.getMetadata(), "discount");
+        if (discount == null) {
+            // 无效优惠券
+            return false;
+        }
+        // 判断满减策略
+        return super.canUse(orderVO, coupon) && (Double)discount > 0 && (Double)discount < 1;
+    }
+    @Override
+    public int useCoupon(CourseOrderVO orderVO, Coupon coupon) {
+        Double discount = (Double)JSONHelper.getByProperty(coupon.getMetadata(), "discount");
+        int initialCost = orderVO.getCost() == null ? orderVO.getOrigin() : orderVO.getCost();
+       return (int)(initialCost * discount);
+    }
+}

+ 0 - 14
src/main/java/cn/seecoder/courselearning/serviceimpl/couponstrategy/UniversalCouponStrategyImpl.java

@@ -1,14 +0,0 @@
-package cn.seecoder.courselearning.serviceimpl.couponstrategy;
-
-import cn.seecoder.courselearning.po.Coupon;
-import cn.seecoder.courselearning.service.CouponStrategy;
-import cn.seecoder.courselearning.vo.CourseOrderVO;
-import org.springframework.stereotype.Service;
-
-@Service
-public class UniversalCouponStrategyImpl implements CouponStrategy {
-    @Override
-    public boolean isMatch(CourseOrderVO orderVO, Coupon coupon) {
-        return coupon.getCourseId() == -1 && coupon.getValidUserId() == -1;
-    }
-}

+ 0 - 14
src/main/java/cn/seecoder/courselearning/serviceimpl/couponstrategy/UserCouponStrategyImpl.java

@@ -1,14 +0,0 @@
-package cn.seecoder.courselearning.serviceimpl.couponstrategy;
-
-import cn.seecoder.courselearning.po.Coupon;
-import cn.seecoder.courselearning.service.CouponStrategy;
-import cn.seecoder.courselearning.vo.CourseOrderVO;
-import org.springframework.stereotype.Service;
-
-@Service
-public class UserCouponStrategyImpl implements CouponStrategy {
-    @Override
-    public boolean isMatch(CourseOrderVO orderVO, Coupon coupon) {
-        return coupon.getCourseId() == -1 || (coupon.getCourseId().equals(orderVO.getCourseId()) && coupon.getValidUserId().equals(orderVO.getUserId()));
-    }
-}

+ 25 - 0
src/main/java/cn/seecoder/courselearning/util/AsyncConfig.java

@@ -0,0 +1,25 @@
+package cn.seecoder.courselearning.util;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+@EnableAsync
+@Configuration
+public class AsyncConfig {
+    @Bean("taskExecutor")
+    public Executor taskExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setCorePoolSize(10);
+        executor.setMaxPoolSize(20);
+        executor.setQueueCapacity(200);
+        executor.setKeepAliveSeconds(60);
+        executor.setThreadNamePrefix("taskExecutor-");
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        return executor;
+    }
+}

+ 1 - 5
src/main/java/cn/seecoder/courselearning/util/CouponValidator.java

@@ -4,13 +4,9 @@ import cn.seecoder.courselearning.po.Coupon;
 
 public class CouponValidator {
     public static boolean isInvalid(Coupon coupon) {
-        if (coupon.getCourseId() < -1 || coupon.getValidUserId() < -1)
-            return true;
         if (coupon.getStartTime() != null && coupon.getEndTime() != null
                 && coupon.getStartTime().isAfter(coupon.getEndTime()))
             return true;
-        if (coupon.getDiscount() > 1 || coupon.getDiscount() < 0)
-            return true;
-        return coupon.getCutDown() < 0;
+        return false;
     }
 }

+ 28 - 0
src/main/java/cn/seecoder/courselearning/util/JSONHelper.java

@@ -0,0 +1,28 @@
+package cn.seecoder.courselearning.util;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.HashMap;
+
+public class JSONHelper {
+
+    /**
+     * 获取json字符串对应的属性
+     * @param jsonStr
+     * @param property
+     * @return
+     */
+    public static Object getByProperty(String jsonStr, String property) {
+        HashMap<String, Object> map;
+        try {
+            map = new ObjectMapper().readValue(jsonStr, new TypeReference<HashMap<String, Object>>() {});
+            if (map.get(property) != null) {
+                return map.get(property);
+            }
+        } catch (Exception e) {
+            return null;
+        }
+        return null;
+    }
+}

+ 10 - 27
src/main/java/cn/seecoder/courselearning/vo/CouponVO.java

@@ -21,6 +21,11 @@ public class CouponVO {
      */
     private String type;
 
+    /**
+     * 优惠券作用范围(单个课程、某类老师的课程,某个学校的课程)
+     */
+    private String scope;
+
     /**
      * 优惠券名称
      */
@@ -32,29 +37,10 @@ public class CouponVO {
     private String description;
 
     /**
-     * 如果为-1,表示对所有课程均有效
-     */
-    private Integer courseId;
-
-    /**
-     * 如果为-1,表示对所有用户均有效
-     */
-    private Integer validUserId;
-
-    /**
-     * 满减型 - 使用门槛
-     */
-    private Integer threshold;
-
-    /**
-     * 折扣型 - 折扣(介于0~1之间)
-     */
-    private double discount;
-
-    /**
-     * 满减型 - 优惠金额
+     * 优惠券元信息
+     * @see Coupon 类的metadata属性
      */
-    private Integer cutDown;
+    private String metadata;
 
     /**
      * 生效时间
@@ -89,13 +75,10 @@ public class CouponVO {
     public CouponVO(Coupon coupon) {
         this.id = coupon.getId();
         this.type = coupon.getType().toString();
+        this.scope = coupon.getScope().toString();
         this.name = coupon.getName();
         this.description = coupon.getDescription();
-        this.courseId = coupon.getCourseId();
-        this.validUserId = coupon.getValidUserId();
-        this.threshold = coupon.getThreshold();
-        this.discount = coupon.getDiscount();
-        this.cutDown = coupon.getCutDown();
+        this.metadata = coupon.getMetadata();
         this.startTime = coupon.getStartTime();
         this.endTime = coupon.getEndTime();
         this.valid = coupon.isValid();

+ 4 - 0
src/main/java/cn/seecoder/courselearning/vo/CourseOrderVO.java

@@ -6,6 +6,7 @@ import lombok.Data;
 import lombok.NonNull;
 
 import java.util.Date;
+import java.util.List;
 
 @Data
 public class CourseOrderVO {
@@ -28,6 +29,9 @@ public class CourseOrderVO {
 
     private Integer status;
 
+    // 目前已经使用的优惠券
+    private List<CouponVO> usedCoupons;
+
     public CourseOrderVO() {
     }
 

+ 20 - 28
src/main/resources/mapper/CouponMapper.xml

@@ -4,13 +4,10 @@
   <resultMap id="BaseResultMap" type="cn.seecoder.courselearning.po.Coupon">
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="type" jdbcType="VARCHAR" property="type" />
+    <result column="scope" jdbcType="VARCHAR" property="scope" />
     <result column="name" jdbcType="VARCHAR" property="name" />
     <result column="description" jdbcType="VARCHAR" property="description" />
-    <result column="course_id" jdbcType="INTEGER" property="courseId" />
-    <result column="valid_user_id" jdbcType="INTEGER" property="validUserId" />
-    <result column="threshold" jdbcType="INTEGER" property="threshold" />
-    <result column="discount" jdbcType="DOUBLE" property="discount" />
-    <result column="cut_down" jdbcType="INTEGER" property="cutDown" />
+    <result column="metadata" jdbcType="VARCHAR" property="metadata" />
     <result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
     <result column="end_time" jdbcType="TIMESTAMP" property="endTime" />
     <result column="is_valid" jdbcType="BIT" property="valid" />
@@ -21,27 +18,22 @@
     where id = #{id,jdbcType=INTEGER}
   </delete>
   <insert id="insert" parameterType="cn.seecoder.courselearning.po.Coupon" useGeneratedKeys="true" keyProperty="id">
-    insert into coupon (id, type, name, 
-      description, course_id, valid_user_id, 
-      threshold, discount, cut_down, 
+    insert into coupon (id, type, scope, name,
+      description, metadata,
       start_time, end_time, is_valid, 
       is_sharable)
-    values (#{id,jdbcType=INTEGER}, #{type,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, 
-      #{description,jdbcType=VARCHAR}, #{courseId,jdbcType=INTEGER}, #{validUserId,jdbcType=INTEGER}, 
-      #{threshold,jdbcType=INTEGER}, #{discount,jdbcType=DOUBLE}, #{cutDown,jdbcType=INTEGER}, 
+    values (#{id,jdbcType=INTEGER}, #{type,jdbcType=VARCHAR}, #{scope,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
+      #{description,jdbcType=VARCHAR}, #{metadata,jdbcType=VARCHAR},
       #{startTime,jdbcType=TIMESTAMP}, #{endTime,jdbcType=TIMESTAMP}, #{valid,jdbcType=BIT}, 
       #{sharable,jdbcType=BIT})
   </insert>
   <update id="updateByPrimaryKey" parameterType="cn.seecoder.courselearning.po.Coupon">
     update coupon
     set type = #{type,jdbcType=VARCHAR},
+      scope = #{scope,jdbcType=VARCHAR},
       name = #{name,jdbcType=VARCHAR},
       description = #{description,jdbcType=VARCHAR},
-      course_id = #{courseId,jdbcType=INTEGER},
-      valid_user_id = #{validUserId,jdbcType=INTEGER},
-      threshold = #{threshold,jdbcType=INTEGER},
-      discount = #{discount,jdbcType=DOUBLE},
-      cut_down = #{cutDown,jdbcType=INTEGER},
+      metadata = #{cutDown,jdbcType=VARCHAR},
       start_time = #{startTime,jdbcType=TIMESTAMP},
       end_time = #{endTime,jdbcType=TIMESTAMP},
       is_valid = #{valid,jdbcType=BIT},
@@ -49,26 +41,26 @@
     where id = #{id,jdbcType=INTEGER}
   </update>
   <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
-    select id, type, name, description, course_id, valid_user_id, threshold, discount, cut_down, 
+    select id, type, scope, name, description, metadata,
     start_time, end_time, is_valid, is_sharable
     from coupon
     where id = #{id,jdbcType=INTEGER}
   </select>
   <select id="selectAll" resultMap="BaseResultMap">
-    select id, type, name, description, course_id, valid_user_id, threshold, discount, cut_down, 
+    select id, type, scope, name, description, metadata,
     start_time, end_time, is_valid, is_sharable
     from coupon
   </select>
-  <select id="selectByCourseIdAndUserId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
-    select id, type, name, description, course_id, valid_user_id, threshold, discount, cut_down,
-           start_time, end_time, is_valid, is_sharable
-    from coupon
-    where course_id = #{courseId,jdbcType=INTEGER} and valid_user_id = #{validUserId,jdbcType=INTEGER}
-  </select>
   <select id="selectByUserId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
-    select id, type, name, description, course_id, valid_user_id, threshold, discount, cut_down,
-           start_time, end_time, is_valid, is_sharable
-    from coupon
-    where valid_user_id = #{validUserId,jdbcType=INTEGER}
+    select c.id, c.type, c.scope, c.name, c.description, c.metadata,
+           c.start_time, c.end_time, c.is_valid, c.is_sharable
+    from coupon c, user_coupon uc
+    where uc.user_id = #{userId,jdbcType=INTEGER} and c.id = uc.coupon_id
+  </select>
+  <select id="selectByOrderId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
+    select c.id, c.type, c.scope, c.name, c.description, c.metadata,
+           c.start_time, c.end_time, c.is_valid, c.is_sharable
+    from coupon c, course_order_coupon coc
+    where coc.order_id = #{orderId,jdbcType=INTEGER} and c.id = coc.coupon_id
   </select>
 </mapper>

+ 26 - 0
src/main/resources/mapper/CourseOrderCouponMapper.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.seecoder.courselearning.mapperservice.CourseOrderCouponMapper">
+    <resultMap id="BaseResultMap" type="cn.seecoder.courselearning.po.CourseOrderCoupon">
+        <id column="order_id" jdbcType="INTEGER" property="orderId" />
+        <id column="coupon_id" jdbcType="INTEGER" property="couponId" />
+    </resultMap>
+    <insert id="insert" parameterType="cn.seecoder.courselearning.po.CourseOrderCoupon">
+    insert into course_order_coupon (order_id, coupon_id)
+    values (#{orderId,jdbcType=INTEGER}, #{couponId,jdbcType=INTEGER})
+    </insert>
+    <select id="selectByOrderId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
+    select order_id, coupon_id
+    from course_order_coupon
+    where order_id = #{orderId,jdbcType=INTEGER}
+  </select>
+    <select id="selectByOrderIdAndCouponId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
+    select order_id, coupon_id
+    from course_order_coupon
+    where order_id = #{orderId,jdbcType=INTEGER} and coupon_id = #{couponId,jdbcType=INTEGER}
+  </select>
+    <delete id="deleteByOrderIdAndCouponId" parameterType="java.lang.Integer">
+    delete from course_order_coupon
+    where order_id = #{orderId,jdbcType=INTEGER} and coupon_id = #{couponId,jdbcType=INTEGER}
+  </delete>
+</mapper>

+ 2 - 2
src/main/resources/mapper/CourseOrderMapper.xml

@@ -3,7 +3,7 @@
 <mapper namespace="cn.seecoder.courselearning.mapperservice.CourseOrderMapper">
   <resultMap id="BaseResultMap" type="cn.seecoder.courselearning.po.CourseOrder">
     <id column="id" jdbcType="INTEGER" property="id" />
-    <result column="origin" jdbcType="INTEGER" property="cost" />
+    <result column="origin" jdbcType="INTEGER" property="origin" />
     <result column="cost" jdbcType="INTEGER" property="cost" />
     <result column="course_id" jdbcType="INTEGER" property="courseId" />
     <result column="course_name" jdbcType="VARCHAR" property="courseName" />
@@ -26,7 +26,7 @@
   <update id="updateByPrimaryKey" parameterType="cn.seecoder.courselearning.po.CourseOrder">
     update course_order
     set cost = #{cost,jdbcType=INTEGER},
-      orgin = #{origin,jdbcType=INTEGER},
+      origin = #{origin,jdbcType=INTEGER},
       course_id = #{courseId,jdbcType=INTEGER},
       course_name = #{courseName,jdbcType=VARCHAR},
       create_time = #{createTime,jdbcType=TIMESTAMP},

+ 32 - 0
src/main/resources/mapper/UserCouponMapper.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.seecoder.courselearning.mapperservice.UserCouponMapper">
+    <resultMap id="BaseResultMap" type="cn.seecoder.courselearning.po.UserCoupon">
+        <id column="coupon_id" jdbcType="INTEGER" property="couponId" />
+        <id column="user_id" jdbcType="INTEGER" property="userId" />
+        <result column="nums" jdbcType="INTEGER" property="nums" />
+    </resultMap>
+    <insert id="insert" parameterType="cn.seecoder.courselearning.po.UserCoupon">
+    insert into user_coupon (coupon_id, user_id, nums)
+    values (#{couponId,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER}, #{nums,jdbcType=INTEGER})
+    </insert>
+    <update id="update" parameterType="cn.seecoder.courselearning.po.UserCoupon">
+    update user_coupon
+    set nums = #{nums,jdbcType=INTEGER}
+    where coupon_id = #{couponId,jdbcType=INTEGER} and user_id = #{userId,jdbcType=INTEGER}
+  </update>
+    <select id="selectByCouponIdAndUserId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
+    select coupon_id, user_id, nums
+    from user_coupon
+    where coupon_id = #{couponId,jdbcType=INTEGER} and user_id = #{userId,jdbcType=INTEGER}
+  </select>
+    <select id="selectByUserId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
+    select coupon_id, user_id, nums
+    from user_coupon
+    where user_id = #{userId,jdbcType=INTEGER}
+  </select>
+    <delete id="deleteByUserIdAndCouponId" parameterType="java.lang.Integer">
+    delete from user_coupon
+    where user_id = #{userId,jdbcType=INTEGER} and coupon_id = #{couponId,jdbcType=INTEGER}
+  </delete>
+</mapper>