十、文件上传

一、文件上传介绍

要将客户端(浏览器)大数据存储到服务器端,不将数据直接存储到数据库中,而是要将数据存储到服务器所在的磁盘上,这就要使用文件上传。
作用:减少了数据库服务器的压力,对数据的操作更加灵活。

二、文件上传原理分析

所谓的文件上传就是服务器端通过request对象获取输入流,将浏览器端上传的数据读取出来,保存到服务器端
浏览器端操作
请求方式必须是post
使用,必须有name属性且有值
表单必须设置encType=”multipart/form-data”

服务器端操作
通过request对象,获取inputStream,就可以将浏览器提交的所有数据读取到.
注意:
这时候,获取其他上传参数的时候,通过request.getParameter(“”)获取不到了.

三、Commons-fileupload

commons-fileupload介绍
Apache 开源组织提供了一个用来处理表单文件上传的一个开源组件( Commons-fileupload ),该组件性能优异,并且其API使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用Commons-fileupload组件实现。

使用Commons-fileupload组件实现文件上传,需要导入该组件相应的支撑jar包:
Commons-fileupload和commons-io。

commons-io 不属于文件上传组件的开发jar文件,但Commons-fileupload 组件从1.1 版本开始,它工作时需要commons-io包的支持。

commons-fileupload API简单介绍
DiskFileItemFactory,用于设置缓存大小及临时文件存储位置
ServletFileUpload,真正用于文件上传的核心类
FIleItem,代表的是一个上传项

入门案例
在浏览器端创建一个可以上传文件的表单,在服务器端通过commons-fileupload完成文件上传。

浏览器端注意三件事情:
1.表单的提交方式为post
2.在表单上添加属性 encType=”multipart/form-data”
3.使用,添加name属性且有值

服务器端:
创建DiskFileItemFactory
创建ServletFileUpload
通过ServletFileUpload的parseRequest方法得到所有的FileItem

浏览器端

服务器端

// 1.创建一个 DiskFileItemFactory
    DiskFileItemFactory factory = new DiskFileItemFactory();

    // 2.创建一个ServletFileUpload
    ServletFileUpload upload = new ServletFileUpload(factory);

    // 3.完成上传操作

    try {
        // 3.1 得到所有上传项
        List items = upload.parseRequest(request);
        // 3.2遍历上传项
        for (FileItem item : items) {
            // 3.3判断是否是上传组件
            if (!item.isFormField()) {
                // 3.4 将文件真正上传
                IOUtils.copy(item.getInputStream(), new FileOutputStream(
                        "f:/upload/a.txt"));
            }
        }
    } catch (FileUploadException e) {
        e.printStackTrace();
    }

1. Commons-fileupload详解-DiskFileItemFactory
设置缓存大小
factory.setSizeThreshold(1024*1024); //设置为1m 默认是10k

设置临时文件存储位置
File temp=new File(this.getServletContext().getRealPath("/temp"));
factory.setRepository(temp); //可以指定临时文件存储位置,默认是系统的临时文件存储位置

2. Commons-fileupload详解-ServletFileUpload
parseRequest方法
List pareRequest(HttpServletRequest request)
得到所有的上传信息,将每一部分映射成FileItem对象.

isMultipartContent方法
boolean isMultipartContent(HttpServletRequest request)
这个方法返回值是boolean,它是用于判断当前表单是否是一个上传的表单,
简单说,就判断它的encType的值是否是 multipart/form-data.

setHeaderEncoding方法
用于解决上传文件名称中文乱码问题

设置上传文件大小
void setFileSizeMax(long fileSizeMax) 设置单个文件上传大小
void setSizeMax(long sizeMax) 设置总文件上传大小

3. Commons-fileupload详解-FileItem
isFormField方法
这个方法返回的是boolean类型,
它是判断当前组件是否是上传组件 简单说,就是判断type="file",
如果返回true,代表不是上传组件,返回false,代表是上传组件

