谷粒商城基础篇------商品服务 - 三级分类(gulimall-product:pms_category表)


**数据库:**首先gulimall_pms数据库中的pms_category表要复制粘贴内容
在这里插入图片描述
在这里插入图片描述

4.1 递归树形结构获取商品分类(pms_category)数据

①在注册中心中“product”命名空间中,创建“gulimall-product.yml”配置文件:
在这里插入图片描述
在这里插入图片描述
②将idea的application.yml内容复制到nacos配置中心去

gulimall-product的application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.146.129:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: gulimall-product
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0

server:
  port: 10000

③在本地创建“bootstrap.properties”文件,指明配置中心的位置和使用到的配置文件:

gulimall-product的bootstrap.properties

spring.application.name=gulimall-product
spring.cloud.nacos.config.server-addr=localhost:8848
spring.cloud.nacos.config.namespace=902b203e-41b0-47b2-8b87-56f90b4e4162
spring.cloud.nacos.config.extension-configs[0].data-id=gulimall-product.yml
spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[0].refresh=true

④然后启动gulimall-product,查看到该服务已经出现在了nacos的注册中心中了
在这里插入图片描述
⑤业务代码编写

CategoryEntity实体类

@TableField(exist=false)//该注解的false表示表中不存在该字段,只是自定义的字段,方便编码
	private List<CategoryEntity> children;

gulimall-product的controller层

(1)在CategoryController 中编写请求入口方法:

/**
  * 查询出所有分类,以树形结构组装起来
*/
@RequestMapping("/list/tree")
public R list(){
    //listWithTree()该方法被CategoryServiceImpl重写
    List<CategoryEntity> entities = categoryService.listWithTree();
    return R.ok().put("data", entities);
}

gulimall-product的service层

(2) 在CategoryServiceImpl中编写方法递归查找所有分类,以树形结构组装起来:一级分类ParentCid=0,一级分类的下的二级分类满足categoryEntity.getParentCid() == root.getCatId()

CategoryService接口

public interface CategoryService extends IService<CategoryEntity> {

    PageUtils queryPage(Map<String, Object> params);

    List<CategoryEntity> listWithTree();
}

CategoryServiceImpl实现类,重写上述的接口方法

@Override
public List<CategoryEntity> listWithTree() {
    //1、查出所有分类
    List<CategoryEntity> entities = baseMapper.selectList(null);
    //2、组装成父子的树形结构
    //2.1)、找到所有的一级分类
    List<CategoryEntity> level1Menus = entities.stream().filter(
        //一级分类的parentId=0,根据这个条件构建出一级分类的数据
        categoryEntity -> categoryEntity.getParentCid() == 0
    ).map((menu)->{
        menu.setChildren(getChildrens(menu,entities));
        return menu;
    }).sorted((menu1,menu2)->{
        return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
    }).collect(Collectors.toList());
    return level1Menus;
}

//递归查找所有菜单的子菜单
private List<CategoryEntity> getChildrens(CategoryEntity root,List<CategoryEntity> all){
    List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
        return categoryEntity.getParentCid() == root.getCatId();
    }).map(categoryEntity -> {
        //1、找到子菜单
        categoryEntity.setChildren(getChildrens(categoryEntity,all));
        return categoryEntity;
    }).sorted((menu1,menu2)->{
        //2、菜单的排序
        return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
    }).collect(Collectors.toList());
    return children;
}

(3)访问:http://localhost:10000/product/category/list/tree
在这里插入图片描述

4.2 配置网关路由与路径重写

1、 前端修改

① 后端启动renren-fast项目(为了发送验证码给页面),右键以管理员身份运行vscode,打开前端renren-fast-vue项目,在终端运行前端项目:npm run dev

② 进入人人后台管理系统,创建一级菜单:
在这里插入图片描述
同时数据库中便生成了该数据
在这里插入图片描述
③ 给商品系统添加一个子菜单,点击新增,选择菜单:

