ARTICLE DETAIL

资讯详情

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

鸿蒙Flutter插件集成casbin-cpp:NAPI桥接实现RBAC/ABAC权限引擎

鸿蒙Flutter插件集成casbin-cpp:NAPI桥接实现RBAC/ABAC权限引擎 提到鸿蒙适配很多 Flutter 开发者第一反应是“引擎能用UI 能跑就行”。但一旦业务里出现权限控制需求比如要在鸿蒙的 Flutter 应用里做一套支持 RBAC/ABAC 的权限引擎情况就完全不一样了。casbin 是我在多个项目里反复使用的权限访问控制库跨语言版本很齐全可偏偏鸿蒙生态里没有一个开箱即用的官方适配层。这篇指南基于我实际踩过的坑目标是把 casbin-cpp 通过 NAPI 桥接到鸿蒙 Flutter 插件的完整思路讲透顺带把 RBAC/ABAC 两种工业级权限模型的配置方法也过一遍。新手可以照方抓药老手可以直接杀进工程去改。无论你是正在做鸿蒙应用、Flutter 插件还是单纯想了解三方库怎么接入鸿蒙原生生态这篇都值得收藏。1. 为什么要在鸿蒙上做 casbin 适配1.1 casbin 到底解决了什么问题casbin 不是一般意义上的“用户体系”库它管的是“权限判断”这一层。你可以用它来回答“用户 A 能不能对资源 B 做操作 C”这种问题。它把权限模型从业务代码里抽离出来通过 model 配置和 policy 策略来描述访问规则。这种设计最大的好处是权限变更不用改业务代码只需要调整策略文件或数据库记录。RBAC基于角色的访问控制是最常见的模型简单理解就是“用户绑定角色角色绑定权限”。用户不是直接挂在资源上的而是通过角色来获得访问能力。这样做的好处是企业里几十个上百个用户的权限维护成本会被大大降低——你不需要给每个用户单独加策略只要把角色分好权限就跟山体滑坡一样一传一串。ABAC基于属性的访问控制则是更细粒度的模型它不看“你是谁”而是看“你、资源、环境”这三者的一组属性。比如“只有部门等于研发且职级高于 P7 的用户才能查看这个项目的代码”。这种模型适合权限维度非常多、策略经常变化的场景。casbin 对这两种模型都支持而且都有官方配置范式这也是它成为工业级权限引擎的重要原因。但 casbin 的官方实现基本覆盖在 Go、Java、C、Node.js 等语言上鸿蒙生态下并没有现成的稳定绑定。对于使用 Flutter 开发鸿蒙应用的项目来说如果权限模块直接绕过 casbin要么自研判断逻辑要么把权限硬编码到业务代码里后期维护起来非常痛苦。这也是我做鸿蒙化适配最直接的原因。1.2 鸿蒙 Flutter 生态中的三方库困境鸿蒙推出 Flutter 支持后很多团队开始尝试把现有的 Flutter 应用移植过去。最让人头疼的往往不是 Dart 层代码而是依赖的原生三方库。像 casbin 这种权限引擎通常会涉及原生文件读写、复杂的字符串匹配、策略缓存等能力它在 Java/C 里已经非常成熟但要移植到鸿蒙上就绕不开“原生适配”。目前鸿蒙的生态还不像 Android 那样有完备的 JNI/NDK 支持模式。HarmonyOS NEXT 主打 ArkTS 原生开发对 C 的支持是通过 NAPI 来做的。这就意味着要在一个 Flutter 插件里接入 casbin你需要在 ArkTS 与 C 之间搭一座桥再在 Dart 与 ArkTS 之间再搭一座桥。虽然看起来绕了一层但逻辑上并不复杂关键是对 NAPI 的类型传递、生命周期管理、编译配置要有清晰的理解。如果你只是在一个小工具应用里做简单的权限判断自研当然没问题。但如果你面对的是企业级应用比如办公协作、医疗系统、工业控制平台那么“用什么权限模型、策略怎么演进、审计日志怎么出”都是硬性要求。casbin 的价值就在于它把这些都沉淀成一套成熟协议我们花力气去做鸿蒙化适配等于把整套协议搬到了新平台而不是重复造轮子。2. 适配方案的选型与整体设计2.1 方案对比原生桥接 vs Dart 重写 vs C 编译动手前我先列了一下可选路径省得一头扎进去写代码又走回头路。方案核心思路优点难点方案 AArkTS 重写 casbin用 ArkTS 从零实现一份权限引擎无跨语言依赖调试链路短工作量大策略语法兼容性难保证后期 casbin 上游更新难以同步方案 BDart 版 casbin使用或者移植 Dart 版本的权限库纯 Dart 链路理论上跨平台一致Dart 版本成熟度参差不齐性能与内存控制跟 C 不是一个量级维护方分散方案 Ccasbin-cpp NAPI通过鸿蒙原生 C 能力编译 casbin-cpp用 NAPI 暴露给 ArkTS再通过 MethodChannel 暴露给 Flutter复用 casbin 官方 C 核心性能稳健逻辑和 C 生态保持同步需要处理 NAPI 桥接编译链相对复杂从表格可以看出来方案 C 是唯一能同时兼顾“复用成熟的 casbin 逻辑”和“保持原生性能”的路线。鸿蒙的 NAPI 本身就是为 C/C 与 ArkTS 通信设计的稳定性上有保障。casbin-cpp 是官方 C 版本策略模型和 Go/Java 版保持一致后续上游修复 bug 我们可以直接同步。所以最终的适配方案就是 C。2.2 为什么我选 casbin-cpp NAPI 路线选型的时候我其实是有点纠结的。一开始想直接找一个 Dart 版权限库在 Dart 层解决所有问题。但后来发现真要做“工业级”权限控制Dart 版本的生态和性能还是差口气。casbin 的 C 版在内存管理上更可控而且鸿蒙 NAPI 对 C 的支持已经是标准能力不需要额外引入第三方运行时。casbin-cpp 另外一个优势是它自带一套完整的 model 解析和 policy 管理逻辑。我们只需要做 NAPI 封装把“NewEnforcer”“AddPolicy”“Enforce”这些核心操作暴露到 ArkTS 层再让 Flutter 通过 MethodChannel 调用。这样一个插件工程就能让 Dart 侧实现对权限引擎的完整控制业务代码不需要关心底层是 C 还是鸿蒙非常干净。还有一点不能忽略casbin-cpp 的轮子都经过社区大量项目的打磨很多边界情况处理得比自研要细。比如策略匹配的优先级、通配符支持、多个匹配器合并等这些如果我们自己实现可能会遇到很多“测试用例没过”的尴尬场景。直接用 C 核心等于把成熟的逻辑直接迁移到鸿蒙风险最低。2.3 整体架构拆解整个适配链路可以用一个很清晰的分层来描述Flutter/Dart 层应用业务代码调用封装好的 Dart 接口比如enforcer.enforce(sub, obj, act)。Flutter 插件层MethodChannel 收到 Dart 调用把参数打包成字符串或 JSON转交给鸿蒙原生插件。ArkTS 层插件注册 MethodCallHandler解析参数后调用 NAPI 模块暴露的函数。NAPI 层C 代码负责把 ArkTS 传过来的参数转换成 std::string 或结构体再调用 casbin-cpp。casbin-cpp 层执行真正的模型解析、策略匹配和权限判断把结果返回给上一层。如果你觉得链条太长可以理解为一次权限判断就是一次“参数打包接力”。这个链路虽然多了两层但每一层都是薄封装没有复杂的业务逻辑。真正厚的核心逻辑都在 casbin-cpp 里这样也方便我们后续做单元测试和性能优化。3. 环境准备与工程搭建3.1 工具链与源码准备在做适配前先把工具链准备好。我这里用的是 DevEco Studio 的鸿蒙开发环境配合 OpenHarmony SDK 和 Flutter for HarmonyOS 的 SDK。你需要确认以下几点DevEco Studio 版本能支持 OpenHarmony API 9 以上最好直接安装新版。Flutter SDK 是支持鸿蒙的那个版本不是普通 Android/iOS 版本。C 编译工具链DevEco 通常自带但 CMake 和 Ninja 建议独立安装一份方便调试。clang 或 gcc 工具链能正常跑cmake命令。casbin-cpp 的源码直接从官方仓库拉取不需要我们手动修改太多核心逻辑只需要在工程里引入它的 CMake 配置。需要注意的是casbin-cpp 在 Windows 和 Linux 下的编译依赖略有不同鸿蒙属于类 Linux 环境整体兼容性还不错。3.2 创建 Flutter 插件工程并添加鸿蒙平台我推荐直接创建一个 Flutter 插件工程而不是在业务应用里堆代码这样可以实现权限引擎的复用。在 Flutter SDK 环境里执行flutter create --templateplugin casbin_harmony_plugin这里需要注意的是新版 Flutter 插件默认支持 Android/iOS我们需要手动添加鸿蒙平台支持。在插件工程根目录下创建ohos目录并在pubspec.yaml里声明平台支持flutter: plugin: platforms: android: package: com.example.casbin pluginClass: CasbinPlugin ohos: pluginClass: CasbinPlugin package: com.example.casbinDevEco Studio 打开ohos目录后会自动识别为鸿蒙工程模块。在ohos模块下编写 ArkTS 插件代码并配置 C 的 CMake 构建脚本。3.3 把 casbin-cpp 集成进 CMake打开ohos/CMakeLists.txt把 casbin-cpp 的源码目录加进来。最简单的做法是使用 CMake 的add_subdirectorycmake_minimum_required(VERSION 3.5) project(casbin_harmony_plugin) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 这里是 casbin-cpp 的源码目录 add_subdirectory(third_party/casbin-cpp) # 我们的 NAPI 桥接模块 add_library(napi_casbin SHARED napi_init.cpp ) target_link_libraries(napi_casbin PUBLIC casbin) find_package(ohos_ndk REQUIRED)如果 casbin-cpp 依赖了一些基础库比如rapidjson也要一并加入编译路径。这部分我建议直接通过 CMake 的FetchContent或把源码放到third_party目录下保证离线可编译。编译通过后我们会得到一个libnapi_casbin.so动态库鸿蒙的 ArkTS 侧通过 NAPI 直接加载这个动态库。4. 核心适配细节从 NAPI 封装到 ArkTS 桥接4.1 NAPI 接口设计NAPI 接口设计是整个适配中最核心也最容易被忽略的一步。我一共暴露了以下这些接口基本覆盖了 casbin 的日常操作createEnforcer(configPath, policyPath)根据 model 配置文件和 policy 文件创建权限执行器。enforce(requestJson)传入请求参数的 JSON 字符串返回 true 或 false。addPolicy(policyJson)动态添加策略。removePolicy(policyJson)动态删除策略。getRolesForUser(user)获取用户绑定的角色。updatePolicy(oldPolicy, newPolicy)更新策略。所有参数我都用 JSON 字符串来传递。原因很简单NAPI 对多参数处理虽然也支持但参数一多就容易出现类型匹配错误而且每加一个参数就要改函数签名。JSON 字符串可以把所有结构化数据塞进一个参数字段解析时用nlohmann/json或者rapidjson方便又稳定。一个核心函数的 NAPI 声明大致长这样static napi_value Enforce(napi_env env, napi_callback_info info) { size_t argc 1; napi_value args[1]; napi_get_cb_info(env, info, argc, args, nullptr, nullptr); // 获取字符串参数 size_t strLen 0; napi_status status napi_get_value_string_utf8(env, args[0], nullptr, 0, strLen); std::string requestJsonStr; requestJsonStr.resize(strLen); napi_get_value_string_utf8(env, args[0], requestJsonStr[0], strLen 1, strLen); // 解析 JSON auto request nlohmann::json::parse(requestJsonStr); bool result enforcer-Enforce(request[sub].getstd::string(), request[obj].getstd::string(), request[act].getstd::string()); napi_value napiResult; napi_get_boolean(env, result, napiResult); return napiResult; }4.2 NAPI 类型转换与错误处理NAPI 的坑主要是类型转换。打开一个 NAPI 函数你得明确知道每个参数是 string 还是 number否则napi_get_value_string_utf8可能会直接抛异常。我在开发时遇到过最典型的一个问题ArkTS 侧某次传了一个 undefined 过来C 侧拿到 argc 还是 1但 args[0] 是napi_value的空引用直接取字符串长度导致崩溃。所以我在每次取值之前都会做一次类型校验napi_valuetype type; napi_typeof(env, args[0], type); if (type ! napi_string) { napi_throw_type_error(env, EINVAL, Expected string); return nullptr; }另外NAPI 的错误处理不像 Java 异常信息那么友好。一旦 C 层抛出了std::exception默认情况下 NAPI 并不会自动转成 ArkTS 异常需要自己接住异常并调用napi_throw_error把错误信息抛出去。我会在每个接口外面包一层try...catch统一转成 napi 异常消息这样 ArkTS 侧就能在catch里看到具体原因。资源管理要特别注意NAPI 创建的对象引用尤其是napi_ref如果不主动释放会一直占着内存。casbin 的 Enforcer 实例需要长期存在所以我会用一个全局shared_ptrcasbin::Enforcer来持有它不需要创建 napi_ref直接靠 C 内存管理控制生命周期。接口层面只暴露destroyEnforcer作为兜底。4.3 在 ArkTS 插件侧暴露 MethodChannel鸿蒙插件的入口类和 Android 插件结构类似用 ArkTS 写。你需要在ohos模块里创建一个CasbinPlugin实现 Flutter 插件接口。核心逻辑就是监听 MethodChannel 的调用方法名然后转调 NAPI 模块的函数。大致结构如下import { MethodCall, MethodChannel } from ohos/flutter_ohos import napi from libnapi_casbin.so export default class CasbinPlugin implements FlutterPlugin { private channel: MethodChannel private enforcerPtr: number 0 onAttach(binding: PluginBinding): void { this.channel new MethodChannel(binding.getBinaryMessenger(), com.example.casbin/enforcer) this.channel.setMethodCallHandler((call: MethodCall) { this.handleMethodCall(call) }) } private handleMethodCall(call: MethodCall): Promiseany { switch (call.method) { case createEnforcer: { let configPath call.arguments as string let policyPath call.arguments as string this.enforcerPtr napi.createEnforcer(configPath, policyPath) return Promise.resolve(true) } case enforce: { let requestJson call.arguments as string let result napi.enforce(this.enforcerPtr, requestJson) return Promise.resolve(result) } default: return Promise.reject(Not implemented) } } }这里的call.arguments是 Flutter 侧传下来的方法参数。我建议在 ArkTS 侧也规整一下比如enforce的入参是一个 JSON 字符串由 Dart 层序列化好避免 ArkTS 层做二次类型处理。需要注意的是onAttach和onDetach的生命周期要处理好。Flutter 引擎重启时插件实例会被重建Enforcer 的持有状态要同步清理否则会出现内存泄漏或者下一次创建时拿到旧的指针。我会在onDetach里主动调用napi.destroyEnforcer。5. 在 Flutter 侧实现调用与权限模型落地5.1 Dart 侧 MethodChannel 封装Dart 侧的逻辑其实是最轻松的一层。我建议不要直接在业务代码里MethodChannel.invokeMethod而是封装一个CasbinEnforcer类所有权限操作都通过这个类暴露。import package:flutter/services.dart; class CasbinEnforcer { static const MethodChannel _channel MethodChannel(com.example.casbin/enforcer); Futurevoid create(String configPath, String policyPath) async { await _channel.invokeMethod(createEnforcer, {configPath: configPath, policyPath: policyPath}); } Futurebool enforce({required String sub, required String obj, required String act}) async { final result await _channel.invokeMethod(enforce, { sub: sub, obj: obj, act: act, }); return result as bool; } Futurebool addPolicy(MapString, dynamic policy) async { final result await _channel.invokeMethod(addPolicy, policy); return result as bool; } }这里有个小细节。Flutter 的 MethodChannel 在传递 Map 参数时会对 key 做类型强约束key 必须是 String。所以别把 number 类型当 key 传进去。另外权限判断属于高频操作虽然在鸿蒙上链路较长但只要数据量不大实测下来单次判断在几毫秒内完全够用。如果你是老式的 Flutter 项目还没有用鸿蒙插件平台可以先用MethodChannel加平台判断的方式来渐进式适配。等鸿蒙插件工程稳定后再把这个复用模块提取成独立插件。5.2 RBAC 模型配置与执行casbin 的模型和策略是两个文件这可能是它最容易被团队接受的设计。RBAC 的 model.conf 可以这样写[request_definition] r sub, obj, act [policy_definition] p sub, obj, act [role_definition] g _, _ [policy_effect] e some(where (p.eft allow)) [matchers] m g(r.sub, p.sub) r.obj p.obj r.act p.act简单解读一下请求参数是(sub, obj, act)策略也是这三个元素。g用来定义用户与角色、角色与角色之间的继承关系。匹配规则中g(r.sub, p.sub)表示请求者所属的角色要能匹配到策略里的角色。策略文件 policy.csv 可以这样写p, admin, data1, read p, admin, data2, write p, developer, data1, read g, alice, admin g, bob, developer这样一来alice 拥有 admin 角色所以可以读 data1、写 data2bob 只有 developer 角色只能读 data1。如果后面要给 alice 加权限只需要修改策略文件不需要动代码。我在实际项目里还会维护一张“角色继承表”比如g, admin, super_admin让 super_admin 能继承 admin 的所有权限。casbin 的g天然支持层级关系这个特性在企业组织架构里非常实用。5.3 ABAC 模型配置与动态规则ABAC 模型比 RBAC 稍微绕一点但它的“动态规则”能力是 RBAC 很难替代的。以“用户能查看自己所在部门的项目”为例model.conf 可以这样写[request_definition] r sub, obj, act [policy_definition] p sub, obj, act [policy_effect] e some(where (p.eft allow)) [matchers] m r.sub.department r.obj.department r.act p.act r.sub.level 3这里最关键的是如何把r.sub.department这类属性传进来。casbin 的 C 版通常需要自定义匹配函数。我们可以在 NAPI 层把请求参数解析成包含属性嵌套结构的 JSON再通过 casbin-cpp 的AddFunction设置自定义匹配器。比如请求参数可以构造成{ sub: {name: alice, department: RD, level: 4}, obj: {name: project_alpha, department: RD}, act: view }然后 NAPI 层在创建 Enforcer 后注册一个名为departmentMatch的函数enforcer-AddFunction(departmentMatch, [](const std::vectorstd::string args) { // 从 args 中解析属性结构 auto subJson nlohmann::json::parse(args[0]); auto objJson nlohmann::json::parse(args[1]); return subJson[department] objJson[department]; });这样 matcher 里可以直接调用departmentMatch(r.sub, r.obj)ABAC 的灵活性就出来了。要注意ABAC 的每个请求参数都会附带属性数据量比 RBAC 大性能会比 RBAC 差一点建议在关键路径上做一层结果缓存。6. 实际工程中的常见问题与排查6.1 编译阶段casbin-cpp 依赖问题适配过程中最容易踩的坑其实是编译。casbin-cpp 不是只有一个孤零零的源文件它会依赖一些基础的 C 库。刚开始我直接把它扔进 CMake结果编译时报了一堆关于std::string_view和std::optional的错误后来发现是因为编译标准没有设置对。鸿蒙 NDK 默认的 C 标准比较低必须在CMakeLists.txt里强制指定C17。另外casbin-cpp 内部可能用到了异常处理所以编译选项里最好别加-fno-exceptions。否则运行时一旦在 C 层抛异常整个 NAPI 模块都会静默挂掉查起来特别头疼。我的建议是统一加target_compile_options(napi_casbin PRIVATE -fexceptions -frtti)还有一个经验是不要试图用vcpkg或conan去拉依赖鸿蒙的交叉编译工具链对这两个包管理器的支持不是特别好。直接走源码编译把需要的第三方头文件放到include目录反而更快。6.2 NAPI 传参崩溃NAPI 传参崩溃是个高危问题稍微不注意就是进程级别的崩溃。最常见的原因是参数个数不匹配。比如 C 侧定义了 2 个参数但 ArkTS 侧只传了 1 个那么napi_get_cb_info虽然能返回napi_ok但拿到的参数指针可能无效。我后来在每一个 NAPI 函数入口都强制检查argc expected_argc时就抛异常。另一个常见崩溃是字符串编码问题。鸿蒙的 C 侧默认用 UTF-8但 ArkTS 侧传出的字符串如果含中文有时候长度计算会出错。我习惯的做法是先用napi_get_value_string_utf8获取一次字符串长度再第二次调用真正取值这样可以避免长度缓冲区不够导致的截断和崩溃。如果你在调试 NAPI 时发现崩溃堆栈完全看不出问题建议在 C 层先加一层日志把每次传入的参数原样打出来。很多崩溃其实都是上层参数不符合预期导致的但 C 层哪知道打日志是成本最低的排查手段。6.3 线程并发与 Enforcer 生命周期Flutter 的 MethodChannel 默认在主线程调用所以大部分情况下 NAPI 调用也是主线程。但如果你在 Flutter 侧用了compute或者 Isolate那么 MethodChannel 可能从后台线程发起调用。casbin-cpp 的Enforcer并不是线程安全的多线程同时执行Enforce可能出现策略读取冲突。我的做法是在 C 侧给 Enforcer 加一个简单的读写锁或者每次调用时创建一个带状态的 Enforcer 副本。如果你追求极致的并发性能更建议做一个 Enforcer 池每个请求从池里取出一个可用的 Enforcer 实例用完释放。这种方式比锁的吞吐量更高代价是内存占用会稍微高一点。生命周期问题的另一面是如果 ArkTS 侧的插件被 detachFlutter 引擎仍然可能带着旧的 MethodChannel 发来调用。所以 C 全局持有的 Enforcer 指针需要在 detach 时置空并在每次调用时检查指针有效性。6.4 性能优化与策略加载策略casbin 在鸿蒙上跑出的性能其实相当不错但如果你一次加载成千上万条策略首次Enforce的耗时还是会有感知。我做了三层优化。第一层是策略加载缓存。Model 文件解析完不会变Policy 文件可以常驻内存只有在AddPolicy或RemovePolicy调用时才更新。不要在每次权限判断时重复加载 CSV 文件。第二层是请求结果缓存。对于用户权限不会频繁变化的热点请求可以直接在 Dart 层做一层简单的 LRU Cache比如用户在 5 分钟内对同一资源的同一操作直接返回缓存结果。但注意权限变更后要主动清缓存否则会出现权限失效延迟问题。第三层是减少链路损耗。在我的架构里一次 Enforce 要走 NAPI 调用虽然损耗不大但高并发下还是有开销。如果你追求极致可以在 C 层直接维护一个 Enforcer 实例池并暴露批处理接口把请求集中发送一次调用判断多个请求。这个优化在工业级系统里效果明显。7. 实操心得与后续扩展7.1 权限模型设计的几点经验适配工作做完之后真正的挑战其实不在技术桥接而在权限模型怎么设计。RBAC 适合角色级别相对稳定的系统ABAC 适合基于属性动态判断的系统。但在一个复杂的鸿蒙应用里两者往往混合使用。casbin 支持在同一个 model 里混合配置但你要看清楚策略冲突时的优先级别让 ABAC 的宽松规则覆盖了 RBAC 的严格规则。我的建议是最小权限原则。策略默认 deny只显式 allow。casbin 的 policy_effect 默认是some(where (p.eft allow))如果某条匹配策略被 deny最终结果可能被误判。我在生产环境里会额外加一条全局 deny 规则保证任何未被允许的操作都直接拒绝。这个操作看似多余但真遇到策略配置错误时能救你一命。还有一点策略文件的管理要纳入版本控制。权限变更是高风险操作如果谁手改了一条策略没有留痕后面排查非常痛苦。我会让策略文件走 Git 管理配合自动化测试在每次变更后跑一遍“字段权限矩阵”确保核心路径不受影响。7.2 后续可以扩展的方向casbin 鸿蒙化这条路走通之后可以往三个方向继续扩展。第一个方向是策略动态下发。把 policy 存储放到云端通过 WebSocket 或长连接实时同步到鸿蒙设备端。这样权限变更不用等应用发版适合有远程管理需求的企业应用。第二个方向是审计日志。casbin 的 Enforce 结果跨过 NAPI 回到 Flutter 层时可以在 Dart 侧统一记录请求参数、结果、耗时和用户上下文。把这些日志汇聚到统一平台就是一套完整的权限审计系统。第三个方向是性能监控。可以在 NAPI 层加一些计数器把每次权限判断的耗时、失败率上报到监控平台。鸿蒙端设备性能差异比较大比如平板和手机同一套权限引擎在低端设备上的表现可能完全不同。没有监控数据优化就是拍脑袋。我个人在实际操作里的体会是跨平台适配尤其是鸿蒙这种新生态最考验人的不是写代码而是对底层机制的理解。NAPI、CMake、MethodChannel 这三样东西单独看都不复杂但把它们串起来服务一个工业级权限引擎就需要对每一步都足够熟悉。如果这篇东西能帮你少踩几个坑那这段工程经验就没白攒。
返回列表