SpringMVC之下载文件

springMVC 下载文件

在开发web项目时,我们经常会遇到下载文件的情况。我们下来看下面这个代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void downLoad(HttpServletResponse response, String filename) {
if (file == null || !file.exists()) {
return;
}
OutputStream out = null;
try {
response.reset();
response.setContentType("application/octet-stream; charset=utf-8");
response.setHeader("Content-Disposition", "attachment; filename="
+ file.getName());
out = response.getOutputStream();
File file = new File(savePath + fileName);//文件路径
out.write(FileUtils.readFileToByteArray(file));
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

在之前的servlet中这样的写法非常普遍,但是既然我们使用springmvc框架,不应该在暴露HttpServletResponse 这种j2ee的接口了,所以spring提供了更好、更优雅的实现方式。

1
2
3
4
5
6
7
8
@RequestMapping("/download")  
public ResponseEntity<byte[]> download(String fileName) throws IOException {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", URLEncoder.encode(fileName,"UTF-8"));
return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(mailService.getDownloadFile(fileName)),
headers, HttpStatus.CREATED);
}

我们详细看看这几行代码:

1
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);

在源码中:

1
2
3
4
5
6
7
8
9
10
/**
* Public constant media type for {@code application/octet-stream}.
* */
public final static MediaType APPLICATION_OCTET_STREAM;

/**
* A String equivalent of {@link MediaType#APPLICATION_OCTET_STREAM}.
*/
public final static String APPLICATION_OCTET_STREAM_VALUE = "application/octet-stream";
APPLICATION_OCTET_STREAM = MediaType.valueOf(APPLICATION_OCTET_STREAM_VALUE);

这一句的作用相当于

1
response.setContentType("application/octet-stream; charset=utf-8");

指定contentType为”application/octet-stream”,contentType的作用就是用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件。这里的作用就是,告诉浏览器返回的是二进制流数据。

1
headers.setContentDispositionFormData("attachment", URLEncoder.encode(fileName,"UTF-8"));

Content-disposition其实可以控制用户请求所得的内容存为一个文件的时候提供一个默认的文件名,文件直接在浏览器上显示或者在访问时弹出文件下载对话框。 如attachment为以附件方式下载 。相当于servlet中:

1
2
response.setHeader("Content-Disposition", "attachment; filename="
+ file.getName());

filename就是显示的下载框中默认的下载文件名。Content-Disposition参数本来是为了在客户端另存文件时提供一个建议的文件名,但是考虑到安全的原因,就从规范中去掉了这个参数。但是由于很多浏览器已经能够支持这个参数,所以只是在规范文档中列出,但是要注意这个不是HTTP/1.1的标准参数。为了让建议的文件名支持中文,使用了URLEncoder.encode(fileName,”UTF-8”)。
最后一句代码:

1
2
return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(mailService.getDownloadFile(fileName)),  
headers, HttpStatus.CREATED);

这里getDownloadFile()方法就是通过文件名得到文件,在使用这段代码时需要换成自己的实现方式。headers上面已经介绍过了,就是告诉浏览器返回的是二进制流数据(application/octet-stream),以附件的形式打开(”attachment;filename=xxx”)。并且返回状态码HttpStatus.CREATED(201,代表已创建请求成功并且服务器创建了新的资源。)
最后提醒注意的是,导包的时候不要导错了:

1
2
3
4
5
import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
评论

WebSocket实现即时聊天室

原理

很多网站为了实现即时聊天,使用的是轮询方式(在特定的时间间隔,由浏览器向服务器端发出 Http request,然后由服务器返回最新的数据)实现。这种传统的 Http request的方式有个明显的缺点,浏览器需要不断的向服务器发出请求,然而HTTP request 的header是非常长的,里面包含的有用数据可能只是一个很小的值,这样会占用很多的带宽。
比较新的方式是Comet—用了Ajax,但是还是要发出请求。
但是在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。这样做的两大好处是

  1. Header
    互相沟通的Header是很小的-大概只有 2 Bytes
  2. Server Push
    服务器的推送,服务器不再被动的接收到浏览器的request之后才返回数据,而是在有新数据时就主动推送给浏览器。

开发基于WebSocket协议的聊天室

开发环境

MyEclipse2014、JDK1.7.45 64位、Tomcat8

WebSocketConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.sx2.websocket;

import java.util.Set;

import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpointConfig;
/**
*
* @author Jin
* 在Tomcat启动的时候会加载此类(因为实现了ServerApplicationConfig接口)
*/
public class WebSocketConfig implements ServerApplicationConfig{

@Override
public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scan) {
/*
* 这里会自动扫描带有@ServerEndpoint注解的类
* 返回它们的类名集合,这里可以做一些过滤,比如过滤测试用的带有@ServerEndpoint注解的类
* 最后一定要返回过滤后的set集合,相当于注册服务
* 最后一定要返回过滤后的set集合,相当于注册服务
* 最后一定要返回过滤后的set集合,相当于注册服务
*/
return scan;
}

@Override
public Set<ServerEndpointConfig> getEndpointConfigs(
Set<Class<? extends Endpoint>> arg0) {
return null;
}

}

chatSocket.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import com.alibaba.fastjson.JSONObject;
import com.sx2.pojo.WebSocketResult;

/**
*
* @author Jin
* 这里带有@ServerEndpoint注解,会自动被扫描到
*/
@ServerEndpoint("/chatSocket")
public class ChatSocket {
//所有的实例的集合
private static Set<ChatSocket> sockets=new HashSet<ChatSocket>();
//保存每个username对应的用户头像,这里只是这个项目中需要,如果实际项目中不需要,此变量无用
private static Map<String, String> photos = new HashMap<String, String>();
//保存每个用户的用户名
private static Set<String> names=new HashSet<String>();
//每个用户进入聊天室,都会开启一个session,要注意的是这里的session是javax.websocket.Session
private Session session;
//当有用户进入聊天室时,保存用户名
private String username;
//当有用户进入聊天室时,保存用户头像,不是必须的
private String photo;

/**
* 每当有用户进入聊天室时,都会执行一次此函数
* @param session
*/
@OnOpen
public void open(Session session){
this.session=session;
sockets.add(this);
//这里的逻辑是得到请求路径后的参数
//比如:ws://localhost:8080/SX2/chatSocket?username=jin&photo=/image/user/b3.png",那么queryString就是username=jin&photo=/image/user/b3.png
//username为用户的用户名,是必须的。photo为用户的头像,看实际需要并不是必须的
String queryString = session.getQueryString();
//对得到queryString进行切割,得到用户名和头像
String[] infos = queryString.split("&");
this.username = infos[0].substring(infos[0].indexOf("=") + 1);
this.photo = infos[1].substring(infos[1].indexOf("=") + 1);
//WebSocket返回值的包装类
WebSocketResult message = new WebSocketResult();
String encodeUsername = "";
try {
if(this.username.matches("[A-Za-z]")){ //如果用户的用户名是纯英文,比如Tom,Jerry,不用编码
encodeUsername = this.username;
}else{ //如果用户的用户名不是纯英文的,则需要编码
encodeUsername = URLDecoder.decode(this.username,"UTF-8");
}
this.username = encodeUsername;
//将经过处理的用户名,添加到用户名集合中
names.add( encodeUsername);
//将用户名集合放到返回值中
message.setNames(names);

photos.put(encodeUsername, photo);
message.setPhotos(photos);
//将欢迎信息放到返回值中
message.setWelCome(encodeUsername+"进入聊天室!!");
//将返回值的包装类转为json字符串,向所有人打印欢迎信息。
broadcast(sockets, JSONObject.toJSONString(message));
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
message.setWelCome("服务器异常!");
broadcast(sockets, JSONObject.toJSONString(message));
}


}
@SuppressWarnings("deprecation")
/**
* 当有用户发送消息时,会执行此函数
* @param session 为此用户开启的session
* @param msg 用户发送的消息文本
*/
@OnMessage
public void receive(Session session,String msg ){

WebSocketResult message=new WebSocketResult();
//将消息文本放到返回值中
message.setSendMsg(msg);
//将来自哪个用户放到返回值中
message.setFrom(this.username);
//将发送日期放到返回值中
message.setDate(new Date().toLocaleString());
//用户头像
message.setPhotos(photos);
//向所有人打印发送的消息
broadcast(sockets, JSONObject.toJSONString(message));
}

/**
*
* @param session 为此用户开启的session
*/
@OnClose
public void close(Session session){
//从实例集合中移除此实例
sockets.remove(this);
//从用户名集合中移除此用户名
names.remove(this.username);
//从头像集合中移除此用户的头像
photos.remove(this.username);
WebSocketResult message=new WebSocketResult();
//将欢送消息放到返回值中
message.setWelCome(this.username+"退出聊天室!!");
//重新设置用户名集合
message.setNames(names);
//重新设置头像集合
message.setPhotos(photos);
//向所有人打印欢送消息
broadcast(sockets, JSONObject.toJSONString(message));
}
/**
*
* @param ss 所有实例集合
* @param msg 要向浏览器端发送的json字符串
*/
public void broadcast(Set<ChatSocket> ss ,String msg ){

for (Iterator<ChatSocket> iterator = ss.iterator(); iterator.hasNext();) {
ChatSocket chatSocket = (ChatSocket) iterator.next();
try {
//使用session的getBasicRemote方法向浏览器端发送json字符串
chatSocket.session.getBasicRemote().sendText(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

WebSocketResult.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import java.util.Map;
import java.util.Set;
/**
*
* @author Jin
* WebSocket返回值的包装类
*/
public class WebSocketResult {


private String sendMsg;

private String date;

private String from;

private String welCome;

private Set<String> names;

private Map<String, String> photos;

public String getWelCome() {
return welCome;
}

public void setWelCome(String welCome) {
this.welCome = welCome;
}

public String getSendMsg() {
return sendMsg;
}

public void setSendMsg(String sendMsg) {
this.sendMsg = sendMsg;
}

public String getDate() {
return date;
}

public void setDate(String date) {
this.date = date;
}

public String getFrom() {
return from;
}

public void setFrom(String from) {
this.from = from;
}

public Set<String> getNames() {
return names;
}

public void setNames(Set<String> names) {
this.names = names;
}

public Map<String, String> getPhotos() {
return photos;
}

public void setPhotos(Map<String, String> photos) {
this.photos = photos;
}
}

客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<?xml version="1.0" encoding="UTF-8"?>

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Apache Tomcat WebSocket Examples: Chat</title>
<style type="text/css">
input#chat {
width: 410px
}

#console-container {
width: 400px;
}

#console {
border: 1px solid #CCCCCC;
border-right-color: #999999;
border-bottom-color: #999999;
height: 170px;
overflow-y: scroll;
padding: 5px;
width: 100%;
}
#userlist {
border: 1px solid #CCCCCC;
border-right-color: #999999;
border-bottom-color: #999999;
height: 170px;
overflow-y: scroll;
padding: 5px;
width: 100%;
}
p {
padding: 0;
margin: 0;
}
</style>
<script type="application/javascript">
//这里的target就是websocket服务的路径
//对应于@ServerEndpoint("/chatSocket")
var target = "ws://localhost:8080/SX2/chatSocket?username="+ username +"&photo=<%=path%>/image/user/b3.png";
window.onload = function() {
//进入聊天页面,就打开socket通道;
if ('WebSocket' in window) {
ws = new WebSocket(target);
} else if ('MozWebSocket' in window) {
ws = new MozWebSocket(target);
} else {
alert("WebSocket is not supported by this browser!");
return;
}
//当服务端向浏览器发送消息时会执行此函数
ws.onmessage = function(event) {
eval("var msg=" + event.data);
if(undefined != msg.welCome){
$('#console').append("msg.welCome");
}
if(undefined != msg.sendMsg){
$('#console').append(msg.sendMsg);
}
if(undefined != msg.names){
$('#userlist').html("");
$(msg.names).each(function(){
$('#userlist').append(this);
});
}
};
};
//发送消息
function sendMsg() {
var msg = $('#chat').val();
if(msg == "" || msg == null)
return;
ws.send(msg);
$('#chat').val("");
}
document.getElementById('chat').onkeydown = function(event) {
if (event.keyCode == 13) {
sendMsg();
}
};
</script>
</head>
<body>
<div>
<p>
<input type="text" placeholder="type and press enter to chat" id="chat" />
</p>
<div id="console-container">
<div id="console"/>
</div>
<div id="console-container">
<div id="userlist"/>
</div>
</div>
</body>
</html>
评论