引子:
想起以前第一个项目的时候,常常使用apache的fileUploadutil,感觉效果很不好,又不容易实现一个上传的过程。
后来使用了spring提供的MultipartFile上传机制,将少了很多复杂的处理过程。但是随着业务逻辑的复杂,对于文件的分类那时候处理得比较困难,比如上传一个图片,还是pdf,还是doc,图片多大?pdf多大?分别存储的路径在哪?如果是图片要压缩怎么办?等等,直到后来学习了策略模式,这些问题通通变成了可拓展,可维护的,文件上传现在对我来说,就是一两句代码的事情了。
大概的逻辑是这样的:把multipartFile和一个检验的具体策略传送给一个fileUtil,fileUtil根据具体的上传策略对文件执行上传操作,上传策略定义了文件可被接受的类型,文件大小,图片是否支持图片压缩,文件的存储路径等。当我们想要上传图片的时候,我们就使用图片策略,如果过几天想要上传pdf,我们只需要新写一个pdf上传策略就可以完成拓展,不需要修改原来代码。
概念扫盲
MultipartFile:spring对二进制文件的封装之后得到的一个对象,拿到它之后我们可以使用这个方法对文件进行上传
FileUtils.writeByteArrayToFile()
进行文件上传操作,且调用对象本身我们可以拿到一些文件信息。
A representation of an uploaded file received in a multipart request.
The file contents are either stored in memory or temporarily on disk. In either case, the user is responsible for copying file contents to a session-level or persistent store as and if desired. The temporary storages will be cleared at the end of request processing.
如何实现可拓展上传组件的封装
现在我的包是这样的
首先看一下上传策略接口:
/**
*
*/
package com.ruiyi.upload.component;
/**
* @author jiangjintai
*
*/
public interface UpdateLoadFileStrategy {
/**
* 定义上传文件类型:格式如:bmp.jpg.tiff.gif.pcx.tga.exif.fpx.svg.psd.cdr.pcd.dxf.ufo.eps.ai.raw.png(用点隔开)
* "*"表示全部类型
* jiangjintai
* 2016年8月12日
* @return
*/
String type();
/**
* 定义上传文件的路径
* jiangjintai
* 2016年8月12日
* @return
*/
String path();
/**
* 定义文件的大小 单位为M
* jiangjintai
* 2016年8月12日
* @return
*/
Long size();
}
可被压缩接口:
/**
*
*/
package com.ruiyi.upload.component;
/**
* @author jiangjintai
*
*/
public interface Compressable {
/**
* 压缩后的图片宽
* jiangjintai
* 2016年8月12日
* @return
*/
String pictureWidth();
/**
* 压缩后的图片长
* jiangjintai
* 2016年8月12日
* @return
*/
String pictureLength();
/**
* 压缩后图片的存储路劲
* jiangjintai
* 2016年8月12日
* @return
*/
String changedPath();
}
图片压缩外观类
package com.ruiyi.upload.component;
import java.io.File;
import java.io.IOException;
import net.coobird.thumbnailator.Thumbnails;
/*
* 通过像素的缩放技术,改变图片的大小
* --等距采样法
*/
public class ImageChange {
private File file=null;
private File out=null;
public ImageChange(String filePath,String newFilePath) throws IOException{
this.file = new File(filePath);//原图片
this.out= new File(newFilePath);//目的图片
}
/**
* @param uploadPrictureFile
* @param changePrictureFile
*/
public ImageChange(File uploadPrictureFile, File changePrictureFile) {
this.file=uploadPrictureFile;
this.out=changePrictureFile;
}
/**
*
* @param width 改变后图片的宽
* @param heigth 改变后图片的高
* @throws IOException
* @throws SimpleImageException
*/
public void flex(int width,int height) throws IOException{
/*
* size(width,height) 若图片横比200小,高比300小,不变
* 若图片横比200小,高比300大,高缩小到300,图片比例不变 若图片横比200大,高比300小,横缩小到200,图片比例不变
* 若图片横比200大,高比300大,图片按比例缩小,横为200或高为300
*/
Thumbnails.of(file).size(1024, 768).toFile(out);
//开源框架
}
}
再来看看我们的上传主类
package com.ruiyi.upload.component;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.springframework.web.multipart.MultipartFile;
public class FileUpLoadUtil {
private static final Logger logger = Logger.getLogger(FileUpLoadUtil.class);
private String[] type;//文件类型
private String path;//文件存储路径
private long size;//文件大小(单位M)
private UpdateLoadFileStrategy strategy; //文件存储策略
public FileUpLoadUtil(UpdateLoadFileStrategy strategy) {
type = strategy.type().split("\\.");
path = strategy.path();
size = strategy.size()*1024*1024;
}
/**
* 返回-1,表示类型出现问题,返回-2,表示尺寸过大,文件名表示存入成功,0表示未知错误
* jiangjintai
* 2016年3月23日
* @param multipartFile
* @param realPath
* @return
*/
public String doUpload(MultipartFile multipartFile, String realPath) {
logger.info("选择当前保存根路径:"+realPath);
String fileName = multipartFile.getOriginalFilename();//返回文件在客户机的路径
logger.info("文件原本路径:"+fileName);
String fileUploadName = fileName
.substring(fileName.lastIndexOf("\\") + 1);
logger.info("文件名:"+fileUploadName);
String typeName = fileUploadName.substring(fileUploadName
.lastIndexOf(".") + 1);
logger.info("文件类型:"+typeName);
if (multipartFile.getSize() >= size) {
return "-2";
}
int count = 0;
if("*".equals(type[0]))//代表全部类型
count=1;
for (String aType : type) {
if (aType.toLowerCase().equals(typeName.toLowerCase())) {
count = count + 1;
}
}
logger.info("该类型与策略类型匹配程度:"+count+"匹配");
if (count == 1) {
//命名策略
// 往文件名里面加上时间戳
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
int random = (int) (Math.random()*1000000);
String timeStamp = sdf.format(new Date());
fileUploadName = timeStamp +random+ fileUploadName.substring(fileUploadName
.lastIndexOf("."));//重新命名
//命名策略结束 这里还可以抽出一个部分
try {
String fileLocalPath = realPath+path;//本地图片存储路径
File file = new File(realPath + path);
if (!file.exists()) {
file.mkdirs();
}
//这里用这个方式进行存储
FileUtils.writeByteArrayToFile(new File(fileLocalPath,fileUploadName), multipartFile.getBytes());
//如果是一个图片就进行转化处理
if(strategy instanceof Compressable){
logger.info("是一个图片,进行压缩");
Compressable compressable = (Compressable) this.strategy;
File changeFile = new File(realPath+compressable.changedPath());
if(!changeFile.exists()){
changeFile.mkdirs();
}
File uploadPrictureFile = new File(fileLocalPath,fileUploadName);
File changePrictureFile = new File(realPath + compressable.changedPath(),fileUploadName);
ImageChange ic = new ImageChange(uploadPrictureFile,changePrictureFile);//进行压缩
ic.flex(Integer.valueOf(compressable.pictureWidth()),Integer.valueOf(compressable.pictureLength()));
String afterCompressable = "/"+compressable.changedPath()+"/"+fileUploadName;
logger.info("压缩后的路径:"+afterCompressable);
return afterCompressable;
}else{
String fileSavedPath = "/"+path+"/"+fileUploadName;
logger.info("文件保存路径");
return fileSavedPath;
}
} catch (Exception e) {
e.printStackTrace();
return "0";
}
} else {
return "-1";
}
}
}
最后你只需要根据你的需求定义一个具体的策略,如图片策略
/**
*
*/
package com.ruiyi.upload.component.strategy;
import com.ruiyi.upload.component.Compressable;
import com.ruiyi.upload.component.UpdateLoadFileStrategy;
/**
* @author jiangjintai
*
*/
public class PictureStrategy implements Compressable, UpdateLoadFileStrategy {
private final String type = "bmp.jpg.tiff.gif.pcx.tga.exif.fpx.svg.psd.cdr.pcd.dxf.ufo.eps.ai.raw.png";
private final String path = "picture";
private final String pictureWidth = "1024";
private final String pictureLength = "768";
private final String changedPath = "changed";
private final Long size = 10L;
/* (非 Javadoc)
* @see com.haizhi.upload.component.UpdateLoadFileStrategy#type()
*/
@Override
public String type() {
//
return this.type;
}
/* (非 Javadoc)
* @see com.haizhi.upload.component.UpdateLoadFileStrategy#path()
*/
@Override
public String path() {
//
return path;
}
/* (非 Javadoc)
* @see com.haizhi.upload.component.UpdateLoadFileStrategy#size()
*/
@Override
public Long size() {
//
return size;
}
/* (非 Javadoc)
* @see com.haizhi.upload.component.Compressable#pictureWidth()
*/
@Override
public String pictureWidth() {
//
return pictureWidth;
}
/* (非 Javadoc)
* @see com.haizhi.upload.component.Compressable#pictureLength()
*/
@Override
public String pictureLength() {
//
return pictureLength;
}
/* (非 Javadoc)
* @see com.haizhi.upload.component.Compressable#changedPath()
*/
@Override
public String changedPath() {
//
return changedPath;
}
}
使用事例:
FileUpLoadUtil ufuu = new FileUpLoadUtil(new PictureStrategy());//这里如果你需要也可以使用工厂模式
String flag = ufuu.doUpload(productImage, request
.getServletContext().getRealPath("/"));
switch (flag) {
case "-1":
return ResponseEntityUtil.buildErrorEntity(
ErrorCode.paramError, "类型出错");
case "-2":
return ResponseEntityUtil.buildErrorEntity(
ErrorCode.paramError, "尺寸要小于10M");
case "0":
return ResponseEntityUtil.buildErrorEntity(
ErrorCode.paramError, "未知错误");
default:
tbProduct.setProductImgUrl(flag);
break;
}