赞
踩
最近再写支付模块就到处借鉴 旨在回顾一下。
使用场景是:用户在选择好购物车后,或者是直接选择商品后(选择商品封装为购物车)
这样做是根据尚硅谷来学习的 目前需要这些属性,原因是在确认订单页面后 展现一个最优惠的状态
- /**
- * 用户id
- */
- @ApiModelProperty("用户id")
- private Integer userId;
-
- /**
- * 购物车列表
- */
- @ApiModelProperty("购物车中已选中商品")
- private List<CartInfo> cartItemsList;
-
- @ApiModelProperty("最优惠的优惠券id")
- private Integer userCouponsId;
-
- /**
- * 总金额
- */
- @ApiModelProperty("总金额")
- private Double totalAmount;
-
- /**
- * 优惠金额
- */
- @ApiModelProperty("优惠金额")
- private Double discount;
-
- /**
- * 实付金额
- */
- @ApiModelProperty("实付金额")
- private Double actuallyPay;

期间做了一些修改 之前是设置的有购物车状态 这些步骤都是在后端处理
后来又采用了前端传递购物车属性 美其名曰叫 减少io次数 缓解数据库压力
通过传入的userId 查找 订单列表 购物车列表
期间有个redis操作 是根据时间 来生成 后续在生成订单时会用到
- public OrderConfirmVo confirmOrder(CartConfirmDto cartConfirmDto) {
- //获取用户id参数 以方便后续使用
- //获取用户地址列表
- Integer userId = cartConfirmDto.getUserId();
- List<Addresses> addressesList = addressesDao.getByUserId(userId);
- //获取购物车中已经选中的商品
- List<CartInfo> cartItemsList = cartConfirmDto.getCartItemsList();
- // List<CartItems> cartItemsList = cartItemsDao.querySelectedCartItems(userId);
- if (cartItemsList.isEmpty()){
- throw new PorkException("您购物车中未选中商品",500);
- }
- for (CartInfo cartInfo : cartItemsList) {
- Integer productId = cartInfo.getProductId();
- Products products = productsDao.queryById(productId);
- cartInfo.setMainPhoto(products.getMainPhoto());
- cartInfo.setName(products.getName());
- Integer flavorId = cartInfo.getFlavorId();
- String flavorDescription = flavorsService.queryNameById(flavorId);
- cartInfo.setFlavorDescription(flavorDescription);
- }
- //生成订单唯一标示
- String orderNo = System.currentTimeMillis()+"";
- redisTemplate.opsForValue().set(RedisConst.ORDER_REPEAT+orderNo,orderNo,24, TimeUnit.HOURS);
- //1.获取最优惠优惠券
- //2.先获取订单价格
- //3.找到实付金额
- // Double totalPrice = cartItemsDao.getActuallyPay(userId);
- // UserCoupons userCoupon = userCouponsDao.queryOptimalUserCoupon(totalPrice, userId);
- // Double discount = userCoupon.getCoupons().getDiscount();
- // Double actuallyPay = totalPrice - discount;
- Double totalPrice = cartConfirmDto.getTotalAmount();
- Integer userCouponId = cartConfirmDto.getUserCouponsId();
- Double discount = cartConfirmDto.getDiscount();
- Double actuallyPay = cartConfirmDto.getActuallyPay();
- UserCoupons userCoupon = userCouponsDao.queryById(userCouponId);
- //查询可用优惠券
- List<UserCoupons> userCoupons = userCouponsDao.queryAvailableUserCoupons(totalPrice, userId);
- //进行封装
- OrderConfirmVo orderConfirmVo = new OrderConfirmVo(userId,totalPrice,userCoupon,discount,actuallyPay,orderNo,userCoupons,addressesList,cartItemsList);
-
- return orderConfirmVo;
- }

