文件上传接收处理,以及缩略图生成,辅助类UploadUtil的基本使用

1.需求场景

文件上传是比较常用的功能,一般都是通过表单的file控件,以post方式提交到服务端。在服务端收到数据后,进行存储。

表单需要设置为multipart/form-data属性。如果是通过JS动态创建表单,则需要追加文件对象到表单中。

web前端配置正确后,在服务端如何方便的处理?在此进行一些简单说明。

2.使用说明

框架封装了UploadUtil通用类,专用于处理文件上传相关操作。如果只需要处理文件上传,那么只需要调用saveUploadFile方法即可。同时,UploadUtil类还提供一些额外方法,对文件名和路径进行处理。

2.1.接收文件上传

web客户端通过表单post过来文件数据,servlet收到数据后,调用saveUploadFile方法接收。即可自动完成存储与路径返回。

样例代码如下:saveUploadFile方法接收3个参数:

  1. request对象。当前servlet的request对象,会从servlet中取文件流数据。
  2. 允许上传的文件扩展名列表,为空则不限制上传文件类型。样例格式:jpg#jpeg#png#bmp#gif#mp4#avi#mp3
  3. 允许上传的文件大小(单位字节kb)。

下图的样例,是上传图片、视频、音频文件,最大允许上传100KB。

当接收文件成功后,存储在服务端的文件路径,通过noCodeResult.getData()进行获取。返回的文件路径样例为:UploadFiles/Pics/202002/项目线路图(2)_S.png

如果上传失败,则通过 noCodeResult.getInfo()获取错误提示信息。

//上传图片和文件
private void uploadFile(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	//处理上传。可上传多个文件,每个文件都会放到相应的文件类型文件夹中。每个文件遇到重名,都会自动更名。
	//最终如果是多个文件,则多个文件的路径会组合在一起,存放到data数据字段中,中间用英文逗号隔开。
	NoCodeResult noCodeResult = UploadUtil.saveUploadFile(request, "jpg#jpeg#png#bmp#gif#mp4#avi#mp3", 100*1024);
	//返回上传结果
	if (noCodeResult.isSuccess()) {
		ExceptionUtil.printlnSuccess(response, "上传成功", noCodeResult.getData());
	} else {
		ExceptionUtil.printlnFailure(response, noCodeResult.getInfo());
	}
}

框架的文件接收与保存,会默认处理很多工作,说明如下:

  • 图片类型文件上传后,会默认生成缩略图。
  • 缩略图的文件名与原图文件名一致,会在后面追加“_S”。比如原文件名为:风景图.jpeg,那么缩略图文件名为:风景图_S.jpeg。在列表页和详情页,会展示缩略图。点击缩略图后,会链接到原图。这样在列表页就可以快速加载图片。
  • 缩略图的尺寸,为原图等比例缩放。最宽400像素,最高400像素。宽和高哪个最先达到400像素,就以哪个为准。
  • 文件上传到服务器后,不会自动更名。慎重考虑,因为很多附件,在上传之前,就是正规的名称。特别是一些文件、资料。变更名字后,都是一些无意义的标识,当下载之后,还需要重新改名,比较麻烦。
  • 重名处理。由于上传文件不会自动更名,所以多次上传同一个文件时,就会出现重名问题。为了安全起见,不采用覆盖现有文件的方式,而是对新上传的文件重新命名。重命名的方式,采用Windows系统重名方式,自动在原文件名的后面加上数字后缀。比如文件名为:新建图片.jpg。如果重名,则更名为:新建图片(2).jpg;如果还重名,则更名为:新建图片(3).jpg;直到最后不重名。
  • 文件名中会去掉英文逗号。由于多文件上传时,会将多个文件的路径统一保存在一个字段里,中间使用英文逗号隔开。所以虽然文件不会更名,但是如果文件名中有英文逗号,则会被过滤掉。

2.2.多文件上传

如果web前端是允许上传多个文件,如下代码所示,设置file控件有multiple属性。当选择多个文件上传时,服务端会收到多个文件。

<input type="file" name="file" multiple="multiple" />

如果是多个文件上传,同样调用saveUploadFile方法即可完成接收处理。即saveUploadFile方法支持接收单个和多个文件上传。

当多个文件都成功保存后,所有文件的路径会组合为一整个路径字符串,中间用英文逗号隔开。

