架构探险笔记10-框架优化之文件上传

确定文件上传使用场景

通常情况下,我们可以通过一个form(表单)来上传文件,就以下面的“创建客户”为例来说明(对应的文件名是customer_create.jsp),需要提供一个form,并将其enctype属性设为multipart/form-data,表示以form data方式提交表单数据。

注意:enctype的默认值为application/x-www-form-urlencoded,表示以url encoded方式提交表单数据。

下面我们使用jQuery与jQuery Form插件快速编写一个基于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>客户管理-创建客户</title>
</head>
<body><h1>创建客户界面</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>联系人:</td><td><input type="text" name="contact" value="${customer.contact}"></td></tr><tr><td>电话号码:</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>

当表单提交时,请求会转发到CustomerController的createSubmit方法上。该方法带有一个Param参数,我们打算通过该参数来获取“表单字段的名值对映射”与“所上传的文件参数对象”,应该如何编码呢?下面是我们要实现的目标:

@Controller
public class CustomerController {/*** 处理 创建客户请求 - 带图片*/@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);} 
}

调用Param的getFieldMap()方法来获取表单字段的键值对映射(Map fieldMap),指定一个具体的文件字段名称photo,并调用getFile方法即可获取对应的文件参数对象(FileParam fileParam)。随后,可调用customerService的createCustomer方法,将fieldMap与fileParam这两个参数传入。

Controller层的代码就是这样,具体业务逻辑都在Service层了,对于CustomerService而言,只需写几行代码即可实现业务逻辑,将输入参数存入数据库,同时将文件上传到服务器上。

@Service
public class CustomerService {/*** 创建客户*/@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操作数据库,还可以通过UploadHelper将文件上传到指定的服务器目录中。

注意:实际上,完全可以通过代码来读取配置文件中定义的文件上传路径,此处只是为了简化,请注意。

我们把计划要完成的事情总结一下:

(1)改造Param结构,可以通过它来获取已上传的文件参数(FileParam)

(2)使用UploadHelper助手类来上传文件。

实现文件上传功能 

我们不妨从FileParam开始,它实际上是一个用于封装文件参数的JavaBean,代码如下:

/*** @program: FileParam* @description: 封装文件参数的Bean*/
public class FileParam {private String fieldName;  //文件表单的字段名private String fileName;   //文件名private long fileSize;  //文件大小private String contentType;    //上传文件的Content-Type,可判断文件类型private InputStream inputStream;   //上传文件的字节输入流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;}
}

除了文件参数(FileParam),我们还需要一个表单参数(FormParam),代码如下: 

/*** @program: FormParam* @description: 封装表单参数*/
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;}
}

在一个表单中,所有的参数可分为两类:表单参数与文件参数。有必要将Param类做一个重构,让它封装这两类参数,并提供一系列的get方法,用于从该对象中获取指定的参数。

/*** @program: Param* @description: 请求参数对象*/
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;}/*** 获取请求参数映射* @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();   //表单参数名Object fieldValue = formParam.getFieldValue();   //表单参数值if (fieldMap.containsKey(fieldName)){   //如果已经有此参数名fieldValue = fieldMap.get(fieldName) + StringUtil.SEPARATOR + fieldValue;  // 旧的数据<-->新的数据作为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){    //遍历文件参数String fieldName = fileParam.getFieldName();    //获取表单文件字段名List<FileParam> fileParamList;if (fileMap.containsKey(fieldName)){    //如果Map已经存在fileParamList = fileMap.get(fieldName);   //获取Map中的值}else{fileParamList = new ArrayList<FileParam>();  //否则,新建一个值
                }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;}/*** 验证参数是否为空* @return*/public boolean isEmpty(){return CollectionUtil.isEmpty(formParamList) && CollectionUtil.isEmpty(fileParamList);}/*** 根据参数名获取String型参数值* @param name* @return*/public String getString(String name){return CastUtil.castString(getFieldMap().get(name));}/*** 根据参数名获取Double型参数值* @param name* @return*/public Double getDouble(String name){return CastUtil.castDouble(getFieldMap().get(name));}/*** 根据参数名获取Long型参数值* @param name* @return*/public long getLong(String name){return CastUtil.castLong(getFieldMap().get(name));}/*** 根据参数名获取int型参数值* @param name* @return*/public int getInt(String name){return CastUtil.castInt(getFieldMap().get(name));}/*** 根据参数名获取boolean型参数值* @param name* @return*/public boolean getBoolean(String name){return CastUtil.castBoolean(getFieldMap().get(name));}}

