乐优商城(三)品牌管理

news2024/11/28 0:57:23

1. 品牌的新增

1.1 url 异步请求

  1. 点击品牌管理下的新增品牌,填写品牌信息后提交

2.打开浏览器控制台

由此可以得知:

  • 请求方式:POST
  • 请求路径:/item/brand
  • 请求参数:{name: “测试品牌”, image: “”, cids: “76,327”, letter: “C”}
  • 返回参数:无

1.2 实现品牌新增

1.2.1 Controller

分析四个参数:

  • 请求方式:POST
  • 请求路径:/item/brand
  • 请求参数:brand 对象,外加商品分类的 id 数组 cids
  • 返回参数:无
/**
 * 新增品牌
 * @param cids
 * @param brand 
 * @return
 */
@PostMapping
public ResponseEntity<Void> saveBrand(@RequestParam("cids") List<Long> cids, Brand brand) {
    brandService.saveBrand(cids,brand);
    // 响应 201
    return ResponseEntity.status(HttpStatus.CREATED).build();
}
1.2.2 Service

我们不仅要新增品牌,还要新增品牌和商品分类的中间表。由于是新增操作,所以还需要加上事务管理。

/**
 * 新增品牌
 * @param cids
 * @param brand
 * @return
 */
@Transactional
public void saveBrand(List<Long> cids, Brand brand) {
    // 先新增 Brand
    brandMapper.insertSelective(brand);
    // 再新增中间表
    for (Long cid : cids) {
        brandMapper.insertCategoryAndBrand(cid, brand.getId());
    }
}
1.2.3 Mapper

通用 Mapper 只能处理单表,因此我们要手写新增商品分类和品牌的中间表数据的方法

/**
 * 新增商品分类和品牌的中间表数据
 * @param cid 商品分类 id
 * @param bid 品牌 id
 */
@Insert("INSERT INTO tb_category_brand(category_id, brand_id) VALUES(#{cid}, #{bid})")
void insertCategoryAndBrand(@Param("cid") Long cid, @Param("bid") Long bid);
1.2.4 测试

响应状态码 400,请求参数不合法,那我们再看看请求体的内容

{name: "测试品牌", image: "", cids: "76,327", letter: "C"}

发现请求体的数据格式是 JSON 格式。

但我们 Controller 中接受参数是以 String 类型接受的,那么现在解决办法有两个:

  1. 将 Controller 中接受参数改为以 JSON 类型接受
  2. 将请求体内容改为以 String 类型发送

这两种方法都是可行的。第一种,如果将 Controller 中接受参数改为以 JSON 类型接受,我们就需要创建一个 Java 实体类,好用来反序列化,但这样为了一个方法去创建一个实体类,显然成本太大。

所以我们就采用第二种方法了。

1.2.5 解决问题

找到前端发送请求的代码

修改参数转换为以 String 类型发送

再次测试,新增品牌成功

打开控制台,可以看到请求体已经是以字符串形式发送了

2. 实现图片上传

刚才在实现品牌的新增中,我们并没有上传图片,接下来完成图片上传。

2.1 搭建工程

文件的上传并不只是在品牌管理中有需求,以后的其它服务也可能需要,因此我们创建一个独立的微服务,专门处理各种上传。

2.1.1 创建 leyou-upload

右键 leyou 项目 --> New Module --> Maven --> Next

填写项目信息 --> Next

填写保存的位置 --> Finish

2.1.2 添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>leyou</artifactId>
        <groupId>com.leyou.parent</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou.upload</groupId>
    <artifactId>leyou-upload</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>


</project>
2.1.3 配置文件
server:
  port: 8082
spring:
  application:
    name: upload-service
  servlet:
    multipart:
      max-file-size: 5MB
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
      lease-renewal-interval-in-seconds: 5
      lease-expiration-duration-in-seconds: 10

注意:我们添加了限制文件大小的配置

2.1.4 引导类
@SpringBootApplication
@EnableDiscoveryClient
public class LeyouUploadApplication {
    public static void main(String[] args) {
        SpringApplication.run(LeyouUploadApplication.class, args);
    }
}

