☰
信创环境下Electron应用适配实战:从UEditor到统信UOS稳定运行
2026/10/13 4:47:27 网站建设 项目流程

一次信创项目适配里,我遇到一个非常典型的问题:一套基于 UEditor 的富文本编辑器,被包成了 Electron 桌面应用,部署到预装统信 UOS 的办公电脑上。安装流程很顺利,安装包也完整走完了,但用户双击桌面图标,应用没有任何反应。终端手动拉起来,只看到一行刺眼的错误:

error while loading shared libraries: libnss3.so: cannot open shared object file

这绝对不是个例。Electron 应用在国产操作系统上跑不起来、装不上、白屏、中文输入法失效,几乎是每个做信创桌面应用的人都会撞上的墙。这篇文章不聊宏观趋势,只讲我怎么从“双击没反应”一路排查到“在国产系统里稳定运行”,包括 UEditor 这种老牌编辑器要怎么塞进 Electron 壳子、electron-builder 打包要填哪些信创专属配置、以及到了现场最容易翻车的几个坑。适合正打算把 Web 富文本能力搬到国产终端、或者已经在信创项目里被 Electron 折腾的人参考。

1. 信创环境不是“同一个Linux”:先摸清目标和对手

很多人第一次做国产系统适配,下意识把它当成“在 Linux 上跑起来”来处理。这个想法的确不全面。信创终端的环境差异,比普通 Linux 发行版之间的差异还要大,CPU 架构、内核版本、glibc 版本、包管理器、桌面协议,每一项都可能让同一个安装包在一个系统上顺利运行、在另一个系统上直接闪退。

1.1 一张表看清 CPU 与系统组合

先看最常见的 CPU 体系。Electron 应用的运行依赖系统里对应的二进制版本,CPU 架构不对,安装包根本装不上,或者装上就启动不了。

CPU 体系典型 CPUElectron 官方二进制常见系统场景
x86_64Intel、AMD、兆芯官方支持,问题最少统信 UOS x86 版、麒麟 x86 版
arm64飞腾、鲲鹏官方支持飞腾整机、鲲鹏服务器上跑统信或麒麟
loongarch64龙芯 3A5000/3A6000官方列表里没有,基本靠厂商提供构建统信龙芯版、麒麟龙芯版
sw64申威官方不支持申威平台,适配成本和前两者不同

你在终端执行uname -m,看到的x86_64、aarch64、loongarch64,基本上就决定了你应该去打哪个架构的包。x86 的机器跑 arm64 的 Electron,装包那一步就开始报架构错;反过来,arm64 的机器跑 x86 包,即便用兼容层勉强装上,性能和稳定性也都不行。

实测下来,一个项目如果只打算覆盖最常见场景,就先把 x86_64 和 arm64 这两套包做扎实。龙芯这类平台,首先要确认设备厂商能不能提供对应架构的 Electron 运行时,提前在技术选型阶段确认好,比到了项目交付期再想办法靠谱得多。

1.2 glibc 是隐形门禁

如果说 CPU 架构是明面上的门槛,glibc 就是一道隐形门禁。Electron 的官方 Linux 二进制,对系统的 glibc 版本有明确要求。越新的 Electron,要求越高。

我项目里核对过,Electron 28 之后的官方包,普遍要求系统 glibc 在较新的版本,老系统上直接跑不起来;Electron 22 这类相对宽松的版本,在比较旧的信创系统上反而稳。统信 UOS 的不同版本、麒麟 V10 的不同更新批次之间,glibc 差异也不小,不能拿“都是 UOS”当成同一个环境。

查 glibc 版本很简单:

getconf GNU_LIBC_VERSION ldd --version

如果你打的包要同时兼容新旧两代国产系统,方案就两个:要么把 Electron 主版本往下压,牺牲一部分新特性换兼容;要么对不同系统出不同安装包,用启动脚本做版本判断。我做项目时倾向于后一种,因为 Electron 版本压得太低,Chromium 内核太老,UEditor 的某些现代 CSS 效果又会出问题,两头受气。

