增加admin, mobile 消息io连接
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
迁移管理页面,在正式环境中,需要验证env中配置的密码参数才能打开
|
||||
|
||||
2025.05.15 0.1.6
|
||||
增加admin, mobile 消息io连接
|
||||
增加socketio 路由 支持
|
||||
增加socketio server 支持
|
||||
修正文件分类后端api路由查询表名为file_categories
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { Button, Table, Space, Modal, Form, Input, Select, message } from 'antd';
|
||||
import { io, Socket } from 'socket.io-client';
|
||||
import type { TableProps } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
@@ -8,8 +9,12 @@ import 'dayjs/locale/zh-cn';
|
||||
import { MessageAPI , UserAPI } from './api/index.ts';
|
||||
import type { UserMessage } from '../share/types.ts';
|
||||
import { MessageStatusNameMap , MessageStatus} from '../share/types.ts';
|
||||
import { useAuth } from "./hooks_sys.tsx";
|
||||
|
||||
export const MessagesPage = () => {
|
||||
export const MessagesPage = () => {
|
||||
const { token } = useAuth();
|
||||
const [socket, setSocket] = useState<Socket | null>(null);
|
||||
const [isSocketConnected, setIsSocketConnected] = useState(false);
|
||||
const queryClient = useQueryClient();
|
||||
const [form] = Form.useForm();
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
@@ -59,8 +64,58 @@ export const MessagesPage = () => {
|
||||
});
|
||||
|
||||
// 发送消息
|
||||
// 初始化Socket.IO连接
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
|
||||
const newSocket = io('/', {
|
||||
path: '/socket.io',
|
||||
transports: ['websocket'],
|
||||
autoConnect: false,
|
||||
query: {
|
||||
socket_token: token
|
||||
}
|
||||
});
|
||||
|
||||
newSocket.on('connect', () => {
|
||||
setIsSocketConnected(true);
|
||||
message.success('实时消息连接已建立');
|
||||
});
|
||||
|
||||
newSocket.on('disconnect', () => {
|
||||
setIsSocketConnected(false);
|
||||
message.warning('实时消息连接已断开');
|
||||
});
|
||||
|
||||
newSocket.on('error', (err) => {
|
||||
message.error(`实时消息错误: ${err}`);
|
||||
});
|
||||
|
||||
newSocket.connect();
|
||||
setSocket(newSocket);
|
||||
|
||||
return () => {
|
||||
newSocket.disconnect();
|
||||
};
|
||||
}, [token]);
|
||||
|
||||
const sendMessageMutation = useMutation({
|
||||
mutationFn: (data: any) => MessageAPI.sendMessage(data),
|
||||
mutationFn: async (data: any) => {
|
||||
// 优先使用Socket.IO发送
|
||||
if (isSocketConnected && socket) {
|
||||
return new Promise((resolve, reject) => {
|
||||
socket.emit('message:send', data, (response: any) => {
|
||||
if (response.error) {
|
||||
reject(new Error(response.error));
|
||||
} else {
|
||||
resolve(response.data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// 回退到HTTP API
|
||||
return MessageAPI.sendMessage(data);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['messages'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['unreadCount'] });
|
||||
@@ -266,10 +321,11 @@ export const MessagesPage = () => {
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={sendMessageMutation.status === 'pending'}
|
||||
icon={isSocketConnected ? <span style={{color:'green'}}>●</span> : <span style={{color:'red'}}>●</span>}
|
||||
>
|
||||
发送
|
||||
</Button>
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import { BellIcon } from '@heroicons/react/24/outline';
|
||||
import { MessageStatus } from '../share/types.ts';
|
||||
|
||||
import { io, Socket } from 'socket.io-client';
|
||||
|
||||
// 添加通知页面组件
|
||||
import { MessageAPI } from './api/index.ts';
|
||||
import { useAuth } from "./hooks.tsx";
|
||||
|
||||
export const NotificationsPage = () => {
|
||||
const { token , user} = useAuth();
|
||||
const queryClient = useQueryClient();
|
||||
const [socket, setSocket] = React.useState<Socket | null>(null);
|
||||
const [isSubscribed, setIsSubscribed] = React.useState(false);
|
||||
|
||||
// 获取消息列表
|
||||
const { data: messages, isLoading } = useQuery({
|
||||
@@ -18,6 +22,60 @@ export const NotificationsPage = () => {
|
||||
queryFn: () => MessageAPI.getMessages(),
|
||||
});
|
||||
|
||||
// 初始化Socket.IO连接
|
||||
useEffect(() => {
|
||||
if (!token || !user) return;
|
||||
|
||||
const newSocket = io('/', {
|
||||
path: '/socket.io',
|
||||
transports: ['websocket'],
|
||||
withCredentials: true,
|
||||
query: {
|
||||
socket_token: token
|
||||
}
|
||||
});
|
||||
|
||||
setSocket(newSocket);
|
||||
|
||||
// 订阅消息频道
|
||||
newSocket.on('connect', () => {
|
||||
newSocket.emit('message:subscribe', `user_${user.id}`);
|
||||
setIsSubscribed(true);
|
||||
});
|
||||
|
||||
// 处理实时消息
|
||||
newSocket.on('message:broadcasted', (newMessage) => {
|
||||
queryClient.setQueryData(['messages'], (oldData: any) => {
|
||||
if (!oldData) return oldData;
|
||||
return {
|
||||
...oldData,
|
||||
data: [newMessage, ...oldData.data]
|
||||
};
|
||||
});
|
||||
|
||||
// 更新未读计数
|
||||
queryClient.setQueryData(['unreadCount'], (oldData: any) => {
|
||||
if (!oldData) return oldData;
|
||||
return {
|
||||
...oldData,
|
||||
count: oldData.count + 1
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// 错误处理
|
||||
newSocket.on('error', (error) => {
|
||||
console.error('Socket error:', error);
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (newSocket) {
|
||||
newSocket.emit('message:unsubscribe', `user_${user.id}`);
|
||||
newSocket.disconnect();
|
||||
}
|
||||
};
|
||||
}, [queryClient, token]);
|
||||
|
||||
// 获取未读消息数量
|
||||
const { data: unreadCount } = useQuery({
|
||||
queryKey: ['unreadCount'],
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
"react-hook-form": "https://esm.d8d.fun/react-hook-form@7.55.0?dev&deps=react@18.3.1,react-dom@18.3.1",
|
||||
"@heroicons/react/24/outline": "https://esm.d8d.fun/@heroicons/react@2.1.1/24/outline?dev&deps=react@18.3.1,react-dom@18.3.1",
|
||||
"@heroicons/react/24/solid": "https://esm.d8d.fun/@heroicons/react@2.1.1/24/solid?dev&deps=react@18.3.1,react-dom@18.3.1",
|
||||
"socket.io": "https://deno.land/x/socket_io@0.2.1/mod.ts"
|
||||
"socket.io": "https://deno.land/x/socket_io@0.2.1/mod.ts",
|
||||
"socket.io-client": "https://esm.d8d.fun/socket.io-client@4.8.1"
|
||||
},
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext", "deno.ns"]
|
||||
|
||||
@@ -17,6 +17,83 @@ interface MessageListData {
|
||||
}
|
||||
|
||||
export function setupMessageEvents({ socket , apiClient }:Variables) {
|
||||
// 订阅频道
|
||||
socket.on('message:subscribe', (channel: string) => {
|
||||
try {
|
||||
socket.join(channel);
|
||||
socket.emit('message:subscribed', {
|
||||
message: `成功订阅频道: ${channel}`,
|
||||
channel
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('订阅频道失败:', error);
|
||||
socket.emit('error', '订阅频道失败');
|
||||
}
|
||||
});
|
||||
|
||||
// 取消订阅
|
||||
socket.on('message:unsubscribe', (channel: string) => {
|
||||
try {
|
||||
socket.leave(channel);
|
||||
socket.emit('message:unsubscribed', {
|
||||
message: `已取消订阅频道: ${channel}`,
|
||||
channel
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('取消订阅失败:', error);
|
||||
socket.emit('error', '取消订阅失败');
|
||||
}
|
||||
});
|
||||
|
||||
// 广播消息
|
||||
socket.on('message:broadcast', async (data: {
|
||||
channel?: string;
|
||||
title: string;
|
||||
content: string;
|
||||
type: MessageType;
|
||||
}) => {
|
||||
try {
|
||||
const { channel, title, content, type } = data;
|
||||
const user = socket.user;
|
||||
if (!user) {
|
||||
socket.emit('error', '未授权访问');
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建广播消息
|
||||
const [messageId] = await apiClient.database.table('messages').insert({
|
||||
title,
|
||||
content,
|
||||
type,
|
||||
sender_id: user.id,
|
||||
sender_name: user.username,
|
||||
is_broadcast: 1,
|
||||
created_at: apiClient.database.fn.now(),
|
||||
updated_at: apiClient.database.fn.now()
|
||||
});
|
||||
|
||||
// 广播到所有客户端或特定频道
|
||||
const broadcastTarget = channel ? socket.to(channel) : socket.broadcast;
|
||||
broadcastTarget.emit('message:broadcasted', {
|
||||
id: messageId,
|
||||
title,
|
||||
content,
|
||||
type,
|
||||
sender_id: user.id,
|
||||
sender_name: user.username,
|
||||
created_at: new Date().toISOString()
|
||||
});
|
||||
|
||||
socket.emit('message:broadcasted', {
|
||||
message: '广播消息发送成功',
|
||||
data: { id: messageId }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('广播消息失败:', error);
|
||||
socket.emit('error', '广播消息失败');
|
||||
}
|
||||
});
|
||||
|
||||
// 发送消息
|
||||
socket.on('message:send', async (data: MessageSendData) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user