谷粒商城-基础篇(详细流程梳理+代码)(中)

举报
长路 发表于 2022/11/28 20:31:07 2022/11/28
【摘要】 目前博主正在学习谷粒商城项目中,正在不断更新中…博客目录索引(持续更新)在一般的电商平台上就包含有三级分类:核心字段:parent_cid(父类id)、sort(排序字段)我们的多级分类是根据父类id来进行划分的,一级分类就是为0,对应二级分类的parent_id就是0,同理三级分类的parent_id就是1。

三、商品服务

3.1、三级分类API

三级分类介绍

在一般的电商平台上就包含有三级分类:

image-20221107175627849

在谷粒商城中,三级分类表是属于商品服务数据库中的表:

image-20221107175846872

  • 核心字段:parent_cid(父类id)、sort(排序字段)
  • 我们的多级分类是根据父类id来进行划分的,一级分类就是为0,对应二级分类的parent_id就是0,同理三级分类的parent_id就是1。

默认在表中是没有数据的,我们需要来进行导入一些初始数据:

将课件中的数据sql代码执行:

image-20221107180109889

3.1.1、查询—产品分类递归树型结构数据获取(后端服务)

完成效果

目标:在gulimall-product模块中编写一个产品三级分类的递归数据结构获取的API接口。

接口:http://localhost:10000/product/category/list/tree

image-20221107180436181

实现过程

主要代码包含gulimall-product模块下:

image-20221107180613802

CategoryController.java:控制器,对应产品分类的树型分类接口

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

CategoryEntity:产品分类实体中添加一个children集合属性,用于封装直接响应给前端,但是注意了这个属性我们需要加上@Field注解表示其不是数据库表中自带的属性,避免在使用mybatisplus查询时出现异常

@Data
@TableName("pms_category")
public class CategoryEntity implements Serializable {
    
    //...
    
	//子分类
	@TableField(exist = false) //表示其不存在数据库表
	private List<CategoryEntity> children;
}

CategoryService.java:定义接口方法

public interface CategoryService extends IService<CategoryEntity> {
    List<CategoryEntity> listWithTree();
}

CategoryService.java:树型分类业务代码,其中就有涉及到一个递归处理操作

package com.atguigu.gulimall.product.service.impl;

import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

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


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    
    @Override
    public List<CategoryEntity> listWithTree() {
        //1、查询到所有的分类
        List<CategoryEntity> entities = baseMapper.selectList(null);
        //2、组装成父子的树型结构
        List<CategoryEntity> ans = entities.stream()
                .filter((menu) -> menu.getParentCid() == 0)
                .map((menu) -> {
                    menu.setChildren(getChildren(menu, entities));
                    return menu;
                })
                .sorted((menu1, menu2) -> {
                    if (menu1.getSort() == null || menu2.getSort() == null) return 0;
                    return menu1.getSort() - menu2.getSort();
                })
                .collect(Collectors.toList());
        return ans;
    }

    /**
     * 递归处理获取子分类
     * @param parent 父分类
     * @param all 所有分类
     * @return 已经获取到子分类的分类
     */
    public List<CategoryEntity> getChildren(CategoryEntity parent, List<CategoryEntity> all) {
        List<CategoryEntity> ans = all.stream()
                .filter((menu) -> menu.getParentCid().equals(parent.getCatId())) //Long类型比较需要进行equals
                .map((menu) -> {
                    menu.setChildren(getChildren(menu, all));
                    return menu;
                })
                .sorted((menu1, menu2) -> {
                    if (menu1.getSort() == null || menu2.getSort() == null) return 0;
                    return menu1.getSort() - menu2.getSort();
                })
                .collect(Collectors.toList());
        return ans;
    }

}

其中的这个树型递归流程使用的是stream流来进行处理,filter过滤+map(填充子列表)+sorted(排序)最终使用collect来进行聚合。


3.1.2、配置管理服务renren-fast网关路由(后端+前端配置)

对于后台管理系统,原先是通过直接去访问renren-fast的服务地址来进行验证码、登录以及后台管理的一系列操作。

对于分布式项目所有的请求都来通过统一的一个网关来进行路由到不同的服务,在这里我们首先来配置一下网关路由!

后端服务(网关动态路由、集成跨域服务)

renren-fast集成配置

