news 2026/10/18 10:56:48

Flutter插件适配OpenHarmony:flutter_web_auth OAuth2授权流程实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter插件适配OpenHarmony:flutter_web_auth OAuth2授权流程实战

Flutter 应用往 OpenHarmony 上迁移,单页面跑通其实不难,真正让人头疼的是带原生依赖的三方插件。flutter_web_auth 就是一个很典型的例子:它在 Android、iOS 上都是靠“拉一个系统浏览器上下文完成 OAuth2 授权”来工作的,到了 OpenHarmony 这边突然没有现成的原生实现,直接调authenticate()就会抛 MissingPluginException。我这次做的就是把 flutter_web_auth 适配到 OpenHarmony,同时用一个示例应用把 OAuth2 授权码模式的完整链路走通,以下是我整个从方案选型、原生实现到联调踩坑的记录。

如果你正打算把 Flutter 应用搬上 OpenHarmony,或者对“Flutter 插件怎么在非官方平台扩展”这件事感兴趣,这篇文章应该能帮你少走不少弯路。里面不只是代码,还有我为什么这么设计、哪些地方容易出错、排查顺序是什么,这些在官方文档里基本找不到。

1. 项目背景与适配方案选型

1.1 flutter_web_auth 到底在做什么

先说清楚这个插件的工作机制,因为适配的本质就是把它的工作机制从“官方支持的平台”翻译到 OpenHarmony。

flutter_web_auth 提供的是FlutterWebAuth.authenticate()接口,调用方传入一个授权页 URL 和一个回调 URL 前缀,它会调起系统级浏览器或浏览器上下文,让用户在浏览器里完成输入账号、确认授权这些操作。用户操作完成后,浏览器会重定向到回调地址,此时 URL 里通常会带上一段code参数,插件捕获到这个回调 URL 后,再把整条 URL 字符串通过 Future 返回给 Dart 层。至于拿 code 去换 token,那是业务层的事,插件本身并不关心。

这个设计有一个明显的好处:认证过程的安全敏感部分全部发生在系统浏览器里,应用自身容器拿不到用户密码,符合 OAuth2 授权码模式对“凭证不可见”的要求。但在不同平台上实现路径差异很大,Android 上靠 Chrome Custom Tabs,iOS 上走 ASWebAuthenticationSession,Windows 上则会启动默认浏览器并监听本地回环地址。Dart 层面对开发者只有一个统一接口,平台差异全部被插件内部消化掉了。

这种“Dart 只发指令,原生去跑腿”的架构,是通过 MethodChannel 实现的。Dart 侧把authenticate的 URL 参数通过通道丢给原生,原生拉起对应浏览器组件,完成后把结果字符串再传回 Dart。搞清楚这条链路,适配就成功了一半。

1.2 为什么一到 OpenHarmony 就断片

问题在于,OpenHarmony 不在 Flutter 官方支持平台列表里。虽然开源社区有人维护了基于 OpenHarmony 的 Flutter 发行版,能把 Flutter 引擎跑在 OpenHarmony 设备上,但插件的生态不会自动跟着转移。flutter_web_auth 的原生实现只覆盖了官方那几端,它在插件平台注册表里没有 OpenHarmony 对应的实现类。

所以你在 OpenHarmony 上跑一个依赖 flutter_web_auth 的 Flutter 应用,编译、安装都能过,但一调用authenticate(),MethodChannel 在原生侧找不到任何注册的 handler,最终抛 MissingPluginException。更隐蔽的问题是这个插件用的是PlatformInterface模式,它内部会有一个默认的实现类,某些版本里如果你没主动注册任何平台实现,它甚至可能在 Dart 侧直接给你一个提示说当前平台不支持。

我一开始也没意识到是“整条原生链路缺失”,还以为是环境编译问题,花了不少时间检查依赖和打包配置。后来在设备上抓 log,看到MissingPluginException才明白过来,问题出在平台通道没有原生侧接收端。

1.3 三条适配路线,我为什么选 PlatformInterface

针对这个问题,我当时列了三套可选的方案。

第一套方案是改造业务代码,弃用 flutter_web_auth。用 url_launcher 直接拉起浏览器,然后靠应用内接收回调。这个方案看起来简单,实际上问题很多:应用切到后台再回来,Activity 或 Ability 的生命周期要自己维护;如果用户中途切走,回调时机完全不可控;state 参数校验也要自己造轮子,跟重新发明一个 OAuth2 客户端没区别。我直接排除了。