同样这个数据也会在数据库中生成,这个url:product/category
在这里插入图片描述
④ 创建renren-fast-vue\src\views\modules\product目录,这样创建,是因为url:product/category,对应于product-category,在该目录下,新建“category.vue”文件 ,编写一个方法:
在这里插入图片描述
在这里插入图片描述
编写category.vue代码

Vue的代码框架就是由这三个标签组成:**、

<template>
  <el-tree
    :data="data"
    :props="defaultProps"
    @node-click="handleNodeClick"
  ></el-tree>
</template>

<script>
//这里可以导入其他文件(比如: 组件, 工具 js, 第三方插件 js, json文件, 图片文件等等)
//例如: import 《组件名称》 from '《组件路径》 ';

export default {
  //import 引入的组件需要注入到对象中才能使用
  components: {},
  props: {},
  data() {
    return {
      data: [],
      defaultProps: {
        children: "children",
        label: "label",
      },
    };
  },
  methods: {
    handleNodeClick(data) {
      console.log(data);
    },
    getMenus(){
        this.$http({
          url: this.$http.adornUrl('/product/category/list/tree'),
          method: 'get'
        }).then(data=>{
            console.log("成功获取到菜单数据....",data)
        })
    }
  }, 
  created() {
      this.getMenus();
  }
};
</script>

<style scoped>
</style>

⑤ 刷新人人项目页面出现404异常,查看请求发现,请求的是“http://localhost:8080/renren-fast/product/category/list/tree”,这个请求是不正确的,正确的请求是:http://localhost:10000/product/category/list/tree
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
需要修改renren-fast-vue项目的 static\config\index.js文件中的api接口请求地址,改成给网关发请求,让网关路由到指定的地址:

//修改前为:
window.SITE_CONFIG['baseUrl'] = 'http://localhost:8080/renren-fast';
//修改后为:
// api接口请求地址,改成给网关发请求,让网关路由到指定的地址
window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';

在这里插入图片描述

2、后端-配置网关路由

http://localhost:88,这个地址是我们网关微服务的接口,我们需要通过网关来完成路径的映射,因此将renren-fast注册到nacos注册中心中,并添加配置中心:

① 在renren-fast项目下的pom文件中导入gulimall-common坐标依赖:

<dependency>
   <groupId>com.atguigu.gulimall</groupId>
   <artifactId>gulimall-common</artifactId>
   <version>0.0.1-SNAPSHOT</version>
</dependency>

② 在renren-fast项目下的application.yml中配置服务名称和注册中心地址,将这个服务注册进nacos:

application:
  name: renren-fast
cloud:
  nacos:
    discovery:
      server-addr: 127.0.0.1:8848

③ 在renren-fast项目下的bootstrap.properties文件中配置服务名称和配置中心nacos的地址:

spring.application.name=renren-fast
spring.cloud.nacos.config.server-addr=localhost:8848
spring.cloud.nacos.config.namespace=4e38920c-8a9f-4d6b-b2c1-3907862d7f3a

③ 在renren-fast项目的主启动类上添加@EnableDiscoveryClient注解,开启服务注册与发现:

@EnableDiscoveryClient
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class RenrenApplication {
	public static void main(String[] args) {
		SpringApplication.run(RenrenApplication.class, args);
	}
}

④ 前台的所有请求都是经由“http://localhost:88/api”来转发的,配置网关路由,在gulimall-gateway服务的applicaiton.yml文件中添加路由规则:

gateway项目的yml文件

