本文没有写完~~~~
聊天相关数据结构:
我们初步设计了如下几个数据结构。
//存放 sessionId 与 userId 的map
private Map<String,String> sessionId_userId =new HashMap<>();
// 用于存储用户与群组的关联关系,键为用户ID,值为群组ID列表 一个用户可以加入多个群组 它是一个Map,键是用户ID,值是群组ID列表
private Map<String, List<String>> userGroups = new HashMap<>();
// 用于存储群组信息,键为群组ID,值包含在线用户会话列表和历史消息列表
private Map<String, GroupInfo> groupInfos = new HashMap<>();
用户通过socket和服务端连接的时候,都会传递
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
LOGGER.info("WebSocket收到的消息: {}", message.getPayload());
// 将消息反序列化,假设消息是JSON格式,这里解析为Message对象(下面定义)
Message msg = objectMapper.readValue(message.getPayload().toString(), Message.class);
String groupId = msg.getGroupId();
String userId=msg.getUserId();
int flag=this.checkUserIdAndSessionId(session.getId(),userId); //检查userId和sessionId是否匹配
//flag==0 说明userId和sessionId匹配 ,不做更多的操作 直接执行下面的代码
if(flag==2){ //userId一致 但是 sessionId不一致,说明用户重新建立了session,要把原来该用户相关的session等全部删除掉
//找到这个用户下原来所有的session,然后全部删除掉
for (Map.Entry<String, String> entry : sessionId_userId.entrySet()) {
String storedSessionId = entry.getKey();
String storedUserId = entry.getValue();
if (storedUserId.equals(userId)) {
sessionId_userId.remove(storedSessionId);
}
//by userId remove all the groupInfo
List<String> groupIds = userGroups.get(userId);
if (groupIds!= null) {
for (String groupId1 : groupIds) {
GroupInfo groupInfo = groupInfos.get(groupId1);
if (groupInfo!= null) {
groupInfo.removeSession(session);
}
}
userGroups.remove(userId);
}
}
}else if(flag==3){ //userId不存在 session不存在 ,用户第一次建立连接
//do nothing 放在这里就是为了说明flag==3的情况
}
if(flag==2 || flag==3){
sessionId_userId.put(session.getId(),userId); //sessionId 与 userId 建立映射 是1对1的关系
List<String> groupIds = userGroups.computeIfAbsent(userId, k -> new ArrayList<>()); // 获取用户的群组ID列表
groupIds.add(groupId);
}
GroupInfo groupInfo = groupInfos.get(groupId);
if (groupInfo == null) {
// 如果群组信息不存在,则创建新的群组信息,并添加当前用户的WebSocketSession
groupInfo = new GroupInfo();
groupInfo.addSession(session);
groupInfos.put(groupId, groupInfo);
// 同时,假设这里从WebSocket连接的属性或者请求参数中获取用户ID(实际需按业务逻辑调整获取方式)
//String userId = (String) session.getAttributes().get("userId");
// List<String> groupIds = userGroups.computeIfAbsent(userId, k -> new ArrayList<>()); // 获取用户的群组ID列表
// groupIds.add(groupId);
}else {
groupInfo.addSession(session); //addSession()方法会检查是否已经存在,如果存在就不会再添加
}
// 将消息添加到群组历史消息列表
groupInfo.addHistoryMessage(msg);
// 向群组内所有在线用户发送消息
List<WebSocketSession> sessions = groupInfo.getSessions();
for (WebSocketSession s : sessions) {
try {
s.sendMessage(new TextMessage(objectMapper.writeValueAsString(msg)));
} catch (IOException e) {
LOGGER.error("无法发送WebSocket消息", e);
}
}
}
,
uniapp端页面:
<template>
<view class="chat-room">
<!-- 聊天记录 -->
<scroll-view scroll-y="true" class="message-list">
<view v-for="(message, index) in messages" :key="index" class="message-item">
<view v-if="message.type === 'text'" class="text-message">
<!-- <view class="avatar">{{ message.senderAvatar }}</view> -->
<view class="text">{{ message.content }}</view>
</view>
<view v-else-if="message.type === 'image'" class="image-message">
<image :src="message.content" class="message-image" @click="previewImage(message.content)"></image>
</view>
</view>
</scroll-view>
<!-- 输入框 -->
<view class="input-area">
<input v-model="inputContent" placeholder="输入内容" class="input" />
<button @click="sendMessage">发送</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
messages: [], // 聊天记录
inputContent: '', // 输入框内容
socketOpen: false, // WebSocket连接状态
socket: null, // WebSocket对象
};
},
methods: {
// 初始化WebSocket连接
initWebSocket() {
uni.setStorageSync('userId',1); // only test
uni.setStorageSync('groupIds',2); // only test
const userId = uni.getStorageSync('userId');
const groupIds = uni.getStorageSync('groupIds');
console.log(groupIds);
// /wechat/client/chat
this.socket = uni.connectSocket({
url: 'ws://127.0.0.1:7877/chat/websocket',
data: {
userId: userId,
groupIds: groupIds
},
success: () => {
console.log('WebSocket连接成功');
this.socketOpen = true;
},
fail: () => {
console.error('WebSocket连接失败');
},
});
// 监听WebSocket消息
this.socket.onMessage((res) => {
const message = JSON.parse(res.data);
this.messages.push(message); // 将新消息添加到聊天记录中
this.$forceUpdate(); // 强制更新视图,确保新消息显示
});
// 监听WebSocket连接关闭
this.socket.onClose(() => {
console.log('WebSocket连接关闭');
this.socketOpen = false;
});
},
// 发送消息
sendMessage() {
if (this.inputContent.trim() === '') {
uni.showToast({
title: '请输入内容',
icon: 'none',
});
return;
}
const message = {
type: 'text', // 消息类型,可以是text或image,这里发送文字消息示例,发送图片时修改相应字段
groupId: uni.getStorageSync('groupIds'), // 从本地存储获取当前所在群组ID(需按实际情况调整获取方式)
sender: uni.getStorageSync('userId'), // 从本地存储获取用户ID(需按实际情况调整获取方式)
content: this.inputContent
};
// 通过WebSocket发送消息(或HTTP请求,根据后端接口决定)
if (this.socketOpen) {
this.socket.send({
data: JSON.stringify(message)
});
} else {
//todo
}
},
// 预览图片
previewImage(url) {
uni.previewImage({
current: url, // 当前显示图片的http链接
urls: [url], // 需要预览的图片http链接列表
});
},
},
mounted() {
// 页面加载时初始化WebSocket连接
this.initWebSocket();
// 可以从服务器获取历史聊天记录并初始化messages数组(根据需求实现)
},
};
</script>
<style>
.chat-room {
padding: 10px;
}
.message-list {
height: 500px; /* 根据需要调整高度 */
border-bottom: 1px solid #ccc;
padding-right: 10px; /* 留出空间给滚动条 */
overflow-y: auto;
}
.message-item {
margin-bottom: 10px;
display: flex;
align-items: center;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 10px;
}
.text-message .text {
background-color: #fff;
padding: 5px 10px;
border-radius: 5px;
max-width: 60%; /* 根据需要调整宽度 */
word-wrap: break-word; /* 防止长文本溢出 */
}
.image-message .message-image {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 5px;
}
.input-area {
display: flex;
margin-top: 10px;
}
.input {
flex: 1;
padding: 5px;
border: 1px solid #ccc;
border-radius: 5px;
}
button {
padding: 5px 10px;
margin-left: 10px;
border: none;
background-color: #1aad19;
color: #fff;
border-radius: 5px;
}
</style>
原型效果:
后期再补充~~~~~