当前位置: 首页 > news >正文

第12天 优惠卷的使用

怎么解决重复提交订单?

在订单确认页生成一个预订单ID,并返回给前端,真正下订单的时候会把这个id传给后端,把这个id作为数据库主键就可以防止重复提交订单。 idwork.getid()

inner join 和外连接区别

inner join 只返回两个表链结满足条件的,left  right 外连接 不满足条件的

内连接,也被称为自然连接,只有两个表相匹配的行才能在结果集中出现。返回的结果集选取了两个表中所有相匹配的数据,舍弃了不匹配的数据。由于内连接是从结果表中删除与其他连接表中没有匹配的所有行,所以内连接可能会造成信息的丢失。

外连接不仅包含符合连接条件的行,还包含左表(左连接时)、右表(右连接时)或两个边接表(全外连接)中的所有数据行。SQL外连接共有三种类型:左外连接(关键字为LEFT OUTER JOIN)、右外连接(关键字为RIGHT OUTER JOIN)和全外连接(关键字为FULL OUTER JOIN)。外连接的用法和内连接一样,只是将INNER JOIN关键字替换为相应的外连接关键字即可。

内连接只显示符合连接条件的记录,外连接除了显示符合条件的记录外,还显示表中的记录,例如,如果使用右外连接,还显示右表中的记录。

maptoint 

前端传ids =[1,2,3]s时,后端用@RequestParam接收

优惠券使用 

不过,新的问题来了,用户购物的时候自然要选择优惠券来使用。而现在主流的购物网站都会有优惠券的智能推荐功能,那么:

  • 优惠券的类型不同,折扣计算规则该如何用代码表示?

  • 如何组合优惠券使用才能让用户得到最大优惠?

  • 优惠券叠加的计算算法是怎样的?

  • 如果下单时使用了优惠券,用户退款时又该如何处理?

 

优惠券规则定义

 

所谓的优惠券方案推荐,就是从用户的所有优惠券中筛选出可用的优惠券,并且计算哪种优惠方案用券最少,优惠金额最高。

因此这里包含了对优惠券的下列需求:

  • 判断一个优惠券是否可用,也就是检查订单金额是否达到优惠券使用门槛

  • 按照优惠规则计算优惠金额,能够计算才能比较并找出最优方案

  • 生成优惠券规则描述,目的是在页面直观的展示各种方案,供用户选择

package com.tianji.promotion.strategy.discount;import com.tianji.promotion.domain.po.Coupon;/*** <p>优惠券折扣功能接口</p>*/
public interface Discount {/*** 判断当前价格是否满足优惠券使用限制* @param totalAmount 订单总价* @param coupon 优惠券信息* @return 是否可以使用优惠券*/boolean canUse(int totalAmount, Coupon coupon);/*** 计算折扣金额* @param totalAmount 总金额* @param coupon 优惠券信息* @return 折扣金额*/int calculateDiscount(int totalAmount, Coupon coupon);/*** 根据优惠券规则返回规则描述信息* @return 规则描述信息*/String getRule(Coupon coupon);
}

 

 // 工厂模式

public class DiscountStrategy {private final static EnumMap<DiscountType, Discount> strategies;static {strategies = new EnumMap<>(DiscountType.class);strategies.put(DiscountType.NO_THRESHOLD, new NoThresholdDiscount());strategies.put(DiscountType.PER_PRICE_DISCOUNT, new PerPriceDiscount());strategies.put(DiscountType.RATE_DISCOUNT, new RateDiscount());strategies.put(DiscountType.PRICE_DISCOUNT, new PriceDiscount());}public static Discount getDiscount(DiscountType type) {return strategies.get(type);}
}

 根据优惠卷的类型得到对象的实现对象,然后判断传过来金额数目,判断对于这个数目这个优惠卷是否可用,优惠金额是多少,规则描述是怎样的

就比如说订单金额1000,这个1000的金额是否达到这个优惠卷的门槛了

 这个是无门槛优惠卷的实现

