一、简介

  我们在工作和生活中经常遇到上传资料或者下载资料,这些资料可能是视频,语音,文件等等,我们统称为文件上传和文件下载,今天我们就基于Spring Boot,实现简单的文件上传下载功能。

二、maven依赖

  本文用到的依赖如下:

pom.mxl

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.alian</groupId>
    <artifactId>file</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>file</name>
    <description>文件上传和下载</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.5.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
            <version>2.5.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.14</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

三、配置

3.1、application.yml

application.yml

server:
  port: 8081
  servlet:
    context-path: /fileServer

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB
  freemarker:
    request-context-attribute: request
    charset: UTF-8
    template-loader-path: classpath:/
    suffix: .ftl
    prefix: /view

app:
  download-path: C:\\myFile\\download\\
  upload-path: C:\\myFile\\upload\\
  file-type-array: .png,.jpg,.jpeg,.txt,.zip,.rar,.pdf,.xml,xls
  max-file-size: 8192

  这个配置文件包含几个配置:

  • tomcat容器配置
  • springboot内部文件上传配置
  • freemarker配置
  • 自定义文件处理配置(app)

3.2、属性配置类

AppProperties.java

package com.alian.file.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(value = "app")
public class AppProperties {

    /**
     * 上传路径
     */
    private String uploadPath = "";

    /**
     * 下载路径
     */
    private String downloadPath = "";

    /**
     * 文件类型
     */
    private String[] fileTypeArray;

    /**
     * 文件大小
     */
    private int maxFileSize;

}

  此配置类不懂的可以参考我另一篇文章:Spring Boot读取配置文件常用方式

四、工具类(非FTP)

4.1、文件上传

FileUploadUtil.java

package com.alian.file.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

@Slf4j
public class FileUploadUtil {

