Flutter路由与操作系统交互插件router_os_client的使用

发布于 1周前 作者 sinazl 来自 Flutter

Flutter路由与操作系统交互插件router_os_client的使用

RouterOSClient 是一个Dart/Flutter包,提供了一个易于使用的接口,用于通过Socket连接与Mikrotik的RouterOS设备进行交互。该包支持标准和安全(SSL/TLS)连接,允许你实时发送命令并接收来自RouterOS设备的数据。

功能

  • Socket连接:使用标准TCP或安全SSL/TLS套接字连接到RouterOS设备。
  • 命令执行:向RouterOS发送命令并接收结构化的回复。
  • 流数据:流式处理长时间运行的命令以接收连续更新。
  • 错误处理:提供全面的错误处理机制,包括自定义异常以应对各种失败场景。
  • 详细日志:可选的日志记录功能,便于调试和监控通信。

安装

在你的 pubspec.yaml 文件中添加以下依赖:

dependencies:
  router_os_client: ^1.0.12

然后运行以下命令来安装依赖:

flutter pub get

使用方法

1. 创建 RouterOSClient 实例
import 'package:router_os_client/router_os_client.dart';

void main() async {
  RouterOSClient client = RouterOSClient(
    address: '192.168.88.1', // 替换为你的RouterOS IP地址
    user: 'admin',           // 替换为你的RouterOS用户名
    password: 'password',    // 替换为你的RouterOS密码
    useSsl: false,           // 如果使用SSL/TLS则设置为true
    verbose: true,           // 设置为true以启用详细日志
  );

  bool isConnected = await client.login();

  if (isConnected) {
    print('已连接到RouterOS');
  } else {
    print('连接RouterOS失败');
  }
}
2. 发送命令
void fetchInterfaces() async {
  List<Map<String, String>> interfaces = await client.talk(['/interface/print']);

  for (var interface in interfaces) {
    print('接口名称: ${interface['name']}');
  }
}
3. 流式处理数据

对于长时间运行的命令(如 /tool/torch),你可以流式处理数据:

void streamTorchData() async {
  await for (var data in client.streamData('/tool/torch interface=ether1')) {
    print('Torch数据: $data');
  }
}
4. 关闭连接

完成与RouterOS设备的通信后,关闭连接:

client.close();

错误处理

RouterOSClient 提供了多个自定义异常来优雅地处理错误:

  • LoginError:登录过程中发生错误时抛出。
  • WordTooLong:命令单词超出最大长度时抛出。
  • CreateSocketError:创建套接字连接失败时抛出。
  • RouterOSTrapError:RouterOS返回陷阱错误响应时抛出。

示例:

try {
  await client.login();
} catch (LoginError e) {
  print('登录失败: ${e.message}');
} catch (CreateSocketError e) {
  print('创建套接字失败: ${e.message}');
}

示例代码

以下是一个完整的示例,展示了如何连接、发送命令并流式处理数据:

import 'package:router_os_client/router_os_client.dart';

void main() async {
  RouterOSClient client = RouterOSClient(
    address: '192.168.88.1',
    user: 'admin',
    password: 'password',
    useSsl: false,
    verbose: true,
  );

  try {
    if (await client.login()) {
      print('已连接到RouterOS');

      // 获取并打印接口列表
      List<Map<String, String>> interfaces = await client.talk(['/interface/print']);
      interfaces.forEach((interface) {
        print('接口: ${interface['name']}');
      });

      // 流式处理Torch数据
      await for (var data in client.streamData('/tool/torch interface=ether1')) {
        print('Torch数据: $data');
      }
    } else {
      print('连接RouterOS失败');
    }
  } catch (e) {
    print('错误: $e');
  } finally {
    client.close();
  }
}

使用 talk 方法传递参数

talk 方法现在可以接受一个 Map<String, String> 来发送带有参数的命令到RouterOS设备。

示例:

await client.talk('/queue/simple/add', {
  '.id': '*1',
  'target': '192.168.88.1/32',
  'priority': '1',
  'max-limit': '10M/10M',
  'dynamic': 'false',
  'disabled': 'false',
});

这允许你发送更复杂的命令,并使用键值对配置RouterOS设备。

完整的Flutter应用示例

以下是一个完整的Flutter应用示例,展示了如何使用 RouterOSClient 插件与RouterOS设备进行交互:

import 'package:flutter/material.dart';
import 'package:router_os_client/router_os_client.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'RouterOS Connect',
      home: RouterOSWidget(),
    );
  }
}

class RouterOSWidget extends StatefulWidget {
  const RouterOSWidget({super.key});

  [@override](/user/override)
  RouterOSWidgetState createState() => RouterOSWidgetState();
}

class RouterOSWidgetState extends State<RouterOSWidget> {
  late RouterOSClient client;
  String status = '未连接';
  List<String> connectedDevices = [];
  TextEditingController addressController = TextEditingController();
  TextEditingController userController = TextEditingController();
  TextEditingController passwordController = TextEditingController();
  TextEditingController portController = TextEditingController();
  TextEditingController commandController = TextEditingController();
  String commandOutput = '';
  bool useStream = false;

  [@override](/user/override)
  void initState() {
    super.initState();
    addressController.text = ''; // 默认值
    userController.text = ''; // 默认值
    passwordController.text = ''; // 默认值
    portController.text = ''; // 默认值
  }

  Future<void> _connectToRouter() async {
    try {
      client = RouterOSClient(
        address: addressController.text,
        user: userController.text,
        password: passwordController.text,
        port: int.parse(portController.text),
        useSsl: false,
        timeout: const Duration(seconds: 10),
        verbose: true,
      );

      bool loginSuccess = await client.login();
      if (loginSuccess) {
        setState(() {
          status = '已连接到RouterOS';
        });
      } else {
        setState(() {
          status = '登录失败';
        });
      }
    } catch (e) {
      setState(() {
        status = '连接失败: $e';
      });
    }
  }

  Future<void> _executeCommand() async {
    String command = commandController.text;
    try {
      if (useStream) {
        await startTorchStream(command);
      } else {
        var result = await client.talk(command);
        setState(() {
          commandOutput = result.toString();
        });
      }
    } catch (e) {
      setState(() {
        commandOutput = '执行命令失败: $e';
      });
    }
  }

  Future<void> startTorchStream(String command) async {
    try {
      var stream = client.streamData(command);

      await for (var sentence in stream) {
        setState(() {
          commandOutput = sentence.toString();
        });
      }
    } catch (e) {
      if (kDebugMode) {
        print('流式处理数据时出错: $e');
      }
      setState(() {
        commandOutput = '流式处理数据时出错: $e';
      });
    }
  }

  [@override](/user/override)
  void dispose() {
    client.close();
    addressController.dispose();
    userController.dispose();
    passwordController.dispose();
    portController.dispose();
    commandController.dispose();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('RouterOS连接'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextField(
              controller: addressController,
              decoration: const InputDecoration(
                border: OutlineInputBorder(),
                labelText: 'RouterOS地址',
              ),
            ),
            const SizedBox(height: 10),
            TextField(
              controller: userController,
              decoration: const InputDecoration(
                border: OutlineInputBorder(),
                labelText: '用户名',
              ),
            ),
            const SizedBox(height: 10),
            TextField(
              controller: passwordController,
              decoration: const InputDecoration(
                border: OutlineInputBorder(),
                labelText: '密码',
              ),
            ),
            const SizedBox(height: 10),
            TextField(
              controller: portController,
              decoration: const InputDecoration(
                border: OutlineInputBorder(),
                labelText: '端口',
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async {
                if (addressController.text.isNotEmpty &&
                    userController.text.isNotEmpty &&
                    passwordController.text.isNotEmpty &&
                    portController.text.isNotEmpty) {
                  await _connectToRouter();
                } else {
                  setState(() {
                    status = '请填写所有字段';
                  });
                }
              },
              child: const Text('连接'),
            ),
            const SizedBox(height: 20),
            Text('连接状态: $status'),
            const SizedBox(height: 20),
            ListView.builder(
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(), // 禁用内部滚动
              itemCount: connectedDevices.length,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text(connectedDevices[index]),
                );
              },
            ),
            TextField(
              controller: commandController,
              decoration: const InputDecoration(
                border: OutlineInputBorder(),
                labelText: '输入命令',
              ),
            ),
            const SizedBox(height: 10),
            CheckboxListTile(
              title: const Text('使用流'),
              value: useStream,
              onChanged: (bool? value) {
                setState(() {
                  useStream = value ?? false;
                });
              },
            ),
            const SizedBox(height: 10),
            ElevatedButton(
              onPressed: () async {
                await _executeCommand();
              },
              child: const Text('执行命令'),
            ),
            const SizedBox(height: 20),
            const Text('命令输出:'),
            SingleChildScrollView(
              scrollDirection: Axis.vertical,
              child: Text(commandOutput),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                client.close();
                setState(() {
                  status = '已断开连接';
                  connectedDevices.clear();
                });
              },
              child: const Text('断开连接'),
            ),
          ],
        ),
      ),
    );
  }
}