2.2 实现图片上传

2.2.1 Controller

参考自定义组件中的文件上传组件可以知道以下内容:

  • 请求方式:上传肯定是 POST
  • 请求路径:/upload/image
  • 请求参数:参数名是 file,SpringMVC 会封装为一个接口:MultipartFile
  • 返回结果:上传成功后得到的文件的 url 路径,返回类型 String
@RestController
@RequestMapping("/upload")
public class UploadController {
    @Autowired
    private UploadService uploadService;

    /**
     * 图片上传
     * @param file
     * @return
     */
    @PostMapping("/image")
    public ResponseEntity<String> uploadImage(@RequestParam("file") MultipartFile file) {
        String url = uploadService.uploadImage(file);
        if(StringUtils.isBlank(url)) {
            return ResponseEntity.badRequest().build();
        }
        return ResponseEntity.status(HttpStatus.CREATED).body(url);
    }
}

2.2.2 Service

在上传文件过程中,我们需要对文件进行检验:

  1. 检验文件类型
  2. 检验文件内容
@Service
public class UploadService {
    // 图片类型
    private static final List<String> IMAGE_CONTENT_TYPES = Arrays.asList("image/png", "image/jpeg");
    // 日志
    private static final Logger LOGGER = LoggerFactory.getLogger(UploadService.class);

    /**
     * 图片上传
     *
     * @param file
     * @return
     */
    public String uploadImage(MultipartFile file) {
        // 获取文件名
        String filename = file.getOriginalFilename();
        // 获取文件类型
        String contentType = file.getContentType();

        // 文件类型不合法,直接返回 null
        if (!IMAGE_CONTENT_TYPES.contains(contentType)) {
            LOGGER.info("文件类型不合法:{}", filename);
            return null;
        }

        try {
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            // 如果文件内容不合法,直接返回 null
            if (bufferedImage == null) {
                LOGGER.info("文件内容不合法:{}", filename);
                return null;
            }

            // 保存文件到服务器
            file.transferTo(new File("C:\\Users\\admin\\Desktop\\img\\" + filename));
            // 返回 url
            return "http://image.leyou.com/" + filename;
        } catch (IOException e) {
            e.printStackTrace();
            LOGGER.info("服务器内部错误:{}", filename);
        }

        return null;
    }
}

注意:这里图片地址使用了另外的 url,原因如下:

  • 图片不能保存在服务器内部,这样会对服务器产生额外的加载负担
  • 一般静态资源都应该使用独立域名,这样访问静态资源时不会携带一些不必要的 cookie,减小请求的数据量
2.2.3 测试
  1. 打开 Postman 接口测试工具

  2. 选择 Post 请求方式,输入请求地址

填写 Headers

填写 Body,选择文件

发送请求,得到响应的 url

去目录查看一下

2.2.4 通过浏览器访问图片

现在我们返回了图片的 url,但这个 url 浏览器还不能访问到,也就无法做到图片的回显。

通过 nginx 代理图片路径

打开 nginx 配置文件,添加如下配置

server {
    listen       80;
    server_name  image.leyou.com;

    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location / {
        root C:\\Users\\admin\\Desktop\\img;
    }
}

重启 nginx

配置 host 文件

通过浏览器访问图片

2.3 绕过网关

图片上传是文件的传输,如果也经过 Zuul 网关的代理,文件就会经过多次网路传输,造成不必要的网络负担。在高并发时,可能导致网络阻塞,Zuul 网关不可用。这样我们的整个系统就瘫痪了。所以,我们上传文件的请求就不经过网关来处理了

2.3.1 nginx 反向代理

这里可以看到请求路径为:api/upload/image,那么肯定会经过 Zuul 网关。如果要绕过 Zuul 网关,可以使用 nginx 反向代理到图片上传的服务地址,步骤如下:

修改 nginx 配置文件,将以 /api/upload 开头的请求拦截下来,转交到图片上传的服务地址