- id: admin_route
  uri: lb://renren-fast  # 负载均衡到renren-fast服务
  predicates:
    - Path=/api/**       # 只要请求是localhost:88/api/**这个路径就路由到renren-fast服务

⑤ 启动renren-fast项目,访问http://localhost:8001/#/login,发现验证码报错:没有验证码
在这里插入图片描述
分析原因:

现在验证码请求路径为:http://localhost:88/api/captcha.jpg?uuid=?

原始验证码请求路径为:http://localhost:8080/renren-fast/captcha.jpg?uuid=?

在admin_route的路由规则下,在访问路径中包含了“api”,因此它会将它转发到renren-fast,网关在转发的时候,会使用网关的前缀信息,为了能够正常的取得验证码,我们需要对请求路径进行重写,希望网关将现在验证码的请求路径转成原来验证码的请求路径。

⑥修改gulimall-gateway服务下的application.yml文件中“admin_route”路由规则:添加过滤器filter

- id: admin_route
  uri: lb://renren-fast  # 负载均衡到renren-fast服务
  predicates:
    - Path=/api/**       # 只要请求是localhost:88/api/**这个路径就路由到renren-fast服务
  filters:
    # 意思是将路径 /api/xxx 重写为 /renren-fast/xxx
    - RewritePath=/api/(?<segment>/?.*), /renren-fast/$\{segment}
    # http://localhost:88/api/captcha.jpg?uuid=?
    # http://localhost:8080/renren-fast/captcha.jpg?uuid=?

⑦再次访问:http://localhost:8001/#/login,验证码能够正常的加载了。

但是填写验证码准备登陆时,出现了403状态码, 原因:CORS 头缺少 Access-Control-Allow-Origin,这是一种跨域问题,访问的域名和端口和原来的请求不同,请求就会被限制,因此需要解决跨域问题。
在这里插入图片描述
为了解决上述问题,见下一节

3、跨域问题的解决

①跨域:
在这里插入图片描述
② 跨域流程:
在这里插入图片描述
③ 解决跨域方法1:使用ngnix部署为同一域

④ 解决跨域方法2:配置当次请求允许跨域,添加请求头:
在这里插入图片描述
解决方法:在gulimall-gateway中创建config包并定义“GulimallCorsConfiguration”类,该类用来做过滤,允许所有的请求跨域。
在这里插入图片描述

@Configuration
public class GulimallCorsConfiguration {
    @Bean
    public CorsWebFilter corsWebFilter(){
        UrlBasedCorsConfigurationSource source=new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.setAllowCredentials(true);
        
        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
    }
}

⑤ 重启项目,填写验证码访问http://localhost:8001/#/login,报错:同源策略禁止读取位于 http://localhost:88/api/sys/login 的远程资源。(原因:不允许有多个 ‘Access-Control-Allow-Origin’ CORS 头)
在这里插入图片描述
因为renren-fast项目中也配置了跨域,因此出现了多个跨域,需要把renren-fast中配置的去掉,即去掉该项目下的io.renren.config.CorsConfig类。(注释)
在这里插入图片描述
⑥ 重启renren-fast项目,访问http://localhost:8001/#/login登录成功,进入分类维护菜单,打开f12,发现出现404错误,即请求的http://localhost:88/api/product/category/list/tree不存在
在这里插入图片描述
在这里插入图片描述
因为我们之前定义的网关映射后的路径为http://localhost:8001/renren-fast/product/category/list/tree,但是只有通过http://localhost:10000/product/category/list/tree路径才能够正常访问,所以会报404异常。

4、再次树形展示商品数据

需要在网关gulimall-gateway的application.yml文件中,定义一个gulimall-product服务的路径重写:

- id: product_route
  uri: lb://gulimall-product
  predicates:
    - Path=/api/product/** # 只要请求为/api/product/**,就会路由到gulimall-product服务下
  filters:
    # 意思是将路径 /api/xxx 重写为/xxx
    - RewritePath=/api/(?<segment>/?.*),/$\{segment}

访问:http://localhost:88/api/product/category/list/tree,出现{"msg":"invalid token","code":401}

因为/api/product/**路由规则会拦截/api/** 的路由规则,因此在路由规则的顺序上,将精确的路由规则放置到模糊的路由规则的前面,否则的话,精确的路由规则将不会被匹配到,调整路由规则顺序:

- id: product_route
  uri: lb://gulimall-product
  predicates:
    - Path=/api/product/** # 只要请求为/api/product/**,就会路由到gulimall-product服务下
  filters:
    # 意思是将路径 /api/xxx 重写为/xxx
    - RewritePath=/api/(?<segment>/?.*),/$\{segment}

- id: admin_route
  uri: lb://renren-fast  # 负载均衡到renren-fast服务
  predicates:
    - Path=/api/**       # 只要请求是localhost:88/api/**这个路径就路由到renren-fast服务
  filters:
    # 意思是将路径 /api/xxx 重写为 /renren-fast/xxx
    - RewritePath=/api/(?<segment>/?.*), /renren-fast/$\{segment}
123456789101112131415

再次访问http://localhost:88/api/product/category/list/tree,即可得到所有分类数据:
在这里插入图片描述
到前端人人页面不再报错
在这里插入图片描述

4.3 逻辑删除

逻辑删除:就是并不删除数据库的数据,只是以一种状态不显示它。仅是标记它被删除了,这就是逻辑删除,比如后面可以设置show_status为0,标记它已经被删除。

物理删除:表示真正的删除数据库的数据(某一行或几行)

1、前端页面展示数据库的数据

①在category.vue添加商品栏显示、append、delete、框选择等

<template>
  <el-tree :data="menus" :props="defaultProps" :expand-on-click-node="false" show-checkbox
    node-key="catId">
    <span class="custom-tree-node" slot-scope="{ node, data }">
      <span>{{ node.label }}</span>
      <span>
        <el-button v-if="node.level <= 2" type="text" size="mini" @click="() => append(data)">
          Append
        </el-button>
        <el-button v-if="node.childNodes.length == 0" type="text" size="mini" @click="() => remove(node, data)">
          Delete
        </el-button>
      </span>
    </span></el-tree>
</template>
data() {
    return {
      menus: [],
      defaultProps: {
        children: "children",
        label: "name",
      },
    };
  },
methods: {
    append(data) {
      console.log("append", data);
    },

    remove(node, data) {
      console.log("remove", node, data);
    },
}

在这里插入图片描述

2、后端逻辑删除业务处理

(1)物理删除

① 测试代码生成工具(renren-generate)生成的com.bigdata.gulimall.product.controller.CategoryController类中delete()方法:

/**
   * 删除
   * @RequestBody:获取请求体,必须发送post请求
   * SpringMvc自动将请求体的数据(json),转换为对象
*/
@RequestMapping("/delete")
public R delete(@RequestBody Long[] catIds){
    //传入的是数组
    categoryService.removeByIds(Arrays.asList(catIds));
    return R.ok();
}

