首页 > 其他分享 >SpringBoot 大型线上商城项目实战总结

SpringBoot 大型线上商城项目实战总结

时间:2023-06-06 11:56:21浏览次数:42  
标签:category goods SpringBoot 分类 mall 线上 tb id 商城

SpringBoot 大型线上商城项目实战总结

知识点和可以借鉴到自己项目的点:

分页逻辑的处理操作

​ 这里没有使用封装好的分页处理的相关工具类,而是自己去写分页封装的逻辑代码,帮助我们去了解分页操作的底层逻辑。

​ 一个是PageQueryUtil工具类,这个工具类是作为分页查询操作的一个"参数接受器",为什么这么说,因为它继承了map,它的构造器中的参数也是map类型,这样我们除了传分页查询需要的page(页码)limit(每页条数)两个重要的参数之外,还可以传其他查询条件的参数,例如商城端用户查询我的订单时的controller处理操作:

image-20221215111317548

PageQueryUtil工具类的构造器:

image-20221215110000941

​ 注意红色框住注释的地方,是整个分页处理操作的核心。

​ 除了上面的PageQueryUtil工具类,另一个就是分页查询返回结果类PageResult,它不仅仅返回分页查询出来的结果集,既列表数据,它还封装各种分页的参数给前端使用。

image-20221215110438424

商品分类的三级联动

​ 就是一级商品分类下会显示所有二级商品分类,二级分类下会显示所有三级分类,这是在商城端的商品分类。

image-20221215150018784

​ 在商品分类表中,除了分类id决定唯一一个商品分类之外,分类级别和分类名称两个属性也能决定唯一一个商品分类。比如在添加商品分类时,如果存在分类等级和分类名称相同的商品分类则添加失败。

​ 在商品分类表中,父分类id字段和分类级别是实现商品分级联动的关键。

image-20221215150530740

​ 例如我要查询一级分类下的所有二级分类,那么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对象都是在用户端中使用的,因为管理端都是管理人员在使用,而用户端则需要。

图片上传和回显处理流程

​ 疑问:在图片上传后图片的访问地址是如何保存到数据库,并且如何进行图片的回显操作?

image-20221210134043026

​ img标签中的src属性为什么为:"http://localhost:28080/upload/20221210_11093518.jpg"

​ 然后我就查看了前后端的代码弄明白了它的图片上传的流程和src中url路径的处理

  1. 首先前端利用js根据id绑定点击事件,当我们点击触发上传按钮时,会发送ajax请求到后端。

    image-20221210135205805

    1. 后端接受ajax请求,响应成功后将响应体中的内容进行赋值操作

      image-202212101357527103. 然后表单提交发送ajax请求到后端

      image-20221210143008934

      后端的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);
              }
          }
      

      这样就将数据保存到了数据库。

      image-20221210143428522

      1. 但是我们发现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);
        }
        

标签:category,goods,SpringBoot,分类,mall,线上,tb,id,商城
From: https://www.cnblogs.com/sunshineTv/p/17460139.html

相关文章

  • 记一次线上问题,Netty接收到的报文一次有数据一次没有数据
    最近线上遇到一个问题,客户端发送的tcp报文第一次连接成功后没有数据,第二次连接后正常带数据,第三次又没有数据...问题排查1:是否有负载均衡,其中有一台机器出现了异常,会出现一次成功一次失败的情况经过排查,本服务是没有负载均衡的,排除问题排查2:抓包分析 根据抓包数据,异常情况时......
  • springboot 整合websocket
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>@ConfigurationpublicclassWebsocketConfig{@BeanpublicSe......
  • 【Java】再谈Springboot 策略模式
     第一次使用策略模式是一年前的一个项目:https://www.cnblogs.com/mindzone/p/16046538.html当时还不知道Spring支持集合类型的自动装配在最近一个项目,我发现很多业务需要频繁的使用这种模式去聚合代码 一、牛刀小试这是最开始的定义策略的业务接口/***业务推送管......
  • SpringBoot 文件上传下载工具样例
    最近工作遇到这样的情景:一大堆linux内网服务器,上面部署了mysql,nacos,xxljob等中间件,当然也给了一个很干净的windows内网服务器,什么软件都没有安装。比较欣慰的是:可以通过浏览器访问nacos、xxljob的管理页面。不幸的是:没有安装mysql客户端和xshell等工具。我可以通过......
  • springboot +nginx 配置http2
    说明nginx端使用http2+https,如果不使用https,浏览器会默认走http1.1后台使用http2,不使用https,因为内部服务之间没必要每次校验证书nginx配置#userroot;worker_processesauto;error_logD://nginx-log/error.log;#error_log/dev/null;#pidlogs/ngin......
  • 【Log4j】ログのツールーSpringBoot
    ログのUtilクラス:packagecom.example.demoaop.common;importcom.sun.deploy.config.DefaultConfig;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjava.util.HashMap;importjava.util.Map;publicclassLogUtils{/***系统默认配置......
  • java线上问题定位
    获取pidps-ef|grepjava pid下的线程资源占用情况top-Hppid将资源占用高这几个pid转为16进制下载当前的java线程栈查询16进制pid线程情况 java线程栈 jstack-lpid>/1.txt 导出堆快照jmap-dump:live,format=b,file=./heap.hprofpid 查看资源占用最大前30......
  • springboot中使用cache和redis
    知识点:springboot中使用cache和redis (1)springboot中,整合了cache,我们只需要,在入口类上加 @EnableCaching 即可开启缓存 例如:在service层使用@Cacheable和CacheEvict //添加缓存@Cacheable(cacheNames="TestCACHE",key="#root.methodName+'_'+#id")publicMap<String,......
  • 使用powermock写springboot2.7业务类的测试用例
    1,引入powermock依赖<dependency><groupId>org.powermock</groupId><artifactId>powermock-core</artifactId><version>2.0.9</version><scope>test</......
  • 【SpringBoot】如何配置静态资源的地址与访问路径
    静态资源,例如HTML文件、JS文件,设计到的SpringBoot配置有两项,一是“spring.mvc.static-path-pattern”,一是“spring.resources.static-locations”,很多人都难以分辨它们之间的差异,所以经常出现的结果就是404错误,无法找到静态资源。1.spring.mvc.static-path-patternspring.mvc.sta......