Java 使用 Servlet 上传下载文件

在实际开发中,上传文件和下载文件是很常见的功能,如果文件名是中文的话,还容易会出现乱码问题。

本篇博客采用 Servlet 作为接口演示 Java 上传文件和下载文件的实现方案,同时解决获取上传和下载过程中所遇到的的中文文件名乱码问题,并在本篇博客的最下面提供 demo 源代码下载。


一、搭建工程

新建一个 maven 工程,并导入相关的 jar 包和 tomcat 插件,具体内容如下:

有关具体的 jar 包地址,可以在 https://mvnrepository.com 上进行查询。

有关 tomcat 插件,可以到官网查询,地址为:https://tomcat.apache.org/maven-plugin.html

目前 tomcat 官网的插件,最新版本也就是 tomcat7 的 2.2 版本。如果你想使用更高版本的 tomcat 的话,就下载独立的 tomcat 即可。这里只是进行 demo 演示,就直接使用 tomcat 的插件了,能够满足 demo 运行的环境,使用起来也很方便。

<dependencies>
    <!--servlet支持-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
    <!--文件上传组件支持-->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
    <!--Apache提供的一些实用的工具类支持-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.12.0</version>
    </dependency>
</dependencies>

<build>
    <finalName>fileUpDownTest</finalName>
    <plugins>
        <!--Tomcat官网最新版本的tomcat插件-->
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
                <!--tomcat启动的端口号-->
                <port>80</port>
                <!--网站启动后访问的虚拟地址-->
                <path>/</path>
            </configuration>
        </plugin>
    </plugins>
</build>

由于要使用 servlet web 开发,需要导入 javax.servlet-api 的 jar 包。另外导入了 Apache 的 commons-fileupload 的 jar 包组件,使文件上传变的很容易,同时导入了 commons-lang3 的 jar 包组件,主要是使用里面的一些字符串处理的实用方法,简化代码开发。最后打开右侧的 Maven 窗口,刷新一下,这样 Maven 会自动下载所需的 jar 包文件。

搭建好的项目工程整体目录比较简单,具体如下图所示:

image

项目工程结构简单介绍:

com.jobs.controller 包下面放了 2 个 Servlet 处理上传和下载的请求
com.jobs.filter 包下面放了 1 个过滤器,主要设置请求和响应的字符编码,统一解决乱码问题

webapp 下的 upload 用来存放上传的文件,里面提前放了一个中文名称的 txt 文件,用于下载
webapp 下面放了一个 index.html 页面文件,网站启动时进行展示,提供文件上传和下载操作


二、细节展示

这里先说 CharacterEncodingFilter 过滤器,统一设置请求和响应的字符编码为 UTF-8 ,这样可以方式乱码。另外该过滤器配置的作用地址,本 demo 示例只处理 Servlet 请求,不处理其它请求,例如 css、js 等文件请求。具体内容如下:

package com.jobs.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

//这里只监听 /api/* 的请求,如果你想监听所有请求,可以使用 /* 配置
//另外配置了过滤器的请求和响应的初始化参数,在下面的处理方法中可以获取
@WebFilter(value = "/api/*",
        initParams = {
                @WebInitParam(name = "requestEncoding", value = "UTF-8"),
                @WebInitParam(name = "responseContentType", value = "text/html;charset=UTF-8")})
public class CharacterEncodingFilter implements Filter {

    private FilterConfig filterConfig;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
                FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) resp;

            //获取初始化参数
            String requestEncoding = filterConfig.getInitParameter("requestEncoding");
            String responseContentType = filterConfig.getInitParameter("responseContentType");

            //使用初始化参数,设置请求和响应的编码
            request.setCharacterEncoding(requestEncoding);
            response.setContentType(responseContentType);

            chain.doFilter(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void destroy() {

    }
}

upLoadServlet 是专门用来处理上传文件的请求,同时也接收 form 表单提交过来的字段,具体内容如下:

package com.jobs.controller;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;