- */
- @ApiModelProperty("用户id")
- private Integer userId;
-
- /**
- * 总金额
- */
- @ApiModelProperty("总金额")
- private Double totalAmount;
-
- @ApiModelProperty("最优惠的优惠券")
- private UserCoupons userCoupons;
-
- /**
- * 优惠金额
- */
- @ApiModelProperty("优惠金额")
- private Double discount;
-
- /**
- * 实付金额
- */
- @ApiModelProperty("实付金额")
- private Double actuallyPay;
-
- /**
- * 订单号
- */
- @ApiModelProperty("订单号")
- private String orderNo;
-
- /**
- * 用户所有优惠券
- */
- @ApiModelProperty("用户优惠券")
- private List<UserCoupons> userCouponsList;
- /**
- * 用户地址
- */
- @ApiModelProperty("用户地址列表")
- private List<Addresses> addressesList;
-
- /**
- * 购物车列表
- */
- @ApiModelProperty("购物车列表")
- private List<CartInfo> cartItemsList;

生成订单后里面的属性
- @ApiModelProperty(value = "使用预生产订单号防重")
- private String orderNo;
-
- @ApiModelProperty(value = "用户id")
- private Integer userId;
-
- @ApiModelProperty(value = "下单时所使用的地址信息")
- private Integer addressesId;
-
- @ApiModelProperty(value = "下单选中的优惠券id")
- private Integer userCouponId;
-
- @ApiModelProperty(value = "订单备注")
- private String comment;
-
-
- @ApiModelProperty(value = "所选中商品")
- private List<CartInfo> cartItemsList;
-
- @ApiModelProperty(value = "最后订单总价")
- private Double totalPrice;
- @ApiModelProperty(value = "优惠金额")
- private Double discount;
- @ApiModelProperty("订单实付金额")
- private Double ActuallyPay;

使用lua脚本来保证原子性
如果redis中有相同orderNo 则说明正常提交订单 然后把redis删除
期间也有锁单
- @Override
- public Boolean checkAndLock(List<ProductStockVo> productStockVoList, String orderNo) {
- //1.判断productStockVoList是否为空
- if (CollectionUtils.isEmpty(productStockVoList)){
- throw new PorkException(ResultCodeEnum.DATA_ERROR);
- }
- //2.遍历productStockVoList得到每个商品,验证库存并锁定库存,具备原子性
- productStockVoList.stream().forEach(productStockVo -> {
- this.checkLock(productStockVo);
- });
- //3.只要有一个商品锁定失败,所有锁定成功的商品都解锁 用于检查流中是否至少有一个元素满足指定的条件
- boolean flag = productStockVoList.stream()
- .anyMatch(productStockVo -> !productStockVo.getIsLock());
- if (flag){
- //所有锁定成功的商品都解锁
- productStockVoList.stream().filter(ProductStockVo::getIsLock)
- .forEach(productStockVo -> {
- flavorsDao.unlockStock(productStockVo.getFlavorId(),productStockVo.getSkuNum());
- });
- return false;
- }
- //4 如果所有商品都锁定成功了,redis缓存相关数据,为了方便后面解锁和减库存
- redisTemplate.opsForValue()
- .set(RedisConst.SROCK_INFO+orderNo,productStockVoList,33, TimeUnit.MINUTES);
- return true;
- }

- private void checkLock(ProductStockVo productStockVo) {
- //获取锁 公平锁:谁等待时间长给谁发锁
- RLock rLock = this.redissonClient.getFairLock(RedisConst.SKUKEY_PREFIX+productStockVo.getFlavorId());
- rLock.lock();
- try {
- //验证库存
- Flavors flavors = flavorsDao.checkStock(productStockVo.getFlavorId(),productStockVo.getSkuNum());
- //判断没有满足条件商品,设置isLock值为false,返回
- if (flavors == null){
- productStockVo.setIsLock(false);
- return;
- }
- //又满足条件商品,锁定库存 update rows 影响行数
- Integer rows = flavorsDao.lockStock(productStockVo.getFlavorId(),productStockVo.getSkuNum());
- if (rows == 1) {
- productStockVo.setIsLock(true);
- }
- }finally {
- //解锁
- rLock.unlock();
- }
- }

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

