1.项目简介
基于浏览器和后端SpringBoot在WebScoket基础上实现一个简单的网页聊天程序
1.1 项目设计
- 需求分析(简单理解客户需求有什么功能达到什么效果)
- 概念设计阶段(原型设计、数据设计模型-ER图)
- 详细设计阶段(UML设计图-用例图、时序图)
- 编码阶段(技术选型、基础架构做好)
- 软件测试阶段(黑盒、白盒测试-接口压力测试、并发编程测试)
- 项目部署–软件开发闭环
1.1 技术选型
- 前端: HTML+CSS+JavaScript 工具:VSCode
- 服务端:SpringBoot+WebScoket
2. 实现效果概述
3. 用例、时序展示
4. 框架构建实现
4.1 后端实现
1. SpringBoot项目构建
1.java运行环境配置
1.java-bin路径配置到环境变量path中
2.java-jre-bin配置到环境变量path中
然后使用cmd打开,运行java指令和javac指令有内容输出就代表环境变量配置好了
2. maven
1. 进入maven官网
https://maven.apache.org/
2. 下载完成解压maven包,然后将bin路径配置到path系统环境变量中
3. maven修改conf目录下,settings.xml文件
修改本地仓库目录
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>本地仓库位置复制到这里</localRepository>
<pluginGroups>
</pluginGroups>
<proxies>
</proxies>
<servers>
</servers>
<mirrors>
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*,!jeecg,!jeecg-snapshots,!getui-nexus</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
<profiles>
</profiles>
</settings>
3. 使用idea构建springboot项目
创建项目后点击设置,从新设置maven仓库位置
运行启动类
出现上图效果代表项目启动成功!
如果需要修改端口号,在resources目录下有一个application.properties的全局配置文件
# 设置SpringBoot启动的端口号
server.port=9010
2. SpringBoot集成WebScoket
概述WebSocket
使用目的:能够使用TCP方式实现快速和客户端服务器之间简历长久的Socket通信
全双工通信,且为长链接
一般就是适用于终端机器(安卓、ios、鸿蒙)、客户端网页脚本快速和服务端通信技术。
解决HTTP问题:不适合使用在实时通信场景
webScoket封装好的三种场景
-
单播(Unicast)
1对1发送
-
广播(BroadCast)
1对所有发送
-
组播(Mulitcast)
1对特定一群
3.springBoot引入WebSocket依赖
<!-- 服务端WebSocket需要的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 引入web开发三个包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- 阿里fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
4. 要配置一个聊天服务端
WebSocket配置类
package com.cqgyzyjsxy.liaotianservice.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.websocket.server.ServerEndpointConfig;
/**
* 模块名称:
* 模块类型:
* 编码人:高靖博
* 创建时间:2023/4/12
* 联系电话:18587388612
*/
@Configuration
public class WebScoketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
聊天处理类
package com.cqgyzyjsxy.liaotianservice.config;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 模块名称: 聊天程序服务端WebSocket配置
* 模块类型:
* 编码人:高靖博
* 创建时间:2023/4/12
* 联系电话:18587388612
*/
// LiaoTianSocket springBoot组件放入Spring容器中
@Component
// 客户端要连接聊天服务器时,指定的服务端地址
// 客户端连接服务器聊天服务的地址案例: ws://服务器ip地址:服务端口号/qqserver
@ServerEndpoint("/qqserver/{qqNumber}")
public class LiaoTianSocket {
static{
System.out.println("----------------------------------");
System.out.println("------ WebSocket服务启动 -------");
System.out.println("--------- ----------");
System.out.println("----------------------------------");
}
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
* QQ号
*/
private String qqNumber;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
//虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
// 注:底下WebSocket是当前类名
private static CopyOnWriteArraySet<LiaoTianSocket> webSockets =new CopyOnWriteArraySet<>();
// 用来存在线连接用户信息( 线程安全 )
// key: QQ号 value: 每个客户端的session对象
private static ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<String,Session>();
// 1. 当客户端连接时做什么事情
@OnOpen
public void onOpen(Session session,@PathParam(value="qqNumber") String qqNumber){
System.out.println("--客户端连接[qq:"+qqNumber+"]--");
this.session = session;
this.qqNumber = qqNumber;
sessionPool.put(qqNumber,session);
webSockets.add(this);
}
// 2.当客户端发送消息给服务器时做什么事情
// 参数message就是客户端给服务器发送的消息内容
@OnMessage
public void onMsg(String message){
// 实现群发功能
try {
sendAllMsg(message);
} catch (IOException e) {
System.err.println("发送消息异常");
e.printStackTrace();
}
}
// 3.当客户端断开连接时要做什么事情
@OnClose
public void onClose(){
System.out.println("客户端断开连接~!~!!");
}
// 4.当客户端发送消息、连接或断开连接时错误做什么事情
@OnError
public void onErr(Throwable error){
error.printStackTrace();
System.out.println("服务器或客户端异常!!!!");
}
/**
* 群发功能
* @param msg 群发的消息
*/
public void sendAllMsg(String msg) throws IOException {
System.out.println("--群发消息:"+msg);
// 遍历所有已经连接到服务器的客户端对象
for(LiaoTianSocket socket:webSockets){
if(socket.session.isOpen()){// 判断客户端是否在线
socket.session.getAsyncRemote().sendText(msg);
}
}
}
}
4.2 前端实现
测试页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试</title>
<style>
/* 历史信息窗口样式 */
.hsDiv{
border:1px solid black;
border-radius: 11px;
width: 100%;
height: 500px;
overflow: auto;
box-sizing: border-box;
padding: 8px;
}
/* 消息的时间文字样式 */
.time{
font-size: 10px;
color:gray;
}
/* 消息的文字内容样式 */
.msgText{
color:blue;
}
/* 发送人信息 */
.person{
font-size: 7px;
color:black;
}
</style>
</head>
<body>
<span id="state">离线</span>
<span id="qqNumber"></span>
<input id="nickName" placeholder="定义一个你的昵称" />
<button onclick="toConect()">连接</button>
<hr/>
<button onclick="sendMsg()">发送</button>
<input id="msg" placeholder="请输入消息...." />
<hr/>
<!-- 历史信息框体 -->
<div class="hsDiv" id="hsDiv">
</div>
</body>
<script>
var qqNumber;
var webScoket;
var htmls = ""; // 所有历史信息html
var nikeName = ""; // 客户端昵称
// 连接到服务器
function toConect(){
// 生成一个唯一的数字
qqNumber = parseInt(Math.random()*10000000000000);
webScoket = new WebSocket("ws://localhost:9010/qqserver/"+qqNumber);
// 定义一个连接成功的回调函数
webScoket.onopen = function (){
console.dir("-连接成功!");
document.getElementById("state").innerHTML = "在线";
document.getElementById("qqNumber").innerHTML = qqNumber;
// 获取连接的昵称信息
var nikeNames = document.getElementById("nickName").value;
nikeName = nikeNames;
}
// 定义一个服务器发送消息给客户端的回调函数
webScoket.onmessage = function(data){
console.dir("-接收到消息:"+data.data);
// QQ信息$消息内容$发送时间$昵称
var msgArr = data.data.split("$");
htmls += '<span class="time">时间:'+msgArr[2]+'</span><br/>'+
'<span class="person">['+msgArr[3]+']</span><br/>'+
'<span class="msgText">'+msgArr[1]+'</span>'+
'<hr/>';
// 动态html代码段添加进入聊天历史信息框中
document.getElementById("hsDiv").innerHTML = htmls;
}
}
// 发送消息方法
function sendMsg(){
// 得到要发送的信息文字
var msg = document.getElementById("msg").value;
// 发送消息的格式:
// QQ信息$消息内容$发送时间$昵称
var nowDate = new Date();
var sendMsgStr = qqNumber+"$"+msg+"$"+(nowDate.getFullYear() +"-"+nowDate.getMonth()+"-"+nowDate.getDate()+" "+nowDate.getHours()+":"+nowDate.getMinutes()+":"+nowDate.getSeconds()+"$"+nikeName)
webScoket.send(sendMsgStr); // 消息发送给服务器了
}
</script>
</html>
gitee 仓库源码
https://gitee.com/gdones/cqgy-webqq/blob/master/README.md