当前位置:   article > 正文

最近在写的支付模块

最近在写的支付模块

最近再写支付模块就到处借鉴 旨在回顾一下。

1.确认订单功能

使用场景是:用户在选择好购物车后,或者是直接选择商品后(选择商品封装为购物车)

这样做是根据尚硅谷来学习的 目前需要这些属性,原因是在确认订单页面后 展现一个最优惠的状态 

1.1实体类

  1. /**
  2. * 用户id
  3. */
  4. @ApiModelProperty("用户id")
  5. private Integer userId;
  6. /**
  7. * 购物车列表
  8. */
  9. @ApiModelProperty("购物车中已选中商品")
  10. private List<CartInfo> cartItemsList;
  11. @ApiModelProperty("最优惠的优惠券id")
  12. private Integer userCouponsId;
  13. /**
  14. * 总金额
  15. */
  16. @ApiModelProperty("总金额")
  17. private Double totalAmount;
  18. /**
  19. * 优惠金额
  20. */
  21. @ApiModelProperty("优惠金额")
  22. private Double discount;
  23. /**
  24. * 实付金额
  25. */
  26. @ApiModelProperty("实付金额")
  27. private Double actuallyPay;

1.2确认订单实现类

期间做了一些修改 之前是设置的有购物车状态 这些步骤都是在后端处理 

后来又采用了前端传递购物车属性 美其名曰叫 减少io次数 缓解数据库压力

通过传入的userId 查找 订单列表 购物车列表

期间有个redis操作 是根据时间 来生成 后续在生成订单时会用到

  1. public OrderConfirmVo confirmOrder(CartConfirmDto cartConfirmDto) {
  2. //获取用户id参数 以方便后续使用
  3. //获取用户地址列表
  4. Integer userId = cartConfirmDto.getUserId();
  5. List<Addresses> addressesList = addressesDao.getByUserId(userId);
  6. //获取购物车中已经选中的商品
  7. List<CartInfo> cartItemsList = cartConfirmDto.getCartItemsList();
  8. // List<CartItems> cartItemsList = cartItemsDao.querySelectedCartItems(userId);
  9. if (cartItemsList.isEmpty()){
  10. throw new PorkException("您购物车中未选中商品",500);
  11. }
  12. for (CartInfo cartInfo : cartItemsList) {
  13. Integer productId = cartInfo.getProductId();
  14. Products products = productsDao.queryById(productId);
  15. cartInfo.setMainPhoto(products.getMainPhoto());
  16. cartInfo.setName(products.getName());
  17. Integer flavorId = cartInfo.getFlavorId();
  18. String flavorDescription = flavorsService.queryNameById(flavorId);
  19. cartInfo.setFlavorDescription(flavorDescription);
  20. }
  21. //生成订单唯一标示
  22. String orderNo = System.currentTimeMillis()+"";
  23. redisTemplate.opsForValue().set(RedisConst.ORDER_REPEAT+orderNo,orderNo,24, TimeUnit.HOURS);
  24. //1.获取最优惠优惠券
  25. //2.先获取订单价格
  26. //3.找到实付金额
  27. // Double totalPrice = cartItemsDao.getActuallyPay(userId);
  28. // UserCoupons userCoupon = userCouponsDao.queryOptimalUserCoupon(totalPrice, userId);
  29. // Double discount = userCoupon.getCoupons().getDiscount();
  30. // Double actuallyPay = totalPrice - discount;
  31. Double totalPrice = cartConfirmDto.getTotalAmount();
  32. Integer userCouponId = cartConfirmDto.getUserCouponsId();
  33. Double discount = cartConfirmDto.getDiscount();
  34. Double actuallyPay = cartConfirmDto.getActuallyPay();
  35. UserCoupons userCoupon = userCouponsDao.queryById(userCouponId);
  36. //查询可用优惠券
  37. List<UserCoupons> userCoupons = userCouponsDao.queryAvailableUserCoupons(totalPrice, userId);
  38. //进行封装
  39. OrderConfirmVo orderConfirmVo = new OrderConfirmVo(userId,totalPrice,userCoupon,discount,actuallyPay,orderNo,userCoupons,addressesList,cartItemsList);
  40. return orderConfirmVo;
  41. }

