博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
电商项目——订单服务——第十二章——中篇
阅读量:324 次
发布时间:2019-03-04

本文共 46180 字,大约阅读时间需要 153 分钟。

文章目录

1:订单基本概念

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2:订单登录拦截

mall-order

OrderInterceptor

@Controllerpublic class OrderInterceptor implements HandlerInterceptor{
//有了它就可以共享数据 public static ThreadLocal
loginUser=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"; }}

3:订单确认页模型抽取

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表 List
address; @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 List
skuAttr; private BigDecimal price; private Integer count; private BigDecimal totalPrice;}

4:订单确认页数据获取

完成如下模块的后端代码

在这里插入图片描述

//订单确认页需要的数据//订单确认页需要的数据public class OrderConfirmVo {
@Getter @Setter //收货地址 ums_member_receive_address表 List
address; @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 List
address = 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; }}

5:Feign远程调用丢失请求头问题和Feign异步调用丢失请求头问题

  • Feign远程调用丢失请求头问题

在这里插入图片描述

在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(); CompletableFuture
getaddress = 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; }
  • Feign异步调用丢失请求头问题
    在这里插入图片描述
    解决方案:我们就要在service中共享它的请求头,代码如下
    (每一个父线程进来,我们还是使用RequestContextHolder进行子线程的共享)
    OrderServiceImpl
/**     * 给订单确认页返回需要的数据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(); CompletableFuture
getaddress = 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"); } } }; }}

6:bug修改

在这里插入图片描述

原因就是:mall-cart中调用mall-product的如下方法,不可以解析BigDecimal
mall-cart
ProductFeignService

@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 List
getUserCartItems() {
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()); }

7:订单确认页渲染

在这里插入图片描述

8:订单确认页库存查询

远程调用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(); CompletableFuture
getaddress = 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]?"有货":"无货"}]]

测试:

在这里插入图片描述
在这里插入图片描述

9:订单确认页模拟运费效果

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}

在这里插入图片描述

10:订单确认页细节显示

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/toTrade

5 件商品,总商品金额: ¥29400.0000返现: -¥0.00运费:   ¥2服务费:   ¥0.00退换无忧:   ¥0.00应付总额:¥29402寄送至:福建省 闽侯县金溪大道凤翔胡滨世纪收货人:郑霖俊

11:接口幂等性讨论

12:订单确认页完成

我们可以使用令牌机制来解决接口幂等性问题

在这里插入图片描述
返回确认页的大致流程(confirm.html)
在这里插入图片描述
代码演示:在前端页面可以获取到后端产生的令牌
mall-order
confirm.html

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();

测试:

在这里插入图片描述
我们现在就来开始完成提交订单的功能
mall-order

@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)

成功收到

13:原子验令牌

我们接下来按照图解流程去完成提交订单的流程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

提交订单以后跳转的页面有两种可呢,一种是下单成功返回订单信息,还有一种是下单失败,返回到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;// } }

14:构造订单数据

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
mall-order
OrderCreateTo

@Datapublic class OrderCreateTo {
private OrderEntity order; private List
orderItems; private BigDecimal payPrice;//订单计算的应付价格 private BigDecimal fare;//运费}

OrderServiceImpl

private OrderCreateTo createOrder( ){
OrderCreateTo createTo = new OrderCreateTo(); //1:生成一个订单号 String orderSn = IdWorker.getTimeId(); OrderEntity orderEntity = buildOrder(orderSn); //2:获取到所有的订单项 List
itemEntities = 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&&currentUserCartItem.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; }

15:构造订单项数据

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
mall-order
OrderServiceImpl

/**     * 构建所有订单项数据     * @return     * @param orderSn     */    private List
builderOrderItems(String orderSn){
//2:获取到所有的订单项(创建订单项) List
currentUserCartItem = cartFeignService.getCurrentUserCartItem(); if (currentUserCartItem!=null&&currentUserCartItem.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 }

16:订单验价

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
OrderServiceImpl

@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&&currentUserCartItem.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; }

17:保存订单数据

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
mall-ware
WareSkuLockVo

@Datapublic class WareSkuLockVo {
private String orderSn;//订单号 private List
locks;//需要锁住的所有库存信息}

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 List
skuAttr; 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){
//返回每一个商品锁定成功的信息,谁锁定成功了,谁没有锁定成功 List
lockStockResult= 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; }

18:锁定库存

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
我们所有买的商品都要锁库存,比如1,2,3订单,所以每一个商品都要尝试锁库存,只有全部锁定就可以成功,修改锁定状态,只要有一个没锁库存就会撤销
mall-ware
WareSkuController

@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:找到每个商品在那个仓库都有库存 List
locks = 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}

19:提交订单的问题

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/

你可能感兴趣的文章