//配置访问地址
@WebServlet("/api/upload")
public class upLoadServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request,
                   HttpServletResponse resp) throws IOException {

        //判断该操作是否支持文件上传操作
        //要提交的form表单上必须要有属性 enctype="multipart/form-data"
        if (ServletFileUpload.isMultipartContent(request)) {
            try {
                //将文件保存到 webapp 下的 upload 文件夹中
                String dir = this.getServletContext().getRealPath("upload");
                File f = new File(dir);
                if (!f.exists()) {
                    f.mkdir();
                }

                //采用 apache 的上传组件,只需要编写很少的代码,即可完成文件上传功能
                DiskFileItemFactory factory = new DiskFileItemFactory();
                ServletFileUpload fileUpload = new ServletFileUpload(factory);
                List<FileItem> fileItems = fileUpload.parseRequest(request);

                for (FileItem item : fileItems) {
                    //当前表单是否是文件表单
                    if (item.isFormField()) {
                        //获取表单字段
                        String fieldName = item.getFieldName();
                        //必须使用 utf-8 编码获取表单字段的值,否则就是乱码
                        String fieldValue = item.getString("utf-8");
                        System.out.println("表单字段:" + fieldName + "," + fieldValue);
                    } else {
                        //此 item 表示文件
                        //获取文件的表单字段名称
                        String fieldName = item.getFieldName();
                        //获取文件的原始文件名,不包含路径
                        String fileName = item.getName();
                        System.out.println("上传文件:" + fieldName + "," + fileName);
                        //这里的 StringUtils 是 apache 的 commons-lang3 组件提供的实用工具类
                        if(StringUtils.isNotEmpty(fileName)) {
                            //将上传的文件,以原始名称保存到具体的目录下,
                            //当然你可以生成随机 uuid 给文件命名
                            //这里使用原始文件名,主要演示由于设置了过滤器统一处理字符编码,
                            //所以保存中文文件名,不出现乱码的问题
                            item.write(new File(dir, fileName));
                        }
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        //由于过滤器统一设置了响应编码,
        //所以响应页面中展示的中文,不会出现乱码
        resp.getWriter().write("恭喜你,成功了");
    }
}

需要注意:由于本 demo 示例采用了 tomcat 插件,因此运行的是源码本身的网站上传文件,文件会保存到 webapp 下的 upload 文件夹下,在 idea 工具中可以立刻看到。如果你使用的是独立的 tomcat 进行部署访问时,上传文件后需要到独立部署的目标 webapp 下的 upload 文件夹下去查看上传的文件。

下面列出 downLoadServlet 的内容,这个是专门用来处理下载文件请求的,具体细节如下:

package com.jobs.controller;

import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;

//配置访问地址
@WebServlet("/api/download")
public class downLoadServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req,
                   HttpServletResponse response) throws ServletException, IOException {
        String parm = req.getParameter("downLoadName");
        String downLoadName;
        if (StringUtils.isNotBlank(parm)) {
            downLoadName = parm + ".txt";
        } else {
            downLoadName = "测试文件.txt";
        }

        //获取所要下载的文件,这里只是 demo 演示,因此对下载的目标文件进行了硬编码
        String dir = this.getServletContext().getRealPath("upload");
        File f = new File(dir, "测试文件.txt");

        //下载文件的响应类型,这里统一设置成了文件流
        //你可以根据自己所提供下载的文件类型,使用不同的响应 mime 类型
        response.setContentType("application/octet-stream;charset=utf-8");
        //设置下载弹出框中默认显示的文件名称,如果指定中文名称的话,需要转成 iso8859-1 编码,解决乱码问题
        String fileName = new String(downLoadName.getBytes(), "iso8859-1");
        response.addHeader("Content-Disposition", "attachment;filename=" + fileName);

        //第一种下载方式,适合文件已经在硬盘上,此时读取文件字节流,直接下载
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f))) {
            ServletOutputStream outputStream = response.getOutputStream();
            byte[] bArr = new byte[1024];
            int len;
            while ((len = bis.read(bArr)) != -1) {
                outputStream.write(bArr, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        /*
        //第二种下载方式,适合不在硬盘上生成文件,而是在内存中生成文件字节流,然后下载
        //当然这里的例子,还是从硬盘上读取了文件,但是将字节流存入 ByteArrayOutputStream 内存字节流
        //模拟使用 ByteArrayOutputStream 生成文件流,然后进行下载
        ServletOutputStream outputStream = response.getOutputStream();
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f));
                ByteArrayOutputStream baos=new ByteArrayOutputStream()){

            byte[] bArr = new byte[1024];
            int len;
            while ((len = bis.read(bArr)) != -1) {
                baos.write(bArr, 0, len);
            }

            baos.writeTo(outputStream);
        } catch(IOException e){
            e.printStackTrace();
        }

        outputStream.flush();
        */
    }
}

本 demo 下载的是 webapp 下的 upload 文件夹下的固定文件:测试文件.txt

本 demo 提供了两种下载的方式,第一种是读取硬盘中的文件,采用响应流进行下载。
第二种方式是模拟不生成硬盘文件的前提下,采用内存流的方式生成文件,并最终写入响应流,还是依靠响应流进行下载。
当然本 demo 还是从硬盘中读取了文件,生成该文件的内存流。在实际开发中,我们可能会从数据库中获取记录,然后在内存中生成 Excel 并写入内存流,然后通过响应流进行下载 Excel 文件。

最后展示 index.html 页面的具体内容:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传文件测试</title>
</head>
<body>
<form id="Form1" action="/api/upload" method="post" enctype="multipart/form-data">
    <table>
        <caption>表单提交,文件上传,支持中文文件名称</caption>
        <tr>
            <td>请输入内容:</td>
            <td><input type="text" name="submitName"></td>
        </tr>
        <tr>
            <td>请选择文件01:</td>
            <td><input type="file" name="upFile1"></td>
        </tr>
        <tr>
            <td>请选择文件02:</td>
            <td><input type="file" name="upFile2"></td>
        </tr>
        <tr>
            <td></td>
            <td>
                <button type="submit">提交</button>
                <button type="reset">重置</button>
            </td>
        </tr>
    </table>
</form>
<br/>
<form id="Form2" action="/api/download" method="post">
    <table>
        <caption>文件下载,支持中文文件名称</caption>
        <tr>
            <td>请输入下载后的文件命名(不需要输入后缀名):</td>
            <td><input type="text" name="downLoadName"></td>
        </tr>
        <tr>
            <td>点击按钮下载中文名称的文件:</td>
            <td>
                <button type="submit">下载测试文件</button>
            </td>
        </tr>
    </table>
</form>
</body>
</html>

该页面的界面如下所示:

image

对于上传文件的表单,文本框中可以录入中文,可以同时上传 2 个文件,上传所选择的文件也可以是中文名称的文件。服务器接收到的表单字段内容,以及文件名称,可以正常接收到中文,不会出现乱码。对于下载文件的表单,可以在文本框中录入你想看到的下载文件名称,可以是中文,不会出现乱码问题。

本 demo 示例的代码下载地址为:https://files.cnblogs.com/files/blogs/699532/fileUpDownTest.zip



posted @ 2022-03-12 13:52  乔京飞  阅读(9634)  评论(0编辑  收藏  举报