1.3 最容易被忽略的包管理器差异

同一个“麒麟”名字下,桌面版和服务器版可能一个用apt、一个用yum/dnf。统信 UOS 多数是基于 Debian 体系,习惯用deb包;但某些信创系统又只认rpm。

打包阶段最稳的做法,是把安装包格式和操作系统的包管理器对应起来:

  • 能识别为apt系的系统,给deb包。
  • 能识别为yum/dnf系的系统,给rpm包。
  • 别把 AppImage 当主力分发格式。AppImage 依赖 FUSE,而很多国产系统默认不带 FUSE,用户拿过去直接就是“解压了但启动不了”。

这也是为什么我强烈建议,在项目最开始就确定“适配矩阵”:哪几个操作系统版本、哪几种 CPU 架构、装包格式是 deb 还是 rpm、glibc 门槛是多高。这张矩阵,后面打包、测试、现场排障全都要对着它来。

2. UEditor 进 Electron 前,先给老编辑器补三处“体检”

UEditor 是很有年代感的富文本编辑器。说句实话,它的代码结构和现在的前端工程化风格差别挺大,塞进 Electron 壳子里,不能直接“原封不动”就指望它工作。Electron 本身是 Chromium,UEditor 在浏览器里能跑,但在 Electron 里跑,会有几个此前没人碰过的特殊问题。

2.1 三大技术债逐个拆

UEditor 的三处硬伤,分别是 jQuery 依赖、iframe 承载结构、以及document.execCommand这套已经过时的编辑命令体系。

先说 jQuery。UEditor 底层依赖 jQuery 1.10 左右的老版本。普通 Web 项目里,页面加载顺序控制好就没事;但在 Electron 渲染进程里,只要模块加载时序一乱,UE这个全局对象就可能没被挂上,编辑器直接初始化失败。我的做法是写一个独立的ueditor-bootstrap.js,在 DOMContentLoaded 之前按顺序加载jquery.js、ueditor.config.js、ueditor.all.min.js,保证全局顺序可控。

再说 iframe。UEditor 默认在 iframe 里做编辑,这在 Electron 里会牵扯到安全策略。如果webSecurity设置得过严,iframe 里加载相对路径的资源会被拦下来。这一点,下面自定义协议的部分会具体说。

最后是document.execCommand。Chromium 到现在还在兼容它,给的是 deprecation 警告,UEditor 的大部分基础操作依然能用。但在国产系统某些老内核版本的 Chromium 上,一些格式清除、段落合并的场景确实会出现状态不一致,我遇到过“清除格式”按钮把整篇内容样式清掉的情况。最后的解决方案很朴素:在 UEditor 外层包一层内容校验逻辑,每次编辑器内容变化后做一次 HTML 结构整理,把style里不该出现的多余属性过滤掉。

2.2 离线场景下的 serverUrl 与上传功能

UEditor 原本是给 Web 项目用的,上传图片、抓取远程图片、Word 粘贴中转,全都依赖后端的serverUrl。到了桌面端,很多时候是纯离线环境,根本没有后端可连。

我建议直接把这个包袱卸载掉。桌面编辑器场景下,图片上传可以改成走本地文件对话框,选中图片后通过 Electron 的 preload 桥接层,把图片转成 base64 或本地文件路径再插入编辑器内容。这样至少不会出现“编辑器能用,一传图片就白屏”的尴尬。

原功能依赖桌面端处理方案
图片上传后端 serverUrl本地文件对话框 + IPC 读文件,插入 base64
Word 粘贴后端过滤接口关闭远程抓取,用 UEditor 内置本地粘贴过滤
远程图片抓取后端代理关闭该按钮,避免跨域请求
公式编辑MathType 插件用 KaTeX/MathJax 替代,不用系统插件
涂鸦、音乐Flash/插件直接隐藏相关按钮

