页面不是很优美,但是功能全了,拿到源码的可以自己去美化一下就可以
1 - SpringBoot环境搭建
1 - maven 依赖
<parent>
<artifactId>spring-boot-starter-parentartifactId>
<groupId>org.springframework.bootgroupId>
<version>2.3.5.RELEASEversion>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
<dependency>
<groupId>com.github.ulisesbocchiogroupId>
<artifactId>jasypt-spring-boot-starterartifactId>
<version>3.0.3version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.xmlinclude>
includes>
resource>
<resource>
<directory>src/main/resourcesdirectory>
<includes>
<include>**/*.*include>
includes>
resource>
<resource>
<directory>src/main/webappdirectory>
<targetPath>META-INF/resourcestargetPath>
<includes>
<include>**/*.*include>
includes>
resource>
resources>
build>
2 - socket
@Slf4j //lombok jar包,帮我们自动生成一些代码:@Data
@Component
@ServerEndpoint("/websocket/{username}")
public class ChatServerEndpoint {
public static final Map<String, Session> CLIENTS = new ConcurrentHashMap<>();
/**
* 连接建立时触发
*/
@OnOpen
public synchronized void openSession(@PathParam("username") String username, Session session) {
String message = "[" + username + "]登录";
//存放到map集合中
CLIENTS.put(username,session);
//告诉自己当前在线的人数
Set<String> userNames = CLIENTS.keySet();
userNames.forEach(u -> {
try {
session.getBasicRemote().sendText("[" + u + "]登录");
} catch (IOException e) {
e.printStackTrace();
}
});
//告诉所有人
CLIENTS.forEach((u,s) -> {
try {
if (s != session) //上面我已经告诉自己了 所以不能再告诉自己了
s.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
});
}
/**
* 客户端接收服务端数据时触发
*/
@OnMessage
public synchronized void onMessage(@PathParam("username") String username, String message) {
log.info("发送消息:{}, {}", username, message);
//告诉所有人发消息
String value = "["+username+"]:"+ message;
CLIENTS.forEach((u,s) -> {
try {
s.getBasicRemote().sendText(value);
} catch (IOException e) {
e.printStackTrace();
}
});
}
/**
* 连接关闭时触发
*/
@OnClose
public synchronized void onClose(@PathParam("username") String username, Session session) {
// 当前的Session移除某个用户
CLIENTS.remove(username);
//离开消息通知所有人有人离开了
CLIENTS.forEach((u,s) -> {
try {
s.getBasicRemote().sendText("[" + username + "]离开");
} catch (IOException e) {
e.printStackTrace();
}
});
}
/**
* 通信发生错误时触发
*/
@OnError
public synchronized void onError(Session session, Throwable throwable) {
try {
//关闭WebSocket Session会话
System.out.println("被摧毁");
session.close();
} catch (IOException e) {
e.printStackTrace();
log.error("onError Exception", e);
}
log.info("Throwable msg " + throwable.getMessage());
}
}
3 - 配置类
@EnableWebSocket //启用WebSocket支持
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
4 - controller 获取随机用户名
@RestController
@CrossOrigin
public class ChatController {
private AtomicInteger idProducer = new AtomicInteger();
@RequestMapping("/getName")
public String index(Model model) {
return "user" +idProducer.getAndIncrement();
}
}
App.jsx
import { useEffect, useRef, useState } from "react";
import './App.css';
import axios from 'axios';
import Right from "./components/Right";
import Left from "./components/Left";
import { notification} from 'antd';
import { Avatar,Comment, TextArea, Form, Button, List, Input } from 'antd';
let websocket;
let username
function App() {
const messageRef = useRef(null)
const [messageList,setMessageList] = useState([])
const [message,setMessage] = useState("")
const [userList,setUserList] = useState([])
const { TextArea } = Input;
const data = [
{
render:(item) => {
return <>item</>
}
}
]
useEffect(() => {
async function start() {
if (!localStorage.getItem('username')) {
await axios.get("http://localhost:8080/getName").then(response => {
localStorage.setItem('username', response.data)
})
}
username = localStorage.getItem('username')
let baseUrl = "ws://localhost:8080/websocket/"
websocket = new WebSocket(baseUrl + localStorage.getItem('username'));
websocket.onopen = ()=> {
console.log("建立 websocket 连接...");
};
websocket.onmessage = (event) => {
const data = event.data
setMessage(data)
};
websocket.onerror = (event) => {
console.log("websocket发生错误..." + event + '\n');
}
websocket.onclose = ()=> {
console.log("关闭 websocket 连接...");
};
}
start()
},[])
useEffect(() => {
console.log(message)
if (message.indexOf(":") > 0) {
setMessageList([...messageList,message])
console.log(messageList)
setMessage("")
return
}
if (message.indexOf("登录") > 0) {
setUserList([...userList.filter(item => {
return item !== message
}),message])
notification.info({
message: `${message}`,
description: ``,
placement:'topLeft'
});
setMessage("")
return
}
if (message.indexOf('离开') > 0) {
let messageUsername = message.substr(0,message.indexOf("]") + 1)
setUserList([...userList.filter(item => item.indexOf(messageUsername) < 0 )])
notification.info({
message: `${message}`,
description: ``,
placement:'topLeft'
});
setMessage("")
return
}
},[message])
const sendMessage = () => {
const message = messageRef.current.value
if (message.trim() === "") {
alert("请重新输入")
return
}
messageRef.current.value = ""
websocket.send(message)
}
return (
<>
<div className="header">
<h2>聊天室</h2>
</div>
<div className="container">
<div className="chart">
{
messageList.map(item => {
if (item.indexOf(username) > 0) {
console.log(username)
return <Right value={item} key={item + Math.random()} />
} else {
return <Left value={item} key={item + Math.random()}/>
}
})
}
</div>
<div className="input-value">
<textarea ref={messageRef} type="text" placeholder="发送消息" />
<button onClick={sendMessage}>发送</button>
</div>
</div>
<div className="online">
<span>当前在线人数 {userList.length}</span>
{
userList.map(item => {
return <h2 key={Math.random()}>{item}</h2>
})
}
</div>
</>
)
}
export default App;
App.css
@import '~antd/dist/antd.css';
.header {
width: 100%;
height: 50px;
background-color: #008c8c;
text-align: center;
line-height: 50px;
}
.container {
position: absolute;
margin-top: 50px;
background-color: aqua;
width: 80%;
top: 0;
bottom: 0;
}
.chart {
position: absolute;
top: 0;
width: 100%;
height: 70%;
background-color: #fff;
padding: 20px;
overflow: scroll;
}
.input-value{
width: 100%;
height: 30%;
bottom: 0;
position: absolute;
background-color: #008c8c;
}
.input-value textarea {
width: 100%;
height: 80%;
border: 0;
padding: 20px;
}
.input-value button {
width: 100%;
border: 0;
height: 15%;
}
.online{
position: absolute;
margin-top: 50px;
background-color: aqua;
width: 20%;
top: 0;
bottom: 0;
right: 0;
}
左边组件
import React from "react";
function Left(props) {
let value = props.value
const username = value.substr(0,value.lastIndexOf("]") + 1)
const message = value.substr(value.lastIndexOf(":") + 1);
return (
<>
<div className="item">
<div>
<div style={{fontSize:'15px',color:'#ccc',textAlign:'left'}}>{username}</div>
<div style={{padding:'10px',textAlign:'left'}}>{message}</div>
</div>
</div>
</>
)
}
export default Left;
右边组件
import React from "react";
function Right(props) {
let value = props.value
const username = value.substr(0,value.lastIndexOf("]") + 1)
const message = value.substr(value.lastIndexOf(":") + 1);
return (
<>
<div className="item">
<div>
<div style={{fontSize:'15px',color:'#ccc',textAlign:'right'}}>{username}</div>
<div style={{padding:'10px',textAlign:'right'}}>{message}</div>
</div>
</div>
</>
)
}
export default Right;