第二套方案是 fork flutter_web_auth 源码,直接改 Dart 层逻辑,在里面硬编码 OpenHarmony 分支。这一套能跑通,但坏处很明显:以后官方插件升级,你要手动合代码,每一次都像做一次代码移植手术。而且把 OpenHarmony 相关代码塞进官方包,等于污染了原包,这不是一个可持续的维护姿势。

第三套方案是保持官方插件不动,在外面包一个自定义实现,注册成该插件的 OpenHarmony 平台实现。这依赖于 Flutter 插件体系的PlatformInterface机制,我们可以自己实现接口,然后在放在应用或独立适配包里统一注册。这个方案的好处是干净、内聚,不影响其它平台,升级官方插件时也不需要改动适配层。我最终选的就是这一套。

提示:选任何适配路线之前,都要先搞清楚目标插件是不是基于PlatformInterface实现。如果是老的、直接用 MethodChannel name 全网找 handler 的插件,适配方式完全不同,不能生搬硬套。

2. 工程化准备与适配工程搭建

2.1 环境与版本对齐

先把工具链准备好。我这边用的是一套 OpenHarmony SDK 配套的开发环境和某个开源社区维护的 Flutter OpenHarmony 发行版,两者版本必须严格对应。这里有个常见的坑:Flutter 官方版本和 OpenHarmony 发行版版本不完全一致,你会发现编译出的 so 库对不上,或者 Dart AOT 运行时直接起不来。

具体来说,我建议把你准备用的 Flutter 版本固定在 3.x 的某个稳定点,然后在 OpenHarmony 发行版仓库里找到对应的 tag 来用,不要随手拉最新。另外 OpenHarmony SDK 的 API 版本也有讲究,我这边当时用的是 API 9 以上的版本,Web 组件相关能力已经比较完整,如果 SDK 版本太低,后面 Web 组件的很多接口就调不了。

DevEco Studio 和 Flutter 命令行工具要共存,但构建动作要分开理解:Dart 侧代码和 Flutter 引擎产物由 Flutter 工具链产出,OpenHarmony 的 HAP 打包则由 DevEco 侧完成。实际联调时我是在 DevEco 里跑 HAP,然后把 Flutter 引擎的产物作为依赖带入的,顺序不要搞反。

2.2 适配包工程结构

我建了一个独立的适配包,取名flutter_web_auth_ohos,用它来承载所有 OpenHarmony 专属逻辑。目录结构大致如下:

flutter_web_auth_ohos/ ├── lib/ │ └── src/ │ └── flutter_web_auth_ohos_plugin.dart # Dart 层注册与实现 ├── ohos/ │ ├── entry/ │ │ └── src/main/ │ │ ├── ets/ │ │ │ ├── AuthAbility.ets # 授权页 Ability │ │ │ ├── AuthPage.ets # 授权页 Web 组件宿主 │ │ │ └── AuthChannel.ets # MethodChannel 原生 handler │ │ └── module.json5 # 模块配置 │ └── ... └── example/ └── ...

这个结构参照了 Flutter 插件标准的“平台包在外、Dart 接口在内”的风格。Dart 层只负责注册 PlatformInterface 的实例,真正的授权逻辑全部放在ohos/目录下,由 ArkTS 代码实现。

这么拆分有一个额外好处:独立成包后,示例应用可以直接通过 Git 依赖引用它,不需要把适配代码复制进每个用到它的工程。而且适配层和业务层隔离,以后就算 OpenHarmony 进度变了,比如 Ability 换成 Stage 模型新写法,修改范围也锁定在原生目录里。

2.3 先打通一条能“互通”的 MethodChannel

在写复杂的 OAuth2 逻辑之前,我建议先做最小连通性验证:Dart 发一个 ping,原生回一个 pong。这一步能提前暴露 channel 名称不一致、handler 没注册、二进制不匹配等环境级问题。

flutter_web_auth 内部使用的通道名是固定的,叫com.linusu.flutter_web_auth,这是插件源码里写死的。我们适配的时候,原生侧必须注册一个同名通道,否则 Dart 那边永远收不到结果。这个细节我差点忽略,因为一般自己写插件时会统一命名,但这里是被动适配别人的插件,通道名必须以对方源码为准。

