16 changed files with 302 additions and 13 deletions
@ -0,0 +1,2 @@
@@ -0,0 +1,2 @@
|
||||
NODE_ENV="preview" |
||||
VUE_APP_BASE_URL="http://localhost" |
@ -0,0 +1,2 @@
@@ -0,0 +1,2 @@
|
||||
NODE_ENV="production" |
||||
VUE_APP_BASE_URL="" |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
package vip.xumy.idle.server.ctrl; |
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestBody; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
|
||||
import vip.xumy.core.pojo.com.BaseResponse; |
||||
import vip.xumy.idle.server.pojo.SocketMsg; |
||||
import vip.xumy.idle.server.service.WebSocketService; |
||||
|
||||
/** |
||||
* Ownership belongs to the company |
||||
* |
||||
* @author:mengyxu |
||||
* @date:2025年4月21日 |
||||
*/ |
||||
|
||||
@RestController |
||||
@RequestMapping("public") |
||||
public class PublicController { |
||||
|
||||
@GetMapping("socket/num") |
||||
public BaseResponse getSocketNum() { |
||||
return new BaseResponse(WebSocketService.CLIENT_MAP.keySet().size()); |
||||
} |
||||
|
||||
@PostMapping("update/notify") |
||||
public BaseResponse updateNotify(@RequestBody String notify) { |
||||
WebSocketService.sendMessage(new SocketMsg<String>("update", notify)); |
||||
return new BaseResponse(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
package vip.xumy.idle.server.pojo; |
||||
|
||||
import lombok.Getter; |
||||
|
||||
/** Ownership belongs to the company |
||||
* |
||||
* @author:mengyxu |
||||
* @date:2025年4月9日 |
||||
*/ |
||||
|
||||
@Getter |
||||
public class SocketMsg<T> { |
||||
|
||||
private String type; |
||||
private T data; |
||||
|
||||
public SocketMsg(String type){ |
||||
this.type = type; |
||||
} |
||||
public SocketMsg(String type, T data){ |
||||
this.type = type; |
||||
this.data = data; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,96 @@
@@ -0,0 +1,96 @@
|
||||
package vip.xumy.idle.server.service; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.Collection; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import javax.websocket.OnClose; |
||||
import javax.websocket.OnError; |
||||
import javax.websocket.OnMessage; |
||||
import javax.websocket.OnOpen; |
||||
import javax.websocket.Session; |
||||
import javax.websocket.server.PathParam; |
||||
import javax.websocket.server.ServerEndpoint; |
||||
|
||||
import org.springframework.scheduling.annotation.EnableScheduling; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import com.alibaba.fastjson.JSON; |
||||
|
||||
import lombok.Getter; |
||||
import lombok.extern.log4j.Log4j2; |
||||
import vip.xumy.core.exception.CoreException; |
||||
import vip.xumy.idle.server.pojo.SocketMsg; |
||||
|
||||
/** |
||||
* Ownershid belongs to the company |
||||
* |
||||
* @author:mengyxu |
||||
* @date:2024年1月26日 |
||||
*/ |
||||
|
||||
@Log4j2 |
||||
@Component |
||||
@EnableScheduling |
||||
@ServerEndpoint(value = "/websocket/{id}") |
||||
public class WebSocketService { |
||||
public static final Map<String, WebSocketService> CLIENT_MAP = new HashMap<>(); |
||||
private Session session; |
||||
@Getter |
||||
private String id; |
||||
|
||||
@OnOpen |
||||
public void onOpen(Session session, @PathParam("id") String id) { |
||||
this.session = session; |
||||
this.id = id; |
||||
CLIENT_MAP.put(id, this); |
||||
log.debug(id + "连接websocket"); |
||||
} |
||||
|
||||
@OnClose |
||||
public void onClose() { |
||||
CLIENT_MAP.remove(id); |
||||
} |
||||
|
||||
@OnMessage |
||||
public void onMessage(String message, Session session) { |
||||
} |
||||
|
||||
@OnError |
||||
public void onError(Session session, Throwable error) { |
||||
log.error(id + "连接错误错误", error); |
||||
} |
||||
|
||||
public static Collection<WebSocketService> clients() { |
||||
return CLIENT_MAP.values(); |
||||
} |
||||
|
||||
public void send(String message) { |
||||
try { |
||||
session.getBasicRemote().sendText(message); |
||||
} catch (IOException e) { |
||||
throw new CoreException("下发消息失败", e); |
||||
} |
||||
} |
||||
|
||||
public static void sendMessage(List<String> ids, SocketMsg<?> message) { |
||||
String json = JSON.toJSONString(message); |
||||
for (String id : ids) { |
||||
WebSocketService client = CLIENT_MAP.get(id); |
||||
if (client == null) { |
||||
continue; |
||||
} |
||||
client.send(json); |
||||
} |
||||
} |
||||
|
||||
public static void sendMessage(SocketMsg<?> message) { |
||||
String json = JSON.toJSONString(message); |
||||
for (WebSocketService client : CLIENT_MAP.values()) { |
||||
client.send(json); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
let websocket: WebSocket; |
||||
const msgHandlersMap = {}; |
||||
let lastUrl; |
||||
export const openWebSocket = (url) => { |
||||
if (websocket && websocket.readyState === WebSocket.OPEN) { |
||||
if (lastUrl == url) { |
||||
return; |
||||
} |
||||
websocket.close(); |
||||
} |
||||
websocket = new WebSocket(url); |
||||
lastUrl = url; |
||||
websocket.onopen = () => { |
||||
console.log('websocket已连接'); |
||||
}; |
||||
websocket.onmessage = (msg) => { |
||||
const event = JSON.parse(msg.data); |
||||
const handlers = msgHandlersMap[event.type]; |
||||
if (!handlers) { |
||||
return; |
||||
} |
||||
Object.values(handlers).forEach((handler: any) => { |
||||
handler(event.data); |
||||
}); |
||||
}; |
||||
websocket.onclose = () => { |
||||
console.log('websocket已断开'); |
||||
openWebSocket(url); |
||||
}; |
||||
}; |
||||
|
||||
export const registerHandler = (type, key, handler) => { |
||||
const handlers = msgHandlersMap[type] || {}; |
||||
handlers[key] = handler; |
||||
msgHandlersMap[type] = handlers; |
||||
console.log(msgHandlersMap); |
||||
}; |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
<template> |
||||
<Dialog :title="t('update.0')" v-model="showUpdate" top="5rem" left="5rem"> |
||||
<div class="nitofy"> |
||||
<div class="confirm">{{ t('update.1') }}</div> |
||||
<div class="msg" v-if="message">{{ message }}</div> |
||||
<div class="btns"> |
||||
<button class="button" @click="refresh">{{ t('update.2') }}</button> |
||||
<button class="button" @click="showUpdate = false">{{ t('update.3') }}</button> |
||||
</div> |
||||
</div> |
||||
</Dialog> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { useStore } from "vuex"; |
||||
import { reactive, onMounted, ref } from "vue"; |
||||
import { useI18n } from "vue3-i18n"; |
||||
import { openWebSocket, registerHandler } from "@/tool/websocket"; |
||||
import Dialog from "@/components/dialog.vue"; |
||||
|
||||
const { t } = useI18n(); |
||||
const { state, commit, dispatch } = useStore(); |
||||
const showUpdate = ref(false); |
||||
const message = ref(''); |
||||
|
||||
const uuid = () => { |
||||
var s: any = []; |
||||
var hexDigits = "0123456789abcdef"; |
||||
for (var i = 0; i < 36; i++) { |
||||
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); |
||||
} |
||||
s[14] = "4"; // 代表UUID版本 |
||||
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // 时钟序列 |
||||
s[8] = s[13] = s[18] = s[23] = "-"; |
||||
var uuid = s.join(""); |
||||
return uuid; |
||||
} |
||||
|
||||
let url = 'ws://' + window.location.host + ':' + window.location.port; |
||||
if (process.env.VUE_APP_BASE_URL) { |
||||
url = 'ws://' + process.env.VUE_APP_BASE_URL.substring(7); |
||||
} |
||||
url += '/websocket/' + uuid(); |
||||
openWebSocket(url); |
||||
|
||||
const refresh = () => { |
||||
dispatch('saveGame'); |
||||
window.location.reload(); |
||||
} |
||||
|
||||
const onUpdate = (msg) => { |
||||
message.value = msg; |
||||
showUpdate.value = true; |
||||
} |
||||
registerHandler('update', 'version', onUpdate) |
||||
|
||||
onMounted(() => { }); |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.nitofy { |
||||
width: 25rem; |
||||
padding: 0.5rem; |
||||
} |
||||
|
||||
.confirm { |
||||
width: 100%; |
||||
font-size: 1.2rem; |
||||
text-align: start; |
||||
} |
||||
|
||||
.msg { |
||||
color: #d1cfcf; |
||||
width: 100%; |
||||
white-space: normal; |
||||
word-break: break-all; |
||||
overflow: hidden; |
||||
padding: 1rem; |
||||
} |
||||
</style> |
Loading…
Reference in new issue