可见Param包含了两个成员变量:List<formParamList>与List<fileParamList>;它们分别封装了表单参数与文件参数,随后提供了两个构造器,用于初始化Param对象,还提供了两个get方法,分别用于获取所有的表单参数与文件参数。返回值均为Map类型,其中Map表示请求参数映射,Map表示上传文件映射。对于同名的请求参数,通过一个特殊的分隔符进行了处理,该分隔符定义在StringUtil类中,代码如下:

    /*** 分隔符*/public static final String SEPARATOR = String .valueOf((char)29);

对于同名的上传文件,通过一个List进行了封装,可轻松实现多文件上传的需求。可通过List getFileList(String fieldName) 方法获取所有上传文件,若只上传了一个文件,则可直接使用FileParam getFile(String fieldName)方法获取唯一上传文件。还提供了一个boolean isEmpty()方法,用于验证参数是否为空。最后,提供了一组根据参数名获取指定类型的方法,例如,String getString(String name)、double getDouble(String name)等。

可借助Apache Commons提供的FileUpload类库实现文件上传特性,首先需要在pom.xml中添加如下依赖:

        <!--文件上传--><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version></dependency>

接下来我们需要编写一个UploadHelper类来封装Apache Commons FileUpload的相关代码:

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文件上传对象*/private static ServletFileUpload servletFileUpload;/*** 初始化*/public static void init(ServletContext servletContext){/*获取tomcat的work目录*/File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");/*** DiskFileItemFactory构造的两个参数*  第一个参数:sizeThreadHold - 设置缓存(内存)保存多少字节数据,默认为10240字节,即10K*    如果一个文件没有大于10K,则直接使用内存直接保存成文件就可以了。*    如果一个文件大于10K,就需要将文件先保存到临时目录中去。*  第二个参数 File 是指临时目录位置 - 可以不用tomcat的work目录可以用任意一个目录*/DiskFileItemFactory fileItemFactory = new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD, repository);servletFileUpload = new ServletFileUpload(fileItemFactory);int uploadLimit = ConfigHelper.getAppUploadLimit();  //获取文件上传限制默认为10(M)if (uploadLimit != 0){servletFileUpload.setFileSizeMax(uploadLimit*1024*1024);   //设置单文件最大大小为10M
        }}/*** 判断请求是否为multipart类型*/public static boolean isMultipart(HttpServletRequest request){return ServletFileUpload.isMultipartContent(request);}/*** 创建请求对象* 将request转换为Param参数* @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转换为Mapif (CollectionUtil.isNotEmpty(fileItemListMap)){//遍历Map集合,一个表单名可能有多个文件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);  //创建文件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);}}
}

需要提供一个init方法,在该方法中初始化ServletFileUpload对象。一般情况下,只需设置一个上传文件的临时目录与上传文件的最大限制;上传文件的临时目录可设置为应用服务器的临时目录,上传文件的最大限制可让用户自行配置。所以我们使用了ConfigHelper.getAppUploadLimit()来获取,可以在smart.properties文件中进行配置。

首先,在ConfigConstant中添加一个配置常量APP_UPLOAD_LIMIT;

    String APP_UPLOAD_LIMIT = "smart.framework.app.upload_limit";

这也就意味着,我们可以在smart.properties文件中使用smart.framwork.app.upload_limit配置项来设定上传文件的最大限制。

然后,在ConfigHelper中添加一个int getAppUploadLimit()方法,用于获取该配置的值,此时可设置该配置的初始值(10),也就是说,若不在smart.properties文件中提供该配置,则上传文件的最大限制是10MB。

public class ConfigHelper {/*** 获取应用文件上传限制* @return*/public static int getAppUploadLimit(){return PropsUtil.getInt(CONFIG_PROPS,ConfigConstant.APP_UPLOAD_LIMIT,10);}
}

在UploadHelper中提供一个boolean isMultipart(HttpServletRequest request)方法,用于判断当前请求对象是否为multipart类型。只有在上传文件时对应的请求类型才是multipart类型,也就是说,可通过isMultipart方法来判断当前请求时否为文件上传请求。

