1、pom.xml文件

<?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.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.coffee</groupId>
    <artifactId>file-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>file-demo</name>

    <properties>
        <java.version>1.8</java.version>
        <commons-fileupload.version>1.4</commons-fileupload.version>
        <commons-io.version>2.11.0</commons-io.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>${commons-fileupload.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>${commons-io.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <!-- 启用 -->
            <optional>true</optional>
        </dependency>
    </dependencies>

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

2、application.properties

server.port=8080

spring.thymeleaf.enabled=true
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

spring.servlet.multipart.enabled=true
# Single file max size
spring.servlet.multipart.max-file-size=1024MB
# All files max size
spring.servlet.multipart.max-request-size=2048MB

3、主启动类

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

4、自定义线程池

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Configuration
public class ThreadPoolConfig {

    private static final Integer CPU_NUM = Runtime.getRuntime().availableProcessors();

    @Bean
    public ThreadPoolExecutor customizeThreadPoolExecutor() {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CPU_NUM,
                CPU_NUM * 2,
                5L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(CPU_NUM * 4),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        return threadPoolExecutor;
    }
}

5、controller

import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Encoder;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author lenovo
 */
@Controller
public class FileController {

    private static final String FIREFOX = "Firefox";

    private final ThreadPoolExecutor poolExecutor;

    public FileController(ThreadPoolExecutor threadPoolExecutor) {
        this.poolExecutor = threadPoolExecutor;
    }

    @PostMapping("/fileUpload")
    public ResponseEntity<String> uploadFiles(MultipartFile[] files) throws IOException {
        if (files.length > 0) {
            String parentFilePath = LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);
            // 定义上传文件的存储路径
            String path = "D:" + File.separator + parentFilePath;
            File filePath = new File(path);

            boolean folderExisted = filePath.exists() || filePath.mkdirs();
            if (!folderExisted) {
                throw new IOException("Unable to create path");
            }

            Arrays.asList(files).forEach(file -> poolExecutor.execute(() -> {
                try {
                    file.transferTo(new File(path + File.separator + file.getOriginalFilename()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }));
        } else {
            return new ResponseEntity<>("没有文件可上传", HttpStatus.OK);
        }
        return new ResponseEntity<>("文件上传成功", HttpStatus.OK);
    }

    @GetMapping("/download")
    public void download(@RequestParam("fileName") String fileName, HttpServletRequest request, HttpServletResponse response) {
        try {
            String realPath = ResourceUtils.getURL("classpath:").getPath() + "/static/files";

            //获取文件输入流
            File file = new File(realPath, fileName);
            FileInputStream inputStream = new FileInputStream(file);
            //相应输出流
            ServletOutputStream outputStream = response.getOutputStream();

            // 判断是否是火狐浏览器
            String agent = request.getHeader(HttpHeaders.USER_AGENT);
            if (agent.contains(FIREFOX)) {
                fileName = new BASE64Encoder().encode(fileName.getBytes(StandardCharsets.UTF_8));
            } else {
                fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
            }

            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            // Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
            // attachment表示以附件方式下载   inline表示在线打开
            // filename表示文件的默认名称,因为网络传输只支持URL编码,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
                    "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
            // 告知浏览器文件的大小
            response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(file.length()));

            IOUtils.copy(inputStream, outputStream);
            IOUtils.closeQuietly(inputStream);
            IOUtils.closeQuietly(outputStream);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

6、前端页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <!-- 引入jquery css&js -->
    <script src="https://code.jquery.com/jquery-3.4.1.min.js" crossorigin="anonymous"></script>
    <!-- 引入bootstrap css&js -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
          integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-10 mx-auto">
            <form class="mt-5" method="post" enctype="multipart/form-data">
                <div class="input-group">
                    <div class="custom-file">
                        <input type="file" class="custom-file-input" id="customFile1" name="file">
                        <label class="custom-file-label" for="customFile1" data-browse="选择文件">点击选择...</label>
                    </div>
                    <div class="input-group-append">
                        <button type="button" class="btn btn-outline-secondary reset">重选</button>
                    </div>
                </div>

                <div class="row mt-3">
                    <div class="col-12">
                        <div class="text-center">
                            <button type="submit" id="uploadFileBtn" class="btn btn-outline-secondary">提交</button>
                        </div>
                    </div>
                </div>
            </form>

            <!-- Bootstrap Progress bar -->
            <div class="progress mt-3">
                <div id="progressBar" class="progress-bar progress-bar-success" role="progressbar"
                     aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">0%
                </div>
            </div>
            <!-- Bootstrap Alert -->
            <div id="alertDiv" class="mt-3 alert alert-warning alert-dismissible fade" role="alert">
                <span id="alertMsg"></span>
                <button id="closeButton" type="button" class="close" data-dismiss="alert" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
        </div>
    </div>
</div>

<!-- upload.js -->
<script>
    // 当input标签文件被置换
    $('.custom-file-input').on('change', function () {
        $(this).next('.custom-file-label').html($(this)[0].files[0].name);
        // 恢复提交按钮
        $('button[type=submit]').prop('disabled', false);
    });
    // 重选文件,重选按钮被点击后执行
    $('.reset').click(function () {
        $(this).parent().prev().children('.custom-file-label').html('点击选择...');
        $('.custom-file-input').val('');
        // 恢复提交按钮
        $('button[type=submit]').prop('disabled', false);
    });

    $("#closeButton").click(function (){
        $("#alertDiv1").style.visibility="hidden";
    });

    // 提交按钮被点击后执行
    $("#uploadFileBtn").click(function (e) {

        e.preventDefault();
        // 置灰按钮无效,前台防止双重提交
        $(this).prop('disabled', true);

        // 获取文件
        var file = $('#customFile')[0].files[0];
        var formData = new FormData();
        formData.append("file", file);

        $.ajax({
            url: '/fileUploads',
            type: 'POST',
            data: formData,
            cache: false,
            contentType: false,
            processData: false,
            xhr: function () {
                var xhr = $.ajaxSettings.xhr();
                // 设置onprogress事件控制器
                xhr.upload.onprogress = function (event) {
                    var perc = Math.round((event.loaded / event.total) * 100);
                    $('#progressBar').text(perc + '%');
                    $('#progressBar').css('width', perc + '%');
                };
                return xhr;
            },
            beforeSend: function (xhr) {
                // 提交前重置提示消息为空,并重置进度条
                $('#alertMsg1').text('');
                $('#progressBar').text('');
                $('#progressBar').css('width', '0%');
            }
        })
            .done(function (msg) {
                // 添加提示框显示类
                $('#alertDiv').addClass("show");
                // 设置返回消息
                $('#alertMsg').text(msg);
                // 清空文件
                $('input[type=file]').val('');
                // 恢复提交按钮
                $('button[type=submit]').prop('disabled', false);
            })
            .fail(function (jqXHR) {
                // 添加提示框显示类
                $('#alertDiv').addClass("show");
                // 设置返回消息
                $('#alertMsg').text("发生错误");
            });
        return false;
    });
</script>
</body>
</html>

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