尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

天机学堂day12学习(完结撒花) - 教程

天机学堂day12学习(完结撒花) - 教程
📅 发布时间:2026/6/22 3:48:05

一、优惠券规则定义

1.1.业务流程分析

1.2.优惠券规则定义

二、优惠券智能推荐

2.1.思路分析

2.2.定义接口

  2.2.1.接口基础信息

  2.2.2.实体准备

  在tj-api dto包下新建 promotion 包导入OrderCourseDTO 和 CouponDiscountDTO

OrderCourseDTO

package com.tianji.api.dto.promotion;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
@ApiModel(description = "订单中的课程信息")
public class OrderCourseDTO {@ApiModelProperty("课id")private Long id;@ApiModelProperty("课程的三级分类id")private Long cateId;@ApiModelProperty("课程价格")private Integer price;
}

CouponDiscountDTO

package com.tianji.api.dto.promotion;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Data
@ApiModel(description = "订单的可用优惠券及折扣信息")
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class CouponDiscountDTO {@ApiModelProperty("用户优惠券id集合")private List ids = new ArrayList<>();@ApiModelProperty("优惠券规则")private List rules = new ArrayList<>();@ApiModelProperty("本订单最大优惠金额")private Integer discountAmount = 0;
}

  2.2.3.查询我的优惠券可用方案代码实现

    2.2.3.1.UserCouponController