比如:UploadFiles/Pics/202002/项目线路图(2)_S.png,UploadFiles/Pics/202002/(201565185353)工作流并行会审_S.png,UploadFiles/Pics/202002/IMG_20181019_151336_S.png

如果中途有文件上传失败,则会跳过,继续处理下一个。比如中途有的文件大小超标,有的文件类型不允许。选择了5个文件上传,可能只成功上传4个。如果成功上传4个,则会返回4个文件的路径组合。

3.源码解析

为了方便深入了解功能原理,把源码贴出来方便大家参考和理解。

3.1.通用类UploadUtil源码

3.1.1.依赖引入与变量定义

定义的UploadUtil通用类,引入了Thumbnails进行缩略图处理,以及apache.commons.fileupload进行文件上传处理。


import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import tech.qidian.dev.admincommon.entity.NoCodeResult;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

public class UploadUtil {

	//定义文件类型
	public static final int FILE_TYPE_UNKNOWN = 0;    //未知文件
	public static final int FILE_TYPE_FILE = 1;    //普通文件
	public static final int FILE_TYPE_IMAGE = 2;    //图片
	public static final int FILE_TYPE_VIDEO = 3;    //视频
	public static final int FILE_TYPE_AUDIO = 4;    //音频
	public static final int FILE_TYPE_EXCEL = 5;    //Excel文件
}

3.1.2.saveUploadFile:保存上传文件

