16 changed files with 302 additions and 13 deletions
@ -0,0 +1,2 @@ |
|||||||
|
NODE_ENV="preview" |
||||||
|
VUE_APP_BASE_URL="http://localhost" |
@ -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 @@ |
|||||||
|
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 @@ |
|||||||
|
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 @@ |
|||||||
|
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 @@ |
|||||||
|
<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