近端時間拿jfinal在移動WEB上鼓搗,發現手機在非WIFI模式下上傳圖片有點慢,拿iphone爲例,用戶實時拍照上傳,照片的尺寸保守在3M以上(手機WEB瀏覽器要是都支持在前臺降質量就行了),在2G\3G\4G網絡下頁面出現假死現象,其實圖片正在上傳,於是必需要給上傳頁面加上進度條纔可。因爲jFinal使用了cos組件來處理上傳,而cos組件自己是沒有進度數據接口的,搜索查到有一篇文章,是修改COS源碼的(地址)。固然,做爲一個coder,確定不是很喜歡這種方式,繼續搜索cos組件代理,這回搜索到了一點有用的東西(原文地址),在此基礎上,對jfinal進行了相關類進行了擴展,相關以下:java
首先,添加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); } }
咱們添加一個被觀察者ProgressBarObserverapp
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參數,清空全部的方法體。iphone
如今,咱們開始重載第一個方法,以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>。jsp
重點在底層代理類ServletInputStreamProxy的read方法中觸發被觀察者對象變更,以喚起咱們自定義的觀察者,ProgressBarController代碼以下,因爲字數限制已刪除部分重載方法:ide
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 { com.oreilly.servlet.MultipartRequest multipartRequest = null; 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 { 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; } @Override public String getPara(String name) { if (multipartRequest == null) { return super.getPara(name); } return multipartRequest.getParameter(name); } /*other getPara overrides*/ }
主體代碼部分基本完工,咱們看看調用的狀況,新建一個Controller繼承自ProgressBarController代碼以下:
ui
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的實體類。this
第一次發博文,但願能幫到須要的人,同時也請衆位指正錯誤,這個方案正確的話,但願jfinal下個版本能夠加入對進度條的支持!
spa
加入表單取參數的實現案例,具體見ProgressBarController的修改!