2
0
Просмотр исходного кода

✨ feat(socket): 添加Socket.IO客户端支持和测试界面

- 添加socket.io-client依赖包,版本4.8.1
- 在HomePage组件中实现Socket.IO连接测试界面
- 实现Socket.IO连接、断开、消息发送和接收功能
- 添加连接状态显示和消息记录区域

🔧 chore(server): 配置CORS中间件和优化服务器关闭流程

- 添加Hono CORS中间件,支持跨域请求
- 移除服务器关闭时Socket.IO关闭代码,简化关闭流程
yourname 10 месяцев назад
Родитель
Сommit
4188d6df35
4 измененных файлов с 207 добавлено и 7 удалено
  1. 1 0
      package.json
  2. 39 0
      pnpm-lock.yaml
  3. 8 4
      server.js
  4. 159 3
      src/client/home/pages/HomePage.tsx

+ 1 - 0
package.json

@@ -37,6 +37,7 @@
     "reflect-metadata": "^0.2.2",
     "reflect-metadata": "^0.2.2",
     "sirv": "^3.0.1",
     "sirv": "^3.0.1",
     "socket.io": "^4.8.1",
     "socket.io": "^4.8.1",
+    "socket.io-client": "^4.8.1",
     "typeorm": "^0.3.25"
     "typeorm": "^0.3.25"
   },
   },
   "devDependencies": {
   "devDependencies": {

+ 39 - 0
pnpm-lock.yaml

@@ -83,6 +83,9 @@ importers:
       socket.io:
       socket.io:
         specifier: ^4.8.1
         specifier: ^4.8.1
         version: 4.8.1
         version: 4.8.1
+      socket.io-client:
+        specifier: ^4.8.1
+        version: 4.8.1
       typeorm:
       typeorm:
         specifier: ^0.3.25
         specifier: ^0.3.25
         version: 0.3.25(mysql2@3.14.2)(reflect-metadata@0.2.2)
         version: 0.3.25(mysql2@3.14.2)(reflect-metadata@0.2.2)
@@ -1076,6 +1079,9 @@ packages:
     resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
     resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
     engines: {node: '>= 0.8'}
     engines: {node: '>= 0.8'}
 
 
+  engine.io-client@6.6.3:
+    resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==}
+
   engine.io-parser@5.2.3:
   engine.io-parser@5.2.3:
     resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
     resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
     engines: {node: '>=10.0.0'}
     engines: {node: '>=10.0.0'}
@@ -1918,6 +1924,10 @@ packages:
   socket.io-adapter@2.5.5:
   socket.io-adapter@2.5.5:
     resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==}
     resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==}
 
 
+  socket.io-client@4.8.1:
+    resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==}
+    engines: {node: '>=10.0.0'}
+
   socket.io-parser@4.2.4:
   socket.io-parser@4.2.4:
     resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
     resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
     engines: {node: '>=10.0.0'}
     engines: {node: '>=10.0.0'}
@@ -2169,6 +2179,10 @@ packages:
       utf-8-validate:
       utf-8-validate:
         optional: true
         optional: true
 
 
+  xmlhttprequest-ssl@2.1.2:
+    resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
+    engines: {node: '>=0.4.0'}
+
   y18n@5.0.8:
   y18n@5.0.8:
     resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
     resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
@@ -3031,6 +3045,18 @@ snapshots:
 
 
   encodeurl@2.0.0: {}
   encodeurl@2.0.0: {}
 
 
+  engine.io-client@6.6.3:
+    dependencies:
+      '@socket.io/component-emitter': 3.1.2
+      debug: 4.3.7
+      engine.io-parser: 5.2.3
+      ws: 8.17.1
+      xmlhttprequest-ssl: 2.1.2
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+
   engine.io-parser@5.2.3: {}
   engine.io-parser@5.2.3: {}
 
 
   engine.io@6.6.4:
   engine.io@6.6.4:
@@ -4007,6 +4033,17 @@ snapshots:
       - supports-color
       - supports-color
       - utf-8-validate
       - utf-8-validate
 
 
+  socket.io-client@4.8.1:
+    dependencies:
+      '@socket.io/component-emitter': 3.1.2
+      debug: 4.3.7
+      engine.io-client: 6.6.3
+      socket.io-parser: 4.2.4
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+
   socket.io-parser@4.2.4:
   socket.io-parser@4.2.4:
     dependencies:
     dependencies:
       '@socket.io/component-emitter': 3.1.2
       '@socket.io/component-emitter': 3.1.2
