穀粒商城訂單服務(四十)
阿新 • • 發佈:2020-08-06
261-284 訂單服務
程式碼提交到碼雲:https://gitee.com/dalianpai/gulimall
感覺訂單服務業務邏輯還是比較複雜的,改html改的頭皮發麻。
上個星期寫的,沒有時間簡單的記錄一下,讓人感覺有點意思的也就Feign的請求頭丟失的問題。
ThreadLocal可以檢視之前的部落格:https://www.cnblogs.com/dalianpai/p/12623044.html
/** * @author WGR * @create 2020/7/27 -- 21:52 */ @Component public class LoginUserInterceptor implements HandlerInterceptor { public static ThreadLocal<MemberRespVo> 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.gulimall.com/login.html"); return false; } } }
Feign的請求頭丟失的問題:https://www.cnblogs.com/dalianpai/p/12426425.html
/** * @author WGR * @create 2020/7/28 -- 16:01 */ @Configuration public class GuliFeignConfig { @Bean("requestInterceptor") public RequestInterceptor requestInterceptor(){ return new RequestInterceptor() { @Override public void apply(RequestTemplate requestTemplate) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if(attributes !=null){ HttpServletRequest request = attributes.getRequest(); if(request !=null){ requestTemplate.header("Cookie", request.getHeader("Cookie")); } } } }; } }
業務邏輯:
/** * @author WGR * @create 2020/7/27 -- 22:05 */ @Controller public class OrderWenController { @Autowired OrderService orderService; @GetMapping("/toTrade") public String toTrade(Model model) throws ExecutionException, InterruptedException { OrderConfirmVo confirmVo = orderService.confirmOrder(); model.addAttribute("orderConfirmData",confirmVo); return "confirm"; } @PostMapping("/submitOrder") public String submitOrder(OrderSubmitVo vo, Model model, RedirectAttributes redirectAttributes){ System.out.println("訂單提交的資料。。。"+vo); SubmitOrderResponseVo responseVo = orderService.submitOrder(vo); if(responseVo.getCode() ==0){ System.out.println(responseVo); model.addAttribute("submitOrderResp",responseVo); return "pay"; }else{ String msg="下單失敗:"; switch (responseVo.getCode()){ case 1: msg += "訂單資訊過期,請重新整理再次提交";break; case 2: msg += "訂單商品價格發生變化,請確認後再次提交";break; case 3: msg += "庫存鎖定失敗,商品庫存不足";break; } redirectAttributes.addFlashAttribute("msg",msg); return "redirect:http://order.gulimall.com/toTrade"; } } }
@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {
private ThreadLocal<OrderSubmitVo> confirmVoThreadLocal = new ThreadLocal<>();
@Autowired
MemberFeignService memberFeignService;
@Autowired
OrderItemService orderItemService;
@Autowired(required = false)
OrderDao orderDao;
@Autowired(required = false)
OrderItemDao orderItemDao;
@Autowired
CartFeignService cartFeignService;
@Autowired
WmsFeignService wmsFeignService;
@Autowired
StringRedisTemplate redisTemplate;
@Autowired
ThreadPoolExecutor executor;
@Autowired
ProductFeignService productFeignService;
@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 orderConfirmVo = new OrderConfirmVo();
MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//1.遠端查詢所有的收貨地址列表
CompletableFuture<Void> getCompletableFuture = CompletableFuture.runAsync(() -> {
RequestContextHolder.setRequestAttributes(requestAttributes);
List<MemberReceiveAddressEntity> address = memberFeignService.getAddress(memberRespVo.getId());
System.out.println(address);
orderConfirmVo.setAddress(address);
}, executor);
//2.遠端查詢購物車所有選中的購物項
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
RequestContextHolder.setRequestAttributes(requestAttributes);
List<CartItem> items = cartFeignService.getCurrentUserCartItems();
orderConfirmVo.setItems(items);
}, executor).thenRunAsync( () ->{
List<CartItem> items = orderConfirmVo.getItems();
List<Long> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());
R hasStock = wmsFeignService.getSkusHasStock(collect);
List<SkuHasStockVo> data = hasStock.getData(new TypeReference<List<SkuHasStockVo>>() {
});
if(data !=null){
Map<Long, Boolean> map = data.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock));
orderConfirmVo.setStocks(map);
}
},executor);
//3.查詢使用者積分
Integer integration = memberRespVo.getIntegration();
orderConfirmVo.setIntegration(integration);
//5 防重令牌
String token = UUID.randomUUID().toString().replace("-", "");
System.out.println(OrderConstant.USER_ORDER_TOKEN_PREFIX+memberRespVo.getId());
redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX+memberRespVo.getId(),token,30, TimeUnit.MINUTES);
orderConfirmVo.setOrderToken(token);
CompletableFuture.allOf(completableFuture,getCompletableFuture).get();
return orderConfirmVo;
}
@GlobalTransactional
@Transactional
@Override
public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {
confirmVoThreadLocal.set(vo);
SubmitOrderResponseVo responseVo = new SubmitOrderResponseVo();
responseVo.setCode(0);
MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
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<Long>(script,Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX+memberRespVo.getId() ),orderToken);
if(result ==0L){
responseVo.setCode(1);
return responseVo;
}else{
//令牌驗證成功
//下單: 去建立訂單,驗證令牌,驗價格,鎖庫存
OrderCreateTo order = createOrder();
//2. 驗價
BigDecimal payAmount = order.getOrder().getPayAmount();
BigDecimal payPrice = vo.getPayPrice();
if(Math.abs(payAmount.subtract(payPrice).doubleValue())<0.01){
//3 儲存訂單
saveOrder(order);
//4 庫存鎖定
WareSkuLockVo lockVo = new WareSkuLockVo();
lockVo.setOrderSn(order.getOrder().getOrderSn());
List<CartItem> locks = order.getOrderItems().stream().map(item -> {
CartItem itemVo = new CartItem();
itemVo.setSkuId(item.getSkuId());
itemVo.setCount(item.getSkuQuantity());
itemVo.setTitle(item.getSkuName());
return itemVo;
}).collect(Collectors.toList());
lockVo.setLocks(locks);
R r = wmsFeignService.orderLockStock(lockVo);
if(r.getCode() == 0){
//鎖定成功
responseVo.setOrder(order.getOrder());
// int i = 1/0;
return responseVo;
}else{
//鎖定失敗
responseVo.setCode(3);
return responseVo;
}
}else{
responseVo.setCode(2);
return responseVo;
}
}
}
@Transactional
private void saveOrder(OrderCreateTo order) {
OrderEntity orderEntity = order.getOrder();
orderEntity.setModifyTime(new Date());
this.save(orderEntity);
List<OrderItemEntity> orderItems = order.getOrderItems();
orderItems.forEach(l ->{
orderItemService.save(l);
});
// orderItemService.saveBatch(orderItems);
}
private OrderCreateTo createOrder(){
OrderCreateTo ceateTo = new OrderCreateTo();
//生成單號
String orderSn = IdWorker.getTimeId();
//建立訂單號
OrderEntity entity = buildOrder(orderSn);
//2 獲取到所有的訂單項
List<OrderItemEntity> itemEntities = buildOrderItems();
//3.計算價格相關
computePrice(entity,itemEntities);
ceateTo.setOrder(entity);
ceateTo.setOrderItems(itemEntities);
return ceateTo;
}
private void computePrice(OrderEntity orderEntity, List<OrderItemEntity> 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 gift = new BigDecimal("0.0");
BigDecimal growth = new BigDecimal("0.0");
for(OrderItemEntity entity:itemEntities){
coupon = coupon.add(entity.getCouponAmount());
integration = integration.add(entity.getIntegrationAmount());
promotion = promotion.add(entity.getPromotionAmount());
total = total.add(entity.getRealAmount());
gift = gift.add(new BigDecimal(entity.getGiftIntegration().toString()));
growth = growth.add(new BigDecimal(entity.getGiftGrowth().toString()));
}
orderEntity.setTotalAmount(total);
orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount()));
orderEntity.setIntegrationAmount(integration);
orderEntity.setCouponAmount(coupon);
//設定積分等資訊
orderEntity.setIntegration(gift.intValue());
orderEntity.setGrowth(growth.intValue());
orderEntity.setDeleteStatus(0);
}
private OrderEntity buildOrder(String orderSn) {
MemberRespVo respVo = LoginUserInterceptor.loginUser.get();
OrderEntity entity = new OrderEntity();
entity.setOrderSn(orderSn);
entity.setMemberId(respVo.getId());
OrderSubmitVo submitVo = confirmVoThreadLocal.get();
//獲取地址的收貨資訊
R fare = wmsFeignService.getFare(submitVo.getAddrId());
FareVo fareResp = fare.getData(new TypeReference<FareVo>() {
});
entity.setFreightAmount(fareResp.getFare());
entity.setReceiverCity(fareResp.getAddress().getCity());
entity.setReceiverDetailAddress(fareResp.getAddress().getDetailAddress());
entity.setReceiverName(fareResp.getAddress().getName());
entity.setReceiverPhone(fareResp.getAddress().getPhone());
entity.setReceiverPostCode(fareResp.getAddress().getPostCode());
entity.setReceiverProvince(fareResp.getAddress().getProvince());
entity.setReceiverRegion(fareResp.getAddress().getRegion());
//設定訂單的相關狀態資訊
entity.setStatus(1);
entity.setAutoConfirmDay(7);
return entity;
}
/**
* 構建所有訂單項資料
*/
private List<OrderItemEntity> buildOrderItems(){
List<CartItem> currentUserCartItems = cartFeignService.getCurrentUserCartItems();
if(currentUserCartItems !=null && currentUserCartItems.size()>0){
List<OrderItemEntity> itemEntities = currentUserCartItems.stream().map(cartItem ->{
OrderItemEntity itemEntity =buildOrderItem(cartItem);
return itemEntity;
}).collect(Collectors.toList());
return itemEntities;
}
return null ;
}
private OrderItemEntity buildOrderItem(CartItem cartItem) {
OrderItemEntity itemEntity = new OrderItemEntity();
Long skuId = cartItem.getSkuId();
R r = productFeignService.getSpuInfoBySkuId(skuId);
SpuInfoVo data = r.getData(new TypeReference<SpuInfoVo>() {
});
itemEntity.setSpuId(data.getId());
itemEntity.setSpuBrand(data.getBrandId().toString());
itemEntity.setSpuName(data.getSpuName());
itemEntity.setCategoryId(data.getCatalogId());
//3.商品的sku資訊
itemEntity.setSkuId(cartItem.getSkuId());
itemEntity.setSkuName(cartItem.getTitle());
itemEntity.setSkuPic(cartItem.getImages());
itemEntity.setSkuPrice(cartItem.getPrice());
String skuAttr = StringUtils.collectionToDelimitedString(cartItem.getSkuAttr(), ";");
itemEntity.setSkuAttrsVals(skuAttr);
itemEntity.setSkuQuantity(cartItem.getCount());
//5.積分資訊
itemEntity.setGiftGrowth(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue());
itemEntity.setGiftIntegration(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue());
//6.訂單項的價格資訊
itemEntity.setPromotionAmount(new BigDecimal("0"));
itemEntity.setCouponAmount(new BigDecimal("0"));
itemEntity.setIntegrationAmount(new BigDecimal("0"));
//當前訂單項的實際金額。總額-各種優惠
BigDecimal orign = itemEntity.getSkuPrice().multiply(new BigDecimal(itemEntity.getSkuQuantity().toString()));
BigDecimal subtract = orign.subtract(itemEntity.getCouponAmount())
.subtract(itemEntity.getPromotionAmount())
.subtract(itemEntity.getIntegrationAmount());
itemEntity.setRealAmount(subtract);
return itemEntity;
}
}
鎖庫存的服務:
@Override
public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {
List<SkuHasStockVo> hasStockVoList = skuIds.stream().map(id -> {
SkuHasStockVo vo = new SkuHasStockVo();
Long count = baseMapper.getSkuStock(id);
vo.setSkuId(id);
vo.setHasStock(count ==null ?false:count > 0);
return vo;
}).collect(Collectors.toList());
return hasStockVoList;
}
@Transactional
@Override
public Boolean orderLockStock(WareSkuLockVo vo) {
//1. 找到每個商品在哪個倉庫都有庫存
List<CartItem> locks = vo.getLocks();
List<SkuWareHasStock> collect = locks.stream().map(item -> {
SkuWareHasStock stock = new SkuWareHasStock();
Long skuId = item.getSkuId();
stock.setSkuId(skuId);
stock.setNum(item.getCount());
//查詢這個商品在哪裡有庫存
List<Long> wareIds = wareSkuDao.listWareIdHasSkuStock(skuId);
stock.setWareId(wareIds);
return stock;
}).collect(Collectors.toList());
//2. 鎖定庫存
for(SkuWareHasStock hasStock:collect){
Boolean skuStocked = false;
Long skuId = hasStock.getSkuId();
List<Long> wareIds = hasStock.getWareId();
if(wareIds == null || wareIds.size() ==0){
throw new RRException(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 RRException(skuId+"不存在");
}
}
return true;
}
@Data
class SkuWareHasStock{
private Long skuId;
private List<Long> wareId;
private Integer num;
}
注意演示的時候把feign的超時時間都改大點,不然老是報錯,就很煩
feign:
client:
config:
gulimall-ware:
readTimeout: 12000
connectTimeout: 12000
loggerLevel: FULL
gulimall-member:
readTimeout: 12000
connectTimeout: 12000
loggerLevel: FULL
gulimall-product:
readTimeout: 12000
connectTimeout: 12000
loggerLevel: FULL
gulimall-cart:
readTimeout: 12000
connectTimeout: 12000
loggerLevel: FULL
演示如下: