Flutter下的一点实践

news2025/6/2 15:06:04

目录

  • 1、背景
  • 2、refena创世纪代码
  • 3、localsend里refena的刷新
    • 3.1 初始状态
    • 3.2 发起设备扫描流程
    • 3.3 扫描过程
    • 3.3 刷新界面
  • 4.localsend的设备扫描流程
    • 4.1 UDP广播设备注册流程
    • 4.2 TCP/HTTP设备注册流程
    • 4.3 localsend的服务器初始化工作
    • 4.4总结

1、背景

在很久以前,我曾经经历了一小段时间的Flutter开发,当时Flutter的版本才迭代到1.0,在做一个短视频应用的开发中,我曾经产生了一个巨大的疑问,就是Flutter的状态刷新怎么才能简洁、高效,如果每次及每个地方都使用setstate()来刷新界面确实显得非常笨重。
这么多年过去了,就像一个回旋镖一样,我又进了一个Flutter/kotlin混合开发的项目,项目是在开源的localsend项目上做二次开发,localsend作为跨平台传输软件,可以实现在同一局域网的端到端设备之间共享文件。本篇博客将以最简单的方式介绍refena创世纪代码、localsend里refena的刷新、localsend的设备扫描流程。

2、refena创世纪代码

由于采用了Flutter作为开发框架,状态管理必不可少,相比于市面常见的Flutter状态管理框架async_redux,localsend采用了不太常用的状态管理框架refena。作为被async_redux启迪的状态管理框架,redux中常见的storestateactionsreducers同样适用。store保存了应用里所有的状态,而store被保存在各种provider里;action被保存在自身的reducers里,触发store状态发生变化的唯一办法是发送一个action。有关redux的工作流及基础概念可以参考这篇文章以及下面这张图:
在这里插入图片描述

refena官网有最小化的状态刷新介绍,先搬运介绍一下refena的创世纪代码:

final counterProvider = ReduxProvider<ReduxCounter, int>((ref) => Counter());

//1.在refena中,notifier用以保存实际状态,并且可以触发监听它们的控件刷新
//2.init()方法可以定义Notifier初始化状态
//3.可以通过ref来获取定义的其他provider
//4.这里定义counter的初始状态:10
class ReduxCounter extends ReduxNotifier<int> {
  
  int init() => 10;
}

//1.ReduxAction最重要的方法就是reduce()方法,用于向provider返回一个新的状态
//2.这里返回的状态为ReduxCounter现在的值加上传递过来的值
class AddAction extends ReduxAction<ReduxCounter, int> {
  final int amount;

  AddAction(this.amount);
  
  
  int reduce() => state + amount;
}

class MyPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    int counterState = context.watch(counterProvider);
    return Scaffold(
      body: Center(
        child: Text('Counter state: $counterState'),
      ),
      floatingActionButton: FloatingActionButton(
        //点击触发action dispatch
        onPressed: () => context.redux(counterProvider).dispatch(AddAction(2)),
        child: const Icon(Icons.add),
      ),
    );
  }
}

总结起来,在refena下的工作流为:1、定义初始状态;2、重写reduce()方法,在ReduxAction或各个Action子类中定义要改变的状态;3、定义状态的触发条件,调用dispatch方法触发状态改变。总的来说,refena状态刷新其实和MVVM有许多相似之处。

3、localsend里refena的刷新

localsend典型的功能如下:两个接入同一个局域网的设备相互发送UDP广播或HTTP请求,确立连接之后通过HTTP协议来传输文件。下面将简单介绍localsend扫描到设备后的界面刷新流程。

3.1 初始状态

localsend的初始状态如下:
在这里插入图片描述
打开应用,进入发送页签,在“附件的设备”这个列表下开始扫描局域网内的设备。发送页签进行初始化工作,并且发送了一个全局异步的action——SendTabInitAction:

class SendTab extends StatelessWidget {
  const SendTab();

  
  Widget build(BuildContext context) {
    return ViewModelBuilder(
      provider: sendTabVmProvider,
      //依然是在init方法里分发初始化action
      init: (context) => context.global.dispatchAsync(SendTabInitAction(context)),
      ......

这个SendTabInitAction的代码十分简单:

class SendTabInitAction extends AsyncGlobalAction {
  ……
  
  Future<void> reduce() async {
    //从provider里边读取是否有扫描到的设备
    final devices = ref.read(nearbyDevicesProvider).devices;
    if (devices.isEmpty) {
      //如果没有设备触发设备扫描流程
      await dispatchAsync(StartSmartScan(forceLegacy: false));
    }
  }
}

根据refena工作流,发起一个action后,会直接调用它的reduce方法,在reduce方法里产生新的状态,并通过各种方式把这个新的状态同步给widget刷新界面。所以,设备是如何扫描出来的只需要跟进StartSmartScan这个action即可。

3.2 发起设备扫描流程

接下来就进入了localsend代码的核心——扫描设备流程,这个流程较为复杂,包括dart下高级网络编程及refena线程间通信等,这里只给出扫描开始及获取到扫描结果的伪代码:

//英文注释为原生代码注释
class StartSmartScan extends AsyncGlobalAction {
  static const maxInterfaces = 15;
  final bool forceLegacy;

  
  Future<void> reduce() async {
    // 1.Try performant Multicast/UDP method first
    //首先发起UDP广播,UDP广播性能比TCP/HTTP性能高。
    ref.redux(nearbyDevicesProvider).dispatch(StartMulticastScan());

    // At the same time, try to discover favorites
    //首先从文件里读取是否有收藏的设备
    final favorites = ref.read(favoritesProvider);
    final https = ref.read(settingsProvider).https;
    await ref
        .redux(nearbyDevicesProvider)
        .dispatchAsync(StartFavoriteScan(devices: favorites, https: https));
	……

    // 2.If no devices has been found, then switch to legacy discovery mode
    // which is purely HTTP/TCP based.
    final stillEmpty = ref.read(nearbyDevicesProvider).devices.isEmpty;
    final stillInSendTab =
        ref.read(homePageControllerProvider).currentTab == HomeTab.send;
    if (forceLegacy || (stillEmpty && stillInSendTab)) {
       final networkInterfaces =
           //localIpProvider保存了从platformChannel里读取到的当前设备的IP
           //(这里解释一下为什么当前设备的IP需要单独用一个localIpProvider来保存,
           //在android设备里,根据底软实现可能有多个网卡,对应也有多个设备IP)
           //依然首先从provider(内存)里读取是否之前扫描到过设备
           ref.read(localIpProvider).localIps.take(maxInterfaces).toList();
      
      if (networkInterfaces.isNotEmpty) {
        //开始扫描当前设备所有IP所在局域网的设备
        await dispatchAsync(StartLegacySubnetScan(subnets: networkInterfaces));
      }
    } else {
		……
    }
  }
}

3.3 扫描过程

扫描代码主体流程主要分为两个步骤:1.通过UDP协议扫描局域网设备;2.通过TCP/HTTP协议扫描设备。扫描流程较为复杂,需要对网络编程有一定基础的了解。我们跳过具体的扫描流程,直接到获取扫描结果的部分。

//英文注释为localsend原生注释
/// HTTP based discovery on a fixed set of subnets.
class StartLegacySubnetScan extends AsyncGlobalAction {
  ……
  
  Future<void> reduce() async {
    //读取配置信息,端口号、是否是HTTPS协议等
    final settings = ref.read(settingsProvider);
    final port = settings.port;
    final https = settings.https;

    // send announcement in parallel
    //发起设备扫描流程——UDP组播
    ref.redux(nearbyDevicesProvider).dispatch(StartMulticastScan());

    await Future.wait<void>([
      for (final subnet in subnets)
        ref.redux(nearbyDevicesProvider).dispatchAsync(
            //发起设备扫描流程——TCP/HTTP请求
            StartLegacyScan(port: port, localIp: subnet, https: https)),
    ]);
    ……
  }
}

/// It does not really "scan".
/// It just sends an announcement which will cause a response on every other LocalSend member of the network.
class StartMulticastScan
    extends ReduxAction<NearbyDevicesService, NearbyDevicesState> {
  
  NearbyDevicesState reduce() {
    external(notifier._isolateController)
         //开启线程发起UDP组播
        .dispatch(IsolateSendMulticastAnnouncementAction());
    return state;
  }
}
/// Scans one particular subnet with traditional HTTP/TCP discovery.
/// This method awaits until the scan is finished.
class StartLegacyScan
    extends AsyncReduxAction<NearbyDevicesService, NearbyDevicesState> {
  final int port;
  final String localIp;
  final bool https;
  ……

  
  Future<NearbyDevicesState> reduce() async {
	……
	// 1.Scan all IP addresses on the WLAN
    final stream = external(notifier._isolateController)
        //开启线程发起TCP/HTTP协议设备扫描流程,扫描到的设备保存在Stream里
        .dispatchTakeResult(IsolateInterfaceHttpDiscoveryAction(
      networkInterface: localIp,
      port: port,
      https: https,
    ));
    // 2.Register the device to the RegisterDeviceAction
    await for (final device in stream) {
      //将stream里的设备赋值RegisterDeviceAction
      //扫描过的设备保存到nearbyDevicesProvider
      await dispatchAsync(RegisterDeviceAction(device));
    }
	……
  }
}

3.3 刷新界面

/// Registers a device in the state.
/// It will override any existing device with the same IP.
class RegisterDeviceAction
    extends AsyncReduxAction<NearbyDevicesService, NearbyDevicesState> {
  ……
  
  Future<NearbyDevicesState> reduce() async {
    ……
    //在RegisterDeviceAction的reduce方法里触发界面刷新
    var nearbyDevicesState = state.copyWith(
      devices: {...state.devices}
        ..update(device.ip, (_) => device, ifAbsent: () => device),
    );
    ……
  }
}

这样,通过扫描设备流程就将扫描到的设备更新到了界面上:
在这里插入图片描述

4.localsend的设备扫描流程

4.1 UDP广播设备注册流程

在前文里,我们已经提到设备扫描首先会以UDP广播来扫描设备,先来看看代码实现。

  //英文注释为原生代码注释
  /// Binds the UDP port and listen to UDP multicast packages
  /// It will automatically answer announcement messages
  Stream<Device> startListener() async* {
    ……
    final sockets = await _getSockets(syncState.multicastGroup, syncState.port);
    //遍历UDP组播地址的所有Socket
    for (final socket in sockets) {
      //开始监听是否有组播
      socket.socket.listen((_) {
        final datagram = socket.socket.receive();
        ……
        try {
          //将Socket数据转换为对象
          final dto = MulticastDto.fromJson(jsonDecode(utf8.decode(datagram.data)));
          if (dto.fingerprint == syncState.securityContext.certificateHash) {
            return;
          }
          ……
          if ((dto.announcement == true || dto.announce == true) && syncState.serverRunning) {
            // only respond when server is running
            //向UDP组播广播方返回应答消息
            //这里业务逻辑为UDP设备注册流程
            //广播发送方作为server
            //广播应答方作为client
            _answerAnnouncement(peer);
          }
        } catch (e) {
          ……
        }
      });
    }

    // Tell everyone in the network that I am online
    //向UDP组播地址所有成员发送UDP组播,此举可以提供设备扫描成功率
    sendAnnouncement(); // ignore: unawaited_futures

    yield* streamController.stream;
  }
  
    /// Responds to an announcement.
  Future<void> _answerAnnouncement(Device peer) async {
    try {
      // Answer with TCP
      //通过dio向广播发送方发起一路HTTP请求,这里的请求接口为设备注册接口
      await _ref.read(dioProvider).discovery.post(
            ApiRoute.register.target(peer),
            data: _getRegisterDto().toJson(),
          );
    } catch (e) {
    ……
    }
  }

  /// Sends an announcement which triggers a response on every LocalSend member of the network.
  //发送一个广播,在网络的每个localSend成员上触发应答广播消息
  Future<void> sendAnnouncement() async {
    final syncState = _ref.read(syncProvider);
    final sockets = await _getSockets(syncState.multicastGroup);
    final dto = _getMulticastDto(announcement: true);
    //分别以100ms、500ms、2000ms向发送方应答组播消息
    for (final wait in [100, 500, 2000]) {
      ……
      for (final socket in sockets) {
        try {
          socket.socket.send(dto, InternetAddress(syncState.multicastGroup), syncState.port);
          socket.socket.close();
        } catch (e) {
          ……
        }
      }
    }
    ……
  }

Future<List<_SocketResult>> _getSockets(String multicastGroup, [int? port]) async {
  //通过各个平台的platformChannel获取当前设备的IP(android设备通常是SoftAP IP)
  final interfaces = await NetworkInterface.list();
  final sockets = <_SocketResult>[];
  for (final interface in interfaces) {
    try {
      //根据IP地址绑定到localsend预先定义的端口号上,返回一个Socket端点
      final socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, port ?? 0);
      //把这个Socket加入到UDP组播地址里
      socket.joinMulticast(InternetAddress(multicastGroup), interface);
      ……
    } catch (e) {
      ……
    }
  }

  return sockets;
}

4.2 TCP/HTTP设备注册流程

UDP设备流程结束之后,才会走到HTTP设备注册流程。HTTP设备注册流程资源占用率比UDP广播这种方式大得多。

class HttpScanDiscoveryService {
  ……
  //参数名networkInterface代码当前设备的IP地址。如192.168.31.246
  Stream<Device> getStream({required String networkInterface, required int port, required bool https}) {
    //遍历197.168.31.0~197.168.31.255里的所有IP,尝试向这里面的每个IP地址发起一路HTTP请求
    final ipList = List.generate(256, (i) => '${networkInterface.split('.').take(3).join('.')}.$i').where((ip) => ip != networkInterface).toList();
    _runners[networkInterface]?.stop();
    _runners[networkInterface] = TaskRunner<Device?>(
      initialTasks: List.generate(
        ipList.length,
        //发起设备注册请求
        (index) => () async => _doRequest(ipList[index], port, https),
      ),
      concurrency: 50,
    );

    return _runners[networkInterface]!.stream.where((device) => device != null).cast<Device>();
  }

  Future<Device?> _doRequest(String currentIp, int port, bool https) async {
    ……
    final device = await _targetedDiscoveryService.state.discover(
      ip: currentIp,
      port: port,
      https: https,
      onError: null,
    );
    ……
    }
    return device;
  }

4.3 localsend的服务器初始化工作

前面讲到了localsend的界面刷新、设备注册流程,还有个问题就是localsend到底如何处理这些广播、请求的?简单来说,localsend在进入应用的时候,跑了一个HTTP server来处理组播、HTTP请求。

/// Starts the server.
  Future<ServerState?> startServer({
    required String alias,
    required int port,
    required bool https,
  }) async
  {
    //1.检查用户给localsend客户端取的别名,例如:“美丽的芒果”
    alias = alias.trim();
    if (alias.isEmpty) {
      alias = generateRandomAlias();
    }
	……
    final router = SimpleServerRouteBuilder();
    final fingerprint = ref.read(securityProvider).certificateHash;
    _receiveController.installRoutes(
      router: router,
      alias: alias,
      port: port,
      https: https,
      fingerprint: fingerprint,
      showToken: ref.read(settingsProvider).showToken,
    );
    _sendController.installRoutes(
      router: router,
      alias: alias,
      fingerprint: fingerprint,
    );

    final HttpServer httpServer;
    //默认HTTPS协议,需要先安装默认证书
    if (https) {
      final securityContext = ref.read(securityProvider);
      httpServer = await HttpServer.bindSecure(
        '0.0.0.0',
        port,
        SecurityContext()
          ..usePrivateKeyBytes(securityContext.privateKey.codeUnits)
          ..useCertificateChainBytes(securityContext.certificate.codeUnits),
      );
    } else {
      //HTTP协议无需证书
      httpServer = await HttpServer.bind(
        '0.0.0.0',
        port,
      );
    }
    //启动服务
    final server = SimpleServer.start(server: httpServer, routes: router);

    final newServerState = ServerState(
      httpServer: server,
      alias: alias,
      port: port,
      https: https,
      session: null,
      webSendState: null,
      pinAttempts: {},
    );

    state = newServerState;
    return newServerState;
  }

最后一个问题,localsend作为服务器有哪些RESTful API?根据官方文档,localsend应该提供了以下这些接口:

enum ApiRoute {
  //早期的注册接口,现已废弃
  info('info'),
  //现在版本的注册接口,传递client端IP地址、名称等基础信息
  register('register'),
  //文件传输之前获取token的接口
  prepareUpload('prepare-upload', 'send-request'),
  //文件传输接口
  upload('upload', 'send'),
  //取消接口,包括发送取消、接收取消
  cancel('cancel'),
  ……
  ;

4.4总结

总结起来,localsend的关键原理:

  1. 建立一个HTTP Sever。监听相关端口接收UDP组播;初始化RESTful API接口,用于HTTP的设备注册、文件传输;
  2. UDP设备注册流程中,服务端监听UDP组播端口、客户端回复组播消息并在回复组播消息后发起HTTP注册流程,向服务端传输IP等关键信息;
  3. TCP设备注册流程中,主动作为客户端遍历当前网段的所有IP,发起一路HTTP请求,向服务端注册;
  4. 扫描到设备后,通过在服务端/客户端之间的HTTP协议来传输文件。传输过程中,文件发送方为client;文件接收方为server。client发起一路post请求到服务器即可完成文件传输。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2394271.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VScode ios 模拟器安装cocoapods

使用 Homebrew 安装&#xff08;推荐&#xff09; 如果你有 Homebrew&#xff0c;直接用它安装更稳定&#xff1a; brew install cocoapods

Redis最佳实践——安全与稳定性保障之数据持久化详解

Redis 在电商应用的安全与稳定性保障之数据持久化全面详解 一、持久化机制深度解析 1. 持久化策略矩阵 策略触发方式数据完整性恢复速度适用场景RDB定时快照分钟级快容灾备份/快速恢复AOF实时追加日志秒级慢金融交易/订单关键操作混合模式RDBAOF同时启用秒级中等高安全要求场…

K 值选对,准确率翻倍:KNN 算法调参的黄金法则

目录 一、背景介绍 二、KNN 算法原理 2.1 核心思想 2.2 距离度量方法 2.3 算法流程 2.4算法结构&#xff1a; 三、KNN 算法代码实现 3.1 基于 Scikit-learn 的简单实现 3.2 手动实现 KNN&#xff08;自定义代码&#xff09; 四、K 值选择与可视化分析 4.1 K 值对分类…

技术栈ES的介绍和使用

目录 1. 全文搜索引擎&#xff08;Elastic Search&#xff09;的由来2. Elastic Search 概述2.1 Elastic Search 介绍2.2 Elastic Search 功能2.3 Elastic Search 特点 3. 安装 Elastic Search3.1 ES 的安装3.2 安装 kibana3.3 ES 客户端的安装 4. Elastic Search 基本概念4.1 …

Windows版本的postgres安装插件http

1、下载安装包 这里使用安装 pgsql-http 的扩展 源码地址&#xff1a;GitHub - pramsey/pgsql-http: HTTP client for PostgreSQL, retrieve a web page from inside the database. 编译的安装地址&#xff1a;http extension for windows updated to include PostgreSQL17 …

uni-app学习笔记十六-vue3页面生命周期(三)

uni-app官方文档页面生命周期部分位于页面 | uni-app官网。 本篇再介绍2个生命周期 1.onUnload&#xff1a;用于监听页面卸载。 当页面被关闭时&#xff0c;即页面的缓存被清掉时触发加载onUnload函数。 例如:在demo6页面点击跳转到demo4&#xff0c;在demo4页面回退不了到d…

优化的两极:凸优化与非凸优化的理论、应用与挑战

在机器学习、工程设计、经济决策等众多领域&#xff0c;优化问题无处不在。而在优化理论的世界里&#xff0c;凸优化与非凸优化如同两个截然不同的 “王国”&#xff0c;各自有着独特的规则、挑战和应用场景。今天&#xff0c;就让我们深入探索这两个优化领域的核心差异、算法特…

(五)MMA(OpenTelemetry/Rabbit MQ/ApiGateway/MongoDB)

文章目录 项目地址一、OpenTelemetry1.1 配置OpenTelemetry1. 服务添加2. 添加服务标识3. 添加请求的标识4. 添加中间价 二、Rabbit MQ2.1 配置Rabbit MQ1. docker-compose2. 添加Rabbit MQ的Connect String 2.2 替换成Rabbit MQ1. 安装所需要的包2. 使用 三、API Gateways3.1 …

TCP通信与MQTT协议的关系

1. MQTT 处理核心&#xff08;Mqtt_Pro&#xff09; void Mqtt_Pro(void) { MQTT_Init(); // 初始化MQTT协议栈&#xff08;连接参数、缓冲区等&#xff09; MQTT_SendPro(); // 处理MQTT发送&#xff08;封装消息&#xff0c;调用TCP发送&#xff09; MQTT_RecPro();…

Jetson Orin Nano - SONY imx415 camera驱动开发

目录 前言: 调试准备工作: 修改内核默认打印等级 一、imx415驱动开发 1、硬件接线 2、设备树修改 2.1 创建 tegra234-p3767-camera-p3768-imx415-C-4lane.dtsi 文件 2.2 tegra234-p3767-camera-p3768-imx415-C-4lane.dtsi 添加到设备树 2.3 编译设备树 3、imx415驱动…

word为跨页表格新加表头和表名

问题&#xff1a; 当表格过长需要跨页时&#xff08;如下图所示&#xff09;&#xff0c;某些格式要求需要转页接排加续表。 方法一&#xff1a; 1、选中表格&#xff0c;在“表布局”区域点开“自动调整”&#xff0c;选择“固定列宽”&#xff08;防止后续拆分表格后表格变…

测试用例篇章

本节概要&#xff1a; 测试⽤例的概念 设计测试⽤例的万能思路 设计测试⽤例的⽅法 一、测试用例 1.1 概念 什么是测试用例&#xff1f; 测试⽤例&#xff08;Test Case&#xff09;是为了实施测试⽽向被测试的系统提供的⼀组集合&#xff0c;这组集合包含&#xff1a;测…

2025年北京市职工职业技能大赛第六届信息通信行业网络安全技能大赛复赛CTF部分WP-哥斯拉流量分析

2025年北京市职工职业技能大赛第六届信息通信行业网络安全技能大赛复赛CTF部分WP-哥斯拉流量分析 一、流量分析 题目没有任何提示,附件gzl.pcap 解题哥斯拉流量300多KB包很多,没啥经验只能挨个看回来之后又狠狠得撸了一把哥斯拉流量分析我这里用的是哥斯拉4.0.1 测试链接…

Django ToDoWeb 服务

我们的任务是使用 Django 创建一个简单的 ToDo 应用程序,允许用户添加、查看和删除笔记。我们将通过设置 Django 项目、创建 Todo 模型、设计表单和视图来处理用户输入以及创建模板来显示任务来构建它。我们将逐步实现核心功能以有效地管理 todo 项。 Django ToDoWeb 服务 …

各种数据库,行式、列式、文档型、KV、时序、向量、图究竟怎么选?

慕然回首&#xff0c;发现这些年来涌现出了许多类型的数据库&#xff0c;今天抽空简单回顾一下&#xff0c;以便于后面用到时能快速选择。 1. 关系型数据库(行式) 关系型数据库&#xff08;RDBMS&#xff09;&#xff0c;我们常说的数据库就是指的关系型数据库。 它的全称是关…

全志科技携飞凌嵌入式T527核心板亮相OpenHarmony开发者大会

近日&#xff0c;OpenHarmony开发者大会2025&#xff08;OHDC.2025&#xff0c;以下简称“大会”&#xff09;在深圳举办&#xff0c;全志科技作为OpenHarmony生态的重要合作伙伴受邀参会&#xff0c;并进行了《全志科技行业智能芯片OpenHarmony方案适配与认证经验分享》的主题…

事件驱动架构入门

主要参考资料&#xff1a; 软件架构-事件驱动架构: https://blog.csdn.net/liuxinghao/article/details/113923639 目录 简介事件队列事件日志事件收集器响应队列读事件 vs. 写事件 简介 事件驱动架构是一种系统或组件之间通过发送事件和响应事件彼此交互的架构风格。当某个事…

基于Web的濒危野生动物保护信息管理系统设计(源码+定制+开发)濒危野生动物监测与保护平台开发 面向公众参与的野生动物保护与预警信息系统

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

leetcode hot100刷题日记——30.两数之和

解答&#xff1a; 方法一&#xff1a;迭代 迭代大致过程就是&#xff1a; 算两条链表的当前位的和&#xff0c;加上上一位留下来的进位&#xff0c;就是新链表的当前位的数字。计算当前的进位。 这样&#xff0c;我们迭代需要的东西是&#xff1a;链表1&#xff0c;链表2&…

Fastapi 学习使用

Fastapi 学习使用 Fastapi 可以用来快速搭建 Web 应用来进行接口的搭建。 参考文章&#xff1a;https://blog.csdn.net/liudadaxuexi/article/details/141062582 参考文章&#xff1a;https://blog.csdn.net/jcgeneral/article/details/146505880 参考文章&#xff1a;http…