@RequiredArgsConstructor
public class NoThresholdDiscount implements Discount{private static final String RULE_TEMPLATE = "无门槛抵{}元";@Overridepublic boolean canUse(int totalAmount, Coupon coupon) {return totalAmount > coupon.getDiscountValue();}@Overridepublic int calculateDiscount(int totalAmount, Coupon coupon) {return coupon.getDiscountValue();}@Overridepublic String getRule(Coupon coupon) {return StringUtils.format(RULE_TEMPLATE, NumberUtils.scaleToStr(coupon.getDiscountValue(), 2));}
}

优惠券智能推荐

好了,优惠券规则定义好之后,我们就可以正式开发优惠券的相关功能了。

第一个就是优惠券券方案推荐功能。在订单确认页面,前端会向交易微服务发起预下单请求,以获取id和优惠方案列表,页面请求如图:

 交易服务首先需要查询课程信息,生成订单id,然后还需要调用优惠促销服务。而促销服务则需要根据订单中的课程信息查询当前用户的优惠券,并给出推荐的优惠组合方案,供用户在页面选择:

思路分析

简单来说,这就是一个查询优惠券、计算折扣、筛选最优解的过程。整体流程如下:

1.查询用户的可用优惠卷

2.初步筛选(先不看使用范围,先直接把没有达到优惠金额门槛的筛掉)

3.细晒(查询出每个优惠卷的可有范围,查看在这个范围中是否可用)

4. 全排列,对每个排列组合查看优惠卷是否可用 ,优惠金额是多少

5.使用多线程计算优惠金额

6.选择最优方案(卷相同的话,选金额最高的(因为排列顺序不同,优惠金额也可能不同),优惠金额相同,选用卷数量最少的)

 代码分析

首先弄明白返回什么,前端传递的参数是什么

返回的是多个list,每个list中是这套卷组合的优惠金额

参数是课程id 分类 价格

 

 第一步:查询当前用户的优惠卷(记得判断是否为空)

 第二步:初筛(把不能使用的优惠局去掉)

第三步:细筛(根据优惠卷适用范围)

 循环遍历优惠卷是否有限定范围,有限定范围的话去查找该优惠卷限定范围,看限定范围里是否有前端传来的课程,没有下一个循环,有的话看是否达到优惠卷使用门槛,最后放到map集合中。

map中放的就是 优惠卷 对应该优惠卷对应前端传来的课程中可用的课程

private Map<Coupon,List<OrderCourseDTO>> findAvailableCoupons(List<Coupon> coupons,List<OrderCourseDTO> orderCourses){Map<Coupon,List<OrderCourseDTO>> map = new HashMap<>();//循环遍历初筛后的优惠卷集合for (Coupon coupon : coupons) {// 2. 找出每一个优惠卷的可用课程,默认都可用,如果有限定范围则删选出去List<OrderCourseDTO>  availableCourses = orderCourses;// 2.1 判断优惠卷是否限定了范围,没有限定范围就是默认都可用if (coupon.getSpecific()){//2.2 查询限定范围 查询coupon_scope表List<CouponScope> scopeList = couponScopeService.lambdaQuery().eq(CouponScope::getCouponId, coupon.getId()).list();// 2.3得到限定范围的id集合List<Long> scopeIds = scopeList.stream().map(CouponScope::getCouponId).collect(Collectors.toList());// 2.4 从ordercourses 订单中所有的课程集合 筛选该范围内的课程availableCourses = orderCourses.stream().filter(new Predicate<OrderCourseDTO>() {@Overridepublic boolean test(OrderCourseDTO orderCourseDTO) {return scopeIds.contains(orderCourseDTO.getCateId());}}).collect(Collectors.toList());if (CollUtils.isEmpty(availableCourses)){continue;  // 没有可用课程,直接下一次循环}}// 3.计算该优惠卷是否可用 如果可用 添加到mapint totalSum = availableCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();// 判断优惠卷是否可用 如果可用 则添加到mapDiscount discount = getDiscount(coupon.getDiscountType());if (discount.canUse(totalSum,coupon)){map.put(coupon,availableCourses);}}return map;}

第四步 全排列

全排列的工具类

/*** 基于回溯算法的全排列工具类*/
public class PermuteUtil {/*** 将[0~n)的所有数字重组,生成不重复的所有排列方案** @param n 数字n* @return 排列组合*/public static List<List<Byte>> permute(int n) {List<List<Byte>> res = new ArrayList<>();List<Byte> input = new ArrayList<>(n);for (byte i = 0; i < n; i++) {input.add(i);}backtrack(n, input, res, 0);return res;}/*** 将指定集合中的元素重组,生成所有的排列组合方案** @param input 输入的集合* @param <T>   集合类型* @return 重组后的集合方案*/public static <T> List<List<T>> permute(List<T> input) {List<List<T>> res = new ArrayList<>();backtrack(input.size(), input, res, 0);return res;}private static <T> void backtrack(int n, List<T> input, List<List<T>> 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);}}
}

