这几个月我们团队一直在跟应用上架较劲。不是功能开发拖了后腿而是卡在上架前那一堆繁琐的工程操作上证书手动导出上传、十几个 HAR 共享包依赖理不清、提交前反复人工核对各种配置。后来我把整套上架流程按标题里说的三个方向重构了一遍——云管理证书自动签名、多 HAR 合并、提交自检 2.0整体提效非常明显。今天这篇就是把这三块方案的完整实现拆开讲清楚顺便把我在 HarmonyOS 7 环境下踩过的坑也一并列出适合正在维护中大型 HarmonyOS 应用、或者准备把上架流程做成自动化的开发者和交付负责人参考。1. 一场上架提速的起因签名、依赖和审核的三大堵点1.1 传统证书签名的人肉流程为什么最先被优化HarmonyOS 应用上架的第一步就是签名。以前我们用的是本地证书模式在 AppGallery Connect 上生成证书请求 CSR下载到本地配置 Profile 文件再在打包工具里手动勾选。这套流程单看没啥问题但一旦团队超过三个人、或者应用数量超过两个就开始乱了。最常见的场景证书文件存在同事 A 的电脑里同事 A 请假了同事 B 要出 release 包只能干等或者换了一台新电脑p12 密钥库文件拷过来拷过去密码还容易记混更头疼的是一个应用对应一个签名证书应用多了之后哪个包用哪个证书、哪个 Profile 对应哪个版本全靠脑子记。我见过最离谱的一次是团队里有人把 debug 签名的包误当成 release 包传到了审核后台被驳回之后才发现签名类型不对整条上架链路推倒重来。所以第一个要优化的就是签名环节。HarmonyOS 7 的云管理证书方案本质上把证书和私钥的保管这件事从本地挪到了云端开发者只负责在后台创建证书并关联应用打包时工具自动拉取证书配置完成签名。对个人开发者来说可能差别不大但对团队协作和 CI 流水线来说省掉的是一大堆交接成本和环境同步问题。1.2 HAR 满天飞业务扩展带来的依赖治理负债HAR 是 HarmonyOS 的静态共享包Harmony Archive可以简单理解成我们平时说的通用模块或组件库。业务早期只有一个 HAR大家没觉得有什么问题但随着业务线扩张按功能拆模块、按 UI 拆组件、按团队拆工程HAR 数量迅速膨胀我们项目最多的时候同时存在 18 个 HAR。HAR 多到一定程度问题就暴露了构建时间越来越长因为要逐个模块编译依赖关系越来越复杂经常出现两个 HAR 同时依赖同一个底层库的不同版本还有一个隐蔽的问题——多个 HAR 各自携带资源文件上架打包后同一个资源被重复打进 HAP包体越来越大。最典型的一次我们发现一个网络库的 so 文件被 6 个 HAR 各带了一份最终包里出现了 6 份完全相同的二进制文件。这个阶段不改后面只会越来越痛。所以多 HAR 合并不是代码洁癖而是实打实的构建效率、包体规模、依赖安全三重诉求。1.3 从人工核对到提交自检上架审核的最后一公里签名搞定了、HAR 合并了以为就万事大吉了还差最后一步——提交审核。HarmonyOS 应用上架审核对版本号、权限声明、图标资源、隐私政策这些字段有严格要求而这些信息分散在 module.json5、build-profile.json5、工程资源目录等多个地方。人工核对一次要 20 到 40 分钟还容易漏项。我们做提交自检 2.0 的初衷很简单把上架提交前的人工核对项用脚本在 CI 打包后自动跑一遍发现问题直接让构建失败把审核驳回后返工变成提交前自动拦截。这个思路在移动端上架流程里不算新鲜但真正在 HarmonyOS 工程里完整落地、并且能和签名、打包、归档串成一条流水线的实践确实值得展开讲讲。2. 云管理证书自动签名把发布证书放进流水线2.1 云管理证书与传统本地证书的本质区别先说结论云管理证书和传统本地证书最大的区别在于私钥的存放位置和管理方式。对比项传统本地证书云管理证书私钥存放本地 p12 密钥库文件AGC 云端托管证书交接依赖团队间拷贝文件后台权限控制打包方式手动选择证书文件和 Profile打包工具自动拉取关联配置适用团队单人或小团队多人协作、CI 自动化泄露风险本地文件易丢失或泄露私钥不出云风险更低用生活化类比解释本地证书就像一把自己保管的实体钥匙钥匙丢了就得换锁云管理证书像是小区物业统一管理的门禁卡你在物业登记一下手机号进出小区时自动验证不用随身带钥匙。HarmonyOS 7 的云管理证书接入了 AppGallery Connect 的证书服务创建之后会生成对应的发布 Profile。你在工程里只需要配置用哪个云证书 ID 签名剩下的签名材料获取、证书匹配、摘要算法选择都是由构建工具在云端完成的。2.2 在 DevEco Studio 里配置云证书签名的完整步骤我按照当前我使用的 HarmonyOS 7 配套开发工具版本为例完整走一遍登录 AppGallery Connect 控制台进入目标应用的用户与访问 证书管理页面。创建发布证书类型的云证书填写证书名称。创建成功后会生成一个证书指纹SHA256这个指纹必须和应用签名信息一致。在同一页面创建发布 Profile选择刚才创建的云证书并关联应用包名bundleName。Profile 下载到本地一份后面配置工程要用。回到 DevEco Studio打开工程进入 File Project Structure Signing Configs勾选使用云证书不同版本菜单名称可能有差异核心是选 Cloud 模式。在弹出的配置里填入云证书 ID、Profile 文件路径签名算法保持默认的 SHA256withECDSA 即可。保存配置后构建工具会自动把签名配置写入工程的 build-profile.json5。配置完的效果是你不再需要把 p12 文件放进工程目录也不需要记住 storePassword 和 keyPassword。本地只留 Profile 文件它的权限控制由 AGC 后台管理。对应的工程配置片段长这样供你们对照{ app: { signingConfigs: [ { name: cloud-release, type: HarmonyOS, material: { certpath: , storePassword: , keyAlias: , keyPassword: , profile: release_profile.p7b, signAlg: SHA256withECDSA, storeFile: }, cloudCertificateId: C1234567890 } ] } }注意certpath、storePassword 这些字段在云证书模式下可以为空核心的关联信息是 cloudCertificateId 和 profile 指向的 p7b 文件路径。2.3 命令行与 CI 环境下的自动签名姿势DevEco Studio 里点出来的配置适合本地手动打包真正要提效必须让命令行和 CI 也能调用同一套签名。HarmonyOS 工程的构建命令基于 hvigor签名配置读取的是上面那份 build-profile.json5。你只需要在命令行执行hvigorw assembleHap --mode module -p productdefault构建工具就会根据模块里的签名配置自动完成签名。如果项目里有多个模块可以用 -p module 指定目标模块或者不指定直接构建默认模块。CI 环境下的做法稍微讲究一点。我的建议是把云证书 ID 和 Profile 文件路径做成 CI 变量不要在仓库里硬编码。GitLab CI 的流水线里可以这样做build-release: stage: build script: - echo CLOUD_CERT_ID$CLOUD_CERT_ID local.properties - hvigorw clean assembleHap --mode module -p productdefault -p buildModerelease artifacts: paths: - entry/build/default/outputs/default/*.hap构建完成后只需要把产物目录里的 HAP 包传到 AGC 后台做上架提交或用脚本上传分发。整个过程中没有任何人手动触碰证书文件。2.4 自动签名最常见的三个失败现场第一云证书指纹和应用签名不匹配。这种情况多数发生在应用包名变更或证书重建之后报错信息会提示签名校验失败。解决办法是在 AGC 后台核对应用的签名 SHA256 指纹是否与云证书生成时的一致。第二Profile 文件过期。发布 Profile 通常有有效期过期后构建工具会报Profile not valid。这个最坑因为它不是每次都在构建时报有时候是构建成功但安装到真机运行时系统提示签名异常。处理方式是在 AGC 后台重新生成 Profile替换本地路径。第三多模块工程的签名配置不一致。一个 HAP 工程如果有多个 HAR 依赖每个 HAR 可能都有自己的签名配置或 debug 配置。如果某个模块的签名配置没走上云证书最终 HAP 的签名串就会异常。排查方式是对每个模块逐一执行签名校验或者统一在工程根目录的 build-profile.json5 里覆盖签名配置避免子模块各自为政。3. 多 HAR 合并从 18 个共享包收敛到 5 个的工程化实践3.1 合并前先算清依赖账依赖树就是合并边界合并 HAR 之前我强烈建议先搞清楚两个问题每个 HAR 被谁依赖每个 HAR 依赖了谁这两个问题的答案就是合并的边界。HarmonyOS 工程里可以用 hvigor 的构建任务输出模块依赖关系。我用的方式是在工程根目录执行hvigorw --mode module -p productdefault --info构建日志里会打印模块依赖图也可以直接查看每个 HAR 的 oh-package.json5 里的 dependencies 字段把关系理一遍。我们当时的依赖关系大致是这样的base-utils被所有业务模块依赖base-network被大部分业务模块依赖依赖 base-utilscommon-ui被多个业务模块依赖依赖 base-utilsfeature-login依赖 base-network、common-uifeature-pay依赖 base-network、common-ui...我画了一张简化的依赖矩阵用来决定哪些 HAR 可以合并HAR 名称被谁依赖依赖谁合并建议base-utils全部无保持独立base-network大部分业务base-utils保持独立common-ui大部分业务base-utils保持独立feature-login仅入口模块common-ui, base-network可与相关业务模块合并feature-pay仅入口模块common-ui, base-network可与相关业务模块合并feature-share仅入口模块common-ui可与 feature-login 合并关键判断标准被多个业务模块复用的基础能力工具、网络、UI 组件继续保持独立只有单一入口、业务强相关的模块才适合合并。3.2 按业务域合并 HAR 的落地步骤确定了合并边界之后我按业务域把 HAR 分成了几组登录域feature-login、feature-share、支付域feature-pay、feature-order、公共基础域base-utils、base-network、common-ui 保持原样。具体合并操作是在目标 HAR 的 oh-package.json5 里添加依赖同时把被合并模块的源码目录迁移过来。举个例子我要把 feature-share 合并到 feature-login就在 feature-login 的 oh-package.json5 里把 feature-share 的源码包依赖进来{ name: feature-login, version: 1.0.0, description: 登录与分享业务合集, main: Index.ets, dependencies: { base-utils: file:../base-utils, base-network: file:../base-network, common-ui: file:../common-ui } }然后把 feature-share 的 ets 源码和 resource 目录按目录结构拷贝到 feature-login 的 src/main 下再更新导出入口 Index.ets把原来 feature-share 对外暴露的接口重新导出export { default as ShareService } from ./share/ShareService; export { default as ShareSheet } from ./share/ShareSheet;这一步完成后原来依赖 feature-share 的地方不需要改动因为它们 import 的还是同名 API只是物理文件位置变了。三组分别合并后HAR 数量从 18 个降到了 7 个后续构建日志明显清爽很多。3.3 合并后的四大验证项路由、资源、so、权限合并 HAR 不是搬完文件就结束我在实践中验证了四个方面缺一个都容易出线上问题。第一个是路由表冲突。如果被合并的 HAR 里有基于模块名的路由注册合并后可能出现两个模块注册同一个路由的情况。我在应用启动后的模块初始化逻辑里加了一个路由表 dump检查有没有重复 key。没有冲突才能继续。第二个是资源文件命名冲突。HAR 之间资源命名空间虽然理论上隔离但不同 HAR 里的 media 资源如果同名合并后可能会相互覆盖。我在执行合并后跑了一次资源检查脚本扫描 src/main/resources 下所有同名资源文件逐个确认是否需要重命名。第三个是 so 文件重复打包。这是包体最大的隐患。检查方法是解包最终生成的 HAP查看 libs 目录下的 so 文件列表凡是出现名字相同、MD5 相同的文件就说明重复打包了。解决方式是合并后统一清理各模块 libs 下的重复 so只保留一份并在构建脚本里做一次so 唯一性检查。第四个是权限声明带出。HAR 的 module.json5 里可能有自己的 requestPermissions 声明合并后会带进 HAP。我在提交自检脚本里加了一个权限白名单校验避免合并后意外把不必要的权限声明带入上架包。3.4 为什么我不建议在产物层强行合并 HAR市面上有人直接把多个 HAR 的产物解包、再把里面的资源文件塞进一个新的 HAR 里这种做法我不推荐。原因很简单HAR 不仅是文件集合它还携带了自己的配置文件、能力声明和依赖元数据。在产物层强行合并等于把多个模块的 metadata 揉在一起很容易出现依赖引用找不到、资源 ID 冲突、构建工具无法识别等问题。更麻烦的是后续一旦某个内部模块要升级你必须重新做一次产物合并自动化成本和维护成本都非常高。我推荐的方式是在工程源码层做依赖聚合让 hvigor 在构建时自然地把多个模块编译到同一个 HAR 或直接打进 HAP。这样合并的边界是清晰的、可追溯的后续改动也有版本管理兜底。如果你们的工程已经是用 HAR 产物包来分发的比如提供给其他团队使用那就更应该保持在源码级配置依赖而不是手动合并产物。4. 提交自检 2.0用脚本把上架审核环节前置4.1 上架被拒的高频原因对照表做自检之前我先把 HarmonyOS 应用上架审核常见的驳回原因列了一遍然后挨个转成可检查的规则。审核常见驳回原因对应的工程检查点检查类型版本号与后台不一致module.json5 的 versionCode/versionName配置比对权限声明超范围requestPermissions 白名单配置比对应用图标尺寸不全resources/base/media 下图标文件资源检查隐私政策链接缺失应用配置里的隐私声明字段配置比对备案信息不完整上架附件材料人工复核包名与 AGC 后台不一致bundleName 比对配置比对这些检查点全部可以用脚本自动完成。人工只需要在脚本无法判断的环节比如隐私政策文案本身是否合规做一次复核。4.2 核心自检脚本的数据来源和检查逻辑自检脚本的数据来源主要有三个module.json5模块配置、build-profile.json5构建配置、资源目录文件列表。我用 Python 写了一个检查脚本核心逻辑大致如下import json import os import hashlib APP_ROOT ./entry/src/main MODULE_CONFIG os.path.join(APP_ROOT, module.json5) BUILD_CONFIG ./build-profile.json5 def load_json5(path): # 这里用 json5 库解析因为 HarmonyOS 配置文件是 json5 格式 import json5 with open(path, r, encodingutf-8) as f: return json5.load(f) def check_version(): config load_json5(MODULE_CONFIG) app config[app] assert app[bundleName] com.example.app, bundleName 不匹配 assert app[versionCode] 1001001, versionCode 低于预期 assert app[versionName] 1.0.1, versionName 不一致 print([PASS] version check) def check_permissions(): config load_json5(MODULE_CONFIG) allowed {ohos.permission.INTERNET, ohos.permission.GET_NETWORK_INFO} request_permissions config.get(module, {}).get(requestPermissions, []) for perm in request_permissions: name perm[name] if name not in allowed: raise ValueError(f发现白名单外权限: {name}) print([PASS] permission check) def check_icon(): media_dir os.path.join(APP_ROOT, resources, base, media) required {foreground.png, background.png, icon.png} for name in required: assert os.path.exists(os.path.join(media_dir, name)), f缺少图标: {name} print([PASS] icon check) def check_hap_hash(hap_path): sha256 hashlib.sha256() with open(hap_path, rb) as f: for block in iter(lambda: f.read(4096), b): sha256.update(block) print(f[INFO] HAP SHA256: {sha256.hexdigest()}) if __name__ __main__: check_version() check_permissions() check_icon() check_hap_hash(./entry/build/default/outputs/default/entry-default-signed.hap) print(All checks passed.)这个脚本的精髓不在于检查项多而在于每一条检查都直接映射到上架审核的真实规则。比如 versionCode 的检查背后原因是应用程序市场要求版本号依次递增如果提交的版本号小于线上版本审核后台会自动驳回。4.3 从自检 1.0 到 2.0新增的检查项与判稳机制自检 1.0 是我们最早做的一版只检查了版本号和 bundleName属于最基础的兜底。自检 2.0 在 1.0 的基础上增加了四类检查项第一类是签名校验增强。自动签名跑完之后脚本会用签名工具主动读取 HAP 的签名信息验证签名证书的指纹是否与 AGC 后台的云证书指纹一致。这一项直接解决了打包成功但签错证书的隐蔽问题。第二类是 HAR 合并后的完整性检查。合并完 HAR 后脚本会检查最终 HAP 里是否存在重复的 so 文件、重复的资源名以及依赖模块是否能正常解析。这个检查结合了我们在合并阶段总结的验证项做成自动化。第三类是权限白名单校验。我们把每个应用允许声明的权限维护成一份白名单列表脚本逐个比对。新增权限需要先在白名单列表里登记并注明使用场景否则脚本直接报错。这一条逼着团队在开发阶段想清楚到底需不需要这个权限。第四类是上架材料清单检查。上架时经常要填版本说明、隐私政策链接、应用截图等材料脚本会生成一份上架材料清单把需要人工确认的项目逐条列出避免提交时发现缺文件。这属于半自动检查但很实用。另外2.0 版加了一个判稳机制所有检查项必须连续通过 3 次才允许进入正式提交流程。这个逻辑是为了防止 CI 偶发的网络超时或缓存问题导致误报通过。实践下来判稳机制确实拦住了一次因为 Profile 刚替换、本地缓存未刷新导致的假通过。4.4 和 CI 流水线串起来的完整提交流程自检脚本单独跑没意义要和打包、签名串起来才算完整。我现在的 CI 流水线是这样设计的代码合并到 release 分支后触发构建任务。构建任务执行 hvigor 打包自动使用云证书签名。打包完成后立即运行提交自检脚本。自检脚本读取签名后的 HAP执行上面说的四类检查。如果自检失败流水线中断构建产物不归档开发者收到失败通知。如果自检通过产物归档同时生成一份 Markdown 格式的上架自检报告包含 HAP 哈希、签名指纹、版本信息、权限清单、图标检查结果直接作为上架材料附件。整套流程跑下来从代码合并到拿到一份可提交的 HAP 和自检报告耗时不到 10 分钟而且全程不需要人工干预。5. 实际效果数据与避坑备忘5.1 一个真实版本从构建到可提交的耗时对比拿我们最近一次上版来说在重构前整个流程大致是下午 3 点开始准备 release 包先找证书、确认 Profile再手动编译多个 HAR 模块最后人工核对配置和材料折腾到 6 点多才把包传上去中间还因为 icon 尺寸漏了一张被打回一次。总耗时约 3 小时其中纯人工操作超过 1.5 小时。重构后同样的版本我下午 4 点把 release 分支代码推送4 点 10 分收到自检通过的通知4 点 15 分已经在上架后台填完了材料材料清单是脚本生成的照着复制就行。总耗时约 15 分钟人工操作不到 5 分钟。环节优化前耗时优化后耗时证书签名准备30~60 分钟0自动多 HAR 编译与打包40~90 分钟5~8 分钟上架前人工核对20~40 分钟3~5 分钟提交材料整理15~30 分钟0脚本生成总计2~3 小时10~15 分钟数字本身不夸张真正可贵的是这套流程把人的不确定性剔除了。换人、换电脑、换分支都不影响出包质量这对持续迭代的团队是实打实的解放。5.2 自动化上架链路中最值得记住的 5 个教训第一云证书模式也不是完全不用管证书。Profile 文件虽然不用手动上传了但它的有效期还是要盯着的。我在 CI 里加了一个Profile 剩余有效期的检查项低于 15 天就直接在流水线中告警防止发版时才发现过期。第二har 合并前一定要确定所有引用方都适配了新的导入路径。我们曾遇到过一个业务模块仍然通过旧路径 import 被合并掉的 HAR 的 API导致运行时模块找不到。合并后最好全仓库搜一遍旧模块名确认没有残留引用。第三自检脚本的权限白名单要定期更新。HarmonyOS 新版本有新权限能力但如果那个权限不是应用核心功能需要的建议默认不加进白名单。每加一个权限都要有审核场景支撑这个把关尺度直接影响上架通过率。第四CI 里使用云证书时需要给构建机配置 AGC 的访问凭证。不同团队的网络策略不一样有些构建环境访问 AGC 接口会被防火墙拦截导致签名步骤超时。提前和基础设施团队确认好构建机到 AGC 的网络连通性比什么都重要。第五自检脚本的报错信息要写得足够清晰。刚开始我们脚本失败时会报一堆 traceback开发者要花时间拆解。后来我把每条检查项包了一层 try-except失败时直接输出检查项名称 期望值 实际值三段式信息排查效率提升非常明显。5.3 后续还可以怎么扩展这套流程这套自动化上架流程跑稳之后我准备做两件事来进一步提效。一件是把自检报告和 AGC 后台的上架信息打通尝试用开放接口直接填入版本说明和材料信息省掉最后的复制粘贴环节。不过这块要结合团队的 AGC 权限模型来做不能做成通用方案。另一件是为多个应用做统一的证书和签名管理。现在我们是单应用流水线后续如果接手更多应用计划做一个小的配置中心把每个应用的证书 ID、Profile 路径、权限白名单、图标要求放到一个地方统一维护流水线启动时按应用拉取配置。这样一来新应用接入自动化上架的成本会从几天压缩到半天以内。最后分享一个真实的个人体会自动化这件事最怕的不是技术难点而是做了一半就停。我们 1.0 版本只做版本号检查跑了半年期间还是出现过一次 icon 缺失被驳回。后来下定决心把 2.0 补齐一次性把权限和签名校验全做进去才真正感受到提交前心里有底是什么体验。如果你也正在搭建这套流程建议先跑通最小闭环再逐步补检查项不要一开始就追求大而全。