【Flutter_Web】Flutter编译Web第一篇(插件篇):Flutter_web实现上传TOS上传资源,编写web插件

前言

由于Flutter在双端的开发体验几乎接近的情况下,尝试将Flutter代码转Web端进行部署和发布,在其中遇到的所有问题,我都会通过这种方式分享出来。那么第一个要解决的就是上传资源到TOS上,在双端中都是通过插件的方式在各端通过插件使用不同的SDK去解决上传问题,那么为了改动最小化,web端同样希望使用插件的方式去解决上传问题。

官方链接

tos使用Browser.js SDK去进行上传

https://www.volcengine.com/docs/6349/127737

这次使用的是普通上传的方式

https://www.volcengine.com/docs/6349/127739

分析

将过程进行拆解

流程分析

总流程
在这里插入图片描述

  • Flutter端通过请求获取临时密钥
  • 将密钥交付给web插件,web插件完成初始化工作
  • 用户触发文件选择,将文件交付给web插件
  • web插件获取到文件以及完成初始化工作之后,使用tos sdk发出请求
  • 将结果反馈给Flutter端,完成流程

问题分析

文件上传问题

在双端,可以通过文件选择器file_picker去触发系统的文件管理,这种方式可以获取到临时文件路径,当然也可以通过写入应用沙箱环境获取应用沙箱文件路径,将路径传递给插件处理即可,但是在web端,本质是处于浏览器环境,浏览器环境是不允许直接访问本地文件系统的,也就无法直接获取到任何路径。,我们使用原生html的input上传文件也可以进行验证,能够得到的是一个File对象,再者,web端不仅仅包括H5,还包括各家小程序,不同小程序提供的web环境,文件系统的管理是受各自小程序的管控的,因此这项工作应该交给web插件去处理,判断具体的环境情况,是获取File对象还是通过不同小程序提供的开放能力获取临时文件路径。

Dart和JS互相调用

Dart是强语言,JS是弱语言,没有类型的概念,那么我写了JS插件之后,如何在Dart层提前感知到相应的插件。
Flutter SDK在3.3.0之后,推出两个标准库去处理这样的问题。

JavaScript interop:基于扩展类型的新 JS 互操作机制,当针对 JavaScript 和 Wasm 时,可以在 Dart 代码、浏览器 API 和 JS 库之间进行简洁、类型安全的调用,Dart 开发人员可以访问类型化 API 来与 JavaScript 交互,API 通过静态强制明确定义了两种语言之间的边界,在编译之前消除了许多问题。
package:web:能够直接操作dom相关。

我们需要使用这两个库去解决问题。

编写插件

创建web插件模版

使用命令

flutter create --template=plugin --platforms=web .

这个时候可能会得到一个错误

Ambiguous organization in existing files: {com.example}. The --org command line argument must be specified to recreate project.

这个时候是因为当前的库有了其他端的测试用例,重新生成example文件夹即可。

这个时候会在lib目录下面生成一个文件。

根据个人习惯,我也阅读了其他插件,都会把这份文件放在lib的src目录下面。

配置pubspec.yaml文件

在dependencies下面加入:
同时web的依赖也要添加,便于我们后续操作dom

dependencies:
	、、、
  web: ^1.1.0
  flutter_web_plugins:
    sdk: flutter
   、、、

运行测试

我们在example文件夹下面点击lib的main.dart选择chrome进行运行即可。

选择文件

我们实际的逻辑就在生成的文件下进行编写。

我们首先解决选择文件的问题。

这里仅提供逻辑参考,假如在h5端,本质就是创建一个input元素,模拟点击即可。在小程序就使用其他端的sdk去选择文件,得到的是File对象,比如在小程序,就是wx.chooseMedia的方式直接获取到临时路径

