• Reqable项目技术栈全方面总结


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

    本篇文章的目的,是向大家分享我关于Reqable项目的一些技术思考、细节和填坑概要等。希望能够对大家有所帮助、避坑、少走一些弯路、更快交付。有必要说明的是,Reqable项目完全由我个人独立完成,受限于本人经验和能力,肯定有非常多的不足。如果你对本篇总结中提到的技术选型、处理方式等有不同的见解,欢迎提交评论,一起讨论,共同学习。
    请添加图片描述
    如果有不了解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的接口:
    请添加图片描述
    换句话说,就是另外创建了一个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是面向技术人员的产品,我相信没有多少技术人员喜欢在自己电脑里面安装一堆浏览器内核。猜一猜下图里面(本人开发机器)装有多少个浏览器?
    请添加图片描述
    当然,完全从性能考虑,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 // 数据层
    
    • 1
    • 2
    • 3
    • 4

    当然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());
        }
      
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    1.3.3 代码编辑器

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

    从功能上来讲,API开发过程中常用的JSON、XML等编辑的需求可以说是最基本的要求了,更何况还有Python脚本的编写,以及HTML、JS、CSS等数据格式的显示了。例如,下图JSON数据的编辑,可以说是API工具最基本的功能了。
    请添加图片描述
    基于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实现一套统一风格的。多次尝试后,我放弃了使用系统原生的菜单栏的方案,确定了现在的这种菜单栏样式,原因就是稳定可控,方便定制。
    请添加图片描述

    1.3.6 脚本支持

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

    请添加图片描述

    因此,我需要提供一套丰富的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这个项目的组件,其中绝大多数都是客户端的模块(稍微有点乱,需要进行整理,勿笑)。
    请添加图片描述

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


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

    2. 前端

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

    2.1 官网

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

    官网由一些独立页面 + 文档 + 博客三部分组成,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'],
        ),
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    2.2.5 部署&路由问题

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

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

    flutter build web --release
    
    • 1

    然而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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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中,最后再在项目中使用。

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

    • Font Awesome: https://fontawesome.com/
    • Flaticon: https://www.flaticon.com/
    • Icon8: https://icons8.com/

    Flutter生成IconFont的网站,这里的Icon都是开源免费的,包括Google的Material Design Icons:https://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,谢谢

  • 相关阅读:
    cesium在地形上贴地添加各种entity
    开发了一个在线客服系统
    java常用API之Object
    【编程题】【Scratch三级】2020.09 青蛙捕虫
    超越npm和yarn的包管理工具,为什么说pnpm才是工程化项目的未来。
    车载诊断新驱动——远程诊断
    5.XSS-反射型(post)利用:获取cookie
    工具篇 | 05 | IntelliJ IDEA
    OPENSSL-PKCS7入门知识介绍
    【蜂鸟E203内核解析】Chap.2 E203内核中指令执行的过程-为什么E203是两级流水线?
  • 原文地址:https://blog.csdn.net/MegatronKings/article/details/132761210