首页 / 资讯中心 / 文章详情

Flutter在OpenHarmony上构建电子合同签署App的实践与经验

Flutter在OpenHarmony上构建电子合同签署App的实践与经验 ★ FEATURED ARTICLE
1. 为什么最终选了FlutterOpenHarmony客户端跨端方案的取舍先交代一下背景。我们团队接了个需求在一批OpenHarmony系统的设备上做一个电子合同签署App。这批设备有相当一部分是定制平板和一体机系统版本并不完全相同而且后续大概率还要适配更多硬件形态。业务侧的期望很直接用户能够在线查看合同、完成意愿确认、用电子签名或印章完成签署同时签署过程要符合电子合同的法律效力要求也就是要有可靠的实名认证、意愿验证和操作留痕。摆在面前的第一道选择题是客户端技术栈怎么定。原生ArkTS确实能把OpenHarmony的特性吃透但我们的团队主力一直是前端偏跨端方向对ArkTS的熟悉程度有限而如果只做原生意味着后续每个新硬件平台都要重写一套逻辑这在排期上几乎不可接受。这个时候Flutter for OpenHarmony的适配方案进入了视野。1.1 从适配方案演进看Flutter for OpenHarmony的成熟度有一段时间社区里提到Flutter在OpenHarmony上的支持很多人还停留在“跑个Demo可以生产环境不敢用”的印象。实际上这个判断已经过时了。OpenHarmony生态里维护了专门的Flutter分支把Dart运行时和Flutter引擎整体迁移到了OpenHarmony的Native层之上渲染走的是自绘引擎不依赖系统WebView。这意味着原来在Android上写的那套Flutter业务代码理论上可以以非常低的成本移植到OpenHarmony上运行。我自己的判断依据有三个第一Flutter引擎依赖的底层能力比如图形接口、事件分发、字体渲染在OpenHarmony上都有对应的适配实现第二Platform Channel机制在OpenHarmony上可以正常打通你可以用MethodChannel去调用鸿蒙侧的底层API这个是集成电子签SDK的关键前提第三社区里已经有不少厂商把Flutter应用跑到OpenHarmony的商显设备、教育平板上踩坑的反馈也越来越多说明这条路是有人反复趟过的。当然不是说没有坑。渲染性能在某些GPU驱动不完善的设备上会有波动Plugin生态也不如Android那么全这些都需要在选型阶段就做好心理预期。1.2 原生ArkTS、RN与Flutter的横向比较我们内部做了个简单的选型对比核心考量是业务代码复用率、OpenHarmony特性调用能力、团队上手成本、以及第三方SDK的适配难度。技术方案业务复用率特性调用能力上手成本第三方SDK适配原生ArkTS低仅OpenHarmony最强高需重新学好有官方API可直接调React Native中需依赖RN的鸿蒙适配需通过桥接中桥接层工作量大Flutterohos分支高与Android端保持一致通过MethodChannel桥接低几乎零门槛需要在原生侧封装一层SDK调用最后选定Flutter决定性因素是“业务代码复用率”。电子合同签署这个场景核心流程在Android端我们已经有成熟实现包括合同列表、富文本展示、签署区域定位、手写签名绘制。如果采用Flutter这些逻辑可以直接搬过来只需要重写原生侧的桥接层和少量OpenHarmony专属能力调用。比从零写一套ArkTS应用省了大概三成工作量。2. 环境搭建Flutter的OpenHarmony分支持续集成第一关就是版本对齐选定方案之后真正的坑从环境搭建就开始了。这里是整个项目里最容易劝退人的环节因为网上能查到的资料版本非常杂乱照着旧教程操作很容易编译不过。我把我们最终跑通的版本组合和关键操作记录下来希望能帮你少走弯路。2.1 SDK版本对齐是第一道门槛Flutter for OpenHarmony和OpenHarmony SDK之间存在严格的版本对应关系不是你随便装一个最新版Flutter就能编译的。我们最初就是吃了这个亏装了某版本的Flutter又装了较新的SDK结果一编译就报错说某个Native符号找不到。正确的姿势是先确定OpenHarmony SDK版本再去匹配Flutter分支版本。目前比较稳的组合方式是安装OpenHarmony配套的开发IDE在SDK Manager里选择与设备系统版本匹配的SDK拉取Flutter的OpenHarmony适配分支注意分支名要和SDK版本配套设置Flutter的SDK路径到OpenHarmony SDK目录而不是Android SDK。另外Java环境和鸿蒙的toolchain也有版本要求。我们遇到过因为JDK版本太高导致编译工具链异常的情况后来统一固定到一个长期支持版本才稳定下来。2.2 Flutter工程如何挂到OpenHarmony构建链上Flutter for OpenHarmony的工程结构和Android工程有一个明显差异它需要在Flutter工程外层再包一层OpenHarmony应用工程。简单说Flutter的Dart代码编译成动态库然后由OpenHarmony应用作为宿主加载。操作路径大致是这样# 1. 创建OpenHarmony应用外壳工程 # 2. 在工程内接入Flutter模块 # 3. 配置Flutter的构建产物路径这里最容易踩的坑是OpenHarmony工程的模块名、包名和Flutter侧的Bundle ID不一致导致运行时找不到Flutter引擎动态库。我自己遇到过一次编译全过但一启动就白屏日志里报“Failed to load libflutter.so”。排查了半天最后发现是因为工程模块名里带了个中划线被转成下划线后和Flutter侧配置的库名对不上。经验总结在创建工程时模块名和包名尽量全部使用小写字母加下划线不要带任何特殊符号。这个规则在OpenHarmony的构建体系里非常严格。2.3 运行Demo时及时验证Platform Channel环境搭建完成后的第一件事不要急着写业务代码先验证Platform Channel是否正常。我们当时的验证方案很简单写一个Dart侧的MethodChannel调用让原生ArkTS侧返回当前设备型号和系统版本如果能正确返回说明Flutter引擎和OpenHarmony宿主之间的通信链路是通的。这一步非常关键因为电子合同签署的API集成尤其是涉及到密钥存储、指纹验证、人脸识别这些能力时全部需要通过Platform Channel去调用鸿蒙侧能力。如果基础的通信链路不稳定后续排查问题会极度痛苦。3. 电子合同API的核心链路从模板填充到签署完成的接口设计电子合同签署和普通业务流程不一样它的每一步都涉及法律效力和举证责任所以API设计上要比一般业务系统严谨得多。我们在对接电子签服务商开放平台时把整个业务流程拆成了几个明确的阶段实名认证、合同发起、签署人确认、意愿验证、签署动作、存证回执。3.1 先理解电子签服务商开放平台的接口模型目前主流的电子签开放平台接口模型大同小异核心资源大概有这几种企业/个人实名信息、印章模板、合同模板、签署流程、签署任务。用我接触过的某开放平台举例它把整个签署过程抽象为“创建签署流程 → 添加签署方 → 添加签署区域 → 发起签署 → 查询签署状态”。打个比方这就像线下签合同的过程先确定一份空白合同模板填写甲乙双方信息然后指定双方在哪里签字盖章最后把合同递到对方面前等对方落笔。对应到API层面大致是这样一组调用模板管理API - POST /v3/contract/template 创建模板 - POST /v3/contract/template/parse 解析模板变量 发起签署API - POST /v3/sign/flow 创建签署流程 - POST /v3/sign/flow/{flowId}/signer 添加签署方 - POST /v3/sign/flow/{flowId}/signarea 设置签署区域 - POST /v3/sign/flow/{flowId}/start 发起签署 状态查询API - GET /v3/sign/flow/{flowId} 查询签署进度 - POST /v3/sign/flow/{flowId}/verify 下载签署报告3.2 客户端在签署链路中的角色边界这里要明确一个架构问题客户端在电子合同签署链路里并不是所有API都能直接调用。涉及合同金额、合同编号生成、签署方实名资料等核心业务数据必须由我们自己的服务端去调用电子签平台API。客户端只负责两件事展示合同内容、收集用户签署意愿。所以我们在Flutter工程里封装了两层网络模型第一层是业务服务层与我们的后端API通信负责获取合同列表、签署任务详情、合同展示需要的数据第二层是电子签SDK层通过Platform Channel调用鸿蒙侧封装的电子签SDK能力用于拉起签署页面、调起人脸识别验证等。3.3 模板填充与合同展示的实现合同内容展示是一个容易被低估的难点。电子合同通常不是简单的PDF图片而是包含动态变量的模板。比如一份采购合同甲方名称、乙方名称、合同金额、日期都是运行时填充的。我们的做法是服务端负责模板渲染把标准化合同模板和填充数据合并生成最终的PDF文件返回给客户端。客户端只需要下载PDF文件并用Flutter内置的文档展示能力呈现。这样客户端不直接接触模板变量避免在多个端上渲染效果不一致的问题。这个决策的好处很直接合同内容的一致性由服务端统一保证客户端不管是用Flutter、Web还是原生App展示的都是同一份渲染后的PDF。对电子合同来说一致性就是法律效力的基础。4. 密钥安全把Flutter的签名密钥塞进OpenHarmony的HUKS保险箱电子合同最关键的安全要求就是签署动作的私钥不能被任何人拿到。传统App里最常见的错误做法是把API密钥、签名密钥直接存在SharedPreferences或者本地文件里——这在电子合同场景是绝对不可接受的。4.1 为什么不能用普通文件存储密钥你可能觉得把密钥存在App私有目录里用户拿不到不就安全了吗事实并非如此。只要设备被root过或者App存在备份漏洞私有目录里的文件一样可以被读取。电子合同的举证场景里如果对方律师质疑“这个签名到底是不是用户本人签的、密钥有没有可能被复制”你拿不出密钥保护链路的证据签的合同法律效力就要打折扣。正确的做法是使用硬件级或系统级的安全能力。OpenHarmony提供了HUKSUniversal KeyStore能力它把密钥放在系统安全环境中私有密钥可以做到“只能使用、无法导出”。4.2 MethodChannel打通Flutter与HUKSFlutter的Dart侧是不能直接访问HUKS的需要通过MethodChannel让原生ArkTS侧帮忙完成密钥操作。我们封装了一个独立的安全桥接模块对外暴露三个方法生成密钥对、获取公钥、签名。原生侧用ArkTS实现核心逻辑大致是这样// 示意代码通过HUKS生成RSA密钥对 import huks from ohos.security.huks; const alias sign_key_ flowId; // 每个签署流程对应一个独立密钥 const properties [ { tag: huks.HuksTag.HUKS_TAG_ALGORITHM, value: huks.HuksKeyAlg.HUKS_ALG_RSA }, { tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, value: 2048 }, { tag: huks.HuksTag.HUKS_TAG_DIGEST, value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 }, { tag: huks.HuksTag.HUKS_TAG_PADDING, value: huks.HuksKeyPadding.HUKS_PADDING_PKCS1_V1_5 }, { tag: huks.HuksTag.HUKS_TAG_PURPOSE, value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN } ]; huks.generateKeyItem(alias, { properties }, (err, data) { // 生成成功后私钥不可导出 });Dart侧通过Platform Channel发起调用static const _channel MethodChannel(sec.keystore/channel); FutureString getPublicKey(String flowId) async { final result await _channel.invokeMethod(getPublicKey, {alias: sign_key_$flowId}); return result as String; // Base64编码后的公钥 } FutureString sign(String flowId, String digest) async { final result await _channel.invokeMethod(sign, { alias: sign_key_$flowId, digest: digest, }); return result as String; // Base64编码后的签名结果 }4.3 签名过程的完整代码链路在Dart侧我们对合同内容计算摘要然后签名。这里有一个容易被忽视的点电子合同签名不是对整个PDF签而是对合同内容的标准摘要值签名。摘要算法和签名算法必须和服务端约定的验签规则一致否则服务端验签永远失败。我们最终采用的链路是Flutter拿到待签署PDF的SHA-256摘要 → 通过安全桥接模块调用HUKS私钥签名 → 把签名结果和证书信息一起提交给服务端。密钥的alias设计与用户及签署流程绑定同一用户处理不同合同时使用不同alias这样即使某一笔签署的密钥出现安全风险也不会波及其他合同。每笔合同签署结束后我们会主动调用removeKeyItem把密钥销毁防止密钥堆积。4.4 证书链与后续验签的配合电子合同最终要能在法庭上被验证仅仅有签名还不够还需要配套的证书链。我们在集成时把这一步分为三层服务端保存证书链和签名原始数据客户端只在上传时附带签名值而验证方通过服务端提供的签名报告里的证书链验证签名是否由受信任的CA签发。客户端承担的是“签名动作发生在可信环境中”的证明责任。5. 回调、重试与幂等签署结果同步的生产级设计签署流程不是发起后就完事了从发起签署到所有签署方完成签署中间可能经历几小时甚至几天。如何让客户端准确感知签署状态的变化我们试过几种方案最终形成了一个比较稳的架构。5.1 三条通道并行轮询、推送、本地状态机我们的签署状态同步同时用了三条通道可能有人觉得冗余但电子合同的场景特殊宁可多通道确保不错漏也不能漏掉一次状态更新。第一条通道是服务端回调。签署流程每发生一次状态变更电子签平台会通过回调URL通知我们的服务端。服务端收到回调后会实时推送给对应客户端。这是我们优先依赖的通道。第二条通道是客户端兜底轮询。客户端在进入合同详情页、或者处于待签署列表页时周期性请求服务端的签署状态接口。如果推送通道出了问题轮询会在一定时间内发现状态差异。第三条通道是本地状态机。客户端本地维护每个签署流程的状态缓存每次收到推送或轮询结果时更新本地状态。如果本地状态和服务器状态不一致以服务器为准并更新本地。5.2 超时、断网与重试的幂等设计在线签署最怕的是用户已经完成了签署动作但网络超时导致客户端不知道结果。如果这时候盲目重试发起签署可能会导致同一签署方被创建多个签署任务。我们的方案是引入幂等键。每个签署方的签署请求都携带一个由业务流水号生成的幂等ID服务端在处理请求时先检查是否已经处理过相同幂等ID的请求。如果处理过直接返回上次的结果不重复执行签署。客户端侧的网络层也做了指数退避重试第一次失败后等2秒重试第二次4秒第三次8秒最多尝试5次。为什么用指数退避而不是固定间隔因为如果服务端正在重启或者网络间歇性故障固定间隔的频繁重试会加重服务端压力而指数退避能在恢复服务后自动弥补。5.3 客户端状态机的状态流转我们在Flutter侧的签署状态模型本质是一个有限状态机。每个签署流程实例的状态严格按这个顺序单向流转待填写 - 待签署 - 签署中 - 已完成 └ 已拒绝 / 已撤销这里有一个容易踩的坑不要允许状态跳变。我们在开发早期犯过错误测试人员用弱网工具模拟“发起签署后立刻取消”结果客户端本地状态直接从“待签署”跳到了“已完成”界面上显示了错误状态。后来强制状态只能按预定义路径流转不允许跳变否则触发异常上报这个问题才根治。6. 实测之后几个值得写下来的注意事项整个项目从环境搭建到上线前后大概用了两个月。有一些细节是测试阶段才发现、但在文档里很难看到的写在这里供你参考。6.1 网络权限与网络安全配置OpenHarmony对网络安全有一定限制默认情况下不允许App直接访问明文HTTP接口。如果电子签服务商或你们自己的后端接口还没有全面上HTTPS需要在工程的网络安全配置中显式声明允许访问的域名。我们在联调阶段就遇到了这个问题Android端能正常请求接口OpenHarmony端却一直报网络错误。排查后才发现是网络安全配置没有添加测试域名的白名单。电子合同涉及敏感数据强烈不建议为了省事直接放开所有明文流量只添加必要域名就足够了。6.2 签名区定位与PDF渲染的坐标换算电子合同的签署区域在PDF里有一套坐标体系而Flutter渲染PDF的控件又有一套显示坐标两者需要进行换算。尤其是用户在自定义签署位置时我们在Android端已经有一套成熟的坐标映射逻辑迁移到OpenHarmony后发现渲染控件的缩放宽高比略有不同导致签署区域偏移。这个问题的根源在于不同设备屏幕密度、PDF渲染引擎的显示差异。最终我们采用了一个相对稳妥的方案不让用户自由拖拽签署位置到任意像素而是提供几个预设的签署区域位置用户选择后客户端把预设位置的PDF坐标直接传给服务端。这样既符合电子签产品的普遍使用习惯也彻底绕开了坐标换算的兼容性问题。6.3 手写签名采集的笔迹压感处理手写签名是很多电子合同签署场景的核心交互用户在屏幕上写下的签名要尽量还原真实笔迹。Flutter侧可以使用自定义画笔实现但要注意OpenHarmony设备上的触摸屏采样率差异很大有些设备能高频率上报触摸点有些则比较稀疏。我们最终的签名组件里做了一步简化不追求写字的美观而是要求签名轨迹要能形成平滑曲线。通过贝塞尔曲线插值将采集到的触摸点序列拟合成连续笔迹。但要注意这里有一个坑——插值不能过度。如果你把原始触摸点全部用贝塞尔插值光滑处理签名看起来是漂亮了但笔迹鉴定的特征点可能被抹掉反而影响法律效力认定。稳妥的做法是只做轻度的抗锯齿平滑保留原始采样点的间架结构。6.4 多设备登录与签章绑定的联动电子合同App往往支持多设备登录。用户可能在平板上发起签署又在手机上查看进度甚至两个设备同时在线。我们的处理是签章和用户账号绑定不绑定设备。也就是说一幅用户专属的电子印章在所有登录过的设备上都能使用这符合用户直觉。但这里又引出一个安全面的问题如果用户的手机丢了别人捡到手机能否冒用印章为了防这种风险我们在“使用印章”这个动作上增加了二次校验必须通过指纹或人脸生物识别验证后印章才被允许调用。这个校验在Flutter层的表现是拉起一个本地生物认证页面动作本身不经过网络指纹数据也不会出设备。这个设计在最终验收时得到了业务方的一致认可。老实说做这个项目的最大感受是跨端框架带来的效率红利是真实的但底层的系统能力对接、安全机制设计一条都不能省。如果你正在计划把Flutter应用迁移到OpenHarmony上我的建议是先跑通一个带Platform Channel调用的最小Demo把系统层面的路摸清楚再投入业务开发这样后面会顺很多。
阅读完成 · 觉得有帮助?
咨询建站