记录: 百度webuploader 分片文件上传java服务器端(spring mvc)示例的优化

最近项目上用到文件分片上传,于是找到了百度的一个开源前端控件webuploader。 于是尝试使用。

下载下来后,它提供的服务器端示例代码是php版的,那么Java版的呢?

其实,上传文件都是按照rfc1867标注来的, 只是分段上传需要在前端多做点事情。分段上传原理其实就是在前端使用JavaScript对文件进行分割成不同小块,然后每次ajax请求就post一小块,直到全部收到为止。

但是,为了确保后端能判断文件是否完整的收到,需要得知当前是第几块,一共多少块,每个分段的大小是多少(前后端同学约定好吧),webuploader是有把这些参数传给后端, 但文档里没说明参数名称啊,看了其他后端example,并且自己做了实验才知道。

参数:
name 文件名
chunks 一共有多少分片
chunk 当前分片是第几片file 文件对象
重点来了:后端接收到这些参数应该怎么处理? 之前看过一个example ,是把每个分片文件都暂时存起来,命名为 文件名.part_1, 文件名.part_2, 文件名.part_3...文件名part_n, 每次都从1到总分片个数(注:这个值就是chunks的值),遍历这些文件是否存在.如果都存在说明全部上传完成了,则再循环一遍,把所有分段都合并到一个文件里. 这么做虽然是可以,但是如果文件很大, 最后一个分片到达的时候响应可能会很慢,效率低下. 那么应该怎么解决呢?考虑了一会, 联想到,以前使用迅雷之类的工具下载,除下载下来的文件以外,还会有一个额外的文件用来存放下载之类的信息. 受到这个启发, 我决定这么设计: 每当第n个分片到达时(注:n的值其实就是收到的chunk的值), 使用java的随机文件读写类RandomAccessFile, 定位到 n*分片大小(注:每个分片大小跟前端约定好的)的位置

long offset = chunkSize * param.getChunk();//定位到该分片的偏移量accessTmpFile.seek(offset);
然后写入分片内容.
//写入该分片数据accessTmpFile.write(param.getFileItem().get());

同时,往一个配置文件,暂且命名为 文件名.conf 设置长度为chunks的值, 也就是分片个数. 

accessConfFile.setLength(param.getChunks());
然后往第n个位置写入一个Byte.MAX_VALUE, 

accessConfFile.seek(param.getChunk());accessConfFile.write(Byte.MAX_VALUE);
因为写入的单位就是字节,所以我这么操作就相当于在第n个字节里写入全1的状态. 然后检查,从0到chunks开始每一个字节进行与操作, 一旦到第n个字节发现与运算的结果不是全1(Byte.MAX_VALUE)那么就说明这个文件的第n个部分没有传输完成. 如果conf文件0到chunks的位置全部进行与运算的最后结果还是Byte.MAX_VALUE,那么就说明这个文件已经传输完成,该干嘛就干嘛..
//completeList 检查是否全部完成,如果数组里是否全部都是(全部分片都成功上传)byte[] completeList = FileUtils.readFileToByteArray(confFile);byte isComplete = Byte.MAX_VALUE;for (int i = 0; i < completeList.length && isComplete==Byte.MAX_VALUE; i++) {    //与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE    isComplete = (byte)(isComplete & completeList[i]);    System.out.println(prefix + "check part " + i + " complete?:" + completeList[i]);}

if (isComplete == Byte.MAX_VALUE) {    System.out.println(prefix + "upload complete !!");}
其实还有另一种想法是,前端传来该文件的md5码,然后后端每次接收到都算一次md5码,如果一致则说明上传成功,但是效率应该也不够上面的好,于是没实现. 谁有更好的想法可以留言哈.
 