②使用postman,模拟前端发送请求localhost:88/api/product/category/delete,删除catid=1432的那行数据:
在这里插入图片描述
发现数据库的1432行数据已经被删除
在这里插入图片描述
(2)逻辑删除

参考Mybatis-plus官方文档:https://baomidou.com/guide/logic-delete.html

①gulimall-product的application.yml

#在gulimall-product服务的application.yml文件中配置逻辑删除的规则
mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

#另外在gulimall-product服务的application.yml文件中,设置日志级别,方便打印出SQL语句:
logging:
  level:
    com.atguigu.gulimall: debug

②controller:业务上删除菜单的时候,不能直接删除,需要检查当前删除的菜单是否被别的地方引用,修改delete()方法。

自定义逻辑删除方法removeMenuByIds

@RequestMapping("/delete")
public R delete(@RequestBody Long[] catIds){
    //    categoryService.removeByIds(Arrays.asList(catIds));
    //自定义逻辑删除方法
    categoryService.removeMenuByIds(Arrays.asList(catIds));
    return R.ok();
}

③service:接口(省略)和实现类(在com.bigdata.gulimall.product.service.impl.CategoryServiceImpl中编写业务逻辑:)

@Override
public void removeMenuByIds(List<Long> asList) {
    //TODO  1、检查当前删除的菜单,是否被别的地方引用
    //逻辑删除
    baseMapper.deleteBatchIds(asList);
}

④entity:实体类字段上加上@TableLogic注解:

/**
 * 是否显示[0-不显示,1显示]
 */
@TableLogic(value = "1",delval = "0")//自定义显示,1表示逻辑删除(与模板相反)
private Integer showStatus;