server {
    listen       80;
    server_name  api.leyou.com;

    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location /api/upload {
        proxy_pass http://127.0.0.1:8082;
        proxy_connect_timeout 600;
        proxy_read_timeout 600;
    }

    location / {
        proxy_pass http://127.0.0.1:10010;
        proxy_connect_timeout 600;
        proxy_read_timeout 600;
    }
}

这样请求路径变为了:http://127.0.0.1:8002/api/upload/image,但还是多了一个 /api。

nginx 提供了 rewrite 指令,用于对地址进行重写,格式如下:

rewrite "用来匹配路径的正则" 重写后的路径 [指令];

修改 nginx 配置文件,重写地址

server {
    listen       80;
    server_name  api.leyou.com;

    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location /api/upload {
        proxy_pass http://127.0.0.1:8082;
        proxy_connect_timeout 600;
        proxy_read_timeout 600;

        rewrite "^/api/(.*)$" /$1 break; 
    }

    location / {
        proxy_pass http://127.0.0.1:10010;
        proxy_connect_timeout 600;
        proxy_read_timeout 600;
    }
}
  • 首先,我们映射路径是 /api/upload,而下面一个映射路径是 / ,根据最长路径匹配原则,/api/upload 优先级更高。也就是说,凡是以 /api/upload 开头的路径,都会被第一个配置处理
  • proxy_pass:反向代理,这次我们代理到 8082 端口,也就是 upload-service 服务
  • rewrite "^/api/(.*)$" /$1 break,路径重写:

"^/api/(.*)$":匹配路径的正则表达式,用了分组语法,把/api/以后的所有部分当做1组
/$1:重写的目标路径,这里用$1引用前面正则表达式匹配到的分组(组编号从1开始),即/api/后面的所有。这样新的路径就是除去/api/以外的所有,就达到了去除/api前缀的目的
break:指令,常用的有2个,分别是:last、break
last:重写路径结束后,将得到的路径重新进行一次路径匹配
break:重写路径结束后,不再重新匹配路径。
 

3.重启 nginx 服务器

2.3.2 跨域问题

原来在网关的服务中添加了跨域过滤器,解决了跨域问题。现在不经过网关了,那么同样需要在图片上传的服务中添加了跨域过滤器。

@Configuration
public class LeyouCorsConfigration {

    @Bean
    public CorsFilter corsFilter() {
        // 初始化 cors 配置对象
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("http://manage.leyou.com"); //允许的域
        config.setAllowCredentials(true); //是否发送 Cookie 信息
        config.addAllowedMethod("*"); //允许的请求方式
        config.addAllowedHeader("*"); //允许的头信息

        //初始化 cors 配置源对象
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config); //添加映射路径,拦截一切请求

        return new CorsFilter(configSource); //返回 CorsFilter
    }
}

2.3.3 测试
  1. 重启 leyou-upload 服务

  2. 再次上传图片,成功回显

点击提交,成功新增品牌

2.3.4 文件上传的缺陷

上传本身没有任何问题,问题出在保存文件的方式,我们是保存在服务器机器,就会有下面的问题:

  • 单机器存储,存储能力有限
  • 无法进行水平扩展,因为多台机器的文件无法共享,会出现访问不到的情况
  • 数据没有备份,有单点故障风险
  • 并发能力差

这个时候,最好使用分布式文件存储来代替本地文件存储。

3. FastDFS

3.1 什么是分布式文件系统

分布式文件系统(Distributed File System)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连。

通俗来讲:

  • 传统文件系统管理的文件就存储在本机。
  • 分布式文件系统管理的文件存储在很多机器,这些机器通过网络连接,要被统一管理。无论是上传或者访问文件,都需要通过管理中心来访问

3.2 什么是 FastDFS

FastDFS 是由淘宝的余庆先生所开发的一个轻量级、高性能的开源分布式文件系统。用纯 C 语言开发,功能丰富:

  • 文件存储
  • 文件同步
  • 文件访问(上传、下载)
  • 存取负载均衡
  • 在线扩容

