架构探险笔记10-框架优化之文件上传
確定文件上傳使用場(chǎng)景
通常情況下,我們可以通過一個(gè)form(表單)來上傳文件,就以下面的“創(chuàng)建客戶”為例來說明(對(duì)應(yīng)的文件名是customer_create.jsp),需要提供一個(gè)form,并將其enctype屬性設(shè)為multipart/form-data,表示以form?data方式提交表單數(shù)據(jù)。
注意:enctype的默認(rèn)值為application/x-www-form-urlencoded,表示以u(píng)rl?encoded方式提交表單數(shù)據(jù)。
下面我們使用jQuery與jQuery?Form插件快速編寫一個(gè)基于Ajax的文件上傳表單,代碼如下:
<%@ page pageEncoding="UTF-8" contentType="text/html;charset=UTF-8" language="java" %> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><c:set var="BASE" value="${pageContext.request.contextPath}"/> <html> <head><title>客戶管理-創(chuàng)建客戶</title> </head> <body><h1>創(chuàng)建客戶界面</h1> ${msg} <form id="customer_form" enctype="multipart/form-data"><table><tr><td>客戶名稱:</td><td><input type="text" name="name" value="${customer.name}"></td></tr><tr><td>聯(lián)系人:</td><td><input type="text" name="contact" value="${customer.contact}"></td></tr><tr><td>電話號(hào)碼:</td><td><input type="text" name="telephone" value="${customer.telephone}"></td></tr><tr><td>郵箱地址:</td><td><input type="text" name="email" value="${customer.email}"></td></tr><tr><td>照片:</td><td><input type="file" name="photo" value="${customer.photo}"></td></tr></table><button type="submit">保存</button> </form><script src="${BASE}/asset/lib/jquery/jquery.min.js"></script> <script src="${BASE}/asset/lib/jquery-form/jquery.form.min.js"></script> <script>$(function () {$('#customer_form').ajaxForm({type:'post',url:'${BASE}/customer_create',success:function (data) {if(data){location.href = '${BASE}/customer';}}});}); </script> </body> </html>當(dāng)表單提交時(shí),請(qǐng)求會(huì)轉(zhuǎn)發(fā)到CustomerController的createSubmit方法上。該方法帶有一個(gè)Param參數(shù),我們打算通過該參數(shù)來獲取“表單字段的名值對(duì)映射”與“所上傳的文件參數(shù)對(duì)象”,應(yīng)該如何編碼呢?下面是我們要實(shí)現(xiàn)的目標(biāo):
@Controller public class CustomerController {/*** 處理 創(chuàng)建客戶請(qǐng)求 - 帶圖片*/@Action("post:/customer_create")public Data createSubmit(Param param){Map<String,Object> fieldMap = param.getFieldMap();FileParam fileParam = param.getFile("photo");boolean result = customerService.createCustomer(fieldMap,fileParam);return new Data(result);} }調(diào)用Param的getFieldMap()方法來獲取表單字段的鍵值對(duì)映射(Map?fieldMap),指定一個(gè)具體的文件字段名稱photo,并調(diào)用getFile方法即可獲取對(duì)應(yīng)的文件參數(shù)對(duì)象(FileParam?fileParam)。隨后,可調(diào)用customerService的createCustomer方法,將fieldMap與fileParam這兩個(gè)參數(shù)傳入。
Controller層的代碼就是這樣,具體業(yè)務(wù)邏輯都在Service層了,對(duì)于CustomerService而言,只需寫幾行代碼即可實(shí)現(xiàn)業(yè)務(wù)邏輯,將輸入?yún)?shù)存入數(shù)據(jù)庫,同時(shí)將文件上傳到服務(wù)器上。
@Service public class CustomerService {/*** 創(chuàng)建客戶*/@Transactionpublic boolean createCustomer(Map<String,Object> fieldMap,FileParam fileParam){Boolean result = DBHelper.insertEntity(Customer.class,fieldMap);if (result){UploadHelper.uploadFile("/tmp/upload/",fileParam);}return result;} }可見,除了使用DatabaseHelper操作數(shù)據(jù)庫,還可以通過UploadHelper將文件上傳到指定的服務(wù)器目錄中。
注意:實(shí)際上,完全可以通過代碼來讀取配置文件中定義的文件上傳路徑,此處只是為了簡(jiǎn)化,請(qǐng)注意。
我們把計(jì)劃要完成的事情總結(jié)一下:
(1)改造Param結(jié)構(gòu),可以通過它來獲取已上傳的文件參數(shù)(FileParam)
(2)使用UploadHelper助手類來上傳文件。
實(shí)現(xiàn)文件上傳功能?
我們不妨從FileParam開始,它實(shí)際上是一個(gè)用于封裝文件參數(shù)的JavaBean,代碼如下:
/*** @program: FileParam* @description: 封裝文件參數(shù)的Bean*/ public class FileParam {private String fieldName; //文件表單的字段名private String fileName; //文件名private long fileSize; //文件大小private String contentType; //上傳文件的Content-Type,可判斷文件類型private InputStream inputStream; //上傳文件的字節(jié)輸入流public FileParam(String fieldName, String fileName, long fileSize, String contentType, InputStream inputStream) {this.fieldName = fieldName;this.fileName = fileName;this.fileSize = fileSize;this.contentType = contentType;this.inputStream = inputStream;}public String getFieldName() {return fieldName;}public String getFileName() {return fileName;}public long getFileSize() {return fileSize;}public String getContentType() {return contentType;}public InputStream getInputStream() {return inputStream;} }除了文件參數(shù)(FileParam),我們還需要一個(gè)表單參數(shù)(FormParam),代碼如下:?
/*** @program: FormParam* @description: 封裝表單參數(shù)*/ public class FormParam {private String fieldName; //表單字段名private Object fieldValue; //表單字段值public FormParam(String fieldName, Object fieldValue) {this.fieldName = fieldName;this.fieldValue = fieldValue;}public String getFieldName() {return fieldName;}public Object getFieldValue() {return fieldValue;} }在一個(gè)表單中,所有的參數(shù)可分為兩類:表單參數(shù)與文件參數(shù)。有必要將Param類做一個(gè)重構(gòu),讓它封裝這兩類參數(shù),并提供一系列的get方法,用于從該對(duì)象中獲取指定的參數(shù)。
/*** @program: Param* @description: 請(qǐng)求參數(shù)對(duì)象*/ public class Param {private List<FormParam> formParamList;private List<FileParam> fileParamList;public Param(List<FormParam> formParamList) {this.formParamList = formParamList;}public Param(List<FormParam> formParamList, List<FileParam> fileParamList) {this.formParamList = formParamList;this.fileParamList = fileParamList;}/*** 獲取請(qǐng)求參數(shù)映射* @return*/public Map<String,Object> getFieldMap(){Map<String,Object> fieldMap = new HashMap<String,Object>();if (CollectionUtil.isNotEmpty(formParamList)){for (FormParam formParam:formParamList){String fieldName = formParam.getFieldName(); //表單參數(shù)名Object fieldValue = formParam.getFieldValue(); //表單參數(shù)值if (fieldMap.containsKey(fieldName)){ //如果已經(jīng)有此參數(shù)名fieldValue = fieldMap.get(fieldName) + StringUtil.SEPARATOR + fieldValue; // 舊的數(shù)據(jù)<-->新的數(shù)據(jù)作為value }fieldMap.put(fieldName,fieldValue);}}return fieldMap;}/*** 獲取上傳文件映射*/public Map<String,List<FileParam>> getFileMap(){Map<String,List<FileParam>> fileMap = new HashMap<String,List<FileParam>>();if (CollectionUtil.isNotEmpty(fileMap)){for (FileParam fileParam:fileParamList){ //遍歷文件參數(shù)String fieldName = fileParam.getFieldName(); //獲取表單文件字段名List<FileParam> fileParamList;if (fileMap.containsKey(fieldName)){ //如果Map已經(jīng)存在fileParamList = fileMap.get(fieldName); //獲取Map中的值}else{fileParamList = new ArrayList<FileParam>(); //否則,新建一個(gè)值 }fileParamList.add(fileParam); //值fileMap.put(fieldName,fileParamList); //放入到表單文件字段名,List<FileParam>的映射中 }}return fileMap;}/*** 獲取所有上傳文件* @param fieldName 表單文件字段名* @return*/public List<FileParam> getFileList(String fieldName){return getFileMap().get(fieldName);}/*** 獲取唯一上傳文件* @param fieldName 表單文件字段名* @return*/public FileParam getFile(String fieldName){List<FileParam> fileParamList = getFileList(fieldName);if (CollectionUtil.isNotEmpty(fileParamList) && fileParamList.size() ==1){return fileParamList.get(0);}return null;}/*** 驗(yàn)證參數(shù)是否為空* @return*/public boolean isEmpty(){return CollectionUtil.isEmpty(formParamList) && CollectionUtil.isEmpty(fileParamList);}/*** 根據(jù)參數(shù)名獲取String型參數(shù)值* @param name* @return*/public String getString(String name){return CastUtil.castString(getFieldMap().get(name));}/*** 根據(jù)參數(shù)名獲取Double型參數(shù)值* @param name* @return*/public Double getDouble(String name){return CastUtil.castDouble(getFieldMap().get(name));}/*** 根據(jù)參數(shù)名獲取Long型參數(shù)值* @param name* @return*/public long getLong(String name){return CastUtil.castLong(getFieldMap().get(name));}/*** 根據(jù)參數(shù)名獲取int型參數(shù)值* @param name* @return*/public int getInt(String name){return CastUtil.castInt(getFieldMap().get(name));}/*** 根據(jù)參數(shù)名獲取boolean型參數(shù)值* @param name* @return*/public boolean getBoolean(String name){return CastUtil.castBoolean(getFieldMap().get(name));}}可見Param包含了兩個(gè)成員變量:List<formParamList>與List<fileParamList>;它們分別封裝了表單參數(shù)與文件參數(shù),隨后提供了兩個(gè)構(gòu)造器,用于初始化Param對(duì)象,還提供了兩個(gè)get方法,分別用于獲取所有的表單參數(shù)與文件參數(shù)。返回值均為Map類型,其中Map表示請(qǐng)求參數(shù)映射,Map表示上傳文件映射。對(duì)于同名的請(qǐng)求參數(shù),通過一個(gè)特殊的分隔符進(jìn)行了處理,該分隔符定義在StringUtil類中,代碼如下:
/*** 分隔符*/public static final String SEPARATOR = String .valueOf((char)29);對(duì)于同名的上傳文件,通過一個(gè)List進(jìn)行了封裝,可輕松實(shí)現(xiàn)多文件上傳的需求。可通過List?getFileList(String fieldName)?方法獲取所有上傳文件,若只上傳了一個(gè)文件,則可直接使用FileParam?getFile(String fieldName)方法獲取唯一上傳文件。還提供了一個(gè)boolean?isEmpty()方法,用于驗(yàn)證參數(shù)是否為空。最后,提供了一組根據(jù)參數(shù)名獲取指定類型的方法,例如,String?getString(String name)、double getDouble(String name)等。
可借助Apache?Commons提供的FileUpload類庫實(shí)現(xiàn)文件上傳特性,首先需要在pom.xml中添加如下依賴:
<!--文件上傳--><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version></dependency>接下來我們需要編寫一個(gè)UploadHelper類來封裝Apache?Commons?FileUpload的相關(guān)代碼:
import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.smart4j.framework.bean.FileParam; import org.smart4j.framework.bean.FormParam; import org.smart4j.framework.bean.Param; import org.smart4j.framework.util.CollectionUtil; import org.smart4j.framework.util.FileUtil; import org.smart4j.framework.util.StreamUtil; import org.smart4j.framework.util.StringUtil;import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.Map;/*** @program: UploadHelper* @description: 文件上傳助手類* @author: Created by Autumn* @create: 2018-12-14 16:21*/ public final class UploadHelper {private static final Logger LOGGER = LoggerFactory.getLogger(UploadHelper.class);/*** Apache Commons FileUpload提供的Servlet文件上傳對(duì)象*/private static ServletFileUpload servletFileUpload;/*** 初始化*/public static void init(ServletContext servletContext){/*獲取tomcat的work目錄*/File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");/*** DiskFileItemFactory構(gòu)造的兩個(gè)參數(shù)* 第一個(gè)參數(shù):sizeThreadHold - 設(shè)置緩存(內(nèi)存)保存多少字節(jié)數(shù)據(jù),默認(rèn)為10240字節(jié),即10K* 如果一個(gè)文件沒有大于10K,則直接使用內(nèi)存直接保存成文件就可以了。* 如果一個(gè)文件大于10K,就需要將文件先保存到臨時(shí)目錄中去。* 第二個(gè)參數(shù) File 是指臨時(shí)目錄位置 - 可以不用tomcat的work目錄可以用任意一個(gè)目錄*/DiskFileItemFactory fileItemFactory = new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD, repository);servletFileUpload = new ServletFileUpload(fileItemFactory);int uploadLimit = ConfigHelper.getAppUploadLimit(); //獲取文件上傳限制默認(rèn)為10(M)if (uploadLimit != 0){servletFileUpload.setFileSizeMax(uploadLimit*1024*1024); //設(shè)置單文件最大大小為10M }}/*** 判斷請(qǐng)求是否為multipart類型*/public static boolean isMultipart(HttpServletRequest request){return ServletFileUpload.isMultipartContent(request);}/*** 創(chuàng)建請(qǐng)求對(duì)象* 將request轉(zhuǎn)換為Param參數(shù)* @return*/public static Param createParam(HttpServletRequest request) throws IOException {List<FormParam> formParamList = new ArrayList<FormParam>();List<FileParam> fileParamList = new ArrayList<FileParam>();try{/*解析request*/Map<String,List<FileItem>> fileItemListMap = servletFileUpload.parseParameterMap(request); //將request轉(zhuǎn)換為Mapif (CollectionUtil.isNotEmpty(fileItemListMap)){//遍歷Map集合,一個(gè)表單名可能有多個(gè)文件for (Map.Entry<String,List<FileItem>> fileItemListEntry : fileItemListMap.entrySet()){String fieldName = fileItemListEntry.getKey(); //獲取表單字段名List<FileItem> fileItemList = fileItemListEntry.getValue(); //文件集合if (CollectionUtil.isNotEmpty(fileItemListMap)){for (FileItem fileItem:fileItemList){ //遍歷文件集合if (fileItem.isFormField()){ //如果是表單字段String fieldValue = fileItem.getString("UTF-8");formParamList.add(new FormParam(fieldName,fieldValue));}else{ //如果是文件String fileName = FileUtil.getRealFileName(new String(fileItem.getName().getBytes(),"UTF-8")); //獲取文件名if (StringUtil.isNotEmpty(fileName)){ //如果文件名不為空long fileSize = fileItem.getSize(); //獲取文件大小String contentType = fileItem.getContentType(); //獲取文件類型InputStream inputStream = fileItem.getInputStream(); //獲取文件輸入流fileParamList.add(new FileParam(fieldName,fileName,fileSize,contentType,inputStream));}}}}}}} catch (FileUploadException e) {LOGGER.error("create param failure",e);throw new RuntimeException(e);}return new Param(formParamList,fileParamList);}/*** 上傳文件* @param basePath* @param fileParam*/public static void uploadFile(String basePath,FileParam fileParam){try{if (fileParam != null){String filePath = basePath + fileParam.getFileName(); //路徑+文件名FileUtil.createFile(filePath); //創(chuàng)建文件InputStream inputStream = new BufferedInputStream(fileParam.getInputStream()); //獲取文件的輸入流OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(filePath)); //獲取輸出流StreamUtil.copyStream(inputStream,outputStream); //輸入流拷貝到輸出流中 }} catch (FileNotFoundException e) {LOGGER.error("upload file failure",e);throw new RuntimeException(e);}}/*** 批量上傳文件* @param basePath* @param fileParamList*/public static void uploadFile(String basePath,List<FileParam> fileParamList){try {if (CollectionUtil.isNotEmpty(fileParamList)){for (FileParam fileParam : fileParamList){uploadFile(basePath,fileParam);}}}catch (Exception e){LOGGER.error("upload file failure",e);throw new RuntimeException(e);}} }需要提供一個(gè)init方法,在該方法中初始化ServletFileUpload對(duì)象。一般情況下,只需設(shè)置一個(gè)上傳文件的臨時(shí)目錄與上傳文件的最大限制;上傳文件的臨時(shí)目錄可設(shè)置為應(yīng)用服務(wù)器的臨時(shí)目錄,上傳文件的最大限制可讓用戶自行配置。所以我們使用了ConfigHelper.getAppUploadLimit()來獲取,可以在smart.properties文件中進(jìn)行配置。
首先,在ConfigConstant中添加一個(gè)配置常量APP_UPLOAD_LIMIT;
String APP_UPLOAD_LIMIT = "smart.framework.app.upload_limit";這也就意味著,我們可以在smart.properties文件中使用smart.framwork.app.upload_limit配置項(xiàng)來設(shè)定上傳文件的最大限制。
然后,在ConfigHelper中添加一個(gè)int getAppUploadLimit()方法,用于獲取該配置的值,此時(shí)可設(shè)置該配置的初始值(10),也就是說,若不在smart.properties文件中提供該配置,則上傳文件的最大限制是10MB。
public class ConfigHelper {/*** 獲取應(yīng)用文件上傳限制* @return*/public static int getAppUploadLimit(){return PropsUtil.getInt(CONFIG_PROPS,ConfigConstant.APP_UPLOAD_LIMIT,10);} }在UploadHelper中提供一個(gè)boolean isMultipart(HttpServletRequest request)方法,用于判斷當(dāng)前請(qǐng)求對(duì)象是否為multipart類型。只有在上傳文件時(shí)對(duì)應(yīng)的請(qǐng)求類型才是multipart類型,也就是說,可通過isMultipart方法來判斷當(dāng)前請(qǐng)求時(shí)否為文件上傳請(qǐng)求。
接下來提供一個(gè)非常重要的方法,可從當(dāng)前請(qǐng)求中創(chuàng)建Param對(duì)象,它就是Param?createParam(HttpServletRequest?request)方法:其中我們使用了ServletFileUpload對(duì)象來解析請(qǐng)求參數(shù),并通過遍歷所有請(qǐng)求參數(shù)來初始化List?formParamList與List?fileParamList變量的值。在遍歷請(qǐng)求參數(shù)時(shí),需要對(duì)當(dāng)前的org.apache.commons.fileupload.FileItem對(duì)象進(jìn)行判斷,若為普通表單字段(調(diào)用fileItem.isFormField()返回true),則創(chuàng)建FormParam對(duì)象,并添加到formParamList對(duì)象中。否則即為文件上傳字段,通過FileUtil提供的getRealFileName來獲取上傳文件后的真實(shí)文件名,并從FileItem對(duì)象中構(gòu)造FileParam對(duì)象,添加到fileParamList對(duì)象中,最后,通過formParamList與fileParamList來構(gòu)造Param對(duì)象并返回。
FileUtil代碼如下
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException;/*** @program: FileUtil* @description: 文件操作工具類* @author: Created by Autumn* @create: 2018-12-19 13:03*/ public class FileUtil {private static final Logger LOGGER = LoggerFactory.getLogger(FileUtil.class);/*** 獲取真實(shí)文件名(自動(dòng)去掉文件路徑)** @param fileName* @return*/public static String getRealFileName(String fileName) {return FilenameUtils.getName(fileName);}/*** 創(chuàng)建文件** @param filePath* @return*/public static File createFile(String filePath) {File file;file = new File(filePath); //根據(jù)路徑創(chuàng)建文件try {File parentDir = file.getParentFile(); //獲取文件父目錄if (!parentDir.exists()) { //判斷上層目錄是否存在FileUtils.forceMkdir(parentDir); //創(chuàng)建父級(jí)目錄 }} catch (IOException e) {LOGGER.error("create file failure",e);throw new RuntimeException(e);//e.printStackTrace(); }return file;} }最后提供兩個(gè)用于上傳文件的方法,一個(gè)用于上傳單個(gè)文件,另一個(gè)用于批量上傳。此時(shí)用到了StreamUtil工具類的copyStream方法,代碼如下:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*;/*** @program: StreamUtil* @description: 流操作常用工具類* @author: Created by Autumn* @create: 2018-10-24 15:41*/ public class StreamUtil {private static final Logger LOGGER = LoggerFactory.getLogger(StreamUtil.class);/*** 從輸入流中獲取字符串* @param is* @return*/public static String getString(InputStream is){StringBuilder sb = new StringBuilder();try {BufferedReader reader = new BufferedReader(new InputStreamReader(is));String line;while((line=reader.readLine())!=null){sb.append(line);}} catch (IOException e) {LOGGER.error("get string failure",e);throw new RuntimeException(e);}return sb.toString();}/*** 將輸入流復(fù)制到輸出流* @param inputStream 輸入流* @param outputStream 輸出流*/public static void copyStream(InputStream inputStream, OutputStream outputStream){try {int length;byte[] buffer = new byte[4*1024];while((length = inputStream.read(buffer,0,buffer.length)) != -1){outputStream.write(buffer,0,length);}outputStream.flush();} catch (IOException e) {LOGGER.error("copy stream failure",e);throw new RuntimeException(e);} finally {try {inputStream.close();outputStream.close();} catch (IOException e) {LOGGER.error("close stream failure",e);}}}}現(xiàn)在UploadHelper已編寫完畢,接下來需要找一個(gè)地方來調(diào)用init方法。整個(gè)web框架的入口也就是DispatcherServlet的init方法了,所有我們需要在該方法中調(diào)用UploadHelper的init方法。
除了在DispatcherServlet的init方法中添加一行代碼,還需要對(duì)service代碼進(jìn)行一些重構(gòu)。首先需要跳過/favicon.ico請(qǐng)求,只處理普通的請(qǐng)求。然后需要判斷請(qǐng)求對(duì)象是否為上傳文件,針對(duì)兩種不同的情況來創(chuàng)建Param對(duì)象,其中通過UploadHelper來創(chuàng)建的方式已在前面描述了。相應(yīng)的,我們也對(duì)以前的代碼進(jìn)行封裝,提供一個(gè)名為RequestHelper類,并通過它的createParam方法來初始化Param對(duì)象。
import org.smart4j.framework.bean.FormParam; import org.smart4j.framework.bean.Param; import org.smart4j.framework.util.ArrayUtil; import org.smart4j.framework.util.CodecUtil; import org.smart4j.framework.util.StreamUtil; import org.smart4j.framework.util.StringUtil; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List;/*** @program: RequestHelper* @description: 請(qǐng)求助手類* @author: Created by Autumn* @create: 2018-12-25 13:22*/ public class RequestHelper {public static Param createParam(HttpServletRequest request) throws IOException {List<FormParam> formParamList = new ArrayList<>();formParamList.addAll(parseParameterNames(request));formParamList.addAll(parseInputStream(request));return new Param(formParamList);}/*** 獲取Form表單普通參數(shù)并放入List<FormParam>中* 適用于application/x-www-form-urlencoded* @param request* @return List<FormParam>*/private static List<FormParam> parseParameterNames(HttpServletRequest request){List<FormParam> formParamList = new ArrayList<FormParam>();Enumeration<String> paramNames = request.getParameterNames(); //獲取request中的所有參數(shù)名稱枚舉while (paramNames.hasMoreElements()){ //遍歷參數(shù)名枚舉String fieldName = paramNames.nextElement(); //獲取參數(shù)名稱//!!!!!!!!獲取參數(shù)值(例如CheckBox的值有多個(gè)) request.getParameter(String name)是獲得相應(yīng)名的數(shù)據(jù),如果有重復(fù)的名,則返回第一個(gè)的值.String[] fieldValues = request.getParameterValues(fieldName);if (ArrayUtil.isNotEmpty(fieldValues)){ //判斷是否為空Object fieldValue; //參數(shù)最終值if (fieldValues.length == 1){ //如果只有一個(gè)值fieldValue = fieldValues[0]; //直接賦值} else { //如果有多個(gè)值(CheckBox多選)StringBuilder sb = new StringBuilder("");for (int i = 0; i< fieldValues.length; i++){ //遍歷 sb.append(fieldValues[i]);if (i != fieldValues.length-1){ //如果不是最后一個(gè)sb.append(StringUtil.SEPARATOR); //加上通用分割符 }}fieldValue = sb.toString();}formParamList.add(new FormParam(fieldName,fieldValue)); //將參數(shù)鍵值對(duì)加入List參數(shù)列表中去 }}return formParamList;}/*** 獲取參數(shù)流并放入List<FormParam>中* 適用于application/json,text/xml,multipart/form-data文本流或者大文件形式提交的請(qǐng)求或者xml等形式的報(bào)文* @param request* @return* @throws IOException*/private static List<FormParam> parseInputStream(HttpServletRequest request) throws IOException {List<FormParam> formParamList = new ArrayList<FormParam>();String body = CodecUtil.decodeURL(StreamUtil.getString(request.getInputStream()));if (StringUtil.isNotEmpty(body)){String[] kvs = StringUtil.splitString(body,"&");if (ArrayUtil.isNotEmpty(kvs)){for (String kv:kvs) {String[] array = StringUtil.splitString(kv, "=");if (ArrayUtil.isNotEmpty(array) && array.length == 2){String fieldName = array[0];String fieldValue = array[1];formParamList.add(new FormParam(fieldName,fieldValue));}}}}return formParamList;} }可見以上代碼邏輯并未變化,只是將以前放在DispatcherServlet中的相關(guān)代碼搬到了RequestHelper中了。最后獲取的View同樣也分兩種情況進(jìn)行了處理,只是此時(shí)并未提供其他類來封裝這些代碼,而是直接在當(dāng)前類中添加了兩個(gè)私有方法handleViewResult與handleDataResult。
重構(gòu)后的Dispatcher代碼
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.smart4j.framework.bean.Data; import org.smart4j.framework.bean.Handler; import org.smart4j.framework.bean.Param; import org.smart4j.framework.bean.View; import org.smart4j.framework.helper.*; import org.smart4j.framework.util.*; import javax.servlet.*; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Method; import java.util.Enumeration; import java.util.HashMap; import java.util.Map;/*** @program: DispatcherServlet* @description: 請(qǐng)求轉(zhuǎn)發(fā)器* @author: Created by Autumn* @create: 2018-10-24 11:34*/@WebServlet(urlPatterns = "/*",loadOnStartup = 0) public class DispatcherServlet extends HttpServlet {private static final Logger LOGGER = LoggerFactory.getLogger(DispatcherServlet.class);@Overridepublic void init(ServletConfig servletConfig) throws ServletException {//初始化相關(guān)Helper類 HelperLoader.init();//獲取ServletContext對(duì)象(用于注冊(cè)Servlet)ServletContext servletContext = servletConfig.getServletContext();//注冊(cè)處理JSP的ServletServletRegistration jspServlet = servletContext.getServletRegistration("jsp");jspServlet.addMapping(ConfigHelper.getAppJspPath()+"*");//注冊(cè)處理靜態(tài)資源的默認(rèn)ServletServletRegistration defaultServlet = servletContext.getServletRegistration("default");defaultServlet.addMapping(ConfigHelper.getAppAssetPath()+"*");//初始化上傳文件大小,以及超過最大大小存放的目錄 UploadHelper.init(servletContext);}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//獲取請(qǐng)求方法與請(qǐng)求路徑String requestMethod = req.getMethod().toLowerCase();String requestPath = req.getPathInfo();if (requestPath.equals("\favicon.ico")){return ;}//獲取Action處理器Handler handler= ControllerHelper.getHandler(requestMethod,requestPath);if(handler!=null){//獲取Controller類機(jī)器Bean實(shí)例Class<?> controllerClass = handler.getControllerClass();Object controllerBean = BeanHelper.getBean(controllerClass);Param param;if (UploadHelper.isMultipart(req)){ //如果是multipart/form-data streamparam = UploadHelper.createParam(req); //multipart方式}else{ //如果是非multipart方式提交(即application/x-www-form-urlencoded,application/json,text/xml)param = RequestHelper.createParam(req); //非multipart表單方式 }/*將一下代碼放入RequestHelper中去//創(chuàng)建請(qǐng)求參數(shù)對(duì)象Map<String,Object> paramMap = new HashMap<String, Object>();Enumeration<String> paramNames = req.getParameterNames();while(paramNames.hasMoreElements()){String paramName = paramNames.nextElement();String paramValue = req.getParameter(paramName);paramMap.put(paramName,paramValue);}//獲取請(qǐng)求body中的參數(shù)String body = CodecUtil.decodeURL(StreamUtil.getString(req.getInputStream()));if (StringUtil.isNotEmpty(body)){String[] params = StringUtil.splitString(body,"&");if (ArrayUtil.isNotEmpty(params)){for (String param:params){String[] array = StringUtil.splitString(param,"=");if (ArrayUtil.isNotEmpty(array)&&array.length==2){String paramName = array[0];String paramValue = array[1];paramMap.put(paramName,paramValue);}}}}Param param = new Param(paramMap);*/Object result = null;//調(diào)用Action方法Method actionMethod = handler.getActionMethod();/*優(yōu)化沒有參數(shù)的話不需要寫參數(shù)*/if (param.isEmpty()){ //如果沒有參數(shù)result = ReflectionUtil.invokeMethod(controllerBean,actionMethod); //就不傳參數(shù)}else{ //有參數(shù)result = ReflectionUtil.invokeMethod(controllerBean,actionMethod,param); //傳參數(shù) }//處理Action方法返回值if (result instanceof View){//返回JSP頁面 handleViewResult((View) result, req, resp);}else if (result instanceof Data){//返回Json數(shù)據(jù) handleDataResult((Data) result, resp);}}else{LOGGER.error("Request-Handler Mapping get null by Request("+requestMethod+","+requestPath+")");throw new RuntimeException("Request-Handler Mapping get null by Request("+requestMethod+","+requestPath+")");}}/*** 處理Json格式的數(shù)據(jù)* @param result Data對(duì)象* @param resp* @throws IOException*/private void handleDataResult(Data result, HttpServletResponse resp) throws IOException {Data data = result;Object model = data.getModel();if (model!=null){resp.setContentType("application/json");resp.setCharacterEncoding("UTF-8");PrintWriter writer = resp.getWriter();String json = JsonUtil.toJson(model);writer.write(json);writer.flush();writer.close();}}/*** 處理視圖結(jié)果* @param result View對(duì)象(jsp路徑+數(shù)據(jù))* @param req* @param resp* @throws IOException* @throws ServletException*/private void handleViewResult(View result, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {View view = result;String path = view.getPath();if (StringUtil.isNotEmpty(path)){if (path.startsWith("/")){ //如果View的Path以/開頭則以項(xiàng)目根目錄為根路徑resp.sendRedirect(req.getContextPath()+path);} else { //如果View的Path沒有以/開頭,則以配置的APPJSP(/WEB-INF/view/)為根目錄Map<String,Object> model = view.getModel();for (Map.Entry<String,Object> entry:model.entrySet()){req.setAttribute(entry.getKey(),entry.getValue());}req.getRequestDispatcher(ConfigHelper.getAppJspPath()+path).forward(req,resp);}}} }此時(shí),一個(gè)簡(jiǎn)單的文件上傳特性已基本具備,可以在框架中正常使用了。
可能出現(xiàn)的問題
獲取注冊(cè)處理JSP的Servlet報(bào)錯(cuò)
問題代碼
這是因?yàn)閠omcat用的是maven插件,并不是真實(shí)的tomcat。所以導(dǎo)致獲取jsp的servlet失敗。
jQuery未引入導(dǎo)致用原生form提交
原生form提交的幾個(gè)要素
action:url 地址,服務(wù)器接收表單數(shù)據(jù)的地址
method:提交服務(wù)器的http方法,一般為post和get
enctype: 表單數(shù)據(jù)提交時(shí)使用的編碼類型,默認(rèn)使用"pplication/x-www-form-urlencoded"。如果是使用POST請(qǐng)求,則請(qǐng)求頭中的content-type指定值就是該值。如果表單中有上傳文件,編碼類型需要使用"multipart/form-data",類型,才能完成傳遞文件數(shù)據(jù)。寫了method、enctype和action后,最后form表單的提交按鈕要用
<input type="submit">保存</input>缺一個(gè)都會(huì)用默認(rèn)的get方式提交。
<form id="customer_form" action="${BASE}/customer_create" method="post" enctype="multipart/form-data"><input type="submit">保存</input> </form>后臺(tái)獲取文件沒有內(nèi)容(此bug由個(gè)人失誤導(dǎo)致,可過濾)
?調(diào)試框架源碼發(fā)現(xiàn)一個(gè)方法判斷有誤,寫成了局部變量fileMap了。寫時(shí)候一個(gè)不小心,調(diào)試要調(diào)試半天吶
最終文件上傳完畢,結(jié)果如下。
框架源碼
項(xiàng)目源碼(使用開發(fā)框架)
?
轉(zhuǎn)載于:https://www.cnblogs.com/aeolian/p/10118806.html
總結(jié)
以上是生活随笔為你收集整理的架构探险笔记10-框架优化之文件上传的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 做梦梦到鱼竿断了是什么意思
- 下一篇: jQuery禁止Ajax请求缓存