是这样的,公式编辑器这个项目表面上看是一个低代码平台里的输入控件,但它背后的业务问题其实是:怎么让非研发人员安全、准确地配置复杂业务规则。
在低代码平台里,业务人员经常要配置计算逻辑,比如薪资规则里的”员工底薪 + 提成 * 绩效系数”,或者审批金额、折扣规则、跨字段校验。早期我们提供的是一个多行文本框,让用户写一段字符串,前端再用 eval 或 new Function 去执行。这个方案实现快,但问题很快暴露出来:用户写错括号、字段名或函数名时,只有运行到最后才报错;实施同学也不知道系统到底支持哪些函数;更严重的是,用户输入的字符串直接进入 JS 执行环境,安全风险很高。(2 分钟)
这个问题最早不是从技术评审里冒出来的,而是从实施反馈和线上配置错误里暴露出来的。实施同学经常需要找研发确认”这个函数能不能用""这个字段为什么算不出来”,研发排查时又发现很多错误只是括号、字段名、类型转换这类低级问题。如果一个能力每次使用都需要研发兜底,它就还没有真正产品化。
所以我当时把它重新定义成一个业务问题:不是做一个更漂亮的输入框,而是把”规则配置能力”从研发手里交给实施、产品和业务人员,同时不能牺牲安全、稳定性和可调试性。
这里的核心技术矛盾有两个。
第一是安全和灵活性的矛盾。公式本质上是一段用户自定义逻辑,越灵活越像代码;但一旦把它当代码执行,就会带来 XSS、越权访问、全局作用域污染,甚至原型链污染。
第二是表达能力和使用门槛的矛盾。平台需要支持函数、变量、嵌套表达式、空值处理和类型转换,但用户又不是程序员。如果只给纯文本框,用户就是盲写,配置效率很低,出错以后也很难定位。(4 分钟)
当时也评估过几种方案。第一种是继续用 eval,外面加一些黑名单过滤,比如禁止 window、document 这些危险词。这个方案很快,但安全上不可控,因为 JS 的逃逸路径太多,黑名单一定会漏。第二种是接入一个第三方表达式库,能解决一部分解析和执行问题,但平台里的业务字段、函数提示、权限上下文、前后端复用都需要深度定制,黑盒库很难满足。第三种是把公式全部丢给后端执行,前端只做输入,这样安全边界清楚一些,但用户输入时无法实时语法提示,也很难做到类似 Excel 的配置体验。
所以最后我选择自己做一套受控的公式引擎,底层用 AST 执行沙箱保证安全,上层用 Tiptap 做富文本交互,既控制执行环境,也把输入体验做得足够接近业务人员熟悉的表格公式。(6 分钟)
底层执行引擎分成三步。
第一步是词法分析和语法分析。用户输入公式后,我先把字符串拆成 Token,比如变量、数字、操作符、函数名、括号,再通过语法分析生成 AST。这样系统不需要等到运行时才发现错误,在输入阶段就能提示括号不匹配、非法运算符、函数参数数量不对这些问题。
第二步是受控执行。我彻底去掉 eval,不把用户输入交给 JS 引擎直接执行,而是对 AST 做后序遍历。每个节点只允许访问我显式注入的上下文,比如当前表单字段值、白名单函数和只读变量。加减乘除、函数调用、条件表达式都在这个受控解释器里完成。这样用户没机会碰到 window、document、Function、prototype 这些危险对象,也就从根上降低了 XSS 和原型链污染风险。
第三步是测试约束。公式引擎的边界情况非常多,比如除以零、空字符串、null、数组字段、日期比较、类型隐式转换,所以我用 TDD 的方式沉淀了大量测试用例。因为这个引擎一旦出错,影响的是所有使用公式的业务流程,不能靠人工回归兜底。
上层交互我选了基于 ProseMirror 的 Tiptap,而不是普通 textarea。原因是公式里有很多业务字段,比如”员工姓名""部门""审批金额”,这些字段不应该被用户当普通文本随意拆开。我们把字段渲染成原子化胶囊,也就是自定义 NodeView。视觉上它是一个标签,底层对应稳定的字段 id,用户可以整体删除,但不能把光标插到中间改掉一半。这样既保证展示友好,也保证底层公式结构不会被误操作破坏。(9 分钟)
为了降低盲写成本,我还做了实时语法高亮和智能提示。用户输入函数名、左括号或者字段引用时,编辑器会根据当前 Token 和光标位置弹出候选项,展示函数参数说明和示例。这里有一个细节是,提示框不能简单跟随输入框左上角,而要根据 Tiptap 当前 selection 映射到 DOM 坐标,才能在复杂滚动容器、弹窗、缩放场景下准确贴着光标出现。
这套方案也有几个需要提前处理的边界。比如公式里如果未来要支持跨表查询或远程函数,AST 执行就会遇到异步 Promise,不能再按纯同步解释器处理。再比如原子化胶囊要拦截退格、方向键、复制粘贴和 undo-redo,否则用户还是可能把 NodeView 破坏掉。还有,如果前后端都要执行同一套公式,就要考虑 AST 结构、函数白名单和缓存策略怎么复用,不能让前端算一套、后端算一套。(12 分钟)
最终它带来的业务价值,不是前端做了一个复杂编辑器,而是把低代码平台的规则配置能力真正产品化了。实施和产品人员可以自己配置复杂规则,不需要每次找研发改代码;语法提示和即时错误定位降低了配置门槛;AST 沙箱让平台没有因为公式执行暴露安全漏洞;这套表达式引擎后来也能抽成基础能力,给后端轻量规则引擎复用。
这里如果被追问”效果怎么证明”,我会按 目标、边界、证据、价值 把口径讲具体。可用性看实施侧是否还需要研发频繁介入公式配置,以及错误能不能在输入阶段定位;稳定性看解析、执行、边界条件的自动化测试覆盖,而不是靠上线后人工试;安全性看执行上下文是否有白名单边界,能不能访问 window、document、Function、prototype 这些危险入口。后面我们也会用 AI 辅助补边界测试样例,比如空值、深层嵌套、非法函数、类型转换,但 AI 生成的 case 只作为候选,最终仍然要落到 AST 结构和解释器输出的断言上。
从全流工程师角度看,这个项目的价值在于把业务规则从研发脑子里的代码逻辑,变成实施、产品和后端都能消费的结构化知识。
早期公式只是字符串,真正的规则含义藏在运行时和排查经验里;一旦配置错了,实施不知道错在哪里,研发也要回到代码和现场数据里反推。后来我把变量、函数、类型、上下文和执行结果都显式建模到 AST 和函数白名单里,前端编辑器负责让用户低门槛表达规则,执行沙箱负责保证安全,测试用例负责沉淀边界条件,后端规则引擎也可以复用同一套结构。
这里体现的全流能力,是我把”输入一个公式”放回了规则配置的完整链路里看:业务人员要能表达,实施要能排错,研发要能扩展函数,测试要能验证边界,后端要能在关键流程里稳定执行。
一句话说,公式编辑器要回答的是”业务规则配置如何既开放又受控”。正文里这些设计也自然会引出几个深挖点,比如 AST 沙箱遇到异步逻辑怎么处理,Tiptap 原子化胶囊如何防止光标越界,智能提示层如何计算光标坐标,前端不用 iframe 时怎么防原型链污染,以及后端如何复用前端 AST 逻辑并避免重复解析。(15 分钟)
- 核心防御与深挖靶场 (面试官可能的追问)
在 15 分钟的面谈讲完上述框架后,面试官极大概率会顺着你的诱饵进行深挖。我已经为你准备好了以下 5 个独立的高频防御阵地(点击跳转复习):
- 🛡️ 编译原理与引擎防线(前端):AST 沙箱如何处理异步逻辑和性能黑洞 (公式里如果需要调用后端接口查跨表数据,沙箱怎么处理 Promise?)
- 🛡️ 富文本交互防线(前端):Tiptap 原子化胶囊如何防止光标越界或被部分删除 (用户强行把光标塞进只读胶囊里,或者退格键只删了一半怎么拦截?)
- 🛡️ 坐标计算防线(前端):公式智能提示层如何跟随光标位置 (在复杂的 DOM 嵌套里,如何精准计算出提示框应该弹出的 Absolute 坐标?)
- 🛡️ 隔离机制防线(前端):前端 JS 沙箱隔离如何防御原型链污染 (如果没有用 iframe,如何在前端层彻底杜绝业务通过原型链修改原生 Array 或 Object 的行为?)
- 🛡️ 解析与执行防线(后端):后端规则引擎如何复用前端 AST 逻辑 (前端配置的公式在后端执行时,如果业务量巨大,后端如何避免频繁的脚本解析开销?)