【鸿蒙心迹】端云协同踩坑录:HarmonyOS 7 应用调后端 API,我在证书、鉴权、弱网上交的学费
【鸿蒙心迹】端云协同踩坑录:HarmonyOS 7 应用调后端 API,我在证书、鉴权、弱网上交的学费
免责声明:本文为个人开发笔记,环境为 HarmonyOS 7.x(API 26)/ DevEco Studio 5.x,后端为 Nginx + 自建 API。不同 API 版本行为可能有差异,请以官方文档为准。
起因:本地能跑,真机白屏
App 业务逻辑写完,接口一接就翻车:Previewer 里 mock 数据一切正常,真机上一片空白。服务端 Nginx access log 里连一条请求都没有——说明请求根本没发出去。
这类"端上没发、服务端没收"的问题,我把排查顺序固定下来了:网络权限 → 网络策略 → 证书 → 鉴权与参数。我这次正好四个全踩了一遍。
坑一:请求没发出去,日志却只字不提权限
现象:http.createHttp().request() 直接进 err 回调,控制台信息含糊。
根因:module.json5 里没声明网络权限。这是鸿蒙和 Android 第一个差异——Android 的 INTERNET 是普通权限(声明即给),鸿蒙这边必须显式声明:
// entry/src/main/module.json5
{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" }
]
}
}
判断技巧:先看服务端有没有收到请求。服务端无日志 = 端上问题(权限/网络/崩溃);服务端有日志 = 服务端问题(鉴权/参数/超时)。这一条能省掉一半排查时间。
坑二:明文 HTTP 被拦,以及那两个默认打开的开关
测试环境接口是 http://,真机请求全挂。
解法两条路,我推荐第二条:
方案 A(临时放行):在 entry/src/main/resources/base/profile/ 下建 network_config.json,放行指定调试域名:
// entry/src/main/resources/base/profile/network_config.json
{
"network-security-config": {
"base-config": {
"cleartextTrafficPermitted": false // 生产环境禁止明文
},
"domain-config": [
{
"domains": [{ "include-subdomains": true, "name": "test.example.com" }],
"cleartextTrafficPermitted": true // 仅调试域名放行
}
]
},
"trust-global-user-ca": false, // 不信任 MDM/设备管理员安装的 CA,默认 true
"trust-current-user-ca": false // 不信任当前用户安装的 CA,默认 true
}
两个细节特别容易踩:
trust-global-user-ca和trust-current-user-ca是顶层字段,与network-security-config平级、在它外面。我一开始塞进了base-config里,配置完全不生效,排查了半天——这两个字段默认都是 true,也就是默认信任用户自行安装的 CA 证书。设备一旦被装上恶意证书,HTTPS 流量可能被解密,生产环境务必显式关掉。- 引用要写在
module.json5的 metadata 节点里,不是顶层字段:
{
"module": {
"metadata": [
{
"name": "ohos.network.security.config",
"resource": "$profile:network_config"
}
]
}
}
配置优先级是 component-config > domain-config > base-config,所以"base 全禁 + domain 单独放行"成立。改完必须重新编译打包,热重载不一定生效。
方案 B(推荐):给测试环境直接上 HTTPS。反正正式环境迟早要上,早一天上就少一次"开发能跑、上线就炸"。
抓包相关的副作用:上面两个开关关掉后,Charles / mitmproxy 就抓不到明文了。调试期用临时配置放行,上架前务必改回来。
坑三:SSL 握手失败——坑在中间证书
正式环境切 HTTPS 后真机报 SSL 握手失败,但浏览器访问同一域名完全正常。
根因:证书链不完整——服务端只配了站点证书,没配中间证书。浏览器会自动补全链(AIA 追链),而移动端的 TLS 实现通常不做这个动作。
这个坑我在服务端文章里写过,没想到在鸿蒙端上又遇到一次。检查与修复:
# 看服务端实际发出的链
openssl s_client -connect api.example.com:443 -servername api.example.com
# 正确配置:站点证书 + 中间证书合并为一个 fullchain
cat your_domain.crt intermediate.crt > fullchain.pem
Nginx 里 ssl_certificate 要指向 fullchain.pem,不是单独的站点证书。这类问题在服务端补全证书链即可,不需要在客户端动信任锚。
顺带提醒:证书有效期在往 45 天甚至更短演进,端上无感知,但服务端必须把到期监控做到位——按剩余比例告警(比如剩余 30% 就告警),别等过期了全线 502。
坑四:鉴权别散落在业务页面里——用拦截器统一注入
现象:登录接口通了之后,我一开始在每个业务请求里手动塞 Authorization 头。十几个接口写完,Token 刷新逻辑已经在四个地方各写了一遍,改一次要找四处。
正确做法:请求层统一封装,鉴权头、日志、重试全放拦截器里,业务代码只管传 URL 和参数:
// 统一在拦截器里注入,业务侧无感
sessionConfig.interceptors = [{
async onRequest(req: rcp.Request) {
req.headers = {
...req.headers,
Authorization: `Bearer ${await getToken()}`,
'X-Request-ID': genId() // 贯穿端云的 requestId
};
return req;
}
}]
401 处理建议:在拦截器的 onError 里统一拦 401,触发一次刷新后重放原请求;刷新失败再跳登录。别让刷新逻辑散落在业务页面里。
凭据存储提示:Token 不要直接落 Preferences——它是明文 KV,安全性靠沙箱隔离,挡不住 root 设备和备份提取。正确做法是走系统安全能力:用 HUKS 生成 AES-256-GCM 密钥,加密后写入 el2 加密目录,取用时再解密。这块站内已有专门文章,不重复展开。
坑五:从 http 模块迁到 RCP 的三个注意点
老写法是每个请求 createHttp → request → destroy 三件套,功能没毛病,但有两个隐患:destroy 时机不好把握(页面切走早了容易崩)、每个请求都新建连接(列表页连拉十几个接口时明显慢)。
迁到 RCP(Remote Communication Kit) 之后:
1. Session 别到处 new:有同事在每个页面都建 Session,连接根本没复用。正确做法是应用启动时建一个全局单例:
import { rcp } from '@kit.RemoteCommunicationKit';
const sessionConfig: rcp.SessionConfiguration = {
// 用于指定与会话关联的 HTTP 请求的配置
requestConfiguration: {
transfer: {
// 超时时间参数设置
timeout: {
connectMs: 5000, // 连接超时,单位毫秒,默认值 60000
transferMs: 10000 // 传输超时,单位毫秒,默认值 60000
}
}
}
};
const session = rcp.createSession(sessionConfig);
2. 超时一定要自己配:默认值是 60000 毫秒,也就是弱网下要等整整一分钟才报错,用户感知就是"转圈圈"。一般连接 5 秒、传输 10 秒够用。
3. 自签证书握手失败:测试环境用自签证书时,RCP 默认校验直接失败,开发期可以在 security 配置里临时跳过:
security: {
remoteValidation: 'skip', // 开发期临时用;上架前必须改回系统校验
tlsOptions: { tlsVersion: 'TlsV1.2' }
}
重试建议:只对幂等请求(GET)做指数退避重试,POST 谨慎(可能重复下单);页面退出时取消未完成请求,否则容易内存泄漏。
端云联调:用一个 requestId 把两头串起来
这是我觉得最值得沉淀的一条经验。
做法:App 每次请求生成一个 requestId,放进请求头;服务端 Nginx 和 API 都把它写进日志:
# Nginx:把端上带来的 requestId 记录到 access log
log_format main '$remote_addr - $request_time - "$http_x_request_id" '
'"$request" $status';
端上排障时,从 hilog 拿到 requestId,直接去服务端日志里 grep:
grep 'a1b2c3d4' /var/log/nginx/access.log
一次定位,不用再靠时间戳猜。 弱网、重试、超时这类问题,有没有 requestId 完全是两种排障效率。
抓包提醒:真机抓 HTTPS 需要装 CA 证书,而上面提到的 trust-current-user-ca 关掉后就抓不到明文了。判断依据:能抓到 CONNECT 但解不出明文 = CA 没被信任,不是代理配错了。
问题速查表
| 现象 | 大概率原因 | 处理 |
|---|---|---|
| 请求直接失败,服务端无日志 | 未声明 INTERNET 权限 | 加 requestPermissions |
| 只有 http 接口失败 | 明文流量被拦 | 配 network_config 或改 https |
| 改完配置没生效 | 热重载未生效 | 重新编译打包 |
| 浏览器正常、App SSL 失败 | 中间证书缺失 | 配 fullchain 并 reload |
| 切 HTTPS 后全线挂 | 证书过期 | 查有效期,补到期监控 |
| 抓包只见 CONNECT | 用户 CA 未信任 | 检查 trust-* 配置 |
| 地铁/弱网转圈 | 超时默认值 + 无退避重试 | 显式设超时,GET 做指数退避 |
| 页面退出后偶发崩溃 | 请求未取消 | 取消请求 + 挂生命周期 |
| 排障靠猜 | 端云日志无关联 | 加 requestId 贯穿 |
FAQ
Q1:Previewer 里能调网络接口吗?
不能,Previewer 不提供网络等系统能力。涉及网络的调试必须上真机或模拟器。
Q2:一定要上 HTTPS 吗?
正式环境基本是硬性要求。即便平台不强制,明文传输 Token 的安全风险也不值得冒。
Q3:证书问题怎么定位是端还是服务端?
浏览器正常、App 失败 → 大概率证书链或系统信任问题;浏览器也失败 → 服务端配置问题。再配合 openssl s_client 看实际发出的链。
Q4:Token 该存在哪里?
别用 Preferences。走系统安全能力(HUKS 生成密钥 + 加密写入 el2 目录,或使用 Asset Store Kit)。业务里取用时再解密,不要常驻全局状态。
Q5:刷新 Token 的逻辑放哪一层?
统一在请求拦截器的错误回调里处理,业务页面不感知。刷新成功重放原请求,刷新失败再跳登录。
Q6:重试会不会造成重复提交?
会。所以只给幂等请求加重试;写操作要么不自动重试,要么带幂等键由服务端去重。
Q7:老项目要不要从 http 迁到 RCP?
新项目直接用 RCP。老项目不急的话可以按模块慢慢换,改动面主要在 Session 单例改造和超时参数迁移两块,风险可控。
写在最后
这个 App 从"接口调不通"到稳定上线,真正卡时间的技术难点不在 UI,而在端和云之间那条链路:网络权限、明文策略、证书链、鉴权注入、超时重试、日志关联。而这些恰好是后端/运维视角的强项。
如果你也在做鸿蒙适配,建议第一周就把这四件事定死:网络安全配置、凭据存储方案、请求层拦截器封装、requestId 贯穿。后面接多少接口都不会乱。
感兴趣可以关注一下,我们下期见。
更多推荐



所有评论(0)