首先将后台管理服务renren-fast也集成gulimall-common模块,让其也拥有服务注册的能力:

image-20221107210940303

pom.xml

<!--   引入公共模块     -->
<dependency>
    <groupId>com.atguigu.gulimall</groupId>
    <artifactId>gulimall-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

编写一个bootstrap.properties文件:用于进行服务注册以及配置中心地址配置、应用名

spring.application.name=renren-fast
# 服务注册
spring.cloud.nacos.discovery.server-addr=localhost:8848
# 配置中心
spring.cloud.nacos.config.server-addr=localhost:8848

在启动器上开启服务发现:

@EnableDiscoveryClient //开启服务发现

gulimall-gateway集成配置

image-20221107211547120

准备操作好了之后,我们就来进行gulimall-gateway服务模块的动态路由部分的编写,application.yml配置如下:

spring:
  cloud:
    gateway:
      routes:
        # 自定义后台管理服务的动态路由
        # 路由转换:http://localhost:88/api/renren-fast => http://localhost:8080/renren-fast
        - id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}   # 覆盖重写路径

由于所有的请求都会走我们的网关,那么我们就需要在网关处来进行跨域处理,我们编写一个跨域配置类GulimallCorsConfiguration

package com.atguigu.gulimall.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;

/**
 * @Description: 网关统一处理跨域请求配置类
 * @Author: changlu
 * @Date: 8:02 PM
 */
@Configuration
public class GulimallCorsConfiguration {

    @Bean
    public CorsWebFilter corsWebFilter() {
        //基于url跨域,选择reactive包下
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        //跨域配置信息
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //允许跨域的头
        corsConfiguration.addAllowedHeader("*");
        //允许跨域的请求方式
        corsConfiguration.addAllowedMethod("*");
        //允许跨域的请求来源
        corsConfiguration.addAllowedOrigin("*");
        //是否允许携带cookie跨域
        corsConfiguration.setAllowCredentials(true);

        //任意url都需要进行跨域请求
        source.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsWebFilter(source);
    }

}

在网关处集成了跨域请求之后,我们需要去查看下网关路由的那些服务是否也有跨域请求,因为若是其他服务也有跨域请求处理的话,那么就会在响应头部分添加两次允许跨域的响应头信息,在renren-fast服务中就有,我们需要将其进行注释掉:

image-20221108083325692

至此,我们的后端服务就暂且集成完毕!


前端服务

对于前端的话无需做很大的改动,只需要将全局的请求路径进行修改即可:

image-20221107212342890

http://localhost:8080/renren-fast  => http://localhost:88/api

