AnythingLLM Electron 鸿蒙 PC 适配全记录:从秒退崩溃到真机跑通本地知识库
一、写在前面
欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区:https://harmonypc.csdn.net/
项目开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_AnythingLLM
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
环境搭建文章:https://blog.csdn.net/lbcyllqj/article/details/161286249?sharetype=blogdetail&sharerId=161286249&sharerefer=PC&sharesource=lbcyllqj&spm=1011.2480.3001.8118
这篇文章记录的是一次把 AnythingLLM 适配到 OpenHarmony / HarmonyOS PC 的完整过程。
AnythingLLM 是一个开源的本地知识库 / RAG 对话应用,本体并不是一个普通 Electron 应用,而是「Express API 服务 + React/Vite 前端 + 文档采集器 collector」三件套的 Web 架构。它默认还带一整套较重的原生依赖:Prisma + SQLite 数据库、LanceDB 向量库、onnxruntime 本地向量化等。
因此这次鸿蒙适配的目标不是简单让窗口打开,而是:把整套 Web 架构包成一个 Electron 应用(主进程拉起 server 与 collector,再开窗加载本地 UI),塞进 OpenHarmony Electron 运行时壳工程,最后逐个解决数据库原生引擎、设置持久化、平台判断、原生库加载等在鸿蒙侧不兼容的问题。
最终适配路线可以概括为:
AnythingLLM(server + frontend + collector)
↓
新增 electron/ 主进程:进程内拉起 server + collector,开窗加载本地 UI
↓
scripts/build-ohos-package.js 组装应用负载(前端构建→server/public,打包运行依赖)
↓
ohos_hap/web_engine/src/main/resources/resfile/resources/app
↓
OpenHarmony Electron runtime 启动
↓
hvigor / DevEco Studio 打包并签名 HAP
↓
鸿蒙 PC 真机安装运行
本次适配后的 HAP 已经可以完成签名构建并在真机运行,当前产物路径为:
ohos_hap/electron/build/default/outputs/default/electron-default-signed.hap
签名 HAP 大约 666 MB,鸿蒙运行时应用负载目录大约 618 MB。体积偏大的主要原因是 OpenHarmony Electron runtime、Chromium 资源、原生库,以及 production 运行依赖闭包(含 Node server 全套 node_modules)都被一起打进了 HAP。
真机环境为 HUAWEI MateBook(HarmonyOS HAD-W24 6.0.0),最终在设备上成功启动并渲染出完整界面。
二、项目背景与适配边界
本次适配对象是 anything-llm,版本为:
1.14.0
原项目主要技术栈包括:
- Express(API server,默认端口 3001)
- React + Vite(前端,单页应用)
- Prisma + SQLite(数据库,本地持久化)
- collector(文档处理服务,默认端口 8888)
- LanceDB / onnxruntime / @xenova/transformers(默认本地向量库与向量化,原生模块)
从目录结构看,和鸿蒙适配关系最密切的目录主要有:
anything-llm-ohos/
├── electron/ # 新增:Electron 主进程(拉起 server+collector,开窗)
│ ├── main.js
│ ├── services.js # 进程内/子进程启动、端口等待、迁移 ping
│ ├── env.js # 环境拼装、设置持久化、Prisma 引擎指定
│ ├── paths.js / harmony.js / preload.js / loading.html
├── server/ # API 服务(Express + Prisma)
├── frontend/ # React 前端
├── collector/ # 文档采集器
├── scripts/
│ ├── build-ohos-package.js # 组装鸿蒙应用负载
│ └── build-ohos-hap.js # 同步负载 + 复制原生库 + hvigor 打 HAP
├── ohos_hap/ # 新增:OpenHarmony Electron HAP 壳工程
└── OHOS_ADAPTATION.md
这次适配的核心边界判断有两条:
- 不重写前端:前端按「单源」方式构建(
VITE_API_BASE=/api),让 server 在同一端口同时托管 SPA 与 API,WebSocket 也走同源,前端代码零改动。 - 本地 AI 走云端:onnxruntime、LanceDB 这类本地原生件在鸿蒙 arm64 上没有现成编译产物,所以默认做「纯云端」构建(LEAN),把它们从包里删掉,AI 计算交给云端 API;而数据库(Prisma + SQLite)必须在本地跑通,这是全应用的基础。

