HarmonyOS开发—— uni-app 一键登录鸿蒙端排障续篇:从「AppId尚未开通」(errCode 1000) 到"取号成功、换号失败"的全链路填坑

上一篇《uni-app 手机号一键登录鸿蒙端适配全记录》里,我记录过用 GETUI_APPID 打通鸿蒙端一键登录的过程。没想到几个月后,同样的报错「当前应用AppId尚未开通uni一键登录」再次找上门——这一次,后台明明已开通、审核明明已通过、GETUI_APPID 明明配了,错误就是纹丝不动。

最终靠反编译 uni-verify 的编译产物才找到真凶:一个官方文档从未提及的专属配置键 GETUI_VERIFY_APPID。

而故事没到这里就结束——修好取号后,我又接连踩了「点击后一直 loading」「换号失败」「参数错误:appid」三个坑。本篇把整条链路一次讲透。

(说明:文中所有 xxxxxx / <...> 均为占位符,请替换为你自己的真实值。)

一、问题现象

鸿蒙端登录页调用预登录,稳定报错:

{
  "name": "UniError",
  "errSubject": "uni-verify",
  "errCode": 1000,
  "errMsg": "当前应用AppId尚未开通uni一键登录"
}

但去 DCloud 开发者中心「一键登录 → 应用管理」看,开通列表里赫然在自己的应用(AppID 已打码),平台报备状态显示「鸿蒙:审核已通过」,而且已经通过好几天了。

官方文档对 1000 的处理建议就一句话:「检查是否配置/已通过审核」。检查了,都通过了,那问题出在哪?

二、先梳理:鸿蒙端一键登录的 appid 到底从哪来

我们的应用是鸿蒙原生应用 + 离线打包(HBuilderX 编译前端资源 → 同步进 DevEco 原生工程 → DevEco 构建 HAP)。排查前先明确工程里所有跟 appid 沾边的配置点:

# 位置 配置项
1 DCloud 后台 DCloud AppId
2 DCloud 后台 一键登录应用ID(离线打包使用) ← 注意这一列
3 manifest.json 顶层 appid
4 manifest.json app-harmony.distribute.modules.uni-verify.appid
5 原生工程 module.json5 metadata → GETUI_APPID
6 云函数 getPhoneNumber appid
7 原生工程 EntryAbility.ets UNI_APPID

第一个关键发现来自后台列表页顶部的官方注释:

一键登录应用ID 为离线打包时配置的 appid

也就是说,后台里「DCloud AppId」和「一键登录应用ID(离线打包使用)」是两个不同的值!离线打包场景(鸿蒙原生应用无一例外)下,取号链路校验的是后者。而我们全部配置点填的都是 DCloud AppId。

三、第一次修复尝试:改 GETUI_APPID —— 失败

按上面的思路,把 module.json5 的 GETUI_APPID、manifest 的 uni-verify.appid、云函数 appid 统一改成「一键登录应用ID」,重新构建、重装 HAP。

结果:依然 1000,纹丝不动。

这说明一个重要事实:preLogin 发出去的认证 appid,根本不是从 GETUI_APPID 读的。

有同学会问:之前不是写过「不配 GETUI_APPID 会报 缺少参数: appid or gyid」吗?
注意区分:那是个推基础 SDK 初始化(gyuid 生成)的报错,和认证 appid 服务端校验不是同一条链路。两个坑长得像,根因完全不同。

四、反编译找真凶

鸿蒙的 uni_modules 能力是以预编译 har 形式提供的——本地只有 .d.ets 类型声明和编译后的 modules.abc 字节码,没有源码,官方文档也搜不到任何关于认证 appid 配置键的说明。

那就直接对 oh_modules/@uni_modules/uni-verify/ets/modules.abc 提取可打印字符串(PowerShell):

$bytes = [System.IO.File]::ReadAllBytes("...\@uni_modules\uni-verify\ets\modules.abc")
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
[regex]::Matches($text, "[\x20-\x7E]{6,}") | ForEach-Object { $_.Value } |
  Where-Object { $_ -match "GETUI|appid|verify|GY_" } | Select-Object -Unique

在字符串表里发现了决定性的线索:

getGYAppConfig
GETUI_APPID
GETUI_VERIFY_APPID          ← !一键登录有自己专属的 metadata 键!
GYAppId / setAppid / GY_APP_CONFIG
modules.json metadata GETUI_VERIFY_APPID is empty   ← 模块内置的报错文案

真相大白。当前版本的 uni-verify 内部存在两条独立的配置链:

metadata 键 消费方 用途
GETUI_APPID 个推基础 SDK / uni-push gyuid 初始化、推送通道标识
GETUI_VERIFY_APPID uni-verify 的 getGYAppConfig() → setAppid() preLogin/login 发给服务端校验的取号 appid