细筛之后得到map,然后取得map中所有的key,对这些key做全排列,并添加 该组合中对应的单卷

 第五步 对每套排列组合做循环,得到每一套组合的优惠明细

这个dto是用来记录,使用了方案后优惠的明细

 detailmap是为了记录使用了某个优惠卷之后 每个课程的优惠价格,一开始初始化为优惠金额为0

    /*** 查看每一种优惠卷排序方案对应的优惠卷优惠明细* @param avaMap  能用的优惠卷 对应订单中可用的课程的map* @param courses 订单中的课程* @param solution  优惠卷使用顺序* @return   这一套优惠卷使用顺序的 优惠价格等*/private CouponDiscountDTO calculateSolutionDiscount(Map<Coupon,List<OrderCourseDTO>> avaMap,List<OrderCourseDTO> courses,List<Coupon> solution){//1.创建方案结果dto对象CouponDiscountDTO dto = new CouponDiscountDTO();// 2. 初始化商品id 和 商品折扣明细的映射,初始折扣明细全为0,设置map key为 商品的id  value初始值都为0Map<Long, Integer> detailMap = courses.stream().collect(Collectors.toMap(OrderCourseDTO::getId, c -> 0));// 3. 循环方案,计算优惠信息for (Coupon coupon : solution) {// 得出该优惠卷对应的可使用课程List<OrderCourseDTO> availableCourses = avaMap.get(coupon);//  计算可用课程的总金额(商品价格-该课程的折扣明细)int totalAmount = availableCourses.stream().mapToInt(value -> value.getPrice() - detailMap.get(value.getId())).sum();// 判断优惠卷是否可用Discount discount = getDiscount(coupon.getDiscountType());if (!discount.canUse(totalAmount,coupon)){continue; // 不可用 跳出循环 继续处理下一次循环}// 计算该优惠卷使用后的折扣值int discountAmount = discount.calculateDiscount(totalAmount, coupon);// 计算商品的折扣明细 更新到 detailMapcalculateDiscountDetails(detailMap, availableCourses, totalAmount, discountAmount);// 累加每一个优惠卷的优惠金额 赋值给方案结果dto对象dto.getIds().add(coupon.getId()); // 只要执行这句话一维这这个优惠卷生效了dto.getRules().add(discount.getRule(coupon));dto.setDiscountAmount(discountAmount + dto.getDiscountAmount()); // 不能覆盖 应该是所有生效的优惠卷 累加的结果}return dto;}

 计算折扣明细,为了防止出现无穷,最后一个课程的折扣金额用   总的折扣金额 - 前面的课程的折扣金额

多线程改造计算优惠明细

 CountDownLatch latch = new CountDownLatch(solutions.size());for (List<Coupon> solution : solutions) {CompletableFuture.supplyAsync(new Supplier<CouponDiscountDTO>() {@Overridepublic CouponDiscountDTO get() {CouponDiscountDTO dto = calculateSolutionDiscount(availableCouponMap,orderCourses,solution);return dto;}},discountSolutionExecutor).thenAccept(new Consumer<CouponDiscountDTO>() {   // 上面return的dto就是下面的参数@Overridepublic void accept(CouponDiscountDTO dto) {dtos.add(dto);latch.countDown(); // 计数器减一}});try {latch.await(2, TimeUnit.SECONDS);} catch (InterruptedException e) {log.error("多线程计算优惠明细报错!!!");}}

 最后一步:计算最优解

现在,我们计算出了成吨的优惠方案及其优惠金额,但是该如何从其中筛选出最优方案呢?最优方案的标准又是什么呢?

首先来看最优标准:

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

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

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

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

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

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

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

例子:

比如 最开始 卷1 3 2 优惠金额是50,放入两个map中,然后是 卷 1 2 3 优惠金额20,小于之前的优惠金额,跳过,卷3 2 1 优惠金额是70,则会替换左边map的优惠方案,同时在右边map中新增一个70的键值对,卷1 2优惠金额也是70,则会替换掉原来70的值。这样下来,左边的map 同一个组合的只有一个。取交集正好满足最优方案。

       private List<CouponDiscountDTO> findBestSolution(List<CouponDiscountDTO> solutions) {// 1. 创建两个map 分别记录用卷相同,金额最高     金额相同,用卷最少Map<String ,CouponDiscountDTO> moreDiscountMap = new HashMap<>();Map<Integer ,CouponDiscountDTO> lessCouponMap = new HashMap<>();// 2 循环方案,向map中记录  用卷相同 金额最高       金额相同,用卷最少for (CouponDiscountDTO solution : solutions) {// 2.1 对优惠卷id升序,转字符串然后以逗号拼接String ids = solution.getIds().stream().sorted(Comparator.comparing(Long::longValue)).map(String::valueOf).collect(Collectors.joining(","));// 2.2 从 moreDiscountMap中取 旧的记录判断旧方案是否大于等于 当前优惠方案CouponDiscountDTO old = moreDiscountMap.get(ids);if (old!=null && old.getDiscountAmount()>= solution.getDiscountAmount()){continue;}// 2.从lessCouponMap中取旧的记录 判断旧的方案用卷数量 小于当前方案用卷数量old = lessCouponMap.get(solution.getDiscountAmount());int newSize = solution.getIds().size(); //当前方案的用卷数量if (old!=null && newSize>1 && old.getIds().size() <= newSize){continue;}moreDiscountMap.put(ids,solution);lessCouponMap.put(solution.getDiscountAmount(),solution);}Collection<CouponDiscountDTO> bestSolution = CollUtils.intersection(moreDiscountMap.values(), lessCouponMap.values());// 排序 优惠金额降序return bestSolution.stream().sorted(Comparator.comparing(CouponDiscountDTO::getDiscountAmount).reversed()).collect(Collectors.toList());}

多线程


http://www.mrgr.cn/news/484.html

相关文章:

  • 2024年运营技术与网络安全态势研究报告:遭遇多次网络威胁的比例暴增
  • 克服编程学习中的挫败感,收获满满的成就感
  • 爬虫配置代理:保护隐私有效地抓取数据
  • 超网和无类间路由是什么?
  • 尊享奢睡新境界:康姿百德柔压磁性枕匠心设计引领品质睡眠革命
  • C# VideoCapture 多路视频播放
  • 在亚马逊云科技上部署开源大模型并利用RAG和LangChain开发生成式AI应用
  • vuex的原理和使用方法
  • DDPM | 扩散模型代码详解【较为详细细致!!!】
  • [SWPUCTF 2021 新生赛]babyrce
  • RegFormer:用于大规模点云配准的高效投影感知Transformer网络
  • Cmake编译工程
  • leetcode350. 两个数组的交集 II,哈希表
  • leetcode_53. 最大子数组和
  • MCU复位RAM会保持吗,如何实现复位时变量数据保持
  • 网络编程 8/15 基于UDP多人聊天室
  • linux部署elasticserch单节点
  • js取消焦点事件
  • 【边缘计算与智能家居】边缘计算在智能家居中的应用
  • c#实现数据导出为PDF的方式