把 Mockoon 搬上鸿蒙 PC:开源 API 模拟工具的完整移植与上架实战

欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/

欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper

适配开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_Mockoon

本文记录一次完整的开源桌面软件移植实战:将知名开源 API 模拟工具 Mockoon(Electron 43 + Angular 22)完整移植到 HarmonyOS PC 平台,以 HAP 形式承载上架应用市场,并顺利通过应用市场审核的全过程。文章覆盖技术选型、构建链路改造、签名配置、上架材料准备,以及一次真实的应用市场审核被拒(应用内弹窗名称与上架名不一致)的定位与修复全过程,附踩坑清单与可复现命令。

真机:设置弹窗

一、为什么选择 Mockoon

做后端联调的同学都体会过这种痛苦:前端页面写好了,后端接口还没就绪;或者第三方服务限流、收费、不稳定,测试环境里想模拟一个超时、一个 500 错误都费劲。API Mock 就是解决这类问题的——在本地快速搭建一个假的 REST API 服务,返回你定义好的状态码、响应头和响应体。

在这里插入图片描述

在众多 Mock 方案里,我最终选定了 Mockoon,理由有三个:

第一,它是真正的桌面软件,不是网页工具。 不需要把接口数据上传到任何云服务,纯本地运行,这对有数据安全顾虑的团队和个人都很关键。

第二,它是开源的(MIT 协议),并且代码质量很高。 GitHub 上持续维护,架构清晰:主进程、渲染进程、公共库分层明确,这意味着移植过程中遇到问题可以读源码解决,而不是黑盒猜测。

第三,它的功能密度足够支撑一个"工具类"上架应用。 可视化创建路由、CRUD 数据桶、动态响应模板、Faker.js 随机数据生成、请求代理转发、TLS、请求日志——这些能力让应用在应用市场上是有真实使用价值的作品,而不是一个空壳 Demo。

Mockoon 官网对它的定位非常直白:“the easiest and quickest way to design and run mock APIs. No remote deployment, no account required, free and open-source.”

Mockoon 官网

二、认识上游:技术栈与架构

动手之前必须先把上游摸透。Mockoon 当时的目标版本是 9.8.0,整个 monorepo 结构大致是:

mockoon-src/
├── packages/
│   ├── commons/            # 公共类型与工具(纯 TS 库)
│   ├── commons-server/     # Mock 服务端核心(基于 Express 封装)
│   ├── app/                # 桌面应用(本次移植主体)
│   │   ├── src/main/       # Electron 主进程(TypeScript + esbuild 打包)
│   │   ├── src/renderer/   # Angular 22 渲染进程
│   │   └── src/shared/     # 主/渲染进程共享类型
│   ├── cli/                # 命令行版(本次不涉及)
│   └── desktop/            # Electron 打包器(electron-builder,本次不涉及)
└── package.json            # npm workspaces

几个对移植决策影响很大的技术事实:

  • 渲染进程是 Angular 22,构建产物是静态文件(index.html + JS bundle + CSS),这决定了渲染层可以原样搬迁,不需要任何重写;
  • 主进程用 esbuild 打包成单文件 app.js,Electron 本身被声明为 external,这是后面构建策略改造的核心位置;
  • 数据存储走本地 JSON 文件(环境配置、设置项都是 JSON 落盘),没有 SQLite 之类的原生依赖,省掉了 Node 原生模块交叉编译这个大坑;
  • 服务端核心 commons-server 是纯 JS,起 HTTP 服务用的是 Node 的 http/express 生态,在鸿蒙侧只要 Node 运行时可用,这部分就是零成本。

先看一下原版在桌面上的样子,这是一个典型的三栏布局 Mock 工具:左侧环境列表、中间路由列表、右侧路由详情(请求方法、路径、状态码、响应体编辑器),顶部一排页签切换路由/数据/请求头/回调/日志/代理/设置。

Mockoon macOS 原版界面

在动手前,值得把它的核心能力逐个盘点一遍,因为这些能力决定了移植后的应用"值不值得上架":

可视化路由定义。 每条路由由请求方法(GET/POST/PUT/PATCH/DELETE 等)+ 路径 + 响应规则组成,路径支持 :param 参数和通配符匹配。所有配置都是表单化操作,不写一行代码就能起一个 API。

CRUD 数据桶。 这是它最"聪明"的功能:定义一个数据桶(Data bucket),一条路由配置自动展开成 GET/POST/PUT/DELETE 全套接口,数据持久化在本地 JSON 里,相当于白送一个带增删改查的假数据库。上图里的 users 路由就是这种模式。

动态响应模板。 响应体里可以嵌模板语法,用 Faker.js 生成随机姓名、邮箱、地址、UUID,用 {{body 'xxx'}} 回显请求参数,实现"每次请求返回不一样"的拟真效果。服务端核心 commons-server 里内置了完整的模板解析器。

请求代理与录制。 可以把本地 Mock 服务配置成反向代理指向真实后端,未命中 Mock 规则的请求透传给真实服务并录制响应,实现"渐进式 Mock"——联调初期全透传,逐步把稳定下来的接口替换成 Mock 规则。

请求日志。 每个环境有独立的请求日志页签,记录每次命中的路由、请求头、请求体、响应状态与耗时,是排查"为什么我的 Mock 没生效"的第一现场。

这些能力全部跑在主进程的 Node 运行时上,这正是路线 A(Electron 全量承载)能保真的根本原因:只要 Node 跑起来了,服务端核心一行不改。

三、移植路线选型:三条路的取舍

拿到一个 Electron 应用,往鸿蒙 PC 上搬,摆在面前的路有三条:

路线 A:Electron 全量承载(定制运行时)。 鸿蒙生态里有社区维护的 Electron 定制运行时(libelectron.so + libadapter.so + libffmpeg.so,通过 web_engine HAR 提供 WebWindowNode),可以把 Electron 应用原封不动跑起来。优点是改动量最小、UI 与功能 100% 保真;缺点是包体大(运行时 .so 本身就 170MB+),且依赖运行时与新系统版本的兼容性。

路线 B:ArkUI 原生重写。 用 ArkTS 把界面重写一遍,逻辑层保留 JS/TS 部分。理论上包体最小、体验最"原生",但对 Mockoon 这种功能密度极高的工具(几十个界面组件、Monaco 编辑器、树形控件、弹窗系统),重写工作量是以月计的,且很难保证行为一致。

路线 C:ArkWeb 加载 Web 版。 类似把 renderer 部分塞进 Web 组件,主进程逻辑另想办法。问题是 Mockoon 的核心价值恰恰在主进程(起本地 HTTP 服务、文件读写、系统对话框),Web 化之后这些能力全要重新桥接,改造量反而最大。

我选择了路线 A。决策依据:

  1. 我此前的移植实践已经验证过这条链路的可行性——定制 Electron 运行时在 HarmonyOS PC(API 24/26)上可以稳定拉起多进程(主进程 + GPU + NetworkService),关键是要用与新系统匹配的新版底座(Electron 37+ 底座,旧底座在新系统上会因 NAPI 注册失败直接闪退);
  2. Mockoon 的三个适配障碍(依赖打包、入口扩展名、资源路径)都是构建期问题,改配置就能解决,不涉及运行时逻辑改写;
  3. 作为上架作品,"运行的是真正的 Mockoon"本身就是说服力——功能完整性远比包体大小重要,工具类应用用户对安装包体积的容忍度也高。

把三条路线的关键维度摆在一起看更清楚:

维度A:Electron 全量承载B:ArkUI 原生重写C:ArkWeb 混合
改动量构建期配置级界面层全部重写主进程能力全部重桥接
功能保真100%取决于重写投入渲染层保真、主进程重写
包体200 MB 级最小中等
上手周期天级月级周级
后续跟版同步上游成本低每次升级手工对齐升级需重做桥接
适合对象功能完整的成熟工具界面简单的轻工具以网页为主的工具

选型时还有两个隐性成本容易被低估:一是维护成本——上游 Mockoon 迭代很快,路线 A 下同步一个新版本基本等于"重新构建 + 重新同步产物",路线 B 下每次上游升级都要人工对齐几十处界面差异;二是验证成本——重写版本和原版的行为差异需要逐功能回归,而承载版本天然与原版一致,回归范围只有运行时兼容性一层。

当然,路线 A 不是无脑最优:如果目标应用重度依赖 Node 原生模块(SQLite、native 加密库等),交叉编译 aarch64 的 .node 文件会是另一场硬仗;如果目标应用是单窗口轻界面,路线 B 的原生体验回报则更高。选型的本质是让改动量与保真需求匹配,而不是套用统一答案。

定下路线后,整个移植的本质就变成了两件事:把 Electron 应用改造成能在鸿蒙沙箱里自包含运行的形态,以及把它装进鸿蒙的 HAP 包壳里并签好名。

四、工程结构设计

最终工程目录结构如下:

ohos_Mockoon/
├── mockoon-src/                  # 上游源码 monorepo(构建前端产物用)
│   ├── packages/app/             # 改造后的应用包
│   └── package-lock.json
└── ohos_hap/                     # DevEco HAP 工程
    ├── AppScope/app.json5        # bundleName: com.mockoon.ohos
    ├── build-profile.json5       # 签名与产品配置
    ├── oh-package.json5
    ├── electron/                 # entry 模块(宿主)
    │   ├── libs/arm64-v8a/       # libelectron/libadapter/libffmpeg 预编译 so
    │   └── src/main/ets/         # Ability + 页面(拉起 WebWindowNode)
    └── web_engine/               # HAR:Electron 适配框架 + 应用资源
        └── src/main/resources/resfile/resources/app/
            ├── main.cjs          # esbuild 打包的 Mockoon 主进程(6.1 MB)
            ├── preload.cjs       # contextBridge 桥
            ├── package.json      # main: main.cjs, dependencies: {}
            └── renderer/         # Angular 构建产物(3.1 MB)

两个设计要点:

双模块分工。 electron 模块是 HAP 的 entry,负责 Ability 生命周期和通过 import { WebWindowNode } from 'web_engine' 拉起 Web 窗口节点;web_engine 是 HAR 包,携带适配框架和全部应用资源。应用资源放在 resfile/resources/app/ 下,这是鸿蒙应用沙箱内随包分发的标准位置。

产物与源码分离。 mockoon-src 只负责产出四个东西:app.js(主进程)、preload.js(桥)、renderer/(Angular 静态产物)。构建完用脚本同步进 web_engine 的 resfile,HAP 工程本身不包含任何 TS/JS 源码。这样上游升级时只需要重新构建同步,HAP 壳完全不用动。

五、四个关键适配点

路线定了,真正动手时面对的是四个具体的坑。这四个点是整个移植里最核心的技术内容。

5.1 esbuild 打包策略:external 改 bundle

上游的 esbuild 配置里,packages 选项是 'external'——所有 npm 依赖不打进产物,运行时从 node_modules 解析。这在常规桌面分发里没问题,因为 electron-builder 会把 node_modules 一起打进安装包。但鸿蒙 HAP 内没有 node_modules,产物必须自包含。

所以第一刀砍在 packages/app/src/main/esbuild.config.js:

// packages/app/src/main/esbuild.config.js
 export const buildConfig: BuildOptions = {
   entryPoints: ['src/main/main.ts'],
   bundle: true,
   platform: 'node',
-  packages: 'external',
+  packages: 'bundle',
   external: ['electron'],   // 仅 electron 保持 external,由鸿蒙运行时提供
   outfile: 'dist/app.js'
 };

external: ['electron'] 必须保留——electron 模块在鸿蒙侧由 libadapter 运行时注入,打包进去反而找不到。其余所有依赖(Express、RxJS 等几十个包)全部打进单文件。改完之后 app.js 从几百 KB 涨到 6.1 MB,这是预期内的。

5.2 入口文件扩展名:preload.js → preload.cjs

鸿蒙侧的应用 package.json 把 main 指向 main.cjs。之所以用 .cjs 后缀,是为了强制 CommonJS 解析语义(Electron 主进程传统上是 CJS),避免被运行时按 ESM 处理时 require 报错。preload 同理:

// packages/app/src/main/libs/main-window.ts
   const mainWindow = new BrowserWindow({
     width: mainWindowState.width,
     height: mainWindowState.height,
     title: '月桥接口',
     backgroundColor: '#252830',
-    preload: pathJoin(__dirname, 'preload.js'),
+    preload: pathJoin(__dirname, 'preload.cjs'),
     icon: pathJoin(__dirname, 'renderer/assets/icon.png'),
     webPreferences: { ... }
   });

对应的部署脚本里也要做同名映射:dist/app.js → main.cjs,dist/preload.js → preload.cjs。顺手在 main-window.ts 里改了窗口标题(这里是应用内窗口标题的源头之一)和图标路径。

5.3 渲染进程产物部署路径

Electron 主进程里加载渲染页面的代码通常是 loadFile('renderer/index.html') 这类相对路径。鸿蒙侧 resfile 布局里应用根就是 resfile/resources/app/,所以 Angular 构建产物要部署到 renderer/ 子目录,与主进程 loadFile 的路径约定严格对齐:

APP_DIR=ohos_hap/web_engine/src/main/resources/resfile/resources/app
SRC_DIR=mockoon-src/packages/app/dist

cp $SRC_DIR/app.js      $APP_DIR/main.cjs
cp $SRC_DIR/preload.js  $APP_DIR/preload.cjs
cp $SRC_DIR/renderer/index.html     $APP_DIR/renderer/
cp $SRC_DIR/renderer/main-*.js      $APP_DIR/renderer/
cp $SRC_DIR/renderer/polyfills-*.js $APP_DIR/renderer/
cp $SRC_DIR/renderer/styles-*.css   $APP_DIR/renderer/
cp -r $SRC_DIR/renderer/assets      $APP_DIR/renderer/

注意 main-*.js 是带内容 hash 的文件名(比如 main-CP2B434R.js),每次重新构建 hash 都会变。同步脚本必须先删旧的 main-*.js 再拷新的,否则旧 bundle 残留在包里,既浪费体积,更危险的是旧代码还在——我后来修复审核问题时就特意验证了这一点。

完整的同步脚本(PowerShell 版,含旧产物清理)建议固定下来放在工程根目录,每次构建后一键执行:

$APP = 'ohos_hap\web_engine\src\main\resources\resfile\resources\app'
$SRC = 'mockoon-src\packages\app\dist'

Copy-Item "$SRC\app.js"     "$APP\main.cjs"    -Force
Copy-Item "$SRC\preload.js" "$APP\preload.cjs" -Force
Remove-Item "$APP\renderer\main-*.js"          -Force   # 先删旧 hash bundle
Copy-Item "$SRC\renderer\index.html"     "$APP\renderer\" -Force
Copy-Item "$SRC\renderer\main-*.js"      "$APP\renderer\" -Force
Copy-Item "$SRC\renderer\polyfills-*.js" "$APP\renderer\" -Force
Copy-Item "$SRC\renderer\styles-*.css"   "$APP\renderer\" -Force
Copy-Item "$SRC\renderer\assets\*"       "$APP\renderer\assets\" -Recurse -Force

一个部署后的体积参考:主进程 main.cjs 6.1 MB,渲染产物(main bundle + polyfills + styles + assets)约 3.1 MB,加上 Angular 的懒加载分片,应用自身资源总共不到 10 MB。对比动辄数百 MB 的运行时 so,应用层是"轻"的——这也说明包体优化未来唯一的抓手在运行时裁剪,与应用逻辑无关。

5.4 资源路径相对化

这是四个点里最隐蔽的一个。Angular 构建的 index.html 里,<base href="."> 配合资源引用,在常规桌面环境从文件协议加载没问题,但在鸿蒙的 WebWindowNode 里,绝对路径(/assets/xxx)会落到沙箱根而不是应用目录,直接 404 白屏。

改造两处:

<!-- packages/app/src/renderer/index.html -->
-  <base href="." />
+  <base href="./" />

同时把 angular.json 里的资源部署配置改为相对引用,确保构建出来的 HTML 里全部是 ./main-xxx.js、./styles-xxx.css 这种形态。改完可以用一个快速自检:构建产物 index.html 里 grep 一遍,不允许出现以 / 开头的资源引用。

六、构建流水线:从源码到 HAP

四个适配点改完,完整的构建流水线如下(Windows 环境实测通过):

# ① 构建公共库(commons + commons-server)
cd mockoon-src
npm install --legacy-peer-deps
npm run build:libs

# ② 构建应用(Angular 渲染进程 + esbuild 主进程)
npm run build:prod:all --workspace=@mockoon/app

