单文件上传

Struts2 框架为依据“基于表单的HTML文件上传”所进行的文件处理上传提供了内置支持
当文件上传时,它通常会存储在临时目录中,然后Action类应对其进行处理或移动到固定目录中,
以确保数据不会丢失。通过一个名为FileUpload的预定义拦截器可以在Struts中上传文件,
该拦截器可通过org.apache.struts2.interceptor.FileUploadInterceptor类获得,
并作为defaultStack的一部分包含在内。
你也将在接下来的内容中看到如何使用它在struts.xml文件中设置各种参数。

上传页面:

<s:form action="/file/fileUploadAction" enctype="multipart/form-data" method="post"><!-- 默认为post -->
        <s:file name="file" label="上传文件"/>
        <s:submit value="提交"/>
        <s:reset value="重置"/>
</s:form>

upload_test.png

enctype指定了HTTP请求的Content-Type。
默认情况下,HTML的form表单的enctype=application/x-www-form-urlencoded。
application/x-www-form-urlencoded是指表单的提交,并且将提交的数据进行urlencode。
默认情况下,我们所有的表单提交都是通过这种默认的方式实现的。

上述为文件上传,需将form的enctype参数设置为multipart/form-data。这种方式只支持POST的请求方式。
Contype-Type=multipart/form-data情况的时候,都会通过一个特殊的字符串来将原始POST数据进行分割。
我们可以看到下面的请求中Content-type的类型:
file_upload_contentType.png
file_core_analysis.png

总结:

  1. application/x-www-form-urlencoded : 窗体数据被编码为名称/值对。这是标准的编码格式。
  2. multipart/form-data : 窗体数据被编码为一条消息,页上的每个控件对应消息中的一个部分。浏览器会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition(form-data或者file)、Content-Type(默认为text/plain)、name(控件name)等信息,并加上分割符(boundary)。
  3. text/plain : 窗体数据以纯文本形式进行编码,其中不含任何控件或格式字符。

Action

public class FileUploadAction extends ActionSupport {

    private static final long serialVersionUID = 1L;

    private File file;
    private String fileFileName;
    private String fileContentType;

    // 省略getter/setter

    public String execute() {
        ActionContext context = ActionContext.getContext();
        String msg = "";
        try {
            InputStream is = new FileInputStream(getFile());
            String uploadPath = ServletActionContext.getServletContext().getRealPath("/file");
            System.out.println(uploadPath);
            File toFile = new File(uploadPath, getFileFileName());
            OutputStream os = new FileOutputStream(toFile);

            byte[] buffer = new byte[1024];
            int length = 0;
            while(-1!=(length=is.read(buffer, 0, buffer.length))) {
                os.write(buffer);
            }
            is.close();
            os.close();
        }catch(Exception err){
            err.printStackTrace();
            msg = "Error!";
            context.put("message", msg);
            return INPUT;
        }

        msg = "Successfully";
        context.put("message", msg);// 默认为request范围
        context.put("fileFileName", fileFileName);
        context.put("fileContentType", fileContentType);
        return SUCCESS;
    }
}

由于Struts2内置的文件拦截器FileUploadInterceptor的原因,Action中的属性名fileFileName和fileContentType是固定的,由该拦截器负责填充;
而file属性则与上传页面中的file标签的属性name值相对应。简单建立文件输入输出流,然后构建缓冲区,先读到缓冲区,然后再从缓冲区中刷出到指定目录下的文件中。先通过上下文找到对应的文件存储目录,
根据上传的文件获得文件名,在该目录下建立一个相同的文件,从而构建输出流。

Struts.xml配置

<action name="fileUploadAction" class="com.livejq.action.FileUploadAction">
        <result>/done.jsp</result>
        <result name="input">/undone.jsp</result><!--没有错误输出页面可能会抛出异常 -->
        <interceptor-ref name="defaultStack">
            <param name="fileUpload.maximumSize">10485760</param><!-- 默认2MB -->
            <param name="fileUpload.allowedExtensions">.txt,.doc,.jpg,.png,.mp3</param>
            <param name="fileUpload.allowedType">text/plain,application/msword,image/jpeg,audio/mpeg</param>
        </interceptor-ref>
</action>

设置文件上传最大值也可通过配置常量(此常量会覆盖上面设置的最大值):

struts.multipart.maxSize=140000

输出结果:

F:\eclipseFramework\LoginDemo\WebContent\file

upload_test_success.png

file_ok.png

由于环境整体采用了UTF-8编码,结果没有出现中文乱码。

单文件下载

下载页面:

<s:a href="/LoginDemo/file/downLoad?filename=%E6%B5%8B%E8%AF%95.txt">测试.txt</s:a>
<s:a href="/LoginDemo/file/downLoad?filename=test.txt">test.txt</s:a>

Action

public class DownLoadAction extends ActionSupport {

