0
点赞
收藏
分享

微信扫一扫

分片上传和秒传

云竹文斋 2022-02-07 阅读 27

java秒传和分片上传

<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>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

<!--        文件上传的依赖-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>
<!--     redis   -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.78</version>
        </dependency>

package com.demo.zhk.entity;
import lombok.Data;

@Data
public class FileDTO {
    private Integer id;//id
    private String path;//相对路径
    private String name;//文件名称
    private String suffix;//后缀
    private Integer size;//文件大小
    private Integer shardIndex;//已上传分片
    private Integer shardSize;//分片大小
    private Integer shardTotal;//分片总数
    private String fileKey;//文件标识

}


package com.demo.zhk.utils;

import lombok.Data;

@Data
public class Result {

    // 成功状态码
    public static final int SUCCESS_CODE = 200;

    // 请求失败状态码
    public static final int FAIL_CODE = 500;

    // 查无资源状态码
    public static final int NOTF_FOUNT_CODE = 404;

    // 无权访问状态码
    public static final int ACCESS_DINE_CODE = 403;

    /**
     * 状态码
     */
    private int code;

    /**
     * 提示信息
     */
    private String msg;

    /**
     * 数据信息
     */
    private Object data;

    /**
     * 请求成功
     *
     * @return
     */
    public static Result ok() {
	  Result r = new Result();
	  r.setCode(SUCCESS_CODE);
	  r.setMsg("请求成功!");
	  r.setData(null);
	  return r;
    }

    /**
     * 请求失败
     *
     * @return
     */
    public static Result fail() {
	  Result r = new Result();
	  r.setCode(FAIL_CODE);
	  r.setMsg("请求失败!");
	  r.setData(null);
	  return r;
    }

    /**
     * 请求成功,自定义信息
     *
     * @param msg
     * @return
     */
    public static Result ok(String msg) {
	  Result r = new Result();
	  r.setCode(SUCCESS_CODE);
	  r.setMsg(msg);
	  r.setData(null);
	  return r;
    }

    /**
     * 请求失败,自定义信息
     *
     * @param msg
     * @return
     */
    public static Result fail(String msg) {
	  Result r = new Result();
	  r.setCode(FAIL_CODE);
	  r.setMsg(msg);
	  r.setData(null);
	  return r;
    }

    /**
     * 请求成功,自定义信息,自定义数据
     *
     * @param msg
     * @return
     */
    public static Result ok(String msg, Object data) {
	  Result r = new Result();
	  r.setCode(SUCCESS_CODE);
	  r.setMsg(msg);
	  r.setData(data);
	  return r;
    }

    /**
     * 请求失败,自定义信息,自定义数据
     *
     * @param msg
     * @return
     */
    public static Result fail(String msg, Object data) {
	  Result r = new Result();
	  r.setCode(FAIL_CODE);
	  r.setMsg(msg);
	  r.setData(data);
	  return r;
    }
    public Result code(Integer code){
	  this.setCode(code);
	  return this;
    }


    public Result data(Object data){
	  this.setData(data);
	  return this;
    }

    public Result msg(String msg){
	  this.setMsg(msg);
	  return this;
    }
}
package com.demo.zhk.service;

import com.alibaba.fastjson.JSON;
import com.demo.zhk.entity.FileDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;



@Service
@RequiredArgsConstructor
public class FileService {
    private final RedisTemplate redisTemplate;


    //保存文件
    public void save(FileDTO file1) {
        //根据文件标识来查询当前视频是否存在
	  String fileKey = file1.getFileKey();
	  Boolean aBoolean = redisTemplate.hasKey(fileKey);
	  //原本使用mysql  现在修改使用redis
	  if (aBoolean) {
		//存在文件修改文件信息
		redisTemplate.opsForValue().set(fileKey,file1);
	  }
		//没有就添加
	  	redisTemplate.opsForValue().set(fileKey,file1);
    }

    //检查文件
    public FileDTO check(String key){
	  FileDTO fileDTO = JSON.parseObject(redisTemplate.opsForValue().get(key).toString(), FileDTO.class);
	  return fileDTO;
    }


}

package com.demo.zhk.controller;

import com.demo.zhk.entity.FileDTO;
import com.demo.zhk.service.FileService;
import com.demo.zhk.utils.Result;
import io.netty.util.internal.ObjectUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