适合有大容量存储需求的应用或系统。同类的分布式文件系统有谷歌的 GFS、HDFS(Hadoop)、TFS(淘宝)等。

3.3 FastDFS 的架构

3.3.1 架构图

FastDFS 两个主要的角色:Tracker Server 和 Storage Server 。

  • Tracker Server:跟踪服务器,主要负责调度 storage 节点与 client 通信,在访问上起负载均衡的作用,和记录 storage 节点的运行状态,是连接 client 和 storage 节点的枢纽。
  • Storage Server:存储服务器,保存文件和文件的 meta data(元数据),每个 storage server 会启动一个单独的线程主动向 Tracker cluster 中每个 tracker server 报告其状态信息,包括磁盘使用情况,文件同步情况及文件上传下载次数统计等信息
  • Group:文件组,多台 Storage Server 的集群。上传一个文件到同组内的一台机器上后,FastDFS 会将该文件即时同步到同组内的其它所有机器上,起到备份的作用。不同组的服务器,保存的数据不同,而且相互独立,不进行通信。
  • Tracker Cluster:跟踪服务器的集群,有一组 Tracker Server(跟踪服务器)组成。
  • Storage Cluster :存储集群,有多个 Group 组成。
     
3.3.2 上传和下载流程

  1. Client 通过 Tracker server 查找可用的 Storage server。
  2. Tracker server 向 Client 返回一台可用的 Storage server 的 IP 地址和端口号。
  3. Client 直接通过 Tracker server 返回的 IP 地址和端口与其中一台 Storage server 建立连接并进行文件上传。
  4. 上传完成,Storage server 返回 Client 一个文件 ID,文件上传结束。

  1. Client 通过 Tracker server 查找要下载文件所在的的 Storage server。
  2. Tracker server 向 Client 返回包含指定文件的某个 Storage server 的 IP 地址和端口号。
  3. Client 直接通过 Tracker server 返回的 IP 地址和端口与其中一台 Storage server 建立连接并指定要下载文件。
  4. 下载文件成功。

3.4 安装 FastDFS

参考下载的 FastDFS 资料

注意:配置 FastDFS 服务器 ip 是你自己虚拟机的 ip

3.5 使用 FastDFS 客户端

3.5.1 添加依赖

在父工程中,我们已经锁定了依赖的版本:

<fastDFS.client.version>1.26.1-RELEASE</fastDFS.client.version>

所以直接在 leyou-upload 工程中直接引入依赖即可:

<dependency>
    <groupId>com.github.tobato</groupId>
    <artifactId>fastdfs-client</artifactId>
</dependency>
3.5.2 引入配置类

@Configuration
@Import(FdfsClientConfig.class)
// 解决jmx重复注册bean的问题
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class FastClientImporter {
    
}
3.5.3 配置文件

在 leyou-upload 工程中添加 FastDFS 配置

fdfs:
  so-timeout: 1501 # 超时时间
  connect-timeout: 601 # 连接超时时间
  thumb-image: # 缩略图
    width: 60
    height: 60
  tracker-list: # tracker地址:你的虚拟机服务器地址+端口(默认是22122)
    - 192.168.222.132:22122
3.5.4 配置 host 文件

将来通过 image.leyou.com 这个域名访问 fastDFS 服务器上的图片资源。所以需要配置 hosts 文件,将 image.leyou.com 代理到虚拟机地址。

192.168.222.132 image.leyou.com
3.5.5 测试

注意:测试的时候虚拟机上 FastDFS 和 Nginx 需要是启动的,并且防火请要是关闭的。

编写测试类

@SpringBootTest
@RunWith(SpringRunner.class)
public class FastDFSTest {

    @Autowired
    private FastFileStorageClient storageClient;

    @Autowired
    private ThumbImageConfig thumbImageConfig;