1.3返回类

  1. */
  2. @ApiModelProperty("用户id")
  3. private Integer userId;
  4. /**
  5. * 总金额
  6. */
  7. @ApiModelProperty("总金额")
  8. private Double totalAmount;
  9. @ApiModelProperty("最优惠的优惠券")
  10. private UserCoupons userCoupons;
  11. /**
  12. * 优惠金额
  13. */
  14. @ApiModelProperty("优惠金额")
  15. private Double discount;
  16. /**
  17. * 实付金额
  18. */
  19. @ApiModelProperty("实付金额")
  20. private Double actuallyPay;
  21. /**
  22. * 订单号
  23. */
  24. @ApiModelProperty("订单号")
  25. private String orderNo;
  26. /**
  27. * 用户所有优惠券
  28. */
  29. @ApiModelProperty("用户优惠券")
  30. private List<UserCoupons> userCouponsList;
  31. /**
  32. * 用户地址
  33. */
  34. @ApiModelProperty("用户地址列表")
  35. private List<Addresses> addressesList;
  36. /**
  37. * 购物车列表
  38. */
  39. @ApiModelProperty("购物车列表")
  40. private List<CartInfo> cartItemsList;

2.生成订单

2.1请求实体类

生成订单后里面的属性

  1. @ApiModelProperty(value = "使用预生产订单号防重")
  2. private String orderNo;
  3. @ApiModelProperty(value = "用户id")
  4. private Integer userId;
  5. @ApiModelProperty(value = "下单时所使用的地址信息")
  6. private Integer addressesId;
  7. @ApiModelProperty(value = "下单选中的优惠券id")
  8. private Integer userCouponId;
  9. @ApiModelProperty(value = "订单备注")
  10. private String comment;
  11. @ApiModelProperty(value = "所选中商品")
  12. private List<CartInfo> cartItemsList;
  13. @ApiModelProperty(value = "最后订单总价")
  14. private Double totalPrice;
  15. @ApiModelProperty(value = "优惠金额")
  16. private Double discount;
  17. @ApiModelProperty("订单实付金额")
  18. private Double ActuallyPay;

2.2生成订单方法实体类

使用lua脚本来保证原子性 

如果redis中有相同orderNo 则说明正常提交订单 然后把redis删除

期间也有锁单 

2.2.1检查锁

  1. @Override
  2. public Boolean checkAndLock(List<ProductStockVo> productStockVoList, String orderNo) {
  3. //1.判断productStockVoList是否为空
  4. if (CollectionUtils.isEmpty(productStockVoList)){
  5. throw new PorkException(ResultCodeEnum.DATA_ERROR);
  6. }
  7. //2.遍历productStockVoList得到每个商品,验证库存并锁定库存,具备原子性
  8. productStockVoList.stream().forEach(productStockVo -> {
  9. this.checkLock(productStockVo);
  10. });
  11. //3.只要有一个商品锁定失败,所有锁定成功的商品都解锁 用于检查流中是否至少有一个元素满足指定的条件
  12. boolean flag = productStockVoList.stream()
  13. .anyMatch(productStockVo -> !productStockVo.getIsLock());
  14. if (flag){
  15. //所有锁定成功的商品都解锁
  16. productStockVoList.stream().filter(ProductStockVo::getIsLock)
  17. .forEach(productStockVo -> {
  18. flavorsDao.unlockStock(productStockVo.getFlavorId(),productStockVo.getSkuNum());
  19. });
  20. return false;
  21. }
  22. //4 如果所有商品都锁定成功了,redis缓存相关数据,为了方便后面解锁和减库存
  23. redisTemplate.opsForValue()
  24. .set(RedisConst.SROCK_INFO+orderNo,productStockVoList,33, TimeUnit.MINUTES);
  25. return true;
  26. }

2.2.2获得公平锁

  1. private void checkLock(ProductStockVo productStockVo) {
  2. //获取锁 公平锁:谁等待时间长给谁发锁
  3. RLock rLock = this.redissonClient.getFairLock(RedisConst.SKUKEY_PREFIX+productStockVo.getFlavorId());
  4. rLock.lock();
  5. try {
  6. //验证库存
  7. Flavors flavors = flavorsDao.checkStock(productStockVo.getFlavorId(),productStockVo.getSkuNum());
  8. //判断没有满足条件商品,设置isLock值为false,返回
  9. if (flavors == null){
  10. productStockVo.setIsLock(false);
  11. return;
  12. }
  13. //又满足条件商品,锁定库存 update rows 影响行数
  14. Integer rows = flavorsDao.lockStock(productStockVo.getFlavorId(),productStockVo.getSkuNum());
  15. if (rows == 1) {
  16. productStockVo.setIsLock(true);
  17. }
  18. }finally {
  19. //解锁
  20. rLock.unlock();
  21. }
  22. }