- @Transactional(rollbackFor = {Exception.class})
- public OrderGenerateInfo saveOrder(OrderSubmitVo orderSubmitVo) {
- Integer userId = orderSubmitVo.getUserId();
- List<CartInfo> cartItemsList = orderSubmitVo.getCartItemsList();
- if (CollectionUtils.isEmpty(cartItemsList)){
- throw new PorkException(ResultCodeEnum.DATA_ERROR);
- }
-
- List<String> goodInfoList = new ArrayList<String>();
- String goodInfo = "";
- for (CartInfo cartInfo : cartItemsList) {
- Integer flavorId = cartInfo.getFlavorId();
- Integer productId = cartInfo.getProductId();
- Integer quantity = cartInfo.getQuantity();
- String productName = productsService.queryNameById(productId);
- String flavorName = flavorsService.queryNameById(flavorId);
- goodInfo = productName+":"+flavorName+"*"+quantity;
- goodInfoList.add(goodInfo);
- }
- //查数据 顾客收货地址
- Integer addressesId = orderSubmitVo.getAddressesId();
- Addresses addresses = addressesDao.queryById(addressesId);
- if (addresses == null){
- throw new PorkException(ResultCodeEnum.DATA_ERROR);
- }
- String recipientName = addresses.getRecipientName();
- String recipientPhone = addresses.getRecipientPhone();
- String province = addresses.getProvince();
- String city = addresses.getCity();
- String district = addresses.getDistrict();
- String detail = addresses.getDetail();
- String orderAddress = province + city + district + detail;
- //计算金额
- Double totalPrice = orderSubmitVo.getTotalPrice();
- Double discount = orderSubmitVo.getDiscount();
- Double actuallyPay = orderSubmitVo.getActuallyPay();
- //原金额
- // Double totalAmount = cartItemsDao.getActuallyPay(userId);
- // Double discount = 0.00;
- // Double actuallyPay = totalAmount;
- // Integer couponId = 0;
- Integer userCouponId = orderSubmitVo.getUserCouponId();
- // UserCoupons userCoupons = userCouponsDao.queryById(userCouponId);
- //把优惠券设置为已使用
- userCouponsDao.update(userCouponId);
- // if (userCoupons!=null){
- // couponId = userCoupons.getCouponId();
- // }
-
-
- //优惠券金额
- // if (userCouponId != null){
- // UserCoupons userCoupons = userCouponsDao.queryById(userCouponId);
- // couponId = userCoupons.getCouponId();
- // discount = couponsDao.queryById(couponId).getDiscount();
- // }
- // //实付金额
- // actuallyPay = totalPrice - discount;
- //封装订单项
- List<OrderItems> orderItemsList = new ArrayList<>();
- for (CartInfo cartItems : cartItemsList) {
- OrderItems orderItem = new OrderItems();
- orderItem.setProductId(cartItems.getProductId());
- orderItem.setFlavorId(cartItems.getFlavorId());
- orderItem.setQuantity(cartItems.getQuantity());
- orderItem.setPrice(cartItems.getPrice());
- orderItem.setStatus(0);
- orderItemsList.add(orderItem);
- }
- Orders order = new Orders();
- order.setUserId(userId);
- order.setTotalAmount(totalPrice);
- order.setStatus(0);
- order.setConsignee(recipientName);
- order.setPhone(recipientPhone);
- order.setAddress(orderAddress);
- order.setDiscount(discount);
- order.setOrderNo(orderSubmitVo.getOrderNo());
- order.setComment(orderSubmitVo.getComment());
- order.setActuallyPay(actuallyPay);
- order.setCouponId(userCouponId);
- order.setGoodInfo(String.join(", ", goodInfoList));
- //添加数据到订单基本表
- ordersDao.insert(order);
-
- //添加订单里面的订单项
- orderItemsList.forEach(orderItems -> {
- orderItems.setOrderId(order.getId());
- orderItemsDao.insert(orderItems);
- });
-
- //如果当前订单使用优惠券更新优惠券状态
- if (order.getCouponId()!= null){
- userCouponsDao.update(userCouponId);
- }
- //在redis中记录用户购物数量
- //hash类型 key(userId) - field(skuId)-value(skuNum)
- String orderSkuKey = RedisConst.ORDER_SKU_MAP + orderSubmitVo.getUserId();
- BoundHashOperations<String, String, Integer> hashOperations = redisTemplate.boundHashOps(orderSkuKey);
- cartItemsList.forEach(cartInfo -> {
- if(hashOperations.hasKey(cartInfo.getFlavorId().toString())) {
- Integer orderSkuNum = hashOperations.get(cartInfo.getFlavorId().toString()) + cartInfo.getQuantity();
- hashOperations.put(cartInfo.getFlavorId().toString(), orderSkuNum);
- }
- });
- redisTemplate.expire(orderSkuKey, DateUtil.getCurrentExpireTimes(), TimeUnit.SECONDS);
- //设置订单过期时间 30分钟后取消订单
- long orderTimeOut = 1;
- String keyRedis = String.valueOf(StrUtil.format("{}:{}",RedisConst.REDIS_ORDER_KEY_IS_PAY_0,order.getId()));
- //设置过期时间
- redisTemplate.opsForValue().set(keyRedis,order.getOrderNo(),orderTimeOut,TimeUnit.MINUTES);
- //订单id
- OrderGenerateInfo orderGenerateInfo = new OrderGenerateInfo(order.getId(),orderTimeOut);
- return orderGenerateInfo;
- }