getName方法
获取上传组件的上传文件的名称,如果是非上传组件,返回的是null

getFieldName方法
获取组件名称,简单说,就是表单的元素的name值。

getString方法
获取非上传组件的value值,
通过它也可以获取上传的文件内容,但是,使用它获取不合适。如果使用了commons-fileupload进行文件上传,而上传表单中包含了 非上传组件,获取其值,不能使用request获取.getString()有一个重载的方法
getString(String encoding)可以解决乱码问题

getInputStream方法
通过FileItem.getInputStream();可以获取一个输入流,这个输入流就可以读取出上传文件内容。

    InputStream is = item.getInputStream(); // 用于读取上传文件内容的输入流.
    FileOutputStream fos = new FileOutputStream("f:/upload/" + filename);
    int len = -1;
    byte[] b = new byte[1024 * 10];
    while ((len = is.read(b)) != -1) {
        fos.write(b, 0, len);
        fos.flush();
    }
    fos.close();
    is.close();

可以使用IOUtils工具完成文件copy操作
IOUtils.copy(is, fos);

delete方法
它是用于上传完成后,删除临时文件的

四、 多文件上传

我们在写邮件中可以添加多个附件,那么我们在文件上传时,是不是也可以上传多个文件哪,答案是一定的,那么怎样实现多个文件上传哪?
我们可以通过js实现浏览器端的上传文件框的动态添加。服务器端代码不需要改变。

