三级分类
分类表
在 CategoryController 中编写查询分类方法
/**
* 查出所有分类以及子分类,以树形结构组装起来
*/
@RequestMapping("/list/tree")
//@RequiresPermissions("product:category:list")
public R list(){
List<CategoryEntity> entities = categoryService.listWithTree();
return R.ok().put("data", entities);
}
CategoryEntity 实体类中加上 children 字段,表示所有的子分类
// 表示所有的子分类,由于这个字段不在数据表里面所以加上 exist=false
@TableField(exist=false)
private List<CategoryEntity> children;
创建 service 接口方法和 Impl 实现类
List<CategoryEntity> listWithTree();
@Override
public List<CategoryEntity> listWithTree() {
//1、查出所有分类
List<CategoryEntity> entities = baseMapper.selectList(null);
//2、组装成父子的树形结构
//2.1 找到所有的一级分类
List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity ->
categoryEntity.getParentCid() == 0 // 一级分类的 ParentCid 是 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()); // 排完序后收集到 level1Menus 中
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 -> {
// 递归找到子菜单
categoryEntity.setChildren(getChildrens(categoryEntity,all));
return categoryEntity;
}).sorted((menu1, menu2) -> {
// 菜单排序
return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
}).collect(Collectors.toList());
return children;
}
在这里遇到了一个端口冲突的 bug,解决方法:https://blog.csdn.net/zzxwefxf/article/details/82048057
后端启动 renren-fast,前端启动:npm run dev,在启动 product 模块,nacos 也要启动
views 下 moudules 下新建 category.vue
<!-- -->
<template>
<el-tree
:data="data"
:props="defaultProps"
@node-click="handleNodeClick"
></el-tree>
</template>
<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
export default {
//import引入的组件需要注入到对象中才能使用
components: {},
//监听属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {},
data: [], // 真正的数据从后台发送请求获取
methods: {
// 获取数据列表
getMenus () {
this.$http({
url: this.$http.adornUrl('/product/category/list/tree'),
method: 'get'
}).then(data=>{
console.log("获取到数据",data);
})
},
},
//生命周期 - 创建完成(可以访问当前this实例)
created() {
this.getMenus();
},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {}, //生命周期 - 销毁之前
destroyed() {}, //生命周期 - 销毁完成
activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>
浏览器输入 http://localhost:8001/#/product-category 测试是否可以获取到数据,控制台 404 请求
配置网关
要开始配置网关了,前端配置文件中配置网关地址
后端将 renren-fast 中配置到注册中心中(引入 common 模块,配置 yml,添加启动类注解)
配置 gateway 模块
spring:
cloud:
gateway:
routes:
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
目前这种配置登录的时候验证码还是会获取不到的
这个请求效果还是 404 的,因为满足断言规则网关找到注册中心中的 renren-fast 后,会转到这里:http://renren-fast:8080/api/captcha.jpg。但是 renren-fast 默认请求是从 localhost:8080/renren-fast/captcha.jpg 中获取
这怎么办呢?
这时候可以使用网关的路径重写,springcloud 官网中找到
spring:
cloud:
gateway:
routes:
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
只要发送 api 下的任意请求,默认先路由到后台管理系统 renren-fast(uri: lb://renren-fast),并且重写成指定路径
这里图片我报了 503 错误,看了弹幕说是网关没注册到注册中心,果然我的网关没有注册到 nacos
但是登录还是报错,跨域
跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是
浏览器对 javascript 施加的安全限制。
同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域
跨域流程
解决跨域
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);
}
}
注释掉 renren-fast 自带跨域配置类,要不然会报错
登录问题解决,但是分类菜单数据并没有出来
因为网关现在默认是把所有的请求转给 renren-fast 的,但其实这个请求应该转给 product,在 gateway 模块中配置网关
- id: product_route
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
在 product 的 bootstrap.yml 中添加配置,nacos 中新建命名空间
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
namespace:
application:
name: gulimall-product
将 product 注册到 nacos,bootstrap.yml 添加配置,这样网关就能实时的发现 product 服务然后负载均衡路由到这
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
namespace: 898bc93f-e8e9-4709-9b05-10e101ab8358
application:
name: gulimall-product
浏览器地址栏输入:http://localhost:88/api/product/category/list/tree 报错
由于网关配置中路由到 renren-fast 中的断言 - Path=/api/** 先生效了,我们的请求被它路由走了,所以调整以下路由顺序,把精确匹配 product 的放在上面
再次测试,正常执行
前端页面
这样就可以要到数据了,然后我们要在页面上显示出来,开始写前端 category.vue 了
<!-- -->
<template>
<el-tree
:data="menus"
:props="defaultProps"
@node-click="handleNodeClick"
></el-tree>
</template>
<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
export default {
//import引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
return {
menus: [],
defaultProps: {
children: "children",
label: "name"
}
}
},
//监听属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {},
methods: {
// 获取数据列表
getMenus () {
this.$http({
url: this.$http.adornUrl('/product/category/list/tree'),
method: 'get'
}).then(({data})=>{ // 解构
console.log("获取到数据",data.data);
this.menus = data.data;
})
},
},
//生命周期 - 创建完成(可以访问当前this实例)
created() {
this.getMenus();
},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {}, //生命周期 - 销毁之前
destroyed() {}, //生命周期 - 销毁完成
activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>
刷新显示出来了