三、为什么不能只把前端 dist 当静态页面塞进去
很多 Electron 项目迁到鸿蒙 PC 时,如果渲染层比较简单,可以把前端构建产物放进 HAP 资源目录,让窗口直接加载 index.html。AnythingLLM 不能这么处理。
原因很直接:它的能力主要在后端。
- 前端只是一个 SPA,所有数据(工作区、对话、设置、用户、向量记录)都来自 Express server 的
/api。 - server 用 Prisma 读写本地 SQLite 数据库,必须在运行时初始化、迁移、读写。
- 聊天、文档、向量检索等逻辑全部在 server 端。
- collector 是独立进程,负责把上传文档解析成可向量化的内容。
所以适配方案是新增一个 electron/ 主进程:在鸿蒙下进程内(in-process) 直接 require 启动 server(以及可选的 collector),等端口可达后,再把窗口从加载页切到 http://127.0.0.1:<port>/,由 server 同源托管前端与 API。
之所以选「进程内」而不是子进程,是因为 OpenHarmony Electron runtime 对 child_process / ELECTRON_RUN_AS_NODE 的支持不确定,进程内 require 最稳。
四、桌面端先跑通:把 Electron 这层验证好
在碰鸿蒙之前,先在 macOS 上把这套 Electron 壳验证通,能极大缩短真机调试成本。
桌面端启动流程(electron/main.js):
- 解析可写数据目录(
app.getPath('userData')),确定STORAGE_DIR、共享 hotdir、日志目录。 - 首次启动把种子数据库拷入可写目录。
- 用空闲端口拉起 collector 与 server(鸿蒙下进程内,桌面默认子进程)。
- 等 server 端口可达 → 请求一次
GET /api/migrate(生产模式下迁移是被 stub 的,必须 ping 一下才会执行)。 - 把窗口切到
http://127.0.0.1:<port>/。
桌面端构建并启动:
yarn desktop:setup # 装依赖 + 生成 Prisma + 迁移数据库
yarn desktop:dev # 单源构建前端 + Electron 拉起整套应用
这一步如果能看到 AnythingLLM 界面,就说明「Electron 壳 + server 进程内启动 + 前端单源托管」这条主链没问题,可以放心往鸿蒙搬了。

五、建立 OpenHarmony Electron HAP 工程
鸿蒙这层不需要从零写,直接复用现成的 OpenHarmony Electron 运行时壳,把它拷进仓库的 ohos_hap/,改成 AnythingLLM 品牌即可:
ohos_hap/electron/:entry 模块,含 EntryAbility 与 Electron 原生库(libs/arm64-v8a/libelectron.so等)。ohos_hap/web_engine/:Electron runtime HAR,其src/main/resources/resfile/resources/app就是JS 应用被加载的位置。
改品牌主要是几处资源字符串:AppScope/resources/.../string.json 里的 app_name、electron 模块的 EntryAbility_label、AppScope/app.json5 的 vendor, AnythingLLM。
然后用脚本把 AnythingLLM 的运行负载同步进壳:
# 纯云端(删掉本地 AI 原生件)+ 不打包 collector
OHOS_LEAN=1 OHOS_SKIP_COLLECTOR=1 npm run ohos:sync
build-ohos-package.js 做的事:单源构建前端→拷进 server/public;生成 Prisma client;把 electron/、server/、生成的种子库组装进 app/;写好引导 main.js(注入 ANYTHINGLLM_OPENHARMONY=1 后 require('./electron/main.js'))。
同步后在 DevEco Studio 里打开 ohos_hap,配好你自己的签名证书,构建即可得到签名 HAP;也可以命令行 npm run ohos:build 走 hvigor。

