Skip to content

智能回潮率检测仪系统 — 面试问答

项目时间:2025年4月 - 至今 | 已部署至纺织行业产线 技术栈:Electron + Vue3 + TypeScript + Node.js + Express + Prisma + SQLite + MQTT + UDP + Socket.IO + Element Plus + ECharts + TailwindCSS + Decimal.js + Winston + JWT

Q1: Electron 主进程架构设计

面试官可能问:"说说 Electron 主进程是怎么设计的?启动流程和生命周期怎么管理?"

回答

整体设计用了三层架构:启动层(index.ts)→ 服务层(ApiServer)→ 管理器层(ServiceManager)

启动层负责:单例锁防多开(app.requestSingleInstanceLock())、全局错误兜底(unhandledRejection + uncaughtException)、生产环境开机自启 + 全屏工业模式、窗口无边框。

核心是 ServiceManager(单例模式),用 EventEmitter 统一管理 12 个服务的生命周期——MQTT Broker、UDP、硬件通信、数据库、品种库、测量引擎、标定曲线等。每个服务独立初始化、独立报错不阻塞整体,启动顺序有依赖的就 await,可并行的就 Promise.all。

Express 服务器内嵌在主进程里,端口 3000,渲染进程通过 Preload 暴露的 IPC → 主进程 api:request handler → axios 转发到 Express API,不用在渲染进程直接发 HTTP 请求。

关闭有优雅退出流程:will-quit → 停止 WebRTC Worker → 心跳/同步/更新服务 → ApiServer.stop() → ServiceManager.shutdown() → app.exit(0),确保 SQLite 连接正常关闭。

追问:"为什么把 Express 嵌在主进程而不是独立进程?"

工业产线机器的性能有限,独立进程会增加资源开销。而且渲染进程和主进程本来就通过 IPC 通信,统一在主进程处理请求减少了跨进程调用的延迟和复杂度。SQLite 是单写锁的,所有数据库操作都走一个 Prisma Client 实例也避免了并发写入冲突。

Q2: Prisma + Electron 打包攻坚

面试官可能问:"Prisma 在 Electron 里打包有什么难点?怎么解决的?"

回答

这确实是个业界公认的痛点。Prisma 在 Node.js 后端项目里运行很丝滑,但到了 Electron 打包环境下有几个问题:

问题一:Prisma Client 不在 node_modules 里。 Prisma generate 生成的 client 代码默认在 node_modules/.prisma/client,Electron 打包时 asar 会把它打进去,但 Prisma 的查询引擎(query-engine-*.node)是原生二进制,asar 里的路径无法被 Node.js 的 require 正确加载。

解决方案:修改 schema.prisma 的 generator output 路径——output = "../src/generated/client"——把 generated client 迁出 node_modules,作为源码一起打包。然后在 package.json 里配置 "db": "link:src/generated/client" 做本地软链接,开发和生产都能正确引用。

问题二:查询引擎二进制找不到。 Prisma 需要 .node.dll 文件,打包后路径全变了。

解决方案:在 prisma.ts 里写了一个 getQueryEnginePath() 函数,按平台(win32/darwin/linux/darwinArm64)映射引擎文件名,然后遍历 5 个可能的路径(extraResources、asar.unpacked、resourcesPath 等),用 existsSync 找到实际存在的那个。打包配置里用 electron-builderextraResources 把 Prisma 引擎文件拷贝出来。

问题三:SQLite 数据库文件路径。 开发环境用相对路径 file:./app.db,生产环境要放到 app.getPath('userData') 下防止被覆盖。

追问:"为什么不直接用 better-sqlite3 替代 Prisma?"

Prisma 的类型安全和 schema migration 是刚需——品种库、批次、检测记录、报告等 6 个数据模型,字段多、关联复杂。用 Prisma 的 schema-first 方式,改了 schema 跑个 migrate 就行,不用手写 SQL。如果换成 better-sqlite3,得手写所有建表、迁移、查询逻辑,维护成本太高。