//  wx.chooseMedia({
//   count: 9,
//   mediaType: ['image','video'],
//   sourceType: ['album', 'camera'],
//   maxDuration: 30,
//   camera: 'back',
//   success(res) {
//     console.log(res.tempFiles[0].tempFilePath)
//     console.log(res.tempFiles[0].size)
//   }
// })
  Future<dynamic> pickWebFile() async {
    // 判断是否在微信小程序环境
    if (isWeChatMiniProgram()) {
      // 微信小程序逻辑处理
      return await pickFileInWeChatMiniProgram();
    } else {
      // 非微信小程序环境下,使用浏览器的文件选择器
      final input = web.document.createElement('input') as web.HTMLInputElement;
      input.type = 'file'; // 设置为文件选择类型
      input.accept = '*/*'; // 可选:限制选择的文件类型,例如 'image/*''.txt'
      input.click();

      // 等待文件选择完成
      await input.onChange.first;

      if (input.files != null) {
        // 返回选中的第一个文件
        return input.files!;
      }
    }

    // 如果未选择文件或处理失败,返回 null
    return null;
  }

上传文件

JS插件

这里我是根据官方文档去编写的js代码

这里我创建了一个类

  • 提供一个初始化方法init函数
  • 提供获取当前web环境的函数
  • 提供一个上传文件的方法uploadFiles函数

class TosPluginJx {
    static instance; // 定义静态属性来保存单例实例

    constructor(client, dir, host) {
        this._client = client;
        this._dir = dir;
        this._host = host;
    }
    get host() {
        return this._host;
    }

    set host(host) {
        this._host = host;
    }

    get client() {
        return this._client;
    }

    set client(client) {
        this._client = client;
    }


    get dir() {
        return this._dir;
    }

    set dir(dir) {
        this._dir = dir;
    }

    static getInstance() {
        if (!TosPluginJx.instance) {
            TosPluginJx.instance = new TosPluginJx();
        }
        return TosPluginJx.instance;
    }


    checkOut(data) {
        if (!data.accessKeyId) {
            return false
        }
        if (!data.secretAccessKey) {
            return false
        }
        if (!data.sessionToken) {
            return false
        }
        if (!data.region) {
            return false
        }
        if (!data.bucket) {
            return false
        }
        if (!data.dir) {
            return false
        }
        if (!data.host) {
            return false
        }
        return true
    }

    init(data) {
        if (!this.checkOut(data)) {
            console.log("tos_sdk_初始化失败", data)
            return false
        }
        this.dir = data.dir
        this.host = data.host
        this.client = new TOS({
            // 从 STS 服务获取的临时访问密钥 AccessKeyId
            accessKeyId: data.accessKeyId,
            // 从 STS 服务获取的临时访问密钥 AccessKeySecret
            accessKeySecret: data.secretAccessKey,
            // 从 STS 服务获取的安全令牌 SessionToken
            stsToken: data.sessionToken,
            // 填写 Bucket 所在地域。以华北2(北京)为例,Region 填写为cn-beijing
            region: data.region,
            // 填写 Bucket 名称
            bucket: data.bucket,
        });
        console.log("tos_sdk_初始化完成", this.client, data)
        return true
    }


    //获取当前web环境
    getEnv() {
        if (window.WeixinJSBridge) {
            return 'wxMiniProgram'
        }
        return 'h5'
    }



    /// 上传文件

    //返回一个数组回去
    async uploadFiles(data) {
        try {
            console.log("开始上传", data)
            console.log("上传路径为", this.dir)
            if (data.length == 0) {
                console.log("当前文件为空")
                return [{
                    fileStr: '',
                    uuid: data[0].uuid,
                    downloadUrl: '',
                    isCompleted: true,
                    msg: '当前文件为空',
                    code: 0
                }];
            }
            if (data[0].fileStr) {
                console.log("当前为文件路径", data[0].fileStr)
                const result = await this.client.putObject({
                    key: this.dir + data[0].fileStr,
                    body: data[0].fileStr ? data[0].fileStr : data[0].fileBlob,
                    // headers,
                });
                console.log("上传结果", result)
                if (result.statusCode == 200) {
                    return [{
                        fileStr: '',
                        uuid: data[0].uuid,
                        downloadUrl: this.host + this.dir + data[0].fileStr,
                        isCompleted: true,
                        msg: '上传成功',
                        code: 1
                    }];
                }
            } else if (data[0].fileBlob) {
                console.log("当前为文件对象", data[0].fileBlob)
                const result = await this.client.putObject({
                    key: this.dir + data[0].fileBlob.name,
                    body: data[0].fileBlob,
                });
                console.log("上传结果", result)
                if (result.statusCode == 200) {
                    return [{
                        fileStr: '',
                        uuid: data[0].uuid,
                        downloadUrl: this.host + this.dir + data[0].fileBlob.name,
                        isCompleted: true,
                        msg: '上传成功',
                        code: 1
                    }];
                }
            }
            return [{
                fileStr: '',
                uuid: data[0].uuid,
                downloadUrl: '',
                isCompleted: true,
                msg: '上传失败',
                code: 0
            }];
        } catch (e) {
            console.log(e)
            return [{
                fileStr: '',
                uuid: data[0].uuid,
                isCompleted: true,
                downloadUrl: '',
                msg: '上传失败,' + e,
                code: 0
            }];
        }
    }


}