现在可以开始例子:在前端webuploader source的examples/image-upload/upload.js 中可以看到
        // 实例化
        uploader = WebUploader.create({
            pick: {
                id: ‘#filePicker‘,
                label: ‘点击选择图片‘
            },
            formData: {
                uid: 123
            },
            dnd: ‘#dndArea‘,
            paste: ‘#uploader‘,
            swf: ‘../../dist/Uploader.swf‘,
            chunked: false,
            chunkSize: 512 * 1024,
            server: ‘../../server/fileupload.php‘,
            // runtimeOrder: ‘flash‘,

            // accept: {
            //     title: ‘Images‘,
            //     extensions: ‘gif,jpg,jpeg,bmp,png‘,
            //     mimeTypes: ‘image/*‘
            // },

            // 禁掉全局的拖拽功能。这样不会出现图片拖进页面的时候,把图片打开。
            disableGlobalDnd: true,
            fileNumLimit: 300,
            fileSizeLimit: 200 * 1024 * 1024,    // 200 M
            fileSingleSizeLimit: 50 * 1024 * 1024    // 50 M
        });
chunked 被设置为false, 改为true就可以分片上传了。
chunkSize这个后端需要用到,所以前后端需要保持一致。
server改成java后端自己定义的上传文件接口的地址,我这里根据后端例子改成了“http://127.0.0.1:8080/file/test-upload2”

下面是 FileUploadAction.java 示例逻辑都在这里 ,注:有可能因为网络缘故不同分片接收到的时间不一致,并不是顺序的,所以加了个原子计数器方便从标注输出信息看到整个流程.
 
package cn.thomastest.controller;

import cn.thomastest.controller.request.MultipartFileParam;
import cn.thomastest.controller.response.DsResponse;
import cn.thomastest.util.MultipartFileUploadUtil;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.RandomAccessFile;
import java.util.concurrent.atomic.AtomicLong;

/**
 * FileUpload
 *
 * @author ThomasWong
 * @date 2016/7/6 15:15
 */
@Controller
@RequestMapping("/file")
public class FileUploadAction {

    private static AtomicLong counter = new AtomicLong(0L);

    @RequestMapping(method = {RequestMethod.POST}, value = {"test-upload2"})
    @ResponseBody
    public DsResponse uploadv2(HttpServletRequest request, HttpServletResponse resp) throws Exception {

        String prefix = "req_count:" + counter.incrementAndGet() + ":";
        System.out.println(prefix + "start !!!");
        DsResponse response = new DsResponse();
        //使用 工具类解析相关参数,工具类代码见下面
        MultipartFileParam param = MultipartFileUploadUtil.parse(request);
        System.out.println(prefix + "chunks= " + param.getChunks());
        System.out.println(prefix + "chunk= " + param.getChunk());
        System.out.println(prefix + "chunkSize= " + param.getParam().get("chunkSize"));
        //这个必须与前端设定的值一致
        long chunkSize = 512 * 1024;

        if (param.isMultipart()) {

            String finalDirPath = "/data0/uploads/";
            String tempDirPath = finalDirPath + param.getId();
            String tempFileName = param.getFileName() + "_tmp";
            File confFile = new File(tempDirPath, param.getFileName() + ".conf");
            File tmpDir = new File(tempDirPath);
            File tmpFile = new File(tempDirPath, tempFileName);
            if (!tmpDir.exists()) {
                tmpDir.mkdirs();
            }

            RandomAccessFile accessTmpFile = new RandomAccessFile(tmpFile, "rw");
            RandomAccessFile accessConfFile = new RandomAccessFile(confFile, "rw");

            long offset = chunkSize * param.getChunk();
            //定位到该分片的偏移量
            accessTmpFile.seek(offset);
            //写入该分片数据
            accessTmpFile.write(param.getFileItem().get());

            //把该分段标记为 true 表示完成
            System.out.println(prefix + "set part " + param.getChunk() + " complete");
            accessConfFile.setLength(param.getChunks());
            accessConfFile.seek(param.getChunk());
            accessConfFile.write(Byte.MAX_VALUE);

            //completeList 检查是否全部完成,如果数组里是否全部都是(全部分片都成功上传)
            byte[] completeList = FileUtils.readFileToByteArray(confFile);
            byte isComplete = Byte.MAX_VALUE;
            for (int i = 0; i < completeList.length && isComplete==Byte.MAX_VALUE; i++) {
                //与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE
                isComplete = (byte)(isComplete & completeList[i]);
                System.out.println(prefix + "check part " + i + " complete?:" + completeList[i]);
            }

            if (isComplete == Byte.MAX_VALUE) {
                System.out.println(prefix + "upload complete !!");
            }
            accessTmpFile.close();
            accessConfFile.close();
        }
        System.out.println(prefix + "end !!!");
        return response;
    }

}

