实验目的
不过最后的验收只要能实现功能就可以,不限制使用的语言。
所以我是用java来实现的。
项目技术栈
数据库:mysql
后台框架:springboot + mybatis
前端:html + css + js + ajax
页面展示
登录:
注册:
聊天室:
项目结构
代码逻辑说明
说明:前端和后端之前的交互信息为json格式,此程序中包含两类响应信息类,一个是Result,包含两个字段,flag和message,若flag为1则代表登录/注册成功,若flag为-1则说明失败,并在前端打印message,提示用户登陆失败;另一个是ResultMessage,同样包含两个字段,isSystem和message,若isSystem为真,则说明message是诸如提示上线等的系统信息,前端应当将其展示在广播位置,若isSystem为假,则说明message是用户的聊天信息,前端应当将其展示在聊天面板。
登录 / 注册
登录时用户在页面填写用户名和密码,点击登录按钮后执行login方法,JavaScript通过填写框的id获得用户写入其中的内容,并将获取到的信息打包成参数,通过ajax技术向服务器发起post请求:
后端处理并响应:前端请求的地址指向后端的sign-in
在userController的login方法中取到前端发来的参数,交给userService的sigin方法去进行业务逻辑的处理,处理思路为:根据用户名在数据库中查找该用户,若查找结果为空则说明不存在该用户,若不为空则比较传过来的密码和数据库里存的密码,若不相等则说明用户名或密码错误,若相等则登陆成功。
注册的请求和响应逻辑与登录的一致,只是在Service层处理的逻辑不一样,注册的处理逻辑是:根据前端发来的用户名在数据库里查找该用户,若查找结果不为空,则说明该用户名已存在,注册失败;若查找结果为空,则将该用户信息添加进数据库,添加成功则返回“注册成功”,否则返回“注册失败,数据库出错”。
聊天室
实现前后端通信用的是websocket协议。WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。其使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
登陆成功后,需要建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接:
服务器处理:
ChatEndPoint类用于处理客户端的websocket请求,每有一个客户端发起连接请求,就创建一个ChatEndPoint实例,每个实例包含的成员变量有:
- onlineUsers,这是一个ConcurrentHashMap,用来存储当前处于连接状态的客户端,即在线用户。其key是用户名,value是ChatEndPoint实例;
- session,存储了标识用户的信息;
- username,当前用户的用户名。
当前端发起连接请求,服务器收到后自动调用onOpen方法:
@OnOpen //链接建立时被调用
public void onOpen(Session session,@PathParam("param")String username){
//将局部session赋值给成员session
this.session = session;
this.username = username;
onlineUsers.put(username,this);
//群发告诉每一位
String systemMessaage = MessageUtils.getMessage(true,username+"上线,在线人数为:"+onlineUsers.size());
System.out.println("systemMessaage:"+systemMessaage);
broadcast(systemMessaage);
}
如上,其逻辑是:首先将获取到的参数赋值给成员变量,然后广播告诉每一位在线用户“某某某上线”。
广播的方法:遍历在线列表,调用sendText()方法将消息发给其他用户。
public void broadcast(String message){
try {
//要将该消息推送给所有的客户端
Set<String> names = onlineUsers.keySet();
for (String name : names) { //遍历每个客户端,并发送消息
if(name.equals(username))
continue;
ChatEndPoint chatEndPoint = onlineUsers.get(name);
chatEndPoint.session.getBasicRemote().sendText(message);
}
}catch (Exception e){
e.printStackTrace();
}
}
当接收到客户端的信息后自动调用onMessage()方法:转发客户端发来的信息
@OnMessage //接收到客户端的消息后被自动调用
public void onMessage(String message){
System.out.println("来自客户端的消息:"+message);
//群发
String clientMessage = MessageUtils.getMessage(false,message);
System.out.println("clientMessage:"+clientMessage);
broadcast(clientMessage);
}
当连接关闭时自动调用onClose()方法:
@OnClose //链接关闭时被调用
public void onClose(){
onlineUsers.remove(username);
System.out.println("有一位下线了,当前在线人数为:"+onlineUsers.size());
String systemMessaage = MessageUtils.getMessage(true,username+"潇洒下线,在线人数为:"+onlineUsers.size());
System.out.println("systemMessaage:"+systemMessaage);
broadcast(systemMessaage);
}
当通信发生错误时自动调用onError()方法:
@OnError //发生错误时调用
public void onError(Session session,Throwable error){
System.out.println("发生错误");
error.printStackTrace();
}
客户端:
连接成功后自动调用onopen方法:将用户状态改为在线,文字颜色改为绿色。
websocket.onopen = function (){
var stu = document.getElementById('status');
stu.innerHTML= "在线";
stu.style='color:green';
}
当收到服务器的信息后自动调用onmessage方法:
//接收到消息的回调方法
websocket.onmessage = function(event) {
// var res = event.data;
var res = eval("(" + event.data + ")");
var date = new Date();
var time = date.getFullYear()+"年"+date.getMonth()+"月"+date.getDay()+"日 "+date.getHours()+":"+date.getMinutes()+":"+date.getSeconds();
if(res.isSystem){
var time2 = date.getHours()+":"+date.getMinutes()+":"+date.getSeconds();
var str = '<p style="font-size: small;color: red">'+time2+'</p>'+res.message
document.getElementById('broadcastlist').innerHTML += str + '<br/>';
}else{
var str = '<div class="leftMessage"><p style="font-size: xx-small;color: #606c71">'+time+'</p><span>' + res.message +'</span></div>' ;
document.getElementById('content').innerHTML += str;
}
}
如上,其逻辑是将服务器发来的响应信息解析成js对象,然后判断是否为系统信息,根据信息的类型将携带的message展示在页面相应的位置。
当断开连接时自动调用onclose方法:将用户状态改为离线
//连接关闭的回调方法
websocket.onclose = function() {
var sta = document.getElementById('status');
sta.innerHTML= "离线";
sta.style="color:red";
}
当通信发生错误时自动调用onerror方法:在页面提示出现信息
//发生错误的回调方法
websocket.onerror = function() {
document.getElementById('new').innerHTML= "与服务器未连接成功";
};