ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flutter库鸿蒙化改造实录:Jira与Confluence移动端对接方案

Flutter库鸿蒙化改造实录:Jira与Confluence移动端对接方案 如果你在一家稍微正经一点的互联网公司大概率逃不开 Jira 和 Confluence 这两个东西早上在 Jira 上领任务、改状态下午在 Confluence 写方案、贴链接周五还要把一周内容整理成周报。问题在于Web 端用着还行一旦想在手机上看一眼进度、在通勤路上回一条评论体验就比较难受了。于是项目组决定做一个小应用把 Jira 和 Confluence 的关键流程搬进 App技术栈选 FlutterDart 生态里现成的封装库就是atlassian_apis。想法很好结果一编译就懵了这个库根本没有鸿蒙平台的支持跑在 HarmonyOS NEXT 设备上直接报错。把 Jira 和 Confluence 办公管理流程真正接到鸿蒙端中间隔着的不是“改几行代码”那么简单而是要处理依赖渠道、平台通道、证书校验、Token 存储、LDAP 组同步等一系列问题。这篇文章就是我实际做完这件事之后的完整记录适合那些打算把 Flutter 三方库迁移到鸿蒙、并且需要对接 Atlassian 系服务的团队参考。1. atlassian_apis 到底干了什么鸿蒙化绕不开的三块硬骨头1.1 这个库的职责拆解atlassian_apis是 pub.dev 上一个针对 Atlassian 全家桶的 Dart 封装核心覆盖 Jira 和 Confluence部分版本还顺带支持 Jira Service Management、Bitbucket 的基础模型。它的价值不在于“能调 API”而在于把 API 返回的 JSON 变成了强类型 Model把认证、分页、错误码、字段校验这些脏活累活提前消化掉了。举个例子Jira 的 issue 返回结构极其庞杂嵌套的 fields、assignee、status、priority 全是对象手工用MapString, dynamic解析第一版能跑第二版加字段就崩。用这个库你可以直接操作JiraIssue、Project、Sprint这些对象代码的意图清晰得多。Confluence 那边也一样content body 存储格式有storage、editor、view多种表示库帮你封装好后至少不用整天和转义后的 HTML 片段搏斗。按理说纯 Dart 的 HTTP 封装在鸿蒙上应该能跑因为鸿蒙的 Flutter 运行时也提供 Dart VM 和dart:io网络栈。但麻烦在于这个库并不是“纯 Dart”它内部依赖了若干 Flutter 插件比如拿本地 Token 时用shared_preferences判断运行环境时用flutter/foundation某些版本还引了平台通道相关的代码。1.2 三块硬骨头依赖渠道、平台通道、网络与存储我梳理下来鸿蒙化绕不开三块硬骨头。第一块是依赖渠道。pub.dev 上的大量 Flutter 插件默认只支持 Android/iOS/Web/Windows/macOS/Linux鸿蒙不在支持列表里。你要么等官方出鸿蒙适配版要么找社区 fork要么自己在 pubspec 里用dependency_overrides强制替换。atlassian_apis的直接依赖里dio是非 Flutter 的纯 Dart 包问题不大但shared_preferences、path_provider这类就有鸿蒙适配版本需要逐个替换。第二块是平台通道。如果库内部或者你们自己加的功能里用了MethodChannel、EventChannel那原生侧实现原本是 Kotlin 或 Objective-C 的鸿蒙上必须用 ArkTS 在 ets 目录里重新实现一遍。这个不是改配置能解决的是真要写代码。第三块是网络与存储。Atlassian 的 Cloud API 对 TLS 证书校验很敏感而鸿蒙系统的 CA 根证书体系、网络栈行为和 Android/iOS 有差异自签名证书、内网代理这些场景特别容易翻车。Token 存储也不能直接搬 Android 的Keystore或 iOS 的Keychain得换到鸿蒙的 HUKS通用密钥库服务。这三块搞清楚之后整个改造才有一个清晰的路线图。2. 动手前的依赖体检先把“能不能跑”从玄学变成清单2.1 逐行检查 pubspec 的依赖我建议你不要直接改代码先做一次依赖体检。打开pubspec.yaml把atlassian_apis的传递依赖全部列出来逐个确认鸿蒙支持情况。我用一个表格记录当时的结果方便你套用依赖包用途鸿蒙支持情况处理方式dio核心 HTTP 客户端纯 Dart可正常使用无需替换但需关注版本shared_preferences本地键值存储有社区鸿蒙适配dependency_overrides 替换path_provider获取应用目录有社区鸿蒙适配dependency_overrides 替换xmlConfluence 正文解析纯 Dart可正常使用无需替换intl日期、时区处理纯 Dart可正常使用无需替换flutter/foundation平台判断、方法通道鸿蒙适配层行为需实测改代码避免硬编码判断oauth2Jira Server 的 OAuth 流程纯 Dart 逻辑可跑但密钥存储需替换保持逻辑改造存储层表格里最后一行值得多说一句。oauth2包本身是纯 Dart 实现的跑在鸿蒙上没毛病但它默认把 Token 写到内存或本地文件真实项目里不可能这么干。一旦涉及密钥和 Token 持久化就要接到鸿蒙的 HUKS 上。2.2 平台通道扫描别靠猜直接 grep很多人改了三方库鸿蒙化之后还是跑不起来最后发现是库深处藏了一个MethodChannel原生实现压根没人写。扫描这一步一定要做方法很简单在atlassian_apis的源码目录下直接搜grep -rn MethodChannel\|EventChannel\|Platform.is lib/你会看到三类情况MethodChannel调用Dart 侧发消息给原生需要鸿蒙侧实现对应 channel。EventChannel调用原生侧往 Dart 侧推事件鸿蒙侧同样要实现。Platform.isAndroid这类判断鸿蒙 Flutter 适配层返回的操作系统标识可能是ohos也可能是android取决于你们用的运行时版本如果你的代码里写死了Platform.isAndroid才走某一个分支在鸿蒙上行为就不确定。至少要改成final bool isHarmony Platform.operatingSystem ohos || Platform.operatingSystem harmony;不要问我为什么这么写还留两个值因为不同适配版本的返回值确实有差异先兼容两种等真机日志确认后删掉多余的。2.3 网络与证书策略体检这一步不能省。企业里很多 Atlassian 实例部署在内网走的是自签名证书或者内网 CA 签发的证书。Android 上你可以信任用户证书iOS 上要装描述文件到了鸿蒙上这一套又不一样。我当时的做法是先用抓包工具比如 Charles对比同一请求在 Android 和鸿蒙上的行为差异。常见现象就是Android 请求正常鸿蒙请求直接报网络错误。如果你在网上搜过类似问题会看到鸿蒙侧报2300056这类错误码多数情况就是 TLS 握手阶段失败了。这类问题跟代码逻辑无关纯粹是网络栈和证书体系差异排查的时候别一头扎进 Dart 代码里先看抓包结果。体检完成之后你会得到一份改造清单比直接懵着改要靠谱得多。3. 实战改造依赖替换、平台通道与网络层的落地方案3.1 替换依赖源把三方库指向鸿蒙适配版第一步是把pubspec.yaml里的相关依赖替换为支持鸿蒙的版本。这里有两种做法一种是把atlassian_apisfork 到自己仓库在ohos分支上改代码然后通过 Git 依赖引用另一种是在dependency_overrides里只替换它跑不了的底层插件。我当时是两种一起用的pubspec.yaml大概长这样dependencies: flutter: sdk: flutter atlassian_apis: git: url: https://gitee.com/your-company/atlassian_apis.git ref: ohos-support shared_preferences: ^2.3.0 path_provider: ^2.1.0 dependency_overrides: shared_preferences: git: url: https://gitee.com/ohos-packages/shared_preferences.git ref: ohos path_provider: git: url: https://gitee.com/ohos-packages/path_provider.git ref: ohos注意atlassian_apis本身不能动不动就跟着上游最新版跑。fork 之后锁定一个稳定的 commit所有鸿蒙适配改动基于这个 commit 来做。否则上游一重构你改的补丁全冲突那心情只能用酸爽来形容。3.2 MethodChannel 迁移到 ArkTS 侧如果你 fork 的atlassian_apis内部用到了平台通道或者你们自己封装的功能里要调用鸿蒙原生能力就需要在鸿蒙工程里补实现。以最基础的 MethodChannel 为例Dart 侧代码是这样// dart 侧定义一个 channel 并调用原生方法 const MethodChannel _channel MethodChannel(com.example/atlassian_token); FutureString? getTokenFromNative() async { return await _channel.invokeMethodString(getToken); }鸿蒙侧在entry/src/main/ets/目录下新建一个插件类用 ArkTS 注册同名 channel 并处理调用import { MethodChannel, MethodCall, FlutterPlugin } from ohos/flutter_ohos/plugin; export class AtlassianTokenPlugin implements FlutterPlugin { onAttach(engine: FlutterEngine): void { const channel engine.channelRegistry.methodChannel(com.example/atlassian_token); channel.setMethodCallHandler((call: MethodCall) { if (call.method getToken) { // 这里读取鸿蒙侧的加密存储 return Promise.resolve(getTokenFromHUKS()); } return Promise.reject(new Error(unsupported method)); }); } }这段代码是基于鸿蒙 Flutter 适配版的常见写法不同适配层 API 可能略有差异但思路是一致的channel 名称必须和 Dart 侧完全一致方法名保持一致返回值要能正确序列化。最容易踩的坑是 channel 名字拼错Dart 侧叫atlassian_tokenArkTS 侧写成了atlassian__token运行时不会编译报错但一调用就MissingPluginException排查起来很费劲。3.3 网络层与 HUKS 密钥存储网络层是鸿蒙化最容易出问题的地方。如果你是直接用dio发请求务必在初始化时检查一下底层 HttpClient 的行为。遇到自签名证书可以在HttpClient层加一个badCertificateCallbackimport dart:io; final dio Dio( BaseOptions( baseUrl: https://your-jira.example.com/rest/api/3, connectTimeout: const Duration(seconds: 10), ), ); (dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient () { final client HttpClient() ..badCertificateCallback (X509Certificate cert, String host, int port) { // 内网环境只对特定 host 放行生产环境别直接 return true return host your-jira.example.com; }; return client; };提示badCertificateCallback里不要无脑return true否则等于关闭了 TLS 校验。正确的做法是校验证书指纹至少也要限定 host。这块如果写错了等于把企业内网的账号密码裸奔在网络上。Token 存储方面鸿蒙提供了 HUKS 服务。你可以把 Jira 的 API Token 或者 Confluence 的 Personal Access Token 加密后存放在 HUKS 里而不是直接写本地文件。思路很简单Dart 侧通过 MethodChannel 把 Token 传给 ArkTS 侧ArkTS 调用 HUKS 的接口生成密钥、加密存储、再读取解密返回。流程比 Android 的 Keystore 多绕一步但安全级别是够的。4. EventChannel 与 PlatformViewJira/Confluence 场景里最容易卡住的两处4.1 实时通知为什么要用 EventChannelJira 的 REST API 本身没有长连接推送但很多团队会自建一条实时通知链比如某个 issue 被分配给你、评论有人回复、Confluence 页面被提到你App 要第一时间弹出提醒。Android 上可以用 FCMiOS 上用 APNs鸿蒙上则要接入鸿蒙自己的推送服务。从 Flutter 的角度看鸿蒙原生侧拿到推送消息之后需要把这条消息源源不断地传给 Dart 侧这就用到了EventChannel。Dart 侧定义一个 event stream 接收const EventChannel _pushChannel EventChannel(com.example/push_events); void listenPush() { _pushChannel.receiveBroadcastStream().listen((event) { // event 是原生侧传来的推送内容 if (event is Map) { _handlePushEvent(event); } }, onError: (Object e) { // 通道断开后的重连逻辑 }); }ArkTS 侧的对应实现核心是重写setStreamHandlerconst channel engine.channelRegistry.eventChannel(com.example/push_events); channel.setStreamHandler({ onListen: () { // 注册鸿蒙推送消息回调回调里向 Dart 侧发送事件 pushService.onMessage((message) { channel.sendEvent(message); }); }, onCancel: () { pushService.offMessage(); } });4.2 PlatformView 常见场景与注意点另一个容易被卡住的是 PlatformView。道理很简单有些团队根本不想用 REST API 重新绘制 Jira 的富文本详情而是直接塞一个 WebView加载 Jira 的 Web 页面或者内嵌 Confluence 编辑器。Flutter 要在鸿蒙上承载一个原生 WebView就得用到 PlatformView。鸿蒙上的 PlatformView 能力比 Android/iOS 成熟得要晚一些主要遇到两类问题一是键盘弹出时页面高度异常二是手势事件穿透。前者通常需要在 ArkTS 侧监听软键盘状态主动调整 WebView 的布局参数后者则需要严格控制 PlatformView 的触摸事件到底层还是 Flutter 层。调试的时候建议先用一个最小 Demo 验证 PlatformView别一上来就加载完整的 Jira 页面否则你会分不清到底是页面交互问题还是 PlatformView 本身的问题。5. Jira 与 Confluence 双端打通从拉取任务到自动写周报5.1 认证与 LDAP 组同步的影响API 都调通了最后的办公流设计才是这个项目的灵魂。Atlassian Cloud 和自建 Server/Data Center 的认证方式不同我先整理成表格场景认证方式Token 获取位置Jira Cloud邮箱地址 API Tokenhttps://id.atlassian.com/manage-profile/security/api-tokensJira Server/Data CenterPersonal Access Token个人头像 - Personal Access TokensConfluence Cloud邮箱地址 API Token同上同一份 Token 可用Confluence ServerPersonal Access Token个人设置 - PATs这里有一个特别容易被忽略的坑如果你们的 Jira 配置了 LDAP 目录并且开启了“同步组关系”那用户从 AD 域同步过来Jira 里的组也是实时映射的。Confluence 的 space 权限很多时候就是跟着 Jira 组走的如果 LDAP 组同步没有配好你调用 API 获取的用户组列表和实际权限会不一致表现出来就是“接口返回正常但用户看不了某个空间的页面”。建议在对接权限相关功能之前先调一次这个接口确认组关系GET /rest/api/3/user?accountIdxxxxexpandgroups返回的groups列表会显示该用户当前所属的 Jira 组如果这个列表和 LDAP 里的不一致先回 Jira 管理后台检查目录配置而不是在代码里找 bug。5.2 完整的办公流代码串联认证理顺之后核心办公流其实可以串成三段拉取我的待办、读取团队空间更新、自动生成周报。我用 forked 的atlassian_apis的写法大致是这样// 1. 从 HUKS 读取 token经过 method channel final jiraToken await tokenRepository.read(jira_token); // 2. 初始化 Jira 客户端 final jira JiraClient( baseUrl: https://your-company.atlassian.net, auth: AtlassianAuth.basic(your-emailcompany.com, jiraToken), ); // 3. 查当前用户未解决的 issue final issues await jira.searchIssues( jql: assignee currentUser() AND resolution Unresolved, fields: [summary, status, priority, updated], ); // 4. 初始化 Confluence 客户端读团队空间最近更新的文档 final confluence ConfluenceClient( baseUrl: https://your-company.atlassian.net/wiki, auth: AtlassianAuth.basic(your-emailcompany.com, jiraToken), ); final pages await confluence.getPages( spaceKey: TEAM, limit: 10, expand: [body.storage, version], );再往下就可以组合了。比如每周五下午拉取一周内自己解决的所有 Jira issue加上团队空间里本周新增的 Confluence 页面拼成一篇周报草稿创建一个新的 Confluence 页面存到个人空间。整个流程不复杂但省下来的却是每周半小时的重复劳动。实际写的时候要注意 Atlassian Cloud API 的限流策略一般按 user 维度有额度限制循环调接口前先看一眼返回头里的X-RateLimit-Limit别把公司实例打到限流。5.3 可以继续扩展的方向这个方案跑通之后最自然的几个扩展方向是把 Jira 的 Sprint 数据拉出来做一个简单的燃尽图把 Confluence 的评论和提醒接入 EventChannel 做成实时通知甚至是把审批流和 Jira 工作流绑定在鸿蒙上做一个轻量审批入口。基本思路都一样认证、网络、存储三层已经打通剩下的就是往上堆业务。我在实际做完这个鸿蒙化改造之后最深的一个体会是真正花时间的不是改代码本身而是把依赖关系、平台通道、证书策略这些“看不见的层”理清楚。如果你们也要做类似的事情记住一件事fork 一个稳定版本把鸿蒙适配改动沉淀进去不要每回都追上游的最新版。另外在dio外层一定要包一层自己的 Repository统一收口认证和重试逻辑否则每个页面都在裸调 API后面维护起来会很痛苦。最后分享一个小技巧鸿蒙模拟器上平台通道偶尔会出现调用超时真机一般没事。遇到这种问题先看 ArkTS 侧的事件序列别急着改业务逻辑很多时候是模拟器的系统服务调度问题不是你的代码问题。
返回列表