Q3: MQTT + UDP 多协议硬件通信

面试官可能问:"MQTT 和 UDP 分别负责什么?怎么设计的多协议通信架构?"

回答

整个硬件通信分两层:

MQTT 层(控制面):负责设备状态监控和控制指令下发。内置了 Aedes MQTT Broker 跑在端口 1883,作为设备和后端的中转桥梁。硬件设备(物联网关)连到这个 Broker 订阅控制主题,后端往对应主题 publish 指令就行——比如风扇模式切换、压力值设定、急停指令等。Aedes 配置了 concurrency: 500queueLimit: 1000,确保高频消息不丢包。

UDP 层(数据面):负责传感器高频数据采集。UDP 协议不需要建连接,延迟极低。FL-WAPP 探头通过私有协议返回数据,格式很简单——ASCII 数字加回车符,比如 1.234\r。UdpService 监听端口接收所有 UDP 包,用 allowedSources IP 白名单过滤非探头设备,然后解析数值。

两层之间通过 ServiceManager 的 EventEmitter 统一调度:UDP 收到探头数据 → probe-data 事件 → RealtimeDataCollectorService 收集缓存 → WebSocket 推前端渲染。MQTT 收到设备状态 → mqtt-message-published 事件 → 前端更新设备面板。监控响应延迟小于 100ms。

追问:"为什么自己内置 MQTT Broker 而不是用外部 Mosquitto?"

这是工业产线的部署痛点——客户工厂的网络环境千差万别,有的根本不联网。如果依赖外部的 Mosquitto,每次部署都得现场配置,出了故障排查也麻烦。内置 Broker 做到"安装即用",零配置。而且可以通过开关控制,网络好的产线也能连外部 Broker。

Q4: 实时数据采集引擎重构

面试官可能问:"实时数据采集这块做了什么优化?"

回答

早期版本的采集模式是传统的请求-响应——前端点"开始测量"→ 发一次 UDP 请求 → 等探头回一个值 → 展示结果。问题很明显:时序不可靠、没有异常处理、一次只能拿一个值。

后来做了三次迭代重构:

第一次:从请求-响应到持续采集。 把采集逻辑从 FlWappService 抽出来,写了一个独立的 RealtimeDataCollectorService。用 setInterval 100ms 高频轮询——定时发 QUO1(读 Q 值)或 FMG(读 M 值)的 UDP 请求,每个响应都缓存到 qValueData[]mValueData[] 数组中,前端取最后一位就是最新值。

第二次:滑动窗口 + 智能异常值过滤。 Q 值有效范围 [0.5, 3.5],M 值范围 [1, 100]。超出范围的直接丢弃不存,但记录 warn 日志。数组用 slice(-maxDataPoints) 限制 100 点容量,内存占用可控。

第三次:5 点去极值取中值。 最终测量不再用单点值,而是取最近 5 个有效数据点 → 排序 → 去掉最大值和最小值 → 取中间值。这样抗干扰能力大幅提升,个别异常点不会影响最终结果。

追问:"100ms 轮询会不会压力太大?"

UDP 本身就是无连接轻量协议,一个请求就几十字节。探头那边是硬件固件响应,处理能力是够的。而且我们用的是 setInterval 而非递归 setTimeout,避免了回调堆叠。

Q5: 高精度回潮率计算引擎

面试官可能问:"回潮率计算公式是怎么设计的?为什么用 Decimal.js?"

回答

回潮率测量值的计算用的是微波探测原理:探头发出微波穿过纱线,测量 Q 值(品质因数)和直径 D,然后通过数学模型反推出回潮率 M。该模型包含了去中心化 Q 值、Q 值标准差、有效探测比等多个特征参数,以及材质参数来适配不同纱线品种。

Decimal.js 的必要性:公式里用到平方、除法、乘法,浮点误差会被放大。JS 原生的 0.1 + 0.2 !== 0.3 在这种工业场景下会出大事——客户对回潮率的精度要求到 0.01%。我用 Decimal.js 替代了原生 number,所有中间计算都是高精度 Decimal 类型,只在最后 toFixed(2) 转回数字。

