import com.bruce.util.BeanHelper; |
import com.bruce.util.Logger; |
import com.bruce.util.http.FileUploader; |
import com.bruce.util.http.FileUploader.FileInfo; |
import static com.bruce.util.http.FileUploader.*; |
@SuppressWarnings ( "unused" ) |
public class CheckUpload extends ActionSupport |
{ |
// 上传路径 |
private static final String UPLOAD_PATH = "upload" ; |
// 可接受的文件类型 |
private static final String[] ACCEPT_TYPES = { "txt" , "pdf" , "doc" , ".Jpg" , "*.zip" , "*.RAR" }; |
// 总上传文件大小限制 |
private static final long MAX_SIZE = 1024 * 1024 * 100 ; |
// 单个传文件大小限制 |
private static final long MAX_FILE_SIZE = 1024 * 1024 * 10 ; |
@Override |
public String execute() |
{ |
// 创建 FileUploader 对象 |
FileUploader fu = new FileUploader (UPLOAD_PATH, ACCEPT_TYPES, MAX_SIZE, MAX_FILE_SIZE); |
// 根据实际情况设置对象属性(可选) |
/* |
fu.setFileNameGenerator(new FileNameGenerator() |
{ |
@Override |
public String generate(FileItem item, String suffix) |
{ |
return String.format("%d_%d", item.hashCode(), item.get().hashCode()); |
} |
}); |
fu.setServletProgressListener(new ProgressListener() |
{ |
@Override |
public void update(long pBytesRead, long pContentLength, int pItems) |
{ |
System.out.printf("%d: length -> %d, read -> %d.\n", pItems, pContentLength, pBytesRead); |
} |
}); |
*/ |
// 执行上传并获取操作结果 |
Result result = fu.upload (getRequest(), getResponse() ); |
// 检查操作结果 |
if (result != FileUploader.Result.SUCCESS) |
{ |
// 设置 request attribute |
setRequestAttribute ( "error" , fu.getCause() ); |
// 记录日志 |
Logger.exception (fu.getCause(), "upload file fail" , Level.ERROR, false ); |
return ERROR; |
} |
// 通过非文件表单域创建 Form Bean |
Persion persion = BeanHelper.createBean (Persion. class , fu.getParamFields() ); |
// 图片保存路径的列表 |
List<String> photos = new ArrayList<String>(); |
/* 轮询文件表单域,填充 photos */ |
Set<String> keys = fu.getFileFields().keySet(); |
for (String key : keys) |
{ |
FileInfo[] ffs = fu.getFileFields().get (key); |
for (FileInfo ff : ffs) |
{ |
photos.add (String.format ( "(%s) %s%s%s" , key, fu.getSavePath(), File.separator, ff.getSaveFile().getName() ) ); |
} |
} |
// 设置 Form Bean 的 photos 属性 |
persion.setPhotos (photos); |
// 设置 request attribute |
setRequestAttribute ( "persion" , persion); |
return SUCCESS; |
} |
} |
class Persion |
{ |
private String firstName; |
private String lastName; |
private Date birthday; |
private boolean gender; |
private int workingAge; |
private int [] interest; |
private List<String> photos; |
} |
static class FileInfo |
{ |
private String uploadFileName; |
private File saveFile; |
} |
/** 文件上传器 */ |
class FileUploader |
{ |
/** 不限制文件上传总大小的 Size Max 常量 */ |
public static final long NO_LIMIT_SIZE_MAX = - 1 ; |
/** 不限制文件上传单个文件大小的 File Size Max 常量 */ |
public static final long NO_LIMIT_FILE_SIZE_MAX = - 1 ; |
/** 默认的写文件阀值 */ |
public static final int DEFAULT_SIZE_THRESHOLD = DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD; |
/** 默认的文件名生成器 */ |
public static final FileNameGenerator DEFAULT_FILE_NAME_GENERATOR = new CommonFileNameGenerator(); |
/** 设置上传文件的保存路径(不包含文件名) |
* |
* 文件路径,可能是绝对路径或相对路径<br> |
* 1) 绝对路径:以根目录符开始(如:'/'、'D:\'),是服务器文件系统的路径<br> |
* 2) 相对路径:不以根目录符开始,是相对于 WEB 应用程序 Context 的路径,(如:mydir 是指 |
* '${WEB-APP-DIR}/mydir')<br> |
* 3) 规则:上传文件前会检查该路径是否存在,如果不存在则会尝试生成该路径,如果生成失败则 |
* 上传失败并返回 {@link Result#INVALID_SAVE_PATH} |
* |
*/ |
private String savePath; |
/** 文件上传的总文件大小限制 */ |
private long sizeMax = NO_LIMIT_SIZE_MAX; |
/** 文件上传的单个文件大小限制 */ |
private long fileSizeMax = NO_LIMIT_FILE_SIZE_MAX; |
/** 可接受的上传文件类型集合,默认:不限制 */ |
private Set<String> acceptTypes = new LStrSet(); |
/** 非文件表单域的映射 */ |
private Map<String, String[]> paramFields = new HashMap<String, String[]>(); |
/** 文件表单域的映射 */ |
private Map<String, FileInfo[]> fileFields = new HashMap<String, FileInfo[]>(); |
/** 文件名生成器 */ |
private FileNameGenerator fileNameGenerator = DEFAULT_FILE_NAME_GENERATOR; |
// commons file upload 相关属性 |
private int factorySizeThreshold = DEFAULT_SIZE_THRESHOLD; |
private String factoryRepository; |
private FileCleaningTracker factoryCleaningTracker; |
private String servletHeaderencoding; |
private ProgressListener servletProgressListener; |
/** 文件上传失败的原因(文件上传失败时使用) */ |
private Throwable cause; |
/** 执行上传 |
* |
* @param request : {@link HttpServletRequest} 对象 |
* @param response : {@link HttpServletResponse} 对象 |
* |
* @return : 成功:返回 {@link Result#SUCCESS} ,失败:返回其他结果, |
* 失败原因通过 {@link FileUploader#getCause()} 获取 |
* |
*/ |
@SuppressWarnings ( "unchecked" ) |
public Result upload (HttpServletRequest request, HttpServletResponse response) |
{ |
reset(); |
// 获取上传目录绝对路径 |
String absolutePath = getAbsoluteSavePath (request); |
if (absolutePath == null ) |
{ |
cause = new FileNotFoundException (String.format ( "path '%s' not found or is not directory" , savePath) ); |
return Result.INVALID_SAVE_PATH; |
} |
ServletFileUpload sfu = getFileUploadComponent(); |
List<FileItemInfo> fiis = new ArrayList<FileItemInfo>(); |
List<FileItem> items = null ; |
Result result = Result.SUCCESS; |
// 获取文件名生成器 |
String encoding = servletHeaderencoding != null ? servletHeaderencoding : request.getCharacterEncoding(); |
FileNameGenerator fnGenerator = fileNameGenerator != null ? fileNameGenerator : DEFAULT_FILE_NAME_GENERATOR; |
try |
{ |
// 执行上传操作 |
items = (List<FileItem>) sfu.parseRequest (request); |
} |
catch (FileUploadException e) |
{ |
cause = e; |
if (e instanceof FileSizeLimitExceededException) |
{ |
result = Result.FILE_SIZE_EXCEEDED; |
} |
else if (e instanceof SizeLimitExceededException) |
{ |
result = Result.SIZE_EXCEEDED; |
} |
else if (e instanceof InvalidContentTypeException) |
{ |
result = Result.INVALID_CONTENT_TYPE; |
} |
else if (e instanceof IOFileUploadException) |
{ |
result = Result.FILE_UPLOAD_IO_EXCEPTION; |
} |
else |
{ |
result = Result.OTHER_PARSE_REQUEST_EXCEPTION; |
} |
} |
if (result == Result.SUCCESS) |
{ |
// 解析所有表单域 |
result = parseFileItems (items, fnGenerator, absolutePath, encoding, fiis); |
if (result == Result.SUCCESS) |
// 保存文件 |
{ |
result = writeFiles (fiis); |
} |
} |
return result; |
} |
// 解析所有表单域 |
private Result parseFileItems (List<FileItem> items, FileNameGenerator fnGenerator, String absolutePath, String encoding, List<FileItemInfo> fiis) |
{ |
for (FileItem item : items) |
{ |
if (item.isFormField() ) |
// 解析非文件表单域 |
{ |
parseFormField (item, encoding); |
} |
else |
{ |
if (item.getSize() == 0 ) |
{ |
continue ; |
} |
// 解析文件表单域 |
Result result = parseFileField (item, absolutePath, fnGenerator, fiis); |
if (result != Result.SUCCESS) |
{ |
reset(); |
cause = new InvalidParameterException (String.format ( "file '%s' not accepted" , item.getName() ) ); |
return result; |
} |
} |
} |
return Result.SUCCESS; |
} |
// 解析文件表单域 |
private Result parseFileField (FileItem item, String absolutePath, FileNameGenerator fnGenerator, List<FileItemInfo> fiis) |
{ |
String suffix = null ; |
String uploadFileName = item.getName(); |
boolean isAcceptType = acceptTypes.isEmpty(); |
if (!isAcceptType) |
{ |
suffix = null ; |
int stuffPos = uploadFileName.lastIndexOf ( "." ); |
if (stuffPos != - 1 ) |
{ |
suffix = uploadFileName.substring (stuffPos, uploadFileName.length() ).toLowerCase(); |
isAcceptType = acceptTypes.contains (suffix); |
} |
} |
if (!isAcceptType) |
{ |
return Result.INVALID_FILE_TYPE; |
} |
// 通过文件名生成器获取文件名 |
String saveFileName = fnGenerator.generate (item, suffix); |
if (!saveFileName.endsWith (suffix) ) |
{ |
saveFileName += suffix; |
} |
String fullFileName = absolutePath + File.separator + saveFileName; |
File saveFile = new File (fullFileName); |
FileInfo info = new FileInfo (uploadFileName, saveFile); |
// 添加表单域文件信息 |
fiis.add ( new FileItemInfo (item, saveFile) ); |
addFileField (item.getFieldName(), info); |
return Result.SUCCESS; |
} |
private void parseFormField (FileItem item, String encoding) |
{ |
String name = item.getFieldName(); |
String value = item.getString(); |
// 字符串编码转换 |
if (!GeneralHelper.isStrEmpty (value) && encoding != null ) |
{ |
try |
{ |
value = new String (value.getBytes ( "ISO-8859-1" ), encoding); |
} |
catch (UnsupportedEncodingException e) |
{ |
} |
} |
// 添加表单域名/值映射 |
addParamField (name, value); |
} |
/** 文件名生成器接口 |
* |
* 每次保存一个上传文件前都需要调用该接口的 {@link FileNameGenerator#generate} 方法生成要保存的文件名 |
* |
*/ |
public static interface FileNameGenerator |
{ |
/** 文件名生成方法 |
* |
* @param item : 上传文件对应的 {@link FileItem} 对象 |
* @param suffix : 上传文件的后缀名 |
* |
*/ |
String generate (FileItem item, String suffix); |
} |
/** 默认通用文件名生成器 |
* |
* 实现 {@link FileNameGenerator} 接口,根据序列值和时间生成唯一文件名 |
* |
*/ |
public static class CommonFileNameGenerator implements FileNameGenerator |
{ |
private static final int MAX_SERIAL = 999999 ; |
private static final AtomicInteger atomic = new AtomicInteger(); |
private static int getNextInteger() |
{ |
int value = atomic.incrementAndGet(); |
if (value >= MAX_SERIAL) |
{ |
atomic.set ( 0 ); |
} |
return value; |
} |
/** 根据序列值和时间生成 'XXXXXX_YYYYYYYYYYYYY' 格式的唯一文件名 */ |
@Override |
public String generate (FileItem item, String suffix) |
{ |
int serial = getNextInteger(); |
long millsec = System.currentTimeMillis(); |
return String.format ( "%06d_%013d" , serial, millsec); |
} |
} |
/** 上传文件信息结构体 */ |
public static class FileInfo |
{ |
private String uploadFileName; |
private File saveFile; |
// getters and setters ... |
} |
private class FileItemInfo |
{ |
FileItem item; |
File file; |
// getters and setters ... |
} |
/** 文件上传结果枚举值 */ |
public static enum Result |
{ |
/** 成功 */ |
SUCCESS, |
/** 失败:文件总大小超过限制 */ |
SIZE_EXCEEDED, |
/** 失败:单个文件大小超过限制 */ |
FILE_SIZE_EXCEEDED, |
/** 失败:请求表单类型不正确 */ |
INVALID_CONTENT_TYPE, |
/** 失败:文件上传 IO 错误 */ |
FILE_UPLOAD_IO_EXCEPTION, |
/** 失败:解析上传请求其他异常 */ |
OTHER_PARSE_REQUEST_EXCEPTION, |
/** 失败:文件类型不正确 */ |
INVALID_FILE_TYPE, |
/** 失败:文件写入失败 */ |
WRITE_FILE_FAIL, |
/** 失败:文件的保存路径不正确 */ |
INVALID_SAVE_PATH; |
} |
} |