商城订单结算购物幂等性设计 根据Session状态判断登录,准备订单确认页信息 (获取购物车商品,获取会员收货地址列表,获取优惠卷,计算价格) 创建防虫令牌,渲染订单页(在渲染页面前创建令牌,防止占用资源) 防虫令牌采用Redis来设计 渲染订单确认页,然后结束流程商城订单结算购物幂等性设计
根据Session状态判断登录,准备订单确认页信息
(获取购物车商品,获取会员收货地址列表,获取优惠卷,计算价格)
创建防虫令牌,渲染订单页(在渲染页面前创建令牌,防止占用资源)
防虫令牌采用Redis来设计
渲染订单确认页,然后结束流程
package com.alatus.mall.order.vo; import lombok.Data; import java.math.BigDecimal; @Data public class OrderSubmitVo { // 收货地址的ID private Long addrId; // 支付方式 private Integer payType; // TODO 送货清单 // TODO 无需提交购物车的商品,我们还会再去获取一次的 // TODO 优惠,发票 // 令牌 private String orderToken; // 应付价格验证价格 private BigDecimal payPrice; // 用户信息在session里面 // 订单备注 private String note; }package com.alatus.mall.order.vo; import lombok.Data; import java.math.BigDecimal; @Data public class OrderSubmitVo { // 收货地址的ID private Long addrId; // 支付方式 private Integer payType; // TODO 送货清单 // TODO 无需提交购物车的商品,我们还会再去获取一次的 // TODO 优惠,发票 // 令牌 private String orderToken; // 应付价格验证价格 private BigDecimal payPrice; // 用户信息在session里面 // 订单备注 private String note; }
package com.alatus.mall.order.service; import com.alatus.mall.order.vo.OrderConfirmVo; import com.baomidou.mybatisplus.extension.service.IService; import com.alatus.common.utils.PageUtils; import com.alatus.mall.order.entity.OrderEntity; import java.math.BigDecimal; import java.util.Map; import java.util.concurrent.ExecutionException; /** * 订单 * * @author alatus * @date 2024-03-12 13:50:51 */ public interface OrderService extends IService<OrderEntity> { PageUtils queryPage(Map<String, Object> params); OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException; BigDecimal changeAddr(Long addrId) throws ExecutionException, InterruptedException; }package com.alatus.mall.order.service; import com.alatus.mall.order.vo.OrderConfirmVo; import com.baomidou.mybatisplus.extension.service.IService; import com.alatus.common.utils.PageUtils; import com.alatus.mall.order.entity.OrderEntity; import java.math.BigDecimal; import java.util.Map; import java.util.concurrent.ExecutionException; /** * 订单 * * @author alatus * @date 2024-03-12 13:50:51 */ public interface OrderService extends IService<OrderEntity> { PageUtils queryPage(Map<String, Object> params); OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException; BigDecimal changeAddr(Long addrId) throws ExecutionException, InterruptedException; }
package com.alatus.mall.order.web; import com.alatus.mall.order.service.OrderService; import com.alatus.mall.order.vo.OrderConfirmVo; import com.alatus.mall.order.vo.OrderSubmitVo; import com.alibaba.fastjson.JSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.math.BigDecimal; import java.util.concurrent.ExecutionException; @Controller public class OrderWebController { @Autowired private OrderService orderService; @GetMapping("/toTrade") public String toTrade(Model model) throws ExecutionException, InterruptedException { OrderConfirmVo confirmVo = orderService.confirmOrder(); model.addAttribute("orderConfirmData",confirmVo); return "confirm"; } @GetMapping("/changeAddr/{addrId}") @ResponseBody public String changeAddr(@PathVariable("addrId") Long addrId) throws ExecutionException, InterruptedException { BigDecimal bigDecimal = orderService.changeAddr(addrId); return JSON.toJSONString(bigDecimal); } @PostMapping("/submitOrder") public String submitOrder(OrderSubmitVo orderSubmitVo){ // 去创建订单,验证令牌,验证价格,锁定库存 // 下单成功去支付选择页面,下单失败去重新确认订单信息 return null; } }package com.alatus.mall.order.web; import com.alatus.mall.order.service.OrderService; import com.alatus.mall.order.vo.OrderConfirmVo; import com.alatus.mall.order.vo.OrderSubmitVo; import com.alibaba.fastjson.JSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.math.BigDecimal; import java.util.concurrent.ExecutionException; @Controller public class OrderWebController { @Autowired private OrderService orderService; @GetMapping("/toTrade") public String toTrade(Model model) throws ExecutionException, InterruptedException { OrderConfirmVo confirmVo = orderService.confirmOrder(); model.addAttribute("orderConfirmData",confirmVo); return "confirm"; } @GetMapping("/changeAddr/{addrId}") @ResponseBody public String changeAddr(@PathVariable("addrId") Long addrId) throws ExecutionException, InterruptedException { BigDecimal bigDecimal = orderService.changeAddr(addrId); return JSON.toJSONString(bigDecimal); } @PostMapping("/submitOrder") public String submitOrder(OrderSubmitVo orderSubmitVo){ // 去创建订单,验证令牌,验证价格,锁定库存 // 下单成功去支付选择页面,下单失败去重新确认订单信息 return null; } }
package com.alatus.mall.order.service.impl; import com.alatus.common.to.MemberAddressTo; import com.alatus.common.utils.R; import com.alatus.common.vo.MemberRespVo; import com.alatus.common.vo.SkuHasStockVo; import com.alatus.mall.order.constant.OrderConstant; import com.alatus.mall.order.feign.CartFeignService; import com.alatus.mall.order.feign.MemberFeignService; import com.alatus.mall.order.feign.WareFeignService; import com.alatus.mall.order.interceptor.LoginUserInterceptor; import com.alatus.mall.order.vo.OrderConfirmVo; import com.alatus.mall.order.vo.OrderItemVo; import com.alibaba.fastjson.TypeReference; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.alatus.common.utils.PageUtils; import com.alatus.common.utils.Query; import com.alatus.mall.order.dao.OrderDao; import com.alatus.mall.order.entity.OrderEntity; import com.alatus.mall.order.service.OrderService; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @Service("orderService") public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService { @Autowired private MemberFeignService memberFeignService; @Autowired private CartFeignService cartFeignService; @Autowired private ThreadPoolExecutor threadPoolExecutor; @Autowired private WareFeignService wareFeignService; @Autowired private StringRedisTemplate redisTemplate; @Override public PageUtils queryPage(Map<String, Object> params) { IPage<OrderEntity> page = this.page( new Query<OrderEntity>().getPage(params), new QueryWrapper<OrderEntity>() ); return new PageUtils(page); } @Override public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException { OrderConfirmVo confirmVo = new OrderConfirmVo(); // 获取原请求信息,避免异步导致丢失 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get(); // 远程查询购物车所有选中的购物项 CompletableFuture<Void> itemVoList = CompletableFuture.runAsync(() -> { RequestContextHolder.setRequestAttributes(requestAttributes); confirmVo.setItemVoList(cartFeignService.getCurrentCartItem()); }, threadPoolExecutor).thenRunAsync(()->{ // 获取原请求信息,避免异步导致丢失 RequestContextHolder.setRequestAttributes(requestAttributes); // 查询库存 List<OrderItemVo> itemList = confirmVo.getItemVoList(); List<Long> ids = itemList.stream().map(OrderItemVo::getSkuId).collect(Collectors.toList()); R skuHasStock = wareFeignService.getSkuHasStock(ids); if(skuHasStock.getCode()==0){ List<SkuHasStockVo> data = skuHasStock.getData(new TypeReference<List<SkuHasStockVo>>(){}); if(data!=null&& !data.isEmpty()){ Map<Long, Boolean> collect = data.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock)); confirmVo.setStocks(collect); } } },threadPoolExecutor); // 远程查询所有的用户地址列表 CompletableFuture<Void> addressTask = CompletableFuture.runAsync(() -> { RequestContextHolder.setRequestAttributes(requestAttributes); confirmVo.setAddress(memberFeignService.getAddress(memberRespVo.getId())); }, threadPoolExecutor).thenRunAsync(()->{ // 获取运费信息 RequestContextHolder.setRequestAttributes(requestAttributes); List<MemberAddressTo> address = confirmVo.getAddress(); for (MemberAddressTo memberAddressTo : address) { if(memberAddressTo.getDefaultStatus()==1){ R fare = wareFeignService.getFare(memberAddressTo.getId()); if (fare.getCode()==0) { confirmVo.setFare(fare.getData(new TypeReference<BigDecimal>() {})); } } } },threadPoolExecutor); // 用户积分 CompletableFuture<Void> integrationTask = CompletableFuture.runAsync(() -> { confirmVo.setIntegration(memberRespVo.getIntegration()); }, threadPoolExecutor); // TODO 订单令牌 // 订单令牌 CompletableFuture<Void> tokenTask = CompletableFuture.runAsync(() -> { String token = UUID.randomUUID().toString().replace("-", ""); confirmVo.setOrderToken(token); redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId(), token, 30, TimeUnit.MINUTES); }, threadPoolExecutor); CompletableFuture.allOf(addressTask,itemVoList,integrationTask,tokenTask).get(); return confirmVo; } @Override public BigDecimal changeAddr(Long addrId) throws ExecutionException, InterruptedException { // 获取原请求信息,避免异步导致丢失 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get(); CompletableFuture<Void> changeAddr = CompletableFuture.runAsync(() -> { RequestContextHolder.setRequestAttributes(requestAttributes); R r = memberFeignService.changeAddr(memberRespVo.getId(), addrId); if (r.getCode() != 0) { throw new RuntimeException("修改失败"); } }, threadPoolExecutor); // 查询新的运费信息 CompletableFuture<BigDecimal> getFareTask = CompletableFuture.supplyAsync(() -> { RequestContextHolder.setRequestAttributes(requestAttributes); R fare = wareFeignService.getFare(addrId); if (fare.getCode() == 0) { BigDecimal farePay = fare.getData(new TypeReference<BigDecimal>() {}); return farePay; } else{ return new BigDecimal(0); } }, threadPoolExecutor); CompletableFuture.allOf(changeAddr,getFareTask).get(); return getFareTask.get(); } }package com.alatus.mall.order.service.impl; import com.alatus.common.to.MemberAddressTo; import com.alatus.common.utils.R; import com.alatus.common.vo.MemberRespVo; import com.alatus.common.vo.SkuHasStockVo; import com.alatus.mall.order.constant.OrderConstant; import com.alatus.mall.order.feign.CartFeignService; import com.alatus.mall.order.feign.MemberFeignService; import com.alatus.mall.order.feign.WareFeignService; import com.alatus.mall.order.interceptor.LoginUserInterceptor; import com.alatus.mall.order.vo.OrderConfirmVo; import com.alatus.mall.order.vo.OrderItemVo; import com.alibaba.fastjson.TypeReference; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.alatus.common.utils.PageUtils; import com.alatus.common.utils.Query; import com.alatus.mall.order.dao.OrderDao; import com.alatus.mall.order.entity.OrderEntity; import com.alatus.mall.order.service.OrderService; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @Service("orderService") public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService { @Autowired private MemberFeignService memberFeignService; @Autowired private CartFeignService cartFeignService; @Autowired private ThreadPoolExecutor threadPoolExecutor; @Autowired private WareFeignService wareFeignService; @Autowired private StringRedisTemplate redisTemplate; @Override public PageUtils queryPage(Map<String, Object> params) { IPage<OrderEntity> page = this.page( new Query<OrderEntity>().getPage(params), new QueryWrapper<OrderEntity>() ); return new PageUtils(page); } @Override public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException { OrderConfirmVo confirmVo = new OrderConfirmVo(); // 获取原请求信息,避免异步导致丢失 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get(); // 远程查询购物车所有选中的购物项 CompletableFuture<Void> itemVoList = CompletableFuture.runAsync(() -> { RequestContextHolder.setRequestAttributes(requestAttributes); confirmVo.setItemVoList(cartFeignService.getCurrentCartItem()); }, threadPoolExecutor).thenRunAsync(()->{ // 获取原请求信息,避免异步导致丢失 RequestContextHolder.setRequestAttributes(requestAttributes); // 查询库存 List<OrderItemVo> itemList = confirmVo.getItemVoList(); List<Long> ids = itemList.stream().map(OrderItemVo::getSkuId).collect(Collectors.toList()); R skuHasStock = wareFeignService.getSkuHasStock(ids); if(skuHasStock.getCode()==0){ List<SkuHasStockVo> data = skuHasStock.getData(new TypeReference<List<SkuHasStockVo>>(){}); if(data!=null&& !data.isEmpty()){ Map<Long, Boolean> collect = data.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock)); confirmVo.setStocks(collect); } } },threadPoolExecutor); // 远程查询所有的用户地址列表 CompletableFuture<Void> addressTask = CompletableFuture.runAsync(() -> { RequestContextHolder.setRequestAttributes(requestAttributes); confirmVo.setAddress(memberFeignService.getAddress(memberRespVo.getId())); }, threadPoolExecutor).thenRunAsync(()->{ // 获取运费信息 RequestContextHolder.setRequestAttributes(requestAttributes); List<MemberAddressTo> address = confirmVo.getAddress(); for (MemberAddressTo memberAddressTo : address) { if(memberAddressTo.getDefaultStatus()==1){ R fare = wareFeignService.getFare(memberAddressTo.getId()); if (fare.getCode()==0) { confirmVo.setFare(fare.getData(new TypeReference<BigDecimal>() {})); } } } },threadPoolExecutor); // 用户积分 CompletableFuture<Void> integrationTask = CompletableFuture.runAsync(() -> { confirmVo.setIntegration(memberRespVo.getIntegration()); }, threadPoolExecutor); // TODO 订单令牌 // 订单令牌 CompletableFuture<Void> tokenTask = CompletableFuture.runAsync(() -> { String token = UUID.randomUUID().toString().replace("-", ""); confirmVo.setOrderToken(token); redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId(), token, 30, TimeUnit.MINUTES); }, threadPoolExecutor); CompletableFuture.allOf(addressTask,itemVoList,integrationTask,tokenTask).get(); return confirmVo; } @Override public BigDecimal changeAddr(Long addrId) throws ExecutionException, InterruptedException { // 获取原请求信息,避免异步导致丢失 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get(); CompletableFuture<Void> changeAddr = CompletableFuture.runAsync(() -> { RequestContextHolder.setRequestAttributes(requestAttributes); R r = memberFeignService.changeAddr(memberRespVo.getId(), addrId); if (r.getCode() != 0) { throw new RuntimeException("修改失败"); } }, threadPoolExecutor); // 查询新的运费信息 CompletableFuture<BigDecimal> getFareTask = CompletableFuture.supplyAsync(() -> { RequestContextHolder.setRequestAttributes(requestAttributes); R fare = wareFeignService.getFare(addrId); if (fare.getCode() == 0) { BigDecimal farePay = fare.getData(new TypeReference<BigDecimal>() {}); return farePay; } else{ return new BigDecimal(0); } }, threadPoolExecutor); CompletableFuture.allOf(changeAddr,getFareTask).get(); return getFareTask.get(); } }


被折叠的 条评论
为什么被折叠?



