JavaWeb 文件上传和下载

一、文件上传

步骤

前端部分

1.提供一个post方法的表单,并设置enctype属性(设置请求内容的MIME类型)为multpart/from-data,代表该表单支持文件上传
2.提供一个文件上传框:<input type="file">
举例:

<form action="/xxx" method="post" enctype="multipart/form-data">
    <input type="text" name="description">
    <input type="file" name="file">
    <input type="submit">
</form>
后端部分

将内容通过流读取进来,举例:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("utf-8");
    InputStream in = request.getInputStream();
    ServletOutputStream out = response.getOutputStream();
    int len = 0;
    byte[] b = new byte[1024];
    while((len = in.read(b))!=-1){
        out.write(b, 0, len);
    }
}

结果为:
------WebKitFormBoundary5x8Olc9cfCFQzCFd
Content-Disposition: form-data; name="description"

这是一个测试描述
------WebKitFormBoundary5x8Olc9cfCFQzCFd
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/plain

这是1.txt的内容
------WebKitFormBoundary5x8Olc9cfCFQzCFd--

fileupload

是apache的commons组件提供的上传组件,主要用于解析上传的输入流内容,比如上面可以看出内容都是上传的各个表单内容的字节流,并且是混杂在一起的,而该组件则可以对这些流进行分类和处理。

使用步骤

1.添加相关jar包——commons-fileupload/commons-io(放到WEB-INF/lib下)
2.实例化工厂类DiskFileItemFactory()对象
3.实例化工厂解析类ServletFileUpload()对象
4.调用解析类对象的parseRequest()方法进行解析,里面直接传入request对象即可,返回的是一个List<FileItem>对象(解析前可以先通过isMultipartContent()方法来判断传入的request对象是否支持文件上传)

核心对象
1.DiskFileItemFactory

工厂类

2.ServletFileUpload

用于解析工厂对象的解析类

3.FileItem

代表表单项,比如下面这样就是一个FileItem:

Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/plain

这是1.txt的内容

其主要提供了以下方法:
(1)isFormField():判断是否为普通文本字段,如果返回false,表明是文件字段
(2)getContentType():获取上传文件的类型
其中对于普通表单项(textpassword等)提供了以下方法:
(1)getFieldName():获取字段名
(2)getString():获取值,可以传入编码参数来设置编码
对于上传的文件提供了以下方法:
(1)getInputStream():获取文件输入流
(2)getName():获取文件名
(3)getSize():获取文件大小
(4)write(File):把上传的文件内容保存到指定文件中

