在使用时,发现很少有相关博客,特此进行记录,不足之处望指出
1、 前端下载依赖包
pnpm install @noear/socket.d@2.5.16
新建socket-d.ts
文件
使用了全局单例,防止多次创建链接,官方示例是直接挂载在Window,传递的token是用来给后端进行权限校验的,不需要的话可以删除,以下为全部代码
import { SocketD } from '@noear/socket.d'
import { localStg } from '@/utils/storage'
import { isEmpty } from '@/utils/is-empty'
import { Session } from '@noear/socket.d/transport/core/Session'
import { ClientSession } from '@noear/socket.d/transport/client/ClientSession'
type MessageCallback = (val: { type: SocketDType.ChannelMap; msg: string }) => void
class SocketClient {
private readonly serverUrl: string = `sd:ws://${import.meta.env.VITE_FILE_SOCKETD_HOST}/?u=a&p=2`
private readonly tokenName: string = import.meta.env.VITE_HERDER_TOKEN_NAME
private readonly token: string = ''
private static instance: SocketClient
private client: ClientSession | null = null
private listeners = new Map<string, MessageCallback[]>()
private constructor() {
if (isEmpty(localStg.get('token'))) {
throw new Error('未登录!')
}
this.token = localStg.get('token') as string
}
/**
* 获取单例实例
*/
public static getInstance(): SocketClient {
if (!SocketClient.instance) {
SocketClient.instance = new SocketClient()
}
return SocketClient.instance
}
/**
* 初始化 Socket
*/
public async init(): Promise<void> {
if (isEmpty(localStg.get('token'))) {
throw new Error('未登录!')
}
if (this.client) {
console.warn('Socket 客户端已初始化,无需重复初始化!')
return
}
try {
this.client = await SocketD.createClient(this.serverUrl)
.config((c) => c.metaPut(this.tokenName, this.token))
.listen(
SocketD.newEventListener()
.doOnOpen((s: Session) => {
console.log('连接成功!')
})
.doOnMessage((s, m) => {
this.emit('message', m.dataAsString())
})
.doOn('/alarm', (s, m) => {
this.emit('alarm', m.dataAsString())
})
.doOn('/appstore', (s, m) => {
this.emit('appstore', m.dataAsString())
})
.doOn('/notice', (s, m) => {
this.emit('notice', m.dataAsString())
})
)
.open()
console.log('Socket 客户端初始化完成!')
} catch (e) {
this.emit('error', { type: '连接失败', msg: '连接失败,请稍后再试!' })
throw e
}
}
/**
* 关闭链接
* @param callback
*/
public close(callback?: () => void): void {
this.client?.close()
this.client = null
this.listeners.clear()
console.log('Socket 客户端已关闭!')
callback?.()
}
/**
* 添加事件监听器
* @param event 事件名称
* @param callback 回调函数
*/
public on(event: SocketDType.ChannelEvent, callback: MessageCallback): void {
if (!this.listeners.has(event)) {
this.listeners.set(event, [])
}
this.listeners.get(event)?.push(callback)
}
/**
* 移除事件监听器
* @param event 事件名称
* @param callback 回调函数
*/
public off(event: SocketDType.ChannelEvent, callback: MessageCallback): void {
const callbacks = this.listeners.get(event)
if (callbacks) {
const index = callbacks.indexOf(callback)
if (index !== -1) {
callbacks.splice(index, 1)
}
if (callbacks.length === 0) {
this.listeners.delete(event)
}
}
}
/**
* 发送
* @param channel
* @param data
* @private
*/
public send(channel: SocketDType.ChannelUrl, data: any) {
this.handleSendError(channel, data)
this.client?.send(channel, SocketD.newEntity(data))
}
/**
* 发送并请求
* @param channel
* @param data
* @param callback
* @private
*/
public sendAndRequest(
channel: SocketDType.ChannelUrl,
data: any,
callback?: (type: string, value: any) => void
) {
this.handleSendError(channel, data)
this.client?.sendAndRequest(channel, SocketD.newEntity(data)).thenReply((reply) => {
callback?.('收到回复', reply)
})
}
/**
* 发送并订阅
* @param channel
* @param data
* @param callback
* @private
*/
public sendAndSubscribe(
channel: SocketDType.ChannelUrl,
data: any,
callback?: (type: string, value: any) => void
) {
this.handleSendError(channel, data)
this.client
?.sendAndRequest(
channel,
SocketD.newEntity(data).metaPut(SocketD.EntityMetas.META_RANGE_SIZE, '3')
)
.thenReply((reply) => {
if (reply.isEnd()) {
callback?.('订阅结束', reply)
} else {
callback?.('订阅回复', reply)
}
})
}
/**
* 发送前校验
* @param channel
* @param data
* @private
*/
private handleSendError(channel: SocketDType.ChannelUrl, data: any) {
if (!this.client) throw new Error('客户端尚未正确初始化!')
if (isEmpty(channel)) throw new Error('信息通道不能为空!')
if (isEmpty(data)) throw new Error('信息内容不能为空!')
}
/**
* 触发事件
* @param event 事件名称
* @param data 事件数据
* @private
*/
private emit(event: SocketDType.ChannelEvent, data: any): void {
const callbacks = this.listeners.get(event)
if (callbacks) {
callbacks.forEach((callback) => callback(data))
}
}
}
export const socketClient = SocketClient.getInstance()
2、页面使用
const handleMessage = (val: any) => {
console.log('收到消息:', val)
}
const handleAlarm = (val: any) => {
console.log('收到 alarm 消息:', val)
}
onMounted(async () => {
await socketClient.init()
// await socketClient.init((val{ hint: string; msg: string })=>{}) // 可以传递一个回调函数,用于接受所有的信息
socketClient.on('message', handleMessage)
socketClient.on('alarm', handleAlarm)
socketClient.send('/alarm', '开始')
setTimeout(() => {
socketClient.off('alarm', handleAlarm)
setTimeout(() => {
socketClient.close()
// socketClient.close(() => {}) // 可传递回调函数,用于处理关闭后的炒作
}, 2000)
}, 5000)
})
3、后台代码(官方示例)
public class WebSocketToSocketd extends ToSocketdWebSocketListener {
public WebSocketToSocketd() {
super(new ConfigDefault(false));
setListener(buildListener());
}
/**
* 构建监听器
*/
private Listener buildListener() {
return new EventListener()
.doOnOpen(s -> {
System.out.println("onOpen: " + s.sessionId());
}).doOnMessage((s, m) -> {
System.out.println("onMessage: " + m);
}).doOn("/demo", (s, m) -> {
if (m.isRequest()) {
s.reply(m, new StringEntity("me to!"));
}
if (m.isSubscribe()) {
int size = m.metaAsInt(EntityMetas.META_RANGE_SIZE);
for (int i = 1; i <= size; i++) {
s.reply(m, new StringEntity("me to-" + i));
}
s.replyEnd(m, new StringEntity("welcome to my home!"));
}
}).doOn("/upload", (s, m) -> {
if (m.isRequest()) {
String fileName = m.meta(EntityMetas.META_DATA_DISPOSITION_FILENAME);
if (StrUtils.isEmpty(fileName)) {
s.reply(m, new StringEntity("no file! size: " + m.dataSize()));
} else {
s.reply(m, new StringEntity("file received: " + fileName + ", size: " + m.dataSize()));
}
}
}).doOn("/download", (s, m) -> {
if (m.isRequest()) {
FileEntity fileEntity = new FileEntity(new File("/Users/noear/Movies/snack3-rce-poc.mov"));
s.reply(m, fileEntity);
}
}).doOn("/push", (s, m) -> {
if (s.attrHas("push")) {
return;
}
s.attrPut("push", "1");
while (true) {
if (s.attrHas("push") == false) {
break;
}
s.send("/push", new StringEntity("push test"));
RunUtils.runAndTry(() -> Thread.sleep(200));
}
}).doOn("/unpush", (s, m) -> {
s.attrMap().remove("push");
})
.doOnClose(s -> {
System.out.println("onClose: " + s.sessionId());
}).doOnError((s, err) -> {
System.out.println("onError: " + s.sessionId());
err.printStackTrace();
});
}
}