2.2.3提交订单 

  1. public OrderGenerateInfo submitOrder(OrderSubmitVo orderSubmitVo) {
  2. //第一步拿出userId确定给那个用户设置订单
  3. Integer userId = orderSubmitVo.getUserId();
  4. //第二步 订单不能重复提交,重复提交验证
  5. //通过redis + lua 脚本实现 //lua脚本保证原子性
  6. //1.获取传递过来的orderNo
  7. String orderNo = orderSubmitVo.getOrderNo();
  8. if (orderNo.isEmpty()){
  9. throw new PorkException(ResultCodeEnum.ILLEGAL_REQUEST);
  10. }
  11. //2.拿着orderNo到redis中查询 此lua脚本解析 如果redis中存在的值 = 这一个值 那么 这个值 , 不过没有存在 就返回0 然后结束
  12. String script = "if(redis.call('get', KEYS[1]) == ARGV[1]) then return redis.call('del', KEYS[1]) else return 0 end";
  13. //3.如果redis有相同orderNo,表示正常提交订单 ,把redis的orderNo删除
  14. Boolean flag = (Boolean) redisTemplate
  15. .execute(new DefaultRedisScript(script, Boolean.class),
  16. Arrays.asList(RedisConst.ORDER_REPEAT + orderNo),orderNo);
  17. //4.如果redis没有相同orderNo,表示重复提交了,不能再往后进行
  18. if (!flag){
  19. throw new PorkException(ResultCodeEnum.REPEAT_SUBMIT);
  20. }
  21. //第三步 验证库存 并且 锁定库存(订单在30分钟内锁定库存 没有真正减库存)
  22. //获取当前购物车商品
  23. List<CartInfo> cartItemsList = orderSubmitVo.getCartItemsList();
  24. //新建一个锁单Vo 然后把商品信息封装到 Vo里面
  25. if (!CollectionUtils.isEmpty(cartItemsList))
  26. {
  27. List<ProductStockVo> productStockVoList =
  28. cartItemsList.stream().map(item ->{
  29. ProductStockVo productStockVo = new ProductStockVo();
  30. productStockVo.setFlavorId(item.getFlavorId());
  31. productStockVo.setSkuNum(item.getQuantity());
  32. return productStockVo;
  33. }).collect(Collectors.toList());
  34. //验证库存,保证具备原子性 解决超卖问题
  35. Boolean isLockSuccess = flavorsService.checkAndLock(productStockVoList, orderNo);
  36. if (!isLockSuccess){
  37. throw new PorkException(ResultCodeEnum.ORDER_STOCK_FALL);
  38. }
  39. }
  40. //第四步 下单过程
  41. OrderGenerateInfo orderGenerateInfo = this.saveOrder(orderSubmitVo);
  42. //对已生成订单的购物车进行删除
  43. List<Integer> cartIdList = cartItemsList.stream()
  44. .map(CartInfo::getId)
  45. .collect(Collectors.toList());
  46. cartItemsDao.deleteBatchIds(cartIdList);
  47. //1.向两张表中添加数据 order_info order_item
  48. //返回订单id
  49. return orderGenerateInfo;
  50. }

