当前位置: 代码网 > it编程>编程语言>Java > 基于SpringBoot实现大文件分块上传功能

基于SpringBoot实现大文件分块上传功能

2024年09月06日 Java 我要评论
1.分块上传使用场景大文件加速上传:当文件大小超过100mb时,使用分片上传可实现并行上传多个part以加快上传速度。网络环境较差:网络环境较差时,建议使用分片上传。当出现上传失败的时候,您仅需重传失

1.分块上传使用场景

  • 大文件加速上传:当文件大小超过100mb时,使用分片上传可实现并行上传多个part以加快上传速度。

  • 网络环境较差:网络环境较差时,建议使用分片上传。当出现上传失败的时候,您仅需重传失败的part。

  • 文件大小不确定: 可以在需要上传的文件大小还不确定的情况下开始上传,这种场景在视频 监控等行业应用中比较常见。

2.实现原理

实现原理其实很简单,核心就是客户端把大文件按照一定规则进行拆分,比如20mb为一个小块,分解成一个一个的文件块,然后把这些文件块单独上传到服务端,等到所有的文件块都上传完毕之后,客户端再通知服务端进行文件合并的操作,合并完成之后整个任务结束。

3.代码工程

实验目的

实现大文件分块上传

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactid>springboot-demo</artifactid>
        <groupid>com.et</groupid>
        <version>1.0-snapshot</version>
    </parent>
    <modelversion>4.0.0</modelversion>

    <artifactid>file</artifactid>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-web</artifactid>
        </dependency>

        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-autoconfigure</artifactid>
        </dependency>
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-test</artifactid>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupid>org.apache.httpcomponents</groupid>
            <artifactid>httpclient</artifactid>
        </dependency>
        <dependency>
            <groupid>org.apache.httpcomponents</groupid>
            <artifactid>httpmime</artifactid>
        </dependency>
        <dependency>
            <groupid>org.projectlombok</groupid>
            <artifactid>lombok</artifactid>
        </dependency>
        <dependency>
            <groupid>cn.hutool</groupid>
            <artifactid>hutool-core</artifactid>
            <version>5.8.15</version>
        </dependency>
    </dependencies>
</project>

controller

package com.et.controller;

import com.et.bean.chunk;
import com.et.bean.fileinfo;
import com.et.service.chunkservice;

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.core.io.resource;
import org.springframework.http.httpheaders;
import org.springframework.http.responseentity;
import org.springframework.web.bind.annotation.*;

import java.util.list;

@restcontroller
@requestmapping("file")
public class chunkcontroller {
    @autowired
    private chunkservice chunkservice;

    /**
     * upload by part
     *
     * @param chunk
     * @return
     */
    @postmapping(value = "chunk")
    public responseentity<string> chunk(chunk chunk) {
        chunkservice.chunk(chunk);
        return responseentity.ok("file chunk upload success");
    }

    /**
     * merge
     *
     * @param filename
     * @return
     */
    @getmapping(value = "merge")
    public responseentity<void> merge(@requestparam("filename") string filename) {
        chunkservice.merge(filename);
        return responseentity.ok().build();
    }


    /**
     * get filename
     *
     * @return files
     */
    @getmapping("/files")
    public responseentity<list<fileinfo>> list() {
        return responseentity.ok(chunkservice.list());
    }

    /**
     * get single file
     *
     * @param filename
     * @return file
     */
    @getmapping("/files/{filename:.+}")
    public responseentity<resource> getfile(@pathvariable("filename") string filename) {
        return responseentity.ok().header(httpheaders.content_disposition,
                "attachment; filename=\"" + filename + "\"").body(chunkservice.getfile(filename));
    }
}

config

package com.et.config;

import com.et.service.fileclient;
import com.et.service.impl.localfilesystemclient;

import org.springframework.beans.factory.annotation.value;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;

import java.util.hashmap;
import java.util.map;
import java.util.function.supplier;


@configuration
public class fileclientconfig {
    @value("${file.client.type:local-file}")
    private string fileclienttype;

    private static final map<string, supplier<fileclient>> file_client_supply = new hashmap<string, supplier<fileclient>>() {
        {
            put("local-file", localfilesystemclient::new);
           // put("aws-s3", awsfileclient::new);
        }
    };