接收servlet数据,从servlet中直接取文件流,进行处理。所以要注意,提交文件上传,最好是一个单独的表单,不要再混合提交其他的表单字段数据进来。因为流只允许接收一次,如果中途拦截取了参数数据,可能导致取不到文件流。

	//上传多个文件。每个文件都根据类型存放到相应的文件夹中。最终将所有文件组合为一个路径列表,以英文逗号隔开。
	public static NoCodeResult saveUploadFile(HttpServletRequest request, String allowFileTypes, long allowFileSize) {
		NoCodeResult noCodeResult = new NoCodeResult();
		noCodeResult.setFailureInfo("上传失败,未知原因");

		//根目录绝对路径
		String rootRealDir = request.getServletContext().getRealPath("/");

		//region 初始化存储临时目录
		String tempPath = request.getServletContext().getRealPath("/WEB-INF/Temp");
		if (!createFileDir(tempPath)) {
			noCodeResult.setFailureInfo("临时目录创建失败");
			return noCodeResult;
		}
		//endregion

		String fileName, filePath, fileThumbPath;
		try {
			//region 基本设置与对象创建
			//1、创建一个DiskFileItemFactory工厂
			DiskFileItemFactory factory = new DiskFileItemFactory();
			//设置工厂的缓冲区的大小,当上传的文件大小超过缓冲区的大小时,就会生成一个临时文件存放到指定的临时目录当中。
			factory.setSizeThreshold(1024 * 100);//设置缓冲区的大小为100KB,如果不指定,那么缓冲区的大小默认是10KB

			//设置上传时生成的临时文件的保存目录
			factory.setRepository(new File(tempPath));

			//2、创建一个文件上传解析器
			ServletFileUpload upload = new ServletFileUpload(factory);

			//3、判断提交上来的数据是否是上传表单的数据
			if (!ServletFileUpload.isMultipartContent(request)) {
				noCodeResult.setFailureInfo("提交的非文件数据。请以表单form的方式提交上传。");
				return noCodeResult;
			}

			//设置上传单个文件的大小的最大值。根据设定设置,最大4G。
			upload.setFileSizeMax(allowFileSize);
			//设置上传文件总量的最大值,最大值=同时上传的多个文件的大小的最大值的和,最大为4G
			upload.setSizeMax(4L * 1024 * 1024 * 1024);
			//4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项
			List<FileItem> list = upload.parseRequest(request);
			//endregion

			//region 循环处理上传文件
			//文件数量索引,以及上传成功的数量索引
			int index = 0, successIndex = 0;
			StringBuilder sbFilesPath = new StringBuilder();
			StringBuilder sbInfo = new StringBuilder();
			for (FileItem item : list) {
				//如果fileitem中封装的是上传文件
				if (!item.isFormField()) {
					//索引数量加1
					index++;

					//region 处理文件夹、文件名、文件路径
					//得到上传的文件名称
					fileName = item.getName();
					//注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如:  c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
					//处理获取到的上传文件的文件名的路径部分,只保留文件名部分
					fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
					if (StringUtil.isEmpty(fileName)) {
						item.delete();
						sbInfo.append("第").append(index).append("个文件名获取失败");
						continue;
					}

					//获取文件上传存储文件夹。图片、音频、视频、文件,分别存储到不同的文件夹。
					String uploadDirAbsolute = createUploadFileDir(rootRealDir, fileName);
					//初始化文件夹
					if (StringUtil.isEmpty(uploadDirAbsolute)) {
						sbInfo.append("第").append(index).append("个存储文件夹创建失败");
						continue;
					}


					//得到上传文件的扩展名
					String fileExtName = getFileExtName(fileName);
					//如果扩展名不包含在内,则不允许上传。
					if (!allowFileType(allowFileTypes, fileExtName)) {
						item.delete();
						sbInfo.append(fileExtName).append("文件类型不允许上传");
						continue;
					}

					//判断文件是否重名。如果重名会更名,返回不重名的新名称。去掉英文逗号。
					fileName = getFileNameByCheckExist(uploadDirAbsolute, fileName);
					if (fileName == null) {
						item.delete();
						sbInfo.append("第").append(index).append("个文件名称处理失败");
						continue;
					}
					//endregion

					//region 处理文件写入服务器上
					//获取文件路径:文件夹+文件名
					filePath = combineFilePath(uploadDirAbsolute, fileName);

					//获取item中的上传文件的输入流
					InputStream in = item.getInputStream();
					//创建一个文件输出流
					FileOutputStream out = new FileOutputStream(filePath);
					//创建一个缓冲区
					byte[] buffer = new byte[4096];
					//判断输入流中的数据是否已经读完的标识
					int len;
					//循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据
					while ((len = in.read(buffer)) > 0) {
						//使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
						out.write(buffer, 0, len);
					}
					//关闭输入流
					in.close();
					//关闭输出流
					out.close();
					//删除处理文件上传时生成的临时文件
					item.delete();
					//endregion

					//判断文件类型。如果是图片,则生成缩略图;如果是视频,则提取缩略图
					switch (getFileTypeByName(filePath)) {
						case UploadUtil.FILE_TYPE_IMAGE:    //生成缩略图
							filePath = createThumbnailPic(filePath);
							if (StringUtil.isEmpty(filePath)) {
								sbInfo.append("第").append(index).append("个缩略图生成失败");
								continue;
							}
							break;
					}

					//设置上传成功后的文件路径。将绝对路径换成相对路径。
					sbFilesPath.append(filePath.substring(rootRealDir.length()));
					sbFilesPath.append(",");

					//成功上传,数量加1
					successIndex++;
				}

			}
			//endregion

			//如果成功上传文件为0个,则返回失败
			if (successIndex == 0) {
				noCodeResult.setFailureInfo(sbInfo.toString());
			} else {
				noCodeResult.setSuccessInfo("成功上传" + index + "个文件");
			}

			//将所有文件路径返回。删除最后面的逗号
			StringUtil.deleteLastString(sbFilesPath, ",");
			noCodeResult.setData(sbFilesPath.toString());

		} catch (FileUploadBase.FileSizeLimitExceededException e) {
			noCodeResult.setFailureInfo("文件大小超出限制:" + StringUtil.getFileSizeString(allowFileSize));
			ExceptionUtil.insertDB(e, noCodeResult.getInfo());
			return noCodeResult;
		} catch (FileUploadBase.SizeLimitExceededException e) {
			noCodeResult.setFailureInfo("文件总大小超出限制:4GB");
			ExceptionUtil.insertDB(e, noCodeResult.getInfo());
			return noCodeResult;
		} catch (Exception e) {
			noCodeResult.setFailureInfo("上传解析异常");
			ExceptionUtil.insertDB(e, noCodeResult.getInfo());
			return noCodeResult;
		}

		return noCodeResult;
	}

3.1.3.createThumbnailPic:生成缩略图

目前采用的方法,是调用Thumbnails进行等比例缩略图生成。缩略图的最大高宽都是400像素。

public static String createThumbnailPic(String fileRealPath) throws IOException {
	if (StringUtil.isEmpty(fileRealPath)) {
		return "";
	}

	String fileThumbnail = getThumbPathFromOriginal(fileRealPath);
	Thumbnails.of(fileRealPath).size(400, 400).toFile(fileThumbnail);

	return fileThumbnail;
}

3.1.4.createUploadFileDir:创建存储文件夹