2.2.4保存订单

  1. @Transactional(rollbackFor = {Exception.class})
  2. public OrderGenerateInfo saveOrder(OrderSubmitVo orderSubmitVo) {
  3. Integer userId = orderSubmitVo.getUserId();
  4. List<CartInfo> cartItemsList = orderSubmitVo.getCartItemsList();
  5. if (CollectionUtils.isEmpty(cartItemsList)){
  6. throw new PorkException(ResultCodeEnum.DATA_ERROR);
  7. }
  8. List<String> goodInfoList = new ArrayList<String>();
  9. String goodInfo = "";
  10. for (CartInfo cartInfo : cartItemsList) {
  11. Integer flavorId = cartInfo.getFlavorId();
  12. Integer productId = cartInfo.getProductId();
  13. Integer quantity = cartInfo.getQuantity();
  14. String productName = productsService.queryNameById(productId);
  15. String flavorName = flavorsService.queryNameById(flavorId);
  16. goodInfo = productName+":"+flavorName+"*"+quantity;
  17. goodInfoList.add(goodInfo);
  18. }
  19. //查数据 顾客收货地址
  20. Integer addressesId = orderSubmitVo.getAddressesId();
  21. Addresses addresses = addressesDao.queryById(addressesId);
  22. if (addresses == null){
  23. throw new PorkException(ResultCodeEnum.DATA_ERROR);
  24. }
  25. String recipientName = addresses.getRecipientName();
  26. String recipientPhone = addresses.getRecipientPhone();
  27. String province = addresses.getProvince();
  28. String city = addresses.getCity();
  29. String district = addresses.getDistrict();
  30. String detail = addresses.getDetail();
  31. String orderAddress = province + city + district + detail;
  32. //计算金额
  33. Double totalPrice = orderSubmitVo.getTotalPrice();
  34. Double discount = orderSubmitVo.getDiscount();
  35. Double actuallyPay = orderSubmitVo.getActuallyPay();
  36. //原金额
  37. // Double totalAmount = cartItemsDao.getActuallyPay(userId);
  38. // Double discount = 0.00;
  39. // Double actuallyPay = totalAmount;
  40. // Integer couponId = 0;
  41. Integer userCouponId = orderSubmitVo.getUserCouponId();
  42. // UserCoupons userCoupons = userCouponsDao.queryById(userCouponId);
  43. //把优惠券设置为已使用
  44. userCouponsDao.update(userCouponId);
  45. // if (userCoupons!=null){
  46. // couponId = userCoupons.getCouponId();
  47. // }
  48. //优惠券金额
  49. // if (userCouponId != null){
  50. // UserCoupons userCoupons = userCouponsDao.queryById(userCouponId);
  51. // couponId = userCoupons.getCouponId();
  52. // discount = couponsDao.queryById(couponId).getDiscount();
  53. // }
  54. // //实付金额
  55. // actuallyPay = totalPrice - discount;
  56. //封装订单项
  57. List<OrderItems> orderItemsList = new ArrayList<>();
  58. for (CartInfo cartItems : cartItemsList) {
  59. OrderItems orderItem = new OrderItems();
  60. orderItem.setProductId(cartItems.getProductId());
  61. orderItem.setFlavorId(cartItems.getFlavorId());
  62. orderItem.setQuantity(cartItems.getQuantity());
  63. orderItem.setPrice(cartItems.getPrice());
  64. orderItem.setStatus(0);
  65. orderItemsList.add(orderItem);
  66. }
  67. Orders order = new Orders();
  68. order.setUserId(userId);
  69. order.setTotalAmount(totalPrice);
  70. order.setStatus(0);
  71. order.setConsignee(recipientName);
  72. order.setPhone(recipientPhone);
  73. order.setAddress(orderAddress);
  74. order.setDiscount(discount);
  75. order.setOrderNo(orderSubmitVo.getOrderNo());
  76. order.setComment(orderSubmitVo.getComment());
  77. order.setActuallyPay(actuallyPay);
  78. order.setCouponId(userCouponId);
  79. order.setGoodInfo(String.join(", ", goodInfoList));
  80. //添加数据到订单基本表
  81. ordersDao.insert(order);
  82. //添加订单里面的订单项
  83. orderItemsList.forEach(orderItems -> {
  84. orderItems.setOrderId(order.getId());
  85. orderItemsDao.insert(orderItems);
  86. });
  87. //如果当前订单使用优惠券更新优惠券状态
  88. if (order.getCouponId()!= null){
  89. userCouponsDao.update(userCouponId);
  90. }
  91. //在redis中记录用户购物数量
  92. //hash类型 key(userId) - field(skuId)-value(skuNum)
  93. String orderSkuKey = RedisConst.ORDER_SKU_MAP + orderSubmitVo.getUserId();
  94. BoundHashOperations<String, String, Integer> hashOperations = redisTemplate.boundHashOps(orderSkuKey);
  95. cartItemsList.forEach(cartInfo -> {
  96. if(hashOperations.hasKey(cartInfo.getFlavorId().toString())) {
  97. Integer orderSkuNum = hashOperations.get(cartInfo.getFlavorId().toString()) + cartInfo.getQuantity();
  98. hashOperations.put(cartInfo.getFlavorId().toString(), orderSkuNum);
  99. }
  100. });
  101. redisTemplate.expire(orderSkuKey, DateUtil.getCurrentExpireTimes(), TimeUnit.SECONDS);
  102. //设置订单过期时间 30分钟后取消订单
  103. long orderTimeOut = 1;
  104. String keyRedis = String.valueOf(StrUtil.format("{}:{}",RedisConst.REDIS_ORDER_KEY_IS_PAY_0,order.getId()));
  105. //设置过期时间
  106. redisTemplate.opsForValue().set(keyRedis,order.getOrderNo(),orderTimeOut,TimeUnit.MINUTES);
  107. //订单id
  108. OrderGenerateInfo orderGenerateInfo = new OrderGenerateInfo(order.getId(),orderTimeOut);
  109. return orderGenerateInfo;
  110. }