@Controller
@RequestMapping("/file")
@Slf4j
public class FileController {
    @Autowired
    private FileService fileService;
    @Autowired
    private RedisTemplate redisTemplate;
    public static final String BUSINESS_NAME = "普通分片上传";
    // 设置图片上传路径
    @Value("${file.basepath}")
    private String basePath;
    @RequestMapping("/show")
    public String show(){
	  return "file";
    }
    /**
     * 上传
     * @param file
     * @param suffix
     * @param shardIndex
     * @param shardSize
     * @param shardTotal
     * @param size
     * @param key
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @RequestMapping("/upload")
    @ResponseBody
    public String upload(MultipartFile file,
				 String suffix,
				 Integer shardIndex,
				 Integer shardSize,
				 Integer shardTotal,
				 Integer size,
				 String key) throws IOException, InterruptedException {
        log.info("上传文件开始");
        //文件名称
	  String name = UUID.randomUUID().toString().replaceAll("-", "");
	  //获取文件的扩展名
	  String ext = FilenameUtils.getExtension(file.getOriginalFilename());
	  //设置图片新的名字
	  String fileName = new StringBuffer().append(key).append(".").append(suffix).toString(); // course\6sfSqfOwzmik4A4icMYuUe.mp4
	  //这个是分片的名字
	  String localfileName = new StringBuffer(fileName).append(".").append(shardIndex).toString();// course\6sfSqfOwzmik4A4icMYuUe.mp4.1
	  // 以绝对路径保存重名命后的图片(分片的名字)
	  File targeFile=new File(basePath,localfileName);
	  if(!targeFile.exists()){
		targeFile.mkdirs();
	  }
	  //上传这个图片
	  file.transferTo(targeFile);
	  //持久化这个数据
	  FileDTO file1=new FileDTO();
	  file1.setPath(basePath+localfileName);
	  file1.setSuffix(suffix);
	  file1.setName(name);
	  file1.setSuffix(ext);
	  file1.setSize(size);
	  file1.setShardIndex(shardIndex);
	  file1.setShardSize(shardSize);
	  file1.setShardTotal(shardTotal);
	  file1.setFileKey(key);
	  redisTemplate.opsForValue().set(key,file1);
	  //判断当前是不是最后一个分页 如果不是就继续等待其他分页  合并分页
	  if(shardIndex.equals(shardTotal) ){
		file1.setPath(basePath+fileName);
		this.merge(file1);
	  }
	  return "上传成功";

    }
    @RequestMapping("/check")
    @ResponseBody
    public Result check(String key){
	  FileDTO check = fileService.check(key);
	  //如果这个key存在的话 那么就获取上一个分片去继续上传
	  if(!Objects.isNull(check)){
		return Result.ok("查询成功",check);
	  }
	  return Result.fail("查询失败,可以添加");
    }
    /**
     *合并分页
     */
    private void merge(FileDTO fileDTO) throws FileNotFoundException, InterruptedException {
        log.info("分页合并开始");
	  String path = fileDTO.getPath();//获取路径  没有 1.2 这样的东西
	  //截取视频所在的路径
	  path = path.replace(basePath, "");//key.suffix
	  Integer shardTotal= fileDTO.getShardTotal();//获取总分片数量
	  File newFile = new File(basePath + path);
	  FileOutputStream outputStream = new FileOutputStream(newFile,true); // 文件追加写入
	  FileInputStream fileInputStream = null; //分片文件
	  byte[] byt = new byte[10 * 1024 * 1024];
	  int len;
	  try {
		for (int i = 0; i < shardTotal; i++) {
		    // 读取第i个分片
		    fileInputStream = new FileInputStream(new File(basePath + path + "." + (i + 1))); //  course\6sfSqfOwzmik4A4icMYuUe.mp4.1
		    while ((len = fileInputStream.read(byt)) != -1) {
			  outputStream.write(byt, 0, len);
		    }
		}
	  } catch (IOException e){
		log.error("分片合并异常", e);
	  } finally {
		try {
		    if (fileInputStream != null) {
			  fileInputStream.close();
		    }
		    outputStream.close();
		    log.info("IO流关闭");
		} catch (Exception e) {
		    log.error("IO流关闭", e);
		}
		log.info("分片结束了");
		//告诉java虚拟机去回收垃圾 至于什么时候回收  这个取决于 虚拟机的决定
		System.gc();
		//等待100毫秒 等待垃圾回收去 回收完垃圾
		Thread.sleep(100);
		log.info("删除分片开始");
		for (int i = 0; i < shardTotal; i++) {
		    String filePath = basePath + path + "." + (i + 1);
		    File file = new File(filePath);
		    boolean result = file.delete();
		    log.info("删除{},{}", filePath, result ? "成功" : "失败");
		}
		log.info("删除分片结束");
	  }
    }

}

