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
}
配套动作:
manifest.json的app-harmony.distribute.modules.uni-verify.appid同步改为该值;- DevEco 重新构建并安装 HAP——metadata 是打包进 HAP 的,杀进程重开无效,必须重装;
- 不要动的地方:
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 |
未开移动数据 / 副卡无流量 | 打开蜂窝数据重试 |
十三、总结:三条通用经验
- 离线打包场景下,「DCloud AppId」≠「一键登录应用ID」:客户端取号(
GETUI_VERIFY_APPID)用后者,服务端换号(云函数getPhoneNumber)用前者(DCloud AppId),别弄反。 - "取号成功"和"换号成功"是两个独立战场:鸿蒙的 login 成功回调结构与安卓/iOS 不一致,别照搬老代码;能弹授权页离"跑通"还差得远。
- 遇到闭源 har 行为与文档对不上时,对
modules.abc提取字符串是成本极低的排查手段;凡涉及云函数的结论,一定要"部署后复测"再下定论——我一度以为第五节那句是对的,其实是线上根本没跑到那行。改完就部署、部署后看云函数运行日志里的event,才不会被"本地代码"骗。
浙公网安备 33010602011771号