当前位置:   article > 正文

java实现大文件分片上传功能(前后端都有,代码down下来配置完后可以直接运行)_java大文件分片上传

java大文件分片上传

问题

项目解决的问题主要是java实现分片上传功能,问题描述:
楼主在公司最近项目中使用multipart文件上传视频文件到服务器上,然后用fastdfs保存到数据库中。发现当上传的视频文件太大的时候会使服务器内存的buf/cache占用很高(好几个G),虽然可以手动清除,但是依旧无法从根源上解决视频上传内存占用太大的问题。

在这里插入图片描述
清理的方式如下,可以看到清理前后buff/cahce有明显变小

sync;echo 1 > /proc/sys/vm/drop_caches # 表示清除pagecache。
sync;echo 2 > /proc/sys/vm/drop_caches # 表示清除回收slab分配器中的对象(包括目录项缓存和inode缓存)。slab分配器是内核中管理内存的一种机制,其中很多缓存数据实现都是用的pagecache。
sync;echo 3 > /proc/sys/vm/drop_caches # 表示清除pagecache和slab分配器中的缓存对象。
  • 1
  • 2
  • 3

在这里插入图片描述


解决问题的思路

lz花了100积分在csdn上面提的问题:急急急,求java上传大文件占用jvm过高的问题解决方案/思路

1、mmf,通过memory mapped file 内存映射文件将数据分段存储到mysql或者其他数据库中,不适合,略
2、服务器上ftp,然后通过代码来借助ftp实现
3、前端通过vue-upload来实现文件的分片上传功能,后端使用fastdfs自带的分片功能实现数据存储(lz的解决方式)
4、使用ffmpeg将视频分成m3u8视频然后本地服务器再实现合成


解决的问题/实现的功能

前端实现的功能:simple-uploader.js(也称 Uploader) 是一个上传库,支持多并发上传,文件夹、拖拽、可暂停继续、秒传、分块上传、出错自动重传、手工重传、进度、剩余时间、上传速度等特性;该上传库依赖 HTML5 File API。

后端实现的功能:springboot 整合 fastDfs,redis实现,.本地路径文件分片上传 2.fastDfs 文件上传,下载,分片上传


项目实现技术 - 先说一下使用到大概的东西,防止大家数据库或者其他原因无法使用,导致浪费时间。 **使用到的技术:simple-uploader(前端) + fastdfs(数据库)+ springboot(项目框架)+Redis**

项目源地址:


实现的效果 -

在这里插入图片描述
在这里插入图片描述


实现原理:

  • 前端使用分片插件后,一个请求会被分成多个请求。多个upload请求均为分片的请求,把大文件分成多个小份一次一次向服务器传递分片完成后,即upload完成后,需要向服务器传递一个merge请求,让服务器将多个分片文件合成一个文件,当我们上传一个大文件时,会被插件分片,ajax请求如下:
    在这里插入图片描述

  • 可以看到发起了多次upload的请求,我们来看看upload发送的具体参数
    在这里插入图片描述

  • 第一个配置(content-disposition)中的guid和第二个配置中的access_token,是我们通过webuploader配置里的formData,即传递给服务器的参数后面几个配置是文件内容,chunkNumber、chunkSize、currentChunkSize等其中totalChunks为总分片数,chunkSize为当前第几个分片。图片中为13。当你看到chunk是130的upload请求时,代表这是最后一个upload请求了

  • 分片后,文件还未整合,数据大概是下面这个样子:
    在这里插入图片描述

  • 后台的验证
    1 在“加入文件”的回调中,通过FileReader读取文件,生成MD5,发给后台
    2.1 如果后台直接返回了“跳过上传”字段和文件的url,则跳过上传,这是秒传;
    2.2 如果后台返回了分片信息,这是断点续传。后台会在每个分片中标识这个分片是否上传过,你需要在分片上传校验的回调中判断,如果true则跳过该分片。
    3 每个分片上传成功,后台都会返回一个字段判断是否需要合并;在“上传完成”的回调中,如果这个字段为true,则需要给后台发一个请求合并的ajax请求