单面/多面双模式:多面检测使用完整公式计算,单面检测使用简化版公式。系统根据 detection_range 配置自动切换,每个品种可以独立配置自己的公式参数。

追问:"怎么验证公式的准确性?"

用烘干称重法做对照。同一批纱线,先走一遍微波检测仪,再送实验室烘干称重(国标方法)。把两组数据做线性回归拟合,不断调整模型系数使 MSE 最小。拟合好的系数存为品种的默认配置。

Q6: 云端设备管理与数据同步

面试官可能问:"设备怎么注册和同步数据的?"

回答

设备认证:首次启动生成 UUID 作为设备 ID,调用云端 /api/device/register 注册,拿回 deviceId + deviceSecret + JWT Token,存到 electron-settings 里。之后每次启动用 deviceId/secret 登录换取新 Token。Token 过期后用 refreshToken 自动续,用户无感知。

做了容错处理——如果云端数据库重置导致 401 "设备不存在",自动清掉本地凭证重新注册。

心跳保活:HeartbeatService 定期发心跳,带设备状态和系统信息,云端用来确认设备在线。

数据同步:因为 SQLite 是本地数据库,同步到 MySQL 云端用了"全量上传"策略。每 5 分钟串行化 SQLite 数据库文件 → 通过 FormData 上传到 /api/sync/upload-db → 后端解析后入库。文件先 copyFileSync 到临时文件再读,避免 SQLite 读写锁导致读到一半报错。

同步完成后云端返回 syncedIds,本地把这些记录的 synced 字段标记为 true。

追问:"为什么不做增量同步?"

产线数据量不大,一次测量就一条检测报告记录,SQLite 文件也才几 MB。全量上传实现最简单可靠,60 秒超时足够。如果后续数据量大到 GB 级,再考虑用 Binlog 或 WAL 模式做增量同步。

Q7: 测量稳定性兜底体系

面试官可能问:"产线 7×24 小时运行,怎么保证稳定性?"

回答

工业产线对稳定性要求极高,任何卡死都会导致停线。我做了三重保障:

第一层:WebSocket 心跳 + 自动重连。 前端用 Socket.IO 连接主进程的 WebSocket 服务,定时发 ping/pong 心跳。断连后自动重试,重连成功后自动拉取最新设备状态。

第二层:测量超时自动复位。 前端发起测量后启动 60 秒倒计时——如果 60 秒内没收到 measurement-completed 事件,自动把 UI 状态复位为 "就绪",清除测量中状态,不阻塞下一次操作。后端也有对应超时保护,防止 UDP 请求卡在等待中。

第三层:全局异常捕获 + 自动重启。 主进程注册了 4 个全局监听:unhandledRejection(未处理的 Promise 拒绝)、uncaughtException(未捕获的异常)、multipleResolves(Promise 多次 resolve/reject 警告)、warning(进程警告)。前两个不退出进程只记录日志,uncaughtException 严重时 1 秒后调用 app.relaunch() + app.exit(1) 自动重启。生产环境还配置了开机自启动,即使整机断电重启也能自动恢复。

追问:"WebSocket 和 Socket.IO 有什么区别?为什么选 Socket.IO?"

Socket.IO 在原生 WebSocket 之上加了自动重连、心跳、房间、namespace 等能力。这种工业场景下,稳定性和可靠性比原始性能更重要,Socket.IO 的自动重连和心跳机制省了很多自己写的心智负担。

Q8: 检测报告与数据闭环

面试官可能问:"从品种配置到报告打印,数据是怎么流转的?"

回答

整个数据闭环用 Prisma 建模了完整的实体关系:

StandardVariety / MixedVariety(品种库)
  → Batch(批次管理)
    → DetectionRecord(每次检测记录,含 Q 值、温度、直径、回潮率、计算结果等)
      → DetectionReport(检测报告,汇总多条记录为一份正式报告)
        → ExcelJS 导出 / vue3-print-nb 打印