    private static final long serialVersionUID = 1L;
    private String filename;


    public String getContentType(){
        return ServletActionContext.getServletContext().getMimeType(filename);
    }


    public InputStream getDownloadFile(){
        String filePath = "/file/"+filename;
        return ServletActionContext.getServletContext().getResourceAsStream(filePath);
    }

    public void setFilename(String filename){

            System.out.println("setFilename "+filename);
            this.filename = filename;
    }

    public String getFilename() throws IOException {
        return encodeDownloadFilename(filename, ServletActionContext.getRequest().getHeader("User-Agent"));
    }

    private String encodeDownloadFilename(String name, String agent) {
        try {
            // TODO Auto-generated method stub
            if (null != agent && -1 != agent.indexOf("MSIE") || null != agent
                    && -1 != agent.indexOf("Trident")) {// ie

                name = java.net.URLEncoder.encode(name, "UTF-8");
                System.out.println("encodeDownloadFilename "+name);

            } else if (null != agent && -1 != agent.indexOf("Mozilla")) {// 火狐,chrome等

                name = new String(name.getBytes("UTF-8"), "ISO-8859-1");
                System.out.println("after encodeDownloadFilename "+name);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return name;
    }

    public String execute() throws Exception{
        return SUCCESS;
    }
}

由于Action是直接从上文中的上传目录中对文件构建源进行下载的,请确保file目录中存在要下载的文件。

中文乱码问题

编程人员无论如何都会遇到中文乱码这个问题,这个得通过对整个传输过程的细致分析,才能一步一步得出结论来。
由于浏览器的不同,有IE/Firefox/Chrome/Opera等,得首先对他们进行判断。

  • IE(带MSIE/Trident字样):
User-Agent: Mozilla/5.0 (MSIE 9.0; Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
  • 其它(Chrome为例:只是Mozilla):
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36

分析

这里通过查看浏览器发送的请求和给出的响应,还有Action中的局部输出来证实自己的分析:

IE

这里是IE中点击下载的文件时发送的请求:

请求 URL: http://localhost:8080/LoginDemo/file/downLoad?filename=%E6%B5%8B%E8%AF%95.txt

细致的你可能已经发现下载页面中的filename的值为UTF-8编码后的字符。对于IE来说,当判断为已编码,则直接发送请求,
但如果是未编码,在这里为中文,则IE浏览器会采用默认的西文字符ISO8859-1进行编码,你也可以试试中文,只要做相应调整。
而中文编码后发送的请求如下:

请求 URL: http://localhost:8080/LoginDemo/file/downLoad?filename=æµè¯.txt

然后传到Action中时,由于全局过滤器的缘故,
对所有到来的请求进行了UTF-8的转码,控制台输出如下:

setFilename 测试.txt

然后在getFilename方法中:
对IE进行java.net.URLEncoder.encode(name,”UTF-8”)编码,
因为从上文可以看出其在通过Get方法传递参数时并未做编码处理。编码后控制台输出:

encodeDownloadFilename %E6%B5%8B%E8%AF%95.txt

IE响应头:

Content-Disposition: attachment; filename="%E6%B5%8B%E8%AF%95.txt"

转码统一采用Unicode,然后在浏览器中就可以看到中文了。
IE.png

其它浏览器

其它浏览器相对友好一点,如Chrome,filename当设置成中文时则采用Unicode进行编码,若已编码则不变,它发送请求时都为:

Request URL: http://localhost:8080/LoginDemo/file/downLoad?filename=%E6%B5%8B%E8%AF%95.txt

Action中与上同:

setFilename 测试.txt

然后在getFilename方法中:

name = new String(name.getBytes("UTF-8"), "ISO-8859-1");

由于Chrome等浏览器在接收数据时默认采用西文字符ISO8859-1进行解码,所以在Action中使用ISO8859-1进行编码,
输出为:

after encodeDownloadFilename æµè¯.txt

然后在浏览器中就可以看到中文了,Chrome响应头直接为中文:

Content-Disposition: attachment;filename=测试.txt

Chrome.png

参考文献:

  1. initphp
  2. 脚本之家
    …等
  • Struts2 值栈、OGNL表达式

    要想全面地理解Struts2中数据的存取,就必须要结合值栈和OGNL表达式一起来理解,而OGNL存取数据的地方就是值栈,值栈是整个St...

    Struts2 值栈、OGNL表达式
  • Struts2 标签库

    简介早期的jsp页面需嵌入大量的Java脚本来显示后台传递的数据,不但大大降低了可读性,而且非常不利于维护。 对于一个MVC框架而言,...

    Struts2 标签库
  • Struts2 基础

    Struts2 概述Struts2 是目前较为普及和成熟的基于MVC设计模式的web应用程序框架,它不仅仅是Struts1 的升级版本...

    Struts2 基础