代码分享

楼主前面分享的项目改后的代码

后端核心代码:

api层:

@PostMapping(value = "/fastDfsChunkUpload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Map chunkUpload1(MultipartFileParam multipartFileParam, HttpServletResponse response) {
        Map<String, String> map = new HashMap<>();
        long chunk = multipartFileParam.getChunkNumber();
        long totalChunk = multipartFileParam.getTotalChunks();
        long chunkSize = multipartFileParam.getChunkSize();
        long historyUpload = (chunk - 1) * chunkSize;
        String md5 = multipartFileParam.getIdentifier();
        MultipartFile file = multipartFileParam.getFile();
        String fileName = FileUtil.extName(file.getOriginalFilename());
        StorePath path = null;
        String groundPath;

        try {
            if (chunk == 1) {
                path = appendFileStorageClient.uploadAppenderFile(UpLoadConstant.DEFAULT_GROUP, file.getInputStream(),
                        file.getSize(), fileName);
                if (path == null) {
                    map.put("result", "上传第一个就错了");
                    response.setStatus(500);
                    return map;
                } else {
                    redisUtil.setObject(UpLoadConstant.uploadChunkNum + md5, 1, cacheTime);
                    map.put("result", "上传成功");
                }
                groundPath = path.getPath();
                redisUtil.setObject(UpLoadConstant.fastDfsPath + md5, groundPath, cacheTime);

            } else {
                groundPath = (String) redisUtil.getObject(UpLoadConstant.fastDfsPath + md5);
                appendFileStorageClient.modifyFile(UpLoadConstant.DEFAULT_GROUP, groundPath, file.getInputStream(),
                        file.getSize(), historyUpload);
                Integer chunkNum = (Integer) redisUtil.getObject(UpLoadConstant.uploadChunkNum + md5);
                chunkNum = chunkNum + 1;
                redisUtil.setObject(UpLoadConstant.uploadChunkNum + md5, chunkNum, cacheTime);
            }
            Integer num = (Integer) redisUtil.getObject(UpLoadConstant.uploadChunkNum + md5);
            if (totalChunk == num) {
                response.setStatus(200);
                map.put("result", "上传成功");
                map.put("path", groundPath);
                redisUtil.del(UpLoadConstant.uploadChunkNum + md5);
                redisUtil.del(UpLoadConstant.fastDfsPath + md5);
            }
        } catch (FdfsIOException | SocketTimeoutException e) {
            response.setStatus(407);
            map.put("result", "重新发送");
            return map;
        } catch (Exception e) {
            e.printStackTrace();
            redisUtil.del(UpLoadConstant.uploadChunkNum + md5);
            redisUtil.del(UpLoadConstant.fastDfsPath + md5);
            response.setStatus(500);
            map.put("result", "upload error");
            return map;
        }
        System.out.println("result=" + map.get("result"));
        System.out.println("path=" + map.get("path"));
        return map;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

实体类:MultipartFileParam

package com.dgut.fastdfs.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.web.multipart.MultipartFile;

import java.io.Serializable;

/**
 * @author :CZW
 * @date :Created in 2019/12/15 12:27
 * @description:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class MultipartFileParam implements Serializable {

    private String taskId;//文件传输任务ID
    private long chunkNumber;//当前为第几分片
    private long chunkSize;//每个分块的大小
    private long totalChunks;//分片总数
    private String identifier;//文件唯一标识
    private MultipartFile file;//分块文件传输对象

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

工具类:RedisUtil

package com.dgut.fastdfs.utils;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;


import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    //写入对象
    public boolean setObject(final String key, Object value, Integer expireTime) {
        try {

            redisTemplate.opsForValue().set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    //获取对象
    public Object getObject(final String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }


    //写入集合
    public boolean setList(final String key, Object value, Integer expireTime) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    //获取集合
    public List<Object> getList(final String key) {
        try {
            return redisTemplate.opsForList().range(key, 0, -1);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    //判断时候存在key
    public boolean hasKey(final String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    //删除key
    public void del(final String key) {
        if (hasKey(key)) {
            redisTemplate.delete(key);
        }
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

前端主要需要修改的地方

  • 访问的api
  • 同时上传的分片数量
    在这里插入图片描述
    项目代码已经上传到百度网盘上面去了,可以自己去下载

尾声

lz花了两个小时的时间整理资源,忘大家看到后别忘了点个赞,谢谢


来生还长,切勿惆怅


*******************20210408网友提问补充******************

问题:fastdfs如何配置以及相关依赖

依赖(好像不是很好下载,可能需要去github上下载)

<!--FastDFS-->
        <dependency>
            <groupId>org.csource</groupId>
            <artifactId>fastdfs-client-java</artifactId>
            <version>1.27-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.github.tobato</groupId>
            <artifactId>fastdfs-client</artifactId>
            <version>1.26.2</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

配置文件项目位置及参数:fdfs_client.properties
在这里插入图片描述

connect_timeout = 10
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 80
http.anti_steal_token = no
http.secret_key = FastDFS1234567890
tracker_server = FastDFS_IP:22122
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

加载FastDFS配置文件的工具类

import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.URLDecoder;

/**
 * @author Marsj
 * @description: FastDFS工具类【实现文件上传、下载、删除、查询】
 * @date 2019/3/26
 */

public class FastDFSClientUtil {


    private TrackerClient trackerClient;
    private TrackerServer trackerServer;
    private StorageServer storageServer;
    private StorageClient1 storageClient;

    public FastDFSClientUtil() throws IOException, MyException {
//        FastDFSConfig config = ApplicationContext.getBean(FastDFSConfig.class);
        //使用类加载器方法加载配置文件在linux上打成jar包后会造成无法读取参数,可以使用上面方法或者直接写服务器配置文件路径

        String osName = System.getProperty("os.name");
        if (osName.startsWith("Windows")) {
            // windows
            String conf = this.getClass().getClassLoader().getResource("fdfs_client.properties").getPath();
                String path = URLDecoder.decode(getClass().getProtectionDomain().getCodeSource().getLocation().toString(), "UTF-8");
                path = path.substring(6);
                conf = conf.replace("classpath:", URLDecoder.decode(path, "UTF-8"));
                ClientGlobal.init(conf);

        } else {
            ClientGlobal.init("/data/fdfs_client.properties");
        }



        trackerClient = new TrackerClient();
        trackerServer = trackerClient.getConnection();
        storageServer = null;
        storageClient = new StorageClient1(trackerServer, storageServer);
    }

    /**
     * @param filePath 需要查询的文件路径
     * @return 为null则代表文件不存在,正常返回FileInfo文件信息(包含文件大小,创建时间,上传者的id等等)
     * @author Czw
     * @Description 查询FastDFS中路径文件是否存在
     * @date 2019/5/8 下午 6:58
     */
    public FileInfo query_file_info(String filePath) {
        FileInfo fileInfo = null;
        try {
            fileInfo = storageClient.query_file_info1(filePath);
        } catch (IOException | MyException e) {
            e.printStackTrace();
        }
        return fileInfo;
    }


    /**
     * 上传文件方法
     * <p>Title: uploadFile</p>
     * <p>Description: </p>
     *
     * @param fileName 文件全路径
     * @param extName  文件扩展名,不包含(.)
     * @param metas    文件扩展信息
     */
    public String uploadFile(String fileName, String extName, NameValuePair[] metas) {
        String result = null;
        try {
            result = storageClient.upload_file1(fileName, extName, metas);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 上传文件,传fileName
     *
     * @param fileName 文件的磁盘路径名称 如:D:/image/aaa.jpg
     * @return null为失败
     */
    public String uploadFile(String fileName) {
        return uploadFile(fileName, null, null);
    }

    /**
     * @param fileName 文件的磁盘路径名称 如:D:/image/aaa.jpg
     * @param extName  文件的扩展名 如 txt jpg等
     * @return null为失败
     */
    public String uploadFile(String fileName, String extName) {
        return uploadFile(fileName, extName, null);
    }

    /**
     * 上传文件方法
     * <p>Title: uploadFile</p>
     * <p>Description: </p>
     *
     * @param fileContent 文件的内容,字节数组
     * @param extName     文件扩展名
     * @param metas       文件扩展信息
     */
    public String uploadFile(byte[] fileContent, String extName, NameValuePair[] metas) {
        String result = null;
        try {
            result = storageClient.upload_file1(fileContent, extName, metas);
        } catch (IOException | MyException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 上传文件
     *
     * @param fileContent 文件的字节数组
     * @return null为失败
     */
    public String uploadFile(byte[] fileContent) {
        return uploadFile(fileContent, null, null);
    }

    /**
     * 上传文件
     *
     * @param fileContent 文件的字节数组
     * @param extName     文件的扩展名 如 txt  jpg png 等
     * @return null为失败
     */
    public String uploadFile(byte[] fileContent, String extName) {
        return uploadFile(fileContent, extName, null);
    }

    /**
     * 文件下载到磁盘
     *
     * @param path   图片路径
     * @param output 输出流 中包含要输出到磁盘的路径
     * @return -1失败,0成功
     */
    public int download_file(String path, BufferedOutputStream output) {
        int result = -1;
        try {
            byte[] b = storageClient.download_file1(path);
            try {
                if (b != null) {
                    output.write(b);
                    result = 0;
                }
            } catch (Exception e) {
            } //用户可能取消了下载
            finally {
                if (output != null) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 获取文件数组
     *
     * @param path 文件的路径 如group1/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
     * @return
     */
    public byte[] download_bytes(String path) {
        byte[] b = null;
        try {
            b = storageClient.download_file1(path);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return b;
    }

    /**
     * 删除文件
     *
     * @param group       组名 如:group1
     * @param storagePath 不带组名的路径名称 如:M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
     * @return -1失败,0成功
     */
    public Integer delete_file(String group, String storagePath) {
        int result = -1;
        try {
            result = storageClient.delete_file(group, storagePath);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * @param storagePath 文件的全部路径 如:group1/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
     * @return -1失败,0成功
     */
    public Integer delete_file(String storagePath) {
        int result = -1;
        try {
            result = storageClient.delete_file1(storagePath);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 获取远程服务器文件资源信息
     *
     * @param groupName      文件组名 如:group1
     * @param remoteFileName M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
     */
    public FileInfo getFile(String groupName, String remoteFileName) {
        try {
            return storageClient.get_file_info(groupName, remoteFileName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 获取远程服务器文件资源信息
     *
     * @param remoteFileName /group01/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
     */
    public FileInfo getFile(String remoteFileName) {
        try {
            return storageClient.get_file_info1(remoteFileName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255

20210510网友提问补充**
上传小文件时正常,上传大文件时报错找不到节点,报错点是appendFileStorageClient.modifyFile 处的代码,个人认为是fastdfs的tracker_server 或者tracker_list参数配置错误导致的,目前还在找问题中,及时更新
在这里插入图片描述


************20210522补充******************* 上面提到的 节点找不到的错误经过网友@阿姆斯特狼 差不多一个月的努力总算找到了问题的原因,跟我之前的猜想差不多,确实是fastdfs的参数有问题,需要把store_group 和store lookup的参数配置正确就可以,特别是store lookup参数默认参数是1,指定. 0就是轮训

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/170171
推荐阅读
相关标签
  

闽ICP备14008679号