front模块
分类 service
@DubboService
@Service
@Slf4j
public class FrontCategoryServiceImpl implements IFrontCategoryService {
// 项目中涉及Redis的信息读取,定义这个常量,降低拼写错误风险
public static final String CATEGORY_TREE_KEY="category_tree";
// 利用Dubbo获得可以连接数据库获得所有分类信息的业务逻辑层方法
@DubboReference
private IForFrontCategoryService dubboCategoryService;
@Autowired
private RedisTemplate redisTemplate;
@Override
public FrontCategoryTreeVO categoryTree() {
// 凡是有Redis读取检查的都是这个模式
if(redisTemplate.hasKey(CATEGORY_TREE_KEY)){
// 如果Redis中包含分类树信息,直接从Redis中获取返回即可
FrontCategoryTreeVO<FrontCategoryEntity> treeVO=
(FrontCategoryTreeVO<FrontCategoryEntity>)
redisTemplate.boundValueOps(CATEGORY_TREE_KEY).get();
return treeVO;
}
// 如果Redis中没有数据,证明本次运行需要从数据库查询所有分类,拼接从三级分类树并返回
// 首先一定是先要从数据库中查询所有分类对象
List<CategoryStandardVO> categoryStandardVOs=
dubboCategoryService.getCategoryList();
// 将所有分类对象关联成三级分类树返回
FrontCategoryTreeVO<FrontCategoryEntity> treeVO=initTree(categoryStandardVOs);
// 将确定好的三级分类树保存到Redis
redisTemplate.boundValueOps(CATEGORY_TREE_KEY)
.set(treeVO, 24 , TimeUnit.HOURS);
// 千万别忘了返回!!!
return treeVO;
}
private FrontCategoryTreeVO<FrontCategoryEntity> initTree(List<CategoryStandardVO> categoryStandardVOs) {
// 第一部分,确定所有分类对象的父分类
// 声明一个Map,这个map的Key是父分类的Id,这个Map的Value是当前父分类的所有子分类对象
Map<Long,List<FrontCategoryEntity>> map=new HashMap<>();
// 日志输出分类对象个数
log.info("当前分类对象总数:{}",categoryStandardVOs.size());
// 下面编写for循环,遍历categoryStandardVOs集合,将其中的所有元素保存到父分类id值对应的Map中
// 根分类parentId是0
for(CategoryStandardVO categoryStandardVO : categoryStandardVOs){
// 因为CategoryStandardVO类型中没有children属性保存子分类对象
// 所以我们要使用FrontCategoryEntity来保存同名属性
FrontCategoryEntity frontCategoryEntity=new FrontCategoryEntity();
// 利用BeanUtils将categoryStandardVO同名属性赋值给frontCategoryEntity
BeanUtils.copyProperties(categoryStandardVO,frontCategoryEntity);
// 提取当前分类对象的父级id(parentId)
Long parentId=frontCategoryEntity.getParentId();
// 判断当前的父级Id是否在Map中已经存在
if(!map.containsKey(parentId)){
// 如果parentId是第一次出现,就要向map中添加一个元素
List<FrontCategoryEntity> value=new ArrayList<>();
value.add(frontCategoryEntity);
map.put(parentId,value);
}else{
// 如果当前以parentId值作为key的map元素已经存在,
// 我们就向当前map元素的List集合中添加当前分类对象即可
map.get(parentId).add(frontCategoryEntity);
}
}
log.info("当前map中包含父级id的个数为:{}",map.size());
// 第二部分,将每个分类对象关联到正确父分类对象中
// 我们已经获得了每个父分类包含了内些子分类的数据
// 下面就可以从根分类开始,通过循环遍历将每个分类对象包含的子分类添加到children属性中
// 因为根分类id为0,所以先key为0的获取
List<FrontCategoryEntity> firstLevels=map.get(0L);
//判断根分类是否为null
if(firstLevels==null){
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"当前项目没有根分类");
}
// 首先遍历我们从Map中获取的所有根分类
for(FrontCategoryEntity oneLevel: firstLevels){
// 获得当前根分类对象的id
Long secondLevelParentId=oneLevel.getId();
// map中获取当前分类对象的所有子分类的集合
List<FrontCategoryEntity> secondLevels=map.get(secondLevelParentId);
// 判断二级分类是否为null
if(secondLevels==null){
log.warn("当前分类缺少二级分类内容:{}",secondLevelParentId);
// 为了防止空集合遍历发生异常,我们直接跳过本次循环,运行下次循环
continue;
}
// 遍历当前根分类的所有二级分类
for(FrontCategoryEntity twoLevel : secondLevels){
// 获得当前二级分类对象id,作为三级分类的父id保存
Long thirdLevelParentId = twoLevel.getId();
// 获得当前二级分类的所有子元素
List<FrontCategoryEntity> thirdLevels=map.get(thirdLevelParentId);
if(thirdLevels==null){
log.warn("当前分类缺少三级分类内容:{}",thirdLevelParentId);
continue;
}
// 将三级分类对象集合赋值给二级分类对象的children属性
twoLevel.setChildrens(thirdLevels);
}
// 将二级分类对象集合赋值给一级分类对象的children属性
oneLevel.setChildrens(secondLevels);
}
// 将转换完成的所有一级分类对象,按方法要求返回
FrontCategoryTreeVO<FrontCategoryEntity> treeVO=new FrontCategoryTreeVO<>();
// 向对象中属性赋值
treeVO.setCategories(firstLevels);
// 别忘了修改返回treeVO
return treeVO;
}
}
product模块
添加购物车
@Override
public void addCart(CartAddDTO cartDTO) {
// 获取当前登录用户的userId
Long userId=getUserId();
// 查询这个userId的用户是否已经将指定的sku添加到购物车
OmsCart omsCart=omsCartMapper.selectExistsCart(userId,cartDTO.getSkuId());
// 判断查询结果是否为null
if(omsCart!=null){
// 不等于null,表示当前用户这个sku已经添加在购物车列表中
// 我们需要做的就是修改它的数量,根据cartDTO对象的quantity属性值添加
omsCart.setQuantity(omsCart.getQuantity()+cartDTO.getQuantity());
// 调用持久层方法修改数量
omsCartMapper.updateQuantityById(omsCart);
}else{
// 如果omsCart是null 会运行else代码块
// 去完成购物车对象的新增,先实例化OmsCart对象
OmsCart newOmsCart=new OmsCart();
// 将参数cartDTO的同名属性赋值给newOmsCart
BeanUtils.copyProperties(cartDTO,newOmsCart);
// cartDTO对象中没有userId属性,需要单独赋值
newOmsCart.setUserId(userId);
// 执行新增
omsCartMapper.saveCart(newOmsCart);
}
}
添加订单
@GlobalTransactional
@Override
public OrderAddVO addOrder(OrderAddDTO orderAddDTO) {
// 在连接数据库操作前,一定要先把所有数据准备好
// 1.将订单实体的所有属性赋值OmsOrder
OmsOrder omsOrder=new OmsOrder();
// 将参数orderAddDTO所有同名属性赋值给omsOrder
BeanUtils.copyProperties(orderAddDTO,omsOrder);
// orderAddDTO中数据并不全面,我们需要更详细的信息才能新增订单
// 因为收集信息代码较多,单独编写一个方法实现
loadOrder(omsOrder);
// 2.遍历订单中包含的所有商品的集合,也保证所有属性被赋值OmsOrderItem
// 获得orderAddDTO对象中的所有商品sku集合,判断是否为空
List<OrderItemAddDTO> orderItemAddDTOs=orderAddDTO.getOrderItems();
if(orderItemAddDTOs==null || orderItemAddDTOs.isEmpty()){
// 如果为null或集合中没有元素,抛出异常,终止新增订单
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"订单中必须有商品");
}
// 我们的目标是将订单中的商品增到oms_order_item表中
// 我们持有的持久层方法参数是List<OmsOrderItem>
// 我们先在获得的是List<OrderItemAddDTO>,类型不一致,
// 我们需要讲OrderItemAddDTO转换成OmsOrderItem,保存到一个新的集合里
List<OmsOrderItem> omsOrderItems=new ArrayList<>();
// 遍历参数中包含的所有商品列表
for(OrderItemAddDTO addDTO: orderItemAddDTOs){
// 先是转换我们的OrderItemAddDTO为OmsOrderItem
OmsOrderItem orderItem=new OmsOrderItem();
// 同名属性赋值
BeanUtils.copyProperties(addDTO,orderItem);
// 和Order一样OrderItem也有属性要单独复制
loadOrderItem(orderItem);
// 根据上面方法获得的omsOrder的订单id,给当前订单项的订单id属性赋值
orderItem.setOrderId(omsOrder.getId());
// 到此为止,我们的订单和循环遍历的订单中的订单项都已经赋好值,下面就要开始进行数据库操作了!
// 将收集好信息的orderItem对象添加到omsOrderItems集合中
omsOrderItems.add(orderItem);
// 3.遍历中所有值被赋值后,修改集合中所有商品的库存,并从购物车中删除这些商品
// 减少库存数
// 获得skuId
Long skuId=orderItem.getSkuId();
// 获取减少的商品的数量
Integer quantity=orderItem.getQuantity();
// dubbo调用减少库存的方法
int rows=dubboSkuService.reduceStockNum(skuId,quantity);
// 如果rows值为0,表示这次修改没有修改任何行,一般因为库存不足导致的
if(rows==0){
log.warn("商品skuId:{},库存不足",skuId);
// 抛出异常,Seata组件可以另之前循环过程中已经新增到数据库的信息回滚
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"库存不足");
}
// 删除购物车中商品
// 删除购物车商品的方法需要OmsCart实体类
OmsCart omsCart=new OmsCart();
omsCart.setSkuId(skuId);
omsCart.setUserId(omsOrder.getUserId());
cartService.removeUserCarts(omsCart);
}
// 4.将订单信息新增到数据(包括OrderItem和Order)
// 新增OrderItem对象,利用mapper中批量新增的方法
orderItemMapper.insertOrderItems(omsOrderItems);
// 新增订单表
orderMapper.insertOrder(omsOrder);
// 最后要保证用户能够看到订单详情
// 我们不需要返回订单的所有信息,因为前端包含大部分订单信息
// 我们只需要返回后端生成的一些数据即可
// OrderAddVO完成这个功能
OrderAddVO addVO=new OrderAddVO();
addVO.setId(omsOrder.getId());
addVO.setSn(omsOrder.getSn());
addVO.setCreateTime(omsOrder.getGmtCreate());
addVO.setPayAmount(omsOrder.getAmountOfActualPay());
// 别忘了返回正确的对象
return addVO;
}
private void loadOrderItem(OmsOrderItem orderItem) {
if(orderItem.getId()==null){
Long id=IdGeneratorUtils.getDistributeId("order_item");
orderItem.setId(id);
}
// 必须包含skuid信息,才能确定商品信息
if(orderItem.getSkuId()==null){
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,
"订单中商品必须包含skuId");
}
}
// 新增订单业务中需要的收集Order信息的方法
private void loadOrder(OmsOrder omsOrder) {
// 针对OmsOrder对象为空的值进行收集或生成
// 判断id是否为空
if(omsOrder.getId()==null){
// Leaf获得分布式id
Long id= IdGeneratorUtils.getDistributeId("order");
omsOrder.setId(id);
}
// 判断userId是否为空
if(omsOrder.getUserId()==null){
// 从SpringSecurity容器中获得jwt解析而来的用户id
omsOrder.setUserId(getUserId());
}
// 判断sn
if(omsOrder.getSn()==null){
omsOrder.setSn(UUID.randomUUID().toString());
}
// 判断state
if (omsOrder.getState()==null){
// 如果订单状态为null默认是新生成的订单,状态为0:未支付
omsOrder.setState(0);
}
// 下面要保证订单的生成实际,订单数据的创建实际和最后修改时间一致
// 我们手动获取当前系统时间,统一给他们赋值
if(omsOrder.getGmtOrder()==null){
LocalDateTime now=LocalDateTime.now();
omsOrder.setGmtOrder(now);
omsOrder.setGmtCreate(now);
omsOrder.setGmtModified(now);
}
// 下面是系统计算金额,前端实际上有基本计算显示,但是前安全性相对差,后端还要计算
// 计算基本逻辑 原价+运费-优惠=最终价格
// 判断运费,默认为0
if(omsOrder.getAmountOfFreight()==null){
// 默认运费为0
omsOrder.setAmountOfFreight(new BigDecimal(0.0));
}
// 判断优惠,默认为0
if(omsOrder.getAmountOfDiscount()==null){
omsOrder.setAmountOfDiscount(new BigDecimal(0.0));
}
// 获取传递过来的原价信息,如果原价为空,抛出异常
if(omsOrder.getAmountOfOriginalPrice()==null){
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"没有提供订单原价");
}
// 计算实际支付金额
// 原价+运费-优惠=最终价格
BigDecimal originalPrice=omsOrder.getAmountOfOriginalPrice();
BigDecimal freight=omsOrder.getAmountOfFreight();
BigDecimal discount=omsOrder.getAmountOfDiscount();
BigDecimal actualPay=originalPrice.add(freight).subtract(discount);
// 赋值给实际支付属性
omsOrder.setAmountOfActualPay(actualPay);
}
查询当前登录用户在指定时间范围内(默认一个月内)所有订单
// 查询当前登录用户在指定时间范围内(默认一个月内)所有订单
// 订单包含订单信息和订单项信息两个方面(xml的sql语句是关联查询)
@Override
public JsonPage<OrderListVO> listOrdersBetweenTimes(OrderListTimeDTO orderListTimeDTO) {
// 因为默认为最近一个月内,如果没有起始和结束时间,需要我们自动添加
// 要检查起始时间和结束时间是否合理,我们单独编写方法校验上面业务
validaTimeAndLoadTimes(orderListTimeDTO);
// 时间验证通过,开始进程查询
// 获得当前用户id
Long userId=getUserId();
// 将userId赋值给参数
orderListTimeDTO.setUserId(userId);
// 设置分页条件
PageHelper.startPage(orderListTimeDTO.getPage(),orderListTimeDTO.getPageSize());
// 执行查询
List<OrderListVO> list=orderMapper.selectOrdersBetweenTimes(orderListTimeDTO);
// 别忘了返回
return JsonPage.restPage(new PageInfo<>(list));
}
private void validaTimeAndLoadTimes(OrderListTimeDTO orderListTimeDTO) {
// 取出起始和结束时间对象
LocalDateTime start=orderListTimeDTO.getStartTime();
LocalDateTime end=orderListTimeDTO.getEndTime();
// 如果start和end中有任何一个为null,默认查询一个月内
if(start==null || end==null){
// 起始时间是当前时间减一个月minusMonths就是减月份的意思,1就是一个月
start=LocalDateTime.now().minusMonths(1);
// 默认结束时间是当前时间
end=LocalDateTime.now();
// 赋值给orderListTimeDTO参数
orderListTimeDTO.setStartTime(start);
orderListTimeDTO.setEndTime(end);
}else{
// 如果是国际的时间判断,需要添加时区修正来判断时间
// 判断结束时间大于起始时间,否则发生异常
if(end.toInstant(ZoneOffset.of("+8")).toEpochMilli()<
start.toInstant(ZoneOffset.of("+8")).toEpochMilli()){
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,
"结束时间应该大于起始时间");
}
}
}