http文件上传的原理以及Spring中如何处理上传文件

12 篇文章 0 订阅

问题背景:

最近在做人脸相关的接入,很简单的一个demo,需要图片上传功能,那么我们该怎么操作才能将图片从本地上传到服务器呢?

问题本质:

图片的上传(其实算文件上传的一种)本质上就是文件的读写加上文件在http请求中的传输。
文件: 保存在磁盘的一段二进制数字(0或者1)
传输:通过http协议将这段二进制数字传输到服务器端,并在磁盘创建空间,将这段文字写进去。
http协议是否提供给我们这样一个类似的协议呢?肯定是有的。

HTTP协议:

http协议主要有请求报文和相应报文,我们今天重点说下请求报文,相应报文其实差不多。
http请求报文分为三个部分:请求行、请求头、请求体。先来看下这张图:
在这里插入图片描述
我们这里需要重点关注的是 请求头中“Content-Type”属性。这个属性是告诉服务器我们发送的数据是什么根式的内容。查阅资料发现,如果我们需要上传图片的话,需要将Content-Type 设置为:multipart/form-data

SpringMVC中如何上传文件:

我们可以使用 commons-fileupload 来帮助我们更加简洁的实现文件的上传:
maven 的pom.xml中引入相关的文件包:

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.2.1</version>
</dependency>
<dependency>
  <groupId>org.apache.directory.studio</groupId>
  <artifactId>org.apache.commons.pool</artifactId>
  <version>1.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.4</version>
</dependency>

后台相关逻辑:

 @RequestMapping("/uploadFile")
    public void uploadFile(HttpServletRequest request, HttpServletResponse response) {
        ProgressListener progressListener = new ProgressListener() {
            @Override
            public void update(long l, long l1, int i) {

            }
        };
        String savePath = "D:\\faceImage\\";
        File file = new File(savePath);
        if (!file.exists() && file.isDirectory()) {
            System.out.println(savePath + "目录不存在,需要创建");
            file.mkdir();
        }
        try {
            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            upload.setHeaderEncoding("UTF-8");
            if (!ServletFileUpload.isMultipartContent(request)) {
                //
            }
            upload.setProgressListener(progressListener);
            List<FileItem> fileItemList = upload.parseRequest(request);
            fileItemList.forEach(item -> {
                if (item.isFormField()) {
                    //普通的参数对应的读取
                    String name = item.getFieldName();
                    try {
                        String value = item.getString("UTF-8");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                } else {
                    //如果fileupload中是上传文件
                    String fileName = item.getName();
                    if (fileName == null || fileName.trim().equals("")) {
                        return;
                    }
                    if (fileName.contains("\\")) {
                        fileName = fileName.substring(fileName.lastIndexOf("\\" + 1));
                    }
                    try {
                        InputStream inputStream = item.getInputStream();
                        FileOutputStream fileOutputStream = new FileOutputStream(savePath + "\\" + fileName);
                        byte[] buffer = new byte[1024];
                        int len = 0;
                        while ((len = inputStream.read(buffer)) > 0) {
                            fileOutputStream.write(buffer);
                        }
                        inputStream.close();
                        fileOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

前端页面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form th:action="@{/uploadFile}" enctype="multipart/form-data" method="post">
    <label style="float: left">上传文件:</label>
    <input type="file" name="filename" style="height: 40px;padding-top: 10px" class="showFileName">
    <button type="submit" class="btn btn-primary">保存</button>
</form>
</body>
</html>

注意:form表单的 eenctype需要设置multipart/form-data。这个属性表明发送的请求体的内容是多表单元素的。也就是请求体中会有各式各样的数据,比如二进制、表单数据。我们可以F12看下发送的内容

springboot中如何上传文件:

springboot 中有两种上传文件的方式:

  1. 和springmvc 类似,使用commons-fileupload组件上传。
  2. 利用MultipartRequest来处理文件内容。
    我们这里先使用第二种方式来上传文件,首先先对MultipartRequest做一个简单的介绍:

MultipartReques类主要是对文件上传进行的处理,在上传文件时,编码格式为enctype="multipart/form-data"格式,以二进制形式提交数据,提交方式为post方式。

后台代码:

  @RequestMapping("/uploadFile")
    @ResponseBody
    public String test(HttpServletRequest request, HttpServletResponse response) {
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());
        int personId = Integer.valueOf(request.getParameter("personId"));
        //判断 request 是否有文件上传,即多部分请求
        if (multipartResolver.isMultipart(request)) {
            //转换成多部分request
            try {
                MultipartHttpServletRequest params = ((MultipartHttpServletRequest) request);
                List<MultipartFile> files = ((MultipartHttpServletRequest) request).getFiles("file");
                MultipartFile file = null;
                BufferedOutputStream stream = null;
                for (int i = 0; i < files.size(); ++i) {
                    file = files.get(i);
                    if (!file.isEmpty()) {
                        if (file.getSize() > 2000000) {
                            return "上传失败,文件大小不成超过2M";
                        }
                        InputStream inputStream = file.getInputStream();
                        //保存图片
                        ImageUtils.SaveFileFromInputStream(inputStream, "D:\\faceImage\\", file.getOriginalFilename());
                        String base64 = ImageUtils.getImageBinary("D:\\faceImage\\" + file.getOriginalFilename());
                        //注册
                        ReturnBean returnBean = register(personId, base64);
                        if (returnBean.getCode() == 0) {
                            returnBean.setMsg("注册成功");
                        } else {
                            String result = returnBean.getMsg();
                            returnBean.setMsg("注册失败 " + result);
                        }
                        return gson.toJson(returnBean);
                    } else {
                        return "You failed to upload " + i
                                + " because the file was empty.";
                    }
                }
                return "upload successful";
            } catch (Exception e) {
                e.printStackTrace();
                return "上传失败," + e.getMessage();
            }

        }
        return null;
    }

相比较第一种方法,这种方法其实方便不少,不需要引入相关的maven依赖。代码量也相对较少。
2 使用commons-fileupload组件上传:
springboot 中如果使用commons-fileupload上传时,代码不变,pom和之前一样。但是在执行过程会出现一个问题:
在执行到 upload.parseRequest(request);发现获取到 List 为空,也就是没有数据。
这个问题是由于springboot中已经认识到这次的request是一个MultipartRequest,所以在获取到request之后,springboot帮我们做了一个转化,将普通的httpRequest转化成MultipartRequest。
如何解决:
类似这种springboot自动配置的参数,我们都可以在properties自己设置:

spring.servlet.multipart.enabled=false

这样spirngboot 就不会自动帮我们处理enctype="multipart/form-data”格式的请求。

总结:

仔细看下来,文件上传的主要逻辑还是在后端的逻辑处理。

  1. 创建工厂类
  2. 使用核心类,也就是ServletFileUpload文件上传解析器。
  3. 解析request请求,遍历请求体,拿出上传内容和普通表单内容。
  4. 对二进制文件进行读写操作,写入到具体的磁盘位置。

通过这次学习文件上传,我再次发现基础真的很重要,我在写代码的时候,发现最简单的读写流操作都不写,基础对file操作也不会。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值