需求
1、最开始想到是今年的课程设计,想着做个有点意思的东西,发现关于websocket技术没有接触过,就搞了类似一个QQ的多聊天室聊天的小项目。
2、需要实现,登录,注册,等等等…当然最核心,最基础的还是我们的聊天的实现
3、采用的技术,后端java,前端vue
关于websocket
1、WebSocket是一种在单个TCP连接上进行全双工通信的协议
2、WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
3、简单来说,就是客户端给服务器端发消息,服务器端会有监听,并可以再给客户端发消息,客户端也有监听,对服务器端的消息进行处理。甲——>服务端——>其他用户
websocket的方法
后端
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、在自己的websocket上加ServerEndpoint注解
因为要实现多聊天室,多用户聊天,所以,需要一个当前聊天室的id(page),以及当前用户id(userId)
@ServerEndpoint(value = "/webSocket/{page}/{userId}")
3、监听的方法
1、@OnOpen(连接建立时触发)
2、@OnClose(关闭连接时触发)
3、@OnMessage( 收到客户端消息触发事件)
4、@OnError(通信发生错误时触发)
4、发送消息的方法(利用Session会话)
1、在建立连接时触发@OnOpen,
将当前用户的会话存到一个map集合<房间id,该房间内用户sessionId集合set>
- 如果不实现,多聊天室,set集合即可
2、收到客户端消息时,将该消息进行转发,
//遍历集合,将消息给该集合中的所有人
for (Session s : sessions) {
// if (!s.getId().equals(session.getId())) 可以加判断,不发给谁,
s.getBasicRemote().sendText(JSONUtil.toJsonStr(contentBo));
}
前端
1、实例化websocket
1、利用vue在data里面,定义一个变量socket
2、进行实例化操作,之后便可利用socket进行操作
if (typeof (WebSocket) === "undefined") {
alert("您的浏览器不支持socket")
} else {
// 实例化socket
this.socket = new WebSocket("ws://url(服务地址) + webSocket/" + page + "/" +userId)
}
2、监听的方法
3、4方法需要进行实时监听,建议写在vue 生命周期的mounted里面
1、websocket.onopen(建立连接成功时触发)
2、websocket.onclose(连接关闭时触发)
3、websocket.onmessage(收到服务器的数据触发)
4、websocket.onerror(连接出现错误时触发)
3、发送消息的方法
send: function (msg) {
this.socket.send(msg)
}
websocket核心代码
后端
package cn.itcast.config;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import cn.itcast.api.bo.ContentBo;
import cn.itcast.api.entity.Content;
import cn.itcast.api.entity.User;
import cn.itcast.api.service.IContentService;
import cn.itcast.api.service.IUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author: zpt
* @Date: 2019-09-20 15:12
* @Description:
*/
@Component
@ServerEndpoint(value = "/webSocket/{page}/{userId}")
public class WebSocket {
private static IUserService userService;
@Autowired
public void setMyServiceImpl(IUserService userService){
WebSocket.userService = userService;
}
private static IContentService contentService;
@Autowired
public void setMyServiceImpl(IContentService contentService){
WebSocket.contentService = contentService;
}
private Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 用来记录房间及房间内用户session_id
*/
private static Map<String, Set> roomMap = new ConcurrentHashMap(8);
/**
* 用来记录session_id与用户id之间的关系
*/
private static Map<String, Integer> userIdMap = new ConcurrentHashMap(8);
@OnOpen
public void open( @PathParam("page") String page, Session session, @PathParam("userId") Integer userId ) throws IOException {
Set set = roomMap.get(page);
userIdMap.put(session.getId(), userId);
// 如果是新的房间,则创建一个映射,如果房间已存在,则把用户放进去
if (set == null) {
set = new CopyOnWriteArraySet();
set.add(session);
roomMap.put(page, set);
} else {
set.add(session);
}
}
@OnClose
public void close( @PathParam("page") String page, Session session, @PathParam("userId") Integer userId ) {
userIdMap.remove(session.getId());
// 如果某个用户离开了,就移除相应的信息
if (roomMap.containsKey(page)) {
roomMap.get(page).remove(session);
}
}
@OnMessage
public void reveiveMessage( @PathParam("page") String page, Session session, String message ) throws IOException {
log.info("接受到用户{}的数据:{}", session.getId(), message);
Integer userId = userIdMap.get(session.getId());
//存入数据库中
Content content = new Content();
content.setRoomId(Integer.valueOf(page));
content.setContent(message);
content.setSendTime(LocalDateTime.now());
content.setSendId(userId);
contentService.save(content);
//查询当前用户信息
User user = userService.getById(userId);
//返回给前端的Bo对象
ContentBo contentBo = new ContentBo();
BeanUtil.copyProperties(content, contentBo);
BeanUtil.copyProperties(user, contentBo);
// 拼接一下用户信息
//String msg = session.getId() + " : " + message;
Set<Session> sessions = roomMap.get(page);
// 给房间内所有用户推送信息
for (Session s : sessions) {
if (!s.getId().equals(session.getId())) {
s.getBasicRemote().sendText(JSONUtil.toJsonStr(contentBo));
}
}
}
@OnError
public void error( Throwable throwable ) {
try {
throw throwable;
} catch (Throwable e) {
log.error("未知错误");
}
}
}
前端(首先要在created里面获取到当前聊天的roomId,以及当前用户userId!!!)
new Vue({
el: '#app',
data: {
"roomId": null,
"userId": null,
"websocket": null,
},
created() {
//因为我是html页面引入的vue,因此页面之间的数据直接用了,location.href = ""
//getQueryVariable方法起到了一个接受上一个页面传的参数的作用
this.userId = this.getQueryVariable('userId')
this.roomId = this.getQueryVariable('roomId')
},
mounted() {
// 初始化
this.init()
},
computed() {
},
methods: {
init: function () {
if (typeof (WebSocket) === "undefined") {
alert("您的浏览器不支持socket")
} else {
// 实例化socket
this.socket = new WebSocket("ws://url(服务端地址)/webSocket/" + this.roomId + "/" + this.userId)
// 监听socket连接
this.socket.onopen = this.open
// 监听socket错误信息
this.socket.onerror = this.error
// 监听socket消息
this.socket.onmessage = this.getMessage
}
},
open: function () {
console.log("socket连接成功")
},
error: function () {
console.log("连接错误")
},
send: function (msg) {
this.socket.send(msg)
},
close: function () {
console.log("socket已经关闭")
},
// 获取多个参数的时候
getQueryVariable: function (variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1]; }
}
return (false);
},
destroyed() {
// 销毁监听
this.socket.onclose = this.close
},
}
)
遇到的问题
1、websocket不能正常注入service(直接@Autowired),需采用以下方式
https://blog.csdn.net/qq_43532386/article/details/111783423
2、后端服务器部署,websocket不能正常使用,需要在nginx里面配置一些东西
https://blog.csdn.net/qq_43532386/article/details/111784152
3、实现聊天的时候,聊天界面的滚动条,vuejs不能控制到最下边
https://blog.csdn.net/qq_43532386/article/details/111784809
完整代码(ui设计比较简略)
链接:https://pan.baidu.com/s/1FWnDR-psILqPEC4XLeRs6w
提取码:jqzq
文件上传,直接上传的服务器,通过配置nginx进行访问。
整体仅供参考,实用意义不大!!!