文件会按照日期、类型进行分门别类的存储。这些文件夹都会自动创建,避免人为的遗忘创建,导致文件写入失败。

目前已分类的有:图片、视频、音频、Excel、文件、其他。

//生成文件上传夹,返回绝对路径。格式:D:\wwwroot\test\UploadFiles\Pics\202002。中间会根据文件类型,放到各个类型文件夹中
	public static String createUploadFileDir(String dirRoot, String fileName) {
		String dirUploadFiles = combineFilePath(dirRoot, "UploadFiles");
		//“UploadFiles”文件夹
		if (!createFileDir(dirUploadFiles)) {
			return null;
		}

		//创建图片、文件、Excel文件夹
		int uploadType = getFileTypeByName(fileName);
		String dirUploadType;
		switch (uploadType) {
			case FILE_TYPE_FILE:
				dirUploadType = "Files";
				break;
			case FILE_TYPE_IMAGE:
				dirUploadType = "Pics";
				break;
			case FILE_TYPE_VIDEO:
				dirUploadType = "Video";
				break;
			case FILE_TYPE_AUDIO:
				dirUploadType = "Audio";
				break;
			case FILE_TYPE_EXCEL:
				dirUploadType = "Excels";
				break;
			case FILE_TYPE_UNKNOWN:
			default:
				dirUploadType = "Others";
				break;
		}

		dirUploadType = combineFilePath(dirUploadFiles, dirUploadType);
		if (!createFileDir(dirUploadType)) {
			return null;
		}

		//创建月份文件夹
		String dirDate = new SimpleDateFormat("yyyyMM").format(new Date());
		dirDate = combineFilePath(dirUploadType, dirDate);
		if (!createFileDir(dirDate)) {
			return null;
		}

		return dirDate;
	}

	//创建文件夹。如果不存在,就创建;存在,就跳过。
	public static boolean createFileDir(String dirPath) {
		File fileDir = new File(dirPath);
		//如果存在,直接返回
		if (fileDir.exists() && fileDir.isDirectory()) {
			return true;
		}
		return fileDir.mkdir();
	}

3.1.5.getFileNameByCheckExist:获取可用的文件名

在服务器验证文件是否已存在。如果已存在,则进行重名;不存在,则写入。不会采用覆盖现有文件的方式。最终获取一个可用的文件名。

	//生成文件名。如果重名,在文件名后面追加“(1)、(2)...”直到不重名。该功能重点是判断是否重名。
	public static String getFileNameByCheckExist(String dirRealPath, String fileName) {
		if (StringUtil.isEmpty(dirRealPath) || StringUtil.isEmpty(fileName)) {
			return null;
		}

		//最终文件名。替换文件名中的英文逗号,因为在多文件上传中会以英文逗号隔开。
		String fileNameNew = fileName.replace(",", "");

		//判断是否重名
		File file = new File(dirRealPath, fileNameNew);
		int index = 2;
		while (file.exists()) {
			fileNameNew = getFileNameNoExt(fileName) + "(" + index + ")." + getFileExtName(fileName);
			file = new File(dirRealPath, fileNameNew);
			index++;
		}

		return fileNameNew;
	}

3.1.6.combineFilePath:合并路径

如果在一个现有的文件夹路径上,追加一个文件名,变成文件路径,就需要合并路径。如果仅靠手动的去处理路径字符串,既要考虑正斜杠”/“和反效果”\“的问题,还要考虑前后路径的首尾是否有斜杠的问题。处理起来比较麻烦,所以这里借用系统自带的File类来处理。

//合并路径。传入父路径和子路径,合并为一个完整路径
	public static String combineFilePath(String parentPath, String childPath) {
		File fileDir = new File(parentPath, childPath);
		return fileDir.getPath();
	}

3.1.7.getThumbPathFromOriginal:根据原图路径获取缩略图路径

缩略图文件名会在原文件名后面追加”_S“,其他部分与原文件名路径保持一致。

	//根据文件路径,生成缩略图路径
	public static String getThumbPathFromOriginal(String orginalPath) {
		if (StringUtil.isEmpty(orginalPath)) {
			return "";
		}

		return getFileNameNoExt(orginalPath) + "_S." + getFileExtName(orginalPath);
	}

3.1.8.getOriginalPathFromThumbPath:根据缩略图路径获取原图路径

