目录
6 商品服务
6.1 三级分类
商城的商品页面展示是一个三级分类的。有一级分类、二级分类、三级分类。这就是我们接下来要进行的操作。
表名解释
数据库中表名 | 对应表 |
---|---|
pms_attr | 属性表 |
pms_attr_attrgroup_relation | 属性&属性分组关联表 |
pms_attr_group | 属性分组表 |
pms_brand | 商品品牌表 |
pms_category | 商品三级分类表 |
6.1.1 数据库
- 首先我们在
gulimall_pms
这个数据库中的pms_category
这个表下插入数据(此处展示不了,自己去找资源创建)
以上就将商品分类的表和数据都给创建好了,接下来我们就需要进行代码编写了。
6.1.2 查出所有分类及其子分类
1、CategoryController
gulimall-product中的controller包下的CategoryController
- 在类中对原来逆向生成的代码进行修改,
@RestController
@RequestMapping("product/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 查出所有分类以及子分类,以树形结构组装起来
*/
@RequestMapping("/list/tree")
public R list(){
List<CategoryEntity> entities = categoryService.listWithTree();
return R.ok().put("data", entities);
}
}
2、CategoryService
接着我们使用idea自带的工具帮助我们生成相应的方法。
/**
* 商品三级分类
*/
public interface CategoryService extends IService<CategoryEntity> {
void removeMenuByIds(List<Long> asList); //本次修改的方法
}
3、CategoryServiceImpl
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
// @Autowired
// CategoryDao categoryDao; //其实这里ServiceImpl的泛型就是categoryDao,所以我们可以直接使用ServiceImpl里面的baseMapper
@Override
public PageUtils queryPage(Map<String, Object> params) {
IPage<CategoryEntity> page = this.page(
new Query<CategoryEntity>().getPage(params),
new QueryWrapper<CategoryEntity>()
);
return new PageUtils(page);
}
@Override
public List<CategoryEntity> listWithTree() {
//1.查出所有分类
//没有查询条件,就是查询所有
List<CategoryEntity> entities = baseMapper.selectList(null);
//2.组装成父子的树形结构
//2.1 找到所有的一级分类 categoryEntity -> {} lambda表达式
List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity ->
categoryEntity.getParentCid() == 0 //一级分类的父分类id是0 过滤
).map((menu) -> {
//在菜单收集成list之前先通过递归找到菜单的所有子分类,放在map中,然后排序,即将当前菜单改了之后重新返回,然后再收集菜单。
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;
}
@Override
public void removeMenuByIds(List<Long> asList) {
baseMapper.deleteBatchIds(asList);
}
//递归查找所有的子菜单
//root 当前菜单 all 所有菜单
private List<CategoryEntity> getChildrens(CategoryEntity root,List<CategoryEntity> all){
List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
return categoryEntity.getParentCid() == root.getCatId(); // 二级菜单的父分类id == 一级分类的catid
}).map(categoryEntity -> {
categoryEntity.setChildren(getChildrens(categoryEntity, all)); // 二级菜单下还有三级菜单,继续查找
return categoryEntity;
}).sorted((menu1, menu2) -> {
//sorted()定制排序
return (menu1.getSort() == null ?0:menu1.getSort()) - (menu2.getSort() == null ?0:menu2.getSort());
}).collect(Collectors.toList());
return children;
}
}
这里使用的是流式编程,对于这方面我们可去参考java8新特性的StreamAPI来进行相应的学习。
4、启动测试(端口占用问题解决)
我们启动gulimall-product微服务进行测试查询。在启动的时候发现有报错
Description:
The Tomcat connector configured to listen on port 10000 failed to start. The port may already be in use or the connector may be misconfigured.
Action:
Verify the connector's configuration, identify and stop any process that's listening on port 10000, or configure this application to listen on another port.
上面的意思是我们的10000端口被占用。接下来我们看看到底是什么服务占用了10000端口。
netstat -aon |findstr "12000" #找到12000端口
taskkill /F /PID 6164 #杀死12000端口对应的线程
记:深信服vpn进程在日常工作中需要,而且不知道为什么杀不死?
-
解决办法:暂时修改gulimall_product端口为11000,gulimall_ware端口为12000.
我们接着进行测试,浏览器发送
http://localhost:11000/product/category/list/tree
,测试结果如下图,显示正确。这里我们推荐浏览器装一个Json格式的处理的插件可以很好的帮助我们查看Json数据。 -
6.1.3 配置网关路由与路径重写
启动renren-fast微服务,这个是后台。将renren-fast-vue在vscode中启动,接着我们来到后台系统进行菜单模块的添加。
1、 后台添加目录和菜单
-
在菜单管理中添加一个商品系统的目录。如下图。
-
在商品系统中新增一个分类维护的菜单。菜单的路由其实就是我们商品微服务中的访问路径。
我们在后台系统中修改的,在数据库的gulimall-admin中也会同步进行修改。
-
我们可以看到如果我们点击角色管理的话,地址栏是
/sys-role
,但是我们实际发送的请求应该是/sys/role
,所以由此可以知道后台会将/
自动转换为-
,同理我们去访问/product/category
也会自动被转换为/product-category
。 -
我们在
renren-fast-vue
中可以看到有一个文件,对应的其实就是/sys-role
,即sys文件夹下的role.vue对应的就是角色管理这个页面的展示。所以对于商品分类/product/category
,我们接下来要做的就是在renren-fast-vue
下创建一个product文件夹,文件夹中创建一个category.vue来进行页面展示。
2、编写树形结构
- 对于这一段前端开发的代码,我们可以借鉴
element.eleme.cn
中的快速开发指南进行编写。
<template>
<el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>
<script>
export default {
name: 'category',
components: {},
directives: {},
data() {
return {
data: [],
defaultProps: {
children: 'children',
label: 'label'
}
};
},
mounted() {
},
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>
- 进行测试
测试中发现检查网页源代码发现,本来应该是给商品微服务11000端口发送的查询的,但是发送到了renren-fast了。
renren-fast-vue中有一个Index.js是管理api接口请求地址的,如下图。如果我们本次只是简单的将8080改为11000端口,那么当下次如果是12000呢?难道每次都要改吗?所以我们的下一步做法是使用网关进行路由。通过网关映射到具体的请求地址。
注意这里截图中vue的格式是有错误的,如果不进行修正的话,运行会报错。我们前端的代码中只要显示了红色的,就要检查是哪里错误
对于微服务,后面我们统一改为加api前缀才能路由过去。
接下来进行测试访问,我们发现验证码一直加载不出来。检查网页源代码发现是因为我们直接给网关发送验证码请求了。但是真实的应该是给renren-fast发送请求。
3、将renren-fast注册进nacos,使用网关进行统一管理并进行路径重写
- 引入gulimall-common
-
在renren-fast的application.yml文件中配置nacos注册中心地址
-
在renren-fast的主启动类上加入
@EnableDiscoveryClient
注解,使得该微服务会被注册中心发现 -
在网关配置文件中加入:
- id: admin_route uri: lb://renren-fast # 路由给renren-fast,lb代表负载均衡 predicates: # 什么情况下路由给它 - Path=/api/** # 默认前端项目都带上api前缀,路径中带了api前缀的 filters: - RewritePath=/api/(?<segment>.*),/renren-fast/$\{ segment}
注意:
这个地方一定要格式对齐,否则启动后会出现下面这个问题:
启动报错:
Caused by: org.yaml.snakeyaml.scanner.ScannerException: mapping values are not allowed here
这个地方报错的原因大概率是yml文件语法错误:注意这个坑找了好久,id uri predicates filters都要对齐,同一层级。
4、启动测试(有坑)
- 最开始进行启动,在renren-fast的CorsConfig跨域配置中,allowedOriginPatterns报错。出现原因是因为:我们使用的springboot版本是2.1.8.RELEASE。所以将这个.allowedOriginPatterns换成.allowedOrigins即可。
- 最开始报错,在b站看了评论和弹幕之后将gulimall-common这个依赖给取消了,因为启动报依赖循环报错。后面我将所有的依赖都换成老师的同样的版本之后就没有了。
-
记一次错误踩坑 (这个在后来将gulimall下的所有依赖进行按照老师统一版本进行重构之后,还是使用的引入)对于renren-fast中不要引入gulimall-common这个依赖,否则会出现依赖循环报错这个情况以及一系列的突发情况,正常应该是我们自己引入nacos的pom。舍弃:<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2.1.0.RELEASE</version> </dependency>
<!--nacos作为注册中心,服务注册与发现--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.1.0.RELEASE</version> </dependency>
如此这样之后,启动renren-fast项目后发现仍然报错,报错的原因居然是:no server available nacos
这样的错误可能是application.yml文件中nacos配置错误。回去一看,果然是写成了nacos-config-addr
,其实应该是nacos-discovery-addr
. (注意,细心啊。)
再次启动,启动成功。
鉴于上面出现很多错误,但是老师视频中没有出现这些错误,大概率是因为依赖的原因,所以对于gulimall中所有的依赖进行统一,按照老师的依赖进行配置。以防止后面出现很多突发的错误。
- 根据老师的依赖进行重新设置,然后重新运行网关。
修改后运行成功,验证码出现。
5、浏览器跨域问题
上面我们验证码出现了,但是我们登录却报错,原因在于浏览器的跨域问题。
- 引入浏览器跨域知识
- 解决办法:在gulimall-gateway中设置
GulimallCorsConfiguration
解决跨域问题
我们在gulimall-gateway中创建一个config来存放GulimallCorsConfiguration。注意这个包一定是要在gateway这个包下,否则启动报错(坑)。
@Configuration
public class GulimallCorsConfiguration {
@Bean // 添加过滤器
public CorsWebFilter corsWebFilter(){
// 基于url跨域,选择reactive包下的
UrlBasedCorsConfigurationSource source = new