# ③ 部署产物到 HAP 资源目录(见 5.3 的脚本)
# ④ 构建 HAP
cd ../ohos_hap
ohpm install          # 生成 electron/oh_modules/web_engine 软链,不可省略
hvigorw assembleHap --mode module -p product=default

流水线里有几个环境细节值得单独说:

Node 版本卡点。 Angular CLI 22 对 Node 有硬性下限(≥ 22.22.3),低版本构建会直接报引擎不匹配。本机用 Node v24.x 通过。

Windows 上跑上游构建脚本。 上游 clean-dist 脚本写的是 rm -rf ./dist,Windows 的 cmd 里没有 rm,npm run build:prod:all 第一步就挂。两种解法:要么给系统装POSIX 工具,要么绕开聚合脚本手动分步执行:

Remove-Item -Recurse -Force .\dist
npx ng build --configuration=production
node ./src/main/esbuild.config.js --mode production

hvigor 的环境变量。 命令行构建 HAP 需要 JAVA_HOME 指向 DevEco 自带 jbr(PackageHap 任务要起 java 进程),DEVECO_SDK_HOME 指向 DevEco SDK 目录,并把 jbr\bin 前置到 PATH:

$env:JAVA_HOME='C:\Program Files\Huawei\DevEco Studio\jbr'
$env:DEVECO_SDK_HOME='C:\Program Files\Huawei\DevEco Studio\sdk'
$env:PATH="C:\Program Files\Huawei\DevEco Studio\jbr\bin;$env:PATH"
hvigorw.bat --mode module -p module=electron@default -p product=default assembleHap --no-daemon

ohpm install 是隐性前置。 electron 模块通过 import { WebWindowNode } from 'web_engine' 引用本地 HAR,这个依赖解析靠 ohpm install 在 electron/oh_modules/ 下生成的软链。漏掉这一步会收获二十多个 ArkTS:10905204 编译错误,报错信息完全不指向根因,属于"报错在东、病灶在西"的典型。

全链路第一次跑通后,产出签名 HAP 约 205 MB,其中 libelectron.so 占 169 MB,应用自身产物(main.cjs + renderer)不到 10 MB。

七、签名配置:三个经典报错

签名是鸿蒙上架的第一道关卡,这次把三个高频报错全踩了一遍。

00303074(SignHap 失败)。 profile 或证书路径失效、密码不匹配导致。处理方式是在 DevEco Studio 的 Signing Configs 里走自动签名,重新生成本 bundleName 的调试材料。

9568320(no signature file)。 最迷惑的一个:签名步骤显示成功,但装到设备上时报"没有签名文件"。根因是 build-profile.json5 里 products[].signingConfig 是空字符串——signingConfigs 数组里定义了签名配置,但 product 没有引用它,构建全程跳过签名,产出的是 unsigned 包。修复就一行:

"products": [{
  "name": "default",
  "signingConfig": "default",   // 必须与 signingConfigs[].name 一致
  ...
}]

9568322(not trusted app source)。 用了 release profile 的包走 hdc 直接安装时出现——正式签名的包只能走应用市场通道分发安装,真机日常调试要用 debug 签名的包。这是签名体系的设计而非 bug,但要提前知道,否则会误判成签名坏了。

另外一个容易忽略的约束:签名 profile 与 bundleName 是绑定的。换包名必须重新生成 profile,拿 A 应用的 profile 签 B 应用的包必然失败。上架用的 release 证书、调试用的 debug 证书建议从第一天就分开管理。

八、上架准备:应用命名与商店素材

技术链路通了之后进入上架环节。这一环节的头号决策是应用命名。

直接以"Mockoon"这个名字上架是不合适的:一是名称归属,Mockoon 是上游项目的名称,移植分发版本需要一个自己的产品名;二是应用市场要求应用名称在商店、安装后的桌面图标、应用内展示三处保持一致,这个一致性要求后面还会再出现一次(就是审核被拒的那次)。

最终定名"月桥接口"——"月桥"作为产品名有辨识度,"接口"点明工具属性。应用内窗口标题、about 信息统一用"月桥接口(From Mockoo)"的形态,既保持一致又注明技术来源,尊重上游。

商店素材方面,工具类应用的核心是把"能用它干什么"在三张图内讲清楚。我做了两张特性图:

第一张主打开箱即用的 CORS 配置——这是前端联调场景的刚需,新建环境自动预置 Content-Type 和完整的 CORS 跨域请求头,浏览器直接可调:

商店特性图:CORS 开箱即用

第二张主打隐私与本地运行——所有数据仅保存在本机、无需注册登录,这是相对云端 Mock 平台的差异化卖点:

商店特性图:细节可调/隐私可控

一句话简介按 15 字限制打磨:“鸿蒙本地一键模拟接口调试工具”。应用描述里把功能密度讲全:可视化创建模拟 API、多环境管理、动态响应模板、随机数据生成、请求代理转发、请求日志查看。

商店素材的制作有几个实操层面的规范值得记录:

截图与宣传图分开做。 纯截图(真实界面 + 真实数据)负责"可信",宣传图(提炼卖点 + 大字标语 + 特性图标)负责"好懂",两类图按顺序排布,前一张讲清楚是什么,后几张讲清楚好在哪。

宣传图内的界面必须与实际版本一致。 图里嵌的应用窗口截图要用最终版本的界面,窗口标题、汉化文案、菜单结构都要和用户装到手里的一致,否则图文不符本身就会引发审核或用户投诉。

文字信息控制在三行以内。 工具类应用的商店图在手机上会被缩略浏览,每张图传递一个卖点就够:第一张"CORS 开箱即用",第二张"隐私可控/本地运行",各自配一句副标题说明,不贪多。

