From 3b3646583f8d866aeca6dcbd56e0d21c3a22d09a Mon Sep 17 00:00:00 2001
From: Bryan <15172886+bryanlufee@user.noreply.gitee.com>
Date: Wed, 12 Nov 2025 18:25:52 +0800
Subject: [PATCH] 22
---
RuoYi-Vue/ruoyi-admin/pom.xml | 8 +
.../ruoyi/redis/RedisMessagePublisher.java | 28 +++
.../ruoyi/redis/RedisMessageSubscriber.java | 56 +++++
.../com/ruoyi/redis/RedisPubSubConfig.java | 62 ++++++
.../server/GameRoomWebSocketHandler.java | 60 ++++-
scoring/scoring/App.vue | 4 +-
scoring/scoring/main.js | 23 +-
scoring/scoring/package.json | 7 +-
scoring/scoring/pages.json | 2 +
scoring/scoring/pages/index/index.vue | 1 +
scoring/scoring/pages/more/more.vue | 206 +++++++++++-------
scoring/scoring/pages/single/single.vue | 60 ++++-
12 files changed, 405 insertions(+), 112 deletions(-)
create mode 100644 RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/redis/RedisMessagePublisher.java
create mode 100644 RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/redis/RedisMessageSubscriber.java
create mode 100644 RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/redis/RedisPubSubConfig.java
diff --git a/RuoYi-Vue/ruoyi-admin/pom.xml b/RuoYi-Vue/ruoyi-admin/pom.xml
index 8d86e17..282b9e4 100644
--- a/RuoYi-Vue/ruoyi-admin/pom.xml
+++ b/RuoYi-Vue/ruoyi-admin/pom.xml
@@ -87,6 +87,14 @@
json
20231013
+
+ com.ruoyi
+ ruoyi-system
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
diff --git a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/redis/RedisMessagePublisher.java b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/redis/RedisMessagePublisher.java
new file mode 100644
index 0000000..4346d88
--- /dev/null
+++ b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/redis/RedisMessagePublisher.java
@@ -0,0 +1,28 @@
+package com.ruoyi.redis;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class RedisMessagePublisher {
+
+ @Autowired
+ private StringRedisTemplate redisTemplate;
+
+ /**
+ * 向指定频道发布消息
+ * @param channel 频道名称
+ * @param message 消息内容(JSON字符串)
+ */
+ public void publish(String channel, String message) {
+ redisTemplate.convertAndSend(channel, message);
+ }
+ /**
+ * 作用:发布消息到 Redis 频道:将 message 发送到指定的 channel。
+ * 触发订阅者处理:所有通过 RedisMessageListenerContainer 订阅了该频道的客户端(如其他微服务、后台任务等)会立即收到消息,并调用对应的消息处理器(如 onMessage 方法)。
+ *
+ * channel:目标频道名称(如 "game_room_channel")。
+ * message:要发送的消息,可以是任意对象(Spring 会自动序列化为字节数组或 JSON)。
+ */
+}
\ No newline at end of file
diff --git a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/redis/RedisMessageSubscriber.java b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/redis/RedisMessageSubscriber.java
new file mode 100644
index 0000000..1060efc
--- /dev/null
+++ b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/redis/RedisMessageSubscriber.java
@@ -0,0 +1,56 @@
+package com.ruoyi.redis;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import com.ruoyi.websocket.server.GameRoomWebSocketHandler;
+import java.io.IOException;
+import java.util.Map;
+
+@Component
+public class RedisMessageSubscriber {
+
+ @Autowired
+ private ObjectMapper objectMapper;//是jackson里面的方法,主要处理序列化和反序列化
+
+ /**
+ * 处理从Redis订阅到的消息
+ */
+ public void onMessage(String redisMessage, String channel) {
+ try {
+ // 解析消息,其中应包含房间ID和要发送的实际消息体
+ Map messageMap = objectMapper.readValue(redisMessage, Map.class);
+ String targetRoomId = (String) messageMap.get("roomId");
+ Map actualMessageToSend = (Map) messageMap.get("message");
+
+ // 调用本地广播方法,只发送给本实例内指定房间的用户
+ broadcastToLocalRoom(targetRoomId, actualMessageToSend);
+
+ } catch (Exception e) {
+ System.err.println("处理Redis消息失败: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 仅向本地会话(即当前JVM内的WebSocketSession)广播消息
+ */
+ private void broadcastToLocalRoom(String roomId, Map message) throws Exception {
+ // 遍历原Handler中的sessions变量(需稍作调整使其可被访问)
+ for (Map.Entry entry : GameRoomWebSocketHandler.getSessions().entrySet()) {
+ String sessionKey = entry.getKey();
+ if (sessionKey.startsWith(roomId + "_")) { // 判断是否属于目标房间
+ WebSocketSession session = entry.getValue();
+ if (session != null && session.isOpen()) {
+ try {
+ String jsonMessage = objectMapper.writeValueAsString(message);
+ session.sendMessage(new TextMessage(jsonMessage));
+ } catch (IOException e) {
+ System.err.println("向本地会话发送消息失败: " + e.getMessage());
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/redis/RedisPubSubConfig.java b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/redis/RedisPubSubConfig.java
new file mode 100644
index 0000000..3f41fa7
--- /dev/null
+++ b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/redis/RedisPubSubConfig.java
@@ -0,0 +1,62 @@
+package com.ruoyi.redis;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.listener.ChannelTopic;
+import org.springframework.data.redis.listener.RedisMessageListenerContainer;
+import org.springframework.data.redis.listener.Topic;
+import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
+
+@Configuration
+public class RedisPubSubConfig {
+
+ @Autowired
+ private RedisConnectionFactory redisConnectionFactory;
+
+ @Autowired
+ private RedisMessageSubscriber redisMessageSubscriber;
+
+ @Bean
+ public RedisMessageListenerContainer redisMessageListenerContainer() {
+ RedisMessageListenerContainer container = new RedisMessageListenerContainer();
+ container.setConnectionFactory(redisConnectionFactory);// 设置 Redis 连接
+
+ // 订阅游戏房间频道
+ Topic gameRoomTopic = new ChannelTopic("game_room_channel"); // 订阅的频道
+ // 指定消息处理器(即redisMessageSubscriber的onMessage方法)
+ container.addMessageListener(new MessageListenerAdapter(redisMessageSubscriber, "onMessage"), gameRoomTopic);// addMessageListener绑定监听器和频道
+ /**
+ * 整工作流程
+ * 启动应用:Spring 初始化 RedisMessageListenerContainer,并建立与 Redis 的连接。
+ * 订阅频道:容器开始监听 game_room_channel 频道。
+ * 发布消息:其他服务或代码通过 Redis 的 PUBLISH 命令向 game_room_channel 发送消息,例如:
+ * bash
+ * PUBLISH game_room_channel "Hello, Game Room!"
+ * 消息处理:
+ * Redis 将消息推送给所有订阅者。
+ * RedisMessageListenerContainer 接收到消息后,调用 RedisMessageSubscriber.onMessage() 方法。
+ * 消息内容作为参数传递给 onMessage 方法,由开发者自定义处理逻辑(如更新游戏状态、广播通知等)。
+ */
+ /**
+ * MessageListenerAdapter
+ * 作用:将 Redis 消息转发到指定的处理器方法(这里是 redisMessageSubscriber.onMessage)。
+ * 关键点:
+ * 第二个参数 "onMessage" 指定处理器方法名(需在 RedisMessageSubscriber 中实现)。
+ * 默认情况下,消息会以 byte[] 形式传递给处理器,Spring 会自动反序列化。
+ */
+
+ /***
+ * 发布消息的步骤
+ * 选择消息中间件:根据需求(如吞吐量、持久性、延迟)选择合适的工具。
+ * 创建主题(Topic)或队列(Queue):
+ * 主题(Topic):一对多广播(所有订阅者都能收到消息)。
+ * 队列(Queue):点对点(消息被一个订阅者消费)。
+ * 发布消息:
+ * 指定主题/队列名称。
+ * 发送消息内容(通常是JSON、Protobuf等格式)。
+ */
+ return container;
+ }
+}
\ No newline at end of file
diff --git a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/websocket/server/GameRoomWebSocketHandler.java b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/websocket/server/GameRoomWebSocketHandler.java
index 26120dc..04067af 100644
--- a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/websocket/server/GameRoomWebSocketHandler.java
+++ b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/websocket/server/GameRoomWebSocketHandler.java
@@ -1,19 +1,32 @@
package com.ruoyi.websocket.server;
+import com.ruoyi.system.domain.ScoreRoomDetail;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
+import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
public class GameRoomWebSocketHandler extends TextWebSocketHandler {
+
+
+ @Autowired
+ private StringRedisTemplate redisTemplate;
// 存储所有连接的会话
private static final Map sessions = new ConcurrentHashMap<>();
private final ObjectMapper objectMapper = new ObjectMapper();
+ public static Map getSessions() {
+ return Collections.unmodifiableMap(sessions); // 返回不可修改的视图,保证线程安全
+ }
+
+
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 1. 从路径参数中获取 roomId 和 userId
@@ -40,9 +53,12 @@ public class GameRoomWebSocketHandler extends TextWebSocketHandler {
welcomeMsg.put("message", "欢迎加入房间: " + roomId);
welcomeMsg.put("roomId", roomId);
welcomeMsg.put("userId", userId);
+
welcomeMsg.put("timestamp", System.currentTimeMillis());
sendJsonMessage(session, welcomeMsg);
+
+ broadcastJsonToRoom(roomId,welcomeMsg);
}
@Override
@@ -64,22 +80,42 @@ public class GameRoomWebSocketHandler extends TextWebSocketHandler {
try {
// 解析客户端发送的 JSON 消息
Map clientMessage = objectMapper.readValue(payload, Map.class);
- String msgType = (String) clientMessage.getOrDefault("type", "chat");
+ String msgType = (String) clientMessage.getOrDefault("type", "system");
+ System.out.println("消息类型" + msgType);
+ if(msgType.equals("scoreUpdate")){
+ int Score = (int) clientMessage.get("Score");
+ int giveUserScore = (int) clientMessage.get("giveUserScore") - Score;
+ int givedUserScore = (int) clientMessage.get("givedUserScore") + Score;
+ Map scoreUpdateMsg = new HashMap<>();
+ scoreUpdateMsg.put("type", msgType);
+ scoreUpdateMsg.put("giveUserId",clientMessage.get("giveUserId"));
+ scoreUpdateMsg.put("giveUserScore",giveUserScore);
+ scoreUpdateMsg.put("givedUserId",clientMessage.get("givedUserId"));
+ scoreUpdateMsg.put("givedUserScore",givedUserScore);
+ broadcastJsonToRoom(roomId,scoreUpdateMsg);
+ }else if(msgType.equals("scoreUpdates")){
- Map broadcastMsg = new HashMap<>();
- broadcastMsg.put("type", msgType);
- broadcastMsg.put("userId", userId);
- broadcastMsg.put("roomId", roomId);
- broadcastMsg.put("content", clientMessage.get("content"));
- broadcastMsg.put("timestamp", System.currentTimeMillis());
+ }
- broadcastJsonToRoom(roomId, broadcastMsg);
+
+ else if(msgType.equals("userJoined")){
+
+ Map broadcastMsg = new HashMap<>();
+ broadcastMsg.put("type", msgType);
+ broadcastMsg.put("userId", userId);
+ broadcastMsg.put("roomId", roomId);
+ broadcastMsg.put("nickName", clientMessage.get("nickName"));
+ broadcastMsg.put("avatars", clientMessage.get("avatars"));
+ broadcastMsg.put("timestamp", System.currentTimeMillis());
+
+ broadcastJsonToRoom(roomId, broadcastMsg);
+ }
} catch (Exception e) {
System.out.println("解析客户端消息失败,按文本处理: " + e.getMessage());
Map broadcastMsg = new HashMap<>();
- broadcastMsg.put("type", "chat");
+ broadcastMsg.put("type", "error");
broadcastMsg.put("userId", userId);
broadcastMsg.put("roomId", roomId);
broadcastMsg.put("content", payload);
@@ -151,12 +187,14 @@ public class GameRoomWebSocketHandler extends TextWebSocketHandler {
// 辅助方法:广播 JSON 消息到指定房间
private void broadcastJsonToRoom(String roomId, Map message) throws Exception {
- String jsonMessage = objectMapper.writeValueAsString(message);
+
for (Map.Entry entry : sessions.entrySet()) {
if (entry.getKey().startsWith(roomId + "_")) {
WebSocketSession session = entry.getValue();
if (session.isOpen()) {
- session.sendMessage(new TextMessage(jsonMessage));
+ System.out.println(message);
+ sendJsonMessage(session, message);
+ System.out.println(111);
}
}
}
diff --git a/scoring/scoring/App.vue b/scoring/scoring/App.vue
index 8c2b732..2457395 100644
--- a/scoring/scoring/App.vue
+++ b/scoring/scoring/App.vue
@@ -13,5 +13,5 @@
+
+
\ No newline at end of file
diff --git a/scoring/scoring/main.js b/scoring/scoring/main.js
index 96f67d2..fa1ba5f 100644
--- a/scoring/scoring/main.js
+++ b/scoring/scoring/main.js
@@ -1,26 +1,33 @@
import App from './App'
-import { getToken,checkLoginStatus } from '@/api/login.js';
+import {
+ getToken,
+ checkLoginStatus
+} from '@/api/login.js';
import StaticValue from '@/utils/StaticValue.js'
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
+import uView from 'uview-ui'; // 如果 uView 支持模块化导出
+Vue.use(uView)
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
- ...App
+ ...App
})
app.$mount()
// #endif
// #ifdef VUE3
-import { createSSRApp } from 'vue'
+import {
+ createSSRApp
+} from 'vue'
export function createApp() {
- const app = createSSRApp(App)
- app.config.globalProperties.$StaticValue = StaticValue; // 挂载全局变量
- return {
- app
- }
+ const app = createSSRApp(App)
+ app.config.globalProperties.$StaticValue = StaticValue; // 挂载全局变量
+ return {
+ app
+ }
}
// #endif
checkLoginStatus();
diff --git a/scoring/scoring/package.json b/scoring/scoring/package.json
index dc8ed94..e48ddbe 100644
--- a/scoring/scoring/package.json
+++ b/scoring/scoring/package.json
@@ -1,6 +1,11 @@
{
"dependencies": {
"less": "^4.4.2",
- "less-loader": "^12.3.0"
+ "less-loader": "^12.3.0",
+ "uview-ui": "^2.0.38",
+ "vue": "^3.5.24"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^6.0.1"
}
}
diff --git a/scoring/scoring/pages.json b/scoring/scoring/pages.json
index f4a62f0..9dba11d 100644
--- a/scoring/scoring/pages.json
+++ b/scoring/scoring/pages.json
@@ -59,4 +59,6 @@
]
},
"uniIdRouter": {}
+
+
}
\ No newline at end of file
diff --git a/scoring/scoring/pages/index/index.vue b/scoring/scoring/pages/index/index.vue
index 87ec1bf..a81e611 100644
--- a/scoring/scoring/pages/index/index.vue
+++ b/scoring/scoring/pages/index/index.vue
@@ -8,6 +8,7 @@
+
diff --git a/scoring/scoring/pages/more/more.vue b/scoring/scoring/pages/more/more.vue
index f934607..ad74e90 100644
--- a/scoring/scoring/pages/more/more.vue
+++ b/scoring/scoring/pages/more/more.vue
@@ -7,23 +7,12 @@
@@ -79,7 +68,7 @@
@@ -103,12 +92,19 @@