MultipartFileUploadUtil.java 负责从request对象中取出文件以及相应的参数
package cn.thomastest.util;

import cn.thomastest.controller.request.MultipartFileParam;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.lang.math.NumberUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * Created by ThomasWong on 2016/9/8.
 */
public class MultipartFileUploadUtil {

    /**
     * 在HttpServletRequest中获取分段上传文件请求的信息
     * @param request
     * @return
     */
    public static MultipartFileParam parse(HttpServletRequest request) throws Exception {
        MultipartFileParam param = new MultipartFileParam();

        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        param.setMultipart(isMultipart);
        if(isMultipart){
            FileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            // 得到所有的表单域,它们目前都被当作FileItem
            List<FileItem> fileItems = upload.parseRequest(request);
            for (FileItem fileItem : fileItems) {
                System.out.println("field name has:"+fileItem.getFieldName());
                if (!"file".equals(fileItem.getFieldName())){
                    System.out.println("field val has:"+fileItem.getString());
                }

                if (fileItem.getFieldName().equals("id")) {
                    param.setId(fileItem.getString());
                } else if (fileItem.getFieldName().equals("name")) {
                    param.setFileName(new String(fileItem.getString().getBytes(
                            "ISO-8859-1"), "UTF-8"));
                } else if (fileItem.getFieldName().equals("chunks")) {
                    param.setChunks(NumberUtils.toInt(fileItem.getString()));
                } else if (fileItem.getFieldName().equals("chunk")) {
                    param.setChunk(NumberUtils.toInt(fileItem.getString()));
                } else if (fileItem.getFieldName().equals("file")) {
                    param.setFileItem(fileItem);
                    param.setSize(fileItem.getSize());
                } else{
                    param.getParam().put(fileItem.getFieldName(), fileItem.getString());
                }
            }
        }

        return param;
    }

}
MultipartFileParam.java 上面取出的参数都暂时存在这个类的实例里
package cn.thomastest.controller.request;

import org.apache.commons.fileupload.FileItem;

import java.util.HashMap;

/**
 * 特殊参数. 分段文件上传
 * Created by ThomasWong on 2016/9/8.
 */
public class MultipartFileParam {

    //该请求是否是multipart
    private boolean isMultipart;
    //任务ID
    private String id;
    //总分片数量
    private int chunks;
    //当前为第几块分片
    private int chunk;
    //当前分片大小
    private long size = 0L;
    //文件名
    private String fileName;
    //分片对象
    private FileItem fileItem;
    //请求中附带的自定义参数
    private HashMap<String, String> param = new HashMap<>();

    public boolean isMultipart() {
        return isMultipart;
    }

