mirror of
https://github.com/MatrixSeven/file-transfer-go.git
synced 2026-02-04 03:25:03 +08:00
feat: 调整UI/RTC逻辑
This commit is contained in:
@@ -148,7 +148,7 @@ build_frontend() {
|
|||||||
|
|
||||||
# 构建
|
# 构建
|
||||||
print_verbose "执行 SSG 构建..."
|
print_verbose "执行 SSG 构建..."
|
||||||
if ! NEXT_EXPORT=true yarn build > build.log 2>&1; then
|
if ! NEXT_EXPORT=true NODE_ENV=production NEXT_PUBLIC_BACKEND_URL= NEXT_PUBLIC_WS_URL= NEXT_PUBLIC_API_BASE_URL= yarn build > build.log 2>&1; then
|
||||||
print_error "前端构建失败,查看 $FRONTEND_DIR/build.log"
|
print_error "前端构建失败,查看 $FRONTEND_DIR/build.log"
|
||||||
cat build.log
|
cat build.log
|
||||||
# 恢复 API 目录后再退出
|
# 恢复 API 目录后再退出
|
||||||
|
|||||||
@@ -228,10 +228,10 @@ set_build_env() {
|
|||||||
NEXT_EXPORT=true
|
NEXT_EXPORT=true
|
||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
|
|
||||||
# 后端连接配置(用于静态模式)
|
# 后端连接配置(用于静态模式 - 使用相对路径)
|
||||||
NEXT_PUBLIC_GO_BACKEND_URL=http://localhost:8080
|
NEXT_PUBLIC_GO_BACKEND_URL=
|
||||||
NEXT_PUBLIC_WS_URL=ws://localhost:8080/ws
|
NEXT_PUBLIC_WS_URL=
|
||||||
NEXT_PUBLIC_API_BASE_URL=http://localhost:8080
|
NEXT_PUBLIC_API_BASE_URL=
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
print_verbose "已创建 .env.ssg 文件"
|
print_verbose "已创建 .env.ssg 文件"
|
||||||
@@ -252,9 +252,9 @@ run_static_build() {
|
|||||||
|
|
||||||
# 设置环境变量并执行构建
|
# 设置环境变量并执行构建
|
||||||
if [ "$VERBOSE" = true ]; then
|
if [ "$VERBOSE" = true ]; then
|
||||||
NEXT_EXPORT=true NODE_ENV=production NEXT_PUBLIC_BACKEND_URL=http://localhost:8080 yarn build
|
NEXT_EXPORT=true NODE_ENV=production NEXT_PUBLIC_BACKEND_URL= yarn build
|
||||||
else
|
else
|
||||||
NEXT_EXPORT=true NODE_ENV=production NEXT_PUBLIC_BACKEND_URL=http://localhost:8080 yarn build > build.log 2>&1
|
NEXT_EXPORT=true NODE_ENV=production NEXT_PUBLIC_BACKEND_URL= yarn build > build.log 2>&1
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
print_error "构建失败,查看 build.log 获取详细信息"
|
print_error "构建失败,查看 build.log 获取详细信息"
|
||||||
cat build.log
|
cat build.log
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { Upload, MessageSquare, Monitor, Github, ExternalLink } from 'lucide-react';
|
import { Upload, MessageSquare, Monitor, Github, ExternalLink } from 'lucide-react';
|
||||||
import Hero from '@/components/Hero';
|
import Hero from '@/components/Hero';
|
||||||
@@ -10,6 +11,21 @@ import { WebRTCFileTransfer } from '@/components/WebRTCFileTransfer';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const [activeTab, setActiveTab] = useState('webrtc');
|
||||||
|
const [hasInitialized, setHasInitialized] = useState(false);
|
||||||
|
|
||||||
|
// 根据URL参数设置初始标签(仅首次加载时)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!hasInitialized) {
|
||||||
|
const urlType = searchParams.get('type');
|
||||||
|
if (urlType && ['webrtc', 'text', 'desktop'].includes(urlType)) {
|
||||||
|
setActiveTab(urlType);
|
||||||
|
}
|
||||||
|
setHasInitialized(true);
|
||||||
|
}
|
||||||
|
}, [searchParams, hasInitialized]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50">
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50">
|
||||||
<div className="container mx-auto px-4 py-4 sm:py-6 md:py-8">
|
<div className="container mx-auto px-4 py-4 sm:py-6 md:py-8">
|
||||||
@@ -20,7 +36,7 @@ export default function HomePage() {
|
|||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<Tabs defaultValue="webrtc" className="w-full">
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||||
{/* Tabs Navigation - 横向布局 */}
|
{/* Tabs Navigation - 横向布局 */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<TabsList className="grid w-full grid-cols-3 max-w-lg mx-auto h-auto bg-white/90 backdrop-blur-sm shadow-lg rounded-xl p-2 border border-slate-200">
|
<TabsList className="grid w-full grid-cols-3 max-w-lg mx-auto h-auto bg-white/90 backdrop-blur-sm shadow-lg rounded-xl p-2 border border-slate-200">
|
||||||
|
|||||||
38
chuan-next/src/app/api/webrtc-room-status/route.ts
Normal file
38
chuan-next/src/app/api/webrtc-room-status/route.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
const GO_BACKEND_URL = process.env.GO_BACKEND_URL || 'http://localhost:8080';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const code = searchParams.get('code');
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, message: '取件码不能为空' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('API Route: Getting WebRTC room status, proxying to:', `${GO_BACKEND_URL}/api/webrtc-room-status?code=${code}`);
|
||||||
|
|
||||||
|
const response = await fetch(`${GO_BACKEND_URL}/api/webrtc-room-status?code=${code}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
console.log('Backend response:', response.status, data);
|
||||||
|
|
||||||
|
return NextResponse.json(data, { status: response.status });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API Route Error:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, message: '获取房间状态失败', details: error instanceof Error ? error.message : 'Unknown error' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ export const WebRTCFileTransfer: React.FC = () => {
|
|||||||
// 房间状态
|
// 房间状态
|
||||||
const [pickupCode, setPickupCode] = useState('');
|
const [pickupCode, setPickupCode] = useState('');
|
||||||
const [mode, setMode] = useState<'send' | 'receive'>('send');
|
const [mode, setMode] = useState<'send' | 'receive'>('send');
|
||||||
|
const [hasProcessedInitialUrl, setHasProcessedInitialUrl] = useState(false);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -54,27 +55,43 @@ export const WebRTCFileTransfer: React.FC = () => {
|
|||||||
onFileProgress
|
onFileProgress
|
||||||
} = useWebRTCTransfer();
|
} = useWebRTCTransfer();
|
||||||
|
|
||||||
// 从URL参数中获取初始模式
|
// 从URL参数中获取初始模式(仅在首次加载时处理)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const urlMode = searchParams.get('mode') as 'send' | 'receive';
|
const urlMode = searchParams.get('mode') as 'send' | 'receive';
|
||||||
const type = searchParams.get('type');
|
const type = searchParams.get('type');
|
||||||
const code = searchParams.get('code');
|
const code = searchParams.get('code');
|
||||||
|
|
||||||
if (type === 'webrtc' && urlMode && ['send', 'receive'].includes(urlMode)) {
|
// 只在首次加载且URL中有webrtc类型时处理
|
||||||
|
if (!hasProcessedInitialUrl && type === 'webrtc' && urlMode && ['send', 'receive'].includes(urlMode)) {
|
||||||
|
console.log('=== 处理初始URL参数 ===');
|
||||||
|
console.log('URL模式:', urlMode, '类型:', type, '取件码:', code);
|
||||||
|
|
||||||
setMode(urlMode);
|
setMode(urlMode);
|
||||||
|
setHasProcessedInitialUrl(true);
|
||||||
|
|
||||||
if (code && urlMode === 'receive') {
|
if (code && urlMode === 'receive') {
|
||||||
// 自动加入房间
|
// 自动加入房间,使用房间状态检查
|
||||||
|
console.log('URL中有取件码,自动加入房间');
|
||||||
joinRoom(code);
|
joinRoom(code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [searchParams]);
|
}, [searchParams, hasProcessedInitialUrl]);
|
||||||
|
|
||||||
// 更新URL参数
|
// 更新URL参数
|
||||||
const updateMode = useCallback((newMode: 'send' | 'receive') => {
|
const updateMode = useCallback((newMode: 'send' | 'receive') => {
|
||||||
|
console.log('=== 手动切换模式 ===');
|
||||||
|
console.log('新模式:', newMode);
|
||||||
|
|
||||||
setMode(newMode);
|
setMode(newMode);
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
params.set('type', 'webrtc');
|
params.set('type', 'webrtc');
|
||||||
params.set('mode', newMode);
|
params.set('mode', newMode);
|
||||||
|
|
||||||
|
// 如果切换到发送模式,移除code参数
|
||||||
|
if (newMode === 'send') {
|
||||||
|
params.delete('code');
|
||||||
|
}
|
||||||
|
|
||||||
router.push(`?${params.toString()}`, { scroll: false });
|
router.push(`?${params.toString()}`, { scroll: false });
|
||||||
}, [searchParams, router]);
|
}, [searchParams, router]);
|
||||||
|
|
||||||
@@ -164,14 +181,46 @@ export const WebRTCFileTransfer: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 加入房间 (接收模式)
|
// 加入房间 (接收模式)
|
||||||
const joinRoom = (code: string) => {
|
const joinRoom = async (code: string) => {
|
||||||
console.log('=== 加入房间 ===');
|
console.log('=== 加入房间 ===');
|
||||||
console.log('取件码:', code);
|
console.log('取件码:', code);
|
||||||
|
|
||||||
setPickupCode(code.trim());
|
const trimmedCode = code.trim();
|
||||||
connect(code.trim(), 'receiver');
|
|
||||||
|
|
||||||
showToast(`正在连接到房间: ${code}`, "info");
|
// 检查取件码格式
|
||||||
|
if (!trimmedCode || trimmedCode.length !== 6) {
|
||||||
|
showToast('请输入正确的6位取件码', "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 先检查房间状态
|
||||||
|
console.log('检查房间状态...');
|
||||||
|
showToast('正在检查房间状态...', "info");
|
||||||
|
|
||||||
|
const response = await fetch(`/api/webrtc-room-status?code=${trimmedCode}`);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
showToast(result.message || '房间不存在或已过期', "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查发送方是否在线
|
||||||
|
if (!result.sender_online) {
|
||||||
|
showToast('发送方不在线,请确认取件码是否正确', "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('房间状态检查通过,开始连接...');
|
||||||
|
setPickupCode(trimmedCode);
|
||||||
|
connect(trimmedCode, 'receiver');
|
||||||
|
|
||||||
|
showToast(`正在连接到房间: ${trimmedCode}`, "info");
|
||||||
|
} catch (error) {
|
||||||
|
console.error('检查房间状态失败:', error);
|
||||||
|
showToast('检查房间状态失败,请重试', "error");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置连接状态 (用于连接失败后重新输入)
|
// 重置连接状态 (用于连接失败后重新输入)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useCallback, useRef } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { FileInfo, TransferProgress } from '@/types';
|
import { TransferProgress } from '@/types';
|
||||||
import { useToast } from '@/components/ui/toast-simple';
|
import { useToast } from '@/components/ui/toast-simple';
|
||||||
|
|
||||||
interface FileTransferData {
|
interface FileTransferData {
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ export function useWebRTCTransfer() {
|
|||||||
|
|
||||||
// 设置数据通道消息处理
|
// 设置数据通道消息处理
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dataChannel = connection.getDataChannel();
|
const dataChannel = connection.localDataChannel || connection.remoteDataChannel;
|
||||||
if (dataChannel && dataChannel.readyState === 'open') {
|
if (dataChannel && dataChannel.readyState === 'open') {
|
||||||
console.log('设置数据通道消息处理器');
|
console.log('设置数据通道消息处理器, 通道类型:', connection.localDataChannel ? '本地' : '远程');
|
||||||
|
|
||||||
// 扩展消息处理以包含文件列表
|
// 扩展消息处理以包含文件列表
|
||||||
const originalHandler = fileTransfer.handleMessage;
|
const originalHandler = fileTransfer.handleMessage;
|
||||||
@@ -50,7 +50,7 @@ export function useWebRTCTransfer() {
|
|||||||
originalHandler(event);
|
originalHandler(event);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [connection.isConnected, connection.getDataChannel, fileTransfer.handleMessage]);
|
}, [connection.localDataChannel, connection.remoteDataChannel, fileTransfer.handleMessage]);
|
||||||
|
|
||||||
// 发送文件
|
// 发送文件
|
||||||
const sendFile = useCallback((file: File, fileId?: string) => {
|
const sendFile = useCallback((file: File, fileId?: string) => {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
|||||||
* 用于在静态导出模式下直接与 Go 后端通信
|
* 用于在静态导出模式下直接与 Go 后端通信
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { config, getApiUrl, getDirectBackendUrl } from './config';
|
import { config } from './config';
|
||||||
|
|
||||||
interface ApiResponse {
|
interface ApiResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
|
|||||||
@@ -27,11 +27,16 @@ const getCurrentBaseUrl = () => {
|
|||||||
// 动态获取 WebSocket URL
|
// 动态获取 WebSocket URL
|
||||||
const getCurrentWsUrl = () => {
|
const getCurrentWsUrl = () => {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
// 在开发模式下,始终使用后端的WebSocket地址
|
// 检查是否是 Next.js 开发服务器(端口 3000 或 3001)
|
||||||
if (window.location.hostname === 'localhost' && (window.location.port === '3000' || window.location.port === '3001')) {
|
const isNextDevServer = window.location.hostname === 'localhost' &&
|
||||||
|
(window.location.port === '3000' || window.location.port === '3001');
|
||||||
|
|
||||||
|
if (isNextDevServer) {
|
||||||
|
// 开发模式:通过 Next.js 开发服务器访问,连接到后端 WebSocket
|
||||||
return 'ws://localhost:8080/ws/p2p';
|
return 'ws://localhost:8080/ws/p2p';
|
||||||
}
|
}
|
||||||
// 在生产模式下,使用当前域名
|
|
||||||
|
// 生产模式或通过 Go 服务器访问:使用当前域名和端口
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
return `${protocol}//${window.location.host}/ws/p2p`;
|
return `${protocol}//${window.location.host}/ws/p2p`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ export const apiRoutes = [
|
|||||||
|
|
||||||
// 客户端API配置(用于静态导出时的客户端请求)
|
// 客户端API配置(用于静态导出时的客户端请求)
|
||||||
export const clientApiConfig = {
|
export const clientApiConfig = {
|
||||||
// 直接连接到 Go 后端
|
// 直接连接到 Go 后端 - 动态获取当前域名
|
||||||
baseUrl: 'http://localhost:8080', // 构建时可通过环境变量替换
|
baseUrl: typeof window !== 'undefined' ? window.location.origin : '',
|
||||||
wsUrl: 'ws://localhost:8080/ws', // 构建时可通过环境变量替换
|
wsUrl: typeof window !== 'undefined'
|
||||||
|
? `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`
|
||||||
|
: '',
|
||||||
}
|
}
|
||||||
|
|||||||
25
cmd/main.go
25
cmd/main.go
@@ -2,6 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -19,6 +21,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// 定义命令行参数
|
||||||
|
var port = flag.Int("port", 8080, "服务器监听端口")
|
||||||
|
var help = flag.Bool("help", false, "显示帮助信息")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// 显示帮助信息
|
||||||
|
if *help {
|
||||||
|
fmt.Println("文件传输服务器")
|
||||||
|
fmt.Println("用法:")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化服务
|
// 初始化服务
|
||||||
p2pService := services.NewP2PService()
|
p2pService := services.NewP2PService()
|
||||||
|
|
||||||
@@ -62,9 +77,15 @@ func main() {
|
|||||||
r.Post("/api/create-room", h.CreateRoomHandler)
|
r.Post("/api/create-room", h.CreateRoomHandler)
|
||||||
r.Get("/api/room-info", h.RoomInfoHandler)
|
r.Get("/api/room-info", h.RoomInfoHandler)
|
||||||
|
|
||||||
|
// WebRTC API路由
|
||||||
|
r.Get("/api/webrtc-room-status", h.WebRTCRoomStatusHandler)
|
||||||
|
|
||||||
|
// 构建服务器地址
|
||||||
|
addr := fmt.Sprintf(":%d", *port)
|
||||||
|
|
||||||
// 启动服务器
|
// 启动服务器
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: ":8080",
|
Addr: addr,
|
||||||
Handler: r,
|
Handler: r,
|
||||||
ReadTimeout: 30 * time.Second,
|
ReadTimeout: 30 * time.Second,
|
||||||
WriteTimeout: 30 * time.Second,
|
WriteTimeout: 30 * time.Second,
|
||||||
@@ -73,7 +94,7 @@ func main() {
|
|||||||
|
|
||||||
// 优雅关闭
|
// 优雅关闭
|
||||||
go func() {
|
go func() {
|
||||||
log.Printf("服务器启动在端口 :8080")
|
log.Printf("服务器启动在端口 %s", addr)
|
||||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
log.Fatalf("服务器启动失败: %v", err)
|
log.Fatalf("服务器启动失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,3 +279,52 @@ func (h *Handler) RoomInfoHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WebRTCRoomStatusHandler 获取WebRTC房间状态API
|
||||||
|
func (h *Handler) WebRTCRoomStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 设置响应为JSON格式
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"message": "方法不允许",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code := r.URL.Query().Get("code")
|
||||||
|
if code == "" || len(code) != 6 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"message": "请提供正确的6位房间码",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取WebRTC房间状态
|
||||||
|
status := h.webrtcService.GetRoomStatus(code)
|
||||||
|
|
||||||
|
if !status["exists"].(bool) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"message": "房间不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建响应
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": "房间状态获取成功",
|
||||||
|
"exists": status["exists"],
|
||||||
|
"sender_online": status["sender_online"],
|
||||||
|
"receiver_online": status["receiver_online"],
|
||||||
|
"created_at": status["created_at"],
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ type WebRTCMessage struct {
|
|||||||
|
|
||||||
// HandleWebSocket 处理WebRTC信令WebSocket连接
|
// HandleWebSocket 处理WebRTC信令WebSocket连接
|
||||||
func (ws *WebRTCService) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
|
func (ws *WebRTCService) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Printf("收到WebRTC WebSocket连接请求: %s", r.URL.String())
|
||||||
|
|
||||||
conn, err := ws.upgrader.Upgrade(w, r, nil)
|
conn, err := ws.upgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("WebRTC WebSocket升级失败: %v", err)
|
log.Printf("WebRTC WebSocket升级失败: %v", err)
|
||||||
@@ -64,6 +66,8 @@ func (ws *WebRTCService) HandleWebSocket(w http.ResponseWriter, r *http.Request)
|
|||||||
code := r.URL.Query().Get("code")
|
code := r.URL.Query().Get("code")
|
||||||
role := r.URL.Query().Get("role")
|
role := r.URL.Query().Get("role")
|
||||||
|
|
||||||
|
log.Printf("WebRTC连接参数: code=%s, role=%s", code, role)
|
||||||
|
|
||||||
if code == "" || (role != "sender" && role != "receiver") {
|
if code == "" || (role != "sender" && role != "receiver") {
|
||||||
log.Printf("WebRTC连接参数无效: code=%s, role=%s", code, role)
|
log.Printf("WebRTC连接参数无效: code=%s, role=%s", code, role)
|
||||||
return
|
return
|
||||||
@@ -78,6 +82,8 @@ func (ws *WebRTCService) HandleWebSocket(w http.ResponseWriter, r *http.Request)
|
|||||||
Room: code,
|
Room: code,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("WebRTC客户端已创建: ID=%s, Role=%s, Room=%s", clientID, role, code)
|
||||||
|
|
||||||
// 添加客户端到房间
|
// 添加客户端到房间
|
||||||
ws.addClientToRoom(code, client)
|
ws.addClientToRoom(code, client)
|
||||||
log.Printf("WebRTC %s连接到房间: %s (客户端ID: %s)", role, code, clientID)
|
log.Printf("WebRTC %s连接到房间: %s (客户端ID: %s)", role, code, clientID)
|
||||||
@@ -86,6 +92,9 @@ func (ws *WebRTCService) HandleWebSocket(w http.ResponseWriter, r *http.Request)
|
|||||||
defer func() {
|
defer func() {
|
||||||
ws.removeClientFromRoom(code, clientID)
|
ws.removeClientFromRoom(code, clientID)
|
||||||
log.Printf("WebRTC客户端断开连接: %s (房间: %s)", clientID, code)
|
log.Printf("WebRTC客户端断开连接: %s (房间: %s)", clientID, code)
|
||||||
|
|
||||||
|
// 通知房间内其他客户端对方已断开连接
|
||||||
|
ws.notifyRoomDisconnection(code, clientID, client.Role)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 处理消息
|
// 处理消息
|
||||||
@@ -201,7 +210,45 @@ func (ws *WebRTCService) generateClientID() string {
|
|||||||
return fmt.Sprintf("webrtc_client_%d", rand.Int63())
|
return fmt.Sprintf("webrtc_client_%d", rand.Int63())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取房间状态
|
// 通知房间内客户端有人断开连接
|
||||||
|
func (ws *WebRTCService) notifyRoomDisconnection(roomCode string, disconnectedClientID string, disconnectedRole string) {
|
||||||
|
ws.roomsMux.Lock()
|
||||||
|
defer ws.roomsMux.Unlock()
|
||||||
|
|
||||||
|
room := ws.rooms[roomCode]
|
||||||
|
if room == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建断开连接通知消息
|
||||||
|
disconnectionMsg := &WebRTCMessage{
|
||||||
|
Type: "disconnection",
|
||||||
|
From: disconnectedClientID,
|
||||||
|
Payload: map[string]interface{}{
|
||||||
|
"role": disconnectedRole,
|
||||||
|
"message": "对方已停止传输",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通知房间内其他客户端
|
||||||
|
if room.Sender != nil && room.Sender.ID != disconnectedClientID {
|
||||||
|
err := room.Sender.Connection.WriteJSON(disconnectionMsg)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("通知发送方断开连接失败: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("已通知发送方: 对方已断开连接")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if room.Receiver != nil && room.Receiver.ID != disconnectedClientID {
|
||||||
|
err := room.Receiver.Connection.WriteJSON(disconnectionMsg)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("通知接收方断开连接失败: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("已通知接收方: 对方已断开连接")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
func (ws *WebRTCService) GetRoomStatus(code string) map[string]interface{} {
|
func (ws *WebRTCService) GetRoomStatus(code string) map[string]interface{} {
|
||||||
ws.roomsMux.RLock()
|
ws.roomsMux.RLock()
|
||||||
defer ws.roomsMux.RUnlock()
|
defer ws.roomsMux.RUnlock()
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
|||||||
2:I[6801,["177","static/chunks/app/layout-d0f2a5cbcfca20f5.js"],"ToastProvider"]
|
2:I[6801,["177","static/chunks/app/layout-d0f2a5cbcfca20f5.js"],"ToastProvider"]
|
||||||
3:I[7555,[],""]
|
3:I[7555,[],""]
|
||||||
4:I[1295,[],""]
|
4:I[1295,[],""]
|
||||||
5:I[1287,["423","static/chunks/423-3db9dd818e8fd852.js","974","static/chunks/app/page-6f704b6eb3095813.js"],"default"]
|
5:I[7443,["423","static/chunks/423-3db9dd818e8fd852.js","974","static/chunks/app/page-c710cf440dafbd05.js"],"default"]
|
||||||
6:I[9665,[],"OutletBoundary"]
|
6:I[9665,[],"OutletBoundary"]
|
||||||
8:I[4911,[],"AsyncMetadataOutlet"]
|
8:I[4911,[],"AsyncMetadataOutlet"]
|
||||||
a:I[9665,[],"ViewportBoundary"]
|
a:I[9665,[],"ViewportBoundary"]
|
||||||
@@ -12,7 +12,7 @@ f:I[8393,[],""]
|
|||||||
:HL["/_next/static/media/569ce4b8f30dc480-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
:HL["/_next/static/media/569ce4b8f30dc480-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||||
:HL["/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
:HL["/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||||
:HL["/_next/static/css/f6f47fde0030ec04.css","style"]
|
:HL["/_next/static/css/f6f47fde0030ec04.css","style"]
|
||||||
0:{"P":null,"b":"8bbWAyBmnmNj0Jk5ryXl4","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/f6f47fde0030ec04.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"zh-CN","children":["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","children":["$","$L2",null,{"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L5",null,{}],null,["$","$L6",null,{"children":["$L7",["$","$L8",null,{"promise":"$@9"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$La",null,{"children":"$Lb"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$Lc",null,{"children":["$","div",null,{"hidden":true,"children":["$","$d",null,{"fallback":null,"children":"$Le"}]}]}]]}],false]],"m":"$undefined","G":["$f",[]],"s":false,"S":true}
|
0:{"P":null,"b":"8y8TXPJV_4IIplGqW3zUm","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/f6f47fde0030ec04.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"zh-CN","children":["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","children":["$","$L2",null,{"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L5",null,{}],null,["$","$L6",null,{"children":["$L7",["$","$L8",null,{"promise":"$@9"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$La",null,{"children":"$Lb"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$Lc",null,{"children":["$","div",null,{"hidden":true,"children":["$","$d",null,{"fallback":null,"children":"$Le"}]}]}]]}],false]],"m":"$undefined","G":["$f",[]],"s":false,"S":true}
|
||||||
b:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
b:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
||||||
7:null
|
7:null
|
||||||
10:I[8175,[],"IconMark"]
|
10:I[8175,[],"IconMark"]
|
||||||
|
|||||||
Reference in New Issue
Block a user