Przeglądaj źródła

增加admin, mobile 消息io连接

yourname 6 miesięcy temu
rodzic
commit
2f171d4335

+ 1 - 0
HISTORY.md

@@ -3,6 +3,7 @@
 迁移管理页面,在正式环境中,需要验证env中配置的密码参数才能打开
 
 2025.05.15 0.1.6
+增加admin, mobile 消息io连接
 增加socketio 路由 支持
 增加socketio server 支持
 修正文件分类后端api路由查询表名为file_categories

+ 61 - 5
client/admin/pages_messages.tsx

@@ -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>

+ 60 - 2
client/mobile/pages_messages.tsx

@@ -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'],

+ 2 - 1
deno.json

@@ -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"]

+ 77 - 0
server/routes_io_messages.ts

@@ -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 {