UE.getEditor初始化时,把serverUrl设为空字符串,同时过滤掉工具栏里与上传、远程抓取相关的按钮项。纯文本编辑、表格、代码块这些核心能力保留即可,够用也不容易出问题。

2.3 用自定义协议替代 file://

UEditor 的静态资源如果直接用file://加载,会遇到一个很现实的问题:Electron 的安全策略会对本地文件访问做限制,UEditor 的 iframe 内部需要加载相对路径的样式、脚本时,经常出现资源加载失败。我在项目里就是从file://切换到自定义协议解决的。

主进程里注册一个app://协议,把本地 dist 目录映射成协议可访问的资源:

// main.js const { app, protocol, net } = require('electron'); const { pathToFileURL } = require('url'); const path = require('path'); app.whenReady().then(() => { protocol.handle('app', (request) => { const urlPath = request.url.replace(/^app:\/\//, ''); return net.fetch(pathToFileURL(path.join(__dirname, urlPath)).toString()); }); });

BrowserWindow 加载页面的时候,用app://协议加载,而不是file://。UEditor 内部 iframe 里的相对路径,也会沿着这个协议解析。实测下来,图片、CSS、JS 的加载稳定多了,iframe 里的样式错乱问题也少了很多。

还有一个小细节:UEditor 的 config 里,可以设置一个iframeCssUrl,给编辑区域指定统一的基础样式。这个值在自定义协议下要写成app://...的完整路径,否则编辑区里的字体、行高在国产系统的默认字体环境下会显得很难看。

2.4 与前端框架共存时的小心机

如果你是在 Vue 或 React 项目里集成 UEditor,还有一类问题:UEditor 会默认扫描页面里所有script标签并尝试自动初始化编辑器,这跟前端框架的组件生命周期很容易打架。最直接的办法是在ueditor.config.js里把自动初始化关掉:

// ueditor.config.js window.UEDITOR_CONFIG = { autoClearinitialContent: true, initialFrameWidth: '100%', initialFrameHeight: 600, serverUrl: '', zIndex: 1000 };

然后在组件里手动初始化:

const editor = UE.getEditor('editor-container', config);

每次组件销毁时,记得editor.destroy()。不销毁的话,Electron 窗口关闭再打开,会残留多个编辑器实例,内存上涨很直观,用户体感就是“越用越卡”。

3. 打包环节:electron-builder 要填好的“信创配置”

UEditor 侧面的问题解决后,真正的重头戏是打包。很多团队在自己的开发机上打包跑得很欢,一到国产系统就出问题,问题往往就出在 electron-builder 配置里,没填信创环境要用的那几项。

3.1 Electron 版本与二进制镜像

先定 Electron 版本。信创项目不建议追新,建议选择一个稳定性经过验证的版本,并且明确该版本对 glibc 的要求。我在多个信创系统上实测,Electron 27 左右版本在统信 UOS 和麒麟 V10 上都有不错的表现,新老系统覆盖比较均衡。版本定下来之后,要配置国内可用的 Electron 二进制镜像,否则electron-builder下载 Electron 压缩包时经常卡在半路:

export ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ export ELECTRON_CUSTOM_DIR={{ version }}

这一条看起来不起眼,但能直接省掉“打包打了半天,最后下载失败”的烦恼。

3.2 一份可以直接抄的 build 配置

electron-builder 的配置文件里,Linux 相关的 target、依赖声明这两块,是信创适配的关键。

