本文共 46180 字,大约阅读时间需要 153 分钟。
mall-order
OrderInterceptor@Controllerpublic class OrderInterceptor implements HandlerInterceptor{ //有了它就可以共享数据 public static ThreadLocalloginUser=new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { MemberRespVo attribute = (MemberRespVo) request.getSession().getAttribute(AuthServerConstant.LOGIN_USER); if (attribute!=null){ loginUser.set(attribute); return true; }else { //没登录就去登录 request.getSession().setAttribute("msg","请先进行登录"); response.sendRedirect("http://auth.mall.com/login.html"); return false; } }}
MallWebConfig
@Configurationpublic class MallWebConfig implements WebMvcConfigurer{ /** * 添加一个拦截器,拦截器才可以工作 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { //拦截所有的请求/** registry.addInterceptor(new OrderInterceptor()).addPathPatterns("/**"); }}
OrderWebController
@Controllerpublic class OrderWebController { @GetMapping("toTrade") public String toTrade(){ return "confirm"; }}
mall-order
MemberAddressVo@Datapublic class MemberAddressVo { private Long id; /** * member_id */ private Long memberId; /** * 收货人姓名 */ private String name; /** * 电话 */ private String phone; /** * 邮政编码 */ private String postCode; /** * 省份/直辖市 */ private String province; /** * 城市 */ private String city; /** * 区 */ private String region; /** * 详细地址(街道) */ private String detailAddress; /** * 省市区代码 */ private String areacode; /** * 是否默认 */ private Integer defaultStatus;}
OrderConfirmVo
//订单确认页需要的数据public class OrderConfirmVo { @Getter @Setter //收货地址 ums_member_receive_address表 Listaddress; @Getter @Setter //所有选中的购物项 List items; @Getter @Setter //发票信息 //优惠劵信息 Integer integer; BigDecimal total;//订单总额 public BigDecimal getTotal() { BigDecimal sum = new BigDecimal(0); if (items!=null) { for (OrderItemVo item : items ) { BigDecimal peritems = item.getPrice().multiply(new BigDecimal(item.getCount().toString())); sum= sum.add(peritems); } } return sum; } BigDecimal payPrice;//应付价格 public BigDecimal getpayPrice() { BigDecimal sum = new BigDecimal(0); if (items!=null) { for (OrderItemVo item : items ) { BigDecimal peritems = item.getPrice().multiply(new BigDecimal(item.getCount().toString())); sum= sum.add(peritems); } } return sum; } //防重令牌 @Getter @Setter String orderToken;}
OrderItemVo
@Datapublic class OrderItemVo { private Long skuId; private Boolean check=true; private String title; private String image; private ListskuAttr; private BigDecimal price; private Integer count; private BigDecimal totalPrice;}
完成如下模块的后端代码
//订单确认页需要的数据//订单确认页需要的数据public class OrderConfirmVo { @Getter @Setter //收货地址 ums_member_receive_address表 Listaddress; @Getter @Setter //所有选中的购物项 List items; @Getter @Setter //发票信息 //优惠劵信息 Integer integer; BigDecimal total;//订单总额 public BigDecimal getTotal() { BigDecimal sum = new BigDecimal(0); if (items!=null) { for (OrderItemVo item : items ) { BigDecimal peritems = item.getPrice().multiply(new BigDecimal(item.getCount().toString())); sum= sum.add(peritems); } } return sum; } BigDecimal payPrice;//应付价格 public BigDecimal getpayPrice() { BigDecimal sum = new BigDecimal(0); if (items!=null) { for (OrderItemVo item : items ) { BigDecimal peritems = item.getPrice().multiply(new BigDecimal(item.getCount().toString())); sum= sum.add(peritems); } } return sum; } //防重令牌 @Getter @Setter String orderToken;}
OrderWebController
@Controllerpublic class OrderWebController { /** * 给订单确认页返回需要的数据confirm * @return */ @Override public OrderConfirmVo confirmOrder() { OrderConfirmVo confirmVo = new OrderConfirmVo(); //使用拦截器里面的threalocal MemberRespVo memberRespVo = OrderInterceptor.loginUser.get(); //1:远程查询所有的收货地址列表。。。远程调用查询收货地址ums_member_receive_address表 mall-member Listaddress = memberFeignService.getAddress(memberRespVo.getId()); //2:远程查询购物车所有选中的购物项 mall-order List currentUserCartItem = cartFeignService.getCurrentUserCartItem(); //3:查询用户积分 Integer integration = memberRespVo.getIntegration(); confirmVo.setInteger(integration); confirmVo.setAddress(address); confirmVo.setItems(currentUserCartItem); //4:一些其他数据 //5:todo 防重令牌 return confirmVo; }}
在mall-order中加入下代码解决Feign远程调用丢失请求头问题
MallFeignConfig//通过feign调用的源码我们知道,feign远程调用的时候会丢失请求头@Configurationpublic class MallFeignConfig { @Bean("") public RequestInterceptor requestInterceptor(){ return new RequestInterceptor() { @Override public void apply(RequestTemplate requestTemplate) { //可以使用RequestContextHolder拿到刚进来的这个请求的数据(http://order.mall.com/toTrade) ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); System.out.println("feign远程之前先进行RequestInterceptor.apply"); //同步请求头数据,cookie,为了发送到cart,让他知道order发送了cookie给cart,请求头不会丢失 String cookie = request.getHeader("Cookie"); //给新请求同步了老请求的cookie requestTemplate.header("Cookie",cookie); } }; }}
我们以后要使用异步的方式给订单确认页返回需要的数据confirm
如下代码 OrderServiceImpl/** * 给订单确认页返回需要的数据confirm * @return */ @Override public OrderConfirmVo confirmOrder() { OrderConfirmVo confirmVo = new OrderConfirmVo(); //使用拦截器里面的threalocal MemberRespVo memberRespVo = OrderInterceptor.loginUser.get(); CompletableFuturegetaddress = CompletableFuture.runAsync(() -> { //1:远程查询所有的收货地址列表。。。远程调用查询收货地址ums_member_receive_address表 mall-member List address = membersFeignService.getAddress(memberRespVo.getId()); confirmVo.setAddress(address); }, executor); CompletableFuture getcurrentUserCartItem = CompletableFuture.runAsync(() -> { //2:远程查询购物车所有选中的购物项 mall-order List currentUserCartItem = cartFeignService.getCurrentUserCartItem(); confirmVo.setItems(currentUserCartItem); }, executor); CompletableFuture getintegration = CompletableFuture.runAsync(() -> { //3:查询用户积分 Integer integration = memberRespVo.getIntegration(); confirmVo.setInteger(integration); }, executor); try { CompletableFuture.allOf(getaddress,getintegration,getcurrentUserCartItem).get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } //4:一些其他数据 //5:todo 防重令牌 return confirmVo; }
/** * 给订单确认页返回需要的数据confirm * @return */ @Override public OrderConfirmVo confirmOrder() { OrderConfirmVo confirmVo = new OrderConfirmVo(); //使用拦截器里面的threalocal MemberRespVo memberRespVo = OrderInterceptor.loginUser.get(); System.out.println("主线程。。。"+Thread.currentThread().getId()); //得到/addtTocart的请求进来的头数据 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); CompletableFuturegetaddress = CompletableFuture.runAsync(() -> { System.out.println("address线程。。。"+Thread.currentThread().getId()); //每一个线程都来共享之前的数据 RequestContextHolder.setRequestAttributes(requestAttributes); //1:远程查询所有的收货地址列表。。。远程调用查询收货地址ums_member_receive_address表 mall-member List address = membersFeignService.getAddress(memberRespVo.getId()); confirmVo.setAddress(address); }, executor); CompletableFuture getcurrentUserCartItem = CompletableFuture.runAsync(() -> { System.out.println("currentUserCartItem线程。。。"+Thread.currentThread().getId()); RequestContextHolder.setRequestAttributes(requestAttributes); //每一个线程都来共享之前的数据 //2:远程查询购物车所有选中的购物项 mall-order List currentUserCartItem = cartFeignService.getCurrentUserCartItem(); confirmVo.setItems(currentUserCartItem); }, executor); CompletableFuture getintegration = CompletableFuture.runAsync(() -> { RequestContextHolder.setRequestAttributes(requestAttributes); //每一个线程都来共享之前的数据 //3:查询用户积分 Integer integration = memberRespVo.getIntegration(); confirmVo.setInteger(integration); }, executor); try { //Returns a new CompletableFuture that is completed when all of // * the given CompletableFutures complete. If any of the given // * CompletableFutures complete exceptionally, then the returned // * CompletableFuture also does so, with a CompletionException // * holding this exception as its cause. CompletableFuture.allOf(getaddress,getintegration,getcurrentUserCartItem).get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } //4:一些其他数据 //5:todo 防重令牌 return confirmVo; }
MallFeignConfig
//通过feign调用的源码我们知道,feign远程调用的时候会丢失请求头@Configurationpublic class MallFeignConfig { @Bean("RequestContextHolder") public RequestInterceptor requestInterceptor(){ return new RequestInterceptor() { @Override public void apply(RequestTemplate requestTemplate) { System.out.println("拦截器的线程。。。"+Thread.currentThread().getId()); //可以使用RequestContextHolder拿到刚进来的这个请求的数据(http://order.mall.com/toTrade) ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); if (request!=null){ //同步请求头数据,cookie,为了发送到cart,让他知道order发送了cookie给cart,请求头不会丢失 String cookie = request.getHeader("Cookie"); //给新请求同步了老请求的cookie requestTemplate.header("Cookie",cookie); System.out.println("feign远程之前先进行RequestInterceptor.apply"); } } }; }}
@GetMapping("/product/skuinfo/{skuId}/price") public BigDecimal getPrice(@PathVariable("skuId") Long skuId);
所以我们作如下修改
mall-cart ProductFeignService@GetMapping("/product/skuinfo/{skuId}/price") public R getPrice(@PathVariable("skuId") Long skuId);
mall-cart
CartServiceImpl@Override public ListgetUserCartItems() { UserInfoTo userInfoTo = CartInterceptor.threadLocal.get(); if (userInfoTo.getUserId()==null){ return null; }else { String s = CART_PREFIX + userInfoTo.getUserId(); List cartItems = getCartItems(s); //从redis(getCartItems(s);)里面获取所有被选中的购物项 List collect = cartItems.stream(). filter(cartItem -> cartItem.getCheck() == true) .map(item->{ //todo 更新为最新价格 R price = productFeignService.getPrice(item.getSkuId()); String data = (String) price.get("data"); item.setPrice(new BigDecimal(data)); return item; }) .collect(Collectors.toList()); return collect; } }
mall-product
SkuInfoController@RestController@RequestMapping("product/skuinfo")public class SkuInfoController { @Autowired private SkuInfoService skuInfoService; @GetMapping("/{skuId}/price") public R getPrice(@PathVariable("skuId") Long skuId){ SkuInfoEntity byId = skuInfoService.getById(skuId); return R.ok().setData(byId.getPrice().toString()); }
远程调用mall-ware库存服务去查询是否有库存
mall-order** * 给订单确认页返回需要的数据confirm * @return */ @Override public OrderConfirmVo confirmOrder() { OrderConfirmVo confirmVo = new OrderConfirmVo(); //使用拦截器里面的threalocal MemberRespVo memberRespVo = OrderInterceptor.loginUser.get(); System.out.println("主线程。。。"+Thread.currentThread().getId()); System.out.println("memberRespVo的id。。。"+memberRespVo.getId()); //得到/addtTocart的请求进来的头数据(获取之前的请求) RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); CompletableFuturegetaddress = CompletableFuture.runAsync(() -> { System.out.println("address线程。。。"+Thread.currentThread().getId()); //每一个线程都来共享之前的数据 RequestContextHolder.setRequestAttributes(requestAttributes); //1:远程查询所有的收货地址列表。。。远程调用查询收货地址ums_member_receive_address表 mall-member System.out.println("memberRespVo的id。。。"+memberRespVo.getId()); List address = membersFeignService.getAddress(memberRespVo.getId()); confirmVo.setAddress(address); }, executor); CompletableFuture getcurrentUserCartItem = CompletableFuture.runAsync(() -> { //每一个线程都来共享之前的数据 System.out.println("currentUserCartItem线程。。。"+Thread.currentThread().getId()); RequestContextHolder.setRequestAttributes(requestAttributes); //2:远程查询购物车所有选中的购物项 mall-order List currentUserCartItem = cartFeignService.getCurrentUserCartItem(); confirmVo.setItems(currentUserCartItem); }, executor).thenRunAsync(()->{ List items = confirmVo.getItems(); List collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList()); R hasStock = wareFeignService.getSkusHasStock(collect); List data = hasStock.getData("data", new TypeReference< List >() { }); if (data!=null){ Map collect1 = data.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock)); confirmVo.setStocks(collect1); } },executor); CompletableFuture getintegration = CompletableFuture.runAsync(() -> { //每一个线程都来共享之前的数据 RequestContextHolder.setRequestAttributes(requestAttributes); //3:查询用户积分 Integer integration = memberRespVo.getIntegration(); confirmVo.setInteger(integration); }, executor); try { //Returns a new CompletableFuture that is completed when all of // * the given CompletableFutures complete. If any of the given // * CompletableFutures complete exceptionally, then the returned // * CompletableFuture also does so, with a CompletionException // * holding this exception as its cause. CompletableFuture.allOf(getaddress,getintegration,getcurrentUserCartItem).get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } //4:一些其他数据 //5:todo 防重令牌 return confirmVo; }
[[${item.title}]] ¥[[${item.price}]] x[[${item.count}]] [[${orderConfirmData.stocks[item.skuId]?"有货":"无货"}]]
测试:
mall-order
confirm.html[[${addr.name}]]
[[${addr.name}]] [[${addr.province}]] [[${addr.detailAddress}]] [[${addr.phone}]]应付总额:¥[[${orderConfirmData.total}]]
highlight(); var addid= $(".addr-item p[def='1']").attr("addrId") getFare(addid) }) function highlight() { $(".addr-item p").css({ "border":"2px sold gray"}) $(".addr-item p[def='1']").css({ "border":"2px sold red"}) } $(".addr-item p").click(function () { $(".addr-item p").attr("def","0") $(this).attr("def","1") highlight() //获取当前的地址id var addrId=$(this).attr("addrId") getFare(addrId) }) function getFare(addrId) { //发送ajax获取运费信息 $.get("http://zlj.mall.com/api/ware/wareinfo/fare?addrId="+addrId,function (data) { console.log(data) $("#fareEle").text(data.data) var total=[[${ orderConfirmData.total}]] $("#payPriceEle").text(total*1+data.data*1); }) }
{msg: "success", code: 0, data: 2} {msg: "success", code: 0, data: 3}
mall-ware
@RestController@RequestMapping("ware/wareinfo")public class WareInfoController { @Autowired private WareInfoService wareInfoService; @GetMapping("/fare") public R getFare(@RequestParam("addrId") Long addrId){ FareVo fare= wareInfoService.getFare(addrId); return R.ok().setData(fare); }
FareVo
@Datapublic class FareVo { private MemberReceiveAddressVo address; private BigDecimal fare;}
测试接口
http://zlj.mall.com/api/ware/wareinfo/fare?addrId=1
{"msg":"success","code":0,"data":{"address":{"id":1,"memberId":1,"name":"郑霖俊","phone":"18058008512","postCode":"23456","province":"福建省","city":"福州市","region":null,"detailAddress":"闽侯县金溪大道凤翔胡滨世纪","areacode":null,"defaultStatus":1},"fare":2}}
mall-order
confirm.html寄送至:收货人:
function getFare(addrId) { //发送ajax获取运费信息 mall-ware WareInfoController中 $.get("http://zlj.mall.com/api/ware/wareinfo/fare?addrId="+addrId,function (data) { console.log(data) $("#fareEle").text(data.data.fare) var total=[[${orderConfirmData.total}]] //设置运费等信息 $("#payPriceEle").text(total*1+data.data.fare*1); //设置收货人等信息 $("#receiveAddressEle").text(data.data.address.province+" "+data.data.address.detailAddress) $("#recieverEle").text(data.data.address.name) }) }
测试
mall-order confirm.html http://order.mall.com/toTrade5 件商品,总商品金额: ¥29400.0000返现: -¥0.00运费: ¥2服务费: ¥0.00退换无忧: ¥0.00应付总额:¥29402寄送至:福建省 闽侯县金溪大道凤翔胡滨世纪收货人:郑霖俊
我们可以使用令牌机制来解决接口幂等性问题
mall-order
OrderServiceImpl/** * 给订单确认页返回需要的数据confirm * @return */ @Override public OrderConfirmVo confirmOrder() { //.... //5:todo 防重令牌 String token = UUID.randomUUID().toString().replace("-", ""); //todo 给redis,confirmvo设置防重令牌 confirmVo.setOrderToken(token); redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX+memberRespVo.getId(),token,30, TimeUnit.MINUTES); CompletableFuture.allOf(getaddress,getintegration,getcurrentUserCartItem).get();
测试:
@Datapublic class OrderSubmitVo { private Long addrId;//收货地址 private Integer payType;//支付方式 //无需提交需要购买的商品,去购物车在获取一遍即可(redis) //优惠,发票 private String orderToken;//前端提交的防重令牌 private BigDecimal payPrice;//应付价格 验价 可以不做 //用户的所有信息,直接去session取出登录用户 private String note;//订单备注 可以不做}
在下单之前,我们必须要确保前端页面(confirm.html)的数据可以顺利提交给后端
mall-order OrderSubmitVo//封装订单提交的数据@Datapublic class OrderSubmitVo { private Long addrId;//收货地址 private Integer payType;//支付方式 //无需提交需要购买的商品,去购物车在获取一遍即可(redis) //优惠,发票 private String orderToken;//前端提交的防重令牌 private BigDecimal payPrice;//应付价格 验价 可以不做 //用户的所有信息,直接去session取出登录用户 private String note;//订单备注 可以不做}
confirm.html
function getFare(addrId) { //给表单回填选择的地址 $("#addIdInput").val(addrId) //... //设置运费等信息 var payPrice=total*1+resp.data.fare*1// $("#payPriceInput").val(payPrice)
OrderWebController
/** * 提交订单功能 * @param vo * @return */ @PostMapping("/submitOrder") public String submitOrder(OrderSubmitVo vo){ //todo 下单:去创建订单,验证令牌,验证价格,锁库存。。。。。 //下单成功来到支付选择页 //下单失败回到订单确认页重新确认订单信息 System.out.println("订单提交的数据。。"+vo); return null; }
测试前端发送的action="http://order.mall.com/submitOrder"数据是否会被后端成功接收
订单提交的数据。。OrderSubmitVo(addrId=1, payType=null, orderToken=f2765938350c45eb8767aa4697a8660d, payPrice=17402, note=null)
成功收到
我们接下来按照图解流程去完成提交订单的流程
提交订单以后跳转的页面有两种可呢,一种是下单成功返回订单信息,还有一种是下单失败,返回到confirm.html的页面,显示是什么原因失败(重复提交,还是验价,库存不足等等原因)
mall-order OrderWebController/** * 提交订单功能 * @param vo * @return */ @PostMapping("/submitOrder") public String submitOrder(OrderSubmitVo vo){ //todo 下单:去创建订单,验证令牌,验证价格,锁库存。。。。。就是去submitOrder方法中执行 SubmitOrderResponseVo submitOrderResponseVo=submitOrderResponseVo=orderService.submitOrder(vo); System.out.println("订单提交的数据。。"+vo); if (submitOrderResponseVo.getCode()==0){ //下单成功来到支付选择页 return "pay"; }else { //下单失败回到订单确认页重新确认订单信息 return "redirect:http://order.mall.com/toTrade"; } }
OrderServiceImpl
@Override public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) { SubmitOrderResponseVo submitOrderResponseVo=new SubmitOrderResponseVo(); //使用拦截器里面的threalocal MemberRespVo memberRespVo = OrderInterceptor.loginUser.get(); //1:验证令牌【令牌的对比和删除,获取令牌必须保证原子性】 //如果redis调用get方法获取KEYS[1]值,然后它就会返回令牌删除,然后返回0 //0令牌对比不一样就删除失败 1令牌对比删除成功 String script="if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; String orderToken = vo.getOrderToken(); //原子令牌验证和删除 Long result = redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()), orderToken); if (result==0){ //令牌验证不通过 return null; }else { //令牌验证通过// 下单:去创建订单,验证令牌,验证价格,锁库存 return null; }// String redistoken = redisTemplate.opsForValue().get(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());// if (orderToken!=null&&orderToken.equals(redistoken)){ // //令牌验证通过// redisTemplate.delete(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());// return null;// }else { // //不通过// return null;// } }
@Datapublic class OrderCreateTo { private OrderEntity order; private ListorderItems; private BigDecimal payPrice;//订单计算的应付价格 private BigDecimal fare;//运费}
OrderServiceImpl
private OrderCreateTo createOrder( ){ OrderCreateTo createTo = new OrderCreateTo(); //1:生成一个订单号 String orderSn = IdWorker.getTimeId(); OrderEntity orderEntity = buildOrder(orderSn); //2:获取到所有的订单项 ListitemEntities = builderOrderItems(); //3:验价 return createTo; } private OrderEntity buildOrder(String orderSn) { OrderEntity orderEntity = new OrderEntity(); orderEntity.setOrderSn(orderSn); //获取收货地址的信息 //使用ThreadLocal皆可以使用一条线程获取数据 OrderSubmitVo submitVo = confirmVoThreadLocal.get(); R fare = wareFeignService.getFare(submitVo.getAddrId()); FareVo fareresp = fare.getData("data", new TypeReference () { }); //设置运费信息 orderEntity.setFreightAmount(fareresp.getFare()); //设置收货人信息 orderEntity.setReceiverCity(fareresp.getAddress().getCity()); orderEntity.setReceiverDetailAddress(fareresp.getAddress().getDetailAddress()); orderEntity.setReceiverName(fareresp.getAddress().getName()); orderEntity.setReceiverPhone(fareresp.getAddress().getPhone()); orderEntity.setReceiverPostCode(fareresp.getAddress().getPostCode()); orderEntity.setReceiverProvince(fareresp.getAddress().getProvince()); orderEntity.setReceiverRegion(fareresp.getAddress().getRegion()); return orderEntity; } /** * 构建所有订单项数据 * @return */ private List builderOrderItems(){ //2:获取到所有的订单项(创建订单项) List currentUserCartItem = cartFeignService.getCurrentUserCartItem(); if (currentUserCartItem!=null&¤tUserCartItem.size()>0){ List itemEntities = currentUserCartItem.stream().map(cartItems -> { OrderItemEntity itemEntity = builderOrderItem(cartItems); return itemEntity; }).collect(Collectors.toList()); return itemEntities; } return null; } /** * 构建所有订单项中的每一个订单项 * @param cartItem * @return */ private OrderItemEntity builderOrderItem(OrderItemVo cartItem) { return null; }
/** * 构建所有订单项数据 * @return * @param orderSn */ private ListbuilderOrderItems(String orderSn){ //2:获取到所有的订单项(创建订单项) List currentUserCartItem = cartFeignService.getCurrentUserCartItem(); if (currentUserCartItem!=null&¤tUserCartItem.size()>0){ List itemEntities = currentUserCartItem.stream().map(cartItems -> { OrderItemEntity itemEntity = builderOrderItem(cartItems); itemEntity.setOrderSn(orderSn); return itemEntity; }).collect(Collectors.toList()); return itemEntities; } return null; } /** * 构建所有订单项中的每一个订单项 * @param cartItem * @return */ private OrderItemEntity builderOrderItem(OrderItemVo cartItem) { OrderItemEntity orderItemEntity = new OrderItemEntity(); //1:订单信息,订单号 //2:商品的spu信息 Long skuId = cartItem.getSkuId(); R spuInfoBySkuId = productFeignService.getSpuInfoBySkuId(skuId); SpuInfoVo data = spuInfoBySkuId.getData("data", new TypeReference () { }); orderItemEntity.setSpuBrand(data.getBrandId().toString()); orderItemEntity.setSpuId(data.getId()); orderItemEntity.setSpuName(data.getSpuName()); orderItemEntity.setCategoryId(data.getCatalogId()); //3:商品的sku信息 orderItemEntity.setSkuId(cartItem.getSkuId()); orderItemEntity.setSkuName(cartItem.getTitle()); orderItemEntity.setSkuPic(cartItem.getImage()); orderItemEntity.setSkuPrice(cartItem.getPrice()); String skuAttr = org.springframework.util.StringUtils.collectionToDelimitedString(cartItem.getSkuAttr(), ";"); orderItemEntity.setSkuAttrsVals(skuAttr); orderItemEntity.setSkuQuantity(cartItem.getCount()); //4:优惠信息(不做) //5:积分信息 orderItemEntity.setGiftGrowth(cartItem.getPrice().intValue()); orderItemEntity.setGiftIntegration(cartItem.getPrice().intValue()); ret }
@Override public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) { confirmVoThreadLocal.set(vo); SubmitOrderResponseVo submitOrderResponseVo=new SubmitOrderResponseVo(); //使用拦截器里面的threalocal MemberRespVo memberRespVo = OrderInterceptor.loginUser.get(); //1:验证令牌【令牌的对比和删除,获取令牌必须保证原子性】 //如果redis调用get方法获取KEYS[1]值,然后它就会返回令牌删除,然后返回0 //0令牌对比不一样就删除失败 1令牌对比删除成功 String script="if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; String orderToken = vo.getOrderToken(); //原子令牌验证和删除 Long result = redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()), orderToken); if (result==0){ //令牌验证不通过 submitOrderResponseVo.setCode(1); return submitOrderResponseVo; }else { //令牌验证通过// 下单:去创建订单,验证令牌,验证价格,锁库存 //第一步:创建订单,订单项等信息 OrderCreateTo order=createOrder(); //第二步:验证价格 BigDecimal payAmount = order.getOrder().getPayAmount(); BigDecimal payPrice = vo.getPayPrice(); //前端传来的页面的值和后端我们计算的价格这两个值相差不可以大于0.1;;就是会有误差很正常 if (Math.abs(payAmount.subtract(payPrice).doubleValue())<0.1){ //金额对比.. }else { submitOrderResponseVo.setCode(2); return submitOrderResponseVo; } }// String redistoken = redisTemplate.opsForValue().get(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());// if (orderToken!=null&&orderToken.equals(redistoken)){ // //令牌验证通过// redisTemplate.delete(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());// return null;// }else { // //不通过// return null;// } return submitOrderResponseVo; } private OrderCreateTo createOrder( ){ OrderCreateTo createTo = new OrderCreateTo(); //1:生成一个订单号 String orderSn = IdWorker.getTimeId(); OrderEntity orderEntity = buildOrder(orderSn); //2:获取到所有的订单项 List itemEntities = builderOrderItems(orderSn); //3:验价(计算价格相关) computePrice(orderEntity,itemEntities); return createTo; } private void computePrice(OrderEntity orderEntity, List itemEntities) { BigDecimal total = new BigDecimal("0.0"); //订单总额,叠加每一个订单项的总额信息 BigDecimal coupon=new BigDecimal("0.0"); BigDecimal integration=new BigDecimal("0.0"); BigDecimal promotion=new BigDecimal("0.0"); BigDecimal growth=new BigDecimal("0.0"); BigDecimal gift=new BigDecimal("0.0"); //订单的总额,叠加每一个订单的总额信息 for (OrderItemEntity entity:itemEntities ) { BigDecimal realAmount = entity.getRealAmount(); coupon=coupon.add(entity.getCouponAmount()); integration=integration.add(entity.getIntegrationAmount()); promotion=promotion.add(entity.getPromotionAmount()); total=total.add(realAmount); //设置积分,成长值 gift.add(new BigDecimal(entity.getGiftIntegration().toString())); growth.add(new BigDecimal(entity.getGiftGrowth().toString())); } //1:订单价格相关 orderEntity.setTotalAmount(total); //应付总额 orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount())); orderEntity.setPromotionAmount(promotion); orderEntity.setIntegrationAmount(integration); orderEntity.setCouponAmount(coupon); //设置积分等信息 orderEntity.setIntegration(gift.intValue()); orderEntity.setGrowth(growth.intValue()); orderEntity.setDeleteStatus(0);//未删除 } private OrderEntity buildOrder(String orderSn) { OrderEntity orderEntity = new OrderEntity(); orderEntity.setOrderSn(orderSn); //获取收货地址的信息 //使用ThreadLocal皆可以使用一条线程获取数据 OrderSubmitVo submitVo = confirmVoThreadLocal.get(); R fare = wareFeignService.getFare(submitVo.getAddrId()); FareVo fareresp = fare.getData("data", new TypeReference () { }); //设置运费信息 orderEntity.setFreightAmount(fareresp.getFare()); //设置收货人信息 orderEntity.setReceiverCity(fareresp.getAddress().getCity()); orderEntity.setReceiverDetailAddress(fareresp.getAddress().getDetailAddress()); orderEntity.setReceiverName(fareresp.getAddress().getName()); orderEntity.setReceiverPhone(fareresp.getAddress().getPhone()); orderEntity.setReceiverPostCode(fareresp.getAddress().getPostCode()); orderEntity.setReceiverProvince(fareresp.getAddress().getProvince()); orderEntity.setReceiverRegion(fareresp.getAddress().getRegion()); //设置订单的相关状态信息 orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode()); orderEntity.setAutoConfirmDay(7); return orderEntity; } /** * 构建所有订单项数据 * @return * @param orderSn */ private List builderOrderItems(String orderSn){ //2:获取到所有的订单项(创建订单项) List currentUserCartItem = cartFeignService.getCurrentUserCartItem(); if (currentUserCartItem!=null&¤tUserCartItem.size()>0){ List itemEntities = currentUserCartItem.stream().map(cartItems -> { OrderItemEntity itemEntity = builderOrderItem(cartItems); itemEntity.setOrderSn(orderSn); return itemEntity; }).collect(Collectors.toList()); return itemEntities; } return null; } /** * 构建所有订单项中的每一个订单项 * @param cartItem * @return */ private OrderItemEntity builderOrderItem(OrderItemVo cartItem) { OrderItemEntity orderItemEntity = new OrderItemEntity(); //1:订单信息,订单号 //2:商品的spu信息 Long skuId = cartItem.getSkuId(); R spuInfoBySkuId = productFeignService.getSpuInfoBySkuId(skuId); SpuInfoVo data = spuInfoBySkuId.getData("data", new TypeReference () { }); orderItemEntity.setSpuBrand(data.getBrandId().toString()); orderItemEntity.setSpuId(data.getId()); orderItemEntity.setSpuName(data.getSpuName()); orderItemEntity.setCategoryId(data.getCatalogId()); //3:商品的sku信息 orderItemEntity.setSkuId(cartItem.getSkuId()); orderItemEntity.setSkuName(cartItem.getTitle()); orderItemEntity.setSkuPic(cartItem.getImage()); orderItemEntity.setSkuPrice(cartItem.getPrice()); String skuAttr = org.springframework.util.StringUtils.collectionToDelimitedString(cartItem.getSkuAttr(), ";"); orderItemEntity.setSkuAttrsVals(skuAttr); orderItemEntity.setSkuQuantity(cartItem.getCount()); //4:优惠信息(不做) //5:积分信息 orderItemEntity.setGiftGrowth(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue()); orderItemEntity.setGiftIntegration(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue()); //6:订单项的价格信息 orderItemEntity.setPromotionAmount(new BigDecimal("0")); orderItemEntity.setCouponAmount(new BigDecimal(("0"))); orderItemEntity.setIntegrationAmount(new BigDecimal("0")); //当前订单项的实际金额=总额减去各种优惠 BigDecimal orign = orderItemEntity.getSkuPrice().multiply(new BigDecimal(orderItemEntity.getSkuQuantity().toString())); BigDecimal subtract = orign.subtract(orderItemEntity.getCouponAmount()) .subtract(orderItemEntity.getPromotionAmount()) .subtract(orderItemEntity.getIntegrationAmount()); orderItemEntity.setRealAmount(subtract); return orderItemEntity; }
@Datapublic class WareSkuLockVo { private String orderSn;//订单号 private Listlocks;//需要锁住的所有库存信息}
LockStockResult
@Datapublic class LockStockResult { private Long skuId; private Integer num; private Boolean locked;}
OrderItemVo
@Datapublic class OrderItemVo { private Long skuId; private Boolean check=true; private String title; private String image; private ListskuAttr; private BigDecimal price; private Integer count; private BigDecimal totalPrice; //todo 查询库存状态 private boolean hasStock=true;}
WareSkuController
@RestController@RequestMapping("ware/waresku")public class WareSkuController { @Autowired private WareSkuService wareSkuService; @PostMapping("/lock/order") public R orderLockStock(@RequestBody WareSkuLockVo vo){ //返回每一个商品锁定成功的信息,谁锁定成功了,谁没有锁定成功 ListlockStockResult= wareSkuService.orderLockStock(vo); return R.ok().setData(lockStockResult); }
mall-order
WareFeignService@FeignClient("mall-ware")public interface WareFeignService { @PostMapping("/lock/order") public R orderLockStock(@RequestBody WareSkuLockVo vo);}
OrderServiceImpl
@Transactional @Override public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) { confirmVoThreadLocal.set(vo); SubmitOrderResponseVo submitOrderResponseVo=new SubmitOrderResponseVo(); //使用拦截器里面的threalocal MemberRespVo memberRespVo = OrderInterceptor.loginUser.get(); //1:验证令牌【令牌的对比和删除,获取令牌必须保证原子性】 //如果redis调用get方法获取KEYS[1]值,然后它就会返回令牌删除,然后返回0 //0令牌对比不一样就删除失败 1令牌对比删除成功 String script="if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; String orderToken = vo.getOrderToken(); //原子令牌验证和删除 Long result = redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()), orderToken); if (result==0){ //令牌验证不通过 submitOrderResponseVo.setCode(1); return submitOrderResponseVo; }else { //令牌验证通过// 下单:去创建订单,验证令牌,验证价格,锁库存 //第一步:创建订单,订单项等信息 OrderCreateTo order=createOrder(); //第二步:验证价格 BigDecimal payAmount = order.getOrder().getPayAmount(); BigDecimal payPrice = vo.getPayPrice(); //前端传来的页面的值和后端我们计算的价格这两个值相差不可以大于0.1;;就是会有误差很正常 if (Math.abs(payAmount.subtract(payPrice).doubleValue())<0.1){ //金额对比.. //第三步:保存订单 saveOrder(order); //第四步:库存锁定 我们现在要加一个事务(只要有异常回滚订单数据) 只要订单一保存成功,我们马上就要锁住库存,如果库存没锁住就会回调返回失败 //订单号,所有订单项(skuId,skuName,num) WareSkuLockVo lockVo=new WareSkuLockVo(); lockVo.setOrderSn(order.getOrder().getOrderSn()); List locks = order.getOrderItems().stream().map(item -> { OrderItemVo itemVo = new OrderItemVo(); itemVo.setSkuId(item.getSkuId()); itemVo.setCount(item.getSkuQuantity()); itemVo.setTitle(item.getSkuName()); return itemVo; }).collect(Collectors.toList()); lockVo.setLocks(locks); //去(mall-ware)库存服务锁住我们在mall-order中传递过来的lockVo R r = wareFeignService.orderLockStock(lockVo); if (r.getCode()==0){ //锁成功 }else { //锁失败 } }else { submitOrderResponseVo.setCode(2); return submitOrderResponseVo; } }// String redistoken = redisTemplate.opsForValue().get(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());// if (orderToken!=null&&orderToken.equals(redistoken)){ // //令牌验证通过// redisTemplate.delete(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());// return null;// }else { // //不通过// return null;// } return submitOrderResponseVo; }
@RestController@RequestMapping("ware/waresku")public class WareSkuController { @PostMapping("/lock/order") public R orderLockStock(@RequestBody WareSkuLockVo vo){ //返回每一个商品锁定成功的信息,谁锁定成功了,谁没有锁定成功 try { Boolean lockStockResult= wareSkuService.orderLockStock(vo); return R.ok(); }catch (NoStockException e){ return R.error(BigCodeEnume.NO_STOCK_EXCEPTION.getCode(),BigCodeEnume.NO_STOCK_EXCEPTION.getMsg()); } }
mall-ware
WareSkuServiceImpl/** * 为某一个订单锁定库存 * @param vo * @return */ //默认是锁库存异常都会回滚 @Transactional(rollbackFor = NoStockException.class) @Override public Boolean orderLockStock(WareSkuLockVo vo) { //1:按照下单的收货地址,找到一个就近的仓库,锁定库存:太麻烦不这么做 //1:找到每个商品在那个仓库都有库存 Listlocks = vo.getLocks(); List collect = locks.stream().map(item -> { SkuWareHasStock skuWareHasStock = new SkuWareHasStock(); Long skuId = item.getSkuId(); skuWareHasStock.setSkuId(skuId); //查询这个商品在哪里有库存 List wareIds= wareSkuDao.listWareIdHasSkuStock(skuId); skuWareHasStock.setNum(item.getCount()); skuWareHasStock.setWareId(wareIds); return skuWareHasStock; }).collect(Collectors.toList()); Boolean allLock=false; //2:锁定库存 for (SkuWareHasStock hasStock:collect ) { Boolean skuStocked=false; Long skuId = hasStock.getSkuId(); List wareIds = hasStock.getWareId(); if (wareIds==null||wareIds.size()==0){ //没有任何仓库有这个商品的库存 throw new NoStockException(skuId); } for (Long wareId:wareIds){ //成功就返回1,否则返回0 Long count= wareSkuDao.lockSkuStock(skuId,wareId,hasStock.getNum()); if (count==1){ //当前仓库锁成功 skuStocked=true; break; }else { //当前仓库锁失败,重试下一个仓库 } } if (skuStocked==false){ //当前商品所有库存都没有锁住 throw new NoStockException(skuId); } } //3:肯定都是全部锁定成功的 return true; }
WareSkuDao
UPDATE wms_ware_sku SET stock_locked=stock_locked+#{num} where sku_id=#{skuId} and ware_id=#{wareId} and stock-stock_locked>=#{num}
mall-order
OrderServiceImpl@Transactional @Override public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) { confirmVoThreadLocal.set(vo); SubmitOrderResponseVo submitOrderResponseVo=new SubmitOrderResponseVo(); //使用拦截器里面的threalocal MemberRespVo memberRespVo = OrderInterceptor.loginUser.get(); //1:验证令牌【令牌的对比和删除,获取令牌必须保证原子性】 //如果redis调用get方法获取KEYS[1]值,然后它就会返回令牌删除,然后返回0 //0令牌对比不一样就删除失败 1令牌对比删除成功 String script="if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; String orderToken = vo.getOrderToken(); //原子令牌验证和删除 Long result = redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()), orderToken); if (result==0){ //令牌验证不通过 submitOrderResponseVo.setCode(1); return submitOrderResponseVo; }else { //令牌验证通过// 下单:去创建订单,验证令牌,验证价格,锁库存 //第一步:创建订单,订单项等信息 OrderCreateTo order=createOrder(); //第二步:验证价格 BigDecimal payAmount = order.getOrder().getPayAmount(); BigDecimal payPrice = vo.getPayPrice(); //前端传来的页面的值和后端我们计算的价格这两个值相差不可以大于0.1;;就是会有误差很正常 if (Math.abs(payAmount.subtract(payPrice).doubleValue())<0.1){ //金额对比.. //第三步:保存订单 saveOrder(order); //todo 第四步:远程库存锁定 我们现在要加一个事务(只要有异常回滚订单数据) 只要订单一保存成功,我们马上就要锁住库存,如果库存没锁住就会回调返回失败 //订单号,所有订单项(skuId,skuName,num) WareSkuLockVo lockVo=new WareSkuLockVo(); lockVo.setOrderSn(order.getOrder().getOrderSn()); List locks = order.getOrderItems().stream().map(item -> { OrderItemVo itemVo = new OrderItemVo(); itemVo.setSkuId(item.getSkuId()); itemVo.setCount(item.getSkuQuantity()); itemVo.setTitle(item.getSkuName()); return itemVo; }).collect(Collectors.toList()); lockVo.setLocks(locks); //去(mall-ware)库存服务锁住我们在mall-order中传递过来的lockVo R r = wareFeignService.orderLockStock(lockVo); if (r.getCode()==0){ //锁成功 submitOrderResponseVo.setOrderEntity(order.getOrder()); return submitOrderResponseVo; }else { //锁失败 submitOrderResponseVo.setCode(3); return submitOrderResponseVo; } }else { submitOrderResponseVo.setCode(2); return submitOrderResponseVo; } }// String redistoken = redisTemplate.opsForValue().get(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());// if (orderToken!=null&&orderToken.equals(redistoken)){ // //令牌验证通过// redisTemplate.delete(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());// return null;// }else { // //不通过// return null;// }// return submitOrderResponseVo; }
OrderWebController
/** * 提交订单功能 * @param vo * @return */ @PostMapping("/submitOrder") public String submitOrder(OrderSubmitVo vo){ //todo 下单:去创建订单,验证令牌,验证价格,锁库存。。。。。就是去submitOrder方法中执行 SubmitOrderResponseVo submitOrderResponseVo=submitOrderResponseVo=orderService.submitOrder(vo); System.out.println("订单提交的数据。。"+vo); if (submitOrderResponseVo.getCode()==0){ //下单成功来到支付选择页 return "pay"; }else { //下单失败回到订单确认页重新确认订单信息 return "redirect:http://order.mall.com/toTrade"; } }
转载地址:http://ckhq.baihongyu.com/