对于后端服务来说,若是匹配到/api/**就会去进行动态路由匹配转发到后端管理服务去进行操作!


测试

启动nacos注册中心、网关服务、后台管理服务:

image-20221107212644845

此时查看下nacos注册中心,看一下是否已经上线:

image-20221107212715300

没有问题,那么我们就启动前端管理系统项目,来进行一个验证码接口和登录接口的测试:

image-20221107212750507

验证码请求:

image-20221107212837692

login登录请求:

image-20221107212927401


3.1.3、产品分类查询(前端)

前端产品分类页面创建

创建目录:

image-20221107220115949

创建一级菜单:

image-20221107220245374

看一下当前的分类效果:

image-20221107220329158

image-20221107220359275

本质实际上就是在sys_menu表中加了两条记录:

image-20221107220911035

对应product-category实际就会映射到product/category.vue这个文件:

image-20221107220719533


配置商品服务gulimall-product的动态路由地址

在gulimall-product服务中application.yml中配置商品服务的动态路由:

spring:
  cloud:
    gateway:
      routes:
        # 商品服务路由
        # 路由转换:http://localhost:88/api/product/category/list/tree => http://localhost:10000/product/category/list/tree
        - id: product_route
          uri: lb://gulimall-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/product/(?<segment>.*),/product/$\{segment}

image-20221108083620018

提示:你可以看到对于产品服务的动态路由是配置到了renren-fast后台管理服务的路由之上,这是因为renren-fast的路由匹配是从/api/**就开始的,在gateway中去进行匹配是根据你配置的上下顺来来进行匹配转发的,而我们的商品服务动态路由则是匹配的/api/product/**,很明显商品服务的路由是后台管理服务的子路由,所以应该需要进行优先匹配,至此需要放置到其上面!

简而言之:精确路由放在高优先级,模糊路由放在低优先级。


前端产品分类接口实现(集成3.1.1查询接口)

对于树型组件的展示我们可以直接使用element ui的树型组件:

image-20221108084204200

代码如下:

<template>
  <div>
    <el-tree
      :data="menus"
      show-checkbox
      node-key="id"
      :default-expanded-keys="[2, 3]"
      :default-checked-keys="[5]"
      :props="defaultProps">
    </el-tree>
  </div>
</template>

 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();
  },

image-20221108084354774

效果如下:

image-20221108084430419

接着我们要实现一个前端需求:

每个分类右边都有一个新增与删除的按钮,并且对于新增与删除按钮的显示也有要求:
①新增Append显示要求:只有12级分类才具备新增选项。
②删除delete显示要求:只有当子分类数量为0时才允许被删除

效果如下:

image-20221108150931991

如何能够去添加右边的Append与Delete呢?这就使用到了vue中的插槽语法,修整后的代码如下:

<el-tree
      :data="menus"
      show-checkbox
      node-key="catId"
      :expand-on-click-node="false"
      :props="defaultProps"
      :default-expanded-keys="expandedKey"
    >
      <!-- 插槽传的值:node表示该结点的属性(组件原生)  data表示我们实际的值 -->
      <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <!-- 只有一级二级分类才能够显示 node.level是组件原生的属性-->
          <el-button
            v-if="node.level <= 2"
            type="text"
            size="mini"
            @click="() => append(data)"
          >
            Append
          </el-button>
          <!-- 若是没有子节点分类时,就可以进行删除 node.childNodes就是组件自带的node节点 -->
          <el-button
            v-if="node.childNodes.length == 0"
            type="text"
            size="mini"
            @click="() => remove(node, data)"
          >
            Delete
          </el-button>
        </span>
      </span>
    </el-tree>

3.1.4、删除单个分类(后端+前端实现)

效果展示及思路

test

思路逻辑:

1、点击delete,进入message选择框。

2、点击message选择框确认,发起删除请求,最后就是刷新菜单。

  • 注意:成功删除之后,原本展开那个分栏框依旧展开。(使用到了el-tree中的default-expanded-keys属性,只需要在刷新菜单后进行绑定即可)

后端代码(逻辑删除)

配置逻辑删除

gulimall-product中的productEntity配置mybatisplus的逻辑删除注解:

image-20221108151544467

/**
	 * 是否显示[0-不显示,1显示]
	 */
@TableLogic(value = "1", delval = "0")
private Integer showStatus;

配置好了之后,我们去使用mp中baseMapper的查询与删除,默认会走的是逻辑删除及查询对应status=0的所有记录!

删除代码逻辑

CategoryController.java

@RestController
@RequestMapping("product/category")
public class CategoryController {
    
    @Autowired
    private CategoryService categoryService;
    
    /**
     * 删除分类标签
     */
    @PostMapping("/delete")
    //@RequiresPermissions("product:category:delete")
    public R delete(@RequestBody Long[] catIds){
		categoryService.removeMenuByIds(Arrays.asList(catIds));
        return R.ok();
    }
}

CategoryServiceImpl.java:当前仅仅直接实现了批量删除,对于引用的代码并没有进行编写

@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    
    @Override
    public void removeMenuByIds(List<Long> asList) {
        //TODO 1、检查当前删除的菜单,是否被其他地方引用

        //2、逻辑删除
        baseMapper.deleteBatchIds(asList);
    }
}

前端代码

image-20221108151910526

<!-- node-key:后端传过来节点中的id名称(这里就是catId) show-checkbox:展示勾选框  expand-on-click-node:需要点击向下箭头才展开  props:自定义组件中显示的名称
default-expanded-keys 默认展开的节点,接收的是数组(绑定对应的catId)
-->
<el-tree
         :data="menus"
         show-checkbox
         node-key="catId"
         :expand-on-click-node="false"
         :props="defaultProps"
         :default-expanded-keys="expandedKey"
         >
    <!-- 插槽传的值:node表示该结点的属性(组件原生)  data表示我们实际的值 -->
    <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
            <!-- 只有一级二级分类才能够显示 node.level是组件原生的属性-->
            <el-button
                       v-if="node.level <= 2"
                       type="text"
                       size="mini"
                       @click="() => append(data)"
                       >
                Append
            </el-button>
            <!-- 若是没有子节点分类时,就可以进行删除 node.childNodes就是组件自带的node节点 -->
            <el-button
                       v-if="node.childNodes.length == 0"
                       type="text"
                       size="mini"
                       @click="() => remove(node, data)"
                       >
                Delete
            </el-button>
        </span>
    </span>