    /**
     * get client
     *
     * @return 
     */
    @bean
    public fileclient fileclient() {
        return file_client_supply.get(fileclienttype).get();
    }
}

service

package com.et.service;

import com.et.bean.chunk;
import com.et.bean.fileinfo;

import org.springframework.core.io.resource;

import java.util.list;


public interface chunkservice {

    void chunk(chunk chunk);
    void merge(string filename);
    list<fileinfo> list();
    resource getfile(string filename);
}

package com.et.service.impl;

import com.et.bean.chunk;
import com.et.bean.chunkprocess;
import com.et.bean.fileinfo;
import com.et.service.chunkservice;
import com.et.service.fileclient;

import lombok.extern.slf4j.slf4j;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.core.io.resource;
import org.springframework.stereotype.service;

import java.text.simpledateformat;
import java.util.date;
import java.util.list;
import java.util.map;
import java.util.optional;
import java.util.concurrent.concurrenthashmap;
import java.util.concurrent.copyonwritearraylist;
import java.util.concurrent.atomic.atomicboolean;


@service
@slf4j
public class chunkserviceimpl implements chunkservice {
    // process
    private static final map<string, chunkprocess> chunk_process_storage = new concurrenthashmap<>();

    // file list
    private static final list<fileinfo> file_storage = new copyonwritearraylist<>();

    @autowired
    private fileclient fileclient;

    @override
    public void chunk(chunk chunk) {
        string filename = chunk.getfilename();
        boolean match = file_storage.stream().anymatch(fileinfo -> fileinfo.getfilename().equals(filename));
        if (match) {
            throw new runtimeexception("file [ " + filename + " ] already exist");
        }
        chunkprocess chunkprocess;
        string uploadid;
        if (chunk_process_storage.containskey(filename)) {
            chunkprocess = chunk_process_storage.get(filename);
            uploadid = chunkprocess.getuploadid();
            atomicboolean isuploaded = new atomicboolean(false);
            optional.ofnullable(chunkprocess.getchunklist()).ifpresent(chunkpartlist ->
                    isuploaded.set(chunkpartlist.stream().anymatch(chunkpart -> chunkpart.getchunknumber() == chunk.getchunknumber())));
            if (isuploaded.get()) {
                log.info("file【{}】chunk【{}】upload,jump", chunk.getfilename(), chunk.getchunknumber());
                return;
            }
        } else {
            uploadid = fileclient.inittask(filename);
            chunkprocess = new chunkprocess().setfilename(filename).setuploadid(uploadid);
            chunk_process_storage.put(filename, chunkprocess);
        }

        list<chunkprocess.chunkpart> chunklist = chunkprocess.getchunklist();
        string chunkid = fileclient.chunk(chunk, uploadid);
        chunklist.add(new chunkprocess.chunkpart(chunkid, chunk.getchunknumber()));
        chunk_process_storage.put(filename, chunkprocess.setchunklist(chunklist));
    }

    @override
    public void merge(string filename) {
        chunkprocess chunkprocess = chunk_process_storage.get(filename);
        fileclient.merge(chunkprocess);
        simpledateformat simpledateformat = new simpledateformat("yyyy-mm-dd hh:mm:ss");
        string currenttime = simpledateformat.format(new date());
        file_storage.add(new fileinfo().setuploadtime(currenttime).setfilename(filename));
        chunk_process_storage.remove(filename);
    }

    @override
    public list<fileinfo> list() {
        return file_storage;
    }

    @override
    public resource getfile(string filename) {
        return fileclient.getfile(filename);
    }
}

package com.et.service.impl;

import com.et.bean.fileinfo;
import com.et.service.fileuploadservice;
import lombok.extern.slf4j.slf4j;
import org.springframework.beans.factory.annotation.value;
import org.springframework.core.io.filesystemresource;
import org.springframework.core.io.resource;
import org.springframework.stereotype.service;
import org.springframework.util.filecopyutils;
import org.springframework.web.multipart.multipartfile;

import java.io.file;
import java.io.ioexception;
import java.io.inputstream;
import java.io.outputstream;
import java.nio.file.files;
import java.nio.file.paths;
import java.text.simpledateformat;
import java.util.date;
import java.util.list;
import java.util.concurrent.copyonwritearraylist;