3.讲讲Redis过期键监听器

redis过期键监听器 实现对键的监听 

如果该键过期了,则进行注册过的操作

3.1配置监听器

像只注册了订单服务的话 你就只能使用订单服务 

若使用其他服务的话 也要进行集成

  1. @Configuration
  2. @AllArgsConstructor
  3. public class RedisListenerConfig {
  4. private final RedisTemplate<String, String> redisTemplate;
  5. private final RedissonConfig redisConfigProperties;
  6. private final OrdersService ordersService;
  7. // private final OrderItemsService orderItemsService;
  8. @Bean
  9. RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
  10. RedisMessageListenerContainer container = new RedisMessageListenerContainer();
  11. container.setConnectionFactory(connectionFactory);
  12. container.addMessageListener(new RedisKeyExpirationListener(redisTemplate, redisConfigProperties, ordersService), new PatternTopic(StrUtil.format("__keyevent@{}__:expired", redisConfigProperties.getDatabase())));
  13. return container;
  14. }
  15. }

3.2配置redis 开启监听器

3.3写监听器

  1. @Component
  2. public class RedisKeyExpirationListener implements MessageListener {
  3. private RedisTemplate<String, String> redisTemplate;
  4. private RedissonConfig redisConfigProperties;
  5. private OrdersService ordersService;
  6. // private OrderItemsService orderItemsService;
  7. public RedisKeyExpirationListener(RedisTemplate<String, String> redisTemplate,
  8. RedissonConfig redisConfigProperties,
  9. OrdersService orderInfoService
  10. ){
  11. this.redisTemplate = redisTemplate;
  12. this.redisConfigProperties = redisConfigProperties;
  13. this.ordersService = orderInfoService;
  14. // this.orderItemsService = orderItemsService;
  15. }
  16. @Override
  17. public void onMessage(Message message, byte[] bytes) {
  18. RedisSerializer<?> serializer = redisTemplate.getValueSerializer();
  19. String channel = String.valueOf(serializer.deserialize(message.getChannel()));
  20. String body = String.valueOf(serializer.deserialize(message.getBody()));
  21. //key过期监听
  22. if(StrUtil.format("__keyevent@{}__:expired", redisConfigProperties.getDatabase()).equals(channel)){
  23. //订单自动取消
  24. if(body.contains(RedisConst.REDIS_ORDER_KEY_IS_PAY_0)) {
  25. body = body.replace(RedisConst.REDIS_ORDER_KEY_IS_PAY_0, "");
  26. String[] str = body.split(":");
  27. String wxOrderId = str[1];
  28. System.out.println(wxOrderId);
  29. Orders orders = ordersService.queryById(Integer.valueOf(wxOrderId));
  30. if(orders != null && orders.getStatus() == 0){//只有待支付的订单能取消
  31. //TODO 订单取消 库存增加 减优惠券
  32. // orderItemsService.toCancel(orders.getId());
  33. ordersService.cancelOrder(orders.getId());
  34. System.out.println("订单id:"+orders.getId()+"已删除");
  35. }
  36. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/977591
推荐阅读
相关标签
  

闽ICP备14008679号