SpringBoot 大型线上商城项目实战总结
知识点和可以借鉴到自己项目的点:
分页逻辑的处理操作
这里没有使用封装好的分页处理的相关工具类,而是自己去写分页封装的逻辑代码,帮助我们去了解分页操作的底层逻辑。
一个是PageQueryUtil工具类,这个工具类是作为分页查询操作的一个"参数接受器",为什么这么说,因为它继承了map,它的构造器中的参数也是map类型,这样我们除了传分页查询需要的page(页码)
和limit(每页条数)
两个重要的参数之外,还可以传其他查询条件的参数,例如商城端用户查询我的订单时的controller处理操作:
PageQueryUtil工具类的构造器:
注意红色框住注释的地方,是整个分页处理操作的核心。
除了上面的PageQueryUtil工具类,另一个就是分页查询返回结果类PageResult,它不仅仅返回分页查询出来的结果集,既列表数据,它还封装各种分页的参数给前端使用。
商品分类的三级联动
就是一级商品分类下会显示所有二级商品分类,二级分类下会显示所有三级分类,这是在商城端的商品分类。
在商品分类表中,除了分类id决定唯一一个商品分类之外,分类级别和分类名称两个属性也能决定唯一一个商品分类。比如在添加商品分类时,如果存在分类等级和分类名称相同的商品分类则添加失败。
在商品分类表中,父分类id字段和分类级别是实现商品分级联动的关键。
例如我要查询一级分类下的所有二级分类,那么sql语句就是:
select * from tb_newbee_mall_goods_category where parent_id=0 and category_level=1;
查询一级分类下所有的二级分类,那么不就是上面查询结果获取主键分类id作为查询条件去查询所有二级分类。
SELECT
*
FROM
tb_newbee_mall_goods_category
WHERE
parent_id IN ( SELECT category_id FROM tb_newbee_mall_goods_category WHERE parent_id = 0 AND category_level = 1 )
AND category_level = 2;
同理获取二级分类下的所有三级分类也是一样。
SELECT
*
FROM
tb_newbee_mall_goods_category
WHERE
parent_id IN ( SELECT category_id FROM tb_newbee_mall_goods_category WHERE parent_id IN ( SELECT category_id FROM tb_newbee_mall_goods_category WHERE parent_id = 0 AND category_level = 1 ) AND category_level = 2 )
AND category_level = 3;
其实就是一个子查询操作,此项目中没有很难的sql查询语句,我们将复杂的sql操作都是通过mybatis框架提供的方法写在了业务层进行处理,mapper层只是简单的封装一些增删改查的操作,后续优化我觉得可以使用mybatisplus框架减去mapper层的一些简单重复的增删改查方法,并且还有条件构造器和分页构造器可以大大简化代码量。
public List<NewBeeMallIndexCategoryVO> getCategoriesForIndex() {
List<NewBeeMallIndexCategoryVO> newBeeMallIndexCategoryVOS = new ArrayList<>();
//获取一级分类的固定数量的数据
//sql语句:select * from tb_newbee_mall_goods_category where parent_id=0 and category_level=1;
List<GoodsCategory> firstLevelCategories = goodsCategoryMapper.selectByLevelAndParentIdsAndNumber(Collections.singletonList(0L), NewBeeMallCategoryLevelEnum.LEVEL_ONE.getLevel(), Constants.INDEX_CATEGORY_NUMBER);
if (!CollectionUtils.isEmpty(firstLevelCategories)) {
//获取所有一级分类的主键id
List<Long> firstLevelCategoryIds = firstLevelCategories.stream().map(GoodsCategory::getCategoryId).collect(Collectors.toList());
//获取二级分类的数据
//sql语句:
// select * from tb_newbee_mall_goods_category
//where parent_id in (select category_id from tb_newbee_mall_goods_category where parent_id=0 and category_level=1)
//and category_level=2;
List<GoodsCategory> secondLevelCategories = goodsCategoryMapper.selectByLevelAndParentIdsAndNumber(firstLevelCategoryIds, NewBeeMallCategoryLevelEnum.LEVEL_TWO.getLevel(), 0);
if (!CollectionUtils.isEmpty(secondLevelCategories)) {
List<Long> secondLevelCategoryIds = secondLevelCategories.stream().map(GoodsCategory::getCategoryId).collect(Collectors.toList());
//获取三级分类的数据
//sql语句:select * from tb_newbee_mall_goods_category where
// parent_id in (select category_id from tb_newbee_mall_goods_category
// where parent_id in (select category_id from tb_newbee_mall_goods_category where parent_id=0 and category_level=1) and category_level=2)
// and category_level=3;
List<GoodsCategory> thirdLevelCategories = goodsCategoryMapper.selectByLevelAndParentIdsAndNumber(secondLevelCategoryIds, NewBeeMallCategoryLevelEnum.LEVEL_THREE.getLevel(), 0);
if (!CollectionUtils.isEmpty(thirdLevelCategories)) {
//根据 parentId--->存放的是二级分类的主键id 将 thirdLevelCategories 分组
//groupingBy()对集合中一个或多个属性进行分组
Map<Long, List<GoodsCategory>> thirdLevelCategoryMap = thirdLevelCategories.stream().collect(groupingBy(GoodsCategory::getParentId));
List<SecondLevelCategoryVO> secondLevelCategoryVOS = new ArrayList<>();
//处理二级分类
for (GoodsCategory secondLevelCategory : secondLevelCategories) {
SecondLevelCategoryVO secondLevelCategoryVO = new SecondLevelCategoryVO();
BeanUtil.copyProperties(secondLevelCategory, secondLevelCategoryVO);
//如果该二级分类下有数据则放入 secondLevelCategoryVOS 对象中
if (thirdLevelCategoryMap.containsKey(secondLevelCategory.getCategoryId())) {
//根据二级分类的id取出thirdLevelCategoryMap分组中的三级分类list
List<GoodsCategory> tempGoodsCategories = thirdLevelCategoryMap.get(secondLevelCategory.getCategoryId());
//ThirdLevelCategoryVO和secondLevelCategoryVO类不同,请注意,secondLevelCategoryVO有list方法存取ThirdLevelCategoryVO对象,就下面这个方法
secondLevelCategoryVO.setThirdLevelCategoryVOS((BeanUtil.copyList(tempGoodsCategories, ThirdLevelCategoryVO.class)));
secondLevelCategoryVOS.add(secondLevelCategoryVO);
}
}
//到这里已经处理完了二级分类VO对象,所以可以直接操作二级分类VO对象即可。
//处理一级分类
if (!CollectionUtils.isEmpty(secondLevelCategoryVOS)) {
//根据 parentId--->存放的是一级分类的主键id 将 secondLevelCategories 分组
Map<Long, List<SecondLevelCategoryVO>> secondLevelCategoryVOMap = secondLevelCategoryVOS.stream().collect(groupingBy(SecondLevelCategoryVO::getParentId));
for (GoodsCategory firstCategory : firstLevelCategories) {
NewBeeMallIndexCategoryVO newBeeMallIndexCategoryVO = new NewBeeMallIndexCategoryVO();
BeanUtil.copyProperties(firstCategory, newBeeMallIndexCategoryVO);
//如果该一级分类下有数据则放入 newBeeMallIndexCategoryVOS 对象中
if (secondLevelCategoryVOMap.containsKey(firstCategory.getCategoryId())) {
//根据一级分类的id取出secondLevelCategoryVOMap分组中的二级级分类list
List<SecondLevelCategoryVO> tempGoodsCategories = secondLevelCategoryVOMap.get(firstCategory.getCategoryId());
newBeeMallIndexCategoryVO.setSecondLevelCategoryVOS(tempGoodsCategories);
newBeeMallIndexCategoryVOS.add(newBeeMallIndexCategoryVO);
}
}
}
}
}
return newBeeMallIndexCategoryVOS;
} else {
return null;
}
}
上面是所有商品分类回显到商城主页的业务逻辑代码,个人觉得还是有点复杂的。这里还使用stream流操作简化代码量了,还学到了stream流collect(groupingBy())对集合中一个或多个属性进行分组,返回结果是一个map类型,这样处理到三级分类时,根据三级分类中的父类id字段进行分组,这样三级分类就归好类,同理二级分类归类到自己所属的一级分类也是如此。(这里可能表达不清楚,建议这里可以进行代码调试就好理解一点,可以学习和借鉴如何去处理这些同一张表下的有关联的数据,是不是可以使用自查询操作?)
VO对象
一开始我不知道什么是Vo对象,然后百度了一下,各有各的说辞,但我觉得Vo对象就是一个为了不暴露过多信息,前端需要什么信息(字段),就将这些字段封装到vo对象响应给前端即可。这个项目分为管理端和用户端,管理端中是不需要用到vo对象的,这里的vo对象都是在用户端中使用的,因为管理端都是管理人员在使用,而用户端则需要。
图片上传和回显处理流程
疑问:在图片上传后图片的访问地址是如何保存到数据库,并且如何进行图片的回显操作?
img标签中的src属性为什么为:"http://localhost:28080/upload/20221210_11093518.jpg"
然后我就查看了前后端的代码弄明白了它的图片上传的流程和src中url路径的处理
-
首先前端利用js根据id绑定点击事件,当我们点击触发上传按钮时,会发送ajax请求到后端。
-
后端接受ajax请求,响应成功后将响应体中的内容进行赋值操作
3. 然后表单提交发送ajax请求到后端
后端的controller层代码:数据检验和调用service层。
/** * 添加 */ @RequestMapping(value = "/goods/save", method = RequestMethod.POST) @ResponseBody public Result save(@RequestBody NewBeeMallGoods newBeeMallGoods) { if (StringUtils.isEmpty(newBeeMallGoods.getGoodsName()) || StringUtils.isEmpty(newBeeMallGoods.getGoodsIntro()) || StringUtils.isEmpty(newBeeMallGoods.getTag()) || Objects.isNull(newBeeMallGoods.getOriginalPrice()) || Objects.isNull(newBeeMallGoods.getGoodsCategoryId()) || Objects.isNull(newBeeMallGoods.getSellingPrice()) || Objects.isNull(newBeeMallGoods.getStockNum()) || Objects.isNull(newBeeMallGoods.getGoodsSellStatus()) || StringUtils.isEmpty(newBeeMallGoods.getGoodsCoverImg()) || StringUtils.isEmpty(newBeeMallGoods.getGoodsDetailContent())) { return ResultGenerator.genFailResult("参数异常!"); } String result = newBeeMallGoodsService.saveNewBeeMallGoods(newBeeMallGoods); if (ServiceResultEnum.SUCCESS.getResult().equals(result)) { return ResultGenerator.genSuccessResult(); } else { return ResultGenerator.genFailResult(result); } }
这样就将数据保存到了数据库。
-
但是我们发现tb_newbee_mall_goods_info该表的goods_cover_img字段中存储照片的路径并不是真实的路径,那我为什么前端图片能正常显示呢?
是因为我们在
NeeBeeMallWebMvcConfigurer
类中添加了本地资源映射方法,他的作用是对设定的请求路径进行一个本地资源文件的映射,它会映射到文件存储真实的路径中去。就上面那个新添加到数据库中的例子,http://localhost:28080/upload/20221209_11093518.jpg符合/upload/**
,会被映射到真实的路径:D:\mall-images\upload\ (在我电脑下)public void addResourceHandlers(ResourceHandlerRegistry registry) { //资源映射绑定 registry.addResourceHandler("/upload/**").addResourceLocations("file:" + Constants.FILE_UPLOAD_DIC); }
-
-