六、真机踩坑实录:一路被原生依赖拦下来
把签名 HAP 装到 MateBook 上(hdc install xxx.hap),第一次运行直接秒退。后面每修一个,就往前走一步,再被下一个拦住。这部分是这次适配最硬核的地方,逐个记录。
坑 1:HAP 里 app 负载是空的 → exit(1) 秒退。
崩溃日志是 appspawn_server.c Unexpected call: exit(1),栈在 libelectron.so。原因是 ohos:sync 没成功跑过,resfile/.../app 是空目录,Electron 找不到入口 main.js 直接 exit(1)。鸿蒙 appspawn 会把任何 exit() 当致命错误 abort,所以表现为秒退。修复:把负载真正同步进去;并且让 Electron 主进程在鸿蒙下绝不调用 exit()(平台检查只记日志、启动失败显示错误页)。
坑 2:.prisma 点号目录被 HAP 打包剥离。
报 Cannot find module '.prisma/client/index'。HAP 资源打包会剥掉以点号开头的目录,而 Prisma 生成的客户端默认在 node_modules/.prisma/client。修复:构建时把 .prisma 重命名为非点号目录 prisma-client-gen,并改写 @prisma/client 的引用路径。
坑 3:设置写进只读包、重启就丢。
AnythingLLM 把界面里改的设置(LLM、API Key、向量库)通过 dumpENV() 写到 server/.env,而它在 HAP 里是只读的,写失败 → 每次启动密钥全没。修复:改写到可写的 <STORAGE_DIR>/.env,并在 Electron 层每次启动时读回。
坑 4:Prisma 平台误判。
鸿蒙 Electron 的 Node 把 process.platform 报成 openharmony,Prisma 不认识 → 猜成需要 debian-openssl-1.1.x 引擎,用不上包里的引擎。修复:用 PRISMA_QUERY_ENGINE_LIBRARY 环境变量强制指向包内的 musl/arm64 引擎,绕过平台探测(设备实际是 musl,崩溃栈里就是 ld-musl-aarch64)。
坑 5:原生 .so 必须放 libs/arm64-v8a。
强制指定引擎后,报 Error loading shared library .../libs/arm64/libquery_engine-...: No such file or directory——注意它去 libs/arm64 找了。HarmonyOS 只允许从 app 的 libs/arm64-v8a/ 目录 dlopen 原生库。修复:把 Prisma 引擎 .so.node 复制进 ohos_hap/electron/libs/arm64-v8a/(已固化到 build-ohos-hap.js)。
坑 6 / 7:引擎缺 OpenSSL 与 libgcc。
引擎放进 libs 后,依次报缺 libssl.so.1.1 → 换 openssl-3.0.x 引擎后又缺 libssl.so.3 / libcrypto.so.3 → 补齐后再缺 libgcc_s.so.1。鸿蒙没有暴露这些库。修复:从 Alpine(musl/aarch64)取来 libssl.so.3、libcrypto.so.3、libgcc_s.so.1,一并放进 libs/arm64-v8a/。
七、补齐之后:真机成功跑通
把上面三类原生库(musl 引擎 + OpenSSL 3 + libgcc)全部放进 libs/arm64-v8a,重打 HAP、重装、启动。这一次设备日志干净了:
[anythingllm] boot start. harmony=true platform=openharmony
[anythingllm] mode=in-process
[backend] info: Primary server in HTTP mode listening on port 40039
[anythingllm] migrate ping -> 200 ← 数据库迁移成功
[anythingllm] loaded http://127.0.0.1:40039/ ← 窗口加载 UI
prisma:error 数量为 0,Prisma 引擎靠 bundle 进去的 libssl.so.3 + libcrypto.so.3 + libgcc_s.so.1 成功加载,数据库可用,界面正常渲染。至此,AnythingLLM 端到端跑通在鸿蒙 PC 上。
安装与启动验证用的命令:
HDC=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc
"$HDC" install ohos_hap/electron/build/default/outputs/default/electron-default-signed.hap
"$HDC" shell aa start -a EntryAbility -b com.huawei.ohos_electron
进界面后,在设置里配一个云端 LLM(如 DeepSeek:Base URL https://api.deepseek.com/v1、模型 deepseek-chat)和一个云端向量库(如 Qdrant),即可开始对话。设置这次会持久化,重启不丢。
八、关键问题复盘
1. 不要把 AnythingLLM 当成普通静态网页。
它的数据和逻辑全在后端,必须把 Express server 在鸿蒙端进程内真正跑起来,并让本地 SQLite 数据库可读写,前端才有数据。
2. 鸿蒙下绝不 exit()。
appspawn 把任何 exit() 当致命崩溃。所有平台检查、异常分支都改成「记日志 + 显示错误页」,不要退出进程,否则就是无信息秒退。
3. 原生模块的「三连坑」要一次性想全。
鸿蒙跑 Node 原生 .node / .so,至少要过三关:① 文件必须在 libs/arm64-v8a;② 平台探测可能失效,要用环境变量强制指定;③ 它依赖的 OpenSSL、libgcc 等运行时库鸿蒙没有,要自己从 musl 发行版(Alpine)取来一并打包。
4. 设置持久化要落到可写目录。
打包后的应用目录是只读的,凡是「运行时要写」的东西(数据库、密钥、用户设置、上传文件)都必须重定向到 STORAGE_DIR 这类可写沙箱目录。
5. 已知限制。
OHOS Electron 的 Node 缺少 WebAssembly,tiktoken(token 计数)会报错,不致命;本地向量化 / LanceDB 因无鸿蒙 arm64 编译产物,当前走云端方案。
九、当前能力与后续方向
当前 AnythingLLM 在鸿蒙 PC 上已经可以:稳定启动、本地数据库读写持久化、配置云端 LLM / 向量库后进行知识库对话、设置跨重启保留。
后续可以继续探索:
- 补齐 collector:它卡在一个 git 依赖上,解决后即可恢复文档上传与 RAG 入库。
- 本地 AI:把 onnxruntime、LanceDB 为鸿蒙 arm64(musl) 重新编译,恢复纯本地向量化与向量检索;或接入设备本地的 LLM 推理服务。
- 补齐
WebAssembly能力或替换 tiktoken,让 token 计数恢复精确。
十、总结
这次 AnythingLLM 鸿蒙 PC 适配,最大的收获是把一个「三件套 Web 架构 + 重原生依赖」的应用真正在新平台上落了地。
主链路上,先在桌面端用 Electron 把「主进程拉起 server、前端单源托管」验证通;再复用 OpenHarmony Electron 壳工程把负载搬进鸿蒙;最后在真机上一路啃下来八个坑——空包秒退、exit 致命、.prisma 被剥、设置丢失、Prisma 平台误判、.so 路径限制、缺 OpenSSL、缺 libgcc。
对这类应用迁鸿蒙 PC 来说,这个过程提供了一个可复制的经验:
先把强后端能力在本地进程内跑通,
再把只读包内必须可写的数据重定向到沙箱目录,
对每一个原生模块都按「路径 + 平台探测 + 运行时依赖」三关逐一补齐,
最后在真机上用 hilog 逐步定位,一关一关往前推。
让一个本地知识库应用在鸿蒙 PC 上稳定打开、数据库可用、配置云端即可对话,已经是一次比较关键的工程落点。
更多推荐


所有评论(0)