jFinal基于COS組件實現文件上傳進度條的一種解決方案
近端時間拿jfinal在移動WEB上鼓搗,發現手機在非WIFI模式下上傳圖片有點慢,拿iphone為例,用戶實時拍照上傳,照片的尺寸保守在3M以上(手機WEB瀏覽器要是都支持在前臺降質量就好了),在2G\3G\4G網絡下頁面出現假死現象,其實圖片正在上傳,因而必須要給上傳頁面加上進度條才可。由于jFinal使用了cos組件來處理上傳,而cos組件本身是沒有進度數據接口的,搜索查到有一篇文章,是修改COS源碼的(地址)。當然,作為一個coder,肯定不是很喜歡這種方式,繼續搜索cos組件代理,這回搜索到了一點有用的東西(原文地址),在此基礎上,對jfinal進行了相關類進行了擴展,相關如下:
首先,添加servlet底層輸入流的代理ServletInputStreamProxy,代碼如下:
package com.nq.jfinal.upload; import java.io.IOException; import javax.servlet.ServletInputStream; /** * 代理底層的輸入流,參考http://bbs.csdn.net/topics/330245424 * @author liu_jingcheng@ctg.com.cn * */ public class ServletInputStreamProxy extends ServletInputStream{ ServletInputStream in; ProgressBarObserver observer; public ServletInputStreamProxy(ServletInputStream in, ProgressBarObserver observer) { this.in = in; this.observer = observer; } @Override public int read(byte[] b, int off, int len) throws IOException { int r = in.read(b, off, len); if (r != -1) { observer.incomingContent(r); } return r; } @Override public int read() throws IOException { return 0; } }
按參考思路繼續添加HttpServletRequest的代理
package com.nq.jfinal.upload; import java.io.IOException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * 代理HttpServletRequest,參考http://bbs.csdn.net/topics/330245424 * @author liu_jingcheng@ctg.com.cn * */ public class HttpServletRequestProxy extends HttpServletRequestWrapper { private ProgressBarObserver observer; public HttpServletRequestProxy(HttpServletRequest request, ProgressBarObserver observer) { super(request); this.observer = observer; } /* (non-Javadoc) * @see javax.servlet.ServletRequestWrapper#getInputStream() */ public ServletInputStream getInputStream() throws IOException { ServletInputStream in = super.getInputStream(); return new ServletInputStreamProxy(in, observer); } }
我們添加一個被觀察者ProgressBarObserver
package com.nq.jfinal.upload; import java.util.Observable; /** * 進度條組件被觀察者 * @author liu_jingcheng@ctg.com.cn * */ public class ProgressBarObserver extends Observable { private int uploadedSize = 0; private ProgressBarEntity bar; public ProgressBarObserver(long totalSize, int uploadedSize) { super(); this.uploadedSize = uploadedSize; bar = new ProgressBarEntity(totalSize, uploadedSize); } public void incomingContent(int readSize) { uploadedSize += readSize; bar.setUploadedSize(uploadedSize); setChanged(); notifyObservers(bar); } public int getUploadedSize() { return uploadedSize; } public void setUploadedSize(int uploadedSize) { this.uploadedSize = uploadedSize; } }
代理相關的部分基本結束了,現在進入對jfinal原有類的擴展,主要是Controller類,我們定義一個擴展類ProgressBarController繼承自Controller,話說jFinal2.1以前的原有類對繼承一點都不友好。首先,我們先觀察一下jFinal原有類Controller和MultipartRequest,需要在后者里面剝離部分代碼出來直接使用(在這里偷個懶,再說原裝的總是感覺踏實些),主要是3個方法handleSaveDirectory,wrapMultipartRequest【這個方法處理上傳】,isSafeFile。再進入原裝的Controller類中,獲取上傳文件相關的是getFile和getFiles的幾個重載方法,這里我們需要把我們的上面創建的被觀察者作為參數重載這幾個方法,直接將原有類中這幾個方法拷貝到ProgressBarController,在每一個方法中加入ProgressBarObserver參數,清空所有的方法體。
現在,我們開始重載第一個方法,以public UploadFile getFile(String parameterName, ProgressBarObserver observer) {}這個方法為例,創建代理類HttpServletRequestProxy對象,組裝上傳方法private List<UploadFile> wrapMultipartRequest(HttpServletRequest request, String saveDirectory, int maxPostSize, String encoding) {},沒有的參數我們利用Constants取默認值,主要是將wrapMultipartRequest中HttpServletRequest參數傳為我們創建的代理類對象,對wrapMultipartRequest的改動僅添加返回值List<UploadFile>。
重點在底層代理類ServletInputStreamProxy的read方法中觸發被觀察者對象變動,以喚起我們自定義的觀察者,ProgressBarController代碼如下,由于字數限制已刪除部分重載方法:
package com.nq.jfinal.upload; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import javax.servlet.http.HttpServletRequest; import com.jfinal.config.Constants; import com.jfinal.core.Controller; import com.jfinal.core.JFinal; import com.jfinal.kit.PathKit; import com.jfinal.upload.UploadFile; import com.oreilly.servlet.multipart.DefaultFileRenamePolicy; import com.oreilly.servlet.multipart.FileRenamePolicy; /** * 進度條控制器,繼承自原Controller * @author liu_jingcheng@ctg.com.cn * */ public class ProgressBarController extends Controller { static FileRenamePolicy fileRenamePolicy = new DefaultFileRenamePolicy(); public UploadFile getFile(String parameterName, String saveDirectory, int maxPostSize, ProgressBarObserver observer) { HttpServletRequestProxy uploadRequest = new HttpServletRequestProxy(getRequest(), observer); List<UploadFile> uploadFiles = wrapMultipartRequest(uploadRequest, saveDirectory, maxPostSize, JFinal.me().getConstants().getEncoding()); for (UploadFile uploadFile : uploadFiles) { if (uploadFile.getParameterName().equals(parameterName)) { return uploadFile; } } return null; } public UploadFile getFile(ProgressBarObserver observer) { HttpServletRequestProxy uploadRequest = new HttpServletRequestProxy(getRequest(), observer); Constants constants = JFinal.me().getConstants(); List<UploadFile> uploadFiles = wrapMultipartRequest(uploadRequest, constants.getUploadedFileSaveDirectory(), constants.getMaxPostSize(), constants.getEncoding()); return uploadFiles.size() > 0 ? uploadFiles.get(0) : null; } public UploadFile getFile(String parameterName, ProgressBarObserver observer) { HttpServletRequestProxy uploadRequest = new HttpServletRequestProxy(getRequest(), observer); Constants constants = JFinal.me().getConstants(); System.out.println(constants.getUploadedFileSaveDirectory()); List<UploadFile> uploadFiles = wrapMultipartRequest(uploadRequest, constants.getUploadedFileSaveDirectory(), constants.getMaxPostSize(), constants.getEncoding()); for (UploadFile uploadFile : uploadFiles) { if (uploadFile.getParameterName().equals(parameterName)) { return uploadFile; } } return null; } /**以下部分代碼剝離自原裝的MultipartRequest**/ /** * 添加對相對路徑的支持 * 1: 以 "/" 開頭或者以 "x:開頭的目錄被認為是絕對路徑 * 2: 其它路徑被認為是相對路徑, 需要 JFinalConfig.uploadedFileSaveDirectory 結合 */ private String handleSaveDirectory(String saveDirectory) { if (saveDirectory.startsWith("/") || saveDirectory.indexOf(":") == 1) return saveDirectory; else{ //這個地方有修改 return PathKit.getWebRootPath() + "/" + saveDirectory; } } @SuppressWarnings("rawtypes") private List<UploadFile> wrapMultipartRequest(HttpServletRequest request, String saveDirectory, int maxPostSize, String encoding) { saveDirectory = handleSaveDirectory(saveDirectory); File dir = new File(saveDirectory); if ( !dir.exists()) { if (!dir.mkdirs()) { throw new RuntimeException("Directory " + saveDirectory + " not exists and can not create directory."); } } // String content_type = request.getContentType(); // if (content_type == null || content_type.indexOf("multipart/form-data") == -1) { // throw new RuntimeException("Not multipart request, enctype=\"multipart/form-data\" is not found of form."); // } List<UploadFile> uploadFiles = new ArrayList<UploadFile>(); try { com.oreilly.servlet.MultipartRequest multipartRequest = new com.oreilly.servlet.MultipartRequest(request, saveDirectory, maxPostSize, encoding, fileRenamePolicy); Enumeration files = multipartRequest.getFileNames(); while (files.hasMoreElements()) { String name = (String)files.nextElement(); String filesystemName = multipartRequest.getFilesystemName(name); // 文件沒有上傳則不生成 UploadFile, 這與 cos的解決方案不一樣 if (filesystemName != null) { String originalFileName = multipartRequest.getOriginalFileName(name); String contentType = multipartRequest.getContentType(name); UploadFile uploadFile = new UploadFile(name, saveDirectory, filesystemName, originalFileName, contentType); if (isSafeFile(uploadFile)) uploadFiles.add(uploadFile); } } } catch (IOException e) { throw new RuntimeException(e); } return uploadFiles; } private boolean isSafeFile(UploadFile uploadFile) { String fileName = uploadFile.getFileName().trim().toLowerCase(); if (fileName.endsWith(".jsp") || fileName.endsWith(".jspx")) { uploadFile.getFile().delete(); return false; } return true; } }
主體代碼部分基本完工,我們看看調用的情況,新建一個Controller繼承自ProgressBarController代碼如下:
package com.nq.jfinal.controller.mobile; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Observable; import java.util.Observer; import com.jfinal.aop.Before; import com.jfinal.aop.Clear; import com.jfinal.kit.PathKit; import com.jfinal.kit.PropKit; import com.jfinal.render.JsonRender; import com.jfinal.upload.UploadFile; import com.nq.jfinal.interceptors.MobileAuthInterceptor; import com.nq.jfinal.interceptors.TokenInterceptor; import com.nq.jfinal.upload.ProgressBarController; import com.nq.jfinal.upload.ProgressBarEntity; import com.nq.jfinal.upload.ProgressBarObserver; import com.nq.jfinal.validators.NySdValidator; @Before(MobileAuthInterceptor.class) public class MobSdController extends ProgressBarController { @Clear public void _get_progressbar() { Object prc = getSessionAttr("progressbar"); if (prc == null) { prc = 0; } renderJson(prc); } @SuppressWarnings("unchecked") public void upload() { removeSessionAttr("progressbar"); ProgressBarObserver observer = new ProgressBarObserver(getRequest().getContentLength(), 0); observer.addObserver(new Observer() { @Override public void update(Observable o, Object arg) { //這里處理進度變化的事情 if (arg instanceof ProgressBarEntity) { ProgressBarEntity bar = (ProgressBarEntity) arg; setSessionAttr("progressbar", bar.getProgress()); System.out.println(bar.getTotalSize() + "\t" + bar.getUploadedSize() + "\t" + bar.getProgress()); } } }); UploadFile file = getFile("uploadFile", observer); //UploadFile file = getFile("uploadFile","",PropKit.getInt("fileMaxPostSize", 5242880)); //TODO } }
字數限制,ProgressBarEntity就不放出來了,這個類就是一個包含了totalSize、uploadedSize的實體類。
第一次發博文,希望能幫到需要的人,同時也請眾位指正錯誤,這個方案正確的話,希望jfinal下個版本可以加入對進度條的支持!