我做后端这几年,见过太多审批系统。有的把审批逻辑硬编码在业务代码里,改一个流程要发版;有的买了第三方的,配置复杂到产品经理都看不懂。去年我开始搭自己的微服务体系,第一个想的不是认证、不是权限——而是「审批」这个事能不能抽出来,做成一个独立服务,不管哪个业务都能接进来。
半年过去了,审批引擎跑起来了。今天跟你聊聊我是怎么想的、怎么做的,以及中间踩的几个坑。
不是所有审批都一样
刚开始我想得很简单:一张申请表、一个审批人、通过或驳回。但真正开始做了才发现,审批的复杂度不在「通过/驳回」,而在「不同业务长得完全不一样」。
权限申请要展示权限名称和理由,博客审核要展示文章标题和内容预览,考试申请要展示考试名称和时间。如果给每种业务写一套展示逻辑,那这个审批引擎就废了——每次接新业务都要改代码。
我的解法:Schema 快照。
每套审批模板定义一份 form_schema,描述这个业务有哪些字段、字段叫什么、怎么展示:
{"fields":[{"key":"permissionName","label":"申请的权限","type":"text"},{"key":"reason","label":"申请理由","type":"textarea"}]}创建审批单的时候,把当时的 schema 拍一个快照存进审批实例。以后不管模板怎么改,这个审批单就按创建时的 schema 展示,互不影响。
这个设计后来被验证了好几次——模板改了十几版,历史审批单一个没乱。
第一个坑:申请人不能审批自己
这是测试的时候发现的。我自己提了个权限申请,跑到「待我审批」里一看,居然能看到自己的申请,点了「通过」居然还能通过。
这要是在生产环境,权限就变成自己给自己发了。
前后端双重校验。后端查申请人 ID 和当前用户 ID,相等直接抛「不能审批自己的申请」。前端在列表里提前过滤掉,不让按钮出现。两边都做了,就算绕过前端直接调接口也过不去。
第二个坑:回调的可靠性
审批通过之后要回调业务系统——比如权限申请通过了,要调 perm-service 给用户加权限。问题来了:如果回调失败了怎么办?
我见过有的系统直接不管,失败了就失败了,管理员手动补。这在一个独立开发者手上就是噩梦——你可能过了两天才发现权限没加上。
我的方案:重试 3 次,间隔 1 秒,记录状态。
审批通过/驳回后,立刻 POST 到业务系统配置的回调地址。失败就重试,最多 3 次。每次重试的间隔递增。3 次都失败,标记 callback_status = failed,详情页显示一个黄色「通知失败」提示,管理员一眼能看到哪些需要手动处理。
成功了就显示绿色「已通知」,不用管了。
第三个坑:撤回和重新提交
用户提了申请,发现填错了,想撤回。简单:pending 状态的申请直接置为 cancelled。
但重新提交呢?这时候模板的 schema 可能已经改了。如果按新 schema 展示,用户填的是旧字段,就对不上了。
重新提交继承旧的 schema_snapshot。不会因为模板改了就让用户的申请变样。
最后:五张表搞定一切
审批引擎只有五张表:
t_approval_template:审批模板(定义流程和 schema)t_approval_node:审批节点(多级审批)t_approval:审批实例(每一条申请)t_approval_record:审批记录(每一步的操作历史)t_user_permission:用户权限(审批通过后写入)
多一级审批就加一个 node,多一种业务就加一个 template。不用改代码。
回头看看
这个审批引擎算不上什么高深的技术,但它是我自己设计、自己实现、自己验证过的。从 schema 快照的思路,到回调重试的机制,到撤回重新提交的处理——每一个决策都是踩了坑以后逼出来的。
独立开发者有个好处:你做的东西不用向任何人妥协。不好用的地方你就改,想不通的地方你就推翻。最后留下来的是什么?是真正经得起推敲的东西。
关于作者:无羡,独立开发者,全栈工程师,专注 AI 应用与微服务架构。
📌 分类:独立开发实战
🔗 个人博客 — 更多技术文章
✨ 开发者福利整理 — 云服务资源汇总
📂 软件工坊 — 我的技术分享
🌐 个人门户 — 独立开发作品全集