globalThis.TosPluginJx = TosPluginJx.getInstance(); //对外暴露单例,这里非常重要

Flutter端

在web编写插件,不像ios或者安卓,有channal管道的概念。

但是我们需要提前声明我们要使用的Js类以及函数以及我们需要使用的数据类型

这里我声明了我要使用的类TosPluginJx,里面有三个函数,分别接收什么类型的参数,返回什么类型的数据

我们使用的就是JavaScript interop提供的能力。

还记得我们上面暴露到全局的TosPluginJx吗,在这里,通过注解@JS去进行标注,告诉Flutter层在web环境中有这样的类。

在Js中类的本质就是一个object,将所有类型定义出来。


//声明tos要获取的数据类型
extension type TosInfoType._(JSObject _) implements JSObject {
  external JSString accessKeyId;
  external JSString secretAccessKey;
  external JSString sessionToken;
  external JSString host;
  external JSString region;
  external JSString endpoint;
  external JSString bucket;
  external JSString dir;

  //转换层
  factory TosInfoType.fromMap(Map param) {
    final obj = JSObject();
    return TosInfoType._(obj)
      ..accessKeyId = param['AccessKeyId']
      ..bucket = param['Bucket']
      ..dir = param['_dir_']
      ..endpoint = param['Endpoint']
      ..host = param['Host']
      ..region = param['Region']
      ..secretAccessKey = param['SecretAccessKey']
      ..sessionToken = param['SessionToken'];
  }
}

//声明要获取的文件类型
extension type FileInfoType._(JSObject _) implements JSObject {
  external JSString uuid;
  external JSString? fileStr;
  external JSAny? fileBlob;
  external JSAny? ext;
  external JSString? downloadUrl;
  external JSBoolean? isCompleted;
  external JSString? msg;
  external JSNumber? code;

  //转换层
  factory FileInfoType.fromMap(Map param) {
    final obj = JSObject();
    return FileInfoType._(obj)
      ..uuid = param['uuid']
      ..fileStr = param['fileStr']
      ..fileBlob = param['fileBlob']
      ..ext = param['ext']
      ..downloadUrl = param['downloadUrl']
      ..isCompleted = param['isCompleted']
      ..msg = param['msg']
      ..code = param['code'];
  }

  //转成map
  Map toMap() {
    return {
      'uuid': uuid,
      'fileStr': fileStr,
      'fileBlob': fileBlob,
      'ext': ext,
      'downloadUrl': downloadUrl,
      'isCompleted': isCompleted,
      'msg': msg,
      'code': code
    };
  }
}


//声明要操作的类
extension type TosPluginJx._(JSObject _) implements JSObject {
  external TosPluginJx();
  external JSBoolean init(TosInfoType tosInfo);
  external JSPromise<JSArray<FileInfoType>> uploadFiles(
      JSArray<FileInfoType> fileInfo);
  external JSString getEnv();
}

@JS('window.TosPluginJx') //标识全局对象
external TosPluginJx get tosPluginJx;

调用js sdk

在上面我们已经定义好了TosPluginJx类所涉及的所有需要使用的类型、方法、返回值。

现在我们可以直接进行使用。

需要注意一点,从js层拿到的是js类型,需要通过toDart进行转换

反之通过toJS

//初始化
tosPluginJx.init(info);

//获取环境
 String current = tosPluginJx.getEnv().toDart;

//上传文件
//将客户端传过来的参数转成js
   final jsFileArray =
         files.map((file) => FileInfoType.fromMap(file)).toList().toJS;
     print(jsFileArray);
     //传递给js插件层
     JSArray<FileInfoType> result =
         await tosPluginJx.uploadFiles(jsFileArray).toDart;
     //拿到结果再转换dart层
     List finl = result.toDart.map((e) => e.toMap()).toList();

