View Javadoc
1   package gboat2.base.plugin.struts.interceptor;
2   
3   import gboat2.base.bridge.GboatAppContext;
4   import gboat2.base.bridge.util.FileUtil;
5   import gboat2.base.bridge.util.security.MD5Util;
6   
7   import java.io.File;
8   import java.io.IOException;
9   import java.util.ArrayList;
10  import java.util.Enumeration;
11  import java.util.List;
12  import java.util.Map;
13  
14  import javax.servlet.ServletContext;
15  import javax.servlet.ServletRequest;
16  import javax.servlet.http.HttpServletRequest;
17  import javax.servlet.http.HttpServletRequestWrapper;
18  import javax.servlet.http.HttpServletResponse;
19  
20  import org.apache.commons.lang3.StringUtils;
21  import org.apache.struts2.StrutsConstants;
22  import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
23  import org.apache.struts2.interceptor.FileUploadInterceptor;
24  
25  import com.opensymphony.xwork2.ActionContext;
26  import com.opensymphony.xwork2.ActionInvocation;
27  import com.opensymphony.xwork2.ValidationAware;
28  import com.opensymphony.xwork2.inject.Inject;
29  import com.opensymphony.xwork2.util.logging.Logger;
30  import com.opensymphony.xwork2.util.logging.LoggerFactory;
31  
32  /**
33   * 此拦截器经过改装:目的是实现断点上传后端接收数据。<br>
34   * 改装的点用注释线隔开,分为三部分:<br>
35   * 1.断点续传所需变量;<br>
36   * 2.请求已经上传文件大小;<br>
37   * 3.把文件片段上传到服务器;
38  <pre>
39   * 1. 在 Struts.xml 中定义拦截器:
40   *    <code>&lt;interceptor name="fileUpload" class="gboat2.base.plugin.struts.interceptor.GboatFileUploadInterceptor" /&gt;</code>
41   * 2. 定义一个拦截器栈,并将 fileUpload 添加到该栈中:<code>
42   *    &lt;interceptor-stack name="gboat2Stack"&gt;
43   *        &lt;interceptor-ref name="fileUpload"&gt;
44   *            &lt;!-- 允许上传的文件后缀 --&gt;
45   *            &lt;param name="allowedExtensions"&gt;.gif,.bmp,.png,.jpg,.jpeg,.tiff,.tif,.mpg&lt;/param&gt;
46   *        &lt;/interceptor-ref&gt;
47   *    &lt;/interceptors&gt;</code>
48   * 3. 将刚才定义的拦截器栈设置为默认执行: <code>&lt;default-interceptor-ref name="gboat2Stack" /&gt;</code>
49   * 4. 可以在 javaWeb 项目的 web.xml 中添加如下配置,扩展允许上传的文件后缀:<code>
50   *    &lt;context-param&gt;
51   *        &lt;description&gt;允许上传的文件后缀&lt;/description&gt;
52   *        &lt;param-name&gt;fileupload.extra.allowed.extensions&lt;/param-name&gt;
53   *        &lt;param-value&gt;
54   *            .pdf,.doc,.docx,.wps,.xls,.xlsx,.ppt,.pptx,.txt
55   *        &lt;/param-value&gt;
56   *    &lt;/context-param&gt;</code>
57   * </pre>
58   * @date 2012-6-29
59   * @author zhangxj-a
60   * @author <a href="mailto:[email protected]">何明旺</a>
61   * @since 1.0
62   */
63  public class GboatFileUploadInterceptor extends FileUploadInterceptor {
64  
65      private static final long serialVersionUID = 1L;
66  
67      private static final Logger logger = LoggerFactory.getLogger(GboatFileUploadInterceptor.class);
68  
69  	/****************************************** 断点续传:参数变量 begin *************************************************************/
70  	// 请求类型:上传文件
71  	private static final String UPLOAD_DATA = "upload_data";
72  
73  	// 断点续传请求类型:上次上传的位置
74  	private static final String ASK_BREAKPOINT = "ask_breakpoint";
75  
76  	/** {@value} */
77  	private static final String CURRENT_CHUNK = "chunk";
78  
79      /** {@value} */
80  	private static final String CHUNKS = "chunks";
81  
82  	// 文件的大小
83  	private static final String RESUMABLE_SIZE = "resumable_size";
84  
85  	// 文件的修改时间
86  	private static final String RESUMABLE_MODIFICATION_DATE = "resumable_modificationDate";
87  
88  	// 文件名字
89  	private static final String RESUMABLE_FILENAME = "resumable_filename";
90  
91  	// 断点续传核心参数
92  	private static final String RESUMABLE_UPLOAD = "ResumableUpload";
93  
94  	/****************************************** 断点续传:参数变量 end *************************************************************/
95  
96  	/**
97  	 * Store state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting.
98  	 */
99  	private String multipartSaveDir;
100 
101 	public String getMultipartSaveDir() {
102 		return multipartSaveDir;
103 	}
104 
105 	private ServletContext servletContext;
106 
107 	@Inject
108 	public void setServletContext(ServletContext context) {
109 		servletContext = context;
110 	}
111 
112 	@Override
113 	public void setAllowedExtensions(String allowedExtensions) {
114 		// 为附件上传类型提供一个白名单扩展机制,除了拦截器中提供的默认白名单外,可以在web.xml中附加项目自定义的扩展类型
115 		String extraExtensions = servletContext.getInitParameter("fileupload.extra.allowed.extensions");
116 		if (StringUtils.isNotEmpty(extraExtensions)) {
117 			allowedExtensions = extraExtensions + "," + allowedExtensions;
118 		}
119 		super.setAllowedExtensions(allowedExtensions);
120 	}
121 
122 	@Inject(StrutsConstants.STRUTS_MULTIPART_SAVEDIR)
123 	public void setMultipartSaveDir(String multipartSaveDir) {
124 		this.multipartSaveDir = multipartSaveDir;
125 	}
126 
127 	@Override
128 	public String intercept(ActionInvocation invocation) throws Exception {
129 		HttpServletRequest request = GboatAppContext.getRequest();
130 		
131 		MultiPartRequestWrapper multiWrapper = null;
132 		if(request instanceof MultiPartRequestWrapper) {
133 		    multiWrapper = (MultiPartRequestWrapper) request;
134 		} else {
135 		    // 经过 siteMesh 等第三方框架包装之后可能 request 本身并不是 MultiPartRequestWrapper 或其子类,这时候递归查找被包装的 request
136 		    ServletRequest req = request;
137 		    while (req instanceof  HttpServletRequestWrapper) {
138 		        req = ((HttpServletRequestWrapper)req).getRequest();
139 		        if(req instanceof MultiPartRequestWrapper) {
140 		            multiWrapper = (MultiPartRequestWrapper) req;
141 		            break;
142 	            }
143 	         }
144 		}
145 
146 		// Gboat2实现断点续传功能,判断是否有 resumable_upload 如果有则说明 是断点续传
147 		String resumable_upload = request.getHeader(RESUMABLE_UPLOAD);
148 		if (StringUtils.isEmpty(resumable_upload) && (multiWrapper == null)) {
149 			if (logger.isDebugEnabled()) {
150 				logger.debug("No MultiPartRequestWrapper, Gboat2FileUploadInterceptor ignore.");
151 			}
152 			return invocation.invoke();
153 		}
154 
155 		/****************************************** 断点续传:获得已经上传的文件大小 bengin *************************************************************/
156 		HttpServletResponse response = GboatAppContext.getResponse();
157 		ServletContext servletContext = GboatAppContext.getServletContext();
158 		if (!StringUtils.isEmpty(resumable_upload)) {
159 			// 请求断点的位置
160 			if (ASK_BREAKPOINT.equals(resumable_upload)) {
161 				File file = new File(getBreak_Point_TempDir(servletContext) + File.separator + getFullMD5FileName(request));
162 			    GboatAppContext.output(file.exists() ? file.length() : 0);
163 				return null;
164 			}
165 		}
166 		List<File> filesToCleanup = new ArrayList<File>(1);
167 		/****************************************** 断点续传:获得已经上传的文件大小 end *************************************************************/
168 
169 		ValidationAware validation = null;
170 
171 		Object action = invocation.getAction();
172 
173 		if (action instanceof ValidationAware) {
174 			validation = (ValidationAware) action;
175 		}
176 
177 		if (multiWrapper.hasErrors()) {
178 			for (String error : multiWrapper.getErrors()) {
179 				if (validation != null) {
180 					validation.addActionError(error);
181 				}
182 				logger.warn(error);
183 			}
184 		}
185 
186 		// bind allowed Files
187 		Enumeration<?> fileParameterNames = multiWrapper.getFileParameterNames();
188 		while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
189 			// get the value of this input tag
190 			String inputName = (String) fileParameterNames.nextElement();
191 
192 			// get the content type
193 			String[] contentType = multiWrapper.getContentTypes(inputName);
194 
195 			if (isNonEmpty(contentType)) {
196 				// get the name of the file from the input tag
197 				String[] fileName = multiWrapper.getFileNames(inputName);
198 
199 				if (isNonEmpty(fileName)) {
200 					// get a File object for the uploaded File
201 					File[] files = multiWrapper.getFiles(inputName);
202 					if (files != null && files.length > 0) {
203 						List<File> acceptedFiles = new ArrayList<File>(files.length);
204 						List<String> acceptedContentTypes = new ArrayList<String>(files.length);
205 						List<String> acceptedFileNames = new ArrayList<String>(files.length);
206 						String contentTypeName = inputName + "ContentType";
207 						String fileNameName = inputName + "FileName";
208 
209 						for (int index = 0; index < files.length; index++) {
210 							if (acceptFile(action, files[index], fileName[index], contentType[index], inputName, validation)) {
211 								acceptedFiles.add(files[index]);
212 								acceptedContentTypes.add(contentType[index]);
213 								acceptedFileNames.add(fileName[index]);
214 							}
215 						}
216 
217 						if (!acceptedFiles.isEmpty()) {
218 							Map<String, Object> params = ActionContext.getContext().getParameters();
219 
220 							params.put(inputName, acceptedFiles.toArray(new File[acceptedFiles.size()]));
221 							params.put(contentTypeName, acceptedContentTypes.toArray(new String[acceptedContentTypes.size()]));
222 							params.put(fileNameName, acceptedFileNames.toArray(new String[acceptedFileNames.size()]));
223 
224 							/****************************************** 断点续传:上传文件 begin *************************************************************/
225 							if (!StringUtils.isEmpty(resumable_upload)) {
226 								// chunks
227 								String chunks = request.getParameter(CHUNKS);
228 								// curentchunks
229 								String currentChunk = request.getParameter(CURRENT_CHUNK);
230 								// 请求上传
231 								if (UPLOAD_DATA.equals(resumable_upload)) {
232 									File file = new File(getBreak_Point_TempDir(servletContext) + File.separator
233 											+ getFullMD5FileName(request));
234 									File appendedFile = acceptedFiles.get(0);
235 									boolean isLastChunck = Integer.valueOf(currentChunk) >= (Integer.valueOf(chunks) - 1);
236 									// 追加:如果文件存在
237 									// 如果写文件失败:設置狀態嗎,返回null
238 									if (file.exists()) {
239 										try {
240 											long size = Long.valueOf(request.getParameter(RESUMABLE_SIZE));
241 											long uploadedSize = file.length();
242 											if (!(isLastChunck && uploadedSize == size)) {
243 											    // if last chunk and uploaded size = file size, don't append
244 												FileUtil.appendFile(file, appendedFile);
245 											}
246 										} catch (Exception e) {
247 											response.setStatus(501);
248 											return null;
249 										}
250 									} else {
251 										// 新建:如果文件不存在创建并写入
252 										// 如果写文件失败:設置狀態嗎,返回null
253 										File outFile = new File(file.getPath());
254 										try {
255 											FileUtil.writeFileToDisk(acceptedFiles.get(0), outFile);
256 										} catch (Exception e) {
257 											response.setStatus(501);
258 											return null;
259 										}
260 									}
261 
262 									// 如果判断当前的chunks,如果达到最大,交给action处理,如果没有达到最大返回
263 									if (!isLastChunck) {
264 										GboatAppContext.output(Boolean.TRUE);
265 										return null;
266 									} else {
267 										// 把最终上传的总文件告诉action
268 										List<File> resultList = new ArrayList<File>();
269 										resultList.add(file);
270 										filesToCleanup.add(file);// 需要手动清除已经上传的文件
271 										params.put(inputName, resultList.toArray(new File[acceptedFiles.size()]));
272 									}
273 
274 								}
275 							}
276 							/****************************************** 断点续传:上传文件 end *************************************************************/
277 						}
278 					}
279 				} else {
280 					logger.warn(getTextMessage(action, "struts.messages.invalid.file", new String[] { inputName }));
281 				}
282 			} else {
283 				logger.warn(getTextMessage(action, "struts.messages.invalid.content.type", new String[] { inputName }));
284 			}
285 		}
286 
287 		// invoke action
288 		String rt = null;
289 		try {
290 			rt = invocation.invoke();
291 		} finally {
292 			cleanupResumableUpoadedFiles(filesToCleanup);
293 		}
294 		return rt;
295 	}
296 
297 	private void cleanupResumableUpoadedFiles(List<File> files) {
298 		if (null != files) {
299 			for (File file : files) {
300 				if (file.exists()) {
301 					try {
302 						file.delete();
303 					} catch (Exception e) {
304 					}
305 				}
306 			}
307 		}
308 	}
309 
310 	private String getBreak_Point_TempDir(ServletContext servletContext) {
311 		// 临时文件夹创建
312 		getTempDir(servletContext);
313 		// 文件存放位置
314 		return getMultipartSaveDir();
315 
316 	}
317 
318 	private String getFullMD5FileName(HttpServletRequest request) throws Exception {
319 		// 文件名字,文件的修改时间,文件的大小
320 		String fileNameUpload = request.getParameter(RESUMABLE_FILENAME);
321 		String modificationDate = request.getParameter(RESUMABLE_MODIFICATION_DATE);
322 		String size = request.getParameter(RESUMABLE_SIZE);
323 		// 散列形成文件名
324 		String md5FileName = MD5Util.getMD5String(fileNameUpload + size + modificationDate);
325 		// 散列后完整文件名
326 		String fullMD4FileName = md5FileName + fileNameUpload.substring(fileNameUpload.lastIndexOf("."));
327 
328 		return fullMD4FileName;
329 	}
330 
331 	private boolean isNonEmpty(Object[] objArray) {
332 		boolean result = false;
333 		for (int index = 0; index < objArray.length && !result; index++) {
334 			if (objArray[index] != null) {
335 				result = true;
336 			}
337 		}
338 		return result;
339 	}
340 
341 	/**
342 	 * 获得断点续传文件路径
343 	 * 
344 	 * @param servletContext
345 	 * @return
346 	 */
347 	private String getTempDir(ServletContext servletContext) {
348 		String saveDir = multipartSaveDir.trim();
349 
350 		if (saveDir.equals("")) {
351 			File tempdir = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
352 			logger.info("Unable to find 'struts.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir");
353 
354 			if (!tempdir.exists()) {
355 				if (tempdir.mkdir() == false) {
356 					String logMessage;
357 					try {
358 						logMessage = "Could not find create multipart save directory '" + tempdir.getCanonicalPath() + "'.";
359 					} catch (IOException e) {
360 						logMessage = "Could not find create multipart save directory '" + tempdir.toString() + "'.";
361 					}
362 					logger.warn(logMessage);
363 				}
364 			}
365 
366 			if (tempdir != null) {
367 				saveDir = tempdir.toString();
368 				setMultipartSaveDir(tempdir.toString());
369 			}
370 		} else {
371 			File gboat2TempFile = new File(multipartSaveDir);
372 			if (!gboat2TempFile.exists()) {
373 				if (gboat2TempFile.mkdir() == false) {
374 					String logMessage;
375 					try {
376 						logMessage = "Could not find create multipart save directory '" + gboat2TempFile.getCanonicalPath() + "'.";
377 					} catch (IOException e) {
378 						logMessage = "Could not find create multipart save directory '" + gboat2TempFile.toString() + "'.";
379 					}
380 					logger.warn(logMessage);
381 				}
382 			}
383 		}
384 
385 		if (logger.isDebugEnabled()) {
386 			logger.debug("saveDir=" + saveDir);
387 		}
388 
389 		return saveDir;
390 	}
391 }