文件分片续传及文件类型判断
程序员文章站
2022-03-10 22:02:15
...
写在最前面
为了实现一个文件分片上传方法,找了很久的demo,但是一直没有找到,最后找到了一个plupload插件,可以支持文件分片。但是,第一次接触这个插件,有很多不会用的地方,网上的许多文章毕竟是他们做的,拿来当然会有许多坑,一步一步调试再理解插件的作用,最后成功。这个插件中,有许多功能,但我最主要的是用了插件的分片的功能,文件类型验证全部由后端验证。
关于plupload插件的使用方法
plupload插件的使用方法在csdn、博客园、开源中国论坛都有很多讲的,在这里给一个我觉得介绍挺完整的:https://www.cnblogs.com/2050/p/3913184.html
下面这个是官方文档:http://www.phpin.net/tools/plupload/
一、jsp页面
<body>
<div id="uploader"></div>
<a href="listFile.jsp">下载</a>
<script type="text/javascript">
// Convert divs to queue widgets when the DOM is ready
$(function() {
var uploader = $("#uploader").plupload({
runtimes: 'gears,flash,silverlight,browserplus,html5',
url : 'file/pluploadUpload',
multipart: true,
upload_tmp_dir:"d:\\temp",//设置临时文件位置
init:{
BeforeUpload: function(up, file) {
up.settings.multipart_params = {
filename : file.name
};
},
},
multipart_params: {
'key': '${filename}', // use filename as a key
'Filename': '${filename}' // adding this to keep consistency across the runtimes
},
max_file_size: '800mb', // 文件上传最大限制。
chunk_size: '10mb', // 上传分块每块的大小
// max_retries: 1,
multiple_queues :true,
unique_names: false, // 上传的文件名是否唯一
// 是否生成缩略图(仅对图片文件有效)
resize: {
width: 1000,
crop: false,
preserve_headers: true
},
file_data_name: 'file',
rename: false,//可重命名
sortable: true,//可排序
dragdrop: false,//可拖拽
filters : [//文件类型在此处设置,但我放后台验证了
{title: "*", extensions: "*"}
],
views: {
list: true,
thumbs: true, // Show thumbs
active: 'thumbs'
},
// Flash settings
flash_swf_url : 'js/Moxie.swf',
// Silverlight settings
silverlight_xap_url : 'js/Moxie.xap'
});
});
</script>
</body>
二、FileUpload类
File dir = new File(request.getSession().getServletContext().getRealPath("/") + FileDir+"/"+userId);
if(!dir.exists()){
//可创建多级目录,而mkdir()只能创建一级目录
dir.mkdirs();
}
//开始上传文件
PluploadService.upload(plupload,fileName,dir);
System.out.println("上传成功,路径为:"+dir);
}catch (Exception e){
e.printStackTrace();
}
return "message";
三、PluloadService类
在这个类中我进行了文件类型判断,由于文件是分片上传的,服务器得到的文件是一块一块的,所以很难进行截取完整的文件头。因此,我多加了一个判断,文件是第一块的时候,截取文件头,获取文件类型从而进行判断。如果文件类型不允许,则直接返回。
private static long UPLOAD_MAXSIZE = 800 * 1024 * 1024;
public static void upload(Plupload plupload,String fileName, File pluploadDir) throws ServletException, IOException {
String name = "" + System.currentTimeMillis()+fileName;
uploads(plupload,name, pluploadDir);
}
/**
* 上传方法
* @param plupload 上传实体对象
* @param pluploadDir 上传的文件夹名称
* @param fileName 保存的文件名
*/
private static void uploads(Plupload plupload,String fileName, File pluploadDir) {
boolean flag=false;
System.out.println(plupload);
//用户上传文件被分隔的总块数
int chunks = plupload.getChunks();
//当前块,从0开始
int nowChunk = plupload.getChunk();
System.out.println("当前块:"+nowChunk+"\n");
//这里Request请求类型的强制转换可能出错,配置文件中向SpringIOC容器引入multipartResolver对象即可。
MultipartHttpServletRequest mulRequest = (MultipartHttpServletRequest) plupload.getRequest();
//map中只有一个键值对
MultiValueMap<String, MultipartFile> map = mulRequest.getMultiFileMap();
if (map != null) {
try {
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
List<MultipartFile> multipartFileList = map.get(key);
//循环只进行一次
for (MultipartFile multipartFile : multipartFileList) {
//手动向Plupload对象传入MultipartFile属性值
plupload.setMultipartFile(multipartFile);
//新建目标文件,只有被流写入时才会真正存在
File targetFile = new File(pluploadDir + "/" + fileName);
//当前块为首块时,截取文件头进行判断
if(nowChunk==0){
if (!creatCheckFile(multipartFile, plupload.getRequest(),fileName)){
return;
}
}
//上传资料总块数大于1,要进行合并
if (chunks >1) {
File tempFile = new File(pluploadDir.getPath() + "/" + multipartFile.getName());
//第一块直接从头写入,不用从末端写入
savePluploadFile(multipartFile.getInputStream(), tempFile, nowChunk == 0 ? false : true);
//全部块已经上传完毕,此时targetFile因为有被流写入而存在,要改文件名字
if (chunks - nowChunk == 1) {
flag=true;
tempFile.renameTo(targetFile);
}
}else {
multipartFile.transferTo(targetFile);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
}
}
}
public static boolean creatCheckFile(MultipartFile file, HttpServletRequest request,String fileName) throws ServletException, IOException {
boolean flag = false;
//获取文件名
String message = "";
//判断文件不为空
if (file.getSize() != 0 && !file.isEmpty()) {
flag = true;
//判断文件大小
if (file.getSize() <= UPLOAD_MAXSIZE) {
flag = true;
//文件类型判断
if (CheckFileType.getCheckFile(file,fileName)) {
flag = true;
} else {
flag = false;
System.out.println("文件格式不符合!");
message = "文件格式不符合";
}
} else {
flag = false;
System.out.println("文件大小超出范围!");
message = "文件大小超出范围!";
}
} else {
flag = false;
System.out.println("文件为空!");
message = "文件为空!";
}
return flag;
}
/**
* 保存文件
* @param is 文件输入流
* @param tempFile 临时文件
* @param flag 操作标识
*/
private static void savePluploadFile(InputStream is, File tempFile, boolean flag){
OutputStream os=null;
try{
//从末端写入
if(tempFile.exists()){
os=new BufferedOutputStream(new FileOutputStream(tempFile,true));
}else {//从头写入
os=new BufferedOutputStream(new FileOutputStream(tempFile));
}
byte []bytes=new byte[1024*1024*20];
int len=0;
while ((len=(is.read(bytes)))>0){
os.write(bytes,0,len);
}
os.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try{
os.close();
is.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
四、文件类型判断
截取八位以上文件头,文件头会发生一点改变,截取八位文件头,部分文件的文件头是一样的。在这里截取的是前八位文件头。
/**
* 判断上传的文件是否合法
* 1.检查文件的扩展名
* 2.检查文件的MIME类型
*
* @param file
* @return boolean
*/
public final static boolean getCheckFile(MultipartFile file, String fileName) {
//为真表示符合上传条件,为假表标不符合
boolean upflag = false;
String sname = fileName.substring(fileName.lastIndexOf(".") + 1);
//转换成小写
sname = sname.toLowerCase();
System.out.println("上传文件后缀:" + sname);
//获取上传附件的文件头,判断属于哪种类型,并获取其扩展名
byte[] b = new byte[4];
try {
InputStream is = file.getInputStream();
is.read(b, 0, b.length);
String fileHeader = getFileHeader(b);
System.out.println("上传文件的文件头为:" + fileHeader);
if (FILE_TYPE_MAP.containsKey(fileHeader)) {
upflag = true;
//检查文件扩展名
String va = FILE_TYPE_MAP.get(fileHeader);
System.out.println("文件的扩展名应为:" + va);
if (va.equals(sname)) {
upflag = true;
} else {
//特殊情况校验,有的文件头前八位
if (elseCheck(sname,fileHeader)){
upflag=true;
}
//特殊情况校验,如果用户上传的扩展名为文本文件,则允许上传
if (sname.indexOf("txt") != -1) {
return true;
} else {
return false;
}
}
is.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return upflag;
}
/**
* 获取上传文件类型
*
* @param b
* @return
*/
public final static String getFileHeader(byte[] b) {
String value = getFileHexString(b);
return value;
}
/**
* 读取文件头,将字节数组的前四位转换成十六进制
*
* @param b 读取文件头信息的文件的byte数组
* @return 文件头信息
*/
public final static String getFileHexString(byte[] b) {
StringBuilder builder = new StringBuilder();
if (b == null || b.length <=0) {
return null;
}
for (int i = 0; i < b.length; i++) {
int v = b[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
builder.append(0);
}
builder.append(hv);
}
return builder.toString();
}
private static boolean elseCheck(String suffix,String fileHeader){
boolean check=false;
//扩展名为"wav"与"avi"的前八位文件头相同
if ("wav".equals(suffix)&& "52494646".equals(fileHeader)){
return true;
}
//扩展名为"f4v"与"flv"的文件头相同
if ("f4v".equals(suffix) && "464c5601".equals(fileHeader)){
return true;
}
//扩展名为"rmvb"与"rm"的文件头相同
if ("rm".equals(suffix)&& "2e524d46".equals(fileHeader)){
return true;
}
//扩展名为"asf"与"wmv"的文件头相同
if ("asf".equals(suffix)&&"3026b275".equals(fileHeader)){
return true;
}
//jar与zip只是前八位相同
for (String s:wpsSame) {
if ( suffix.equals(s)&& "504b0304".equals(fileHeader)){
return true;
}
}
for (String s:docSame) {
if ( suffix.equals(s)&&"d0cf11e0".equals(fileHeader)){
return true;
}
}
return check;
}
五、Plupload类
Plupload类名不能改变,除非在plupload源码插件中改变。在这里,没有给出允许文件类型的集合,这个可以自己定义。
public class Plupload {
/**文件原名*/
private String name;
/**用户上传资料被分解总块数*/
private int chunks = -1;
/**当前块数(从0开始计数)*/
private int chunk = -1;
/**HttpServletRequest对象,不会自动赋值,需要手动传入*/
private HttpServletRequest request;
private HttpServletResponse response;
/**保存文件上传信息,不会自动赋值,需要手动传入*/
private MultipartFile multipartFile;
上一篇: 用Java和js判断文件类型
下一篇: Go的几个小技巧