    @Test
    public void testUpload() throws FileNotFoundException {
        // 上传的文件
        File file = new File("C:\\Users\\admin\\Desktop\\1.jpg");
        // 上传并保存图片,参数:1-上传的文件流 2-文件的大小 3-文件的后缀 4-可以不写
        StorePath storePath = this.storageClient.uploadFile(
                new FileInputStream(file), file.length(), "jpg", null);
        // 带分组的路径
        System.out.println(storePath.getFullPath());
        // 不带分组的路径
        System.out.println(storePath.getPath());
    }

    @Test
    public void testUploadAndCreateThumb() throws FileNotFoundException {
        File file = new File("C:\\Users\\admin\\Desktop\\1.jpg");
        // 上传并且生成缩略图
        StorePath storePath = this.storageClient.uploadImageAndCrtThumbImage(
                new FileInputStream(file), file.length(), "jpg", null);
        // 带分组的路径
        System.out.println(storePath.getFullPath());
        // 不带分组的路径
        System.out.println(storePath.getPath());
        // 获取缩略图路径
        String path = thumbImageConfig.getThumbImagePath(storePath.getPath());
        System.out.println(path);
    }
}

testUpload 运行结果

group1/M00/00/00/wKjehF5bzsuAab4AAADSZYQEtvY842.jpg
M00/00/00/wKjehF5bzsuAab4AAADSZYQEtvY842.jpg

testUploadAndCreateThumb 运行结果

group1/M00/00/00/wKjehF5b0u-AAX-AAADSZYQEtvY315.jpg
M00/00/00/wKjehF5b0u-AAX-AAADSZYQEtvY315.jpg
M00/00/00/wKjehF5b0u-AAX-AAADSZYQEtvY315_60x60.jpg

访问第一组第一个路径(正常上传的图片)

访问第二组第三个路径(上传的缩略图)

3.5.6 使用 FastDFS 改造项目

修改 leyou-upload 项目中的 UploadService,将文件上传到本地改为文件上传到 FastDFS

@Service
public class UploadService {
    // 图片类型
    private static final List<String> IMAGE_CONTENT_TYPES = Arrays.asList("image/png", "image/jpeg");
    // 日志
    private static final Logger LOGGER = LoggerFactory.getLogger(UploadService.class);

    @Autowired
    private FastFileStorageClient storageClient;

    /**
     * 图片上传
     *
     * @param file
     * @return
     */
    public String uploadImage(MultipartFile file) {
        // 获取文件名
        String filename = file.getOriginalFilename();
        // 获取文件类型
        String contentType = file.getContentType();

        // 文件类型不合法,直接返回 null
        if (!IMAGE_CONTENT_TYPES.contains(contentType)) {
            LOGGER.info("文件类型不合法:{}", filename);
            return null;
        }

        try {
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            // 如果文件内容不合法,直接返回 null
            if (bufferedImage == null) {
                LOGGER.info("文件内容不合法:{}", filename);
                return null;
            }

            // 获取文件名后缀
            String type = StringUtils.substringAfterLast(filename, ".");

            // 保存文件到 FastDFS 服务器
            StorePath storePath = this.storageClient.uploadFile(
                    file.getInputStream(), file.getSize(), type, null);

            // 返回 url
            return "http://image.leyou.com/" + storePath.getFullPath();
        } catch (IOException e) {
            e.printStackTrace();
            LOGGER.info("服务器内部错误:{}", filename);
        }

        return null;
    }
}

3.5.7 再次测试

新增品牌完成后,可以看到原来上传到本地的图片已经无法显示了,而上传到 FastDFS 上的图片可以显示

4. 品牌的修改

4.1 品牌的回显

要修改品牌信息,首先要让品牌信息回显。

4.1.1 前端部分

我们找到前端中的修改品牌方法

可以看到这里有一个根据品牌信息查询商品分类的请求,可以得到以下信息:

  • 请求方式:GET
  • 请求参数:品牌 id,这里用的是 Rest 风格的占位符
  • 请求地址:/item/category/bid
  • 返回参数:商品分类的集合
4.1.2 实现品牌的回显
4.1.2.1 Controller

在 CategoryController 中编写方法

/**
 * 根据品牌 Id 查询品牌分类
 * @param bid
 * @return
 */