隐私声明与权限说明提前写。 工具类应用常被要求补充说明"为什么需要这些权限"和"数据存在哪里",这个应用的答案是现成的:全部数据本地沙箱内 JSON 存储,无敏感权限申请,材料里直接如实陈述即可。

上架页在应用市场审核通过后的最终形态如下,分类"工具",安装后桌面上就是"月桥接口":

应用市场商店页

九、审核被拒实战:欢迎弹窗的定位与修复

上架材料提交后收到了应用市场的审核意见,原文是:

应用内引导弹窗模块展示的应用名称与在 AppGallery Connect 上提交的应用名称不一致,不符合相关法律法规要求。

这个审核意见非常精确地命中了我们前面在"命名一致性"里埋的雷:首次启动时,应用会弹一个欢迎引导弹窗,弹窗标题赫然写着"欢迎使用 Mockoon!“,正文里还有两处 Mockoon 字样和三个指向 mockoon.com 的外链。商店里提交的名字是"月桥接口”,用户装上打开第一眼看到的却是另一个名字——这确实不合规,该改。

9.1 定位弹窗的触发链路

这个弹窗是上游 Mockoon 的欢迎引导组件。顺着源码往下挖,完整触发链路是三层:

第一层:弹窗组件本体 welcome-modal.component.html,标题模板:

<h4 class="modal-title">欢迎使用 Mockoon{{ isWeb ? ' Web' : '' }}!</h4>

第二层:注册 ui.service.ts 里以 'welcome' 为名注册到模态框注册表:

welcome: {
  component: WelcomeModalComponent,
  options: commonConfigs.medium
},

第三层:触发条件 settings.service.ts 的 loadSettings():

public loadSettings(): Observable<any> {
  return this.storageService.loadSettings().pipe(
    tap((settings: Settings) => {
      // ... 更新日志弹窗逻辑 ...
      if (!settings.welcomeShown) {
        this.uiService.openModal('welcome');   // ← 首次启动在这里弹出
      }
    })
  );
}

welcomeShown 是一个设置项,默认 false,用户点掉弹窗后写回 true。所以每个新装用户首次启动必弹。

9.2 修复方案:为什么不是简单改个默认值

修这个问题有三种思路,权衡之后选了第三种:

思路一:把弹窗文案里的 Mockoon 全改成月桥接口。 能过审,但正文里"由数十位贡献者共同构建的免费开源项目""支持我们"这些上游社区的表述对月桥接口的用户来说是错位的,而且弹窗本身对用户价值不大——这只是个介绍页。

思路二:把 SettingsDefault.welcomeShown 默认值从 false 改成 true。 一行改动,弹窗不弹了。但这一刀会误伤——往下追一层就发现了:

// environments.service.ts
if (!settings.environments.length && !settings.welcomeShown) {
  // First load: create one blank local environment
  const defaultEnvironment = { ...BuildEnvironment({...}), name: '新环境' };
  // ... 写入存储并加载
}

首次启动时自动创建一个空白环境的逻辑,复用了 welcomeShown === false 作为"这是新用户"的判据。如果默认值改成 true,新用户第一次打开应用会是一个空列表、无环境的裸界面,交互起点直接坏掉。

思路三(采用):只摘除弹窗触发,保留状态语义。 在 settings.service.ts 里删除 openModal('welcome') 调用,welcomeShown 保持默认 false 不动:

public loadSettings(): Observable<any> {
  return this.storageService.loadSettings().pipe(
    tap((settings: Settings) => {
      const validatedSchema = SettingsSchema.validate(settings);
      this.updateSettings(validatedSchema.value);
      settings = validatedSchema.value;

      if (semverGt(Config.appVersion, settings.lastChangelog)) {
        this.uiService.openModal('changelog');
        this.updateSettings({ lastChangelog: Config.appVersion });
      }

      // 欢迎引导弹窗已移除(应用上架名称与上游名不一致,按审核要求不再展示)。
      // welcomeShown 保持默认 false:environments.service 依赖它做首次启动建环境。
    })
  );
}

这个方案的副作用分析:welcomeShown 永远停在 false,唯一的消费方除了首启建环境,还有 web 版的登录弹窗判断(if (!user && settings.welcomeShown)——本地版没有云登录,不受影响);以及"用户删光所有环境后重启会再建一个空环境",这恰好符合"给用户一个可用起点"的产品意图,可以接受。

顺带检查了另一个首启弹窗——更新日志弹窗(changelog modal),它的标题是"更新日志 vX.Y.Z",不带 Mockoon 字样,无需处理。

9.3 整改提交与复核要点

修复出包后重新提审,复核阶段建议主动做两件事:

第一,在提审备注里写明整改内容。 简明告知审核方:已移除首启欢迎弹窗,应用内所有用户可见名称已与 AGC 提交名称统一。审核人员复核时能直奔整改点,缩短复核周期。

第二,自己先把"首次启动全路径"过一遍。 审核方测的是真实体验路径:全新安装 → 首次启动 → 逐个出现的弹窗和页面。开发者自己用"卸载重装 + 清数据"的方式走一遍同样的路径,把每一个弹窗的标题、正文、按钮文案都截图核对,比等审核意见来发现问题快得多。这次的问题如果提审前做过这个自查,一轮审核周期就省下来了。

另外提醒一个容易连坐的点:商店截图与应用截图也要同步核对。如果宣传图里嵌的是旧版界面截图,而弹窗已经改了,图内图外不一致同样可能引起审核质疑。商店素材图是重新制作的(图内窗口标题用的是"月桥接口(From Mockoo)"的统一形态),所以这次没有连带问题。

9.4 重建、部署与自验证

源码改完走一遍完整流水线:

# 渲染进程重建(Windows 下绕开 rm,手动清理)
Remove-Item -Recurse -Force .\dist
npx ng build --configuration=production
# → main-CP2B434R.js(新内容 hash)

# 主进程重建
node ./src/main/esbuild.config.js --mode production
# → dist/app.js 6.1mb, dist/preload.js 1.2kb

# 同步到 resfile(先删旧 hash bundle 防残留)
Remove-Item "$APP\renderer\main-*.js" -Force
Copy-Item "$SRC\renderer\main-*.js" "$APP\renderer\" -Force
# ... 其余产物同步

# HAP 重新构建签名
hvigorw.bat --mode module -p module=electron@default -p product=default assembleHap --no-daemon
# → BUILD SUCCESSFUL, electron-default-signed.hap (206 MB)

部署完成后做了一个产物级自验证——直接在打包进 HAP 的最终 bundle 上确认触发逻辑已经消失,而不是只看源码:

grep 检索目标:openModal("welcome") / welcomeShown
检索文件:resfile/.../renderer/main-CP2B434R.js
结果:0 命中 ✓

这一步很有必要:如果同步脚本漏删旧 bundle 或者拷错了目录,源码对了但包是旧的,装到设备上弹窗照弹。产物级验证能当场暴露这类"构建链路和部署链路不一致"的问题。

9.5 复盘:审核意见其实是产品体检

回过头看,这次被拒的修复成本极低(删 3 行代码 + 重构建),但它暴露的问题是普遍性的:移植/套壳类应用最容易翻车的点不在技术,在"应用身份"的一致性。商店名、桌面名、窗口标题、应用内弹窗、关于页——所有用户能看见名字的地方必须是同一个名字。建议每个做移植上架的开发者在提审前自己过一遍这个清单,尤其是首次启动路径上的每一个弹窗。

十、真机效果验证

重新出包后卸载重装,在鸿蒙 PC 真机上做了完整的启动路径验证:欢迎弹窗不再出现,应用直接进入主界面。

首次进入是一个干净的空环境状态——左侧环境列表里已经自动创建了"新环境",路由页签显示"未定义路由",等待用户创建第一条 Mock 路由:

真机:首次启动路由页(无欢迎弹窗)

新建环境自动预置了四条请求头,Content-Type 为 application/json,外加完整的三段 CORS 跨域请求头(Allow-Origin/Allow-Methods/Allow-Headers),右下角还有"添加 CORS 请求头"的快捷入口——这就是商店图里"开箱即用的 CORS 配置"的真机实证,前端页面跨域调用本地 Mock 服务不需要任何手工配置:

真机:请求头页(自动预置 CORS)

设置弹窗功能完整可用:界面选项、环境变量前缀(MOCKOON_)、环境日志容量、ISO 时间格式开关、Faker.js 数据生成的区域与随机种子、启动行为等全部可调,汉化完整:

真机:设置弹窗

从进程角度看,Electron 定制运行时在鸿蒙上稳定维持主进程 + GPU 进程 + NetworkService 的多进程结构,长时间挂机无崩溃、无闪退,本地 HTTP 服务的生命周期跟随环境启停按钮控制。

10.1 五分钟上手:从空环境到第一条 Mock 接口

光看界面截图还不够,这里给一个完整的上手流程,也是验证移植质量的功能用例:

第一步,启动环境。 打开应用,左侧已经有了自动创建的"新环境",本地端口默认从 3000 起。点击顶部的绿色启动按钮,环境状态变为运行中,本地 HTTP 服务开始监听回环地址。

第二步,创建路由。 在"路由"页签点"添加路由",请求方法选 GET,路径填 /api/hello,状态码保持 200。响应体里填入一段带模板的内容:

{
  "message": "你好,月桥接口",
  "requestId": "{{faker 'string.uuid'}}",
  "timestamp": "{{now 'iso'}}"
}

第三步,发起请求验证。 在同一台机器的终端里用 curl 打一下:

curl http://127.0.0.1:3000/api/hello
# {"message":"你好,月桥接口","requestId":"3f8a...","timestamp":"2026-10-30T..."}

每次请求 requestId 都不同——说明 Faker.js 模板引擎在鸿蒙侧的 Node 运行时里完整工作。

第四步,看日志。 切到"日志"页签,刚才这次请求的方法、路径、请求头、响应耗时全部在列。联调时"我的请求到底发出去什么样"在这里一目了然。

第五步,跨域验证。 在浏览器控制台里用 fetch('http://127.0.0.1:3000/api/hello') 请求任意本地页面,请求直接成功——因为环境预置的 CORS 请求头已经放行了跨域,这正是前端联调场景里最省心的一环。

这五个步骤全部在真机上跑通,覆盖了路由引擎、模板引擎、HTTP 服务、日志、CORS 五个核心子系统,可以作为移植质量的基线用例。

十一、安全与隐私设计

工具类应用过审,隐私合规是必答题,这个应用在这块有天然优势,但值得把设计显式讲出来:

数据全本地。 环境配置、路由定义、请求日志全部以 JSON 文件形式保存在应用沙箱内的本地存储,不经过任何云端。没有账号体系,没有遥测上报。

服务只听本地回环。 Mock 服务默认绑定 127.0.0.1,仅本机可访问,不对外网暴露端口——这也是它作为开发工具的合理安全边界。

权限最小化。 HAP 的权限声明只保留运行时必需项,不申请位置、通讯录、存储等与功能无关的敏感权限;应用内唯一的外部访问是更新日志弹窗对 GitHub release notes 的拉取,且失败时静默降级为"更新日志加载失败"提示,不影响功能。

