048 下单锁库存
文章目录
- cubemall-order
- enume
- OrderStatusEnum.java
- feign
- AuthRemoteClient.java
- ProductRemoteClient.java
- StockRemoteClient.java
- dto
- OrderDTO.java
- SubmitOrderDTO.java
- service
- OrderServiceImpl.java
- OrderService.java
- vo
- OrderItemVo.java
- OrderResultVo.java
- SpuInfoVo.java
- StockSkuLockVo.java
- controller
- OrderWebController.java
- cubemall-product
- controller
- SpuInfoController.java
- service
- SpuInfoServiceImpl.java
- vo
- SpuInfoVo.java
- cubemall-stock
- dao
- StockSkuDao.java
- service
- StockSkuServiceImpl.java
- vo
- OrderItemVo.java
- SkuHasStockVo.java
- StockSkuLockVo.java
- controller
- StockSkuWebController.java
- mapper
- StockSkuDao.xml
1 页面传递参数
地址id:addrId
支付方式:payType
防重令牌:防止订单重复提交
package com.xd.cubemall.order.service.dto;
import lombok.Data;
import lombok.ToString;
@ToString
@Data
public class SubmitOrderDTO {
/*收货地址*/
private Long addrId;
/*支付方式*/
private Integer payType;
/*防重令牌*/
private String orderToken;
}
2 返回值是什么
订单对象
异常码
package com.xd.cubemall.order.web.vo;
import com.xd.cubemall.order.entity.OrderEntity;
import lombok.Data;
import lombok.ToString;
@ToString
@Data
public class OrderResultVo {
/*订单*/
private OrderEntity order;
/*订单异常状态*/
private Integer code;
}
3 订单提交构建订单数据
订单数据
订单明细
价格数据
package com.xd.cubemall.order.service.dto;
import com.xd.cubemall.order.entity.OrderEntity;
import com.xd.cubemall.order.entity.OrderItemEntity;
import lombok.Data;
import lombok.ToString;
import java.math.BigDecimal;
import java.util.List;
@ToString
@Data
public class OrderDTO {
private OrderEntity orderEntity;
private List<OrderItemEntity> orderItemList;
/*实际支付价格*/
private BigDecimal payPrice;
}
cubemall-order
enume
OrderStatusEnum.java
package com.xd.cubemall.order.enume;
public enum OrderStatusEnum {
ORDER_NEW(0,"待付款"),
ORDER_PAYED(1,"已付款"),
ORDER_SEND(2,"已发货"),
ORDER_REC(3,"已完成"),
ORDER_CANCEL(4,"已取消"),
ORDER_SRVING(5,"售后中"),
ORDER_SVRED(6,"售后完");
private Integer code;
private String msg;
OrderStatusEnum(Integer code,String msg){
this.code = code;
this.msg = msg;
}
public Integer getCode(){
return code;
}
public String getMsg(){
return msg;
}
}
feign
AuthRemoteClient.java
package com.xd.cubemall.order.feign;
import com.xd.cubemall.common.utils.R;
import com.xd.cubemall.order.web.vo.UserReceiveAddressVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@FeignClient("cubemall-auth")
public interface AuthRemoteClient {
/**
* 根据token查询用户信息
*/
@RequestMapping("/user/info/{token}")
public String userInfoByToken(@PathVariable("token") String token);
/*
查询用户地址信息:根据用户id
*/
@RequestMapping("/user/userreceiveaddress/address/{userId}")
public List<UserReceiveAddressVo> addressList(@PathVariable("userId") Long userId);
@RequestMapping("/user/userreceiveaddress/info/{addrId}")
R getAddressById(@PathVariable("addrId") Long addrId);
}
ProductRemoteClient.java
package com.xd.cubemall.order.feign;
import com.xd.cubemall.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient("cubemall-product")
public interface ProductRemoteClient {
/**
* 根据skuId查询商品spu信息
* @param skuId
* @return
*/
@RequestMapping("/product/spuinfo/spu/{spuId}")
public R getSpuInfoBySkuId(@PathVariable("spuId") Long skuId);
}
StockRemoteClient.java
package com.xd.cubemall.order.feign;
import com.xd.cubemall.common.utils.R;
import com.xd.cubemall.order.web.vo.StockSkuLockVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient("cubemall-stock")
public interface StockRemoteClient {
/**
* 根据skuid查询库存信息
* @param skuIds
* @return
*/
@RequestMapping("/stock/sku/hasStock")
public R getSkuStock(@RequestBody List<Long> skuIds);
/**
* 远程调用库存服务,锁定库存
* @param stockSkuLockVo
* @return
*/
@RequestMapping("/stock/sku/lock")
R lockOrderStock(@RequestBody StockSkuLockVo stockSkuLockVo);
}
dto
OrderDTO.java
package com.xd.cubemall.order.service.dto;
import com.xd.cubemall.order.entity.OrderEntity;
import com.xd.cubemall.order.entity.OrderItemEntity;
import lombok.Data;
import lombok.ToString;
import java.math.BigDecimal;
import java.util.List;
@ToString
@Data
public class OrderDTO {
private OrderEntity orderEntity;
private List<OrderItemEntity> orderItemList;
/*实际支付价格*/
private BigDecimal payPrice;
}
SubmitOrderDTO.java
package com.xd.cubemall.order.service.dto;
import lombok.Data;
import lombok.ToString;
@ToString
@Data
public class SubmitOrderDTO {
/*收货地址*/
private Long addrId;
/*支付方式*/
private Integer payType;
/*防重令牌*/
private String orderToken;
}
service
OrderServiceImpl.java
package com.xd.cubemall.order.service.impl;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.xd.cubemall.common.utils.R;
import com.xd.cubemall.order.entity.OrderItemEntity;
import com.xd.cubemall.order.entity.UserEntity;
import com.xd.cubemall.order.enume.OrderStatusEnum;
import com.xd.cubemall.order.feign.AuthRemoteClient;
import com.xd.cubemall.order.feign.CartRemoteClient;
import com.xd.cubemall.order.feign.ProductRemoteClient;
import com.xd.cubemall.order.feign.StockRemoteClient;
import com.xd.cubemall.order.interceptor.OrderInterceptor;
import com.xd.cubemall.order.service.OrderItemService;
import com.xd.cubemall.order.service.dto.OrderDTO;
import com.xd.cubemall.order.service.dto.SubmitOrderDTO;
import com.xd.cubemall.order.utils.Constants;
import com.xd.cubemall.order.web.vo.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.*;
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.xd.cubemall.common.utils.PageUtils;
import com.xd.cubemall.common.utils.Query;
import com.xd.cubemall.order.dao.OrderDao;
import com.xd.cubemall.order.entity.OrderEntity;
import com.xd.cubemall.order.service.OrderService;
import org.springframework.util.StringUtils;
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 {
//定义全局threadLocal
private ThreadLocal<SubmitOrderDTO> submitOrderDTOThreadLocal = new ThreadLocal<>();
// 注入线程池对象
@Autowired
private ThreadPoolExecutor poolExecutor;
// 注入远程调用地址接口
@Autowired
private AuthRemoteClient authRemoteClient;
// 注入购物车远程调用接口
@Autowired
private CartRemoteClient cartRemoteClient;
// 注入库存服务
@Autowired
private StockRemoteClient stockRemoteClient;
// 注入redis模板服务
@Autowired
private RedisTemplate<String,String> redisTemplate;
// 注入商品服务
@Autowired
private ProductRemoteClient productRemoteClient;
// 注入订单明细服务对象
@Autowired
private OrderItemService orderItemService;
@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);
}
/*查询订单结算页相关数据*/
// 1.地址收货信息
// 2.购物清单
// 3.支付方式(x)
@Override
public OrderConfirmVo confirmOrder() {
// 创建订单结算页视图对象
OrderConfirmVo confirmVo = new OrderConfirmVo();
//获取请求上下文信息
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 1.查询用户地址信息
// 获取当前用户信息
UserEntity userInfo = OrderInterceptor.dtoThreadLocal.get();
// 根据用户id查询用户信息
CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
// 异步请求之前,共享请求数据
RequestContextHolder.setRequestAttributes(requestAttributes);
// 远程调用用户授权认证,用户中心服务,查询用户数据
List<UserReceiveAddressVo> userReceiveAddressVos = authRemoteClient.addressList(userInfo.getId());
// 把用户地址信息添加到订单结算实体对象
confirmVo.setUserReceiveAddressList(userReceiveAddressVos);
}, poolExecutor);
// 2.查询购物车清单数据
CompletableFuture<Void> cartItemsFuture = CompletableFuture.runAsync(() -> {
// 异步请求之前,共享请求数据
RequestContextHolder.setRequestAttributes(requestAttributes);
// 远程调用购物车服务方法,查询购物车清单
List<CartItemVo> cartItems = cartRemoteClient.getCartItems();
confirmVo.setItems(cartItems);
}, poolExecutor).thenRunAsync(()->{
// 获取购物车清单数据
List<CartItemVo> items = confirmVo.getItems();
// 获取所有的skuIds
List<Long> skuIds = items.stream().map(itemVo -> itemVo.getSkuId()).collect(Collectors.toList());
// 根据商品skuID查询每一个商品的库存信息
// 调用库存服务
R skuStock = stockRemoteClient.getSkuStock(skuIds);
// 获取是否具有库存数据
List<StockSkuVo> skuStockDataList = skuStock.getData("data", new TypeReference<List<StockSkuVo>>() {
});
// 把是否具有库存的数据转换为map结构数据
if(skuStockDataList!=null && skuStockDataList.size()>0){
Map<Long, Boolean> hasStock = skuStockDataList.stream().collect(Collectors.toMap(StockSkuVo::getSkuId, StockSkuVo::getHasStock));
// 添加对象
confirmVo.setStocks(hasStock);
}
},poolExecutor);
// 3.获取用户优惠券信息
Integer integration = userInfo.getIntegration();
confirmVo.setIntegration(integration);
//4.获取商品库存信息,判断商品是否具有库存
// 防重令牌
// 由于网络延迟(重试),订单提交按钮可能多次提交
// 防重令牌,提交订单时携带此令牌
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(Constants.ORDER_TOKEN_PREFIX+userInfo.getId(),token,30, TimeUnit.MINUTES);
// 放入令牌对象属性
confirmVo.setOrderToken(token);
//同步调用
try {
CompletableFuture.allOf(addressFuture,cartItemsFuture).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return confirmVo;
}
/**
* 提交订单的方法
* @param submitOrderDTO
* @return
*/
@Override
public OrderResultVo submitOrder(SubmitOrderDTO submitOrderDTO) {
//放入threadLocal对象
submitOrderDTOThreadLocal.set(submitOrderDTO);
// 构建一个返回值对象
OrderResultVo resultVo = new OrderResultVo();
// 获取登录用户信息
UserEntity userInfo = OrderInterceptor.dtoThreadLocal.get();
// 根据参数令牌,验证令牌信息,防止订单重复提交
// 获取,验证令牌整个操作必须是一个原子性的操作
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return 1 else return 0 end";
// 获取传递的令牌
String orderToken = submitOrderDTO.getOrderToken();
// 执行lua脚本,返回执行结果:1,0
Long res = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
Arrays.asList(Constants.ORDER_TOKEN_PREFIX + userInfo.getId()),
orderToken);
// 判断验证令牌是否成功
if(res == 0L){
// 令牌验证失败
// 1表示验证令牌失败
resultVo.setCode(1);
return resultVo;
}
// 抽取一个方法,构造订单数据
OrderDTO orderDTO = this.createOrderDTO();
// 保存订单数据
this.saveOrder(orderDTO);
// 库存
// 定义锁定库存对象
StockSkuLockVo stockSkuLockVo = new StockSkuLockVo();
// 设置属性值
stockSkuLockVo.setOrderId(orderDTO.getOrderEntity().getOrderSn());
// 设定锁定商品数据信息
List<OrderItemVo> itemVos = orderDTO.getOrderItemList().stream().map(item -> {
OrderItemVo itemVo = new OrderItemVo();
itemVo.setSkuId(item.getSkuId());
itemVo.setCount(item.getSkuQuantity());
itemVo.setTitle(item.getSkuName());
return itemVo;
}).collect(Collectors.toList());
// 把需要锁定库存商品信息放入锁定对象
stockSkuLockVo.setLockList(itemVos);
// 开始锁定库存
R r = stockRemoteClient.lockOrderStock(stockSkuLockVo);
// 判断库存是否锁定成功
if(r.getCode() == 0){
// 返回值对象
resultVo.setOrder(orderDTO.getOrderEntity());
resultVo.setCode(0);
// 清除购物车数据
redisTemplate.delete(Constants.CART_PREFIX+userInfo.getId());
return resultVo;
}else{
// 库存锁定失败
resultVo.setCode(2);
return resultVo;
}
}
/**
* 保存订单数据
* @param orderDTO
*/
private void saveOrder(OrderDTO orderDTO) {
// 获取订单对象
OrderEntity orderEntity = orderDTO.getOrderEntity();
orderEntity.setCreateTime(new Date());
orderEntity.setModifyTime(new Date());
// 保存订单
this.baseMapper.insert(orderEntity);
// 保存订单明细
List<OrderItemEntity> orderItemList = orderDTO.getOrderItemList();
// 保存订单明细的数据
orderItemService.saveBatch(orderItemList);
}
/**
* 构造订单数据
* @return
*/
private OrderDTO createOrderDTO() {
// 验证令牌成功,可以开始下单,开始构建订单数据
// 创建一个订单数据
OrderDTO orderDTO = new OrderDTO();
// 构造订单数据
// 1.生成一个订单号
String orderId = IdWorker.getTimeId();
// 构造订单数据
OrderEntity orderEntity = this.builderOrder(orderId);
// 把订单数据添加到对象
orderDTO.setOrderEntity(orderEntity);
// 构造订单明细数据
List<OrderItemEntity> orderItemEntityList = this.builderOrderItems(orderId);
orderDTO.setOrderItemList(orderItemEntityList);
// 计算整个订单总价格
this.computePrice(orderEntity,orderItemEntityList);
return orderDTO;
}
/**
* 计算订单商品总价格
* @param orderEntity
* @param orderItemEntityList
*/
private void computePrice(OrderEntity orderEntity, List<OrderItemEntity> orderItemEntityList) {
// 订单变量,赋值价格
BigDecimal totalPrice = new BigDecimal("0.0");
// 优惠价
BigDecimal coupon = new BigDecimal("0.0");
BigDecimal intergration = new BigDecimal("0.0");
BigDecimal promotion = new BigDecimal("0.0");
BigDecimal freight = new BigDecimal("0.0");
// 循环订单明细
for (OrderItemEntity orderItemEntity : orderItemEntityList) {
coupon = coupon.add(orderItemEntity.getCouponAmount());
promotion = promotion.add(orderItemEntity.getPromotionAmount());
intergration = intergration.add(orderItemEntity.getIntegrationAmount());
// 总价
totalPrice = totalPrice.add(orderItemEntity.getRealAmount());
}
// 订单价格相关数据
orderEntity.setTotalAmount(totalPrice);
// 设置应付总金额
orderEntity.setPayAmount(totalPrice.add(freight));
orderEntity.setCouponAmount(coupon);
orderEntity.setPromotionAmount(promotion);
orderEntity.setIntegrationAmount(intergration);
// 删除
orderEntity.setDeleteStatus(0);
}
/**
* 构造订单明细
* @param orderId
* @return
*/
private List<OrderItemEntity> builderOrderItems(String orderId) {
// 获取登录用户信息
UserEntity userInfo = OrderInterceptor.dtoThreadLocal.get();
// 构造订单明细数据
List<OrderItemEntity> orderItemEntityList = new ArrayList<>();
// 从购物车中查询订单明细数据
List<CartItemVo> cartItems = cartRemoteClient.getCartItems();
if(cartItems!=null && cartItems.size()>0){
orderItemEntityList = cartItems.stream().map(item->{
// 获取购物车明细数据,变成订单明细
// 创建对象
OrderItemEntity orderItemEntity = new OrderItemEntity();
// 订单号
orderItemEntity.setOrderSn(orderId);
// sku信息
orderItemEntity.setSkuId(item.getSkuId());
orderItemEntity.setSkuName(item.getTitle());
orderItemEntity.setSkuPic(item.getImage());
orderItemEntity.setSkuPrice(item.getPrice());
orderItemEntity.setSkuQuantity(item.getCount());
orderItemEntity.setSkuAttrsVals(StringUtils.collectionToDelimitedString(item.getSkuAttr(),";"));
// 获取spu数据,根据skuId查询spu数据
R r1 = productRemoteClient.getSpuInfoBySkuId(item.getSkuId());
// 获取spu数据
SpuInfoVo spuInfoVo = r1.getData("data", new TypeReference<SpuInfoVo>() {
});
// 构造spu相关订单明细数据
orderItemEntity.setSpuId(spuInfoVo.getId());
orderItemEntity.setSpuName(spuInfoVo.getSpuName());
orderItemEntity.setCategoryId(spuInfoVo.getCategoryId());
orderItemEntity.setSpuBrand(spuInfoVo.getBrandName());
// 用户名
orderItemEntity.setMemberUsername(userInfo.getUsername());
// 运费
orderItemEntity.setFreightAmount(BigDecimal.ZERO);
// 订单项优惠金额
orderItemEntity.setPromotionAmount(BigDecimal.ZERO);
orderItemEntity.setCouponAmount(BigDecimal.ZERO);
orderItemEntity.setIntegrationAmount(BigDecimal.ZERO);
// 原价
BigDecimal originPrice = orderItemEntity.getSkuPrice().multiply(new BigDecimal(orderItemEntity.getSkuQuantity().toString()));
// 实际金额:原价-订单优惠价格
BigDecimal subtract = originPrice.subtract(orderItemEntity.getCouponAmount())
.subtract(orderItemEntity.getIntegrationAmount())
.subtract(orderItemEntity.getPromotionAmount());
// 设置真实价格
orderItemEntity.setRealAmount(subtract);
return orderItemEntity;
}).collect(Collectors.toList());
}
return orderItemEntityList;
}
/**
* 构造订单数据
* @param orderId
* @return
*/
private OrderEntity builderOrder(String orderId) {
// 获取登录用户信息
UserEntity userInfo = OrderInterceptor.dtoThreadLocal.get();
// 2.创建一个订单实体对象,添加数据
OrderEntity orderEntity = new OrderEntity();
orderEntity.setMemberId(userInfo.getId());
orderEntity.setOrderSn(orderId);
orderEntity.setMemberUsername(userInfo.getUsername());
// 获取对应的dto对象
SubmitOrderDTO submitOrderDTO = submitOrderDTOThreadLocal.get();
// 获取订单地址信息
R r = authRemoteClient.getAddressById(submitOrderDTO.getAddrId());
UserReceiveAddressVo userReceiveAddressVo = r.getData("userReceiveAddress", new TypeReference<UserReceiveAddressVo>() {
});
// 构造订单地址数据信息
orderEntity.setReceiverName(userReceiveAddressVo.getName());
orderEntity.setReceiverPhone(userReceiveAddressVo.getPhone());
orderEntity.setReceiverPostCode(userReceiveAddressVo.getPostCode());
orderEntity.setReceiverProvince(userReceiveAddressVo.getProvince());
orderEntity.setReceiverCity(userReceiveAddressVo.getCity());
orderEntity.setReceiverRegion(userReceiveAddressVo.getRegion());
orderEntity.setReceiverDetailAddress(userReceiveAddressVo.getDetailAddress());
// 构造订单状态数据
orderEntity.setStatus(OrderStatusEnum.ORDER_NEW.getCode());
orderEntity.setAutoConfirmDay(7);
orderEntity.setConfirmStatus(0);
return orderEntity;
}
}
OrderService.java
package com.xd.cubemall.order.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xd.cubemall.common.utils.PageUtils;
import com.xd.cubemall.order.entity.OrderEntity;
import com.xd.cubemall.order.service.dto.SubmitOrderDTO;
import com.xd.cubemall.order.web.vo.OrderConfirmVo;
import com.xd.cubemall.order.web.vo.OrderResultVo;
import java.util.Map;
/**
* 订单
*
* @author xuedong
* @email email@gmail.com
* @date 2024-11-23 16:42:02
*/
public interface OrderService extends IService<OrderEntity> {
PageUtils queryPage(Map<String, Object> params);
/*查询订单结算页相关数据*/
// 1.地址收货信息
// 2.购物清单
// 3.支付方式(x)
OrderConfirmVo confirmOrder();
/**
* 提交订单的方法
* @param submitOrderDTO
* @return
*/
OrderResultVo submitOrder(SubmitOrderDTO submitOrderDTO);
}
vo
OrderItemVo.java
package com.xd.cubemall.order.web.vo;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
@ToString
@Data
public class OrderItemVo {
// sku商品id
private Long skuId;
//商品标题
private String title;
//商品图片
private String image;
//商品属性
private List<String> skuAttr;
//商品价格
private BigDecimal price;
//商品数量
private Integer count;
//总价格
private BigDecimal totalPrice;
// 是否选中
private boolean check = true;
//计算单价*数量的商品总价
// public BigDecimal getTotalPrice(){
// return this.price.multiply(new BigDecimal(count+""));
// }
}
OrderResultVo.java
package com.xd.cubemall.order.web.vo;
import com.xd.cubemall.order.entity.OrderEntity;
import lombok.Data;
import lombok.ToString;
@ToString
@Data
public class OrderResultVo {
/*订单*/
private OrderEntity order;
/*订单异常状态*/
private Integer code;
}
SpuInfoVo.java
package com.xd.cubemall.order.web.vo;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* spu信息
*
* @author xuedong
* @email email@gmail.com
* @date 2024-08-13 01:36:04
*/
@Data
public class SpuInfoVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 自增ID
*/
private Long id;
/**
* spu名称
*/
private String spuName;
/**
* spu描述
*/
private String spuDescription;
/**
* 分类ID
*/
private Long categoryId;
/**
* 品牌名称
*/
private String brandName;
/**
* 权重
*/
private BigDecimal weight;
/**
* 发布状态
*/
private Integer publishStatus;
/**
*
*/
private Date createTime;
/**
*
*/
private Date updateTime;
}
StockSkuLockVo.java
package com.xd.cubemall.order.web.vo;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
import java.util.List;
@ToString
@Data
public class StockSkuLockVo {
private String orderId;
/*锁定商品库存信息*/
private List<OrderItemVo> lockList;
}
controller
OrderWebController.java
package com.xd.cubemall.order.web;
import com.xd.cubemall.order.service.OrderService;
import com.xd.cubemall.order.service.dto.SubmitOrderDTO;
import com.xd.cubemall.order.web.vo.OrderConfirmVo;
import com.xd.cubemall.order.web.vo.OrderResultVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@Slf4j
public class OrderWebController {
// 注入订单服务对象
@Autowired
private OrderService orderService;
/**
* 去到订单结算页
*/
@RequestMapping("/order/confirm")
public String confirmOrder(Model model){
// 查询结算页相关数据
OrderConfirmVo confirmVo = orderService.confirmOrder();
model.addAttribute("confirmVo",confirmVo);
// 1.地址收货信息
// 2.购物清单
// 3.支付方式
return "order";
}
/**
* 下单功能实现
*/
@RequestMapping("/order/createOrder")
public String createOrder(SubmitOrderDTO orderDTO, Model model, RedirectAttributes redirectAttributes){
// 调用服务层方法,实现下单操作
OrderResultVo resultVo = orderService.submitOrder(orderDTO);
// 判断下单是否成功
if(resultVo.getCode() == 0){
// 下单成功
model.addAttribute("submitOrder",resultVo);
return "pay";
}
String msg = "下单失败";
// 下单失败
switch (resultVo.getCode()){
case 1: msg += "令牌信息过期";break;
case 2: msg += "订单数据发生变化,请确认后再提交";break;
case 3: msg += "库存锁定失败";break;
}
redirectAttributes.addFlashAttribute("msg",msg);
return "redirect:http://localhost:8084/order/confirm";
}
}
cubemall-product
controller
SpuInfoController.java
/**
* 根据skuId查询商品spu信息
* @param skuId
* @return
*/
@RequestMapping("/spu/{spuId}")
public R getSpuInfoBySkuId(@PathVariable("spuId") Long skuId){
R r = spuInfoService.getSpuInfoBySkuId(skuId);
return r;
}
service
SpuInfoServiceImpl.java
/**
* 根据skuId查询商品spu信息
* @param skuId
* @return
*/
@Override
public R getSpuInfoBySkuId(Long skuId) {
// 根据skuID查询sku实体
SkuInfoEntity skuInfoEntity = skuInfoService.getById(skuId);
// 获取spuID
Long spuId = skuInfoEntity.getSpuId();
// 根据spuID查询spu数据信息
SpuInfoEntity spuInfoEntity = this.baseMapper.selectById(spuId);
// 查询品牌名称
BrandEntity brandEntity = brandService.getById(spuInfoEntity.getBrandId());
// 创建对象
SpuInfoVo spuInfoVo = new SpuInfoVo();
spuInfoVo.setBrandName(brandEntity.getName());
spuInfoVo.setCategoryId(spuInfoEntity.getCategoryId());
spuInfoVo.setId(spuInfoEntity.getId());
spuInfoVo.setCreateTime(spuInfoEntity.getCreateTime());
spuInfoVo.setPublishStatus(spuInfoEntity.getPublishStatus());
spuInfoVo.setSpuDescription(spuInfoEntity.getSpuDescription());
spuInfoVo.setSpuName(spuInfoEntity.getSpuName());
spuInfoVo.setUpdateTime(spuInfoEntity.getUpdateTime());
spuInfoVo.setWeight(spuInfoEntity.getWeight());
return R.ok().setData(spuInfoVo);
}
vo
SpuInfoVo.java
package com.xd.cubemall.product.vo;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* spu信息
*
* @author xuedong
* @email email@gmail.com
* @date 2024-08-13 01:36:04
*/
@Data
public class SpuInfoVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 自增ID
*/
private Long id;
/**
* spu名称
*/
private String spuName;
/**
* spu描述
*/
private String spuDescription;
/**
* 分类ID
*/
private Long categoryId;
/**
* 品牌名称
*/
private String brandName;
/**
* 权重
*/
private BigDecimal weight;
/**
* 发布状态
*/
private Integer publishStatus;
/**
*
*/
private Date createTime;
/**
*
*/
private Date updateTime;
}
cubemall-stock
dao
StockSkuDao.java
package com.xd.cubemall.stock.dao;
import com.xd.cubemall.stock.entity.StockSkuEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 商品库存
*
* @author xuedong
* @email email@gmail.com
* @date 2024-11-25 00:49:57
*/
@Mapper
public interface StockSkuDao extends BaseMapper<StockSkuEntity> {
List<Long> selectHasStockListStockIds(@Param("skuId") Long skuId);
Long lockSkuStock(@Param("skuId") Long skuId, @Param("stockId") Long stockId, @Param("num") Integer num);
}
service
StockSkuServiceImpl.java
package com.xd.cubemall.stock.service.impl;
import com.xd.cubemall.common.utils.R;
import com.xd.cubemall.stock.entity.StockOrderTaskDetailEntity;
import com.xd.cubemall.stock.entity.StockOrderTaskEntity;
import com.xd.cubemall.stock.service.StockOrderTaskDetailService;
import com.xd.cubemall.stock.service.StockOrderTaskService;
import com.xd.cubemall.stock.web.vo.OrderItemVo;
import com.xd.cubemall.stock.web.vo.SkuHasStockVo;
import com.xd.cubemall.stock.web.vo.StockSkuLockVo;
import com.xd.cubemall.stock.web.vo.StockSkuVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
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.xd.cubemall.common.utils.PageUtils;
import com.xd.cubemall.common.utils.Query;
import com.xd.cubemall.stock.dao.StockSkuDao;
import com.xd.cubemall.stock.entity.StockSkuEntity;
import com.xd.cubemall.stock.service.StockSkuService;
import org.springframework.util.StringUtils;
@Service("stockSkuService")
public class StockSkuServiceImpl extends ServiceImpl<StockSkuDao, StockSkuEntity> implements StockSkuService {
@Autowired
private StockOrderTaskService orderTaskService;
// 注入dao
@Autowired
private StockSkuDao stockSkuDao;
@Autowired
private StockOrderTaskDetailService orderTaskDetailService;
@Override
public PageUtils queryPage(Map<String, Object> params) {
IPage<StockSkuEntity> page = this.page(
new Query<StockSkuEntity>().getPage(params),
new QueryWrapper<StockSkuEntity>()
);
return new PageUtils(page);
}
/**
* 根据skuID查询数据信息
* @param skuIds
* @return
*/
@Override
public R getSkuStock(List<Long> skuIds) {
//List<StockSkuEntity> stockSkuList = this.baseMapper.selectBatchIds(skuIds);
//1个skuid只有1个对应的库存数据
// //循环skuids,根据skuId查询
// for (Long skuId : skuIds) {
// StockSkuEntity stockSkuEntity = this.baseMapper.selectOne(new QueryWrapper<StockSkuEntity>().eq("sku_id", skuId));
// stockSkuList.add(stockSkuEntity);
// }
// 创建一个集合,封装对象
// stream
List<StockSkuVo> stockSkuList = skuIds.stream().map(skuId -> {
// 根据skuID查询库存信息
StockSkuEntity stockSkuEntity = this.baseMapper.selectOne(new QueryWrapper<StockSkuEntity>().eq("sku_id", skuId));
// 获取库存数量
Integer stock = stockSkuEntity.getStock();
// 创建封装数据对象
StockSkuVo stockSkuVo = new StockSkuVo();
stockSkuVo.setSkuId(skuId);
// 是否有库存
stockSkuVo.setHasStock(stock == null?false:stock>0);
return stockSkuVo;
}).collect(Collectors.toList());
return R.ok().setData(stockSkuList);
}
/**
* 锁定库存
* @param stockSkuLockVo
* @return
*/
@Override
public boolean lockOrderStock(StockSkuLockVo stockSkuLockVo) {
// 定义字段,保存锁定状态
boolean skuStockLocked = false;
// 保存库存订单工作单信息
StockOrderTaskEntity orderTaskEntity = new StockOrderTaskEntity();
orderTaskEntity.setOrderSn(stockSkuLockVo.getOrderId());
orderTaskEntity.setCreateTime(new Date());
// 保存
orderTaskService.save(orderTaskEntity);
// 按照收货地址,找到一个最近的仓库,锁定库存
// 找到每一个商品属于哪个库存,且库存是否充足,如果条件满足,就锁定库存
// 获取订单商品明细
List<OrderItemVo> lockList = stockSkuLockVo.getLockList();
List<SkuHasStockVo> hasStockVoList = lockList.stream().map(item->{
// 创建对象
SkuHasStockVo hasStockVo = new SkuHasStockVo();
hasStockVo.setSkuId(item.getSkuId());
hasStockVo.setNum(item.getCount());
//查询这个商品在哪个仓库有库存
//总库存减去购买商品数量,如果大于0,那么表示可以锁定库存
List<Long> stockIds = stockSkuDao.selectHasStockListStockIds(item.getSkuId());
hasStockVo.setStockId(stockIds);
return hasStockVo;
}).collect(Collectors.toList());
// 锁定库存
for (SkuHasStockVo hasStockVo : hasStockVoList) {
// 获取仓库数据
List<Long> stockIds = hasStockVo.getStockId();
// 判断仓库是否存在
if(!StringUtils.isEmpty(stockIds)){
// 锁定每一个商品的库存
for (Long stockId : stockIds) {
// 开始锁定,锁定成功返回1,否则返回0
Long count = stockSkuDao.lockSkuStock(hasStockVo.getSkuId(),stockId,hasStockVo.getNum());
// 判断锁定库存是否成功
if(count == 1){
skuStockLocked = true;
// 锁定库存成功,保存工作单详细信息
StockOrderTaskDetailEntity orderTaskDetailEntity = new StockOrderTaskDetailEntity();
orderTaskDetailEntity.setSkuId(hasStockVo.getSkuId());
orderTaskDetailEntity.setSkuName("");
orderTaskDetailEntity.setSkuNum(hasStockVo.getNum());
orderTaskDetailEntity.setTaskId(orderTaskEntity.getId());
// 保存实体对象
orderTaskDetailService.save(orderTaskDetailEntity);
}
}
}
}
return skuStockLocked;
}
}
vo
OrderItemVo.java
package com.xd.cubemall.stock.web.vo;
import lombok.Data;
import lombok.ToString;
import java.math.BigDecimal;
import java.util.List;
@ToString
@Data
public class OrderItemVo {
// sku商品id
private Long skuId;
//商品标题
private String title;
//商品图片
private String image;
//商品属性
private List<String> skuAttr;
//商品价格
private BigDecimal price;
//商品数量
private Integer count;
//总价格
private BigDecimal totalPrice;
// 是否选中
private boolean check = true;
//计算单价*数量的商品总价
public BigDecimal getTotalPrice(){
return this.price.multiply(new BigDecimal(count+""));
}
}
SkuHasStockVo.java
package com.xd.cubemall.stock.web.vo;
import lombok.Data;
import lombok.ToString;
import java.util.List;
@ToString
@Data
public class SkuHasStockVo {
private Long skuId;
private Integer num;
private List<Long> stockId;
}
StockSkuLockVo.java
package com.xd.cubemall.stock.web.vo;
import lombok.Data;
import lombok.ToString;
import java.util.List;
@ToString
@Data
public class StockSkuLockVo {
private String orderId;
/*锁定商品库存信息*/
private List<OrderItemVo> lockList;
}
controller
StockSkuWebController.java
package com.xd.cubemall.stock.web;
import com.xd.cubemall.common.utils.R;
import com.xd.cubemall.stock.service.StockSkuService;
import com.xd.cubemall.stock.web.vo.StockSkuLockVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class StockSkuWebController {
// 注入service服务对象
@Autowired
private StockSkuService stockSkuService;
/**
* 根据skuID查询数据信息
* @param skuIds
* @return
*/
@RequestMapping("/stock/sku/hasStock")
public R getSkuStock(@RequestBody List<Long> skuIds){
R r = stockSkuService.getSkuStock(skuIds);
return r;
}
/**
* 锁定库存
* @param stockSkuLockVo
* @return
*/
@RequestMapping("/stock/sku/lock")
public R lockOrderStock(@RequestBody StockSkuLockVo stockSkuLockVo){
boolean isLock = stockSkuService.lockOrderStock(stockSkuLockVo);
if(isLock){
return R.ok().put("code", 0);
}else{
return R.ok().put("code", 99);
}
}
}
mapper
StockSkuDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xd.cubemall.stock.dao.StockSkuDao">
<!-- 可根据自己的需求,是否要使用 -->
<resultMap type="com.xd.cubemall.stock.entity.StockSkuEntity" id="stockSkuMap">
<result property="id" column="id"/>
<result property="skuId" column="sku_id"/>
<result property="wareId" column="ware_id"/>
<result property="stock" column="stock"/>
<result property="skuName" column="sku_name"/>
<result property="stockLocked" column="stock_locked"/>
</resultMap>
<!--根据skuId查询仓库信息-->
<select id="selectHasStockListStockIds" resultType="java.lang.Long">
SELECT
ware_id
FROM
tb_stock_sku
WHERE
sku_id = #{skuId}
AND stock - stock_locked > 0
</select>
<update id="lockSkuStock">
UPDATE tb_stock_sku
SET stock_locked = stock_locked + #{num}
WHERE
sku_id = #{skuId}
AND ware_id = #{stockId}
AND stock - stock_locked > 0
</update>
</mapper>