    public void setMultipart(boolean multipart) {
        isMultipart = multipart;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public int getChunks() {
        return chunks;
    }

    public void setChunks(int chunks) {
        this.chunks = chunks;
    }

    public int getChunk() {
        return chunk;
    }

    public void setChunk(int chunk) {
        this.chunk = chunk;
    }

    public long getSize() {
        return size;
    }

    public void setSize(long size) {
        this.size = size;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public FileItem getFileItem() {
        return fileItem;
    }

    public void setFileItem(FileItem fileItem) {
        this.fileItem = fileItem;
    }

    public HashMap<String, String> getParam() {
        return param;
    }

    public void setParam(HashMap<String, String> param) {
        this.param = param;
    }
}

				
时间: 2024-08-22 08:02:19

记录: 百度webuploader 分片文件上传java服务器端(spring mvc)示例的优化的相关文章

使用s3 java sdk 分片文件上传API 报‘SignatureDoesNotMatch’ 异常的定位及解决方案

import java.io.File; import com.amazonaws.AmazonClientException; import com.amazonaws.auth.profile.ProfileCredentialsProvider; import com.amazonaws.services.s3.transfer.TransferManager; import com.amazonaws.services.s3.transfer.Upload; public class U

断点调试 WebUploader获取文件上传成功路径问题

WebUploader获取文件上传成功路径问题 最近在使用WebUploader的时候,上传文件成功,但是怎么也获取不到返回的图片文件成功路径,上传成功后,返回文件代码如下: uploader.on('uploadSuccess', function (file, response) { var imgurl = response.url; //上传图片的路径 alert(imgurl);}); 之后看一下浏览器调试结果: 可以看到imgurl=undefined,但同时response _ra

前后端分离跨服务器文件上传-Java SpringMVC版

近来工作上不上特别忙,加上对后台java了解一点,所以就抽时间,写了一个java版本的前后端分离的跨服务器文件上传功能,包括前后端代码. 一.Tomcat服务器部分 1.Tomcat服务器 单独复制一份Tomcat,用来作为文件服务器 1.1 xml文件: 需要在该Tomcat的conf目录下的web.xml文件的大概100行添加如下部分: 1.2 server.xml文件: 需要在该Tomcat的conf目录下的server.xml文件做一些端口的修改 1.3 Tomcat下建立文件夹 在该T

IOS 多文件上传 Java web端(后台) 使用List&lt;MultipartFile&gt; 接收出现的问题

先上正确的示例: 主要是设置我们的request的content-type为multipart/form-data NSDictionary *param = @{@"assignee" :self.userId, @"projectName" :itemName.text, @"proceedingName":Name.text, @"content" :content.text, @"urgency"

文件上传(java web)

文件上传: 对表单的要求: * method="post" * enctype="multipart/form-data" * 表单中需要添加文件表单项:<input type="file" name="xxx" /> 对Servlet的要求: * request.getParametere("xxx");这个方法在表单为enctype="multipart/form-data&quo

Selenium的文件上传JAVA脚本

在写文件上传脚本的时候,遇到了很多问题,包括元素定位,以及上传操作,现在总结下来以下几点: 1. 上传的控件定位要准确,必要时要进行等待 WebElement adFileUpload = driver.findElement(By.xpath("//input[@type='file']")); 2. 上传操作 String filePath ="D:\\taxonomy\\DB\\HaierTaxonomy.xlsx"; adFileUpload.sendKey

winform程序压缩文件上传,服务器端asp.net mvc进行接收解压

期间编程没什么难度,唯一可能忽略导致结果失败是asp.net  mvc配置 对于压缩文件大的话,需要配置mvc的最大接收量: <system.web> <httpRuntime maxRequestLength="2147483647" executionTimeout="3600" /> <!--允许上传数据大小-> </system.web> <system.webServer> <!--允许上传

webuploader限制文件上传类型 为一类的文件 或 文件后缀不确定

例如 prt文件,需要上传的类型支持(prt1,prt11,prt.11,prt.44,prt.....) 配置中设置: accept: {        title:'' '',        extensions:'prt.*,prt,prt*,' ,        mimeTypes: '.prt.*,.prt,.prt*'       }, 原文地址:https://www.cnblogs.com/yangyuzhuo/p/10058138.html

记录-阿里云Oss文件上传

public class OssUtil { /** * 上传图片 * @param file * @param request * @return */ public static Map<String,String> uploadImage(MultipartFile file){ Map<String,String> retMp =new HashMap<String,String>(); OSSClient client = new OSSClient(OssC