文章目录

(一)请求处理过程
(二)品牌新增页面分析
(三)品牌新增后台代码
(四)qs工具
(五)页面校验
(六)新增完成后关闭窗口
(七)文件上传代码逻辑
(八)绕过网关访问图片上传并解决跨域问题
(九)fastDFS的介绍
(十)fastDFS的使用

(一)请求处理过程

品牌查询为例,如下:
在这里插入图片描述

(二)品牌新增页面分析

之前完成了品牌的查询,接下来就是新增功能,点击新增品牌按钮,如下:
在这里插入图片描述
Brand.vue页面有一个提交按钮,如下:
在这里插入图片描述
点击触发addBrand方法,如下:
在这里插入图片描述
this.show就是我们要找的弹窗,如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
分析该自定义组件级联选择的实现,如下:
在这里插入图片描述
新增品牌后看报错,可以看到请求路径请求方式,如下:
在这里插入图片描述
请求参数如下:
在这里插入图片描述
大概的请求信息我们知道了,接下来看回代码,如下:
在这里插入图片描述
在这里插入图片描述

(三)品牌新增后台代码

这里需要注意的是,品牌和商品分类之间是多对多关系
因此我们有一张中间表,来维护两者间关系,如下:
在这里插入图片描述

CREATE TABLE `tb_category_brand` (
  `category_id` bigint(20) NOT NULL COMMENT '商品类目id',
  `brand_id` bigint(20) NOT NULL COMMENT '品牌id',
  PRIMARY KEY (`category_id`,`brand_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品分类和品牌的中间表,两者是多对多关系';

这张表中并没有设置外键约束,似乎与数据库的设计范式不符,为什么这么做?

  • 外键会严重影响数据库读写的效率
  • 数据删除时会比较麻烦

在电商行业,性能是非常重要的,我们宁可在代码中通过逻辑来维护表关系,也不设置外键

代码如下:

    /**
     * 新增品牌
     *
     * @param brand
     * @param cids
     * @return
     */
    @PostMapping
    public ResponseEntity<Void> saveBrand(@RequestBody Brand brand, @RequestParam("cids") List<Long> cids) {
        brandService.saveBrand(brand, cids);
        return ResponseEntity.status(HttpStatus.CREATED).build(); //201,已在服务器上成功创建了一个或多个新资源
        //如果异常自动抛500
    }
    /**
     * 新增品牌
     *
     * @param brand
     * @param cids
     */
    @Transactional
    public void saveBrand(Brand brand, List<Long> cids) {
        brandMapper.insertSelective(brand);
        //再新增中检表
        for (Long cid : cids) {
            //通用mapper操作范围不能超过一张表
            //所以操作中间表需要我们自己写SQL语句
            brandMapper.insertCategoryAndBrand(cid, brand.getId());
        }
    }
    @Insert("insert into tb_category_brand(category_id,brand_id) " +
            "values(#{cid},#{bid})")
    void insertCategoryAndBrand(@Param("cid") Long cid, @Param("bid") Long bid);

测试结果如下:
请添加图片描述
在这里插入图片描述
原因:基于JSON数据格式进行传输,所有请求参数会被封装成一个JSON对象
所以在controller不能使用两个对象去接收它,如下:
在这里插入图片描述
解决:我们让前端不要传JSON了,传普通的字段,4个参数

(四)qs工具

QS是一个第三方库,我们可以用npm install qs --save来安装
不过我们在项目中已经集成了,如下:
在这里插入图片描述
这个工具的名字:QS,即Query String,请求参数字符串
什么是请求参数字符串?例如: name=jack&age=21
QS工具可以便捷的实现 JS的Object与QueryString的转换

在我们的项目中,QS被注入到了Vue的原型对象中,我们可以通过this.$qs来获取这个工具:
在这里插入图片描述
用法如下:
在这里插入图片描述
下面分析qs对象,在控制台中打印,如下:

    created() {
      console.log(this.$qs);
    }

发现其中有3个方法:
在这里插入图片描述

  • parse():把请求参数格式(QueryString)转成JSON格式(Object)
  • stringify():把JSON格式(Object)转成请求参数格式(QueryString)

测试一下,使用浏览器工具,把qs对象保存为一个临时变量temp1,然后调用stringify()方法:
在这里插入图片描述
在这里插入图片描述
注意:此时后端就不能使用@RequestBody去接受JSON数据格式了,要去掉

(五)页面校验

在组件后面指定required :rules,就会开启表单校验,如下:
在这里插入图片描述
其中v代表我们输入框输入的内容,如下:
在这里插入图片描述

(六)新增完成后关闭窗口

我们无论添加成功还是失败,都要关闭窗口,并且同步数据
这需要窗口子组件通知父组件把自己(子组件)关闭掉,代码流程如下:
在这里插入图片描述
在这里插入图片描述

(七)文件上传代码逻辑

文件的上传并不只是在品牌管理中有需求,以后的其它服务也可能需要
因此我们创建一个独立的微服务,专门处理各种上传
在这里插入图片描述
在这里插入图片描述
我们需要EurekaClientweb依赖:

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

覆盖application.yml配置,如下:

server:
  port: 8082
spring:
  application:
    name: upload-service
  servlet:
    multipart:
      max-file-size: 5MB #默认是1MB
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    lease-renewal-interval-in-seconds: 5
    lease-expiration-duration-in-seconds: 15

编写引导类,如下:
在这里插入图片描述

@SpringBootApplication
@EnableDiscoveryClient
public class LeyouUploadApplication {

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

编写controller,如下:
在这里插入图片描述

@Controller
@RequestMapping("upload")
public class UploadController {

    @Autowired
    private UploadService uploadService;
    
    @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);
    }
}

编写service,如下:
在这里插入图片描述

@Service
public class UploadService {

    private static final List<String> CONTENT_TYPES = Arrays.asList("image/jpeg", "image/gif");

    private static final Logger LOGGER = LoggerFactory.getLogger(UploadService.class);

    public String uploadImage(MultipartFile file) {

        String originalFilename = file.getOriginalFilename();
        //校验文件类型
        String contentType = file.getContentType(); //获取mime类型
        if (!CONTENT_TYPES.contains(contentType)) {
            LOGGER.info("文件类型不合法: {}", originalFilename); //{}是占位符,originalFilename会填充占位符
            return null;
        }
        try {
            //校验文件内容
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            if (bufferedImage == null) {
                LOGGER.info("文件内容不合法: {}", originalFilename);
            }
            //保存到文件服务器
            file.transferTo(new File("G:\\image\\" + originalFilename));
            //返回url,进行回显
            return "http://image.leyou.com/" + originalFilename;
        } catch (IOException e) {
            LOGGER.info("服务器内部错误: {}", originalFilename);
            e.printStackTrace();
        }
        return null;
    }
}

配置hosts文件nginx,如下:
127.0.0.1 image.leyou.com

	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 G:\\image;
        }
    }

postman测试结果如下:
在这里插入图片描述

(八)绕过网关访问图片上传并解决跨域问题

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

我们修改nginx配置,将以/api/upload开头的请求拦截下来,转交到真实的服务地址:

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

但是这样写显然是不对的,因为ip和端口虽然对了,但是路径没变,依然是:http://127.0.0.1:8002/api/upload/image,前面多了一个/api
Nginx提供了rewrite指令,用于对地址进行重写,语法规则:rewrite "用来匹配路径的正则" 重写后的路径 [指令];

完整配置如下:

	  	# 上传路径的映射
	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:重写路径结束后,不再重新匹配路径。

      我们这里不能选择last,否则以新的路径/upload/image来匹配,就不会被正确的匹配到8082端口了

修改完成,输入nginx -s reload命令重新加载配置

测试发现存在跨域问题,如下:
在这里插入图片描述
分析:我们之前是在网关使用过滤器统一解决跨域问题,现在不经过网关就存在跨域问题
我们在upload-service中添加一个CorsFilter即可,如下:
在这里插入图片描述
测试结果如下:
在这里插入图片描述
在这里插入图片描述

(九)fastDFS的介绍

先思考一下,现在上传的功能,有没有什么问题?

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

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

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

什么是分布式文件系统

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

通俗来讲:

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

什么是FastDFS

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

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

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

FastDFS的架构

在这里插入图片描述
FastDFS两个主要的角色:Tracker ServerStorage 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组成

上传和下载流程

在这里插入图片描述

  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. 下载文件成功

(十)fastDFS的使用

其实我们一开始就在父工程做了版本管理,之后只需要在子工程引入依赖即可,如下:
在这里插入图片描述
修改application.yml,新增内容如下:

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

修改hosts文件,如下:
在这里插入图片描述
新增一个Java配置类,如下:
在这里插入图片描述

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

编写测试用例,如下:

@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("G:\\dwrg.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("G:\\dwrg.jpg");
        // 上传并且生成缩略图
        StorePath storePath = this.storageClient.uploadImageAndCrtThumbImage(
                new FileInputStream(file), file.length(), "png", null);
        // 带分组的路径
        System.out.println(storePath.getFullPath());
        // 不带分组的路径
        System.out.println(storePath.getPath());
        // 获取缩略图路径
        String path = thumbImageConfig.getThumbImagePath(storePath.getPath());
        System.out.println(path);
    }
}

测试结果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后改造leyou-upload,如下:
在这里插入图片描述

Logo

前往低代码交流专区

更多推荐