天天看点

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

文章目录

  • ​​1.三级分类​​
  • ​​2.递归树形结构获取数据​​
  • ​​3.配置Vue项目​​
  • ​​4.网关配置​​

1.三级分类

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

一级分类查出二级分类数据,二级分类中查询出三级分类数据

数据库设计

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

2.递归树形结构获取数据

在注册中心中“product”命名空间中,创建“goshop-product.properties”配置文件:

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问
《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问
《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问
《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

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

spring.application.name=goshop-product
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=1c4754cb-987f-4362-a871-7ab52136f7ee

spring.cloud.nacos.config.ext-config[0].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.ext-config[0].refresh=true

spring.cloud.nacos.config.ext-config[1].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[1].group=DEFAULT_GROUP
spring.cloud.nacos.config.ext-config[1].refresh=true
        
spring.cloud.nacos.config.ext-config[2].data-id=other.yml
spring.cloud.nacos.config.ext-config[2].group=DEFAULT_GROUP
spring.cloud.nacos.config.ext-config[2].refresh=true      

然后启动goshop-product,查看到该服务已经出现在了nacos的注册中心中了

修改“com.itxiongmao.goshop.product.controller.CategoryController”类,添加如下代码:

/**
  * 查出所有分类以及子分类,以树形结构组装起来
  */
 @RequestMapping("/list/tree")
 public R list(){
     List<CategoryEntity> entities = categoryService.listWithTree();
     return R.ok().put("data", entities);
 }      
@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
     ).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;
 }      

分类实体类:

package com.itxiongmao.goshop.product.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

/**
 * 商品三级分类
 * 
 * @author bruce
 * @email
 * @date 2021-04-18 23:40:14
 */
@Data
@TableName("pms_category")
public class CategoryEntity implements Serializable {
  private static final long serialVersionUID = 1L;

  /**
   * 分类id
   */
  @TableId
  private Long catId;
  /**
   * 分类名称
   */
  private String name;
  /**
   * 父分类id
   */
  private Long parentCid;
  /**
   * 层级
   */
  private Integer catLevel;
  /**
   * 是否显示[0-不显示,1显示]
   */
  private Integer showStatus;
  /**
   * 排序
   */
  private Integer sort;
  /**
   * 图标地址
   */
  private String icon;
  /**
   * 计量单位
   */
  private String productUnit;
  /**
   * 商品数量
   */
  private Integer productCount;


  @JsonInclude(JsonInclude.Include.NON_EMPTY)
  @TableField(exist=false)
  private List<CategoryEntity> children;

}      

测试结果:

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

3.配置Vue项目

启动后端项目​

​renren-fast​

​​

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

启动前端项目renren-fast-vue:

npm run dev      
《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问
《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

创建一级菜单:

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

创建完成后,在后台的管理系统中会创建一条记录:

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

然后创建子菜单:

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问
《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

创建​​

​renren-fast-vue\src\views\modules\product​

​​目录,之所以是这样来创建,是因为​

​product/category​

​​,对应于​

​product-category​

在该目录下,新建“​

​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: {},

  //监听属性 类似于data概念
  computed: {},
  //监控data中的数据变化
  watch: {},
  data() {
    return {
      menus: [],
      defaultProps: {
        children: "childrens",
        label: "name"
      }
    };
  },

  methods: {
    handleNodeClick(data) {
      console.log(data);
    },
    getMenus() {
      this.dataListLoading = true;
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get"
      }).then(({ data }) => {
        console.log("获取到数据", data);
        this.menus = data;
      });
    }
  },

  //生命周期 - 创建完成(可以访问当前this实例)
  created() {
    this.getMenus();
  },
  //生命周期 - 挂载完成(可以访问DOM元素)
  mounted(){},
  beforeCreate(){}, //生命周期 - 创建之前
  beforeMount(){}, //生命周期 - 挂载之前
  beforeUpdate(){}, //生命周期 - 更新之前
  updated(){}, //生命周期 - 更新之后
  beforeDestroy(){}, //生命周期 - 销毁之前
  destroyed(){}, //生命周期 - 销毁完成
  activated(){} //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>      

刷新页面出现404异常,查看请求发现,请求的是“​​http://localhost:8080/renren-fast/product/category/list/tree​​​”

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

这个请求是不正确的,正确的请求是:​​http://localhost:10000/product/category/list/tree​​,

修正这个问题:

替换“​

​static\config\index.js​

​​”文件中的“​

​window.SITE_CONFIG['baseUrl']​

​”

替换前:

window.SITE_CONFIG['baseUrl'] = 'http://localhost:8080/renren-fast';      

替换后:

window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';      

​​http://localhost:88​​,这个地址是我们网关微服务的接口。

4.网关配置

这里我们需要通过网关来完成路径的映射,因此将​

​renren-fast​

​​注册到​

​nacos​

​注册中心中,并添加配置中心

<dependency>
  <groupId>com.itxiongmao.goshop</groupId>
  <artifactId>goshop-common</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>      
application:
    name: renren-fast
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
      config:
        name: renren-fast
        server-addr: 127.0.0.1:8848
        namespace: 5f33d11b-d560-4aaf-ad58-333ed653fbc4      
《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

启动类:

@SpringBootApplication
@EnableDiscoveryClient
public class RenrenApplication {

  public static void main(String[] args) {
    SpringApplication.run(RenrenApplication.class, args);
  }
}      

注册中心:

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

配置网关路由,前台的所有请求都是经由“​​

​http://localhost:88/api​

​​”来转发的,在“​