@GetMapping("/bid/{bid}")
public ResponseEntity<List<Category>> queryCategoryByBrandId(@PathVariable("bid") Long bid) {
    if (bid == null || bid.longValue() < 0) {
        return ResponseEntity.badRequest().build(); // 响应 400
    }
    List<Category> categories = categoryService.queryCategoryByBrandId(bid);
    if(CollectionUtils.isEmpty(categories)) {
        return ResponseEntity.notFound().build(); // 响应 404
    }
    return ResponseEntity.ok(categories);
}
4.1.2.2 Service

在 CategoryService 中编写方法

/**
 * 根据品牌 Id 查询品牌分类
 * @param bid
 * @return
 */
public List<Category> queryCategoryByBrandId(Long bid) {
    List<Category> categories = categoryMapper.queryCategoryByBrandId(bid);
    return categories;
}
4.1.2.3 Mapper

在 CategoryMapper 中编写方法

/**
 * 根据品牌 Id 查询品牌分类
 * @param bid
 * @return
 */
@Select("SELECT * FROM tb_category WHERE id IN (SELECT category_id FROM tb_category_brand WHERE brand_id = #{bid})")
List<Category> queryCategoryByBrandId(Long bid);
4.1.2.4 测试
  1. 点击修改按钮

品牌信息回显成功

4.2 品牌的修改

4.2.1 前端部分

找到前端提交表单的方法

可以看到当 isEdit 值为 true 时,发送 put 请求。

而在修改品牌方法中已经把 isEdit 值赋值为 true 了

于是可以得出四个参数:

  • 请求方式:PUT
  • 请求路径:/item/brand
  • 请求参数:brand 对象,外加商品分类的 id 数组 cids
  • 返回参数:无
4.2.2 实现品牌的修改
4.2.2.1 Controller

在 BrandController 编写方法

/**
 * 更新品牌
 * @param cids
 * @param brand
 * @return
 */