打包测试

由于上传可能有tos的跨域问题,所以我们打包上传之后再进行传输。

执行命令

flutter build  web

注意:生成的index.html要把base的href进行修改,否则找不到资源

    <base href="">

结论

至此,我们完成Flutter_web插件的编写,Flutter提供了两个库去操作dom和js层,但我们的场景是需要使用第三方的js,因此又涉及到了dart层和js层互相调用的地方,两者在类型不同的情况下,如何做到类型兼容,下一篇将解决Flutter转web之后,webview是如何处理的,数据是如何交互的。

如果有更好的想法,欢迎提出。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

成都银泰生物科技有限责任公司简介

成都银泰生物科技有限责任公司成立于2014年&#xff0c;是一家专注于体外诊断产品销售和服务的公司。公司位于中国四川省成都市。其所售产品涵盖了生化、免疫、POCT、凝血、输血、血球、尿液、分子诊断、病理等多个技术平台。 成都银泰生物科技有限责任公司以“科技服务人类健…

【构建工具】现代开发的重要角色

你可能有所听闻构建工具&#xff0c;但是不知道是干什么的&#xff0c;或者是开发中用到了&#xff0c;大概会使用&#xff0c;但是想理解一下具体的工作原理等&#xff0c;那么我将分享一下我对其的理解。【 我将分为两篇来讲解】。 当我们谈到构建工具时&#xff0c;可以把它…

Spring 面试题整理

文章目录 一、控制反转 IoC什么是 Bean 和 Spring Bean&#xff1f;依赖注入的常见方式&#xff1f;Bean 的作用域有哪些&#xff1f;protype bean 里面的依赖是 singleton bean 的话&#xff0c;IoC 容器会怎么处理&#xff1f;Bean 的生命周期&#xff1f;Resource 和 Autowi…

Visual Studio 2022+CMake配置PCL1.14.1

前言 本教程只是提供高效的PCL配置流程&#xff0c;不提供Qt环境配置&#xff0c;如果需要GUI界面&#xff0c;则需要自寻查找Cmake配置QT的教程。请相信&#xff0c;在CMake之下没有任何事是困难的&#xff0c;最困难的工作已经由前辈们完成。因此&#xff0c;对于C用户来说学…

可视化数据

数据科学家会直观呈现数据&#xff0c;以更好地理解数据。 他们可以扫描原始数据、检查摘要度量值&#xff08;如平均值&#xff09;或绘制数据图表。 图表是一种可视化数据的强有力方式&#xff0c;数据科学家经常使用图表快速了解适度复杂的模式。 直观地表示数据 绘制图表…

【Linux网络编程】传输协议UDP

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站 &#x1f308;个人主页&#xff1a; 南桥几晴秋 &#x1f308;C专栏&#xff1a; 南桥谈C &#x1f308;C语言专栏&#xff1a; C语言学习系…

SEC_ASA 第二天作业

拓扑 按照拓扑图配置 NTP&#xff0c;Server端为 Outside路由器&#xff0c;Client端为 ASA&#xff0c;两个设备的 NTP传输使用MD5做校验。&#xff08;安全 V4 LAB考点&#xff09; 提示&#xff1a;Outside路由器作为 Server端要配置好正确的时间和时区&#xff0c;ASA防…

HTML5 拖拽 API 深度解析

一、HTML5 拖拽 API 深度解析 1.1 背景与发展 HTML5 的拖拽 API 是为了解决传统拖拽操作复杂而设计的。传统方法依赖鼠标事件和复杂的逻辑计算&#xff0c;而 HTML5 提供了标准化的拖拽事件和数据传递机制&#xff0c;使得开发者能够快速实现从一个元素拖拽到另一个元素的交互…

阿里云-通义灵码:测试与实例展示

目录 一.引子 二.例子 三.优点 四.其他优点 五.总结 一.引子 在软件开发的广袤天地中&#xff0c;阿里云通义灵码宛如一座蕴藏无尽智慧的宝库&#xff0c;等待着开发者们去深入挖掘和探索。当我们跨越了入门的门槛&#xff0c;真正开始使用通义灵码进行代码生成和开发工作…