@service
@slf4j
public class fileuploadserviceimpl implements fileuploadservice {
    @value("${upload.path:/data/upload/}")
    private string filepath;

    private static final list<fileinfo> file_storage = new copyonwritearraylist<>();

    @override
    public void upload(multipartfile[] files) {
        simpledateformat simpledateformat = new simpledateformat("yyyy-mm-dd hh:mm:ss");
        for (multipartfile file : files) {
            string filename = file.getoriginalfilename();
            boolean match = file_storage.stream().anymatch(fileinfo -> fileinfo.getfilename().equals(filename));
            if (match) {
                throw new runtimeexception("file [ " + filename + " ] already exist");
            }

            string currenttime = simpledateformat.format(new date());
            try (inputstream in = file.getinputstream();
                 outputstream out = files.newoutputstream(paths.get(filepath + filename))) {
                filecopyutils.copy(in, out);
            } catch (ioexception e) {
                log.error("file [{}] upload failed", filename, e);
                throw new runtimeexception(e);
            }
            fileinfo fileinfo = new fileinfo().setfilename(filename).setuploadtime(currenttime);
            file_storage.add(fileinfo);
        }
    }

    @override
    public list<fileinfo> list() {
        return file_storage;
    }

    @override
    public resource getfile(string filename) {
        file_storage.stream()
                .filter(info -> info.getfilename().equals(filename))
                .findfirst()
                .orelsethrow(() -> new runtimeexception("file [ " + filename + " ] not exist"));
        file file = new file(filepath + filename);
        return new filesystemresource(file);
    }
}

以上只是一些关键代码,所有代码请参见下面代码仓库

代码仓库

4.测试

  • 启动sprint boot应用

  • 编写测试类

@test
public void testupload() throws exception {
    string chunkfilefolder = "d:/tmp/";
    java.io.file file = new java.io.file("d:/software/oss-browser-win32-ia32.zip");
    long contentlength = file.length();
    // partsize:20mb
    long partsize = 20 * 1024 * 1024;
    // the last partsize may less  20mb
    long chunkfilenum = (long) math.ceil(contentlength * 1.0 / partsize);
    resttemplate resttemplate = new resttemplate();

    try (randomaccessfile raf_read = new randomaccessfile(file, "r")) {
        // buffer
        byte[] b = new byte[1024];
        for (int i = 1; i <= chunkfilenum; i++) {
            // chunk
            java.io.file chunkfile = new java.io.file(chunkfilefolder + i);
            // write
            try (randomaccessfile raf_write = new randomaccessfile(chunkfile, "rw")) {
                int len;
                while ((len = raf_read.read(b)) != -1) {
                    raf_write.write(b, 0, len);
                    if (chunkfile.length() >= partsize) {
                        break;
                    }
                }
                // upload
                multivaluemap<string, object> body = new linkedmultivaluemap<>();
                body.add("file", new filesystemresource(chunkfile));
                body.add("chunknumber", i);
                body.add("chunksize", partsize);
                body.add("currentchunksize", chunkfile.length());
                body.add("totalsize", contentlength);
                body.add("filename", file.getname());
                body.add("totalchunks", chunkfilenum);
                httpheaders headers = new httpheaders();
                headers.setcontenttype(mediatype.multipart_form_data);
                httpentity<multivaluemap<string, object>> requestentity = new httpentity<>(body, headers);
                string serverurl = "http://localhost:8080/file/chunk";
                responseentity<string> response = resttemplate.postforentity(serverurl, requestentity, string.class);
                system.out.println("response code: " + response.getstatuscode() + " response body: " + response.getbody());
            } finally {
                fileutil.del(chunkfile);
            }
        }
    }
    // merge file
    string mergeurl = "http://localhost:8080/file/merge?filename=" + file.getname();
    responseentity<string> response = resttemplate.getforentity(mergeurl, string.class);
    system.out.println("response code: " + response.getstatuscode() + " response body: " + response.getbody());
}
  • 运行测试类,日志如下

以上就是基于springboot实现大文件分块上传功能的详细内容,更多关于springboot大文件分块上传的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com