package com.tianji.promotion.controller;
import com.tianji.api.dto.promotion.CouponDiscountDTO;
import com.tianji.api.dto.promotion.OrderCourseDTO;
// ... 略
/*** 

* 用户领取优惠券的记录,是真正使用的优惠券信息 控制器*

** @author 虎哥*/ @RestController @RequiredArgsConstructor @RequestMapping("/user-coupons") @Api(tags = "优惠券相关接口") public class UserCouponController {private final IUserCouponService userCouponService;private final IDiscountService discountService;// ... 略@ApiOperation("查询我的优惠券可用方案")@PostMapping("/available")public List findDiscountSolution(@RequestBody List orderCourses){return discountService.findDiscountSolution(orderCourses);} }
    2.2.3.2.新建 IDiscountService
package com.tianji.promotion.service;
import com.tianji.api.dto.promotion.CouponDiscountDTO;
import com.tianji.api.dto.promotion.OrderCourseDTO;
import java.util.List;
public interface IDiscountService {List findDiscountSolution(List orderCourses);
}
    2.2.3.2.新建 DiscountServiceImpl
package com.tianji.promotion.service.impl;
import com.tianji.api.dto.promotion.CouponDiscountDTO;
import com.tianji.api.dto.promotion.OrderCourseDTO;
import java.util.*;
@Slf4j
@Service
@RequiredArgsConstructor
public class DiscountServiceImpl implements IDiscountService {@Overridepublic List findDiscountSolution(List orderCourses) {//TODOreturn null;}
}

2.3.查询用户券并初步筛选

  2.3.1.编写查询SQL语句

UserCouponMapper

public interface UserCouponMapper extends BaseMapper {List queryMyCoupons(@Param("userId") Long userId);
}

UserCouponMapper.xml


  2.3.2.实现查询和初筛 DiscountServiceImpl

package com.tianji.promotion.service.impl;
import com.tianji.api.dto.promotion.CouponDiscountDTO;
import com.tianji.api.dto.promotion.OrderCourseDTO;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.IDiscountService;
import com.tianji.promotion.strategy.discount.Discount;
import com.tianji.promotion.strategy.discount.DiscountStrategy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class DiscountServiceImpl implements IDiscountService {private final UserCouponMapper userCouponMapper;@Overridepublic List findDiscountSolution(List orderCourses) {// 1.查询我的所有可用优惠券List coupons = userCouponMapper.queryMyCoupons(UserContext.getUser());if (CollUtils.isEmpty(coupons)) {return CollUtils.emptyList();}// 2.初筛// 2.1.计算订单总价int totalAmount = orderCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();// 2.2.筛选可用券List availableCoupons = coupons.stream().filter(c -> DiscountStrategy.getDiscount(c.getDiscountType()).canUse(totalAmount, c)).collect(Collectors.toList());if (CollUtils.isEmpty(availableCoupons)) {return CollUtils.emptyList();}// 3.排列组合出所有方案// 3.1.细筛(找出每一个优惠券的可用的课程,判断课程总价是否达到优惠券的使用需求)// 3.2.排列组合// 4.计算方案的优惠明细// 5.筛选最优解return null;}
}

2.4.细筛

  在 DiscountServiceImpl 中添加细筛方法 findAvailableCoupon

private final ICouponScopeService scopeService;
private Map> findAvailableCoupon(List coupons, List courses) {Map> map = new HashMap<>(coupons.size());for (Coupon coupon : coupons) {// 1.找出优惠券的可用的课程List availableCourses = courses;if (coupon.getSpecific()) {// 1.1.限定了范围,查询券的可用范围List scopes = scopeService.lambdaQuery().eq(CouponScope::getCouponId, coupon.getId()).list();// 1.2.获取范围对应的分类idSet scopeIds = scopes.stream().map(CouponScope::getBizId).collect(Collectors.toSet());// 1.3.筛选课程availableCourses = courses.stream().filter(c -> scopeIds.contains(c.getCateId())).collect(Collectors.toList());}if (CollUtils.isEmpty(availableCourses)) {// 没有任何可用课程,抛弃continue;}// 2.计算课程总价int totalAmount = availableCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();// 3.判断是否可用Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());if (discount.canUse(totalAmount, coupon)) {map.put(coupon, availableCourses);}}return map;
}

2.5.优惠方案全排列组合

在 promotion utils 包下导入回溯算法的工具类:PermuteUtil

package com.tianji.promotion.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/*** 基于回溯算法的全排列工具类*/
public class PermuteUtil {/*** 将[0~n)的所有数字重组,生成不重复的所有排列方案** @param n 数字n* @return 排列组合*/public static List> permute(int n) {List> res = new ArrayList<>();List input = new ArrayList<>(n);for (byte i = 0; i < n; i++) {input.add(i);}backtrack(n, input, res, 0);return res;}/*** 将指定集合中的元素重组,生成所有的排列组合方案** @param input 输入的集合* @param    集合类型* @return 重组后的集合方案*/public static  List> permute(List input) {List> res = new ArrayList<>();backtrack(input.size(), input, res, 0);return res;}private static  void backtrack(int n, List input, List> res, int first) {// 所有数都填完了if (first == n) {res.add(new ArrayList<>(input));}for (int i = first; i < n; i++) {// 动态维护数组Collections.swap(input, first, i);// 继续递归填下一个数backtrack(n, input, res, first + 1);// 撤销操作Collections.swap(input, first, i);}}
}

在 DiscountServiceImpl 中添加细筛和全排列的逻辑

package com.tianji.promotion.service.impl;
import com.tianji.api.dto.promotion.CouponDiscountDTO;
import com.tianji.api.dto.promotion.OrderCourseDTO;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.IDiscountService;
import com.tianji.promotion.strategy.discount.Discount;
import com.tianji.promotion.strategy.discount.DiscountStrategy;
import com.tianji.promotion.utils.PermuteUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class DiscountServiceImpl implements IDiscountService {private final UserCouponMapper userCouponMapper;private final ICouponScopeService scopeService;@Overridepublic List findDiscountSolution(List orderCourses) {// 1.查询我的所有可用优惠券List coupons = userCouponMapper.queryMyCoupons(UserContext.getUser());if (CollUtils.isEmpty(coupons)) {return CollUtils.emptyList();}// 2.初筛// 2.1.计算订单总价int totalAmount = orderCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();// 2.2.筛选可用券List availableCoupons = coupons.stream().filter(c -> DiscountStrategy.getDiscount(c.getDiscountType()).canUse(totalAmount, c)).collect(Collectors.toList());if (CollUtils.isEmpty(availableCoupons)) {return CollUtils.emptyList();}// 3.排列组合出所有方案// 3.1.细筛(找出每一个优惠券的可用的课程,判断课程总价是否达到优惠券的使用需求)Map> availableCouponMap = findAvailableCoupon(availableCoupons, orderCourses);if (CollUtils.isEmpty(availableCouponMap)) {return CollUtils.emptyList();}// 3.2.排列组合availableCoupons = new ArrayList<>(availableCouponMap.keySet());List> solutions = PermuteUtil.permute(availableCoupons);// 3.3.添加单券的方案for (Coupon c : availableCoupons) {solutions.add(List.of(c));}// 4.计算方案的优惠明细// 5.筛选最优解return null;}private Map> findAvailableCoupon(List coupons, List courses) {Map> map = new HashMap<>(coupons.size());for (Coupon coupon : coupons) {// 1.找出优惠券的可用的课程List availableCourses = courses;if (coupon.getSpecific()) {// 1.1.限定了范围,查询券的可用范围List scopes = scopeService.lambdaQuery().eq(CouponScope::getCouponId, coupon.getId()).list();// 1.2.获取范围对应的分类idSet scopeIds = scopes.stream().map(CouponScope::getBizId).collect(Collectors.toSet());// 1.3.筛选课程availableCourses = courses.stream().filter(c -> scopeIds.contains(c.getCateId())).collect(Collectors.toList());}if (CollUtils.isEmpty(availableCourses)) {// 没有任何可用课程,抛弃continue;}// 2.计算课程总价int totalAmount = availableCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();// 3.判断是否可用Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());if (discount.canUse(totalAmount, coupon)) {map.put(coupon, availableCourses);}}return map;}
}

2.5.计算优惠明细

  2.5.1.单张优惠券算法

  单张优惠券的优惠金额计算流程如下:

  • 1)判断优惠券限定范围,找出范围内的课程

  • 2)计算课程总价

  • 3)判断券是否可用

  • 4)计算优惠金额

算法来判断:

  • 1)判断限定范围:这张券限定分类 b,对应的商品序号是2、3

  • 2)计算课程总价:商品序号2、3的总价为200

  • 3)判断是否可用:总价刚好达到优惠券满减门槛200,可以使用

  • 4)计算优惠:满200减100,因此最终优惠金额就是100元

  2.5.2.券叠加算法

  券叠加算法比单券算法需要多一步:

  • 1)判断优惠券限定范围,找出范围内的课程

  • 2)计算课程总价

  • 3)判断券是否可用

  • 4)计算优惠金额

  • 5)计算优惠明细

  2.5.3.编码实现算法 DiscountServiceImpl