@@ -4194,6 +4231,8 @@ snapshots:
 
 
   ws@8.17.1: {}
   ws@8.17.1: {}
 
 
+  xmlhttprequest-ssl@2.1.2: {}
+
   y18n@5.0.8: {}
   y18n@5.0.8: {}
 
 
   yallist@5.0.0: {}
   yallist@5.0.0: {}

+ 8 - 4
server.js

@@ -4,6 +4,7 @@ import { Transform } from 'node:stream';
 import { Readable } from 'node:stream';
 import { Readable } from 'node:stream';
 import { pipeline } from 'node:stream/promises';
 import { pipeline } from 'node:stream/promises';
 import { Hono } from 'hono';
 import { Hono } from 'hono';
+import { cors } from 'hono/cors'
 import { logger } from 'hono/logger';
 import { logger } from 'hono/logger';
 import { createServer as createNodeServer } from 'node:http';
 import { createServer as createNodeServer } from 'node:http';
 import process from 'node:process';
 import process from 'node:process';
@@ -17,6 +18,13 @@ const app = new Hono();// API路由
 
 
 // 全局使用 Hono 日志中间件(记录所有请求)
 // 全局使用 Hono 日志中间件(记录所有请求)
 app.use('*', logger());
 app.use('*', logger());
+app.use('*', cors(
+  // {
+  //   origin: ['http://localhost:3000'],
+  //   allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
+  //   credentials: true
+  // }
+))
 
 
 // 常量定义
 // 常量定义
 const isProduction = process.env.NODE_ENV === 'production';
 const isProduction = process.env.NODE_ENV === 'production';