而我们的 module.json5 里从来没有 GETUI_VERIFY_APPID 这个键——认证侧带出去的 appid 始终不对,所以 GETUI_APPID 改成什么都不影响 1000 的报错。

五、修复取号

entry/src/main/module.json5 的 metadata 数组新增一项(值填后台「一键登录应用ID(离线打包使用)」):

{
  name: "GETUI_VERIFY_APPID",
  value: "xxxxxxxxxxxxxxxxxxxxxx", // 后台一键登录应用ID,非 DCloud AppId
}

配套动作:

  1. manifest.json 的 app-harmony.distribute.modules.uni-verify.appid 同步改为该值;
  2. DevEco 重新构建并安装 HAP——metadata 是打包进 HAP 的,杀进程重开无效,必须重装;
  3. 不要动的地方:EntryAbility.ets 的 UNI_APPID、manifest 顶层 appid 仍是 DCloud AppId(那是应用资源加载与身份标识用的),前端 preLogin()/login() 的 API 参数也不接收 appid。

⚠️ 这里当时还写了"云函数 appid 也同步改成一键登录应用ID"——这句话其实是错的,只是当时流程卡死在取号之后(见第八节),根本没走到换号,所以没暴露。留到卡点三更正。

六、验证:错误码 1000 → 1002

重装后再次调用 preLogin,报错变成了:

{ "errCode": 1002, "errMsg": "应用所有者账号信息异常,请检查账号uni一键登录余额是否充足" }

恭喜,这是好消息——1002 说明 appid 校验已经通过,服务端成功识别出了你的应用和所有者账号,只是账号余额不足。一键登录按验证成功次数计费,去开发者中心充值后即可正常调起,且充值后无需重新打包。

七、取号打通,只是战斗的开始

补上 GETUI_VERIFY_APPID、充值之后,授权页确实弹出来了,preLogin 也不再报 1000/1002。我以为到此收工,结果点了「本机号码一键登录」之后——页面开始无限转圈,弹窗既不关闭也不报错。接下来是三个更隐蔽的坑。

八、卡点一:点按钮后一直 loading(前端取值踩了平台差异的雷)

抓设备日志,只看到这一行就再没有下文:

[Harmony] univerify success>>>> [object Object]

success 回调明明触发了,为什么后面 uniCloud.callFunction 的 .then / .catch 一个都没进,loading 还永远关不掉?

因为我沿用了安卓/iOS 老代码里的写法:

const accessToken = res.authResult.access_token; // ← 鸿蒙下 res.authResult 是 undefined

鸿蒙 Next 的 univerifyManager.login 成功回调结构,和安卓/iOS 根本不是一套:

平台 成功回调结构 取值方式
Android / iOS { authResult: { access_token, openid } } res.authResult.access_token
HarmonyOS Next { accessToken, openId }(顶层、驼峰、大写 I) res.accessToken / res.openId

showLoading('登录中...') 已经弹出,紧跟着 res.authResult.access_token 同步抛了个 TypeError。这个异常既不在 .then 也不在 .catch 里(它发生在 callFunction 之前),于是 loading 无人来关、弹窗无人来闭——就是你看到的"一直转圈"。

修复就是兼容两种结构 + 取不到值时兜底关弹窗:

const handleUniverifySuccess = async (res: any) => {
  // 鸿蒙:顶层 { accessToken, openId };安卓/iOS:{ authResult:{ access_token, openid } }
  const accessToken = res?.accessToken ?? res?.authResult?.access_token;
  const openId = res?.openId ?? res?.authResult?.openid;
  if (!accessToken || !openId) {
    uni.hideLoading();
    univerifyManager?.close();
    uni.showToast({ title: '未获取到授权信息,请使用验证码登录', icon: 'none' });
    return;
  }
  uni.showLoading({ title: '登录中...', mask: true });
  uniCloud.callFunction({
    name: 'getPhoneNumber',
    data: { access_token: accessToken, openid: openId /* appid 见卡点三 */ },
  });
};

九、卡点二:换号报「获取手机号失败,请稍后重试」

取值修好后,终于走到了云函数,但换号失败:

Error: [getPhoneNumber]: Error: 获取手机号失败,请稍后重试。
    at .../@common_modules/uni-cloud-verify/index.js

先别急着怀疑配置。uniCloud.getPhoneNumber 用的 access_token 是一次性、约 5 分钟有效的凭证——反复点、拿旧 token 重试必然失败。正确姿势是:杀掉 App → 重新取号 → 只点一次。