package com.tianji.promotion.service.impl;
import com.tianji.api.dto.promotion.CouponDiscountDTO;
import com.tianji.api.dto.promotion.OrderCourseDTO;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.IDiscountService;
import com.tianji.promotion.strategy.discount.Discount;
import com.tianji.promotion.strategy.discount.DiscountStrategy;
import com.tianji.promotion.utils.PermuteUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class DiscountServiceImpl implements IDiscountService {private final UserCouponMapper userCouponMapper;private final ICouponScopeService scopeService;@Overridepublic List findDiscountSolution(List orderCourses) {// 1.查询我的所有可用优惠券List coupons = userCouponMapper.queryMyCoupons(UserContext.getUser());if (CollUtils.isEmpty(coupons)) {return CollUtils.emptyList();}// 2.初筛// 2.1.计算订单总价int totalAmount = orderCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();// 2.2.筛选可用券List availableCoupons = coupons.stream().filter(c -> DiscountStrategy.getDiscount(c.getDiscountType()).canUse(totalAmount, c)).collect(Collectors.toList());if (CollUtils.isEmpty(availableCoupons)) {return CollUtils.emptyList();}// 3.排列组合出所有方案// 3.1.细筛(找出每一个优惠券的可用的课程,判断课程总价是否达到优惠券的使用需求)Map> availableCouponMap = findAvailableCoupon(availableCoupons, orderCourses);if (CollUtils.isEmpty(availableCouponMap)) {return CollUtils.emptyList();}// 3.2.排列组合availableCoupons = new ArrayList<>(availableCouponMap.keySet());List> solutions = PermuteUtil.permute(availableCoupons);// 3.3.添加单券的方案for (Coupon c : availableCoupons) {solutions.add(List.of(c));}// 4.计算方案的优惠明细List list =Collections.synchronizedList(new ArrayList<>(solutions.size()));for (List solution : solutions) {list.add(calculateSolutionDiscount(availableCouponMap, orderCourses, solution));}// 5.筛选最优解return null;}private CouponDiscountDTO calculateSolutionDiscount(Map> couponMap, List courses, List solution) {// 1.初始化DTOCouponDiscountDTO dto = new CouponDiscountDTO();// 2.初始化折扣明细的映射Map detailMap = courses.stream().collect(Collectors.toMap(OrderCourseDTO::getId, oc -> 0));// 3.计算折扣for (Coupon coupon : solution) {// 3.1.获取优惠券限定范围对应的课程List availableCourses = couponMap.get(coupon);// 3.2.计算课程总价(课程原价 - 折扣明细)int totalAmount = availableCourses.stream().mapToInt(oc -> oc.getPrice() - detailMap.get(oc.getId())).sum();// 3.3.判断是否可用Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());if (!discount.canUse(totalAmount, coupon)) {// 券不可用,跳过continue;}// 3.4.计算优惠金额int discountAmount = discount.calculateDiscount(totalAmount, coupon);// 3.5.计算优惠明细calculateDiscountDetails(detailMap, availableCourses, totalAmount, discountAmount);// 3.6.更新DTO数据dto.getIds().add(coupon.getCreater());dto.getRules().add(discount.getRule(coupon));dto.setDiscountAmount(discountAmount + dto.getDiscountAmount());}return dto;}private void calculateDiscountDetails(Map detailMap, List courses,int totalAmount, int discountAmount) {int times = 0;int remainDiscount = discountAmount;for (OrderCourseDTO course : courses) {// 更新课程已计算数量times++;int discount = 0;// 判断是否是最后一个课程if (times == courses.size()) {// 是最后一个课程,总折扣金额 - 之前所有商品的折扣金额之和discount = remainDiscount;} else {// 计算折扣明细(课程价格在总价中占的比例,乘以总的折扣)discount = discountAmount * course.getPrice() / totalAmount;remainDiscount -= discount;}// 更新折扣明细detailMap.put(course.getId(), discount + detailMap.get(course.getId()));}}private Map> findAvailableCoupon(List coupons, List courses) {Map> map = new HashMap<>(coupons.size());for (Coupon coupon : coupons) {// 1.找出优惠券的可用的课程List availableCourses = courses;if (coupon.getSpecific()) {// 1.1.限定了范围,查询券的可用范围List scopes = scopeService.lambdaQuery().eq(CouponScope::getCouponId, coupon.getId()).list();// 1.2.获取范围对应的分类idSet scopeIds = scopes.stream().map(CouponScope::getBizId).collect(Collectors.toSet());// 1.3.筛选课程availableCourses = courses.stream().filter(c -> scopeIds.contains(c.getCateId())).collect(Collectors.toList());}if (CollUtils.isEmpty(availableCourses)) {// 没有任何可用课程,抛弃continue;}// 2.计算课程总价int totalAmount = availableCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();// 3.判断是否可用Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());if (discount.canUse(totalAmount, coupon)) {map.put(coupon, availableCourses);}}return map;}
}

2.6.CompleteableFuture并发计算

新建一个自定义线程池 PromotionConfig

package com.tianji.promotion.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
@Configuration
public class PromotionConfig {@Beanpublic Executor generateExchangeCodeExecutor(){ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 1.核心线程池大小executor.setCorePoolSize(2);// 2.最大线程池大小executor.setMaxPoolSize(5);// 3.队列大小executor.setQueueCapacity(200);// 4.线程名称executor.setThreadNamePrefix("exchange-code-handler-");// 5.拒绝策略executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}//新增discountSolutionExecutor@Beanpublic Executor discountSolutionExecutor(){ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 1.核心线程池大小executor.setCorePoolSize(12);// 2.最大线程池大小executor.setMaxPoolSize(12);// 3.队列大小executor.setQueueCapacity(99999);// 4.线程名称executor.setThreadNamePrefix("discount-solution-calculator-");// 5.拒绝策略executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());executor.initialize();return executor;}
}

修改 DiscountServiceImpl 中查询优惠方案的函数主体

package com.tianji.promotion.service.impl;
import com.tianji.api.dto.promotion.CouponDiscountDTO;
import com.tianji.api.dto.promotion.OrderCourseDTO;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.IDiscountService;
import com.tianji.promotion.strategy.discount.Discount;
import com.tianji.promotion.strategy.discount.DiscountStrategy;
import com.tianji.promotion.utils.PermuteUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class DiscountServiceImpl implements IDiscountService {private final UserCouponMapper userCouponMapper;private final ICouponScopeService scopeService;private final Executor discountSolutionExecutor;@Overridepublic List findDiscountSolution(List orderCourses) {// 1.查询我的所有可用优惠券List coupons = userCouponMapper.queryMyCoupons(UserContext.getUser());if (CollUtils.isEmpty(coupons)) {return CollUtils.emptyList();}// 2.初筛// 2.1.计算订单总价int totalAmount = orderCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();// 2.2.筛选可用券List availableCoupons = coupons.stream().filter(c -> DiscountStrategy.getDiscount(c.getDiscountType()).canUse(totalAmount, c)).collect(Collectors.toList());if (CollUtils.isEmpty(availableCoupons)) {return CollUtils.emptyList();}// 3.排列组合出所有方案// 3.1.细筛(找出每一个优惠券的可用的课程,判断课程总价是否达到优惠券的使用需求)Map> availableCouponMap = findAvailableCoupon(availableCoupons, orderCourses);if (CollUtils.isEmpty(availableCouponMap)) {return CollUtils.emptyList();}// 3.2.排列组合availableCoupons = new ArrayList<>(availableCouponMap.keySet());List> solutions = PermuteUtil.permute(availableCoupons);// 3.3.添加单券的方案for (Coupon c : availableCoupons) {solutions.add(List.of(c));}// 4.计算方案的优惠明细
//        List list =
//                Collections.synchronizedList(new ArrayList<>(solutions.size()));
//        for (List solution : solutions) {
//            list.add(calculateSolutionDiscount(availableCouponMap, orderCourses, solution));
//        }// 4.计算方案的优惠明细  使用线程池List list = Collections.synchronizedList(new ArrayList<>(solutions.size()));// 4.1.定义闭锁CountDownLatch latch = new CountDownLatch(solutions.size());for (List solution : solutions) {// 4.2.异步计算CompletableFuture.supplyAsync(() -> calculateSolutionDiscount(availableCouponMap, orderCourses, solution),discountSolutionExecutor    //注意注入 private final Executor discountSolutionExecutor).thenAccept(dto -> {// 4.3.提交任务结果list.add(dto);latch.countDown();});}// 4.4.等待运算结束try {latch.await(1, TimeUnit.SECONDS);} catch (InterruptedException e) {log.error("优惠方案计算被中断,{}", e.getMessage());}// 5.筛选最优解return null;}private CouponDiscountDTO calculateSolutionDiscount(Map> couponMap, List courses, List solution) {// 1.初始化DTOCouponDiscountDTO dto = new CouponDiscountDTO();// 2.初始化折扣明细的映射Map detailMap = courses.stream().collect(Collectors.toMap(OrderCourseDTO::getId, oc -> 0));// 3.计算折扣for (Coupon coupon : solution) {// 3.1.获取优惠券限定范围对应的课程List availableCourses = couponMap.get(coupon);// 3.2.计算课程总价(课程原价 - 折扣明细)int totalAmount = availableCourses.stream().mapToInt(oc -> oc.getPrice() - detailMap.get(oc.getId())).sum();// 3.3.判断是否可用Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());if (!discount.canUse(totalAmount, coupon)) {// 券不可用,跳过continue;}// 3.4.计算优惠金额int discountAmount = discount.calculateDiscount(totalAmount, coupon);// 3.5.计算优惠明细calculateDiscountDetails(detailMap, availableCourses, totalAmount, discountAmount);// 3.6.更新DTO数据dto.getIds().add(coupon.getCreater());dto.getRules().add(discount.getRule(coupon));dto.setDiscountAmount(discountAmount + dto.getDiscountAmount());}return dto;}private void calculateDiscountDetails(Map detailMap, List courses,int totalAmount, int discountAmount) {int times = 0;int remainDiscount = discountAmount;for (OrderCourseDTO course : courses) {// 更新课程已计算数量times++;int discount = 0;// 判断是否是最后一个课程if (times == courses.size()) {// 是最后一个课程,总折扣金额 - 之前所有商品的折扣金额之和discount = remainDiscount;} else {// 计算折扣明细(课程价格在总价中占的比例,乘以总的折扣)discount = discountAmount * course.getPrice() / totalAmount;remainDiscount -= discount;}// 更新折扣明细detailMap.put(course.getId(), discount + detailMap.get(course.getId()));}}private Map> findAvailableCoupon(List coupons, List courses) {Map> map = new HashMap<>(coupons.size());for (Coupon coupon : coupons) {// 1.找出优惠券的可用的课程List availableCourses = courses;if (coupon.getSpecific()) {// 1.1.限定了范围,查询券的可用范围List scopes = scopeService.lambdaQuery().eq(CouponScope::getCouponId, coupon.getId()).list();// 1.2.获取范围对应的分类idSet scopeIds = scopes.stream().map(CouponScope::getBizId).collect(Collectors.toSet());// 1.3.筛选课程availableCourses = courses.stream().filter(c -> scopeIds.contains(c.getCateId())).collect(Collectors.toList());}if (CollUtils.isEmpty(availableCourses)) {// 没有任何可用课程,抛弃continue;}// 2.计算课程总价int totalAmount = availableCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();// 3.判断是否可用Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());if (discount.canUse(totalAmount, coupon)) {map.put(coupon, availableCourses);}}return map;}
}

2.7.筛选最优解

首先来看最优标准:

  • 用券相同时,优惠金额最高的方案

  • 优惠金额相同时,用券最少的方案

  寻找最优解的流程跟找数组中最小值类似:

  • 定义一个变量记录最小值

  • 逐个遍历数组,判断当前元素是否比最小值更小

  • 如果是,则覆盖最小值;如果否,则放弃

  • 循环结束,变量中记录的就是最小值

  其中:

  • 第一个Map用来记录用券相同时,优惠金额最高的方案;

  • 第二个Map用来记录优惠金额相同时,用券最少的方案。

  最终,两个Map的values的交集就是我们要找的最优解。

  最终代码实现

package com.tianji.promotion.service.impl;
import com.tianji.api.dto.promotion.CouponDiscountDTO;
import com.tianji.api.dto.promotion.OrderCourseDTO;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.IDiscountService;
import com.tianji.promotion.strategy.discount.Discount;
import com.tianji.promotion.strategy.discount.DiscountStrategy;
import com.tianji.promotion.utils.PermuteUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class DiscountServiceImpl implements IDiscountService {private final UserCouponMapper userCouponMapper;private final ICouponScopeService scopeService;private final Executor discountSolutionExecutor;@Overridepublic List findDiscountSolution(List orderCourses) {// 1.查询我的所有可用优惠券List coupons = userCouponMapper.queryMyCoupons(UserContext.getUser());if (CollUtils.isEmpty(coupons)) {return CollUtils.emptyList();}// 2.初筛// 2.1.计算订单总价int totalAmount = orderCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();// 2.2.筛选可用券List availableCoupons = coupons.stream().filter(c -> DiscountStrategy.getDiscount(c.getDiscountType()).canUse(totalAmount, c)).collect(Collectors.toList());if (CollUtils.isEmpty(availableCoupons)) {return CollUtils.emptyList();}// 3.排列组合出所有方案// 3.1.细筛(找出每一个优惠券的可用的课程,判断课程总价是否达到优惠券的使用需求)Map> availableCouponMap = findAvailableCoupon(availableCoupons, orderCourses);if (CollUtils.isEmpty(availableCouponMap)) {return CollUtils.emptyList();}// 3.2.排列组合availableCoupons = new ArrayList<>(availableCouponMap.keySet());List> solutions = PermuteUtil.permute(availableCoupons);// 3.3.添加单券的方案for (Coupon c : availableCoupons) {solutions.add(List.of(c));}// 4.计算方案的优惠明细
//        List list =
//                Collections.synchronizedList(new ArrayList<>(solutions.size()));
//        for (List solution : solutions) {
//            list.add(calculateSolutionDiscount(availableCouponMap, orderCourses, solution));
//        }// 4.计算方案的优惠明细  使用线程池List list = Collections.synchronizedList(new ArrayList<>(solutions.size()));// 4.1.定义闭锁CountDownLatch latch = new CountDownLatch(solutions.size());for (List solution : solutions) {// 4.2.异步计算CompletableFuture.supplyAsync(() -> calculateSolutionDiscount(availableCouponMap, orderCourses, solution),discountSolutionExecutor    //注意注入 private final Executor discountSolutionExecutor).thenAccept(dto -> {// 4.3.提交任务结果list.add(dto);latch.countDown();});}// 4.4.等待运算结束try {latch.await(1, TimeUnit.SECONDS);} catch (InterruptedException e) {log.error("优惠方案计算被中断,{}", e.getMessage());}// 5.筛选最优解return findBestSolution(list);}private List findBestSolution(List list) {// 1.准备Map记录最优解Map moreDiscountMap = new HashMap<>();Map lessCouponMap = new HashMap<>();// 2.遍历,筛选最优解for (CouponDiscountDTO solution : list) {// 2.1.计算当前方案的id组合String ids = solution.getIds().stream().sorted(Long::compare).map(String::valueOf).collect(Collectors.joining(","));// 2.2.比较用券相同时,优惠金额是否最大CouponDiscountDTO best = moreDiscountMap.get(ids);if (best != null && best.getDiscountAmount() >= solution.getDiscountAmount()) {// 当前方案优惠金额少,跳过continue;}// 2.3.比较金额相同时,用券数量是否最少best = lessCouponMap.get(solution.getDiscountAmount());int size = solution.getIds().size();if (size > 1 && best != null && best.getIds().size() <= size) {// 当前方案用券更多,放弃continue;}// 2.4.更新最优解moreDiscountMap.put(ids, solution);lessCouponMap.put(solution.getDiscountAmount(), solution);}// 3.求交集Collection bestSolutions = CollUtils.intersection(moreDiscountMap.values(), lessCouponMap.values());// 4.排序,按优惠金额降序return bestSolutions.stream().sorted(Comparator.comparingInt(CouponDiscountDTO::getDiscountAmount).reversed()).collect(Collectors.toList());}private CouponDiscountDTO calculateSolutionDiscount(Map> couponMap, List courses, List solution) {// 1.初始化DTOCouponDiscountDTO dto = new CouponDiscountDTO();// 2.初始化折扣明细的映射Map detailMap = courses.stream().collect(Collectors.toMap(OrderCourseDTO::getId, oc -> 0));// 3.计算折扣for (Coupon coupon : solution) {// 3.1.获取优惠券限定范围对应的课程List availableCourses = couponMap.get(coupon);// 3.2.计算课程总价(课程原价 - 折扣明细)int totalAmount = availableCourses.stream().mapToInt(oc -> oc.getPrice() - detailMap.get(oc.getId())).sum();// 3.3.判断是否可用Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());if (!discount.canUse(totalAmount, coupon)) {// 券不可用,跳过continue;}// 3.4.计算优惠金额int discountAmount = discount.calculateDiscount(totalAmount, coupon);// 3.5.计算优惠明细calculateDiscountDetails(detailMap, availableCourses, totalAmount, discountAmount);// 3.6.更新DTO数据dto.getIds().add(coupon.getCreater());dto.getRules().add(discount.getRule(coupon));dto.setDiscountAmount(discountAmount + dto.getDiscountAmount());}return dto;}private void calculateDiscountDetails(Map detailMap, List courses,int totalAmount, int discountAmount) {int times = 0;int remainDiscount = discountAmount;for (OrderCourseDTO course : courses) {// 更新课程已计算数量times++;int discount = 0;// 判断是否是最后一个课程if (times == courses.size()) {// 是最后一个课程,总折扣金额 - 之前所有商品的折扣金额之和discount = remainDiscount;} else {// 计算折扣明细(课程价格在总价中占的比例,乘以总的折扣)discount = discountAmount * course.getPrice() / totalAmount;remainDiscount -= discount;}// 更新折扣明细detailMap.put(course.getId(), discount + detailMap.get(course.getId()));}}private Map> findAvailableCoupon(List coupons, List courses) {Map> map = new HashMap<>(coupons.size());for (Coupon coupon : coupons) {// 1.找出优惠券的可用的课程List availableCourses = courses;if (coupon.getSpecific()) {// 1.1.限定了范围,查询券的可用范围List scopes = scopeService.lambdaQuery().eq(CouponScope::getCouponId, coupon.getId()).list();// 1.2.获取范围对应的分类idSet scopeIds = scopes.stream().map(CouponScope::getBizId).collect(Collectors.toSet());// 1.3.筛选课程availableCourses = courses.stream().filter(c -> scopeIds.contains(c.getCateId())).collect(Collectors.toList());}if (CollUtils.isEmpty(availableCourses)) {// 没有任何可用课程,抛弃continue;}// 2.计算课程总价int totalAmount = availableCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();// 3.判断是否可用Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());if (discount.canUse(totalAmount, coupon)) {map.put(coupon, availableCourses);}}return map;}
}

三、练习(TODO)

参考博客:天机学堂day12所有接口及成功测试结果(含答案练习、个人记录、仅供参考)-CSDN博客

3.1.根据券方案计算订单优惠明细

3.2.核销优惠券

3.3.退还优惠券

3.4.查询优惠券

                        都看到这了,给文涛点个赞支持一下呗!

                                        你的‘赞’,是给与文涛最大的动力鸭

                                        有问题,可以评论区大家一起讨论

                                        后续会在此更新,相关问题及解决方案

相关新闻

  • Open-AutoGLM浏览器助手实战指南:5大核心功能让你秒变自动化高手
  • 2025年专业食品加工降温设备厂家排名,渔船/大型工厂降温设备品牌推荐 - myqiye
  • 河南种子会的参与度高吗?对农业增产有作用吗? - 工业推荐榜

最新新闻

  • LLMbench:基于概率可视化的AI文本比较分析平台实战指南
  • 基于YOLOv8与RexNet-150的两阶段深度学习作弊检测框架实践
  • 数据驱动求解湍流PDF方程:基于条件平均估计与DNS数据的实践指南
  • Android Toolbar实战指南:从XML布局到Kotlin菜单响应
  • HsMod炉石传说插件:55项功能全面增强你的游戏体验
  • 高效解决抖音内容批量下载难题的Douyin-Downloader实战指南

日新闻

  • 2026速览惠州叛逆青少年学校前十大排名名单出炉 - 武汉中职最新信息发布
  • 2026上饶白蚁消杀哪家好?15年本土2大权威白蚁防治公司推荐(金盾虫控/青蚁卫士) - 我叫一
  • 天龙八部单机版终极数据管理工具:5个技巧快速掌握游戏数据编辑

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号