侧边栏壁纸
博主头像
liveJQ博主等级

沒有乐趣,何来开始

  • 累计撰写 171 篇文章
  • 累计创建 67 个标签
  • 累计收到 2 条评论

SSH之Struts2上传下载

liveJQ
2019-04-01 / 0 评论 / 0 点赞 / 700 阅读 / 6,226 字

单文件上传

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>

20190401_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的类型

20190401_file_upload_contentType.png

20190401_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

20190401_upload_test_success.png

20190401_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,然后在浏览器中就可以看到中文了。

20190401_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

20190401_Chrome.png

参考资料

  1. initphp
  2. 脚本之家
0

评论区