spring:
  redis:
    host: localhost
    port: 6379
#spring.resources.static-locations=classpath:/static
server.port=8000

#文件上传路径
file.basepath=E:/BaiduNetdiskDownload/

spring.servlet.multipart.max-file-size= 50MB
spring.servlet.multipart.max-request-size= 50MB


# templates文件夹的路径
spring.thymeleaf.prefix=classpath:/templates/
# templates中的所有文件后缀名,如/templates/main.html
spring.thymeleaf.suffix=.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<!--<script src="jquery-3.3.1.min.js"></script>-->
<script type="text/javascript" src="/md5.js"></script>
<script type="text/javascript" src="/tool.js"></script>

<body>
<table border="1px solid red">
    <tr>
        <td>文件1</td>
        <td>
            <input name="file" type="file" id="inputfile"/>
        </td>
    </tr>
    <tr>
        <td></td>
        <td>
            <button onclick="check()">提交</button>
        </td>
    </tr>
</table>
</body>
<script type="text/javascript">
    //上传文件的话  得 单独出来
    function test1(shardIndex) {
        console.log(shardIndex);
        //以formData的方式提交
        var fd = new FormData();
        //获取表单中的file
        var file = $('#inputfile').get(0).files[0];
        //文件分片  以20MB为一分片
        var shardSize = 20 * 1024 * 1024;
        //定义分片索引
        var shardIndex = shardIndex;
        //定义分片的起始位置
        var start = (shardIndex - 1) * shardSize;
        //定义分片结束的位置
        var end = Math.min(file.size, start + shardSize);
        //按大小切割文件段
        var fileShard = file.slice(start, end);
        //分片的大小
        var size = file.size;
        //总片数
        var shardTotal = Math.ceil(size / shardSize);
        //文件的后缀名
        var fileName = file.name;
        var suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length).toLowerCase();
        //把视频的信息存储为一个字符串
        var filedetails = file.name + file.size + file.type + file.lastModifiedDate;
        //使用当前文件的信息用md5加密生成一个key 这个加密是根据文件的信息来加密的  如果相同的文件 加的密还是一样的
        var key = hex_md5(filedetails);
        var key10 = parseInt(key, 16);
        //把加密的信息 转为一个64位的
        var key62 = Tool._10to62(key10);
        //前面的参数必须和controller层定义的一样
        fd.append('file', fileShard);
        fd.append('suffix', suffix);
        fd.append('shardIndex', shardIndex);
        fd.append('shardSize', shardSize);
        fd.append('shardTotal', shardTotal);
        fd.append('size', size);
        fd.append("key", key62)
        $.ajax({
            url: "/file/upload",
            type: "post",
            cache: false,
            data: fd,
            processData: false,//很重要,告诉jquery不要对form进行处理
            contentType: false,//很重要,指定为false才能形成正确的Content-Type
            success: function (data) {
                //这里应该是一个递归调用
                if (shardIndex < shardTotal) {
                    var index = shardIndex + 1;
                    test1(index);
                } else {
                    alert(data)
                }
            },
            error: function () {
                //请求出错处理
            }
        })
        //发送ajax请求把参数传递到后台里面
    }

    //判断这个加密文件存在不存在
    function check() {
        var file = $('#inputfile').get(0).files[0];
        //把视频的信息存储为一个字符串
        var filedetails = file.name + file.size + file.type + file.lastModifiedDate;
        //使用当前文件的信息用md5加密生成一个key 这个加密是根据文件的信息来加密的  如果相同的文件 加的密还是一样的
        var key = hex_md5(filedetails);
        var key10 = parseInt(key, 16);
        //把加密的信息 转为一个64位的
        var key62 = Tool._10to62(key10);
        //检查这个key存在不存在
        $.ajax({
            url: "/file/check",
            type: "post",
            data: {'key': key62},
            success: function (data) {
                console.log(data);
                if (data.code == 500) {
                    //这个方法必须抽离出来
                    test1(1);
                } else {
                    if (data.data.shardIndex == data.data.shardTotal) {
                        alert("极速上传成功");
                    } else {
                        //找到这个是第几片 去重新上传
                        test1(parseInt(data.data.shardIndex));
                    }
                }
            }
        })
    }
</script>
</html>

本人比较懒没测,原因就是看别人csdn代码发现少了js,懒得去改html,可以自己模仿数据列如文件文件key不用MD5就随机一个key,其他看样子改就行,道理就是大概意思中的。

举报

相关推荐

0 条评论