    /**
     * 上传文件
     *
     * @param multiFile      文件
     * @param uploadPath     服务器上要存储文件的路径
     * @param uploadFileName 服务器上要存储的文件的名称
     * @return
     */
    public static boolean uploadToServer(MultipartFile multiFile, String uploadPath, String uploadFileName) {
        //构建文件对象
        File file = new File(uploadPath);
        //文件目录不存在则递归创建目录
        if (!file.exists()) {
            boolean mkdirs = file.mkdirs();
            if (!mkdirs) {
                log.error("创建文件夹异常");
                return false;
            }
        }
        InputStream ins = null;
        FileOutputStream outs = null;
        try {
            //获取文件输入流
            ins = multiFile.getInputStream();
            //构建文件输出流
            outs = new FileOutputStream(uploadPath + uploadFileName);
            int len;
            byte[] bytes = new byte[1024];
            //读取一个bytes的文件内容
            while ((len = ins.read(bytes)) != -1) {
                outs.write(bytes, 0, len);
            }
            outs.close();
            log.info("上传成功:{}", uploadPath + uploadFileName);
            return true;
        } catch (IOException e) {
            log.error("文件上传异常");
            e.printStackTrace();
        } finally {
            try {
                if (outs != null) {
                    outs.close();
                }
                if (ins != null) {
                    ins.close();
                }
            } catch (IOException e) {
                log.error("关闭流异常");
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 新文件上传
     *
     * @param multiFile      文件
     * @param uploadPath     服务器上要存储文件的路径
     * @param uploadFileName 服务器上要存储的文件的名称
     * @return
     */
    public static boolean newUploadToServer(MultipartFile multiFile, String uploadPath, String uploadFileName) {
        //构建文件对象
        File file = new File(uploadPath);
        //文件目录不存在则递归创建目录
        if (!file.exists()) {
            boolean mkdirs = file.mkdirs();
            if (!mkdirs) {
                log.error("创建文件夹异常");
                return false;
            }
        }
        try {
            //获取文件输入流
            InputStream inputStream = multiFile.getInputStream();
            //构建文件输出流
            FileOutputStream outputStream = new FileOutputStream(uploadPath + uploadFileName);
            int copy = FileCopyUtils.copy(inputStream, outputStream);
            log.info("上传成功,文件大小:{}", copy);
            return true;
        } catch (IOException e) {
            log.error("文件上传异常", e);
            e.printStackTrace();
        }
        return false;
    }

}

  文件上传我们这里写了两个方法,一个是原生的,一个是Spring Boot自带工具类org.springframework.util.FileCopyUtils里的,两个方法都可以了解下。

4.2、文件下载

FileDownloadUtil.java

package com.alian.file.utils;

import lombok.extern.slf4j.Slf4j;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

@Slf4j
public class FileDownloadUtil {


    /**
     * 下载文件到服务器
     *
     * @param downloadUrl      要下载的文件的地址
     * @param downloadPath     服务器上存储的文件路径
     * @param downloadFileName 服务器上存储的文件名称
     * @return
     */
    public static boolean downloadToServer(String downloadUrl, String downloadPath, String downloadFileName) {
        FileOutputStream fos = null;
        BufferedInputStream bis = null;
        boolean flag = false;
        try {
            URL url = new URL(downloadUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.connect();
            bis = new BufferedInputStream(connection.getInputStream());
            File file = new File(downloadPath);
            if (!file.exists()) {
                boolean mkdirs = file.mkdirs();
                if (!mkdirs) {
                    log.error("创建文件目录失败");
                    return false;
                }
            }
            String filePathName = downloadPath + File.separator + downloadFileName;
            byte[] buf = new byte[1024];
            int size;
            fos = new FileOutputStream(filePathName);
            while ((size = bis.read(buf)) != -1) {
                fos.write(buf, 0, size);
            }
            flag = true;
            log.info("文件下载成功,文件路径[" + filePathName + "]");
            flag = true;
        } catch (Exception e) {
            log.error("下载文件异常", e);
        } finally {
            try {
                if (bis != null) {
                    bis.close();
                }
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                log.error("关流异常", e);
                e.printStackTrace();
            }
        }
        return flag;
    }

}

五、测试

5.1、远端下载到服务器

  我们这里远端指定是一个地址通过获取到文件的情况,比如一个图片,或者文件,然后通过流处理生成我们要的文件,这里我们写个测试方法即可:

package com.alian.file.service;

import com.alian.file.utils.FileDownloadUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TestService {

    public static void main(String[] args) {
        String downloadUrl = "http://invoice.shenzhentong.com/downInvoice/downpdf/914403007703110594/TG643260802798850048";
        String filePath = "C:\\myFile\\download";
        String fileName = "download.pdf";
        FileDownloadUtil.downloadToServer(downloadUrl, filePath, fileName);
    }
}

执行后,可以看到我们的目录下已经存在一个文件(C:\myFile\download\download.pdf):
在这里插入图片描述

5.2、服务器下载到客户端

  服务器下载到客户端这种情况,一般是浏览器请求服务器进行文件下载,工具类已经实现了,我们写一个接口进行处理:

FileDownloadController.java

package com.alian.file.controller;

import com.alian.file.config.AppProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;

@Slf4j
@RestController
@RequestMapping("/fileDownload")
public class FileDownloadController {

    @Autowired
    private AppProperties appProperties;

    @RequestMapping(value = "/downloadToClient")
    public void downloadToClient(HttpServletRequest request, HttpServletResponse response) {
        String filePath = appProperties.getDownloadPath();
        String fileName = "download.pdf";
        String filePathName = filePath + File.separator + fileName;
        BufferedInputStream bins = null;
        BufferedOutputStream bouts = null;
        try {
        	//同一个窗口下载多次,清除空白流
        	response.reset();
            File file = new File(filePathName);
            if (!file.exists()) {
                log.error("要下载的文件不存在:{}", filePathName);
                return;
            }
            bins = new BufferedInputStream(new FileInputStream(filePathName));
            bouts = new BufferedOutputStream(response.getOutputStream());
            String userAgent = request.getHeader("USER-AGENT").toLowerCase();
            // 如果是火狐浏览器
            if (userAgent.contains("firefox")) {
                fileName = new String(fileName.getBytes(), "ISO8859-1");
            } else {
                fileName = URLEncoder.encode(fileName, "UTF-8");
            }
            //设置发送到客户端的响应的内容类型
            response.setContentType("application/download");
            //指定客户端下载的文件的名称
            response.setHeader("Content-disposition", "attachment;filename=" + fileName);
            int len;
            byte[] bytes = new byte[1024];
            while ((len = bins.read(bytes)) != -1) {
                bouts.write(bytes, 0, len);
            }
            //刷新流
            bouts.flush();
            log.info("下载完成");
        } catch (IOException e) {
            log.error("下载文件异常:{}", e.getMessage());
            e.printStackTrace();
        } finally {
            try {
                if (bouts != null) {
                    bouts.close();
                }
                if (bins != null) {
                    bins.close();
                }
            } catch (IOException e) {
                log.error("关闭流异常", e);
                e.printStackTrace();
            }
        }
    }
}

我们就把我们上一个事例里下载到服务的文件下载到客户端,请求地址:http://localhost:8081/fileServer/fileDownload/downloadToClient我们浏览器里就可以自动下载了

在这里插入图片描述

5.3、浏览器上传到服务端

  文件的具体路径是:resource/view/upload.ftl
upload.ftl

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>文件上传</title>
    <script type="text/javascript" src="http://localhost:8081/fileServer/js/jquery-1.7.2.min.js"></script>
    <script type="text/javascript" src="http://localhost:8081/fileServer/js/ajaxfileupload.js"></script>
    <style>

    </style>
</head>
<body>
<script>
    function upload() {
        var requestUrl= "http://localhost:8081/fileServer/fileUpload/uploadToServer";
        $.ajaxFileUpload({
            url:requestUrl,
            secureuri:true,  //是否启用安全提交
            dataType: 'text',   //数据类型
            fileElementId:'uploadFile', //表示文件域ID
            success: function(data,status){
                alert(status);
                alert(data);
            },
            //提交失败处理函数
            error: function (data,status,e){
                alert(status);
            }
        });
    }
</script>
<div>
    <input type="file" name="uploadFile" id="uploadFile"/>
    <button type="submit" value="" onclick="upload();">上传文件</button>
</div>

</body>
</html>

  我这里简单的写个文件上传的页面,这里使用到了ajaxfileupload.js,大家可以去网上下载到,这里需要注意的是,下面三者要保持一致:

  • input里的id值(id=“uploadFile”)
  • ajax请求里的参数 fileElementId
  • controller接口定义的参数uploadFile

我们再准备一个controller处理请求:

package com.alian.file.controller;

import com.alian.file.config.AppProperties;
import com.alian.file.utils.FileUploadUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Controller
@RequestMapping(value = "/fileUpload")
public class FileUploadController {

    @Autowired
    private AppProperties appProperties;

    @RequestMapping(value = "upload")
    public String index(HttpServletRequest request, HttpServletResponse response) {
        return "/upload";
    }

    @ResponseBody
    @RequestMapping(value = "/uploadToServer")
    public String uploadToServer(HttpServletRequest request, @RequestParam(value = "uploadFile", required = false) MultipartFile multiFile) {
        JSONObject json = new JSONObject();
        try {
            Pair<Boolean, String> pair = checkFile(multiFile);
            if (!pair.getLeft()) {
                json.put("msg", pair.getRight());
                return json.toJSONString();
            }
            boolean b = FileUploadUtil.uploadToServer(multiFile, appProperties.getUploadPath(), multiFile.getOriginalFilename());
            json.put("msg", b ? "上传成功" : "上传失败");
            return json.toJSONString();
        } catch (Exception e) {
            log.error("系统异常e:", e);
            json.put("msg", "上传失败");
            return json.toJSONString();
        }
    }

    public Pair<Boolean, String> checkFile(MultipartFile multiFile) {
        if (multiFile.isEmpty()) {
            return Pair.of(false, "文件为空");
        }
        //获取
        String filename = multiFile.getOriginalFilename();
        String contentType = multiFile.getContentType();
        if (StringUtils.isBlank(filename)) {
            return Pair.of(false, "文件名为空");
        }
        long size = multiFile.getSize();//字节
        log.info("收到的请求文件信息:原生文件名:{},文件类型:{},文件大小:{}", filename, contentType, size);
        //获取文件后缀
        String suffix = filename.substring(filename.lastIndexOf("."));
        //判断配置的文件列表里是否支持该文件类型
        if (!ArrayUtils.contains(appProperties.getFileTypeArray(), suffix)) {
            return Pair.of(false, "不支持该类型文件上传");
        }
        double fileSize = size / 1024.0;//单位kb
        if (fileSize > appProperties.getMaxFileSize()) {
            return Pair.of(false, "文件大小超过限制");
        }
        return Pair.of(true, "验证通过");
    }

}

  你可以对文件大小,类型等进行更严格的限制,我这里是粗略的处理了下,但是需要注意的是,文件上传的大小,在springBoot项目里文件上传的大小是 1M,所以需要你在 application.yml 增加如下配置(文章之前就已经贴出来了),并且要注意的是这里MB是大写,如果你书写时时红色的时候说明就是错误的。

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB

运行结果:
浏览器请求:http://localhost:8081/fileServer/fileUpload/upload

在这里插入图片描述

  在我们的上传目录即可看到我们上传的文件了

在这里插入图片描述
  至此,我们的文件就已经上传到我们的服务器了。

结语

  文件上传的方式有很多,实现方式也很多,本文主要是用原生java的方式来实现,后续我们有时间可以介绍一些其他的类库或者工具来实现。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