更多关于Flutter路由与操作系统交互插件router_os_client的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter路由与操作系统交互插件router_os_client的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,router_os_client 是一个假想的 Flutter 插件,用于在 Flutter 应用中实现与操作系统级别的路由交互。尽管实际中可能不存在一个名为 router_os_client 的官方插件,但我可以为你提供一个概念性的代码示例,展示如何在 Flutter 中通过插件与操作系统进行交互。

假设的 router_os_client 插件功能

  • 监听系统路由变化(如 URL 跳转)
  • 发送系统指令(如打开特定 URL)

Flutter 项目结构

假设你的 Flutter 项目结构如下:

my_flutter_app/
├── android/
├── ios/
├── lib/
│   ├── main.dart
│   └── router_os_client_plugin.dart
├── pubspec.yaml

插件代码 (router_os_client_plugin.dart)

这个文件将模拟一个插件接口,用于与操作系统交互。

import 'dart:typed_data';
import 'package:flutter/services.dart';

class RouterOSClient {
  static const MethodChannel _channel = const MethodChannel('com.example.router_os_client');

  // 监听系统路由变化
  static Future<void> startListeningToSystemRoutes() async {
    _channel.setMethodCallHandler((MethodCall call) async {
      if (call.method == "onSystemRouteChanged") {
        final String newRoute = call.arguments as String;
        // 处理系统路由变化
        print("System route changed to: $newRoute");
        // 可以在这里调用其他方法或更新 UI
      }
      return null;
    });
    
    // 假设调用原生方法开始监听
    await _channel.invokeMethod('startListeningToSystemRoutes');
  }

  // 打开特定 URL
  static Future<void> openUrl(String url) async {
    try {
      await _channel.invokeMethod('openUrl', url);
    } on PlatformException catch (e) {
      print("Failed to open URL: '${e.message}'.");
    }
  }
}

主应用代码 (main.dart)

这个文件将展示如何使用 RouterOSClient 插件。

import 'package:flutter/material.dart';
import 'router_os_client_plugin.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Router OS Client Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String _currentSystemRoute = "N/A";

  @override
  void initState() {
    super.initState();
    // 开始监听系统路由变化
    RouterOSClient.startListeningToSystemRoutes();
  }

  void _openUrl() {
    // 打开一个示例 URL
    RouterOSClient.openUrl("https://www.example.com");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Router OS Client Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Current System Route:',
            ),
            Text(
              _currentSystemRoute,
              style: TextStyle(fontSize: 20),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _openUrl,
              child: Text('Open URL'),
            ),
          ],
        ),
      ),
    );
  }
}

原生平台代码(示例)

为了完整性,这里简要说明如何在原生平台(如 Android 和 iOS)上实现相应的原生方法。

Android (MainActivity.ktMainActivity.java)

// MainActivity.kt
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
    private val CHANNEL = "com.example.router_os_client"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == "startListeningToSystemRoutes") {
                // 开始监听系统路由变化(需要实现具体逻辑)
                // ...

                // 模拟发送系统路由变化
                val newRoute = "https://example.com/new-route"
                MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).invokeMethod("onSystemRouteChanged", newRoute)
                result.success(null)
            } else if (call.method == "openUrl") {
                val url = call.argument<String>() ?: ""
                // 打开 URL(需要实现具体逻辑)
                // ...
                result.success(null)
            } else {
                result.notImplemented()
            }
        }
    }
}

iOS (AppDelegate.swift)

// AppDelegate.swift
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    let channel = FlutterMethodChannel(name: "com.example.router_os_client", binaryMessenger: controller)
    
    channel.setMethodCallHandler {
      (call: FlutterMethodCall, result: @escaping FlutterResult) in
      if call.method == "startListeningToSystemRoutes" {
        // 开始监听系统路由变化(需要实现具体逻辑)
        // ...
        
        // 模拟发送系统路由变化
        let newRoute = "https://example.com/new-route"
        channel.invokeMethod("onSystemRouteChanged", arguments: newRoute)
        result(nil)
      } else if call.method == "openUrl" {
        if let url = call.arguments as? String {
          // 打开 URL(需要实现具体逻辑)
          // ...
        }
        result(nil)
      } else {
        result(.notImplemented)
      }
    }
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

注意事项

  1. 实际插件开发:上述代码仅为示例,实际开发中需要创建并发布一个真正的 Flutter 插件,包括 Android 和 iOS 的原生实现。
  2. 安全性:与操作系统交互时,务必注意安全性,避免潜在的安全漏洞。
  3. 平台差异:不同平台(Android 和 iOS)在实现上可能会有所不同,需要分别处理。

希望这个示例能够帮助你理解如何在 Flutter 中通过插件与操作系统进行交互。

回到顶部