385 lines
12 KiB
Dart
385 lines
12 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:shelf/shelf.dart';
|
|
import 'package:shelf/shelf_io.dart' as io;
|
|
import 'package:shelf_web_socket/shelf_web_socket.dart';
|
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
|
|
|
// 用户连接信息
|
|
class UserConnection {
|
|
final String uuid;
|
|
final String deviceModel;
|
|
final WebSocketChannel channel;
|
|
final DateTime connectedAt;
|
|
String? currentDeviceId;
|
|
|
|
UserConnection({
|
|
required this.uuid,
|
|
required this.deviceModel,
|
|
required this.channel,
|
|
required this.connectedAt,
|
|
this.currentDeviceId,
|
|
});
|
|
}
|
|
|
|
// WebSocket 管理器
|
|
class WebSocketManager {
|
|
final Map<String, UserConnection> _devices = {};
|
|
final Map<String, WebSocketChannel> _webClients = {};
|
|
|
|
// 存储每个连接的注册信息
|
|
final Map<WebSocketChannel, String> _channelToClientId = {};
|
|
final Map<WebSocketChannel, String> _channelToDeviceUuid = {};
|
|
|
|
// 获取设备连接
|
|
UserConnection? getDevice(String uuid) {
|
|
return _devices[uuid];
|
|
}
|
|
|
|
// 获取所有设备列表
|
|
List<Map<String, dynamic>> getDeviceList() {
|
|
return _devices.entries.map((entry) {
|
|
return {
|
|
'uuid': entry.key,
|
|
'deviceModel': entry.value.deviceModel,
|
|
'connectedAt': entry.value.connectedAt.toIso8601String(),
|
|
'isConnected': true,
|
|
};
|
|
}).toList();
|
|
}
|
|
|
|
// 注册设备
|
|
void registerDevice(String uuid, String deviceModel, WebSocketChannel channel) {
|
|
_devices[uuid] = UserConnection(
|
|
uuid: uuid,
|
|
deviceModel: deviceModel,
|
|
channel: channel,
|
|
connectedAt: DateTime.now(),
|
|
);
|
|
_channelToDeviceUuid[channel] = uuid;
|
|
|
|
print('✅ 调试设备注册: $deviceModel ($uuid)');
|
|
_broadcastUserList();
|
|
}
|
|
|
|
// 注册网页客户端
|
|
void registerWebClient(String clientId, WebSocketChannel channel) {
|
|
_webClients[clientId] = channel;
|
|
_channelToClientId[channel] = clientId;
|
|
|
|
print('🌐 网页客户端注册: $clientId');
|
|
|
|
// 发送设备列表
|
|
_sendUserListToClient(channel);
|
|
}
|
|
|
|
// 处理网页消息
|
|
void handleWebMessage(WebSocketChannel channel, Map<String, dynamic> data) {
|
|
final clientId = _channelToClientId[channel];
|
|
if (clientId == null) {
|
|
print('未找到对应的网页客户端');
|
|
return;
|
|
}
|
|
|
|
print('💻 收到网页指令: ${data['type']} from client: $clientId');
|
|
|
|
final commandType = data['type'] as String;
|
|
final targetUuid = data['targetUuid'] as String?;
|
|
|
|
switch (commandType) {
|
|
case 'select_device':
|
|
if (targetUuid != null) {
|
|
final device = _devices[targetUuid];
|
|
if (device != null) {
|
|
print(' -> 网页选择控制设备: $targetUuid (${device.deviceModel})');
|
|
channel.sink.add(jsonEncode({
|
|
'type': 'device_selected',
|
|
'targetUuid': targetUuid,
|
|
'deviceModel': device.deviceModel,
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
}));
|
|
} else {
|
|
print(' -> 设备不存在: $targetUuid');
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'scan':
|
|
if (targetUuid != null) {
|
|
final device = _devices[targetUuid];
|
|
if (device != null) {
|
|
print(' -> 发送扫描命令到设备: $targetUuid');
|
|
device.channel.sink.add(jsonEncode({
|
|
'type': 'scan',
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
}));
|
|
} else {
|
|
print(' -> 设备不存在: $targetUuid');
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'stop_scan':
|
|
if (targetUuid != null) {
|
|
final device = _devices[targetUuid];
|
|
if (device != null) {
|
|
device.channel.sink.add(jsonEncode({
|
|
'type': 'stop_scan',
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
}));
|
|
print(' -> 发送停止扫描命令到设备: $targetUuid');
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'connect':
|
|
if (targetUuid != null) {
|
|
final device = _devices[targetUuid];
|
|
final deviceId = data['deviceId'] as String?;
|
|
if (device != null && deviceId != null) {
|
|
device.currentDeviceId = deviceId;
|
|
device.channel.sink.add(jsonEncode({
|
|
'type': 'connect',
|
|
'deviceId': deviceId,
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
}));
|
|
print(' -> 发送连接设备命令到: $targetUuid, 设备ID: $deviceId');
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'disconnect':
|
|
if (targetUuid != null) {
|
|
final device = _devices[targetUuid];
|
|
if (device != null) {
|
|
device.currentDeviceId = null;
|
|
device.channel.sink.add(jsonEncode({
|
|
'type': 'disconnect',
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
}));
|
|
print(' -> 发送断开连接命令到设备: $targetUuid');
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'send_data':
|
|
if (targetUuid != null) {
|
|
final device = _devices[targetUuid];
|
|
final sendData = data['data'];
|
|
if (device != null && sendData != null) {
|
|
device.channel.sink.add(jsonEncode({
|
|
'type': 'send_data',
|
|
'data': sendData,
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
}));
|
|
print(' -> 发送数据到设备: $targetUuid, 数据: $sendData');
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
print('未知命令: $commandType');
|
|
}
|
|
}
|
|
|
|
// 处理设备消息
|
|
void handleDeviceMessage(WebSocketChannel channel, Map<String, dynamic> data) {
|
|
final uuid = _channelToDeviceUuid[channel];
|
|
if (uuid == null) {
|
|
print('未找到对应的设备');
|
|
return;
|
|
}
|
|
|
|
print('📱 收到设备 $uuid 消息: ${data['type']}');
|
|
|
|
// 转发给所有网页客户端
|
|
for (var entry in _webClients.entries) {
|
|
entry.value.sink.add(jsonEncode({
|
|
'type': 'device_message',
|
|
'fromUser': uuid,
|
|
'data': data,
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
}));
|
|
}
|
|
}
|
|
|
|
// 移除设备
|
|
void removeDevice(WebSocketChannel channel) {
|
|
final uuid = _channelToDeviceUuid[channel];
|
|
if (uuid != null) {
|
|
_devices.remove(uuid);
|
|
_channelToDeviceUuid.remove(channel);
|
|
print('🗑️ 移除调试设备: $uuid');
|
|
_broadcastUserList();
|
|
}
|
|
}
|
|
|
|
// 移除网页客户端
|
|
void removeWebClient(WebSocketChannel channel) {
|
|
final clientId = _channelToClientId[channel];
|
|
if (clientId != null) {
|
|
_webClients.remove(clientId);
|
|
_channelToClientId.remove(channel);
|
|
print('网页客户端断开: $clientId');
|
|
}
|
|
}
|
|
|
|
void _sendUserListToClient(WebSocketChannel client) {
|
|
final deviceList = getDeviceList();
|
|
client.sink.add(jsonEncode({
|
|
'type': 'user_list',
|
|
'users': deviceList,
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
}));
|
|
print('📡 发送设备列表到网页客户端: ${deviceList.length} 个设备');
|
|
}
|
|
|
|
void _broadcastUserList() {
|
|
final deviceList = getDeviceList();
|
|
final message = jsonEncode({
|
|
'type': 'user_list',
|
|
'users': deviceList,
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
});
|
|
|
|
for (var client in _webClients.values) {
|
|
client.sink.add(message);
|
|
}
|
|
print('📡 广播设备列表: ${deviceList.length} 个设备');
|
|
}
|
|
}
|
|
|
|
// WebSocket 处理函数 - 所有消息都在这里统一处理
|
|
Future<Response> handleWebSocket(Request request, WebSocketManager manager) async {
|
|
final handler = webSocketHandler((WebSocketChannel channel, String? protocol) {
|
|
print('🔌 WebSocket 连接建立');
|
|
|
|
// 标识是否已经注册
|
|
bool isRegistered = false;
|
|
String? connectionType;
|
|
|
|
channel.stream.listen((message) {
|
|
try {
|
|
final data = jsonDecode(message);
|
|
final type = data['type'] as String?;
|
|
|
|
print('收到消息,类型: $type, 已注册: $isRegistered');
|
|
|
|
if (!isRegistered) {
|
|
// 处理注册消息
|
|
if (type == 'device_register') {
|
|
final uuid = data['uuid'] as String?;
|
|
final deviceModel = data['deviceModel'] as String?;
|
|
|
|
if (uuid != null && deviceModel != null) {
|
|
isRegistered = true;
|
|
connectionType = 'device';
|
|
manager.registerDevice(uuid, deviceModel, channel);
|
|
} else {
|
|
print('设备注册信息不完整');
|
|
channel.sink.close();
|
|
}
|
|
} else if (type == 'web_register') {
|
|
final clientId = data['clientId'] as String? ??
|
|
'web_${DateTime.now().millisecondsSinceEpoch}';
|
|
isRegistered = true;
|
|
connectionType = 'web';
|
|
manager.registerWebClient(clientId, channel);
|
|
} else {
|
|
print('等待注册消息,收到: $type');
|
|
channel.sink.close();
|
|
}
|
|
} else {
|
|
// 已注册,根据连接类型处理后续消息
|
|
if (connectionType == 'web') {
|
|
manager.handleWebMessage(channel, data);
|
|
} else if (connectionType == 'device') {
|
|
manager.handleDeviceMessage(channel, data);
|
|
} else {
|
|
print('未知连接类型: $connectionType');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('解析消息失败: $e');
|
|
}
|
|
}, onDone: () {
|
|
// 连接关闭时的清理
|
|
if (connectionType == 'device') {
|
|
manager.removeDevice(channel);
|
|
} else if (connectionType == 'web') {
|
|
manager.removeWebClient(channel);
|
|
}
|
|
print('🔌 连接关闭');
|
|
}, onError: (error) {
|
|
print('连接错误: $error');
|
|
if (connectionType == 'device') {
|
|
manager.removeDevice(channel);
|
|
} else if (connectionType == 'web') {
|
|
manager.removeWebClient(channel);
|
|
}
|
|
});
|
|
});
|
|
|
|
return handler(request);
|
|
}
|
|
|
|
// CORS 中间件
|
|
Middleware createCorsMiddleware() {
|
|
return (Handler innerHandler) {
|
|
return (Request request) async {
|
|
if (request.method == 'OPTIONS') {
|
|
return Response.ok('', headers: {
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
'Access-Control-Allow-Headers': 'Origin, Content-Type, X-Requested-With',
|
|
'Access-Control-Allow-Credentials': 'true',
|
|
});
|
|
}
|
|
|
|
final response = await innerHandler(request);
|
|
|
|
return response.change(headers: {
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
'Access-Control-Allow-Headers': 'Origin, Content-Type, X-Requested-With',
|
|
'Access-Control-Allow-Credentials': 'true',
|
|
});
|
|
};
|
|
};
|
|
}
|
|
|
|
void main() async {
|
|
final manager = WebSocketManager();
|
|
|
|
Future<Response> router(Request request) async {
|
|
final path = request.url.path;
|
|
print('📨 请求: ${request.method} $path');
|
|
|
|
if (path == 'ws') {
|
|
return await handleWebSocket(request, manager);
|
|
}
|
|
if (path == 'users') {
|
|
return Response.ok(
|
|
jsonEncode(manager.getDeviceList()),
|
|
headers: {'Content-Type': 'application/json'},
|
|
);
|
|
}
|
|
return Response.notFound('Not Found');
|
|
}
|
|
|
|
final handler = const Pipeline()
|
|
.addMiddleware(logRequests())
|
|
.addMiddleware(createCorsMiddleware())
|
|
.addHandler(router);
|
|
|
|
//端口
|
|
final server = await io.serve(handler, '0.0.0.0', 8089);
|
|
print('\n🚀 ========================================');
|
|
print('✅ 服务器启动成功!');
|
|
print('🌐 HTTP 地址: http://${server.address.host}:${server.port}');
|
|
print('🔌 WebSocket 地址: ws://${server.address.host}:${server.port}/ws');
|
|
print('📋 用户列表 API: http://${server.address.host}:${server.port}/users');
|
|
print('========================================\n');
|
|
} |