接下来提供一个非常重要的方法,可从当前请求中创建Param对象,它就是Param createParam(HttpServletRequest request)方法:其中我们使用了ServletFileUpload对象来解析请求参数,并通过遍历所有请求参数来初始化List formParamList与List fileParamList变量的值。在遍历请求参数时,需要对当前的org.apache.commons.fileupload.FileItem对象进行判断,若为普通表单字段(调用fileItem.isFormField()返回true),则创建FormParam对象,并添加到formParamList对象中。否则即为文件上传字段,通过FileUtil提供的getRealFileName来获取上传文件后的真实文件名,并从FileItem对象中构造FileParam对象,添加到fileParamList对象中,最后,通过formParamList与fileParamList来构造Param对象并返回。

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);/*** 获取真实文件名(自动去掉文件路径)** @param fileName* @return*/public static String getRealFileName(String fileName) {return FilenameUtils.getName(fileName);}/*** 创建文件** @param filePath* @return*/public static File createFile(String filePath) {File file;file = new File(filePath);   //根据路径创建文件try {File parentDir = file.getParentFile();   //获取文件父目录if (!parentDir.exists()) {  //判断上层目录是否存在FileUtils.forceMkdir(parentDir);   //创建父级目录
            }} catch (IOException e) {LOGGER.error("create file failure",e);throw new RuntimeException(e);//e.printStackTrace();
        }return file;}
}

