V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
MegatronKing
V2EX  ›  程序员

Reqable 项目技术栈全方面总结,欢迎来理性讨论

  •  7
     
  •   MegatronKing · 233 天前 · 1820 次点击
    这是一个创建于 233 天前的主题,其中的信息可能已经有所发展或是发生改变。

    大家好,最近有知乎网友问我 Reqable 技术选型的问题,恰好 Reqable 也刚刚发布了非常重要的 1.3 版本更新,所以此次写一篇关于 Reqable 项目技术栈的全方面总结。

    本篇文章的目的,是向大家分享我关于 Reqable 项目的一些技术思考、细节和填坑概要等。希望能够对大家有所帮助、避坑、少走一些弯路、更快交付。有必要说明的是,Reqable 项目完全由我个人独立完成,受限于本人经验和能力,肯定有非常多的不足。如果你对本篇总结中提到的技术选型、处理方式等有不同的见解,欢迎提交评论,一起讨论,共同学习。

    封面.jpg

    如果有不了解 Reqable 的,可以看上面这张图,官网首页: https://reqable.com

    下面,话不多说,正文开始。

    1. 客户端

    Reqable 客户端是基于 Flutter 和 C++开发的,由于整体不涉及非常复杂的层次架构,所以我就不放 Arch 图了。其实简而言之,除了网络流量分析框架和 API 请求框架,这两个模块是使用 C++语言开发外,其他几乎都是使用 Dart 语言开发。下面,我们来依次进行介绍。

    1.1 网络流量分析模块

    这个模块内部的代号是Netbare,如果你查看 Reqable 解压的运行库,应该能看到这个。Netbare从项目初始就确定了使用 C++语言进行开发,最核心的考量标准就是性能。Netbare的职责包含下面几个:

    • 建立代理服务器
    • 分析入流量,解析代理协议
    • SSL 证书重签和握手(MITM
    • 中间人客户端
    • 中间人服务器
    • HTTP 协议解析和封包
    • 插件和拦截器处理

    从上面的职责可以看到,Netbare既承担了服务器的角色,又承担了客户端的角色。这对性能其实是有一定要求的,也就是说多线程是非常有必要的。又由于 Reqable 是跨平台的项目,C 和 C++成了首要选择语言,Java 、Javascript 、Dart 、C#、Python 等基本可以不考虑,Dart 又是单线程机制,Rust 也是一个可选项,但是这个语言我完全不熟悉。在我的技术栈里面,C++以绝对的优势胜出,恰好最近几年内我写了大量 C++的代码,手还热乎着。

    确定 C++作为Netbare开发语言后,网络框架库就非常自然地选择了asio。但是我并不想引入Boost这个庞大的库,所以选择了独立版本的asio,然后 SSL 选择了openssl,最后是格式化 io 库fmt。除此之外,虽然 HTTP2 协议规范是由Netbare自己实现的,但还是引入了nghttp2作为辅助。这几个开源项目都是作为 git submodule 引入的,非常稳定,我没有修改过任何源码,在此向这些伟大的开源项目致敬。

    Netbare采用和我曾经开源的NetBare-Android项目类似的架构设计,支持拦截器机制,但是简化和优化了非常多。C++同样是面向对象的语言,我保留了 HTTP3 ( QUIC )的扩展接口,方便我后面可以很快地接入。Netbare提供了一套非常简单的 API ,简单接入便可以单独编译出可执行文件直接运行,具备完整的抓包功能。事实上,大多数网络协议相关的调试分析我都是直接在Netbare项目中进行的。

    Netbare使用 C++ 11 标准,Cmake作为编译组织脚本,目前支持在 Windows 、Mac 和 Linux 平台下 ARM 和 x86 编译。目前还没有支持 Android 和 iOS 两个平台的编译,但后面很快会支持。

    光有Netbare库还无法直接在 Reqable 客户端中使用,还需要考虑接入的问题。尽管Netbare提供了一套 C 的 API 接口,但由于客户端主体使用Flutter框架( Dart 语言)开发,这里就涉及到跨语言调用的问题。为了给客户端业务逻辑提供更好的支持,我又额外提供了一套Dart的接口:

    截屏 2023-09-07 20.32.29.png

    换句话说,就是另外创建了一个Dart模块项目,接入了Netbare的 C++库,并提供了纯Dart语言的接口供客户端调用,在客户端项目中只需要关注Dart的 API 即可。从上面的图可以看出,无论是 C++接口还是 Dart 接口的调用方式都是基本一致的。

    1.2 Rest API 请求模块

    由于 Reqable 确定要支持 HTTP3 ( QUIC )协议,根据我之前的项目经验,当时可靠的方案有两个:curl vs Cronet,最终我选择了Cronet ,下面分别来说下两个方案的优劣。

    curl是一个非常老牌的网络库了,其已经内置到大部分系统中,例如 MacOS 、Linux 等。非常多常用的软件也都是基于 curl 发送网络请求,例如GitUnity等等。另外,curl很早就开始支持 HTTP3 的草案版本了,但是至今仍然处于实验功能阶段,详见这里。我曾经在 2021 年,编译并测试过 curl 的 HTTP3 协议版本,测试结果让我迅速放弃了它。

    Cronetchromium项目的一个子模块,源码详见这里。Cronet 本身并不是 HTTP 众多版本协议的实现者(真正的实现在这个net目录),只是对net目录代码封装,而net才是整个chromium的网络核心。当然,Cronet 的好处是作为一个模块,可以编译出单独的模块制品,另外 API 设计得也更加地简单。据我之前了解,B 站、字节系等相关产品都采用了 Cronet 的方案,但大多数是移动端。也许是因为 Google 本身的大量移动端产品使用了此方案,另外,Cronet 还提供了 Java 等移动端语言的接口和成品库。

    更多的实现细节,我都在这篇中详细地讲解了,有兴趣可以看看: https://juejin.cn/post/7254076875780620325

    1.3 Dart & Flutter

    Reqable 客户端超过 80%的代码都是通过Dart编写的,包含 UI 、UX 以及业务逻辑,另外还有一些 Flutter 插件,用于与 Native 平台进行相互调用。

    我为什么选用Flutter呢?

    很多小伙伴会觉得Electron更成熟、生态更完善、开发效率更高,而在 2022 年初,Flutter桌面端正式版本都没有发布呢!当然,这些都毫无疑问是正确的、无懈可击的。但是,我还是有一些不同角度的看法。

    首先,Reqable 的目标是支持桌面端+移动端,覆盖全部的终端设备平台。Electron能够很好地解决桌面端的需求,但是解决不了移动端,这是一个致命的影响决策的因素。QT也是一样还要收费,而像Tauri等又小众了一些。

    其次,性能考虑。Electron的性能声名(狼藉)在外,大多数技术员可能都知道这个,不是所有基于Electron的应用都能够像VS Code那样去做特定优化,Postman就是一个反面例子。另一个糟心的就是安装包大小和存储空间,以 Mac 为例,如果一个应用安装包体积超过了 100M ,十有八九是基于Electron,安装后更大,像企 x 微 x 能够占用用到上 G 的空间,而Flutter安装包只有 20M 左右、甚至更低。Reqable 是面向技术人员的产品,我相信没有多少技术人员喜欢在自己电脑里面安装一堆浏览器内核。猜一猜下图里面(本人开发机器)装有多少个浏览器?

    截屏 2023-09-07 20.41.29.jpg

    当然,完全从性能考虑,QT应该是最优项,但是 C++的开发效率又太低了,此外在没有设计伙伴支持的情况下 UI/UX 也很难做出赏心悦目的效果。

    Flutter是一个非常新的框架,至少在桌面端如是,不过基本功能都是相对齐全的,此外大多数不支持的特性都可以通过插件机制来解决。Flutter的插件机制是一个非常好的设计,支持与 Native 语言交互从而可以调用系统的 API 接口,这个几乎可以解决所有的问题。

    另外,作为一个技术人员,我相信新的技术是驱动这个社会进步的动力,相比于沉湎在旧技术的舒适区,拥抱一个全新的技术会带来更大的乐趣,挑战与机遇并存。

    选择Flutter就意味着我选择了Dart,在语言层面,这并没有太大的障碍。在我当时已有的技术栈里面,Java 、C/C++、C#、Python 、JavaScript 、Kotlin 和 Groovy 等我都比较熟悉,也都有过项目实践。所以,在看了几天Dart的官方文档后,便正式开始Flutter之旅了。

    1.3.1 Flutter 状态管理

    熟悉Flutter的同学,应该都能够理解状态管理这个概念。Flutter并不像 Android 、iOS 、Unity 或者前端等支持分离式布局,无论是布局、数据还是逻辑都是在Dart源文件中直接编写,这也是目前非常多 UI 框架的采用的方式。众所周知,布局、数据还是逻辑肯定不应该写在同一个Dart源文件中。MVC 也好,MVVM 也罢,在Flutter中肯定也是需要一种类似的分离布局、数据和逻辑的框架,在Flutter中这个叫做状态管理框架,大概是因为Widget分为有状态和无状态两种。

    Reqable 的定位是中大型 Flutter 项目,选择合适的状态管理框架非常地重要。项目稳定后,推翻这个框架的成本是绝对无法承受的。当时可选的大概就是下面这些:

    • GetX
    • Provider
    • BLoC
    • Riverpod

    我的需求是找一个轻量级的状态管理框架,适合大型项目、利于维护、方便定制扩展。结论是 Reqable 选择了BLoC

    首先我排除掉了GetX,当时简单看了下,第一感觉这是一个非常重的框架,什么都有,像一个杂货店;无法想象在 Flutter 频繁更新的状态下这个框架的稳定性会怎么样,如果停止维护了又是一番什么样的景象。Provider又太简单了些,无法应对复杂的场景。Riverpod很不错,成熟度差一些。BLoC的功能很完善,代码简单、稳定性和成熟度也很高,是一个非常契合 Reqable 项目的选择。

    基于BLoC,Reqable 主要业务逻辑主要分为了三个部分,实现了 MVC 的基本理念:

    Reqable
       ├── bloc  // 逻辑层
       ├── ui    // UI 层
       └── model // 数据层
    

    当然BLoC并不是完美的,Provider + Builder 的编写方式,需要写大量的模板代码,开发效率并不高。适当地基于BLoC再包装一层代码非常有必要,这也是 Reqable 实际的处理方式。

    1.3.2 组件通信机制

    除了状态管理,另一个非常重要的机制就是组件通信。例如当底部栏左下角点击侧边栏图标后,侧边栏状态需要展开,当侧边栏手动关闭后,底部栏左下角图标需要变为闭合状态。这里就涉及到两个逻辑控制器( Bloc )之间的通信了。

    Reqable 基于BLoC框架设计了ReqableBus,实现不过超过 30 行代码,这完全得益于BLoC也是基于 Event 驱动的,可以直接复用此机制。下面是一个非常简单的ReqableBus使用示例:

    /// 事件接受方
    class SidebarBloc extends ReqableBloc<SidebarState> {
        
        SidebarBloc(super.state) {
            on<SidebarOpenEvent>((event, emit) {
              emit(newState);
            });
        }
      
    }
    
    /// 事件发送方
    class BottombarBloc extends ReqableBloc<BottomState> {
        
        void openSidebar() {
            // 发送事件
            ReqableBus.post(const SidebarOpenEvent());
        }
      
    }
    

    熟悉BLoC框架的应该能看出,上图的写法基本上是完全复用了其事件机制,达到了完美的统一。

    1.3.3 代码编辑器

    Reqable 作为一款对标 Postman + Fiddler/Charles 的 API 开发和调试工具,如果没有一个顺手的代码和文本编辑器组件,那么口碑必然要大打折扣。

    从功能上来讲,API 开发过程中常用的 JSON 、XML 等编辑的需求可以说是最基本的要求了,更何况还有 Python 脚本的编写,以及 HTML 、JS 、CSS 等数据格式的显示了。例如,下图 JSON 数据的编辑,可以说是 API 工具最基本的功能了。

    screenshot_01.png

    基于 Electron 开发的应用,编码编辑器基本上都是选用的开源的Monaco Editor,但是在 Flutter 下并没有可用的开源项目。所以我觉得自己从零开始实现一个轻量级的代码编辑器,用于处理文本、代码和常见类型数据的显示和编辑。

    这部分是 Reqable 项目中耗费非常多时间,也是难度很大的一个模块,关于更多的细节和详情,请看我之前写的一篇专题文章: https://juejin.cn/post/7246672925666885689

    1.3.4 数据库

    由于没有加密的需求,再从性能和开发效率等方面考虑,Reqable 选用了ObjectBox。我在多年前还在做 Android 的时候,就对这个数据库所有耳闻,现在终于正式用上了。作为新兴的数据存储框架,ObjectBox的接入效率是极高的,文档齐全,另外对于数据库更新的处理也是非常傻瓜。可能唯一的缺点,就是添加或修改表格字段后,无法再安装回历史版本了。

    最后一个需要注意的是,ObjectBox的默认存储容量上限是1G,超过这个容量,数据库操作就会报错。我们可以通过手动设置更高的上限来避免这个问题。

    1.3.5 桌面端支持

    Flutter的默认桌面端代码模板以及官方支持都存在较多的问题,作为一个追求用户体验完美的项目,Reqable 优化了很多方面。

    首先是标题栏。MacOS 需要在xib中移除默认的标题栏,这个非常简单; Windows 也需要移除默认的标题栏,因为默认的标题栏无法切换暗色和亮色,这是非常棘手的一个问题,有一个知名的库bitsdojo_window可以实现,但存在稳定性和一些其他问题,最后放弃没有使用,然后是自己通过调用 Windows API 代码实现了,但也不完美,比如窗口顶部无法 Resize ; Linux 的标题栏无法移除(也许有,但我没有找到方式),同时高度也存在问题,优化也并不复杂。

    其次是多窗口。同样也有一个不错的开源项目desktop_multi_window,问题有点多,包括上面的标题栏等问题。Reqable 克隆了上面的仓库,进行了大刀阔斧的优化。不管怎样,仍然非常感谢这个库的开源,提供了非常大的帮助。多窗口下面一个非常重要的功能就是多窗口通信,比如用户在设置窗口中切换主题模式(暗色和亮色),需要应用到全部已打开的窗口包括主窗口。这是一个相对复杂的状态管理,Reqable 定义了一个名为ReqableCrossWindowBloc的状态管理父类,自动订阅跨窗口的消息并自动更新状态。

    生命周期问题。由于 Reqable 需要支持在后台工作,例如当用户关闭 Reqable 窗口或者进入了后台,在使用其他应用的时候,Reqable 要仍然能够进行调试。当点击桌面图标后,可以还原之前的 Reqable 窗口而不是新开一个程序进程。无论是 Windows 、Mac 还是 Linux ,都需要进行一些特定平台的功能开发。

    托盘支持。既然支持后台模式,那就非常有必要提供一个托盘功能,可以通过托盘进行一些快捷操作而不需要打开主窗口,或是通过托盘恢复主窗口。Reqable 采用的是开源的tray_manager,一样有些小问题,需要定制优化。

    应用关联支持。这个功能是非常重要的一个交互,例如我们可以直接通过双击视频文件,自动打开播放器进行播放。对于 Reqable ,用户能够通过HAR文件格式关联到 Reqable ,并打开查看详情内容。这个同样需要进行一些特定平台的功能开发。

    文件拖拽支持。Reqable 支持拖拽一个HAR文件到主窗口释放来打开这个文件,同样的也支持拖拽任何文件并设置给一个 API 作为 Body 。Reqable 使用了开源项目desktop_drop,但是在多窗口模式下,支持并不是很好,需要做一些简单的修复。

    应用菜单栏支持。在 Mac 系统上面,应用菜单栏是非常常见的功能,当然在 Windows 和 Linux 上面也有,但是系统原生的现在已经不多见了。我调研了 Flutter 官方出品的插件menubar,但效果并不好。在 MacOS 上,我彻底改造了前面所说的官方插件并利用了一些小 Trick 达到了现在的效果;在 Windows 和 Linux 上则彻底放弃了原生的应用菜单栏,使用 Flutter 实现了一套自己的应用菜单栏。顺便提一下,后来 Flutter 框架中正式加入了控制 Mac 系统菜单栏的 API ,但是效果依然不完美。

    右键菜单栏支持。不同的系统都有各自的右键原生菜单栏样式,在开发 Reqable 的早期阶段,我就纠结过这个问题,是调用系统原生的菜单栏还是在基于Flutter实现一套统一风格的。多次尝试后,我放弃了使用系统原生的菜单栏的方案,确定了现在的这种菜单栏样式,原因就是稳定可控,方便定制。

    截屏 2023-09-07 21.58.38.png

    1.3.6 脚本支持

    脚本功能是 Reqable 非常重要的特性之一,可以给予开发者极大的自由度,也将是后面规划中的插件功能的基石之一。Postman等使用JavaScript作为脚本扩展,但我觉得Python作为脚本而言可能更好一些。使用 Reqable 非常重要的一个群体就是测试人员,Python往往测试人员的首选语言,所以我决定选用Python作为 Reqable 的脚本语言。

    screenshot_06.png

    因此,我需要提供一套丰富的Python API作为支持,通过import reqable引入调用。由于时间关系,尚未来得及公开脚本框架代码(主要是不想写文档的懒),后面还要抽空处理下。

    1.3.7 更多组件

    Reqable 项目整体采用了组件化的模式,很多功能都拆分成了模块。由于 Flutter 生态不成熟的的问题,非常多的模块功能都需要 Reqable 自行实现,因此我借鉴了一些开源项目,在此对这些项目作者表示感谢!下面简单列了一些模块,供大家参考。

    • HexViewer ,这个是在前面所讲的编辑器的基础上修改实现的,作为了一个单独模块,并没有合并到编辑器模块中。
    • HAR 支持,参考了 HAR 的文件规范,自行实现了 HAR 文件的解析和编码。
    • 文件类型检测,基于file-type实现了一套 Dart 语言的版本。
    • curl 支持,参考 curl 官方文档,自行实现了 cURL 命令的解析和生成。
    • 代码生成,基于httpsnippet实现了一套 Dart 语言的版本。

    1.3.8 组件化管理

    组件化带来的一个问题,就是如何管理这些组件,例如在编译前同步所有的组件版本等等。下图是 Reqable 这个项目的组件,其中绝大多数都是客户端的模块(稍微有点乱,需要进行整理,勿笑)。

    截屏 2023-09-07 22.15.51.png

    有的组件是纯Dart库,有的组件是包含 C++代码的,不同的组件类型带来了一些管理复杂度。Reqable 采用的方式是,C++模块单独预先编译(部分需要交叉编译,部分需要在特定平台编译)并发布到线上制品库,组件同步时从线上制品库进行更新。细节部分很多,由于涉及较多项目内部的逻辑,无法深入讲解。组件管理的控制流程,通过 Python 脚本来实现。


    以上就是 Reqable 客户端的部分,在实际的实践过程中还有更多的问题,有的已经解决了,有的还没有处理。跨平台项目如果追求精益求精和完美体验,需要在非常多的细节上面花费时间和精力。非常抱歉这部分无法深入分享,也无法面面俱到,大家只能管中窥豹了,还敬请见谅。

    2. 前端

    Reqable 的前端包含两个域:reqable.com官网主站和license.reqable.com许可证管理中心,采用了完全不同的技术方案。

    2.1 官网

    官网采用的是Docusaurus框架,开发语言是ReactJS,支持中文和英文两种语言,支持亮色和暗色两种主题。下图,是暗色主题的中文官网主站。

    截屏 2023-09-07 22.30.09.png

    官网由一些独立页面 + 文档 + 博客三部分组成,Docusaurus对此提供了非常完善的支持。其中,独立页面(包含主页)都是通过ReactJS编写,文档和博客则通过MarkdownMDX语言编写。Docusaurus框架本身包含前端和后端两部分,支持编写时动态更新,可以非常方便地进行开发。开发完成后,也可以直接本地部署预览效果。

    2.1.1 关于 Docusaurus

    这里讲讲关于前端网站框架的选型,由于我没有多少网站的开发经验,这个领域涉足很浅。很久之前接触过showdocGitbook,但或多或少都有点不满意。后来有个同事推荐了我Docusaurus,第一个感觉就是:这就是我想要的。DocusaurusFacebook的开源项目,算是后起之秀,文档完整,设计也好看,加上有非常多的Showcase可供参考(抄代码),对于非专业前端开发来讲是一个非常适合的框架。

    当然,仅仅依靠Docusaurus肯定做不出现在的效果。我还是使用ReactJS + css实现了主要的交互功能,包括瀑布流、跑马灯效果等,不专业就不多讲了,以免贻笑大方。

    2.1.2 文档

    前面提到文档和博客是通过MarkdownMDX语言编写。Markdown大家应该都了解,不多讲,就简单说一说MDXMDX其实就是允许在Markdown中使用 JSX 代码,更多信息可以查看此网站: https://www.mdxjs.cn/ 。Reqable 编写了一些简单小组件并应用到了MDX中,例如文档中的快捷键内容平台自适应。在 MacOS 上,一般使用 Command 键而不是 Control 键,Windows 和 Linux 都是使用 Control 键,所以非常有必要让 MacOS 的用户看到的快捷键内容是 Command ,例如Command + B,而 Windows 和 Linux 用户看到的是Control + B

    顺便说一句,Reqable 官网的文档部分是公开托管的,如果有想学习或者有兴趣想帮助完善的,可以看看: https://github.com/reqable/reqable-docs

    2.1.3 多语言支持

    Reqable 支持两种语言:中文和英文,无论是网站还是客户端程序,都已经支持了。Docusaurus框架提供了基本的i18n支持,但好像不包括资源文件(官网中文语言下图片还是英文版本的)。遇到的最大的问题还是部署问题,我希望根据浏览器的语言,自动切换到英文或者是中文。从技术方面讲,就是浏览器打开https://reqable.com的时候根据语言自动重定向到https://reqable.com/zh-CN或者https://reqable.com/en-US,语言的检测可以通过请求头中的Accept-Language的值来确定。

    很多浏览器下Accept-Language的值并不是单纯的zh-CN或者en-US,而是一个权重配置。一开始的时候,我尝试使用nginx的反向代理解析Accept-Language并通过rewrite机制重定向,出现的问题是nginx虽然能提取出 locale 但是无法比较权重。最后的一个解决方案是,起了一个专门解析Accept-Language的 server 服务,从代码层面去判断语言并返回重定向的 Location 。

    2.1.4 SEO

    关于搜索引擎优化。Docusaurus支持配置Metadata,但貌似不支持国际化。我还是希望在不同的语言下使用本地语言的搜索关键字,Reqable 最后采用了在代码中配置的方案,因为在代码中可以很好地处理i18n的问题。

    2.2 许可证管理中心

    许可证管理是通过Flutter框架开发的。由于涉及到下单、支付、登录、许可证操作等强交互性功能,极少静态页面,所以我选择了 Flutter 作为开发框架,真正地将 Flutter 框架应用到 Reqable 项目的全平台上。

    使用 Flutter 开发 Web 和桌面端移动端的方式并没有什么太大的区别,也许这就是 Flutter 独特的魅力之一。和桌面端一样,Web 端仍然有一些问题需要我们自己解决,下面我分享下我遇到的问题和解决思路。

    2.2.1 跨域问题

    这可能是非专业前端人员遇到的第一个难题,新手村 Boss 。最常见的就是发送了一个 POST 请求,然后失败了,不知道什么原因。当然,不出意外地我也遇到了,解决起来也很顺利。之前写了一篇文章,专门讲这个: https://juejin.cn/post/7247057861128486968

    2.2.2 字体问题

    Flutter 开发的 Web 最致命的问题,就是加载慢,而加载慢主要就是因为有几个非常大的文件需要下载,字体文件是其中之一。出于安全原因WebAssembly无法使用系统字体,Flutter 只能自己从 Goolge 的字体网站上下载字体文件,包含中文的思源字体大约有 7-8M ,下载下来可想而知有多慢。Reqable 的解决方案,是将字体放到本地 assets 并进行裁剪(即移除掉生僻字),裁剪后的字体大小大约是 900k 左右。

    接下来,代码中再配置下使用本地字体不从 Google 字体网站下载。

    MaterialApp(
        title: 'Reqable',
        theme: ThemeData(
            fontFamilyFallback: const ['Noto Sans SC'],
        ),
    );
    

    2.2.3 CanvasKit 问题

    CanvasKit 也是导致 Flutter Web 加载慢的元凶,wasm好几 M 的文件大小下载就是慢,即使放到本地开了CDN也感觉慢。没太好的解决方案,换回了 html 渲染--web-renderer=html,副作用就是渲染效果差了,文字排版感觉有很大的问题,但也比加载半天才出来体验好些。

    2.2.4 加载进度条

    这个问题出现在选用 CanvasKit 作为渲染方式的时候。知乎上RustDesk的作者建议我加个 Loading ,从效果上来说,确实比白屏好多了,感谢这个建议。在index.html文件里,加了一些代码就可以实现,有需求可以简单参考下:

    <head>
       <style>
        .container {
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
        }
    
        .loading {
          width: 40px;
          height: 40px;
          border: 4px solid #f3f3f3;
          border-top: 4px solid #FCB334;
          border-radius: 50%;
          animation: spin 1s infinite linear;
          margin: 0 auto;
        }
    
        @keyframes spin {
          0% {
            transform: rotate(0deg);
          }
          100% {
            transform: rotate(360deg);
          }
        }
      </style>
      <body>
      <div class="container">
        <div id="loading" class="loading"></div>
      </div>
      <script>
        window.flutterConfiguration = {
          canvasKitBaseUrl: "/canvaskit/"
        };
      </script>
      <script>
        document.getElementById("loading").style.display = "block";
        window.addEventListener('load', function(ev) {
          // Download main.dart.js
          _flutter.loader.loadEntrypoint({
            serviceWorker: {
              serviceWorkerVersion: serviceWorkerVersion,
            },
            onEntrypointLoaded: function(engineInitializer) {
              engineInitializer.initializeEngine().then(function(appRunner) {
                document.getElementById("loading").style.display = "none";
                appRunner.runApp();
              });
            }
          });
        });
      </script>
    </body>
    

    2.2.5 部署&路由问题

    这个问题在开发模式下不会遇到,但是部署上线后会出现,表现就是在某个子页面刷新下浏览器,自动跳回首页了。这个问题通过修改服务器网页托管的代码完成修复。

    在这里,我顺便说下服务端部署的问题。通过下面的命令,可以编译出 Web 端的制品:

    flutter build web --release
    

    然而 Flutter 并不负责服务端的网页部署,因此需要编写一个服务端程序。我选择了Python,原因就是因为简单,下面是服务端程序的脚本示例代码:

    from http.server import SimpleHTTPRequestHandler
    import socketserver
    from urllib.parse import urlparse
    
    PORT = 3000
    
    class MyRequestHandler(SimpleHTTPRequestHandler):
      def do_GET(self):
        print(self.path)
        uri = urlparse(self.path)
        # 解决路由跳转问题的关键逻辑
        if '.' not in uri.path:
            self.path = '/'
        return SimpleHTTPRequestHandler.do_GET(self)
    
    server = socketserver.TCPServer(('', PORT), MyRequestHandler)
    print("Serving at PORT : " + str(PORT))
    server.serve_forever()
    

    2.2.6 支付业务逻辑

    前面的问题相比这个都是毛毛雨,耗费时间最多的就是接支付了。归根到底,还是 Flutter 的生态不够成熟导致,无论是国内的AliPay还是国外的Paypal,都未提供Dart语言的 SDK ,意味着我只能通过REST API的方式进行调用、然后自己排查错误。用一句话来形容这个体验,就是文档都快被翻烂了。这本身不是什么问题,但是对于 Flutter Web 来讲,可能就是最大的问题了。

    3. 服务端

    终于到了本篇总结的最后一个大章节——服务端,服务端内容包括网络服务、数据库、部署,简单运维等。对于 Reqable 这个项目而言,服务端是最不重要的部分,所以也没有花什么精力,就简单说一说了。

    3.1 后端服务

    对于后端来讲,我的经验也不多,写过 Java 、Python 和 NodeJS 这三样,但也是多年前的事情了。可是,我最后的技术选型放弃这老三样,投入到了Dart的怀抱。桌面端 + 移动端 + Web 端都已经拥抱 Flutter 了,为什么服务端不用Dart呢,真正来个All in Dart

    3.1.1 网络服务框架

    网络服务框架我选择了shelf,毕竟是Dart官方出品的。可能是现在大部分网络服务框架设计模式都差不太多,shelf几乎不需要花时间去上手,和NodeJSPython下的一些框架的使用方式非常相像。shelf提供了基本的拦截器功能,可以非常方便地处理CORS和日志记录等中间件功能问题。

    3.1.2 数据库

    数据库采用Mysql,原因就是有历史使用经验,节省时间。如果换个场景,Reqable 项目要做云服务的话,我基本上不会选择这个方案。主要是原因还是Dart的生态问题,换句话说就是没有成熟稳定的的 Dart 语言库来支持MysqlMongodb可能还要稍微好一点。

    还有一点就是关于ORM框架,也没有什么成熟的方案。虽然 Reqable 的服务端逻辑很简单,但是数据库的开发体验也是相当糟糕。

    关于数据库的 GUI 操作软件,Mac 上我强烈推荐Sequel-Ace,很好用。之前都是用Sequel Pro,长久不更新各种闪退之后,换成了Sequel-Ace,两者使用几乎一样。

    最后一点,提一下Mysql的 2038 年时间问题。发现这个问题起因是这样的,Reqable 有用户续费到 2038 年失败了,用户以为我做了限制反馈了给我,事实上我没有限制纯粹是Mysql的 2038 年时间这个 bug 。这个 bug 是因为Mysql使用 32 位整数来表示 Unix 时间戳,到 2038 年就溢出了。这个 bug 至今我还没有修复,有兴趣或者不相信的可以亲自去测试下。

    3.1.3 邮件推送

    Reqable 采用了阿里云的邮件推送服务,到达率还不错。刚开始接入时,每天有 200 份邮件发送的免费额度,但是前几天通知我9 月 1 日起免费额度改成总上限 2000 份了。好吧,还能怎么办呢,项目成本又要上升了。

    3.2 部署 & 运维

    Reqable 使用nginx作为后端反向代理,pm2作为服务进程管理工具。nginx没啥好说的,基本上大家都是用这个。pm2是因为我之前写NodeJS服务时候用的,直接拿来了。

    关于集群部署,目前完全不需要,单台服务器稳妥的。运维也就是定期自动备份下数据库,日志等,目前还缺一个服务稳定性监控,哪天抽个时间做了。

    最后谈一谈部署服务的一些心得。

    后端Dart服务和NodeJS或者Python这一类脚本服务不同,Dart是需要编译成二进制文件的,这个编译的过程挺耗性能的,我测试过阿里云最差的1 核 1G的突发性能实例,代码稍微多一些就编译不了了,但是部署NodeJS或者Python服务一点问题都没有。

    对于Docusaurus,部署时(非运行时)同样挺耗性能,1 核心的云服务器也部署不了,最低 2 核心起步,有条件的话建议 4 核心,不然部署真的就太慢了。

    4. 设计

    从技术方面来讲,能分享的也就这么多了,但还是忍不住在最后谈一谈关于设计方面的心得。这里的设计不是指产品设计,而是 UI/UX 设计。一个好的产品,离不开优秀的 UI/UX 设计。尽管 Reqable 在这方面乏善可陈,但是还是要谈一谈,就当抛砖引玉了。

    不同类型的产品,应当有不同风格的设计。Reqable 是开发工具,应当具备技术人员所偏爱类型的设计风格。因为我本人就是技术人员,相对来说了解这个群体,我认为优秀的开发工具简洁应当是第一要素,用英文来说就是 Clean 。技术人员是讲究效率的群体,应当摒弃一切杂乱又花里胡哨的堆叠。

    VS Code是这一类软件的行业标杆,无论是功能还是设计,Reqable 参考了VS Code的很多的设计理念,包括布局、交互方式和配色方案等等。

    同时,Reqable 基本遵循了Material Design 3的设计规范,这也是Flutter控件主推的设计方案。对此感兴趣的可以看看官方文档: https://m3.material.io/ ,前几天有幸参加的Google IO中国开发者大会,Material Design 3是其中的一项专题,可以预见在未来 Google 系列的产品,包括 Android 等都将进入Material Design 3的风格时代。当然了,也有用户反馈表示不喜欢或者不习惯这个设计风格,这一点我也很难,也许审美方面有时就是众口难调吧。

    谈到 UI 设计,少不了要使用图片或者图标。Reqable 没有图片相关的需求,图标倒是有非常之多。Reqable 的图标部分来自于开源网站,部分是我自己设计。一般是先生成SVG文件,然后加入到IconFont中,最后再在项目中使用。

    推荐一些我常用的图标资源网站(注意非完全免费):

    Flutter 生成IconFont的网站,这里的 Icon 都是开源免费的,包括 Google 的Material Design Iconshttps://www.fluttericon.com/

    如果需要自己设计和编辑图标,推荐使用Sketch,处理矢量图SVG的功能非常强大。

    5 结语

    Reqable的理念是先进 API 生产力工具,宗旨是做优秀的国产软件。无论您是企业工程师还是个人开发者,我都希望 Reqable 的项目经验能够对您有所帮助。如果您对本篇文章满意的话,也可以通过订阅 Reqable的方式来支持我。

    Reqable 的官网: https://reqable.com
    GitHub 建议&反馈: https://github.com/reqable/reqable-app

    如果您对 Reqable 有任何问题都可以与我联系,或者在 GitHub 上提交 Issue !

    感谢支持 Reqable ,谢谢!

    13 条回复    2023-09-09 08:42:39 +08:00
    weiwenhao
        1
    weiwenhao  
       233 天前
    大佬牛逼。 我也还在寻找适合做官网和文档的开源组件,虽然目前用 Docusaurus 但是感觉 文档标题排版 比较差,首页也差点意思。
    qq316107934
        2
    qq316107934  
       233 天前
    图标建议换一个,不然跟 HTTPCanary 完全一致,这是一款安卓端抓包工具
    MegatronKing
        3
    MegatronKing  
    OP
       233 天前
    @weiwenhao 我认为文档的样式风格排版都无所谓了,意思差不多就行,主要还是首页难搞。用 Docusaurus ,首页就需要用 React 去深度定制开发才行,框架我觉得本身没问题,难的是深度开发,毕竟独立开发技术栈很难面面俱到。
    MegatronKing
        4
    MegatronKing  
    OP
       233 天前   ❤️ 4
    @qq316107934 HTTPCanary 也是我写的,Reqable 的规划是完全替换掉 HTTPCanary 。
    jsonzz
        5
    jsonzz  
       233 天前
    牛,谢谢分享,非常期待如何支持 Python 脚本的分享,大佬能给点关键词吗
    qq316107934
        6
    qq316107934  
       233 天前
    @MegatronKing #4 大佬牛逼😱
    u4zada
        7
    u4zada  
       233 天前
    牛的,回头体验体验
    SSQQ
        8
    SSQQ  
       233 天前
    Getx 确实是个杂货铺 作者把啥都放里边
    lzgshsj
        9
    lzgshsj  
       233 天前   ❤️ 1
    看到 Flutter ,不错,
    看到 Flutter 桌面端,卧槽,
    看到 Flutter 网页端,牛皮,
    看到 Flutter 服务端,Orz
    hooych
        10
    hooych  
       232 天前
    感谢分享,学习了
    jackdou
        11
    jackdou  
       232 天前
    牛,收藏一个
    lizhenda
        12
    lizhenda  
       232 天前
    都是干货啊,感谢分享
    putaozhenhaochi
        13
    putaozhenhaochi  
       232 天前 via iPhone
    牛逼啊 敢用 dart 写多端
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5438 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 38ms · UTC 08:58 · PVG 16:58 · LAX 01:58 · JFK 04:58
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.