​goshop-gateway​

​”中添加路由规则:

- id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**      

但是这样做也引入了另外的一个问题,再次访问:​

​http://localhost:8001/#/login​

​,发现验证码不再显示:

分析原因:

  1. 现在的验证码请求路径为,​

    ​http://localhost:88/api/captcha.jpg?uuid=69c79f02-d15b-478a-8465-a07fd09001e6​

  2. 原始的验证码请求路径:​

    ​http://localhost:8001/renren-fast/captcha.jpg?uuid=69c79f02-d15b-478a-8465-a07fd09001e6​

在admin_route的路由规则下,在访问路径中包含了“​

​api​

​​”,因此它会将它转发到​

​renren-fast​

​,网关在转发的时候,会使用网关的前缀信息,为了能够正常的取得验证码,我们需要对请求路径进行重写

关于请求路径重写:

​​6.16. The ​

​RewritePath​

​ ​

​GatewayFilter​

​ Factory​​

The ​

​RewritePath​

​​ ​

​GatewayFilter​

​​ factory takes a path ​

​regexp​

​​ parameter and a ​

​replacement​

​​ parameter. This uses Java regular expressions for a flexible way to rewrite the request path. The following listing configures a ​

​RewritePath​

​​ ​

​GatewayFilter​

​:

Example 41. application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: rewritepath_route
        uri: https://example.org
        predicates:
        - Path=/foo/**
        filters:
        - RewritePath=/red(?<segment>/?.*), $\{segment}      

For a request path of ​

​/red/blue​

​​, this sets the path to ​

​/blue​

​​ before making the downstream request. Note that the ​

​$​

​​ should be replaced with ​

​$\​

​ because of the YAML specification.

修改“admin_route”路由规则:

- id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/(?<segment>/?.*), /renren-fast/$\{segment}      

再次访问:​

​http://localhost:8001/#/login​

​​,验证码能够正常的加载了。

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

但是很不幸新的问题又产生了,访问被拒绝了

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

问题描述:已拦截跨源请求:同源策略禁止读取位于 ​

​http://localhost:88/api/sys/login​

​​ 的远程资源。(原因:CORS 头缺少 ‘​

​Access-Control-Allow-Origin​

​’)。

问题分析:这是一种跨域问题。访问的域名和端口和原来的请求不同,请求就会被限制

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

跨域流程

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

参考地址:​​https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS​​

解决跨域

方式1: 使用nginx部署为同一域

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

方式2: 配置当次请求允许跨域

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

解决方法:在网关中定义“​

​GoShopCorsConfiguration​

​”类,该类用来做过滤,允许所有的请求跨域。

package com.itxiongmao.goshop.gateway.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

/**
 * @BelongsProject: goshop
 * @BelongsPackage: com.itxiongmao.goshop.gateway.config
 * @CreateTime: 2021-05-18 17:31
 * @Description: TODO
 */
@Configuration
public class GoShopCorsConfiguration {

    @Bean
    public CorsWebFilter corsWebFilter(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        CorsConfiguration corsConfiguration = new CorsConfiguration();

        //1、配置跨域
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.setAllowCredentials(true);

        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
    }
}      

再次访问:​

​http://localhost:8001/#/login​

​​

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

​http://localhost:8001/renre​

​已拦截跨源请求:同源策略禁止读取位于 ​

​http://localhost:88/api/sys/login​

​ 的远程资源。(原因:不允许有多个 ‘Access-Control-Allow-Origin’ CORS 头)​

​renren-fast/captcha.jpg?uuid=69c79f02-d15b-478a-8465-a07fd09001e6​

出现了多个请求,并且也存在多个跨源请求。

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

为了解决这个问题,需要修改renren-fast项目,注释掉“​

​io.renren.config.CorsConfig​

​​”类。然后再次进行访问。

《分布式微服务电商》专题(十四)-电商项目商品类目后台页面访问

在显示分类信息的时候,出现了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异常。

解决方法就是定义一个​

​product​

​路由规则,进行路径重写:

server:
  port: 88
spring:
  application:
    name: goshop-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: test_route
          uri: http://baidu.com:80/
          predicates:
            - Query=url,baidu
        - id: qq_route
          uri: http://qq.com:80/
          predicates:
            - Query=url,qq
        #商品路由
        - id: product_route
          uri: lb://goshop-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/(?<segment>/?.*),/$\{segment}
        #将路径为Path=/api/**转发至后台管理
        - id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/(?<segment>/?.*), /renren-fast/$\{segment}
logging:
  level:
    org.springframework.cloud.gateway: TRACE
    org.springframework.http.server.reactive: DEBUG
    org.springframework.web.reactive: DEBUG
    reactor.ipc.netty: DEBUG      
<!--  -->
<template>
  <el-tree :data="menus" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>

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

export default {
  //import引入的组件需要注入到对象中才能使用
  components: {},

  //监听属性 类似于data概念
  computed: {},
  //监控data中的数据变化
  watch: {},
  data() {
    return {
      menus: [],
      defaultProps: {
        children: "children",
        label: "name"
      }
    };
  },

  methods: {
    handleNodeClick(data) {
      console.log(data);
    },
    getMenus() {
      this.dataListLoading = true;
      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>      
<el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
<!--
data    展示数据
props 配置选项
    children 指定子树为节点对象的某个属性值
    label 指定节点标签为节点对象的某个属性值
    disabled 节点选择框是否禁用为节点对象的某个属性值
@node-click 节点被点击时的回调
-->      

继续阅读