ArkTS 侧注册通道的方式大致如下:

import { rpc } from '@kit.RemoteCommunicationKit'; // 在 Flutter 引擎初始化后,注册 MethodChannel handler let channel = new rpc.MethodChannel(engine, "com.linusu.flutter_web_auth"); channel.setMethodCallHandler((call) => { if (call.method === "authenticate") { // 执行授权流程 let url = call.arguments.url; let redirectPrefix = call.arguments.redirectPrefix; // ... } });

这段代码的关键是 channel 名称必须和 Dart 侧一致,其次才是方法名。先跑通 ping/pong 再往里填真正的授权逻辑,后面排查问题时就能少一个变量。

注意:如果你发现原生 handler 有注册但 Dart 还是报 MissingPluginException,请优先检查 engine 实例是否和页面实际使用的 engine 是同一个。有的项目里多次调用createFlutterEngine(),结果通道注册到了别的引擎上,这种问题往往藏得很深。

3. 原生侧核心实现:用 ArkTS Web 组件撑起授权页

3.1 授权链路时序设计

接下来是整个适配的核心:原生侧怎么把“浏览器上下文”这件事在 OpenHarmony 上复刻出来。我的做法是用 ArkTS 的 Web 组件直接渲染授权页面,整个流程分成五个阶段:

  1. Dart 层调用authenticate(),把授权 URL 和回调前缀通过通道传给原生。
  2. 原生侧启动一个全屏的授权页 Ability,Web 组件加载授权 URL。
  3. 用户在 Web 页面里完成登录与授权操作。
  4. OAuth2 服务端重定向到一个以redirectPrefix开头的 URL,里面携带code。
  5. 原生侧通过onLoadIntercept捕获这个回调 URL,解析参数,把结果回传给 Dart。

这个流程和 Android 上的 Chrome Custom Tabs 思路一致,只是没有用独立的浏览器产品,而是用应用内嵌的 Web 组件。因为是在应用内完成的,所以不需要跳系统浏览器,也省去了“从浏览器再跳回应用”的繁琐步骤。

要说清楚的是,这里存在一个安全取舍:应用内嵌 Web 组件意味着用户的输入发生在应用进程可见的环境里。不过对适配和示例应用来说这是合理的,因为很多 OpenHarmony 设备上的系统浏览器组件并不能被第三方应用随意拉起特定页面,尤其是没有浏览器应用的精简设备。

3.2 授权页 Ability 与 Web 组件搭建

授权页原生侧是一个单独的页面,我用 ArkTS 的@Entry组件来承载。页面里只有一个 Web 组件,配置上要特别留意几个开关。

