首次提交:太和e护APP调试模式websocket
This commit is contained in:
385
bin/server.dart
Normal file
385
bin/server.dart
Normal file
@@ -0,0 +1,385 @@
|
||||
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');
|
||||
}
|
||||
Reference in New Issue
Block a user