一、开发环境与技术栈
- Windows
- WebSocket
- SpringBoot
- Vue
- Session
- Ajax
二、项目功能
主要业务
- 实现多人在线群聊,记录并管理所有的聊天信息
- 用户登录,打开主页可以看见登录界面,可以识别用户是否登录
- 登录成功进入主界面,显示聊天窗口和用户信息列表
- 每个用户都可以发送信息,并且可以接受到他人的信息
- 历史消息,每个用户都可以看见历史消息列表
核心技术要点
- 利用websocket实现消息推送机制
- 使用session自动识别用户是否登录
- 通过ajax实现数据异步调用
- 利用vue整合前端
- 利用springboot整合后端
三、项目展示
-
登录界面
-
主界面
-
聊天展示
四、具体实现
1. 前端
1) 登录界面
- 基于element-ui的简单form表单,用户需要填写用户名和密码
<template>
<div style="width: 1920px;height:1080px;display: flex;align-items:center;justify-content:center">
<el-form :model="form" status-icon :rules="rules" ref="form" label-width="80px" style="height: 600px;width: 400px">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="form.password"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">登录</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
- 点击登录按钮,进入submitForm方法,将用户信息存入sessionStorage中并进入聊天界面,在聊天界面中可以获取sessionStorage
submitForm() {
this.$refs['form'].validate((valid) => {
if (valid) {
sessionStorage.setItem('user', this.form.username)
this.$router.push('/chat')
} else {
console.log('error submit!!');
return false;
}
});
- 点击重置,将form表单清空
resetForm() {
this.form = null
}
2)聊天界面
- 使用element-ui组件构建聊天界面,效果如上文所示
- 使用钩子函数延迟加载init方法,若未在sessionStorage中找到用户信息,则跳转回到登录界面,找到则寻找websocket的url开启websocket服务,浏览器接受消息获取服务端的信息,有用户列表或者消息列表,将接受到的列表存入前端定义的列表中
init() {//session
// 如果sessionStorage中没有用户信息,则跳转登录页面
this.user = sessionStorage.getItem('user')
if (!this.user) {
this.$router.push('/')
}
let that = this;
if (typeof (WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
} else {
console.log("您的浏览器支持WebSocket");
let socketUrl = "ws://localhost:8888/socket/" + this.user;
if (socket != null) {
socket.close();
socket = null;
}
// 开启一个websocket服务
socket = new WebSocket(socketUrl);
//打开事件
socket.onopen = function () {
console.log("websocket已打开");
};
// 浏览器端收消息,获得从服务端发送过来的文本消息
socket.onmessage = function (msg) {
console.log("收到数据====" + msg.data)
let data = JSON.parse(msg.data)
if (data.userNames) {
// userNames存在则是有人登录,获取在线人员信息
that.userList = data.userNames
} else {
// userNames不存在则是有人发消息
that.msgList.push(data)
}
};
//关闭事件
socket.onclose = function () {
console.log("websocket已关闭");
};
//发生了错误事件
socket.onerror = function () {
console.log("websocket发生了错误");
}
}
}
- 点击发送按钮,与前端发送框双向绑定,向所有用户发送消息
send() {
if (!this.message.msg) {
this.$message({
message: '大兄弟,请输入聊天消息!',
type: 'warning'
});
} else {
if (typeof (WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
} else {
console.log("您的浏览器支持WebSocket");
this.message.from=this.user;
this.message.time=new Date().toLocaleTimeString();
socket.send(JSON.stringify(this.message));
this.message.msg = '';
}
}
}
3)路由设置
- 设置两个登录和聊天两个路由,默认为登录界面
export default new Router({
routes: [
{
path: '/',
name: 'login',
component: login
},
{
path: '/chat',
name: 'chat',
component: chat
}
]
})
2. 后端
1)Message类
- 创建信息类Message,在类中定义用户名、信息、发送方、接收方、时间
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Message {
// 时间
private String time;
// 接收方
private String to;
// 发送方
private String from;
// 消息
private String msg;
// 登录用户名
private List<String> userNames;
}
2)WebSocket服务
- 使用sessionMap存储每个用户对象,用户名对应一个session对象
public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();
- 建立连接,将登录用户存储在sessionMap中,发给所有人当前的userList
@OnOpen
public void onOpen(Session session, @PathParam("username") String username) {
System.out.println("当前用户名=="+username);
sessionMap.put(username, session);
// 发送登录人员消息给所有的客户端
sendAllMessage(setUserList());
}
- 设置用户列表,遍历sessionMap,设置用户消息类用户名,格式化为字符串格式
private String setUserList(){
ArrayList<String> list = new ArrayList<>();
for(String key:sessionMap.keySet()){
list.add(key);
}
Message message = new Message();
message.setUserNames(list);
return JSON.toJSONString(message);
}
- 发送消息,遍历sessionMap中的session域,将message发送给所有的session,若想指定发送,可以选择特定的session
// 服务端发送消息给指定客户端
private void sendMessage(String message, Session toSession) {
try {
toSession.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
// 服务端发送消息给所有客户端
private void sendAllMessage(String message) {
try {
for (Session session : sessionMap.values()) {
session.getBasicRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
- 接受消息,将消息格式为字符串,解析消息为java对象,根据session进行转发
@OnMessage
public void onMessage(String message) {
// 解析消息为java对象
Message msg = JSON.parseObject(message, Message.class);
if(StringUtils.isEmpty(msg.getTo())){
sendAllMessage(JSON.toJSONString(msg));
}else{
Session sessionTo = sessionMap.get(msg.getTo());
sendMessage(message,sessionTo);
}
}
- 关闭连接,将用户信息从sessionMap中删除,并向所有人发送当前的用户列表
@OnClose
public void onClose(@PathParam("username") String username) {
sessionMap.remove(username);
sendAllMessage(setUserList());
}
3)Session拦截
- 设置session拦截,对未登录的用户拒绝访问,跳转到登录界面,并且识别已登录用户24小时内自动访问
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路由
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
// 是否允许证书(cookies)
.allowCredentials(true)
// 设置允许的方法
.allowedMethods("*")
// 跨域允许时间
.maxAge(24*3600);
}
}
五、项目总结
这个项目实现了多人的网络聊天功能,使用sessionMap记录用户信息,实现了基本的登录和聊天功能,已存在的功能实现和界面的设计都是比较好的,但是缺少了足够强大的数据库来作为支撑,只能支持实时聊天,既不能保存用户群组信息,也不能在用户离线时接收消息,等待用户连接后再推送给用户。但是尽管如此,这个项目的可拓展性还是很强的,它拥有一套简单而强大的数据交换协议。
在技术方面,此项目使用了网络程序设计课程中所学到的知识,基本满足了专题实验的需求。但是仍然发现了个人在项目开发时有一些缺点,比如在需求分析时有时分析的不够深入,在软件设计过程中也存在犯错的地方。此外由于时间关系这个项目还有很多不完善的地方比如异常处理,还有很多代码逻辑有待优化。
感谢孟宁老师的教导,孟老师的网络程序设计,让我改变了对网络刻板的枯燥印象,并对此产生了浓烈的兴趣。同时,孟老师的授课风格也给我带来了以往所收获不到的惊喜,同学们可以自由地发言,孟老师也极其鼓励大家有不同的想法。一个阶段的结束意味着下一个阶段的开始,在以后的学习开发中要吸取从这次课程设计过程中的经验教训,戒骄戒躁,砥砺前行。