第P2周:Pytorch实现CIFAR10彩色图片识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 目标 实现CIFAR-10的彩色图片识别实现比P1周更复杂一点的CNN网络 具体实现 &#xff08;一&#xff09;环境 语言环境&#xff1a;Python 3.10 编 译 器: …

【数字花园】数字花园(个人网站、博客)搭建经历汇总教程

目录 写在最最前面第一章&#xff1a;netlify免费搭建数字花园相关教程使用的平台步骤信息管理 第二章&#xff1a;本地部署数字花园数字花园网站本地手动部署方案1. 获取网站源码2.2 安装 Node.js 3. 项目部署3.1 安装项目依赖3.2 构建项目3.3 启动http服务器 4. 本地预览5. 在…

Hadoop一课一得

Hadoop作为大数据时代的奠基技术之一&#xff0c;自问世以来就深刻改变了海量数据存储与处理的方式。本文将带您深入了解Hadoop&#xff0c;从其起源、核心架构、关键组件&#xff0c;到典型应用场景&#xff0c;并结合代码示例和图示&#xff0c;帮助您更好地掌握Hadoop的实战…

使用 GD32F470ZGT6,手写 I2C 的实现

我的代码&#xff1a;https://gitee.com/a1422749310/gd32_-official_-code I2C 具体代码位置&#xff1a;https://gitee.com/a1422749310/gd32_-official_-code/blob/master/Hardware/i2c/i2c.c 黑马 - I2C原理 官方 - IIC 协议介绍 个人学习过程中的理解&#xff0c;有错误&…

WPF Prism ViewInjection

ViewInjection介绍 ViewInjection是Prism框架提供的一种机制&#xff0c;用于将视图动态地注入到指定的容器&#xff08;Region&#xff09;中。这种注入方式允许你在运行时动态地添加、移除或替换视图&#xff0c;从而实现更灵活的用户界面设计。 ViewInjection示例 GitHub…

软考高级架构 - 11.1- 信息物理系统CPS

信息物理系统CPS 信息物理系统(CPS)是控制系统、嵌入式系统的扩展与延伸。通过集成先进的感知、计算、通信、控制等信息技术和自动控制技&#xff0c;构建了物理空间与信息空间中人、机、物、环境、信息等要素相互映射、适时交互、高效协同的夏杂系统。 CPS的本质是基于…

后端开发工程师需要掌握哪些设计模式?

大家好&#xff0c;我是袁庭新。 作为后端开发者&#xff0c;学习和掌握设计模式是非常有必要的。不仅可以帮助后端开发者更好地设计和实现软件架构&#xff0c;还可以提高代码的质量和可维护性。此外&#xff0c;设计模式也是后端开发面试中常见的考点之一&#xff0c;掌握它…

【Android Studio】学习——数据存储管理

AndroidStudio实验——数据存储管理 文章目录 AndroidStudio实验——数据存储管理[toc]一&#xff1a;实验目标和实验内容&#xff1a;二&#xff1a;数据库的CRUD操作【一】创建&#xff08;Create&#xff09;【2】读取&#xff08;Read&#xff09;【3】更新&#xff08;Upd…

科研绘图系列:R语言绘制热图和散点图以及箱线图(pheatmap, scatterplot boxplot)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载图1图2图3系统信息参考介绍 R语言绘制热图和散点图以及箱线图(pheatmap, scatterplot & boxplot) 加载R包 library(magrittr) library(dplyr) library(ve…

【Qt】信号、槽

目录 一、信号和槽的基本概念 二、connect函数&#xff1a;关联信号和槽 三、自定义信号和槽 1.自定义槽函数 2.自定义信号函数 例子&#xff1a; 四、带参的信号和槽 例子&#xff1a; 五、Q_OBJECT宏 六、断开信号和槽的连接 例子&#xff1a; 一、信号和槽的基本…

一种构建网络安全知识图谱的实用方法

文章主要工作 论述了构建网络安全知识库的三个步骤&#xff0c;并提出了一个构建网络安全知识库的框架;讨论网络安全知识的推演 1.框架设计 总体知识图谱框架如图1所示&#xff0c;其包括数据源&#xff08;结构化数据和非结构化数据&#xff09;、信息抽取及本体构建、网络…