@@ -373,10 +381,6 @@ parentServer.listen(port, () => {
 const shutdownServer = async () => {
 const shutdownServer = async () => {
   console.log('正在关闭服务器...');
   console.log('正在关闭服务器...');
   
   
-  // 新增:关闭 Socket.IO 服务器
-  console.log('正在关闭 Socket.IO 服务器...');
-  await io.close();
-  console.log('Socket.IO 服务器已关闭');
   
   
   // 关闭 Vite 开发服务器(包括 HMR 服务)
   // 关闭 Vite 开发服务器(包括 HMR 服务)
   if (!isProduction && vite) {
   if (!isProduction && vite) {

+ 159 - 3
src/client/home/pages/HomePage.tsx

@@ -1,11 +1,95 @@
-import React from 'react';
+import React, { useState, useEffect, useRef } from 'react';
 import { useAuth } from '@/client/home/hooks/AuthProvider';
 import { useAuth } from '@/client/home/hooks/AuthProvider';
 import { useNavigate } from 'react-router-dom';
 import { useNavigate } from 'react-router-dom';
+import io, { Socket } from 'socket.io-client';
 
 
 const HomePage: React.FC = () => {
 const HomePage: React.FC = () => {
   const { user } = useAuth();
   const { user } = useAuth();
   const navigate = useNavigate();
   const navigate = useNavigate();
-
+  
+  // Socket.IO相关状态和引用
+  const [socket, setSocket] = useState<Socket | null>(null);
+  const [isConnected, setIsConnected] = useState(false);
+  const [connectionStatus, setConnectionStatus] = useState('未连接');
+  const [messages, setMessages] = useState<string[]>([]);
+  const [messageInput, setMessageInput] = useState('');
+  const socketRef = useRef<Socket | null>(null);
+  
+  // 连接到Socket.IO服务器
+  const connectToSocket = () => {
+    // 替换为你的Socket.IO服务器地址
+    // const newSocket = io('/socket.io');
+    const newSocket = io('/', {
+      path: '/socket.io',
+      transports: ['websocket'],
+      withCredentials: true,
+      query: { 
+        socket_token:'3424242423'
+      },
+      auth:{
+        token: 'wfwfw2342424'
+      },
+      reconnection: true,
+      reconnectionAttempts: 5,
+      reconnectionDelay: 1000,
+    });
+    socketRef.current = newSocket;
+    setSocket(newSocket);
+    
+    newSocket.on('connect', () => {
+      setIsConnected(true);
+      setConnectionStatus('已连接 (ID: ' + newSocket.id + ')');
+      addMessage('成功连接到服务器');
+    });
+    
+    newSocket.on('disconnect', () => {
+      setIsConnected(false);
+      setConnectionStatus('已断开连接');
+      addMessage('与服务器断开连接');
+    });
+    
+    newSocket.on('message', (data: string) => {
+      addMessage('收到服务器消息: ' + data);
+    });
+    
+    newSocket.on('error', (error: any) => {
+      addMessage('错误: ' + error.message);
+    });
+  };
+  
+  // 断开Socket.IO连接
+  const disconnectFromSocket = () => {
+    if (socket) {
+      socket.disconnect();
+      setSocket(null);
+      socketRef.current = null;
+    }
+  };
+  
+  // 添加消息到消息列表
+  const addMessage = (message: string) => {
+    const timestamp = new Date().toLocaleTimeString();
+    setMessages(prev => [...prev, `[${timestamp}] ${message}`].slice(-10)); // 只保留最近10条消息
+  };
+  
+  // 发送消息到服务器
+  const sendMessage = () => {
+    if (socket && messageInput.trim()) {
+      socket.emit('message', messageInput.trim());
+      addMessage('发送: ' + messageInput.trim());
+      setMessageInput('');
+    }
+  };
+  
+  // 清理函数,组件卸载时断开连接
+  useEffect(() => {
+    return () => {
+      if (socket) {
+        socket.disconnect();
+      }
+    };
+  }, [socket]);
+  
   return (
   return (
     <div className="min-h-screen bg-gray-50 flex flex-col">
     <div className="min-h-screen bg-gray-50 flex flex-col">
       {/* 顶部导航 */}
       {/* 顶部导航 */}
@@ -82,6 +166,78 @@ const HomePage: React.FC = () => {
               <p className="text-gray-600 text-sm">适配各种设备屏幕,提供良好的用户体验</p>
               <p className="text-gray-600 text-sm">适配各种设备屏幕,提供良好的用户体验</p>
             </div>
             </div>
           </div>
           </div>
+          
+          {/* Socket.IO连接测试区域 */}
+          <div className="mt-12 p-6 bg-gray-50 rounded-lg border border-gray-200">
+            <h3 className="text-xl font-semibold text-gray-800 mb-4 flex items-center">
+              <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-indigo-600 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 9l3 3-3 3m5 0h3M5 21h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2z" />
+              </svg>
+              Socket.IO 连接测试
+            </h3>
+            
+            <div className="flex flex-col md:flex-row gap-4 mb-6">
+              <div className="flex-1">
+                <p className="text-sm text-gray-600 mb-2">连接状态:</p>
+                <div className={`p-3 rounded border ${isConnected ? 'border-green-200 bg-green-50 text-green-700' : 'border-red-200 bg-red-50 text-red-700'}`}>
+                  {connectionStatus}
+                </div>
+              </div>
+              
+              <div className="flex gap-3">
+                <button 
+                  onClick={connectToSocket}
+                  disabled={isConnected}
+                  className="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 disabled:bg-indigo-300"
+                >
+                  连接
+                </button>
+                <button 
+                  onClick={disconnectFromSocket}
+                  disabled={!isConnected}
+                  className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 disabled:bg-gray-300"
+                >
+                  断开
+                </button>
+              </div>
+            </div>
+            
+            {/* 消息发送区域 */}
+            <div className="mb-6">
+              <p className="text-sm text-gray-600 mb-2">发送消息:</p>
+              <div className="flex gap-2">
+                <input
+                  type="text"
+                  value={messageInput}
+                  onChange={(e) => setMessageInput(e.target.value)}
+                  onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
+                  placeholder="输入消息并发送..."
+                  className="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-indigo-500"
+                />
+                <button 
+                  onClick={sendMessage}
+                  disabled={!isConnected || !messageInput.trim()}
+                  className="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 disabled:bg-indigo-300"
+                >
+                  发送
+                </button>
+              </div>
+            </div>
+            
+            {/* 消息记录区域 */}
+            <div>
+              <p className="text-sm text-gray-600 mb-2">消息记录:</p>
+              <div className="h-64 bg-white border border-gray-200 rounded p-3 overflow-y-auto text-sm">
+                {messages.length === 0 ? (
+                  <p className="text-gray-400 italic">暂无消息</p>
+                ) : (
+                  messages.map((msg, index) => (
+                    <p key={index} className="mb-1">{msg}</p>
+                  ))
+                )}
+              </div>
+            </div>
+          </div>
         </div>
         </div>
       </main>
       </main>
       
       
@@ -100,4 +256,4 @@ const HomePage: React.FC = () => {
   );
   );
 };
 };
 
 
-export default HomePage;
+export default HomePage;