⑤我们并不希望将数据库中的数据删除,而是标记它被删除了,这就是逻辑删除,可以设置show_status为0,标记它已经被删除:
在这里插入图片描述
⑥在postman模拟前端发送请求localhost:88/api/product/category/delete,删除catid=1431的数据,检查数据库发现show_status=0;

在这里插入图片描述

3、前端逻辑删除和新增展示(了解即可)

删除delete;新增Append

(1)逻辑删除

点击Delete删除菜单:

  • 参考ElementUI中的Tree树形控件,在templeate标签中添加<el-tree>
  • 在script标签中添加remove()方法,同时参考ElementUI中的 Message 消息提示组件和 MessageBox 弹框组件
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

(2)新增append

点击Append新增菜单:

  • 参考ElementUI中的Dialog 对话框组件,在templeate标签中添加<el-dialog>
  • 在script标签中添加append (data) 方法和addCategory ()方法
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
4、前端-批量删除:

① 在template中新增批量删除按钮

② 在script标签中编写batchDelete ()方法

4.4 数据修改(update)

因为后端的代码基本用代码生成器生成,所以只需要简单修改

1、后端gulimall-product的controller

后端修改package com.atguigu.gulimall.product.controller**.CategoryController()**类中的方法:

/**
     * 信息
     */
    @RequestMapping("/info/{catId}")
    public R info(@PathVariable("catId") Long catId){
        CategoryEntity category = categoryService.getById(catId);
        return R.ok().put("data", category);
    }

2、前端继续修改category.vue

点击edit修改菜单:

① 修改<el-dialog>,添加一个按钮edit

② 修改<el-dialog>,添加绑定的数据

<el-dialog
      :title="title"
      :visible.sync="dialogVisible"
      width="30%"
      :close-on-click-modal="false"
    >
      <el-form :model="category">
        <el-form-item label="分类名称">
          <el-input v-model="category.name" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="图标">
          <el-input v-model="category.icon" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="计量单位">
          <el-input
            v-model="category.productUnit"
            autocomplete="off"
          ></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitData">确 定</el-button>
      </span>
    </el-dialog>

③ 在script标签中添加edit(data)方法,submitData () 方法,editCategory ()方法,并对append (data) 方法和addCategory ()方法进行修改

<el-button type="text" size="mini" @click="edit(data)"
            >edit</el-button
          >
submitData() {
      if (this.dialogType == "add") {
        this.addCategory();
      }
      if (this.dialogType == "edit") {
        this.editCategory();
      }
    },
    //修改三级分类数据
    editCategory() {
      var { catId, name, icon, productUnit } = this.category;
      this.$http({
        url: this.$http.adornUrl("/product/category/update"),
        method: "post",
        data: this.$http.adornData({ catId, name, icon, productUnit }, false),
      }).then(({ data }) => {
        this.$message({
          message: "菜单修改成功",
          type: "success",
        });
        //关闭对话框
        this.dialogVisible = false;
        //刷新出新的菜单
        this.getMenus();
        //设置需要默认展开的菜单
        this.expandedKey = [this.category.parentCid];
      });
    },

4.5 拖拽功能

1、通过拖拽节点改变节点顺序以及父子关系:

① 参考ElementUI中的Tree 树形控件,找到可拖拽节点,给<el-tree>添加属性draggable

② 在script标签中编写 allowDrop ()方法和 countNodeLevel ()方法

2、前端-拖拽数据收集并保存:

拖动菜单时需要修改顺序和级别,收集拖拽后节点发生变化的节点数据:

① 参考ElementUI中的Tree 树形控件,找到可拖拽节点,给<el-tree>添加@node-drop=“handleDrop”

② 在script标签中编写 handleDrop ()方法和updateChildNodeLevel ()方法,将收集的节点数据放进 updateNodes: []

③ 在script标签中编写batchSave ()方法将 updateNodes: []中的数据发送给服务器

④ 后端编写方法接收数据并插入数据库

@RequestMapping("/update/sort")
public R updateSort(@RequestBody CategoryEntity[] category){
    categoryService.updateBatchById(Arrays.asList(category));
    return R.ok();
}
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值