最后提供两个用于上传文件的方法,一个用于上传单个文件,另一个用于批量上传。此时用到了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();}/*** 将输入流复制到输出流* @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);}}}}

现在UploadHelper已编写完毕,接下来需要找一个地方来调用init方法。整个web框架的入口也就是DispatcherServlet的init方法了,所有我们需要在该方法中调用UploadHelper的init方法。

除了在DispatcherServlet的init方法中添加一行代码,还需要对service代码进行一些重构。首先需要跳过/favicon.ico请求,只处理普通的请求。然后需要判断请求对象是否为上传文件,针对两种不同的情况来创建Param对象,其中通过UploadHelper来创建的方式已在前面描述了。相应的,我们也对以前的代码进行封装,提供一个名为RequestHelper类,并通过它的createParam方法来初始化Param对象。

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: 请求助手类* @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表单普通参数并放入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中的所有参数名称枚举while (paramNames.hasMoreElements()){   //遍历参数名枚举String fieldName = paramNames.nextElement();   //获取参数名称//!!!!!!!!获取参数值(例如CheckBox的值有多个) request.getParameter(String name)是获得相应名的数据,如果有重复的名,则返回第一个的值.String[] fieldValues = request.getParameterValues(fieldName);if (ArrayUtil.isNotEmpty(fieldValues)){   //判断是否为空Object fieldValue;   //参数最终值if (fieldValues.length == 1){  //如果只有一个值fieldValue = fieldValues[0];   //直接赋值} else {  //如果有多个值(CheckBox多选)StringBuilder sb = new StringBuilder("");for (int i = 0; i< fieldValues.length; i++){  //遍历
                        sb.append(fieldValues[i]);if (i != fieldValues.length-1){  //如果不是最后一个sb.append(StringUtil.SEPARATOR);  //加上通用分割符
                        }}fieldValue = sb.toString();}formParamList.add(new FormParam(fieldName,fieldValue));   //将参数键值对加入List参数列表中去
            }}return formParamList;}/*** 获取参数流并放入List<FormParam>中* 适用于application/json,text/xml,multipart/form-data文本流或者大文件形式提交的请求或者xml等形式的报文* @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中的相关代码搬到了RequestHelper中了。最后获取的View同样也分两种情况进行了处理,只是此时并未提供其他类来封装这些代码,而是直接在当前类中添加了两个私有方法handleViewResult与handleDataResult。

重构后的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: 请求转发器* @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 {//初始化相关Helper类
        HelperLoader.init();//获取ServletContext对象(用于注册Servlet)ServletContext servletContext = servletConfig.getServletContext();//注册处理JSP的ServletServletRegistration jspServlet = servletContext.getServletRegistration("jsp");jspServlet.addMapping(ConfigHelper.getAppJspPath()+"*");//注册处理静态资源的默认ServletServletRegistration defaultServlet = servletContext.getServletRegistration("default");defaultServlet.addMapping(ConfigHelper.getAppAssetPath()+"*");//初始化上传文件大小,以及超过最大大小存放的目录
        UploadHelper.init(servletContext);}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//获取请求方法与请求路径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类机器Bean实例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中去//创建请求参数对象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);}//获取请求body中的参数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;//调用Action方法Method actionMethod = handler.getActionMethod();/*优化没有参数的话不需要写参数*/if (param.isEmpty()){  //如果没有参数result = ReflectionUtil.invokeMethod(controllerBean,actionMethod);  //就不传参数}else{  //有参数result = ReflectionUtil.invokeMethod(controllerBean,actionMethod,param);  //传参数
            }//处理Action方法返回值if (result instanceof View){//返回JSP页面
                handleViewResult((View) result, req, resp);}else if (result instanceof Data){//返回Json数据
                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格式的数据* @param result Data对象* @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();}}/*** 处理视图结果* @param result View对象(jsp路径+数据)* @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以/开头则以项目根目录为根路径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);}}}
}

此时,一个简单的文件上传特性已基本具备,可以在框架中正常使用了。

可能出现的问题

获取注册处理JSP的Servlet报错

问题代码

这是因为tomcat用的是maven插件,并不是真实的tomcat。所以导致获取jsp的servlet失败。

jQuery未引入导致用原生form提交

原生form提交的几个要素

action:url 地址,服务器接收表单数据的地址

method:提交服务器的http方法,一般为post和get

enctype: 表单数据提交时使用的编码类型,默认使用"pplication/x-www-form-urlencoded"。如果是使用POST请求,则请求头中的content-type指定值就是该值。如果表单中有上传文件,编码类型需要使用"multipart/form-data",类型,才能完成传递文件数据。

写了method、enctype和action后,最后form表单的提交按钮要用

<input type="submit">保存</input>

缺一个都会用默认的get方式提交。

<form id="customer_form" action="${BASE}/customer_create" method="post" enctype="multipart/form-data"><input type="submit">保存</input>
</form>

后台获取文件没有内容(此bug由个人失误导致,可过滤)

 调试框架源码发现一个方法判断有误,写成了局部变量fileMap了。写时候一个不小心,调试要调试半天呐

最终文件上传完毕,结果如下。

框架源码

项目源码(使用开发框架)

 

转载于:https://www.cnblogs.com/aeolian/p/10118806.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/388682.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows Server 2003 DNS服务安装篇

导读-- DNS(Domain Name System&#xff0c;域名系统)是一种组织成层次结构的分布式数据库&#xff0c;里面包含有从DNS域名到各种数据类型(如IP地址)的映射“贵有恒&#xff0c;何必三更起五更勤;最无益&#xff0c;只怕一日曝十日寒。”前一段时间巴哥因为一些生活琐事而中止…

arima模型怎么拟合_7个统计测试,用于验证和帮助拟合ARIMA模型

arima模型怎么拟合什么是ARIMA&#xff1f; (What is ARIMA?) ARIMA models are one of the most classic and most widely used statistical forecasting techniques when dealing with univariate time series. It basically uses the lag values and lagged forecast error…

[WPF]ListView点击列头排序功能实现

[WPF]ListView点击列头排序功能实现 这是一个非常常见的功能&#xff0c;要求也很简单&#xff0c;在Column Header上显示一个小三角表示表示现在是在哪个Header上的正序还是倒序就可以了。微软的MSDN也已经提供了实现方式。微软的方法中&#xff0c;是通过ColumnHeader Templ…

天池幸福感的数据处理_了解幸福感与数据(第1部分)

天池幸福感的数据处理In these exceptional times, the lockdown left many of us with a lot of time to think. Think about the past and the future. Think about our way of life and our achievements. But most importantly, think about what has been and would be ou…

红草绿叶

从小到大喜欢阴天&#xff0c;喜欢下雨&#xff0c;喜欢那种潮湿的感觉。却又丝毫容不得脚上有一丝的水汽&#xff0c;也极其讨厌穿凉鞋。小时候特别喜欢去山上玩&#xff0c;偷桃子柿子&#xff0c;一切一切都成了美好的回忆&#xff0c;长大了&#xff0c;那些事情就都不复存…

詹森不等式_注意詹森差距

詹森不等式背景 (Background) In Kaggle’s M5 Forecasting — Accuracy competition, the square root transformation ruined many of my team’s forecasts and led to a selective patching effort in the eleventh hour. Although it turned out well, we were reminded t…

数据分析师 需求分析师_是什么让分析师出色?

数据分析师 需求分析师重点 (Top highlight)Before we dissect the nature of analytical excellence, let’s start with a quick summary of three common misconceptions about analytics from Part 1:在剖析卓越分析的本质之前&#xff0c;让我们从第1部分中对分析的三种常…

JQuery发起ajax请求,并在页面动态的添加元素

页面html代码&#xff1a; <li><div class"coll-tit"><span class"coll-icon"><iclass"sysfont coll-default"></i>全域旅游目的地</span></div><div class"coll-panel"><div c…

MAYA插件入门

我们知道&#xff0c; MAYA 是一个基于结点的插件式软件架构&#xff0c;这种开放式的软件架构是非常优秀的&#xff0c;它可以让用户非常方便地在其基础上开发一些自已想要的插件&#xff0c;从而实现一些特殊的功能或效果。 在MAYA上开发自已的插件&#xff0c;你有3种选择&a…

(原創) 如何使用C++/CLI读/写jpg檔? (.NET) (C++/CLI) (GDI+) (C/C++) (Image Processing)

Abstract因为Computer Vision的作业&#xff0c;之前都是用C# GDI写&#xff0c;但这次的作业要做Grayscale Dilation&#xff0c;想用STL的Generic Algorithm写&#xff0c;但C Standard Library并无法读取jpg档&#xff0c;用其它Library又比较麻烦&#xff0c;所以又回头想…

猫眼电影评论_电影的人群意见和评论家的意见一样好吗?

猫眼电影评论Ryan Bellgardt’s 2018 movie, The Jurassic Games, tells the story of ten death row inmates who must compete for survival in a virtual reality game where they not only fight each other but must also fight dinosaurs which can kill them both in th…

c#对文件的读写

最近需要对一个文件进行数量的分割&#xff0c;因为数据量庞大&#xff0c;所以就想到了通过写程序来处理。将代码贴出来以备以后使用。 //读取文件的内容 放置于StringBuilder 中 StreamReader sr new StreamReader(path, Encoding.Default); String line; StringBuilder sb …

ai前沿公司_美术是AI的下一个前沿吗?

ai前沿公司In 1950, Alan Turing developed the Turing Test as a test of a machine’s ability to display human-like intelligent behavior. In his prolific paper, he posed the following questions:1950年&#xff0c;阿兰图灵开发的图灵测试作为一台机器的显示类似人类…

关于WKWebView高度的问题的解决

关于WKWebView高度的问题的解决 IOS端嵌入网页的方式有两种UIWebView和WKWebView。其中WKWebView的性能要高些;WKWebView的使用也相对简单 WKWebView在加载完成后&#xff0c;在相应的代理里面获取其内容高度&#xff0c;大多数网上的方法在获取高度是会出现一定的问题&#xf…

测试nignx php请求并发数,nginx 优化(突破十万并发)

一般来说nginx 配置文件中对优化比较有作用的为以下几项&#xff1a;worker_processes 8;nginx 进程数&#xff0c;建议按照cpu 数目来指定&#xff0c;一般为它的倍数。worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;为每个进…

mardown 标题带数字_标题中带有数字的故事更成功吗?

mardown 标题带数字统计 (Statistics) I have read a few stories on Medium about writing advice, and there were some of them which, along with other tips, suggested that putting numbers in your story’s title will increase the number of views, as people tend …

使用Pandas 1.1.0进行稳健的2个DataFrames验证

Pandas is one of the most used Python library for both data scientist and data engineers. Today, I want to share some Python tips to help us do qualification checks between 2 Dataframes.Pandas是数据科学家和数据工程师最常用的Python库之一。 今天&#xff0c;我…

置信区间的置信区间_什么是置信区间,为什么人们使用它们?

置信区间的置信区间I’m going to try something a little different today, in which I combine two (completely unrelated) topics I love talking about, and hopefully create something that is interesting and educational.今天&#xff0c;我将尝试一些与众不同的东西…

php中wlog是什么意思,d-log模式是什么意思

D-Log是一种高动态范围的视频素材记录格式&#xff0c;总而言之这个色彩模式为后期调色提供了更大的空间。在相机和摄影机拍摄时&#xff0c;一颗高性能的传感器通常支持11档以上的动态范围&#xff0c;而在8bit的照片或视频上&#xff0c;以符合人眼感知的Gamma进行机内处理和…

PowerShell入门(三):如何快速地掌握PowerShell?

如何快速地掌握PowerShell呢&#xff1f;总的来说&#xff0c;就是要尽可能多的使用它&#xff0c;就像那句谚语说的&#xff1a;Practice makes perfect。当然这里还有一些原则和方法让我们可以遵循。 有效利用交互式环境 一般来说&#xff0c;PowerShell有两个主要的运行环境…