上架名与内部标识的合规一致性。 bundleName、签名 profile、AGC 应用、商店名、应用内展示五处统一在"月桥接口 / com.mockoon.ohos"体系下,这正是本次审核意见的整改落点。

商店特性图:本地运行更安心

十二、踩坑清单汇总

把全过程的坑浓缩成一张表,后来者可以按图索骥:

#坑现象根因解法
1node_modules 缺失运行时找不到模块HAP 内没有 node_modulesesbuild packages: 'bundle' 全量打包
2preload 加载失败渲染进程无桥ESM/CJS 语义改 .cjs 后缀强制 CJS
3渲染页白屏资源 404绝对路径落沙箱根base href="./" + 相对引用
4rm 不识别构建第一步挂上游脚本面向 POSIXWindows 手动分步构建
5Angular 构建引擎报错node 引擎不匹配Node 版本过低Node ≥ 22.22.3
6ArkTS:1090520420+ 编译错误oh_modules 软链缺失ohpm install
7spawn java ENOENTPackageHap 失败java 不在 PATHjbr 前置 PATH + JAVA_HOME
8SignHap 00303074签名失败材料/密码失效DevEco 自动签名重生成
99568320 无签名文件装包报未签名product 未引用 signingConfig"signingConfig": "default"
109568322 不受信来源hdc 装正式包被拒release 包不走 hdc真机调试用 debug 包
11旧 bundle 残留改动不生效hash 文件名不同的旧 js 未删同步脚本先删 main-*.js
12审核被拒弹窗名与上架名不一致欢迎弹窗写死上游名摘除触发逻辑,保留建环境判据

十三、复现指南

如果想把这套流程完整走一遍,最小复现路径如下:

  1. 准备源码:克隆上游 Mockoon 仓库并检出目标版本,按第五章的四个适配点修改(合计约 10 行 diff);
  2. 准备运行时:获取鸿蒙 Electron 定制底座(libelectron/libadapter/libffmpeg 三件套 + web_engine HAR),按第四章结构放入 electron/libs/arm64-v8a 与 web_engine;
  3. 构建产物:Node 22+ 环境下 npm install --legacy-peer-deps && npm run build:libs,再按 5.3 的同步脚本部署;
  4. 组装 HAP:ohpm install 后用 hvigorw 构建,签名材料用 DevEco 自动签名生成;
  5. 真机验证:按 10.1 的五步用例过一遍核心功能;
  6. 上架:AGC 创建应用 → 提交材料 → 按第九章的命名一致性清单自查后提审。

整个过程里真正需要"理解上游"的只有四个适配点,其余都是工程操作,这也是本文把它写成教程的原因:方法可复制。

十四、常见问题 QA

这一章把移植过程中最容易被问到的问题集中回答一遍,按"选型判断 / 构建排错 / 签名上架 / 运行使用"四类组织。

14.1 选型与可行性

Q1:Electron 承载方案包体动辄 200 MB,值得吗?

要看目标应用和使用场景。对开发工具类应用,用户的使用周期长、安装频次低,包体换来的功能 100% 保真是划算的;对一个只查一次天气的小工具,显然不划算。另外要区分"值不值"和"能不能优化":运行时 .so 占了绝大部分体积,真正由应用层带来的不到 10 MB,若未来需要瘦身,抓手在运行时裁剪(去掉用不到的编解码模块、精简 ffmpeg),而不是精简业务代码。

Q2:这套方法能套用到别的 Electron 应用上吗?判断标准是什么?

可以看三个条件:一是有无 Node 原生模块依赖(.node 文件),没有最好,有则要做 aarch64 交叉编译,成本陡增;二是渲染进程的产物形态,Angular/React/Vue 构建出的静态资源都能原样承载,纯靠 ESM 动态 import 的服务端渲染模式要额外处理;三是数据层是否依赖外部二进制(SQLite、Redis、FFmpeg 可执行文件等),每多一个外部依赖就多一层移植。满足前两条的 Electron 应用,基本都能复用本文的四个适配点。

Q3:上游 Mockoon 出新版本了,跟进成本高吗?

低。因为所有自定义改动都压在构建配置和资源路径上,运行时逻辑一行没动,跟版的上限操作就是:拉上游新代码 → 检查四个适配点对应的配置文件在 diff 里是否被改过 → 重新构建 → 同步产物 → 出包回归。真正需要注意的是"上游是否引入了新的原生依赖",每跟进一个版本先看一遍 package.json 的 dependencies 变化,就能提前规避大多数问题。

14.2 构建与排错

Q4:渲染进程白屏,第一步应该查什么?

三步定位:先确认 index.html 里还有没有以 / 开头的资源引用(相对路径没改干净是首因);再确认 main bundle 的文件名与部署目录里的文件是否同 hash(旧 bundle 没删干净或同步漏拷);最后确认主进程 loadFile 的路径与产物实际落盘路径一致。三处都正常再看运行时报错——打开渲染进程的控制台,看是 404 还是 JS 运行时异常,两者的排查方向完全不同。

Q5:为什么源码改了,打包出来的行为没变?

九成是三个原因之一:旧产物残留(hash 文件名的 bundle 没删)、同步目标写错(改了 web_engine 但 HAP 其实从 electron 模块的 intermediates 取资源,或反之)、构建没实际执行(聚合脚本在第一步 rm 就失败了,后面两步压根没跑,用的还是 dist 里的老产物)。排查手段是"产物级验证":不看源码,直接在最终打包进 HAP 的那个 bundle 上 grep 关键字,能立刻区分"没改到"和"没部署到"。

Q6:Windows 上构建上游项目总报 'rm' is not recognized 怎么办?