{ "appId": "com.example.ueditor-editor", "directories": { "output": "release/${version}" }, "files": [ "dist/**/*", "main.js", "preload.js", "ueditor/**/*" ], "linux": { "target": ["deb", "rpm"], "icon": "build/icon.png", "category": "Office;TextEditor;", "synopsis": "Desktop rich text editor based on UEditor", "executableName": "ueditor-editor" }, "deb": { "depends": [ "libnss3", "libasound2", "libgbm1", "libatk-bridge2.0-0", "libcups2", "libgtk-3-0" ] }, "rpm": { "depends": [ "libnss3", "libasound2", "libgbm1", "libatk-bridge2.0-0", "libcups2", "gtk3" ] }, "artifactName": "${name}-${version}-${arch}.${ext}" }

depends这一串不是随手写的。Electron 在 Linux 上运行,需要libnss3(网络通信相关)、libasound2(音频相关)、libgbm1(GPU 图形缓冲相关)这些底层库。如果不在安装包里预先声明依赖,用户安装时不会报错,但启动时就会像文章开头那样,提示找不到libnss3.so。

声明依赖后,安装时系统会提示自动补装缺失的库,很多“装上了但启动不了”的工单,就是因为漏了这一句。

3.3 桌面菜单里的图标和启动器

deb/rpm 装完后,系统应用菜单里显示的图标和启动命令来自/usr/share/applications下的 desktop 文件。electron-builder 会生成一个 desktop 文件,但有两点经常出问题:

  • Exec 路径。默认值有时候指向安装目录里的二进制名,如果你的executableName和实际产物名不一致,桌面图标点了没反应。
  • Icon 路径。desktop 文件里的 Icon 字段会指向安装目录下的图标文件,如果打包时没带上对应尺寸的 icon,应用菜单里就是灰块或系统默认图标。

我在项目里习惯用一套比较规范的 icon 集,至少包含 256x256 的 png,并在 build 配置里把linux.icon指定为这个 png。打包时确保Icon=指向实际存在文件,装完就能在菜单里看到正常图标,也容易和别的应用区分开。

3.4 多架构安装包怎么出

信创环境需要同时支持 x86 和 arm64,electron-builder 的配置里可以通过单独指定架构来出包:

electron-builder --linux deb --x64 electron-builder --linux deb --arm64

纯前端项目、没有原生 Node 模块的情况下,交叉打包相对容易。如果用了原生模块,就尽量在对应架构的构建机或 CI 上去出包,避免交叉编译的麻烦。UEditor 本身是纯 JS,没有原生依赖,所以我项目里 x86 和 arm64 的包都是直接出的,省了不少事。

出包后的命名建议带上架构,别让用户自己猜。上面配置里的${arch}会自动把x64、arm64塞进文件名,分发、排障都很方便。

4. 装到国产系统上最容易翻车的三个现场

安装包打好了,不等于现场就能跑。真正部署到用户手里,最常见的问题有三个。我逐个说说现象、原因和解决方案。

4.1 GPU 白屏和闪退

现象:应用启动后窗口一闪而过,或者整个窗口白屏,刷新也没用;终端手动启动,能看到 GPU 进程相关的报错。

原因:国产终端很多用的是集成显卡,显卡驱动未必完善,Chromium 的 GPU 加速进程在部分驱动上会崩溃。Electron 默认会启用 GPU 加速,于是启动即崩。

解决方案分两步走。第一步,在应用主进程里主动关闭硬件加速:

const { app } = require('electron'); app.disableHardwareAcceleration(); app.commandLine.appendSwitch('use-gl', 'swiftshader');

swiftshader是 Chromium 自带的软件渲染器,性能比硬件加速差一些,但对富文本编辑这种场景来说,感知不明显,稳定性却大大提升。

第二步,如果用户那里仍然白屏,用应急参数启动验证:

./ueditor-editor --disable-gpu --disable-gpu-compositing

但注意,--disable-gpu只是应急手段,不建议在 final 版本里长挂。长期关掉 GPU 加速,滚动和动画会掉帧,用户会抱怨“不流畅”。

4.2 中文输入法不上屏

现象:编辑器能打开,但切换到中文输入法时,候选框显示不出来,或者候选框在屏幕角落,跟光标位置完全对不上。

