项目
博客
文档
归档
资源链接
关于我
项目
博客
文档
归档
资源链接
关于我
34| 订单微服务 -下单和验价设计和开发
2024-09-11
·
·
原创
·
·
本文共 1,143个字,预计阅读需要 4分钟。
### 订单微服务-下单逻辑之确认收货地址模开发 订单号生产 获取收货地址详情 * 方案一:前端一并传递收货地址详情过来 * 少了一次网络交互 * 前后端通信包更大 * 方案二:后端根据收货地址id调用服务查询地址详情 * 多了一次网络交换 * 前后端通信包更小 ```java /** * 防重提交
* 用户微服务-确认收货地址
* 商品微服务-获取最新购物项和价格
* 订单验价
* * 优惠券微服务-获取优惠券
* * 验证价格
* 锁定优惠券
* 锁定商品库存
* 创建订单对象
* 创建子订单对象
* 发送延迟消息-用于自动关单
* 创建支付信息-对接三方支付 */ @Override public JsonData confirmOrder(ConfirmOrderRequest request){ // 获取token oginUser loginUser = LoginInterceptor.threadLocal.get(); String orderToken = request.getToken(); if(StringUtils.isBlank(orderToken)){ throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_NOT_EXIST); } //原子操作 校验令牌,删除令牌 String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; Long result = redisTemplate.execute(new DefaultRedisScript<>(script,Long.class), List.of(String.format(CacheKey.SUBMIT_ORDER_TOKEN_KEY, loginUser.getId())),orderToken); if(result == 0L){ throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL); } // 获取订单号 String orderOutTradeNo = CommonUtil.getStringNumRandom(32); //获取收货地址详情 ProductOrderAddressVO addressVO = this.getUserAddress(request.getAddressId()); log.info("收货地址信息:{}",addressVO); // **** } ``` 在获取地址详细的时候,是通过Feign来调用user服务获取地址形象,在这里使用了远程获取数据JsonData中添加了引用方式 ```java /** * JsonData获取远程调用对象 */ public
T getData(TypeReference
typeReference){ return JSON.parseObject(JSON.toJSONString(data),typeReference); } ``` 远程获取地址详情数据 ```java // ProductOrderServiceImpl /** * 获取收货地址详情(后端通过id调用服务拿到地址) */ private ProductOrderAddressVO getUserAddress(long addressId) { JsonData addressData = userFeignService.detail(addressId); if(addressData.getCode() !=0){ log.error("获取收获地址失败,msg:{}",addressData); throw new BizException(BizCodeEnum.ADDRESS_NO_EXITS); } return addressData.getData(new TypeReference<>(){}); } @FeignClient(name = "nla-user-service") public interface UserFeignService { /** * 查询用户地址,接口本身有防止水平权限 */ @GetMapping("/user/address/v1/find/{address_id}") JsonData detail(@PathVariable("address_id") long addressId); } ``` 功能测试 * token传递失败 ### Feign底层源码-token令牌丢失原因分析和解决方案 token传递失败原因分析 解决方案-加入到common公共类上(随便的一个Config类中,例如:RedissonConfig) ```java @Bean("requestInterceptor") public RequestInterceptor requestInterceptor() { return template -> { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null) { HttpServletRequest request = attributes.getRequest(); log.info(request.getHeaderNames().toString()); template.header("token", request.getHeader("token")); } else { log.warn("requestInterceptor获取Header空指针异常"); } }; } ``` ### 订单微服务-下单获取商品最新价格开发 获取最新商品价格开发(商品服务) ```java /** * 用于订单服务,确认订单,获取全部购物项 * 会清空购物车对应的商品 */ @ApiOperation("下单清空购物项") @RequestMapping("confirm_order_cart_items") public JsonData confirmOrderCartItem(@ApiParam("商品id列表") @RequestBody List
productIdList ){ List
cartItemVOList = cartService.confirmOrderCartItems(productIdList); return JsonData.buildSuccess(cartItemVOList); } ``` 实现:确认购物车商品信息 ```java @Override public List
confirmOrderCartItems(List
productIdList) { //获取全部购物车的购物项 List
cartItemVOList = buildCartItem(true); //根据需要的商品id进行过滤,并清空对应的购物项 List
resultList = cartItemVOList.stream().filter(obj->{ if(productIdList.contains(obj.getProductId())){ this.deleteItem(obj.getProductId()); return true; } return false; }).collect(Collectors.toList()); return resultList; } ``` 商品信息对象 ```java /** * 购物项 */ @Data public class CartItemVO { /** * 商品id */ @JsonProperty("product_id") private Long productId; /** * 购买数量 */ @JsonProperty("buy_num") private Integer buyNum; /** * 商品标题 */ @JsonProperty("product_title") private String productTitle; /** * 图片 */ @JsonProperty("product_img") private String productImg; /** * 商品单价 */ private BigDecimal amount; /** * 总价格,单价+数量 */ @JsonProperty("total_amount") private BigDecimal totalAmount; } ``` 在订单服务中通过Feign来调用获取订单详情数据 ```java @FeignClient(name = "nla-product-service") public interface ProductFeignService { /** * 订单详情查询 */ @RequestMapping("/pdt/cart/v1/confirm_order_cart_items") JsonData confirmOrderCartItem(@RequestBody List
productIdList); } ``` 在获取收货地址后补充获取商品信息 ```java //获取用户加入购物车的商品 List
productIdList = request.getProductIdList(); JsonData cartItemDate = productFeignService.confirmOrderCartItem(productIdList); List
orderItemList = cartItemDate.getData(new TypeReference<>() { }); log.info("获取的商品:{}", orderItemList); if (orderItemList == null) { //购物车商品不存在 throw new BizException(BizCodeEnum.ORDER_CONFIRM_CART_ITEM_NOT_EXIST); } ``` * 下单协议(下单接口请求参数)http://127.0.0.1:9004/odr/product/v1/confirm ```json { "coupon_record_id": -1, "product_ids": [ 1, 2 ], "pay_type": "ALIPAY", "client_type": "H5", "address_id": 40, "total_amount": 10, "real_pay_amount": 10, "token": "SbD5D4FLpUzemiuwSEytwGM9LLFGISDQ" } ``` 在下单前,要登录用户,获取token;同时还需要在商品服务下添加商品到购物车:/pdt/cart/v1/add ```json { "buy_num": 1, "product_id": 2 } { "buy_num": 1, "product_id": 1 } ``` 在fegin调用超时时,需要给每个服务设置超时时间: ```yml feign: client: config: default: # 设置Feign的连接超时时间(秒) connectTimeout: 5000 # 设置Feign的读取超时时间(秒) readTimeout: 10000 ``` ### 多种解决方案设计-购物车清空下单商品的设计思路 需求作业 * 用户创建订单,购物车的商品什么时候进行清除 * 假如下单流程异常后,怎么回滚 清空购物车逻辑设计方案一 * 想加分布式事务Seata 【推荐】清空购物车逻辑设计方案二 * **直接调用清空-MQ延迟消息**(假如订单创建失败则购物车会丢失数据) * 解决方案:类似库存解锁和优惠券释放一样的思路(购物车这边做) * 延迟消息可以1分钟或者5分钟 * 直接查询订单是否存在即可(协议增加一个outTradeNo) ### 订单微服务-商品验价和优惠券的抵扣功能开发 * 统计商品价格 * 远程调用获取优惠券 * 当前购物车是否满足优惠券使用条件 * 验证价格 ```java //验证价格,减去商品优惠券 this.checkPrice(orderItemList,orderRequest); /** * 验证价格 * 1)统计全部商品的价格 * 2) 获取优惠券(判断是否满足优惠券的条件),总价再减去优惠券的价格 就是 最终的价格 */ private void checkPrice(List
orderItemList, ConfirmOrderRequest orderRequest) { //统计商品总价格 BigDecimal realPayAmount = new BigDecimal("0"); if (orderItemList != null) { for(OrderItemVO orderItemVO : orderItemList){ BigDecimal itemRealPayAmount = orderItemVO.getTotalAmount(); realPayAmount = realPayAmount.add(itemRealPayAmount); } } //获取优惠券,判断是否可以使用 CouponRecordVO couponRecordVO = getCartCouponRecord(orderRequest.getCouponRecordId()); //计算购物车价格,是否满足优惠券满减条件 if(couponRecordVO!=null){ //计算是否满足满减 if(realPayAmount.compareTo(couponRecordVO.getConditionPrice()) < 0){ throw new BizException(BizCodeEnum.ORDER_CONFIRM_COUPON_FAIL); } if(couponRecordVO.getPrice().compareTo(realPayAmount)>0){ realPayAmount = BigDecimal.ZERO; }else { realPayAmount = realPayAmount.subtract(couponRecordVO.getPrice()); } } if(realPayAmount.compareTo(orderRequest.getRealPayAmount()) !=0 ){ log.error("订单验价失败:{}",orderRequest); throw new BizException(BizCodeEnum.ORDER_CONFIRM_PRICE_FAIL); } } /** * 获取优惠券 */ private CouponRecordVO getCartCouponRecord(Long couponRecordId) { if(couponRecordId ==null || couponRecordId < 0){ return null; } JsonData couponData = couponFeignService.findUserCouponRecordById(couponRecordId); if(couponData.getCode()!=0){ throw new BizException(BizCodeEnum.ORDER_CONFIRM_COUPON_FAIL); } if(couponData.getCode()==0){ CouponRecordVO couponRecordVO = couponData.getData(new TypeReference<>(){}); if(!couponAvailable(couponRecordVO)){ log.error("优惠券使用失败"); throw new BizException(BizCodeEnum.COUPON_UNAVAILABLE); } return couponRecordVO; } return null; } /** * 判断优惠券是否可用 */ private boolean couponAvailable(CouponRecordVO couponRecordVO) { if(couponRecordVO.getUseState().equalsIgnoreCase(CouponStateEnum.NEW.name())){ long currentTimestamp = CommonUtil.getCurrentTimestamp(); long end = couponRecordVO.getEndTime().getTime(); long start = couponRecordVO.getStartTime().getTime(); if(currentTimestamp>= start && currentTimestamp<=end){ return true; } } return false; } ``` 获取优惠券调用 ```java /** * Feign调用优惠券服务接口 */ @FeignClient(name = "nla-coupon-service") public interface CouponFeignService { /** * 领取优惠券 */ @GetMapping("/cop/coupon/v1/promotion/{coupon_id}") JsonData findUserCouponRecordById(@PathVariable("record_id") long recordId); } ``` - 锁定优惠券 * 锁定商品库存 ```java //锁定优惠券 this.lockCouponRecords(request, orderOutTradeNo); //锁定库存 this.lockProductStocks(orderItemList, orderOutTradeNo); /** * 锁定优惠券 */ private void lockCouponRecords(ConfirmOrderRequest orderRequest, String orderOutTradeNo) { List
lockCouponRecordIds = new ArrayList<>(); if(orderRequest.getCouponRecordId()>0){ lockCouponRecordIds.add(orderRequest.getCouponRecordId()); LockCouponRecordRequest lockCouponRecordRequest = new LockCouponRecordRequest(); lockCouponRecordRequest.setOrderOutTradeNo(orderOutTradeNo); lockCouponRecordRequest.setLockCouponRecordIds(lockCouponRecordIds); //发起锁定优惠券请求 JsonData jsonData = couponFeignService.lockCouponRecords(lockCouponRecordRequest); if(jsonData.getCode()!=0){ throw new BizException(BizCodeEnum.COUPON_RECORD_LOCK_FAIL); } } } /** * 锁定商品库存 */ private void lockProductStocks(List
orderItemList, String orderOutTradeNo) { List
itemRequestList = orderItemList.stream().map(obj->{ OrderItemRequest request = new OrderItemRequest(); request.setBuyNum(obj.getBuyNum()); request.setProductId(obj.getProductId()); return request; }).collect(Collectors.toList()); LockProductRequest lockProductRequest = new LockProductRequest(); lockProductRequest.setOrderOutTradeNo(orderOutTradeNo); lockProductRequest.setOrderItemList(itemRequestList); JsonData jsonData = productFeignService.lockProducts(lockProductRequest); if(jsonData.getCode()!=0){ log.error("锁定商品库存失败:{}",lockProductRequest); throw new BizException(BizCodeEnum.ORDER_CONFIRM_LOCK_PRODUCT_FAIL); } } ``` ### 订单微服务-创建商品订单和订单项模块开发 * 下单创建商品订单 * 下单创建商品订单项 * bug * 数据库 product_order 表的 user_id 改为bigint类型,java类型是long ```java //创建订单 ProductOrderEntity productOrder = this.saveProductOrder(request,loginUser,orderOutTradeNo,addressVO); //创建订单项 this.saveProductOrderItems(orderOutTradeNo,productOrder.getId(),orderItemList); /** * 新增订单项 */ private void saveProductOrderItems(String orderOutTradeNo, Long orderId, List
orderItemList) { List
list = orderItemList.stream().map( obj->{ ProductOrderItemEntity itemDO = new ProductOrderItemEntity(); itemDO.setBuyNum(obj.getBuyNum()); itemDO.setProductId(obj.getProductId()); itemDO.setProductImg(obj.getProductImg()); itemDO.setProductName(obj.getProductTitle()); itemDO.setOutTradeNo(orderOutTradeNo); itemDO.setCreateTime(new Date()); //单价 itemDO.setAmount(obj.getAmount()); //总价 itemDO.setTotalAmount(obj.getTotalAmount()); itemDO.setProductOrderId(orderId); return itemDO; } ).collect(Collectors.toList()); productOrderItemMapper.insertBatch(list); } /** * 创建订单 */ private ProductOrderEntity saveProductOrder(ConfirmOrderRequest orderRequest, LoginUser loginUser, String orderOutTradeNo, ProductOrderAddressVO addressVO) { ProductOrderEntity productOrder = new ProductOrderEntity(); productOrder.setUserId(loginUser.getId()); productOrder.setHeadImg(loginUser.getHeadImg()); productOrder.setNickname(loginUser.getName()); productOrder.setOutTradeNo(orderOutTradeNo); productOrder.setCreateTime(new Date()); productOrder.setDel(0); productOrder.setOrderType(ProductOrderTypeEnum.DAILY.name()); //实际支付的价格 productOrder.setPayAmount(orderRequest.getRealPayAmount()); //总价,未使用优惠券的价格 productOrder.setTotalAmount(orderRequest.getTotalAmount()); productOrder.setState(ProductOrderStateEnum.NEW.name()); productOrder.setPayType(ProductOrderPayTypeEnum.valueOf(orderRequest.getPayType()).name()); productOrder.setReceiverAddress(JSON.toJSONString(addressVO)); baseMapper.insert(productOrder); return productOrder; } ```