如果干净单跑仍失败,去 DCloud 后台看「一键登录 → 用量/调用记录」,我那里发现一条关键线索:调用记录里归属的 Appid 是安卓/iOS 那个老应用的——也就是说,我本地改了云函数 appid,但压根没重新上传部署,线上跑的还是旧版本,旧版本 appid 和鸿蒙取号 token 对不上,自然换号失败。

血泪教训:云函数改完,必须右键"上传部署"到服务空间才生效,本地文件改了不等于线上改了。

十、卡点三:部署后报「参数错误:appid」—— 更正第五节

把云函数重新部署后,报错变成了:

Error: [getPhoneNumber]: Error: 参数错误:appid

到这一刻我才反应过来:第五节里"云函数 appid 也改成一键登录应用ID"是错的。

uniCloud.getPhoneNumber 服务端校验的 appid,要的是应用的「DCloud AppId」,而不是客户端取号用的「一键登录应用ID」(GETUI_VERIFY_APPID)。把后者传给云函数,服务端直接判为非法 appid:

链路 用的 appid
① 客户端取号(preLogin/login) 一键登录应用ID(GETUI_VERIFY_APPID)
② 服务端换号(云函数 getPhoneNumber) DCloud AppId(与安卓/iOS 各用自身 DCloud AppId 同理)

安卓/iOS 与鸿蒙的 DCloud AppId 也不同,同一个云函数写死单一 appid 就会顾此失彼。稳妥做法是让前端按平台传 appid,云函数兜底保持安卓/iOS 原行为:

// 云函数 getPhoneNumber/index.js
const res = await uniCloud.getPhoneNumber({
  // 未传时兜底为安卓/iOS 的 DCloud AppId,保持既有行为不变
  appid: event.appid || '<安卓/iOS 的 DCloud AppId>',
  provider: 'univerify',
  access_token: event.access_token,
  openid: event.openid,
});
// 鸿蒙前端在 callFunction.data 里额外传自己这份应用的 DCloud AppId
data: { access_token, openid, appid: '<鸿蒙应用的 DCloud AppId>' };

这样三端各传各的、共用一个函数一个服务空间即可(同账号下无需为鸿蒙单独开空间),换号终于成功返回真实手机号。

十一、复盘:一键登录的 appid 其实有多副面孔

整条链路排查下来,最大的坑是"appid"这个词在不同位置指向不同的值,钉死这张表基本能治百病:

位置 该填什么 填错的表现
module.json5 → GETUI_VERIFY_APPID 一键登录应用ID(离线打包用) preLogin errCode 1000
module.json5 → GETUI_APPID 个推 AppID(仅基础 SDK/推送) 与一键登录无关,改它不动 1000
云函数 getPhoneNumber 的 appid DCloud AppId(按平台) 参数错误:appid 或 获取手机号失败
manifest.json 顶层 appid、EntryAbility.ets 的 UNI_APPID DCloud AppId(身份/资源加载) www 资源目录找不到

十二、错误码 / 现象速查(合并版)

现象 根因 解法
4000 缺少参数: appid or gyid 个推基础 SDK 未初始化 配 GETUI_APPID
1000 AppId 尚未开通 认证 appid 不对 / 没配 GETUI_VERIFY_APPID 见第五节
1002 余额不足 账号计费问题 充值,无需重新打包
点按钮后一直 loading、不关窗 鸿蒙回调是 {accessToken,openId},误用 res.authResult.* 抛错 兼容取值 + 兜底 hideLoading/close(第八节)
获取手机号失败,请稍后重试 线上云函数 appid 与 token 归属不符(多为改了没部署),或 token 复用/过期 重新上传部署云函数;杀 App 重新取号、只点一次(第九节)
参数错误:appid 云函数传了「一键登录应用ID」而非 DCloud AppId 传对 DCloud AppId,多端用 event.appid 兜底(第十节)
102103 未开移动数据 / 副卡无流量 打开蜂窝数据重试

十三、总结:三条通用经验

  1. 离线打包场景下,「DCloud AppId」≠「一键登录应用ID」:客户端取号(GETUI_VERIFY_APPID)用后者,服务端换号(云函数 getPhoneNumber)用前者(DCloud AppId),别弄反。
  2. "取号成功"和"换号成功"是两个独立战场:鸿蒙的 login 成功回调结构与安卓/iOS 不一致,别照搬老代码;能弹授权页离"跑通"还差得远。
  3. 遇到闭源 har 行为与文档对不上时,对 modules.abc 提取字符串是成本极低的排查手段;凡涉及云函数的结论,一定要"部署后复测"再下定论——我一度以为第五节那句是对的,其实是线上根本没跑到那行。改完就部署、部署后看云函数运行日志里的 event,才不会被"本地代码"骗。

posted on 2026-10-08 14:58  码间留白  阅读(2)  评论(0)    收藏  举报

导航