Struts2 Annotation实现文件下载功能

1 minute read

一、达到目标:

给定任意Web根路径下面的文件相对路径下载文件,在任意浏览器下下载文件不出现乱码问题;

二、开发要点:

  • Annotation: 经历了Struts1的大量Action配置之后到了Struts2坚决放弃了xml配置,虽然struts2的xml配置比较简单了,但是我真的很懒…… 所以这里实现全部是有注解实现,简单、方便、容易日后的重构
  • 文件名乱码 提高这个问题都想吐,为什么不全部使用UTF-8编码呢…… 既然不能改变那就只能解决,所以这里要考虑各个浏览器对文件名的编码解析

三、具体实现

不说废话,直接上代码,下面再讲解;如果想下载猛击这里

package cn.wsria.demo.web.file;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

import org.apache.commons.lang.StringUtils;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springside.modules.web.struts2.Struts2Utils;

import cn.wsria.util.BrowserUtils;

/**
 * 下载Action
 *
 * @author HenryYan
 *
 */
@Controller
@Namespace("/file")
@Result(type = "stream", params = { "contentType", "application/octet-stream;charset=ISO8859-1", "inputName",
		"inputStream", "contentDisposition", "attachment;filename=${downFileName}", "bufferSize", "4096" })
public class DownloadAction {

	private static Logger logger = LoggerFactory.getLogger(DownloadAction.class); 

	private String fileName;
	private String downFileName;

	public void setFileName(String fileName) throws UnsupportedEncodingException {
		logger.debug("得到原始文件名:{}", fileName);

		// 转码
		fileName = URLDecoder.decode(fileName, "UTF-8");

		logger.debug("转码后的文件名:{}", fileName);

		this.fileName = fileName;
	}

	/**
	 * 如果传过来的fileName参数有路径处理后只返回文件名
	 * @return	例如path/aaa.txt,返回aaa.txt
	 * @throws UnsupportedEncodingException
	 */
	public String getDownFileName() throws UnsupportedEncodingException {
		fileName = StringUtils.defaultIfEmpty(fileName, "");

		// 判断path/aaa/bbb.txt和path\aaa\bbb.txt两种情况
		String tempFileName = StringUtils.substringAfterLast(fileName, "/");
		if (tempFileName.length() == 0) {
			tempFileName = StringUtils.substringAfterLast(fileName, "\\");
		}
		if (tempFileName.length() == 0) {
			tempFileName = fileName;
		}

		// 处理中文
		logger.debug("去除路径信息后得到文件名:{}", tempFileName);

		// 处理IE浏览器乱码问题
		if (BrowserUtils.isIE(Struts2Utils.getRequest())) {
			downFileName = java.net.URLEncoder.encode(tempFileName, "UTF-8");
		} else {
			downFileName = new String(tempFileName.getBytes(), "ISO8859-1");
		}

		return downFileName;
	}

	public InputStream getInputStream() throws UnsupportedEncodingException {
		return ServletActionContext.getServletContext().getResourceAsStream("/" + fileName);
	}

	public String execute() {
		return "success";
	}

}

Struts2本身就支持文件下载的输出类型,所以有一个type叫做“stream”,输出文件流到客户端,也就是Result中配置的type = “stream”

  • contentType设置为application/octet-stream;charset=ISO8859-1,作用是设置输出的类型为二进制流
  • inputName设置为inputStream指定Action中的哪一个方法返回二进制输出流
  • contentDisposition设置为attachment;filename=${downFileName}attachment标示当客户端打开下载链接的时候弹出保存对话框;filename=${downFileName}就是要指定下载的文件名名称,${downFileName}是指从Action中读取getDownFileName方法,这里一定要提供getDownFileName方法,我做的时候脑子短路调试了好一会,原来在生成getter和setter的时候没有生成getDownFileName方法

好,现在如果在项目的webapp/file中放置一个aaa.txt,那就可以使用路径:http://localhost:9000/wsria-demo/file/download.action?fileName=files/aaa.txt访问了 但是如果要下载中文的呢?

这里在开发的时候又被乱码问题玩了一把,最终的解决办法也是最可靠的办法就是前后台编码和解码,具体实现如下: 在前台下载的时候用encodeURI方法编码下载路径,把中文字符转换为UTF-8编码,为了统一不出问题所以我写了一个方法:

function download(fileName){
    var downUrl = $.common.custom.getCtx() + '/file/download.action?fileName=' + fileName;
    open(encodeURI(encodeURI(downUrl)));
}

函数所在文件:common.js

四、解决文件名乱码

1、接着在后台进行解码工作,在setFileName的时候解码文件名,fileName属性是在读取本地文件时使用的,所以要把中文的UTF-8还原回来交给java的输入流

2、在输出文件的时候客户端根据浏览器不同解析文件名的方式也不同,如代码的第67~71行处就是根据浏览器不同设定不同的文件名

就讲到这里了,试试吧,有问题在此文后面留言或者在关于中联系我!