@PutMapping
public ResponseEntity<Void> updateBrand(@RequestParam("cids") List<Long> cids, Brand brand) {
    brandService.updateBrand(cids,brand);
    // 响应 204
    return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
4.2.2.2 Service

在 BrandService 编写方法

/**
 * 更新品牌
 * @param cids
 * @param brand
 */
@Transactional
public void updateBrand(List<Long> cids, Brand brand) {
    // 先更新 Brand
    brandMapper.updateByPrimaryKey(brand);
    // 通过品牌 id 删除中间表
    brandMapper.deleteCategoryAndBrandByBid(brand.getId());
    // 再新增中间表
    for (Long cid : cids) {
        brandMapper.insertCategoryAndBrand(cid, brand.getId());
    }
}
4.2.2.3 Mapper

在 BrandMapper 编写方法

/**
 * 通过品牌 id 删除中间表
 * @param bid
 */
@Delete("DELETE FROM tb_category_brand WHERE brand_id = #{bid}")
void deleteCategoryAndBrandByBid(Long bid);
4.2.2.4 测试
  1. 修改品牌名称

点击提交,修改成功

5. 品牌的删除

5.1 前端部分

前端代码里没有删除函数,我就自己定义了一个,删除成功后重新加载数据

可以看到这里有一个删除品牌的请求,可以得到以下信息:

  • 请求方式:DELETE
  • 请求参数:品牌 id,这里用的是 Rest 风格的占位符
  • 请求地址:/item/brand/bid
  • 返回参数:无

5.2 实现品牌的删除

5.2.1 Controller

在 BrandController 中编写

/**
 * 删除品牌
 * @param bid
 * @return
 */
@DeleteMapping("/bid/{bid}")
public ResponseEntity<Void> deleteBrand(@PathVariable("bid") Long bid) {
    brandService.deleteBrand(bid);
    // 响应 204
    return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
5.2.2 Service

在 BrandService 中编写

/**
 * 删除品牌
 * @param bid
 */
@Transactional
public void deleteBrand(Long bid) {
    // 通过品牌 id 删除中间表
    brandMapper.deleteCategoryAndBrandByBid(bid);
    // 删除品牌
    brandMapper.deleteByPrimaryKey(bid);
}
5.2.3 测试

点击删除按钮,删除成功

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1192650.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Cocos新手进阶】父级预制体中的数据列表,在子预制体中的控制方法!

本篇文章主要讲解&#xff0c;cocos中在预制体操作过程中&#xff0c;父级预制体生成的数据列表中&#xff0c;绑定了子预制体中的事件&#xff0c;在子预制体的时间中如何控制上级列表的具体操作教程。 日期&#xff1a;2023年11月10日 作者&#xff1a;任聪聪 一、实际效果情…

矩阵起源加入 OpenCloudOS 操作系统开源社区,完成技术兼容互认证

近日&#xff0c;超融合异构云原生数据库 MatrixOne企业版软件 V1.0 完成了与 OpenCloudOS 的相互兼容认证&#xff0c;测试期间&#xff0c;整体运行稳定&#xff0c;在功能、性能及兼容性方面表现良好。 一、产品简介 矩阵起源 MatrixOrigin 致力于建设开放的技术开源社区和…

牛客网:NC69 链表中倒数最后k个结点

一、题目 函数原型&#xff1a; struct ListNode* FindKthToTail(struct ListNode* pHead, int k ) 二、思路 本题需要找到链表中的倒数第k个结点&#xff0c;有两种方法&#xff1a; 1.暴力解法&#xff1a;先遍历一次链表&#xff0c;计算出链表的长度&#xff1b;再遍历一次…

大厂面试题-MySQL索引的优点和缺点?

(图片)索引&#xff0c;是一种能够帮助MySQL高效从磁盘上检索数据的一种数据结构。在MySQL中的InnoDB引擎中&#xff0c;采用了B树的结构来实现索引和数据的存储 MySQL里面的索引的优点有很多&#xff1a; 1. 通过B树的结构来存储数据&#xff0c;可以大大减少数据检索时的磁…

【docker:容器提交成镜像】

容器创建部分请看&#xff1a;点击此处查看我的另一篇文章 容器提交为镜像 docker commit -a "sinwa lee" -m "首页变化" mynginx lxhnginx:1.0docker run -d -p 88:80 --name lxhnginx lxhnginx:1.0为啥没有变啊&#xff0c;首页&#xff1f; 镜像打包 …

CAN总线记录诊断助手 CAN记录仪

随着CAN总线的应用市场越来越多&#xff0c;不仅局限于汽车行业&#xff0c;工程车、特种车、消防、医疗等多行业都是以CAN总线通讯为主。总线的调试诊断也成为技术日常工作&#xff0c;有个好的工具能有效帮助发现问题、解决问题。 来可电子的CANLog-VCI是一款即插即用的CAN数…

从零开始:集成视频直播美颜SDK的步骤及注意事项

如果你是一位开发者&#xff0c;想要为你的视频直播应用添加美颜功能&#xff0c;那么你来对地方了。本文将从零开始&#xff0c;介绍集成视频直播美颜SDK的步骤及需要注意的事项&#xff0c;帮助你顺利实现这一技术目标。 步骤一&#xff1a;选择合适的美颜SDK 在开始之前&…

带有密码的Excel只读模式,如何取消?

Excel文件打开之后发现是只读模式&#xff0c;想要退出只读模式&#xff0c;但是只读模式是带有密码的&#xff0c;该如何取消带有密码的excel只读文件呢&#xff1f; 带有密码的只读模式&#xff0c;是设置了excel文件的修改权限&#xff0c;取消修改权限&#xff0c;我们需要…

银河麒麟操作系统安装_V4/V10系统详细使用教程

银河麒麟桌面操作系统V10是一款简单易用、稳定高效、安全创新的新一代图形化桌面操作系统产品。现已适配国产主流软硬件产品&#xff0c;同源支持飞腾、鲲鹏、海思麒麟、龙芯、申威、海光、兆芯等国产CPU和Intel、AMD平台&#xff0c;通过功耗管理、内核锁及页拷贝、网络、VFS、…

Python使用腾讯云SDK实现对象存储(上传文件、创建桶)

文章目录 1. 开通服务2. 创建存储桶3. 手动上传文件并查看4. python上传文件4.1 找到sdk文档4.2 初始化代码4.3 region获取4.4 secret_id和secret_key获取4.5 上传对象代码4.6 python实现上传文件 5 python创建桶 首先来到腾讯云官网 https://cloud.tencent.com/1. 开通服务 来…

Python 实践

文章目录 一、HttpRequests 一、Http Requests python——Request模块

家庭安全计划 挑战赛| 溺水预防

溺水预防 从了解到行动 家庭安全计划 | 少年急救官 地震避险逃生该怎么做&#xff1f; 起火了该如何应对&#xff1f; 哪些行为容易导致溺水&#xff1f; 家庭风险隐患有哪些&#xff1f; 家庭逃生演练四步骤你会吗&#xff1f; 国际救助儿童会&#xff08;英国&#xff…

C/C++ 动态内存管理(内存是如何分布的?malloc/new,free/delete的用法是什么?区别是什么?)

目录 一、前言 二、C/C中的内存分布 &#x1f4a6;了解内存区域的划分 &#x1f4a6;内存存储区域的对比和注意点 &#x1f4a6;内存管理的常考面试题 三、C语言的动态管理方式 四、C的动态管理方式 &#x1f4a6;new / delete 操作内置类型&#xff08;int,char.....&…

5 Paimon数据湖之表数据查询详解

更多Paimon数据湖内容请关注&#xff1a;https://edu.51cto.com/course/35051.html 虽然前面我们已经讲过如何查询Paimon表中的数据了&#xff0c;但是有一些细节的东西还需要详细分析一下。 首先是针对Paimon中系统表的查询&#xff0c;例如snapshots\schemas\options等等这些…

【每日一题】—— C. Anonymous Informant(Codeforces Round 908 (Div. 2))(思维题)

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;每日一题 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日反刍 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓称…

(动手学习深度学习)第7章 残差网络---ResNet

目录 ResNet总结 ResNet代码实现ResNet的梯度计算 ResNet 总结 残差块使得很深的网络更加容易训练 甚至可以训练一千层的网络 残差网络对随后的深层神经网络设计产生了深远影响&#xff0c;无论是卷积类网络还是全连接类网络。 ResNet代码实现 导入相关库 import torch fro…

K8S容器内安装cur/telnet命令(Alpine Linux离线环境安装curl/telnet或其他工具)

背景 需求&#xff1a; 微服务的基础是镜像&#xff0c;通常在最小化的Linux镜像中安装jdk&#xff0c;然后运行编译好的java程序。将镜像运行到K8S上就得到了微服务Pod&#xff0c;Pod通常使用安装K8S时配置的私有网段&#xff0c;与宿主机不同。很多时候需要排查从Pod网段内…

概念解析 |移动群智感知:智能设备时代的数据革命

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:移动群智感知(Mobile Crowd Sensing, MCS)。 移动群智感知:智能设备时代的数据革命 移动群智感知示意图 背景介绍 随着智能手机和物联网设备的普及,我们进入了一个全新的…

《未来之路:技术探索与梦想的追逐》

创作纪念日 日期&#xff1a;2023年07月05日文章标题&#xff1a;《从零开始-与大语言模型对话学技术-gradio篇&#xff08;1&#xff09;》成为创作者第128天 在这个平凡的一天&#xff0c;我撰写了自己的第一篇技术博客&#xff0c;题为《从零开始-与大语言模型对话学技术-…

【Docker】iptables命令的使用

iptables是一个非常强大的Linux防火墙工具&#xff0c;你可以使用它来控制网络流量的访问和转发。 前面已经学习了iptables的基本原理&#xff0c;四表五链的基本概念&#xff0c;也已经安装好了iptables&#xff0c;下面我们主要学习iptables命令的基本使用。 可以使用iptable…