008 三级分类

文章目录

    • tb_category.sql
    • vscode
      • category.vue(模板)
    • static -> config -> index.js
    • eslint不校验
    • 三级分类模板
    • dialog模板
  • 代码
    • 前端
      • category.vue
      • index.js
    • 后端
      • CategoryController.java
      • CategoryService.java
      • CategoryServiceImpl.java
      • application.yml
      • CategoryEntity.java

https://docs.spring.io/spring-framework/reference/web/webmvc-cors.html

https://element.eleme.cn/#/zh-CN/component/tree

https://www.postman.com/

https://baomidou.com/guides/logic-delete/

https://element.eleme.cn/#/zh-CN/component/message-box

https://element.eleme.cn/#/zh-CN/component/dialog

tb_category.sql

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tb_category
-- ----------------------------
DROP TABLE IF EXISTS `tb_category`;
CREATE TABLE `tb_category`  (
  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT '分类ID',
  `name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '分类名称',
  `goods_num` int(0) NULL DEFAULT 0 COMMENT '商品数量',
  `is_show` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '是否显示',
  `is_menu` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '是否导航',
  `seq` int(0) NULL DEFAULT NULL COMMENT '排序',
  `parent_id` int(0) NULL DEFAULT NULL COMMENT '上级ID',
  `template_id` int(0) NULL DEFAULT NULL COMMENT '模板ID',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `parent_id`(`parent_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1221 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '商品类目' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;

vscode

src -> views -> modules -> product -> category.vue

//http://127.0.0.1:8081/product/category/list/tree
//发送请求,最终请求的是 http://127.0.0.1:8888/api/product/category/list/tree
//http://127.0.0.1:8888/api 是 index.js文件中 已经配置好了 baseUrl

category.vue(模板)







static -> config -> index.js

  // api接口请求地址
  window.SITE_CONFIG['baseUrl'] = 'http://localhost:8888/api';

跨域问题

window.SITE_CONFIG['baseUrl'] = 'http://localhost:8080/renren-fast';
package com.xd.cubemall.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;
//import org.springframework.web.filter.CorsFilter;

@Configuration
public class CubemallCorsConfiguration {

    @Bean
    public CorsWebFilter corsWebFilter(){
        CorsConfiguration config = new CorsConfiguration();

        // Possibly...
        // config.applyPermitDefaultValues()

        config.setAllowCredentials(true);//可以携带cookie信息
        config.addAllowedOrigin("*");//允许所有的请求地址 访问
        config.addAllowedHeader("*");//允许携带所有的请求头信息
        config.addAllowedMethod("*");//允许所有的请求方式 get post put delete option

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

spring:
  cloud:
    gateway:
      routes:
        - id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/(?>/?.*), /renren-fast/$\{segment}
在你提供的Spring Cloud Gateway配置中,你定义了一个路由规则,这个规则将所有匹配/api/**路径的请求重写并转发到renren-fast服务。这里是具体的解释:

id: 路由的唯一标识符,这里命名为admin_route。
uri: 目标服务的URI,这里使用了lb://renren-fast,表示这是一个服务发现机制中的服务名,lb代表LoadBalancer(负载均衡器),它将请求分发到renren-fast服务的实例上。
predicates: 断言集合,用于匹配HTTP请求。这里使用了一个Path断言,它匹配所有以/api/开头的请求路径。
filters: 过滤器集合,用于修改匹配的HTTP请求和响应。这里使用了一个RewritePath过滤器,它将请求路径中的/api/部分替换为/renren-fast/,并将剩余的路径部分保持不变。正则表达式中的(?<segment>/?.*)是一个命名捕获组,它捕获了/api/之后的所有内容,并将其存储在segment变量中。然后,在重写路径时,通过$\{segment}引用这个变量。
注意,在YAML文件中,你需要确保字符串中的特殊字符(如{和})被正确地转义或引用,以避免解析错误。在你的配置中,$\{segment}应该是正确的,因为它使用了YAML的字符串插值语法。然而,在一些情况下,你可能需要使用单引号或双引号来包围整个字符串,特别是当字符串中包含YAML的保留字符时。
这个路由配置的作用是:当Gateway接收到一个以/api/开头的请求时,它会将请求的路径重写为以/renren-fast/开头,并将请求转发到renren-fast服务。这样,前端应用就可以通过访问Gateway的/api/路径来间接访问后端服务的/renren-fast/路径,而不需要担心跨域问题,因为Gateway会处理CORS策略。

“跨域”是指在一个网页(通常称为“源”)中运行的脚本尝试访问另一个源中的资源。在Web开发中,源被定义为协议、域名和端口号的组合。如果这三个部分中的任何一个不同,那么两个资源就被认为是来自不同的源,并因此受到同源策略的限制。

同源策略是浏览器安全功能的一部分,它限制了来自不同源的文档或脚本之间的交互方式。这是为了防止恶意脚本从一个源读取敏感数据并将其发送到另一个源。

假设你有一个运行在8080端口上的Tomcat服务器,以及一个运行在8888端口上的Spring Cloud Gateway。如果你的前端应用(比如一个运行在浏览器中的JavaScript应用)尝试直接从8080端口获取数据,而前端应用本身是从另一个端口(比如80或443)加载的,那么浏览器会因为同源策略而阻止这个请求。

为了解决这个问题,你使用了Spring Cloud Gateway作为一个中间层,它可以在8888端口上接收请求,并将它们转发到8080端口上的Tomcat服务器。同时,你可以在Gateway中配置了CORS(跨源资源共享)策略,以允许来自不同源的请求。

这样,即使前端应用和后端服务不在同一个源上,前端应用仍然可以通过Gateway访问后端服务,因为Gateway已经配置了适当的CORS策略来允许跨域请求。

要将请求从Spring Cloud Gateway的8888端口转发到renren-fast服务的8080端口,你需要确保Gateway已经正确配置了路由规则。

前端请求:
前端应用(可能是运行在浏览器中的JavaScript应用)发送一个请求到http://localhost:8888/api/some-endpoint。
Gateway接收请求:
Spring Cloud Gateway在8888端口上接收到这个请求。
路由匹配:
Gateway检查其配置的路由规则,找到匹配/api/**路径的规则。
请求重写:
根据路由规则,Gateway将请求路径重写为/renren-fast/some-endpoint。
转发请求:
Gateway将重写后的请求转发到renren-fast服务,该服务运行在8080端口上。
后端处理请求:
renren-fast服务接收到请求,处理它,并返回响应。
响应返回:
响应通过Gateway返回给前端应用。
前端接收响应:
前端应用接收到响应,并显示结果。
为了确保这一切正常工作,你需要确保以下几点:

Gateway服务已经启动,并且正在监听8888端口。
renren-fast服务已经启动,并且正在监听8080端口。
Gateway的路由规则已经正确配置,以匹配前端应用的请求路径,并将它们重写并转发到renren-fast服务。
如果需要,renren-fast服务应该配置CORS支持,以允许来自不同源的请求。但是,由于请求是通过Gateway转发的,并且Gateway和后端服务通常被认为是同一个源(因为它们都在服务器上),所以通常不需要在后端服务上配置CORS。
确保没有网络防火墙或安全组设置阻止这些端口之间的通信。

eslint不校验

build -> webpack.base.conf.js
//…(config.dev.useEslint ? [createLintingRule()] : []),

三级分类模板

    
      
        {{ node.label }}
        
          
            Append
          
          
            Delete
          
        
      
    

let id = 1000;




    data() {
      const data = [{
        id: 1,
        label: '一级 1',
        children: [{
          id: 4,
          label: '二级 1-1',
          children: [{
            id: 9,
            label: '三级 1-1-1'
          }, {
            id: 10,
            label: '三级 1-1-2'
          }]
        }]
      }, {
        id: 2,
        label: '一级 2',
        children: [{
          id: 5,
          label: '二级 2-1'
        }, {
          id: 6,
          label: '二级 2-2'
        }]
      }, {
        id: 3,
        label: '一级 3',
        children: [{
          id: 7,
          label: '二级 3-1'
        }, {
          id: 8,
          label: '二级 3-2'
        }]
      }];
      return {
        data: JSON.parse(JSON.stringify(data)),
        data: JSON.parse(JSON.stringify(data))
      }
      },
      append(data) {
        const newChild = { id: id++, label: 'testtest', children: [] };
        if (!data.children) {
          this.$set(data, 'children', []);
        }
        data.children.push(newChild);
      },

      remove(node, data) {
        const parent = node.parent;
        const children = parent.data.children || parent.data;
        const index = children.findIndex(d => d.id === data.id);
        children.splice(index, 1);
      }

  .custom-tree-node {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 14px;
    padding-right: 8px;
  }

dialog模板


  
    
      
    
    
      
        
        
      
    
  
  


==============================================

代码

前端

category.vue







index.js

/**
 * 开发环境
 */
;(function () {
  window.SITE_CONFIG = {};

  // api接口请求地址
  window.SITE_CONFIG['baseUrl'] = 'http://localhost:8888/api';

  // cdn地址 = 域名 + 版本号
  window.SITE_CONFIG['domain']  = './'; // 域名
  window.SITE_CONFIG['version'] = '';   // 版本号(年月日时分)
  window.SITE_CONFIG['cdnUrl']  = window.SITE_CONFIG.domain + window.SITE_CONFIG.version;
})();

后端

CategoryController.java

package com.xd.cubemall.product.controller;

import java.util.Arrays;
import java.util.List;
import java.util.Map;


import com.xd.cubemall.common.utils.PageUtils;
import com.xd.cubemall.common.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.xd.cubemall.product.entity.CategoryEntity;
import com.xd.cubemall.product.service.CategoryService;




/**
 * 商品类目
 *
 * @author xd
 * @email [email protected]
 * @date 2024-08-13 07:57:20
 */
@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;

    /**
     * 查询出所有分类,以树形结构组装起来
     */
    @RequestMapping("/list/tree")
    //@RequiresPermissions("product:category:list")
    public R list(){
        List<CategoryEntity> entities = categoryService.listWithTree();

        return R.ok().put("data", entities);
    }


    /**
     * 信息
     */
    @RequestMapping("/info/{id}")
    //@RequiresPermissions("product:category:info")
    public R info(@PathVariable("id") Integer id){
		CategoryEntity category = categoryService.getById(id);

        return R.ok().put("data", category);
    }

    /**
     * 保存
     */
    @RequestMapping("/save")
    //@RequiresPermissions("product:category:save")
    public R save(@RequestBody CategoryEntity category){
		categoryService.save(category);

        return R.ok();
    }

    /**
     * 修改
     */
    @RequestMapping("/update")
    //@RequiresPermissions("product:category:update")
    public R update(@RequestBody CategoryEntity category){
		categoryService.updateById(category);

        return R.ok();
    }

    /**
     * 删除
     */
    @RequestMapping("/delete")
    //@RequiresPermissions("product:category:delete")
    public R delete(@RequestBody Integer[] ids){
        //直接删除方式
		//categoryService.removeByIds(Arrays.asList(ids));

        //TODO 检查当前要删除的菜单,是否被别的地方引用
        //逻辑删除方式
        categoryService.removeMenuByIds(Arrays.asList(ids));

        return R.ok();
    }

}

CategoryService.java

package com.xd.cubemall.product.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.xd.cubemall.common.utils.PageUtils;
import com.xd.cubemall.product.entity.CategoryEntity;

import java.util.List;
import java.util.Map;

/**
 * 商品类目
 *
 * @author xd
 * @email [email protected]
 * @date 2024-08-13 01:36:04
 */
public interface CategoryService extends IService<CategoryEntity> {

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

    List<CategoryEntity> listWithTree();

    void removeMenuByIds(List<Integer> asList);
}


CategoryServiceImpl.java

package com.xd.cubemall.product.service.impl;

import com.xd.cubemall.common.utils.PageUtils;
import com.xd.cubemall.common.utils.Query;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;


import com.xd.cubemall.product.dao.CategoryDao;
import com.xd.cubemall.product.entity.CategoryEntity;
import com.xd.cubemall.product.service.CategoryService;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @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);
    }


    /**
     * 查询所有分类
     * @return
     */
    @Override
    public List<CategoryEntity> listWithTree() {
        //1.查询所有分类
        List<CategoryEntity> entities = baseMapper.selectList(null);

        //2.组装成父子的树形结构
        //2.1 找到所有的一级分类
        List<CategoryEntity> levelOneMenus = entities.stream().filter(
            //过滤出一级分类,parentId==0,根据这个条件构建出所有一级分类的数据
                categoryEntity -> categoryEntity.getParentId() == 0

        ).map((menu)->{
            //出现递归操作,关联出子分类(2,3级分类)
            menu.setChildrens(getChildrens(menu,entities));
            return menu;
        }).collect(Collectors.toList());
        return levelOneMenus;
    }

    /**
     * 递归查找指定分类的所有子分类(所有菜单的子菜单)
     * @param currentMenu
     * @param entities
     * @return
     */
    private List<CategoryEntity> getChildrens(CategoryEntity currentMenu, List<CategoryEntity> entities) {
        List<CategoryEntity> childrens = entities.stream().filter(
            //过滤出 当前菜单的所有匹配的子菜单 currentMenu.id == categoryEntity.parentId
                categoryEntity -> currentMenu.getId().equals(categoryEntity.getParentId())

        ).map((menu)->{
            //找到子分类
            menu.setChildrens(getChildrens(menu,entities));
            return menu;
        }).collect(Collectors.toList());

        return childrens;
    }

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

application.yml


spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/cube_goods?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: cubemall-product
server:
  port: 8081
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)

logging:
  level:
    com.xd.cubemall: debug

CategoryEntity.java

package com.xd.cubemall.product.entity;

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

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

import lombok.Data;

/**
 * 商品类目
 * 
 * @author xd
 * @email [email protected]
 * @date 2024-08-13 01:36:04
 */
@Data
@TableName("tb_category")
public class CategoryEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 子分类
	 */
	@TableField(exist = false)
	private List<CategoryEntity> childrens;

	/**
	 * 分类ID
	 */
	@TableId
	private Integer id;
	/**
	 * 分类名称
	 */
	private String name;
	/**
	 * 商品数量
	 */
	private Integer goodsNum;
	/**
	 * 是否显示
	 */
	@TableLogic(value = "1",delval = "0")
	private String isShow;
	/**
	 * 是否导航
	 */
	private String isMenu;
	/**
	 * 排序
	 */
	private Integer seq;
	/**
	 * 上级ID
	 */
	private Integer parentId;
	/**
	 * 模板ID
	 */
	private Integer templateId;

}

你可能感兴趣的:(Spring,Cloud商城项目,三级分类)