@Entry @Component struct AuthPage { controller: WebController = new WebController(); @State authUrl: string = ""; build() { Column() { Web({ src: this.authUrl, controller: this.controller }) .javaScriptAccess(true) .domStorageAccess(true) .onLoadIntercept((event) => { // 拦截并检查是否为回调 URL if (this.isRedirectCallback(event.request.getRequestUrl())) { this.handleCallback(event.request.getRequestUrl()); return true; } return false; }) .onPageEnd((event) => { console.info("Auth page load finished: " + event.url); }) } } }

javaScriptAccess(true)和domStorageAccess(true)是我开了以后才不让授权页白屏的。很多 OAuth2 登录页都要操作 localStorage 存临时状态,如果不允许 DOM 存储,页面可能一直转圈。

这段代码里还有一个点值得解释:onLoadIntercept返回 true 表示“这个请求我拦截掉了,Web 组件不用继续加载”。当拦截到回调 URL 时是要停止加载的,因为用户已经拿到了授权码,继续加载回调地址毫无意义。返回 true 能避免 Web 组件跳到乱七八糟的中间页。

3.3 回调 URL 捕获与结果回传

回调 URL 的捕获是整个适配成败的关键。OAuth2 服务端在用户授权后会重定向到我们预先注册的redirect_uri,通常是myapp://auth/callback这样的自定义 Scheme。Web 组件在收到这个跳转时,加载的 URL 已经不是 http/https 了,而是一个应用私有 Scheme 的地址。

我在isRedirectCallback里做的事,本质上是前缀匹配:判断当前 URL 是否以redirectPrefix开头。为了调试方便,我没有直接返回布尔值,而是先在回调里把完整 URL 打出来看一眼。

捕获到回调 URL 后,要做的不是把整个 URL 原样丢回 Dart 就算了,而是要先解析出code,最好顺手校验一下state。这里有个经验:原生层不要承担太多业务逻辑,解析 token 和后续换取 access_token 的请求都放到 Dart 层做,原生层只需要完成“拿到 URL、判断是回调、原样回传”这三件事。

回传给 Dart 的方法是对MethodChannel的调用结果做 resolve:

let result = new rpc.Result(); result.success(url); // url 是捕获到的完整回调地址

Dart 侧拿到的就是一个字符串 URL,后面怎么拆参数就是业务层的事了。

3.4 Cookie 与登录态收尾

授权页关闭后,有一件事很容易被忽略:Cookie。Web 组件默认会保留会话信息,也就是说第一次授权成功后,第二次再调authenticate()时,Web 组件可能还带着上一次的登录态,用户会直接看到“确认授权”页而不是登录页。

这在实际联调时很坑,我开始以为每次拉起都是全新上下文,后来发现用户已经登录过了,怎么调都是直接回调。直到我手动清了 Cookie 才恢复登录页。

所以我在适配层的clean()方法里加入了清除 Cookie 的逻辑,对应刷新 Web 组件的 Cookie 管理器:

import { webview } from '@kit.ArkWeb'; function clearWebAuthCookies(): void { let cookieMgr = webview.WebCookieManager.getCookieManager(); cookieMgr.clearAllCookiesSync(); }

这个清理动作放在“用户退出登录”或“开始新一轮授权之前”执行。但也要注意别一刀切清掉所有 Cookie,如果应用本身还有内嵌 WebView 场景,消费方可能不愿意把全部 Cookie 清掉。我这边因为示例应用只有授权这一个场景,所以简化处理了。

4. 示例应用开发与 OAuth2 实战接入

4.1 示例应用的功能与流程

适配层做完,接下来就是用真实业务去检验。我做了一个示例应用,模拟“某协作文档服务客户端”,不认识真实产品,纯粹用本地 mock 的 OAuth2 服务来验证链路。

这个示例应用要覆盖的场景有四个:发起登录、接收回调、换 token、展示用户信息。界面只有两个状态:未登录时显示“立即登录”按钮,已登录时显示用户名和“退出登录”。就这么简单的应用,恰恰能把 flutter_web_auth 的核心能力完整串一遍。

登录流程如下:

点击登录 -> 组装授权 URL -> 调用 flutter_web_auth 的 authenticate() -> 原生拉起授权页 -> 用户输入用户名密码并授权 -> 服务端重定向到自定义 Scheme 回调 -> 原生捕获回调 URL 并返回 Dart -> Dart 解析 code + 校验 state -> 使用 code 调 token 接口 -> 拿到 access_token -> 调用用户信息接口 -> 更新界面

整个链路里,前五步是适配层负责的,后三步是业务层写的。如果适配层没问题,业务层的代码跟你在 Android 上写的基本没有差别。

4.2 授权码模式完整实现

OAuth2 授权码模式在业务层的核心调用,我直接贴简化后的 Dart 代码。

import 'package:flutter_web_auth/flutter_web_auth.dart'; Future<String> startOAuthLogin() async { final state = generateState(); // 随机字符串,防止 CSRF final redirectUri = "myapp://auth/callback"; final authUrl = Uri.https( "auth.example.com", // mock 服务域名 "/oauth2/authorize", { "response_type": "code", "client_id": "demo_client", "redirect_uri": redirectUri, "scope": "read_profile", "state": state, }, ).toString(); final resultUrl = await FlutterWebAuth.authenticate( url: authUrl, redirectPrefix: redirectUri, ); // 解析回调 URL final uri = Uri.parse(resultUrl); final code = uri.queryParameters['code']; final returnedState = uri.queryParameters['state']; if (returnedState != state) { throw Exception("state mismatch, possible CSRF"); } return code!; }

这里的redirectPrefix参数很关键,它的作用就是告诉原生层“目标 URL 长什么样算回调”。我在适配层里就是拿它做前缀匹配的。需要注意一点,redirectPrefix要传完整的前缀,比如myapp://auth/callback,不要只传myapp://,否则一旦有其它动静也会被当成回调。

拿到 code 以后,还要继续走换取 token 的逻辑:

Future<TokenResult> exchangeToken(String code) async { final resp = await http.post( Uri.https("auth.example.com", "/oauth2/token"), body: { "grant_type": "authorization_code", "client_id": "demo_client", "client_secret": "demo_secret", "code": code, "redirect_uri": "myapp://auth/callback", }, ); // 解析 json,拿到 access_token / refresh_token return TokenResult.fromJson(jsonDecode(resp.body)); }

整个过程中,Dart 层唯一需要依赖 flutter_web_auth 的地方就是那一个authenticate()调用。后面所有 HTTP 请求都是普通网络操作,这也是这个插件设计得好的地方:它把最脏的平台活干完了,业务侧保持干净。

4.3 配置清单:module.json5 与 URL Scheme

在 OpenHarmony 上,自定义 Scheme 的回调不是一个松散的字符串,而是需要正式声明的能力。如果你没有在模块配置里声明这个 Scheme,当 Web 组件尝试跳转到myapp://auth/callback时,OpenHarmony 根本不知道该把这个跳转交给哪个应用处理。

我在module.json5里重点配置了几块。首先是授权页 Ability 的注册,如果使用 Stage 模型,要在abilities数组里显式声明AuthAbility,并且把它的启动模式设为单例或独立,避免重复拉起。

然后是 URL Scheme 的声明,在skills里配置起来:

{ "skills": [ { "actions": ["action.system.home"], "entities": ["entity.system.home"] } ] }

这一块主要解决的是首页入口问题。真正让自定义 Scheme 生效的是在模块配置里的uris字段,要允许myapp这个 scheme 被本应用接收。这块配置因为和具体的 OpenHarmony 版本 API 有关系,建议以你当时使用的 SDK 文档为准,不过核心逻辑是固定的:要把你想接收的 scheme 和 host 全部列进去。

我没有把真实产品的域名或 scheme 写进示例代码,实际工程里用你自己的域名替换即可,但要注意域名必须是 OAuth2 服务端注册过的一致,否则服务端会把 redirect_uri 直接拒绝掉。

4.4 联调记录与效果验证

在真机上联调的时候,我第一次跑通并没有想象中顺利。日志前几行看到的不是回调成功,而是 Web 组件加载授权页时出现的大量网络请求日志。我把这些日志等级调低以后才能看到关键信息。

第一次点登录按钮,授权页正常拉起,输入模拟账号密码后点了授权,然后整个授权页直接消失了。我心里一紧,赶紧看日志。发现问题很简单:服务端跳转到了myapp://auth/callback,但 Web 组件的加载被成功拦截,结果也通过 channel 回传了,只是 Dart 解析完后发现state对不上。

我排查后发现是我的generateState()写得不严谨:我用了一个Random实例,但忘了随机源在每次调用时会重置,导致两个 state 字符串一样。当然不是每次都一样,但一旦命中,服务端请求一次是带着新 state,回调返回的却是旧 state,校验就过不去了。这种问题在 Android 上也会发生,只是我平时没注意,因为以前用的库内部做了处理。

修正state生成逻辑后,第二次联调链路就通了:登录按钮变成“欢迎,demo_user”,日志里能清晰看到从 authorize 到 token 再到 userinfo 三个接口的调用记录。

5. 常见问题与排查实录

5.1 问题速查表

我在这次适配中遇到过的问题,整理成了一张速查表,各位在 OpenHarmony 上接认证流程时可以对照排查。

现象可能原因处理办法
调用 authenticate 直接抛 MissingPluginExceptionMethodChannel 名称不一致或原生 handler 未注册确认通道名为com.linusu.flutter_web_auth,确认通道注册在正确的 engine 上
授权页白屏Web 组件没开 JS 或 DOM 存储加上.javaScriptAccess(true)和.domStorageAccess(true)
授权完成后 App 没有响应回调 URL 没有被拦截在onLoadIntercept里打印所有加载 URL,检查是否有子页面跳转被忽略
每次登录都是上次的已登录状态Cookie 残留在重新发起授权前调用clearAllCookiesSync()
服务端提示 redirect_uri 不合法配置的 scheme/host 和注册不一致检查 module.json5 的 uris 声明,确认 OAuth2 服务端注册的地址完全一致
授权页拉起后闪退Ability 生命周期管理问题检查 Ability 的启动模式和页面返回逻辑,避免多次拉起同一个页面
token 换完以后数据不刷新界面状态没更新这是业务层问题,确认异步调用结束后 UI 是在主线程更新

5.2 两个容易反复踩的点

第一个坑是 Web 组件的生命周期。授权页 Ability 如果被系统回收,比如应用在后台呆久了,回跳过来时可能找不到之前的页面实例。我在真机上就遇到过一次,用户在授权页停留较久,授权成功后应用直接从启动页回来了,整个链路断了。解决思路是在 Ability 的onNewWant里补充场景切换处理,保证回跳的时候如果 Ability 已经存在,就把它拉起来而不是新建。

第二个坑是日志打印太少。我开始对回调拦截逻辑不够自信,因为onLoadIntercept并不一定能拦截到所有跳转,尤其是 OAuth2 服务端做了多级重定向以后,真正的回调 URL 可能藏在第三次跳转里。建议在拦截回调里不急着做匹配,先无条件打印每一次的 URL,等你亲眼看到完整回调链了,再收紧匹配条件。我后面适应了这种调试方式,效率明显提升。

还有一个我觉得值得养成的习惯:所有和外部服务相关的配置都放到一个单独的配置文件里,不要在代码里到处硬编码。示例应用的 client_id、redirect_uri、scope 全是集中配置的,换环境时只改一个文件,排查问题也更轻松。

注意:授权页属于应用内的敏感页面,务必在授权流程开始前明确告知用户正在跳转第三方认证服务,并且不要在代码里保存用户密码、验证码等敏感输入,这些数据只应该流经 OAuth2 服务端自己的页面。

最后说点个人体会

适配 flutter_web_auth 这件事,表面上是在写代码,本质上是在把“这个插件在其它平台上依赖了原生什么能力”这个问题彻底想透。很多 Flutter 开发者在官方支持的平台上写业务写习惯了,反而不会去关心插件内部是怎么注册通道、怎么拉起原生页面的,但一到 OpenHarmony 这样的非官方平台,这些隐藏知识就会全部变成刚需。

我个人最大的收获不是把授权流程跑通,而是建立了一套“插件适配方法论”:先读源码弄清通道名和接口语义,再在原生侧最小可运行验证,最后才往里面填完整逻辑。这套方法我后续适配其它三方库时也在用,比如社交登录、支付回调这类同样需要拉原生活动再回跳的插件,思路完全复用得上。

如果你正在做类似的事情,最后再分享一个经验:真机上调试时,一定先把原生侧和 Dart 侧两边的日志同时打开。OpenHarmony 设备和电脑之间如果用日志工具连接,原生日志会和 Flutter 日志混在一起输出,养成用英文前缀区分来源的习惯,排查速度会快很多。适配工作没有太多捷径,但把每一层的关键日志提前打好,能让你每一步都走得踏实。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 10:56:30

高校教师在线学习平台设计与开发:Vue3+Node.js+Element Plus实战

去年给某高校信息中心做教师继续教育平台的时候&#xff0c;对方一开始给我看了一份需求文档&#xff0c;里面赫然写着“参考某某学习App的学生端”。我当场就有点警觉——教师在线学习和学生在线学习&#xff0c;表面上是同一件事&#xff0c;实际上完全是两种产品逻辑。学生有…

作者头像 李华
网站建设 2026/10/18 10:56:25

Python Django+Vue 图书馆管理系统:前后端分离架构与实战解析

简介&#xff1a;一套基于Python开发的图书馆管理系统毕业设计完整资源&#xff0c;面向计算机专业毕业生、需要完成课程设计或毕设项目的学生&#xff0c;也适合希望快速上手Django框架与MySQL数据库的开发者参考。系统涵盖图书信息、借阅、归还、罚款等核心模块&#xff0c;采…

作者头像 李华
网站建设 2026/10/18 10:52:51

[Unity-AI开发篇] AI操控Unity:Unity+MCP+Rider 接入 TaoToken 统一 Key 实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 10:50:28

从AI算力标杆项目看智算交付:训练集群与推理平台的工程能力

某智能计算技术服务商一口气拿下两个AI算力标杆项目的中标消息&#xff0c;这两天在我们这个圈子里讨论度不低。做智算基础设施几年下来&#xff0c;我的第一反应不是去猜合同金额&#xff0c;而是想拆解一件事&#xff1a;这种“AI算力标杆项目”到底在采购什么。表面看是加速…

作者头像 李华