</el-tree>

// 方法集合
methods: {
    //删除单个分类
    remove(node, data) {
      console.log(node, data);
      this.$confirm(`此操作将刪除分类[${node.label}], 是否继续?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      })
        .then(() => {
          //构成id
          var ids = [data.catId];
          this.$http({
            url: this.$http.adornUrl("/product/category/delete"),
            method: "post",
            data: this.$http.adornData(ids, false)
          }).then(({ data }) => {
            this.$message({
              type: "success",
              message: "删除成功!"
            });
            console.log("刪除成功!");
            //刷新菜單
            this.getMenus();
            //设置展开的是当前删除节点的父catId
            this.expandedKey = [node.parent.data.catId];
          });
        })
        .catch(() => {});
    },
}

3.1.5、新增单个分类(后端+前端实现)

效果展示及思路

test

思路:

1、点击Append,出现弹窗,输入内容。

2、点击确定,发起新增请求,请求结束刷新菜单,显示已经展开的分栏框。


后端代码

直接就是之前逆向生成的代码,我们无需进行修改:

image-20221108152546490

@RestController
@RequestMapping("product/category")
public class CategoryController {
	/**
     * 保存
     */
    @PostMapping("/save")
    //@RequiresPermissions("product:category:save")
    public R save(@RequestBody CategoryEntity category){
		categoryService.save(category);
        return R.ok();
    }
}

前端代码

<el-dialog
           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>
    <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitData">确 定</el-button>
    </span>
</el-dialog>


export default {
	mehtods: {
		//el-tree组件调用的append方法
        append(data) {
          console.log("---append---,data", data);
          //打开窗口
          this.dialogVisible = true;
          this.category.parentCid = data.catId;  //父分类id
          this.category.catLevel = data.catLevel * 1 + 1;  //分类id
          this.category.catId = null; //待服务器自己生成
          this.category.name = "";
          this.category.icon = "";
          this.category.productUnit = "";
          this.category.sort = 0;
          this.category.showStatus = 1;
          console.log("待apennd数据", this.category);
          console.log("---append---");
        },
        //添加三级分类
        addCategory() {
          console.log("---addCategory---")
          console.log(this.category)
          this.$http({
            url: this.$http.adornUrl("/product/category/save"),
            method: "post",
            data: this.$http.adornData(this.category, false)
          }).then(({ data }) => {
              this.$message({
                type: "success",
                message: "新增成功!"
              });
              //关闭窗口
              this.dialogVisible = false;
              //刷新菜單
              this.getMenus();
              //设置展开的是当前删除节点的父catId
              this.expandedKey = [this.category.parentCid];
          });
          console.log("---addCategory---")
        },
    }
}

3.1.6、编辑单个分类(后端+前端实现)

效果展示及思路

test

编辑分类思路:

1、点击编辑回显分类信息,这个信息一定要从服务器中进行查询,否则可能会出现多个管理员去操作时显示的数据还是之前树型结构的。

2、对于真正的编辑请求中携带的数据仅仅只是要进行修改的属性,其他不修改的一律不用携带。


后端代码

①根据catId查询分类信息接口

@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
    
    /**
     * 信息
     */
    @RequestMapping("/info/{catId}")
    public R info(@PathVariable("catId") Long catId){
		CategoryEntity category = categoryService.getById(catId);
        return R.ok().put("category", category);
    }
}

image-20221109145544320

②编辑分类接口

@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
    
    /**
     * 修改
     */
    @PostMapping("/update")
    //@RequiresPermissions("product:category:update")
    public R update(@RequestBody CategoryEntity category){
		categoryService.updateById(category);
        return R.ok();
    }
}

image-20221109145249655

前端代码

前端部分编辑的分类我们是复用之前的新增分类弹窗,对于新增、编辑操作区分我们通过一个单独的属性dialogType来表示,对于窗口的标题名称使用title属性来表示:

<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>

而对于真正提交确定按钮,我们则可以根据对应的dialogType来进行表示:

//编辑
edit(data) {
    console.log("---edit---");
    this.dialogType = "edit";
    this.title = "编辑标签";
    //向后台发起请求获取到最新的标签信息
    this.$http({
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        method: "get"
    }).then(({ data }) => {
        this.dialogVisible = true;
        console.log(data.category);
        this.category = data.category;
        console.log("---edit---");
    });
},
//编辑分类
editCategory() {
    console.log("---提交编辑表单---");
    let data = {
        catId: this.category.catId,
        name: this.category.name,
        icon: this.category.icon,
        productUnit: this.category.productUnit
    };
    console.log(data);
    this.$http({
        url: this.$http.adornUrl("/product/category/update"),
        method: "post",
        data: this.$http.adornData(data, false)
    }).then(({ data }) => {
        this.$message({
            type: "success",
            message: "编辑成功!"
        });
        //关闭窗口
        this.dialogVisible = false;
        //刷新菜單
        this.getMenus();
        //设置展开的是当前删除节点的父catId
        this.expandedKey = [this.category.parentCid];
        console.log("---提交编辑表单---");
    });
},
    //提交数据
    submitData() {
        console.log("---提交数据---");
        if (this.dialogType == "add") {
            this.addCategory();
        } else if (this.dialogType == "edit") {
            this.editCategory();
        }
        console.log("---提交数据---");
    },

3.1.7、拖拽分类并实现批量保存(后端+代码实现)

效果展示及思路

拖拽展示:

test

从图中你可以看到只有在开启拖拽选项后,才能够进行拖拽操作,这个小功能实际上对于element ui组件是只需要配置下的,我们只需要控制draggable属性即可控制是否拖拽!

1、对于是否能够拖到指定的层级位置,是要进行判定的。在当前的系统需求中来说是无法将某个分类拖动到第三级的标签中的。

  • 使用allowDrop(draggingNode, dropNode, type)方法来判断是否能够进行拖动到目标位置:属性分别是拖动中的节点,目标节点以及拖动类型(type 参数有三种情况:‘prev’、‘inner’ 和 ‘next’,分别表示放置在目标节点前、插入至目标节点和放置在目标节点后)。

  • 判断是否可拖动目标位置逻辑:

    • 是否允许拖拽(拖拽过程中会进行调用)before、after、inner
      逻辑:
      1、计算出当前拖动的节点它的最大深度
      2、最大深度 - 当前拖动节点的深度 + 1 + 目标节点的深度 <= 3  符合要求
      

2、只有当我们在拖拽移动到目标节点按下时,说明是目标想要移动的操作,对于整个操作完成我们应当提前将需要对应需要改动的节点sort、level来进行统一存储起来。

  • 这个拖拽完成动作绑定的是el-tree的node-drop方法,包含的属性有:draggingNode, dropNode, dropType, ev

  • 拖拽完成的逻辑操作:

    • 拖拽结束动作:统一添加到待更新的数组中  before、after、inner
      逻辑:
      1、获取到目标节点的最新父节点id并收集(目标是为了之后进行刷新展开对应节点)
      2、得到父节点之后取得它的所有子节点,去统一收集三个部分内容
      第一部分:所有子节点中非移动节点的catId、sort
      第二部分:目标节点1个,更新该目标节点的catId、parentId、sort、catLevel
      第三部分:目标节点的所有子节点,这些节点无需更新他们的sort,即catId、catLevel(递归处理)
      

核心在这个过程中需要存储的有:①批量保存后要展开的父节点。②所有需要保存的节点状态。

//临时存储拖拽的父节点
pCid: [],
//需要更新的所有节点
updateNodes: [],

批量保存实现:

test

批量保存点击之后流程:

1、发送待更新数组,进行批量更新请求。(使用到updateNodes)

2、更新完成之后,展开所有移动目标节点的父节点位置。(使用到pCid)

3、重置updateNodes、pCid数组。


后端代码

对于批量更新分类的后端代码比较简单,直接就是把前端传来的进行批量操作即可:

@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
    
	/**
     * 批量修改分类
     * @param category
     * @return
     */
    @PostMapping("/update/sort")
    public R updateSort(@RequestBody CategoryEntity[] category) {
        categoryService.updateBatchById(Arrays.asList(category));
        return R.ok();
    }
}

image-20221109173941503

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。