V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
MegatronKing
V2EX  ›  程序员

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

  •  8
     
  •   MegatronKing · 2023-09-08 15:21:36 +08:00 · 2552 次点击
    这是一个创建于 471 天前的主题,其中的信息可能已经有所发展或是发生改变。

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