我们只用原生的上传和下载或许满足不了项目的需求,我们我们介绍一种断点续传,为什么要使用断点续传呢我们一下就做个分析:
- 500M以下的用http协议传输(网上一搜大把的)
- 500M以上1G以下的用http协议分块或者ftp协议传输
- 1G以上的只能用ftp协议传输
我们这里只实现断点续传的功能
下面是代码的展示:
sql语句:
create table `upload_file` (
`file_id` varchar(32) not null,
`file_path` varchar(128) not null comment '文件存储路径',
`file_size` varchar(32) not null comment '文件大小',
`file_suffix` varchar(8) not null comment '文件后缀',
`file_name` varchar(32) not null comment '文件名',
`file_md5` varchar(32) not null comment '文件md5值',
`create_time` timestamp default '0000-00-00 00:00:00',
`update_time` timestamp default now() on update now(),
`file_status` int not null comment '文件状态',
primary key (`file_id`)
需要的js文件,md5.js :
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
/*
* Perform a simple self-test to see if the VM is working
*/
function md5_vm_test()
{
return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}
/*
* Calculate the MD5 of an array of little-endian words, and a bit length
*/
function core_md5(x, len)
{
/* append padding */
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return Array(a, b, c, d);
}
/*
* These functions implement the four basic operations the algorithm uses.
*/
function md5_cmn(q, a, b, x, s, t)
{
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}
/*
* Calculate the HMAC-MD5, of a key and some data
*/
function core_hmac_md5(key, data)
{
var bkey = str2binl(key);
if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
return core_md5(opad.concat(hash), 512 + 128);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function bit_rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
/*
* Convert a string to an array of little-endian words
* If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
*/
function str2binl(str)
{
var bin = Array();
var mask = (1 << chrsz) - 1;
for(var i = 0; i < str.length * chrsz; i += chrsz)
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
return bin;
}
/*
* Convert an array of little-endian words to a string
*/
function binl2str(bin)
{
var str = "";
var mask = (1 << chrsz) - 1;
for(var i = 0; i < bin.length * 32; i += chrsz)
str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
return str;
}
/*
* Convert an array of little-endian words to a hex string.
*/
function binl2hex(binarray)
{
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for(var i = 0; i < binarray.length * 4; i++)
{
str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF);
}
return str;
}
/*
* Convert an array of little-endian words to a base-64 string
*/
function binl2b64(binarray)
{
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var str = "";
for(var i = 0; i < binarray.length * 4; i += 3)
{
var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16)
| (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
| ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
for(var j = 0; j < 4; j++)
{
if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
}
}
return str;
}
/**使用 方法 : hex_md5("123456");
* 最好用 用户名(手机号)和密码同时 加密产生密文,这样存在数据库里的相同密码的密文就不一样了
*/
前端采用百度的开源框架webUploader做分片,upload.html:
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="utf-8">
<title>HTML5大文件分片上传示例</title>
<script src="http://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
<script src="/js/md5.js"></script>
<script type="text/javascript">
var i = -1;
var shardSize = 1 * 1024 * 1024; //以1MB为一个分片
var succeed = 0;
var dataBegin; //开始时间
var dataEnd; //结束时间
var action = false;
var page = {
init: function () {
console.log(3);
$("#upload").click(function () {
console.log(4);
dataBegin = new Date();
var file = $("#file")[0].files[0]; //文件对象
isUpload(file);
});
}
};
$(function () {
console.log(1);
page.init();
});
function isUpload (file) {
console.log(5);
//构造一个表单,FormData是HTML5新增的
var form = new FormData();
var r = new FileReader();
r.readAsBinaryString(file);
$(r).load(function(e){
var blob = e.target.result;
var md5 = hex_md5(blob);
form.append("md5", md5);
//Ajax提交
$.ajax({
url: "/file/isUpload",
type: "POST",
data: form,
async: true, //异步
processData: false, //很重要,告诉jquery不要对form进行处理
contentType: false, //很重要,指定为false才能形成正确的Content-Type
success: function(data){
var uuid = data.fileId;
if (data.flag == "0") {
//没有上传过文件
realUpload(file,uuid,md5,data.date);
} else if(data.flag == "1") {
//已经上传部分
realUpload(file,uuid,md5,data.date);
} else if(data.flag == "2") {
//文件已经上传过
alert("文件已经上传过,秒传了!!");
}
},error: function(XMLHttpRequest, textStatus, errorThrown) {
alert("服务器出错!");
}
})
})
};
function realUpload(file, uuid, md5, date) {
name = file.name;
size = file.size;
var shardCount = Math.ceil(size / shardSize); //总片数
if (i > shardCount) {
return;
} else {
if (!action) {
i += 1; //只有在检测分片时,i才去加1; 上传文件时无需加1
}
}
//计算每一片的起始与结束位置
var start = i * shardSize;
var end = Math.min(size, start + shardSize);
//构造一个表单,FormData是HTML5新增的
var form = new FormData();
if (!action) {
form.append("action", "check"); //检测分片是否上传
$("#param").append("<br/>" + "action==check " + " ");
} else {
form.append("action", "upload"); //直接上传分片
form.append("data", file.slice(start, end)); //slice方法用于切出文件的一部分
$("#param").append("<br/>" + "action==upload ");
}
form.append("uuid", uuid);
form.append("md5", md5);
form.append("date", date);
form.append("name", name);
form.append("size", size);
form.append("total", shardCount); //总片数
form.append("index", i+1); //当前是第几片
var index = i+1;
$("#param").append("index==" + index);
//按大小切割文件段
var data = file.slice(start, end);
var r = new FileReader();
r.readAsBinaryString(data);
$(r).load(function (e) {
var bolb = e.target.result;
var partMd5 = hex_md5(bolb);
form.append("partMd5", partMd5);
//Ajax提交
$.ajax({
url: "/file/upload",
type: "POST",
data: form,
async: true, //异步
processData: false, //很重要,告诉jquery不要对form进行处理
contentType: false, //很重要,指定为false才能形成正确的Content-Type
success: function (data) {
var fileuuid = data.fileId;
var flag = data.flag;
if (flag != "2") {
//服务器返回该分片是否上传过
if (flag == "0") {
//未上传,继续上传
action = true;
} else if (flag == "1") {
//已上传
action = false;
++succeed;
$("#output").text(succeed + " / " + shardCount);
}
realUpload(file, uuid, md5, date);
} else {
++succeed;
$("#output").text(succeed + " / " + shardCount);
//服务器返回分片是否上传成功
if (succeed == shardCount) {
dataEnd = new Date();
$("#uuid").append(fileuuid);
$("#useTime").append((dataEnd.getTime() - dataBegin.getTime())/1000);
$("#useTime").append("s")
$("#param").append("<br/>" + "上传成功!");
}
}
}, error: function (XMLHttpRequest, textStatus, errorThrown) {
alert("服务器出错!");
}
});
})
}
</script>
</head>
<body>
<input type="file" id="file" />
<button id="upload">上传</button>
<br/><br/>
<span style="font-size:16px">上传进度:</span><span id="output" style="font-size:16px"></span>
<span id="useTime" style="font-size:16px;margin-left:20px;">上传时间:</span>
<span id="uuid" style="font-size:16px;margin-left:20px;">文件ID:</span>
<br/><br/>
<span id="param" style="font-size:16px">上传过程:</span>
</body>
</html>
实体类,UploadFile.java,此实体类用的Lombok 的data 属性,当然你也可以自己添加getter和setter:
package com.haihua.haihua.entity;
import lombok.Data;
import java.util.Date;
/**
* Create by tianci
* 2019/1/10 14:38
*/
@Data
public class UploadFile {
/* uuid */
private String fileId;
/* 文件路径 */
private String filePath;
/* 文件大小 */
private String fileSize;
/* 文件后缀 */
private String fileSuffix;
/* 文件名字 */
private String fileName;
/* 文件md5 */
private String fileMd5;
/* 文件上传状态 */
private Integer fileStatus;
private Date createTime;
private Date updateTime;
}
实体类二,FileForm .java:
package com.haihua.haihua.entity;
import lombok.Data;
/**
* Create by tianci
* 2019/1/10 16:33
*/
@Data
public class FileForm {
private String md5;
private String uuid;
private String date;
private String name;
private String size;
private String total;
private String index;
private String action;
private String partMd5;
}
下面是Util类,KeyUtil :
package com.haihua.haihua.Utils;
import java.util.Random;
/**
* Create by tianci
* 2019/1/10 14:59
*/
public class KeyUtil {
public static synchronized String genUniqueKey() {
Random random = new Random();
Integer num = random.nextInt(900000) + 100000;
return System.currentTimeMillis() + String.valueOf(num);
}
}
NameUtil.java:
package com.haihua.haihua.Utils;
/**
* Create by tianci
* 2019/1/11 14:53
*/
public class NameUtil {
/**
* Java文件操作 获取文件扩展名
*/
public static String getExtensionName(String filename) {
if ((filename != null) && (filename.length() > 0)) {
int dot = filename.lastIndexOf('.');
if ((dot > -1) && (dot < (filename.length() - 1))) {
return filename.substring(dot + 1);
}
}
return filename.toLowerCase();
}
/**
* Java文件操作 获取不带扩展名的文件名
*/
public static String getFileNameNoEx(String filename) {
if ((filename != null) && (filename.length() > 0)) {
int dot = filename.lastIndexOf('.');
if ((dot > -1) && (dot < (filename.length()))) {
return filename.substring(0, dot);
}
}
return filename.toLowerCase();
}
}
FileMd5Util.java:
package com.haihua.haihua.Utils;
import java.io.File;
import java.io.FileInputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
/**
* Create by tianci
* 2019/1/11 14:36
*/
public class FileMd5Util {
public static String getFileMD5(File file) {
if (!file.exists() || !file.isFile()) {
return null;
}
MessageDigest digest = null;
FileInputStream in = null;
byte buffer[] = new byte[1024];
int len;
try {
digest = MessageDigest.getInstance("MD5");
in = new FileInputStream(file);
while ((len = in.read(buffer, 0, 1024)) != -1) {
digest.update(buffer, 0, len);
}
in.close();
} catch (Exception e) {
e.printStackTrace();
return null;
}
BigInteger bigInt = new BigInteger(1, digest.digest());
return bigInt.toString(16);
}
}
通用工具类Constant.java:
package com.haihua.haihua.common;
import java.io.File;
/**
* Create by tianci
* 2019/1/11 14:22
*/
public class Constant {
public static final String PATH = System.getProperty("user.dir") + File.separator + "file";
}
下面是MapperDao方法,UploadFileRepository.java:
package com.haihua.haihua.mapper;
import com.haihua.haihua.entity.UploadFile;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UploadFileRepository {
UploadFile findByFileMd5(String fileMd5);
void deleteByFileMd5(String fileMd5);
void save (UploadFile uploadFile);
}
接着是xml,
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.haihua.haihua.mapper.UploadFileRepository">
<resultMap id="baseResultMap" type="com.haihua.haihua.entity.UploadFile" >
<id column="file_id" jdbcType="VARCHAR" property="fileId"/>
<result column="file_path" property="filePath" jdbcType="VARCHAR"/>
<result column="file_size" property="fileSize" jdbcType="VARCHAR"/>
<result column="file_suffix" property="fileSuffix" jdbcType="VARCHAR"/>
<result column="file_name" property="fileName" jdbcType="VARCHAR"/>
<result column="file_md5" property="fileMd5" jdbcType="VARCHAR"/>
<result column="file_status" property="fileStatus" jdbcType="INTEGER"/>
<result column="create_time" property="createTime" jdbcType="DATE"/>
<result column="update_time" property="updateTime" jdbcType="DATE"/>
</resultMap>
<select id="findByFileMd5" parameterType="java.lang.String" resultMap="baseResultMap" >
select file_id,file_path,file_size,file_suffix,file_name,file_md5,file_status,create_time,update_time
from upload_file
WHERE file_md5 = #{fileMd5} limit 1
</select>
<delete id="deleteByFileMd5" parameterType="java.lang.String">
DELETE FROM upload_file WHERE file_md5 = #{fileMd5}
</delete>
<insert id="save" parameterType="com.haihua.haihua.entity.UploadFile">
INSERT INTO upload_file
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="fileId != null and fileId != ''" >
file_id,
</if>
<if test="filePath != null and filePath != ''" >
file_path,
</if>
<if test="fileSize != null and fileSize != ''" >
file_size,
</if>
<if test="fileSuffix != null and fileSuffix != ''" >
file_suffix,
</if>
<if test="fileName != null and fileName != ''" >
file_name,
</if>
<if test="fileMd5 != null and fileMd5 != ''" >
file_md5,
</if>
<if test="fileStatus != null and fileStatus != ''" >
file_status,
</if>
create_time,update_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="fileId != null and fileId != ''" >
#{fileId,jdbcType=VARCHAR},
</if>
<if test="filePath != null and filePath != ''" >
#{filePath,jdbcType=VARCHAR},
</if>
<if test="fileSize != null and fileSize != ''" >
#{fileSize,jdbcType=VARCHAR},
</if>
<if test="fileSuffix != null and fileSuffix != ''" >
#{fileSuffix,jdbcType=VARCHAR},
</if>
<if test="fileName != null and fileName != ''" >
#{fileName,jdbcType=VARCHAR},
</if>
<if test="fileMd5 != null and fileMd5 != ''" >
#{fileMd5,jdbcType=VARCHAR},
</if>
<if test="fileStatus != null and fileStatus != ''" >
#{fileStatus,jdbcType=VARCHAR},
</if>
now(),now()
</trim>
</insert>
</mapper>
下面是service,UploadFileService:
package com.haihua.haihua.service;
import com.haihua.haihua.entity.FileForm;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Map;
/**
* Create by tianci
* 2019/1/11 11:23
*/
public interface UploadFileService {
/**
* 通过md5值查找文件对象
* @param md5
* @return
*/
Map<String, Object> findByFileMd5(String md5);
/**
* 上传文件
* @param form 文件表单信息
* @param multipartFile 文件
* @return
*/
Map<String, Object> realUpload(FileForm form, MultipartFile multipartFile) throws IOException, Exception;
}
下面是实现类,UploadFileServiceImpl :
package com.haihua.haihua.service.impl;
import com.haihua.haihua.Utils.FileMd5Util;
import com.haihua.haihua.Utils.KeyUtil;
import com.haihua.haihua.Utils.NameUtil;
import com.haihua.haihua.common.Constant;
import com.haihua.haihua.entity.FileForm;
import com.haihua.haihua.entity.UploadFile;
import com.haihua.haihua.mapper.UploadFileRepository;
import com.haihua.haihua.service.UploadFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* Create by tianci
* 2019/1/11 11:24
*/
@Service
public class UploadFileServiceImpl implements UploadFileService {
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
@Autowired
UploadFileRepository uploadFileRepository;
@Override
public Map<String, Object> findByFileMd5(String md5) {
UploadFile uploadFile = uploadFileRepository.findByFileMd5(md5);
Map<String, Object> map = null;
if (uploadFile == null) {
//没有上传过文件
map = new HashMap<>();
map.put("flag", 0);
map.put("fileId", KeyUtil.genUniqueKey());
map.put("date", simpleDateFormat.format(new Date()));
} else {
//上传过文件,判断文件现在还存在不存在
File file = new File(uploadFile.getFilePath());
if (file.exists()) {
if (uploadFile.getFileStatus() == 1) {
//文件只上传了一部分
map = new HashMap<>();
map.put("flag", 1);
map.put("fileId", uploadFile.getFileId());
map.put("date", simpleDateFormat.format(new Date()));
} else if (uploadFile.getFileStatus() == 2) {
//文件早已上传完整
map = new HashMap<>();
map.put("flag" , 2);
}
} else {
map = new HashMap<>();
map.put("flag", 0);
map.put("fileId", uploadFile.getFileId());
map.put("date", simpleDateFormat.format(new Date()));
}
}
return map;
}
@Override
public Map<String, Object> realUpload(FileForm form, MultipartFile multipartFile) throws Exception {
String action = form.getAction();
String fileId = form.getUuid();
int index = Integer.valueOf(form.getIndex());
String partMd5 = form.getPartMd5();
String md5 = form.getMd5();
int total = Integer.valueOf(form.getTotal());
String fileName = form.getName();
String size = form.getSize();
String suffix = NameUtil.getExtensionName(fileName);
String saveDirectory = Constant.PATH + File.separator + fileId;
String filePath = saveDirectory + File.separator + fileId + "." + suffix;
//验证路径是否存在,不存在则创建目录
File path = new File(saveDirectory);
if (!path.exists()) {
path.mkdirs();
}
//文件分片位置
File file = new File(saveDirectory, fileId + "_" + index);
//根据action不同执行不同操作. check:校验分片是否上传过; upload:直接上传分片
Map<String, Object> map = null;
if ("check".equals(action)) {
String md5Str = FileMd5Util.getFileMD5(file);
if (md5Str != null && md5Str.length() == 31) {
System.out.println("check length =" + partMd5.length() + " md5Str length" + md5Str.length() + " " + partMd5 + " " + md5Str);
md5Str = "0" + md5Str;
}
if (md5Str != null && md5Str.equals(partMd5)) {
//分片已上传过
map = new HashMap<>();
map.put("flag", "1");
map.put("fileId", fileId);
if(index != total)
return map;
} else {
//分片未上传
map = new HashMap<>();
map.put("flag", "0");
map.put("fileId", fileId);
return map;
}
} else if("upload".equals(action)) {
//分片上传过程中出错,有残余时需删除分块后,重新上传
if (file.exists()) {
file.delete();
}
multipartFile.transferTo(new File(saveDirectory, fileId + "_" + index));
map = new HashMap<>();
map.put("flag", "1");
map.put("fileId", fileId);
if(index != total)
return map;
}
if (path.isDirectory()) {
File[] fileArray = path.listFiles();
if (fileArray != null) {
if (fileArray.length == total) {
//分块全部上传完毕,合并
File newFile = new File(saveDirectory, fileId + "." + suffix);
FileOutputStream outputStream = new FileOutputStream(newFile, true);//文件追加写入
byte[] byt = new byte[10 * 1024 * 1024];
int len;
FileInputStream temp = null;//分片文件
for (int i = 0; i < total; i++) {
int j = i + 1;
temp = new FileInputStream(new File(saveDirectory, fileId + "_" + j));
while ((len = temp.read(byt)) != -1) {
outputStream.write(byt, 0, len);
}
}
//关闭流
temp.close();
outputStream.close();
//修改FileRes记录为上传成功
UploadFile uploadFile = new UploadFile();
uploadFile.setFileId(fileId);
uploadFile.setFileStatus(2);
uploadFile.setFileName(fileName);
uploadFile.setFileMd5(md5);
uploadFile.setFileSuffix(suffix);
uploadFile.setFilePath(filePath);
uploadFile.setFileSize(size);
uploadFileRepository.save(uploadFile);
map=new HashMap<>();
map.put("fileId", fileId);
map.put("flag", "2");
return map;
} else if(index == 1) {
//文件第一个分片上传时记录到数据库
UploadFile uploadFile = new UploadFile();
uploadFile.setFileMd5(md5);
String name = NameUtil.getFileNameNoEx(fileName);
if (name.length() > 32) {
name = name.substring(0, 32);
}
uploadFile.setFileName(name);
uploadFile.setFileSuffix(suffix);
uploadFile.setFileId(fileId);
uploadFile.setFilePath(filePath);
uploadFile.setFileSize(size);
uploadFile.setFileStatus(1);
uploadFileRepository.save(uploadFile);
}
}
}
return map;
}
}
下面是控制类,
package com.haihua.haihua.controller;
import com.haihua.haihua.entity.FileForm;
import com.haihua.haihua.service.UploadFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid;
import java.util.Map;
/**
* Create by tianci
* 2019/1/10 15:41
*/
@RestController
@RequestMapping("/file")
public class UploadFileController {
@Autowired
UploadFileService uploadFileService;
@GetMapping("/open")
public ModelAndView open() {
return new ModelAndView("upload");
}
@PostMapping("/isUpload")
public Map<String, Object> isUpload(@Valid FileForm form) {
return uploadFileService.findByFileMd5(form.getMd5());
}
@PostMapping("/upload")
public Map<String, Object> upload(@Valid FileForm form,
@RequestParam(value = "data", required = false)MultipartFile multipartFile) {
Map<String, Object> map = null;
try {
map = uploadFileService.realUpload(form, multipartFile);
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
启动类:
package com.haihua.haihua;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import javax.servlet.MultipartConfigElement;
@SpringBootApplication
@MapperScan("com.haihua.haihua.mapper")
public class HaihuaApplication {
public static void main(String[] args) {
SpringApplication.run(HaihuaApplication.class, args);
}
@Bean
public MultipartConfigElement multipartConfigElement(){
MultipartConfigFactory config = new MultipartConfigFactory();
config.setMaxFileSize("1000MB");
config.setMaxRequestSize("1000MB");
return config.createMultipartConfig();a
}
}
实现效果如下:
上传后:
传输的结果:
最后声明原作者是简书的,云里有条大鲸鱼 :
作者的文章地址是:https://www.jianshu.com/p/aa44eb96c7b6
作者的源码地址是:https://github.com/superealboom/bigfile
非常感谢 云里有条大鲸鱼 的支持,大家看到文章可以去参阅原作者的博客.