去掉缩略图文件名中的”_S“,返回原文件名称。传入文件路径也行。

	//根据缩略图路径,获取原图路径
	public static String getOriginalPathFromThumbPath(String thumbPath) {
		if (StringUtil.isEmpty(thumbPath)) {
			return "";
		}

		//获取扩展名,
		String fileNameNoExt = getFileNameNoExt(thumbPath);

		//去除"_S"结尾
		fileNameNoExt = StringUtil.deleteLastString(fileNameNoExt, "_S");
		//小写"_s"结尾
		fileNameNoExt = StringUtil.deleteLastString(fileNameNoExt, "_s");


		return fileNameNoExt + "." + getFileExtName(thumbPath);
	}

 

3.1.9.getFileNameNoExt:获取无扩展名的文件名或路径

去掉文件的扩展名(包括点后)。

public static String getFileNameNoExt(String fileName) {
	if (StringUtil.isEmpty(fileName)) {
		return "";
	}
	return fileName.substring(0, fileName.lastIndexOf("."));
}

 

3.1.10.getFileExtName:获取文件扩展名

获取文件的扩展名,根据点号分隔获取。

public static String getFileExtName(String fileName) {
	if (StringUtil.isEmpty(fileName)) {
		return "";
	}
	return fileName.substring(fileName.lastIndexOf(".") + 1);
}

3.1.11.allowFileType:判断扩展名是否包含

没有直接采用字符串的contains方法,而是要在字符串前后都加上分隔符,否则会存在判断逻辑漏洞。


	//判断文件类型是否包含
	public static boolean allowFileType(String allowFileTypes, String fileNameExt) {
		//不限制,就允许
		if (StringUtil.isEmpty(allowFileTypes) || StringUtil.isEmpty(fileNameExt)) {
			return true;
		}

		return ("#" + allowFileTypes.toLowerCase() + "#").contains("#" + fileNameExt.toLowerCase() + "#");
	}

3.1.12.getFileTypeByName:获取文件类型

根据文件扩展名,进行文件类别归类。

	//根据文件名,获取文件类型
	public static int getFileTypeByName(String fileName) {
		if (StringUtil.isEmpty(fileName)) {
			return FILE_TYPE_UNKNOWN;
		}
		//提取扩展名
		int pointIndex = fileName.lastIndexOf(".");
		if (pointIndex <= 0) {
			return FILE_TYPE_UNKNOWN;
		}
		String extName = fileName.substring(pointIndex + 1).toLowerCase();
		switch (extName) {
			case "pdf":
			case "doc":
			case "docx":
			case "zip":
			case "rar":
				return FILE_TYPE_FILE;
			//图片
			case "jpg":
			case "jpeg":
			case "gif":
			case "png":
			case "bmp":
				return FILE_TYPE_IMAGE;
			//视频
			case "mp4":
			case "avi":
				return FILE_TYPE_VIDEO;
			//音频
			case "mp3":
				return FILE_TYPE_AUDIO;
			case "xls":
			case "xlsx":
				return FILE_TYPE_EXCEL;
			//其他
			default:
				return FILE_TYPE_UNKNOWN;

		}

	}

3.2.前端样例

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你好!很高兴为你解答问题。以下是使用Java实现上传文件至服务器的代码示例: ``` import java.io.File; import java.io.IOException; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.methods.FileRequestEntity; import org.apache.commons.httpclient.methods.PostMethod; public class UploadUtil { /** * 上传文件至服务器 * @param file 需要上传的文件 * @param url 服务器接收文件的地址 * @return 返回上传是否成功的标记 */ public static boolean uploadFile(File file, String url) { boolean success = false; PostMethod postMethod = new PostMethod(url); HttpClient client = new HttpClient(); try { FileRequestEntity fileRequestEntity = new FileRequestEntity(file,"application/octet-stream"); postMethod.setRequestEntity(fileRequestEntity); int statusCode = client.executeMethod(postMethod); if (statusCode == HttpStatus.SC_OK) { success = true; System.out.println("上传成功!"); } } catch (IOException e) { e.printStackTrace(); } finally { postMethod.releaseConnection(); } return success; } /** * 示例 * @param args */ public static void main(String[] args) { File file = new File("/Users/test/test.txt"); // 需要上传到服务器的文件路径 String url = "http://localhost:8080/test/upload"; // 服务器接收文件的地址 uploadFile(file,url); // 上传文件至服务器 } } ``` 希望能够帮助到你!如果有任何问题,欢迎继续向我提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值