redis过期键监听器 实现对键的监听
如果该键过期了,则进行注册过的操作
像只注册了订单服务的话 你就只能使用订单服务
若使用其他服务的话 也要进行集成
- @Configuration
- @AllArgsConstructor
- public class RedisListenerConfig {
-
- private final RedisTemplate<String, String> redisTemplate;
- private final RedissonConfig redisConfigProperties;
- private final OrdersService ordersService;
-
- // private final OrderItemsService orderItemsService;
-
- @Bean
- RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
-
- RedisMessageListenerContainer container = new RedisMessageListenerContainer();
- container.setConnectionFactory(connectionFactory);
- container.addMessageListener(new RedisKeyExpirationListener(redisTemplate, redisConfigProperties, ordersService), new PatternTopic(StrUtil.format("__keyevent@{}__:expired", redisConfigProperties.getDatabase())));
- return container;
- }
- }
-

- @Component
- public class RedisKeyExpirationListener implements MessageListener {
-
- private RedisTemplate<String, String> redisTemplate;
- private RedissonConfig redisConfigProperties;
-
- private OrdersService ordersService;
-
- // private OrderItemsService orderItemsService;
-
- public RedisKeyExpirationListener(RedisTemplate<String, String> redisTemplate,
- RedissonConfig redisConfigProperties,
- OrdersService orderInfoService
- ){
- this.redisTemplate = redisTemplate;
- this.redisConfigProperties = redisConfigProperties;
- this.ordersService = orderInfoService;
- // this.orderItemsService = orderItemsService;
- }
- @Override
- public void onMessage(Message message, byte[] bytes) {
- RedisSerializer<?> serializer = redisTemplate.getValueSerializer();
- String channel = String.valueOf(serializer.deserialize(message.getChannel()));
- String body = String.valueOf(serializer.deserialize(message.getBody()));
- //key过期监听
- if(StrUtil.format("__keyevent@{}__:expired", redisConfigProperties.getDatabase()).equals(channel)){
- //订单自动取消
- if(body.contains(RedisConst.REDIS_ORDER_KEY_IS_PAY_0)) {
- body = body.replace(RedisConst.REDIS_ORDER_KEY_IS_PAY_0, "");
- String[] str = body.split(":");
- String wxOrderId = str[1];
- System.out.println(wxOrderId);
- Orders orders = ordersService.queryById(Integer.valueOf(wxOrderId));
- if(orders != null && orders.getStatus() == 0){//只有待支付的订单能取消
- //TODO 订单取消 库存增加 减优惠券
- // orderItemsService.toCancel(orders.getId());
- ordersService.cancelOrder(orders.getId());
- System.out.println("订单id:"+orders.getId()+"已删除");
- }
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。