品种库分标准品种(棉/毛/麻/丝等写死的国标数据)和混纺品种(自定义纤维配比,自动计算综合回潮率)。每个品种独立配置公式参数。

批次用 batchNumber 区分不同生产线/订单,isActive 标记当前正在检测的批次。

检测记录存的是原始数据:Q 值、温度、直径、压力值,以及计算后的回潮率、偏差、判级结果(qualified/dry/wet)。calculationDetails 字段存完整的中间计算过程(Q_mean、Q_c、Q_c²、Effective_range、Qvar_ratio 等),方便溯源。

报告保存时自动算统计数据:合格率、平均回潮率、平均温度、合格/偏干/偏湿数量。支持 ExcelJS 导出和直接打印。

追问:"数据库设计上有什么考虑?"

SQLite 文件就是一个 .db 文件,备份和迁移简单——直接拷走就行。品种的公式配置用 JSON 字符串存,灵活扩展不需要改表结构。sysnced + syncedAt 字段追踪云端同步状态,离线模式也不丢数据。

Q9: 操作模式与触摸屏体验

面试官可能问:"工业场景的交互体验有什么特殊设计?"

回答

产线工人通常戴手套、用触摸屏操作,跟 Web 应用的使用场景完全不同。

双操作模式:设计了"标准模式"和"快捷模式"。标准模式做多面检测——放好纱线 → 测量多面 → 显示多面结果 → 计算平均值 → 保存报告。快捷模式是单面检测 + 测量完成自动保存,减少操作步骤,适合流水线高速场景。

触摸屏友好:所有按钮尺寸加大到 48px 以上,间距足够避免误触。集成了一个中文输入键盘组件(@crispychicken/zh-keyboard-vue),支持手写识别(@crispychicken/zh-keyboard-recognizer),解决了触摸屏上无法打字的问题——批号、品种名称、备注等需要文字输入的场景都能快速完成。

实时图表防抖动:ECharts 实时曲线做了降频刷新——收到高频数据不直接 setOption,而是用 requestAnimationFrame 合并到一帧渲染,避免 100ms 级别的数据推送导致图表卡顿。

追问:"手写识别是怎么集成的?"

键盘组件内置了手写输入模式,触发后弹出 Canvas 手写区域,用户画完后把笔迹数据发给 TensorFlow.js 做识别(WebGL 后端加速),识别结果插入光标位置。整个过程不依赖网络,完全离线运行。

行为面试常见题快速答

"为什么做这个项目?"

公司做纺织行业的智能制造,客户工厂的回潮率检测还是手工操作——用烘干称重法,一次检测要 2 小时,而且误差大。老板提需求要做一款微波检测仪配套软件,从零开始独立架构开发,硬件那边对接 FL-WAPP 探头和物联网关。

"遇到的最大难点?"

Prisma 在 Electron 里打包。查遍 GitHub Issues 和 Stack Overflow,试了 5-6 套方案才搞定——修改 output 路径、配置 extraResources、手写引擎路径查找函数。还有 UDP 数据采集的时序问题,早期版本时不时丢包导致测量卡住,后来改成 100ms 定时轮询 + 滑动窗口才解决。

"有什么遗憾?"

没有写自动化测试。产线设备不在我工位上,每次改代码都要跑产线去验证,效率很低。如果项目再做一遍,会先搭好 Mock UDP 和 Mock MQTT 的测试环境,把核心计算引擎和通信层的单元测试覆盖率提上来。

"怎么跟前端渲染进程、后端 API 服务做技术选型?"

因为是桌面应用,不需要前后端分离部署。Express 嵌在主进程里跑,渲染进程通过 IPC 间接调用,比配一套独立的 Vite Dev Server + 反向代理简单很多。数据库选 SQLite 是因为单机部署不需要 MySQL,一个 .db 文件就能备份恢复。

上一篇:01-基金助手 fund-helper 下一篇:03-zhihu-fisher

前端开发工程师 · 面试准备