上游脚本默认 POSIX 环境。两种解法:给系统补 POSIX 工具(Git Bash、MSYS2 的 coreutils 加入 PATH),或者绕开聚合脚本、手动分步执行它的每一条命令。后者更可控,代价是要自己知道聚合脚本里到底串了哪几步(可以从 package.json 的 scripts 里读出来)。

Q7:ArkTS:10905204 报了一屏,是不是代码写错了?

绝大多数情况下不是。这个错误指向模块解析失败,典型根因是本地 HAR 依赖的软链缺失——import ... from 'web_engine' 依赖 ohpm install 生成的 oh_modules/web_engine 链接。执行 ohpm install 后全量重编即可。它的迷惑性在于报错位置全部落在业务代码上,而病灶在依赖图上。

14.3 签名与上架

Q8:换了 bundleName,原来的签名材料还能用吗?

不能。签名 profile 与 bundleName 绑定,换包名必须重新生成 profile 和证书。建议每个应用的签名材料单独管理、目录隔离,避免复制粘贴 A 项目的材料给 B 项目用时只改了路径没改 profile 内容——这类错误报出来的往往是 00303074 这类通用签名失败,很难一眼看出是"串了项目"。

Q9:正式签名包为什么用 hdc 装不上?

这是分发通道的设计约束,不是签名坏了:release 签名的包只能通过应用市场通道安装,因为它默认被归类为"正式分发渠道"的应用;日常真机调试用 debug 签名的包走 hdc 安装。常见的误判是"签名工具坏了"或"包有问题",实际换 debug 包立刻就通。

Q10:应用名称到底要在哪些地方保持一致?

至少五处:AGC 上创建的应用名称、商店展示名称、安装后桌面图标下的名称、应用内窗口标题、以及所有首次启动路径上的弹窗文案。本文的审核意见就出在第五处——商店名提交了"月桥接口",但欢迎弹窗标题写死了上游名字。建议在提审前用"卸载重装 + 首次启动全路径截图"的方式自查一遍,成本十分钟,收益是省掉一轮审核周期。

14.4 运行与使用

Q11:移除欢迎弹窗之后,用户会不会缺少上手引导?

不会。欢迎弹窗的内容本质是一个"感谢安装 + 文档链接"的介绍页,不是操作引导;真正的功能入口(新建环境、添加路由、启动服务)在主界面上一目了然。移除它的代价接近于零,而换来的是首页启动路径更干净。真正影响上手体验的是"首次打开有没有一个可用的起点",这个由自动创建空环境来保证,不受本次修改影响。

Q12:Mock 服务能被同一局域网的其他机器访问吗?

出于安全边界考虑,默认只监听本地回环地址(127.0.0.1),设计为仅本机的开发工具。这也符合隐私合规要求:不对外暴露端口,就没有未授权访问的风险。如果确有内网联调需求,应当在明确的知情前提下修改监听地址,而不是作为默认行为。

Q13:数据存在哪里?卸载会不会丢?

环境配置、路由定义、请求日志全部以 JSON 文件形式存在应用沙箱内,随应用卸载一并清除。需要长期保存或团队共享环境配置时,建议通过导出功能把环境文件纳入版本管理——Mockoon 的环境 JSON 是可读文本,天然适合放进 Git 做 diff 和 review。

Q14:只想 Mock 一部分接口、其他走真实后端,能做到吗?

可以,用代理模式:把环境配置成指向真实后端的反向代理,未命中 Mock 规则的请求会透传给真实服务,同时还能录制响应。这让"渐进式 Mock"成为现实——联调初期全透传、随进度逐条把稳定的接口改成本地 Mock 规则,最终把后端依赖彻底摘掉。这是 Mockoon 相对简单 Mock 工具最具生产力的特性之一。

十五、总结

这次移植从动手到上架,验证了一条对整个开源桌面软件生态都成立的路:Electron 应用 → 构建期自包含改造 → 鸿蒙 HAP 壳 → 应用市场。四个适配点(bundle 打包、.cjs 入口、renderer 部署、相对路径)全部是构建期问题,意味着同一套方法可以低成本复用到其他 Electron 应用上。

几点方法论层面的收获:

  1. 选型先看数据层。没有原生模块依赖(SQLite 等)的 Electron 应用,移植成本会低一个数量级,选品时就要把这个因素称进去;
  2. 改动守恒。所有运行时逻辑一行业务代码都没改,全部改动集中在构建配置和资源路径——移植做得好不好,就看能不能把改动压在构建期;
  3. 产物级验证。改完源码要在最终打包产物上验证,构建链路和部署链路的断裂是"明明改了却没生效"的第一大来源;
  4. 应用身份一致性清单。商店名/桌面名/窗口标题/弹窗/关于页五处一致,提审前自查一遍,能省一轮审核周期。

开源软件与鸿蒙 PC 生态的结合,本质上是给成熟的桌面工具找新的运行土壤。Mockoon 这样的开发者工具能以"月桥接口"的形态在应用市场上被搜索、被安装、被使用,这条路对更多开源项目都是敞开的。对开发者个人而言,一次这样的完整实战能同时串起构建工程、签名体系、应用市场合规三块知识——它们平时分散在不同文档里,只有真正走完一遍,才会知道每一块在整条链路上的位置和分量。希望这篇复盘能帮后来者少走弯路,把更多开源好工具带上鸿蒙 PC,也期待这些被移植过来的开发者工具能在新的土壤上真正跑起来、被用起来、长出新价值。

本文涉及的工具版本:Mockoon 9.8.0(上游)、Electron 43(上游栈)/ 37 系定制底座(鸿蒙运行时)、Angular 22、HarmonyOS SDK 6.x、DevEco Studio 命令行构建。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