上传并输出文件示例
public void doGet(HttpServletRequest request, HttpServletResponse response)
        {
    DiskFileItemFactory factory = new DiskFileItemFactory();  //实例化工厂对象
    ServletFileUpload fileupload = new ServletFileUpload(factory);  //实例化工厂解析对象
    try {
        List<FileItem> list = fileupload.parseRequest(request);  //解析工厂
        for (FileItem f : list) {
            if (!f.isFormField()) {  //当为文件字段
                String directoryname = this.getServletContext().getRealPath("/upload");
                File directory = new File(directoryname);
                if(!directory.exists() || !directory.isDirectory()){  //创建上传目录
                    directory.mkdirs();
                }
                System.out.println(f.getFieldName());   //file
                System.out.println(f.getName()); // xxx.jpg,只有文件名
                System.out.println(f.getContentType()); //image/jpeg
                InputStream in = f.getInputStream();
                ServletOutputStream out = response.getOutputStream();
                FileOutputStream file = new FileOutputStream(new File(directoryname, f.getName()));  //根据传的文件名命名
                int len = 0;
                byte[] b = new byte[1024];
                while ((len = in.read(b)) != -1) {
                    out.write(b, 0, len);  //输出图像
                    file.write(b, 0, len);  //保存文件
                }
                file.close();  //关闭文件
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
文件上传路径名问题

可以看到上面传输的文件只会获得文件名,但在一些如IE浏览器中容易出现传输的文件名带路径的问题,如:C:/XXX/xxx.xx,此时上面的代码就会出错。因此可以使用commons-io包下FilenameUtils类提供的静态方法getName(),其对于不管是否带路径的文件字符串,都只会获取文件名部分的字符串,举例:

System.out.println(FilenameUtils.getName("c:/sda/fdg/s"));  //s
System.out.println(FilenameUtils.getName("c:/sda/fdg/s.fs"));  //s.fs
System.out.println(FilenameUtils.getName("s.de"));  //s.de
System.out.println(FilenameUtils.getName("fdg/s"));  //s
System.out.println(FilenameUtils.getName("/sda/fdg/s"));  //s
文件上传问题
1.注入问题

对于上面那段代码,会在项目下新建一个upload文件夹,并生成对应文件,此时访问:http://127.0.0.1:8080/项目名/upload/文件名即可看到刚才上传的文件内容。但是,此时要注意如果上传了像下面这样的文件:

//xxx.jsp
<%@ page contentType="text/html; charset=utf-8"%>
<!DOCTYPE>
<html>
<head>
<title></title>
</head>
<body>
    <%
        Runtime.getRuntime().exec("notepad");
    %>
</body>
</html>

此时,在服务器上可能就会执行该notepad命令打开记事本,即在服务器上执行了用户上传的脚本文件
解决方式:
(1)将上传的文件放到用户无法访问到的文件路径下,如:WEB-INF
(2)限制上传的文件类型,如jsp、exe、bat等

2.文件覆盖问题

对于在上传保存的路径下原有的文件,如果上传的文件和原有的文件重名,将可能发生原文件被覆盖的问题。
解决方式:
给文件名进行处理,如给文件名加上UUID使得名字唯一等

3.文件夹文件内容过多问题

解决方式:
对文件夹目录等进行分类存储,如按日期/文件类别等

4.文件内容过大

解决方式:
通过ServletFileUpload下的setFileSizeMax()方法限制单个文件大小,或者setSizeMax()设置总文件上传大小,或者通过getSize()获取文件大小后根据上传的文件大小进行处理,举例:

DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload fileupload = new ServletFileUpload(factory);
fileupload.setSizeMax(1024 * 1024 * 3);  //文件大小总和不超过3M
fileupload.setFileSizeMax(1024 * 1024);  //单个文件大小不超过1M
try{
    ...
} catch (FileUploadBase.SizeLimitExceededException e) {
    response.getWriter().print("文件总和内容过大!");
} catch (FileUploadBase.FileSizeLimitExceededException e) {
    response.getWriter().print("文件内容过大!");
} catch (Exception e) {
    e.printStackTrace();
}
5.上传文件为空

解决方式:
判断上传的文件名是否为空

6.产生临时文件

在上传文件过程中往往会产生临时文件,导致磁盘空间不足,或者造成磁盘空间的浪费
解决方式:
(1)使用DiskFileItemFactory对象的setRepository(File)方法设置临时文件存储位置,举例:

DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(new File("F:/"));  //在F盘下存放临时文件

(2)通过FileItem下的delete()方法将临时文件删除,举例:

DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload fileupload = new ServletFileUpload(factory);
List<FileItem> list = fileupload.parseRequest(request);
for (FileItem f : list) {
    ...
    f.delete();  //将临时文件删除
}

二、文件下载

在返回头response中添加下面的头信息即可:

response.setHeader("content-disposition", "attachment;filename=文件名" );
response.setHeader("content-type", 文件类型);

举例:

public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException {
    FileInputStream in = new FileInputStream(new File(this.getServletContext().getRealPath("/img/测试图片.jpg")));
    ServletOutputStream out = response.getOutputStream();
    int len = 0;
    byte[] b = new byte[1024];
    response.setHeader("content-disposition", "attachment;filename=" + new String("测试.jpg".getBytes("utf-8"), "iso-8859-1"));  //避免文件名乱码
    response.setHeader("content-type", "image/jpeg");
    while((len = in.read(b)) != -1){
        out.write(b, 0, len);
    }
}

注:
对于上面的文件类型是手动设置十分不便,因此可以用ServletContext下的getMimeType(文件名)方法来根据文件名自动获得文件类型,举例:

response.setContentType(this.getServletContext().getMimeType("xxx.jpg"));  //自动设置为image/jpeg
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容

  • 1. 水乳霜, 2.z a隔离(隔离霜)紫色 3.粉底(自然白,一般人)(象牙白,偏白的人)粉底液 4. 定妆,定...
    晨晨成长记阅读 56评论 0 0
  • 什么是预处理? 预定义符号 #define 指令 #define 指令定义的内容。如果在文本中有出现,就会简单直接...
    Dafanzi阅读 123评论 0 0
  • 昨天晚上我和妈妈一起出去散步,发现了许多很新奇的事情。看见了法国梧桐树的树身,为什么没有皮呢? ...
    杨博钊阅读 281评论 0 1