原因:国产桌面系统上,中文输入法的标准做法是让应用支持ibus或fcitx协议。Electron 应用如果没有正确设置输入法相关的环境变量,Chromium 就识别不到输入法的输入上下文。

解决方法是把输入法环境变量打到一个启动脚本里,或者直接在打包后的 desktop 文件里预置:

#!/bin/bash export GTK_IM_MODULE=ibus export QT_IM_MODULE=ibus export XMODIFIERS=@im=ibus exec /opt/ueditor-editor/ueditor-editor "$@"

注意,这个变量值必须和系统实际用的输入法框架一致。系统是 fcitx 的,就把ibus换成fcitx。我看到过不少项目把GTK_IM_MODULE=ibus写死,结果用户那边实际是 fcitx,问题依旧。稳妥起见,可以在启动脚本里做一个检测:

if dbus-send --session --dest=org.fcitx.Fcitx --type=method_call /org/fcitx/Fcitx org.fcitx.Fcitx.About 2>/dev/null; then export GTK_IM_MODULE=fcitx export QT_IM_MODULE=fcitx export XMODIFIERS=@im=fcitx else export GTK_IM_MODULE=ibus export QT_IM_MODULE=ibus export XMODIFIERS=@im=ibus fi

这个小脚本,解决了我项目里至少三台输入法失效的返修单。

4.3 高分屏与中文字体

现象:在部分国产整机上,界面元素要么字体发虚、要么整体比例失控,编辑器内中文显示效果尤其差。

原因有两个。一是系统未正确设置 DPI 缩放,Chromium 拿到的缩放因子不对;二是系统里中文字体缺失或未优先使用中文字体族。

针对缩放,可以按设备实测设置一个合理的缩放系数:

app.commandLine.appendSwitch('high-dpi-support', '1'); app.commandLine.appendSwitch('force-device-scale-factor', '1.5');

这个 1.5 不是一个通用值。有些设备是 1080p 屏、系统缩放 1.25,有些是 2K 屏、缩放 1.5,得逐一确认。写成一个可配置项放到配置文件里,比在代码里写死好得多。

字体方面,很多信创系统预装的是开源字体,UEditor 默认的字体族名称在系统里未必有对应字体。我在 UEditor 的iframeCssUrl样式里,统一把字体族改成:

body { font-family: "Noto Sans CJK SC", "Source Han Sans SC", "PingFang SC", sans-serif; }

如果没有 Noto CJK 和思源,再留一版 fallback 到系统默认中文字体。同时,在部署文档里建议系统安装中文字体包,比如思源黑体、文泉驿微米黑,能明显改善编辑器的渲染效果。

4.4 依赖缺失快速自救

如果用户已经装好包、双击没反应,先用最简单的方法看日志:

cd /opt/ueditor-editor ldd ./ueditor-editor | grep "not found"

输出里凡是not found的,就是系统里缺的库。比如文章开头的libnss3.so,就在这个列表里。知道缺什么之后,离线处理可以直接安装本地 deb 包:

dpkg -i ./offline-pkgs/*.deb

线上环境有网就简单了:

apt-get install -y libnss3 libasound2 libgbm1 libatk-bridge2.0-0 libcups2

也可以直接把库文件放到应用目录下,通过设置LD_LIBRARY_PATH让应用优先加载本地库。但这个方案有版本冲突风险,我建议只作为应急手段,还是优先补装系统库。

5. 一张适配自检表,和一套现场排查流程

现场问题千变万化,但排查路径是有迹可循的。我把项目里整理的一套排查链路和自检表分享出来,照着走,大多数问题半小时内能定位。

5.1 三分钟定位问题的排查链路

第一步,确认系统基础信息。一台陌生设备送过来,先跑三条命令:

cat /etc/os-release uname -m getconf GNU_LIBC_VERSION

这三条分别回答了三个问题:这是什么发行版?什么架构?glibc 多新?拿到结果,就知道应该装哪个安装包。

第二步,启动应用看输出。桌面图标没反应时,直接去终端里跑二进制:

ELECTRON_ENABLE_LOGGING=1 /opt/ueditor-editor/ueditor-editor 2> /tmp/editor.log

ELECTRON_ENABLE_LOGGING=1能让 Chromium 的日志打到 stderr。等应用崩溃或白屏后,打开/tmp/editor.log看最后几十行,大概率能直接看到崩溃原因。

第三步,看动态库缺失:

ldd /opt/ueditor-editor/ueditor-editor | grep "not found"

第四步,还定位不到就开远程调试。在启动参数里带一个调试端口,本地或内网部署时可以快速打开开发者工具:

/opt/ueditor-editor/ueditor-editor --remote-debugging-port=9222

然后本机浏览器访问http://127.0.0.1:9222,直接看到渲染进程的 console、DOM 和网络请求,前端报错全部一览无遗。排查结束后,记得关掉这个端口,别开着调试端口交付。

5.2 把环境检测做成启动自检

与其等用户报障,不如把上面这套逻辑内置到应用里。我在主进程启动时,先跑一个环境检测模块,把系统 ID、CPU 架构、glibc 版本、缺失动态库情况都收集起来,写入一个日志文件。

// env-check.js const fs = require('fs'); const { execSync } = require('child_process'); function getOsRelease() { const content = fs.readFileSync('/etc/os-release', 'utf8'); const id = content.match(/^ID=(.*)$/m)?.[1]?.replace(/"/g, '') || 'unknown'; const versionId = content.match(/^VERSION_ID="?(.*?)"?$/m)?.[1] || 'unknown'; return { id, versionId }; } function getArch() { return process.arch; } function getGlibc() { try { return execSync('getconf GNU_LIBC_VERSION').toString().trim(); } catch (e) { return 'unknown'; } }

然后把结果打印到日志里。如果用户报障,远程传一份日志回来,根本不用到场,就能判断是安装包选错、依赖缺失,还是系统环境问题。

5.3 自检表模板

这张表可以直接放进项目文档,适配测试、现场验收都能用。

检查步骤命令或对象通过标准
系统发行版cat /etc/os-release明确 ID 和版本号,如 uos 1060、kylin V10
CPU 架构uname -m与安装包架构一致
glibc 版本getconf GNU_LIBC_VERSION与 Electron 版本要求匹配
动态库依赖ldd ./ueditor-editor无not found输出
启动日志终端运行查看 stderr无崩溃、无 GPU 报错
中文输入法打开编辑器输入中文候选框跟随光标,能正常上屏
界面缩放打开设置页面查看图片文字清晰,界面比例正常
编辑器核心功能新建文档、插入表格、粘贴纯文本无白屏、无样式错乱

当初做完第一版适配后,我就是拿着这张表,在统信 UOS x86、统信 UOS arm64(飞腾)、麒麟 V10 三个环境各跑了一遍。大部分问题在测试阶段提前暴露,真正到用户手里的返修率低了很多。

最后再说两句

这个项目做完之后,我有一个很深的体会:信创适配这件事,最麻烦的不是某个单一的技术难点,而是“环境种类多,现场反馈慢,问题又碎”。唯一能对冲这种不确定性的办法,就是把前期调研做足,把安装包的架构、依赖声明、启动脚本一次配好,再把环境检测工具内置到应用里。安装阶段多花十分钟配置,后续能省掉几十张现场工单。

最后分享一个小技巧:给安装包命名时,一定要把架构和系统信息带全。比如ueditor-editor-1.4.0-arm64-ubuntu.deb,用户拿到包就知道该不该装。我见过太多人下发一个不带架构的deb包,x86 机器装不上,arm 机器也装不上,两边互相甩锅。命名规范这件事,看着小,实际是信创项目交付里最容易踩、也最容易被忽略的坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询