function addFile() {
    //1.得到id叫content的div
    var div=document.getElementById("content"); 
    //2.向其中添加一段html代码
div.innerHTML+="
"; } function removeFile(btn){ document.getElementById("content").removeChild(btn.parentNode); }

五、文件上传问题

1. 文件重名

每一个客户端都可以进行文件上传操作,那么当我们上传的文件过多,一定会出现同名的文件,那么在服务器端只能保存一个,对于这个问题,我们在上传文件时,就需要考虑文件重名问题.
一般情况下,对于上传文件,为了保证不重名,会给文件起一个随机名.
一种方案是使用uuid.
一种方案是使用毫秒值

2. 存储位置

本质就是上传的文件是否允许浏览器端直接访问。
例如:商品添加时需要一个图片,这个图片一定是可以直接被浏览器端访问的。

允许被浏览器端访问:放置在WebRoot下,但不能是WEB-INF或META-INF下其及子目录下
不允许被浏览器端访问:
若放在工程下则放置在WEB-INF或META-INF及其子目录下.
也可以不放在工程下

3. 目录分离

当我们上传文件过多,并且保存在同一个目录下时,我们就需要考虑怎样处理它们,因为一个目录下文件过多,不仅降低性能,操作时也不方便。
为了防止同一个目录下方上传文件数量过多

  1. 按照上传时间进行目录分离 (周、月 )
  2. 按照上传用户进行目录分离 ----- 为每个用户建立单独目录
  3. 按照固定数量进行目录分离 ------ 假设每个目录只能存放3000个文件 ,每当一个目录存满3000个文件后,创建一个新的目录
  4. 按照唯一文件名的hashcode 进行目录分离
  public static String generateRandomDir(String uuidFileName) {
        // 获得唯一文件名的hashcode
        int hashcode = uuidFileName.hashCode();
        // 获得一级目录
        int d1 = hashcode & 0xf;       
        // 获得二级目录
        int d2 = (hashcode >>> 4) & 0xf;
        return "/" + d2 + "/" + d1;// 共有256目录
    }

六、案例:使用Servlet3.0技术完成文件的上传

技术分析:
【Servlet3.0】

Servlet3.0 与 Servlet2.5:

 * Servlet3.0需要运行在tomcat7以上的服务器中.

 * Servlet3.0以后web.xml就不是必须的.

1.Servlet3.0支持注解开发.

2.支持文件上传.

【Servlet3.0支持注解开发】

使用@WebServlet替换web.xml中配置的Servlet:

@WebServlet(urlPatterns="/ServletDemo1",loadOnStartup=2,initParams=@WebInitParam(name="username",value="root"))

使用@WebListener替换web.xml中监听器的配置:

@WebListener

[使用@WebFilter替换web.xml中的过滤器的配置:](mailto:使用@WebFilter替换web.xml中的过滤器的配置:)

@WebFilter(urlPatterns="/*")

【Servlet3.0的文件上传】
文件上传:
文件上传:指的是将本地的文件 写到 服务器上.

文件上传的要素:
1.表单的提交的方式必须是POST.
2.表单中必须有一个文件上传项:,而且文件上传项必须有name属性和值.

3.表单的enctype属性的值必须是multipart/form-data

文件上传的抓包分析:
未修改enctype属性的时候:

POST /WEB17_WEB/demo1/demo1.jsp HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
X-HttpWatch-RID: 22325-10011
Referer: http://localhost:8080/WEB17_WEB/demo1/demo1.jsp
Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: localhost:8080
Content-Length: 47
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: JSESSIONID=99CD51DA9A47D29200168968AD983E9E

upload=C%3A%5CUsers%5Capple%5CDesktop%5Caaa.txt

已经修改了enctype属性:

POST /WEB17_WEB/demo1/demo1.jsp HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
X-HttpWatch-RID: 22325-10026
Referer: http://localhost:8080/WEB17_WEB/demo1/demo1.jsp
Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Content-Type: multipart/form-data; boundary=---------------------------7e02e526160b66
Accept-Encoding: gzip, deflate
Host: localhost:8080
Content-Length: 224
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: JSESSIONID=99CD51DA9A47D29200168968AD983E9E

-----------------------------7e02e526160b66
Content-Disposition: form-data; name="upload"; filename="C:\Users\apple\Desktop\aaa.txt"
Content-Type: text/plain

Hello shouyi
-----------------------------7e02e526160b66—

【文件上传的原理】
根据分割线将请求体的部分分成几块:
判断 每块是 普通项还是文件上传项.
普通项:获得名称和值.
文件上传项:获得文件名 和 文件内容输入流.

【文件上传的技术】
JspSmartUpload: jspSmartUpload组件是应用JSP进行B/S程序开发过程中经常使用的上传下载组件,它使用简单,方便。现在我又为其加上了下载中文名字的文件的支持,真个是如虎添翼,必将赢得更多开发者的青睐。-Model1年代的文件上传的工具.
FileUpload 是 Apache commons下面的一个子项目,用来实现Java环境下面的文件上传功能,与常见的SmartUpload齐名.应用在Model2年代了.

代码实现:

/**
 * 文件上传的Servlet
 */
@WebServlet("/UploadServlet")
@MultipartConfig
public class UploadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 接收普通项:
        request.setCharacterEncoding("UTF-8");
        String desc = request.getParameter("desc");
        System.out.println("文件描述:"+desc);
        Part part = request.getPart("upload");
        // 获得上传的文件的大小
        long size = part.getSize();
        System.out.println("文件大小"+size);
        String type = part.getContentType();
        System.out.println("文件类型"+type); // text/plain  image/jpeg 
        String name = part.getName();
        System.out.println(name); 
        // 获得文件名:
        String header = part.getHeader("Content-Disposition");
        System.out.println(header);
        
        int idx = header.lastIndexOf("filename=\"");
        String fileName = header.substring(idx+10, header.length()-1);
        System.out.println(fileName);
        
        // 获得文件内容:
        InputStream is = part.getInputStream();
        
        // 获得文件上传路径:
        String path = this.getServletContext().getRealPath("/upload");
        
        OutputStream os = new FileOutputStream(path+"/"+fileName);
        int len = 0;
        byte[] b = new byte[1024];
        while((len = is.read(b))!=-1){
            os.write(b, 0, len);
        }
        is.close();
        os.close();
        
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

你可能感兴趣的:(十、文件上传)