学校里总有一些“看起来很适合做成小程序”的事情:活动报名、设备报修、培训登记、材料收集、场地借用、经费申请。刚开始,需求往往只有一句话:“做个表单,填完以后负责人能审核。”真正动手才会发现,输入框和列表页并不难,难的是一条申请离开提交人之后,究竟可以被谁看见、被谁修改、被退到哪里,以及每一步该留下什么证据。
一个可用的审批工具,不是给数据加几个“通过”“不通过”按钮,而是把原来依靠口头交代和聊天记录维持的规则,变成系统能够执行、复查和解释的状态流转。
所以,中层管理者或教师用 Codex、OpenCode 一类工具做校园 CRUD 时,第一张图不该是页面原型,第一段提示词也不该是“帮我生成一个管理系统”。先把状态、动作、角色和证据说清楚,后面的页面、接口和数据库才不会越改越乱。
先画出一张业务流转图,再画页面
先选一个足够具体的场景。例如“教师提交校内活动申请,部门负责人审核,退回后可以修改重交,通过后由办公室归档”。把参与者写在纸的两边,把申请可能经过的节点放在中间,再用箭头连接。此时先别讨论按钮颜色、首页统计和消息提醒,只回答三个问题:申请现在在哪里,下一步允许去哪里,谁能推动它。
最小流程可以只有五个状态:草稿、待审核、退回修改、已通过、已归档。它们已经足以暴露许多隐藏规则:草稿能否由别人看到;待审核期间提交人能否改内容;退回后是覆盖原记录,还是生成新版本;通过后如果发现填错,谁有权重新打开;归档是否代表业务真正结束。

画图时不要用“处理中”“已完成”这种包罗万象的词。“处理中”可能是等待部门审核,也可能是等待申请人补材料;“已完成”可能只是审核通过,也可能已经执行、验收和归档。状态越模糊,系统越依赖线下追问,最后只剩下一张比电子表格更难改的表。
状态不是备注,而是系统事实
不少小工具把状态做成一个可以随意编辑的文本字段。管理员输入“通过”,系统就当它通过;输入“退回”,列表就换一种颜色。这种实现看似灵活,实际上把业务规则交给了拼写。有人写“已通过”,有人写“通过”,有人误删一个字,统计、筛选和权限判断就会失去依据。
状态应当使用有限、固定的值,并由后端按合法动作修改。例如数据库内部可以保存 DRAFT、PENDING、RETURNED、APPROVED、ARCHIVED,页面再显示对应的中文。数据库还应使用非空、唯一、外键或检查约束,把“必填”“不能重复”“引用对象必须存在”这类基本规则放到数据层兜底。PostgreSQL 的约束文档列出的这些机制,正是防止无效数据悄悄进入系统的最后一道门。
另一个容易忽略的字段是版本。申请被退回修改后,原内容不应无痕消失。至少要保存修改前后的版本号、修改人和修改时间;重要流程还可以保存字段差异。这样,审核人看到的不只是“又提交了一次”,而是知道申请人具体补了什么。
每个状态只允许少数合法动作
状态机的核心不是状态列表,而是“从这里能做什么”。草稿可以编辑、删除、提交;待审核可以通过或退回,但通常不能由提交人继续改;退回修改可以重新编辑和提交;已通过原则上不能直接删;已归档只允许查看和导出。任何没有被明确允许的跳转都应拒绝。
这条规则必须在服务端执行,不能只靠前端隐藏按钮。用户可能打开旧页面、重复点击,也可能直接调用接口。OWASP 的授权检查建议强调默认拒绝、每次请求都验证权限。落实到审批工具,就是接口收到“通过”请求时,不只检查操作者是不是审核人,还要确认这条申请此刻确实处于待审核状态。
因此,接口最好表达业务动作,而不是让客户端任意改字段。与其提供“把 status 改成任意值”的通用接口,不如提供提交、退回、通过、撤回、归档等明确动作。动作越具体,校验条件、审计内容和错误提示越容易写清楚。
角色和人员分开:提交人、审核人、管理员
“张老师可以审核”不是一条稳定的系统规则,因为人员会调岗,工作会交接。系统应该先定义角色,再把人员放进角色。提交人只能管理自己的草稿和退回件;审核人处理分配给本部门的待审核申请;管理员负责配置、异常恢复和账号管理,但不应默认代替所有人完成业务审核。
NIST 对基于角色的访问控制的解释中,权限通过角色分配,角色对应组织中的工作职能。对校园小工具而言,这比在代码里写死某个姓名、手机号或账号可靠得多。部门负责人更换时,只需调整角色成员,不必修改程序。

角色也不是越多越好。第一版常用的三个角色已经够用:普通提交人、业务审核人、系统管理员。如果某项流程确实需要年级、部门和校级多级审核,再增加审核层级。不要为了“以后可能用到”一次性设计十几种权限,否则配置本身就会变成新的管理负担。
退回、撤回和驳回不是一回事
这三个动作在口头交流中常被混用,放进系统却必须分开。退回意味着材料暂时不完整,申请人修改后可以再次提交;撤回由申请人发起,表示在审核完成前主动收回;驳回则表示本次申请不成立,流程结束,若要再办应重新发起。三者的责任人、后续路径和统计含义都不同。
退回时必须填写具体原因,最好能指向缺失字段或附件。仅写“请完善”会把系统里的问题重新变成聊天追问。撤回要限定时机:已经通过甚至已经执行的事项,不能由提交人一键撤回。驳回应谨慎开放,并与“退回修改”在界面上明显区分,避免审核人点错后让整条申请直接终止。
还要为误操作保留恢复路径,但恢复不能等于直接改数据库。可以由管理员发起“异常重开”,填写原因,并产生一条独立审计记录。这样既能处理现实中的特殊情况,也不会让流程规则形同虚设。
一张表定义状态、动作和证据
在让 AI 生成代码前,先完成下面这张表。它既是需求说明,也是接口测试清单。每一行都要能回答:谁在什么状态下执行什么动作,系统校验什么,成功后留下什么。
表里最好再补上失败情况:非本人修改草稿、普通用户审核、重复提交、已归档记录再次通过、缺少退回原因。把这些写成自动化测试后,生成式工具每次改代码都能重新检查,而不是靠人把所有按钮再点一遍。
通知、超时和并发要从流程里长出来
通知不是每次保存都发一条消息,而是状态发生关键变化时提醒下一位责任人。提交后通知审核人,退回后通知提交人,通过后通知执行或归档人员。通知内容要带申请编号、当前状态和可执行动作;通知失败不能回滚已经成功的审批,但应进入重试队列,并让管理员看见失败记录。
超时也应绑定状态。例如待审核超过三个工作日,先提醒审核人;继续超时再提醒其上级或流程管理员。不要一开始就把复杂的催办规则写死,可以先记录“进入当前状态的时间”,等真实运行一段时间后,再依据积压情况设定阈值。
并发问题则常在上线后才出现:两位审核人同时打开同一申请,一人通过,另一人稍后又退回。如果后一次操作直接覆盖前一次,申请就会倒着走。处理方式可以是事务中的行锁,也可以在更新时比较版本号;不论采用哪一种,都要保证“我审核的仍是刚才看到的那个版本”。PostgreSQL 的显式锁文档说明了行级锁如何阻止同一数据上的冲突更新。
审计记录要能还原事情经过
业务表只保存“现在是什么”,审计表负责回答“之前发生过什么”。每次动作至少记录申请编号、原状态、新状态、动作、操作者、时间、理由和内容版本。记录应追加而不是覆盖,普通用户不能修改或删除。导出、批量下载、管理员代操作等高影响行为,也应该留下记录。

OWASP 的日志建议把增加、修改、删除、导出以及能够还原操作顺序的审计轨迹列为重要事件。同时也提醒日志不能什么都记:密码、令牌、敏感个人信息不应原样进入日志。学校工具里的学生信息、联系方式和附件地址尤其要控制记录范围与查看权限。
有了可信的审计记录,争议处理才不必依赖聊天截图。管理者可以看到申请在何时卡住,开发者可以定位重复操作,教师也能知道材料到底被谁退回、为什么退回。这些价值往往比首页那几张漂亮的统计卡片更实在。
先做最小闭环,再加统计和 AI
第一版只要完成一个可靠闭环:登录后按角色看数据,提交人能保存草稿并提交,审核人能通过或退回,退回件能修改重交,所有动作有审计记录,刷新和重新登录后状态仍然正确。再补上必要的附件限制、数据导出和备份,这个工具就已经可以解决真实问题。
统计应当从稳定状态中生成,例如各部门待审核数量、平均处理时间、退回原因分布。AI 可以放在更靠后的位置:帮助检查必填材料、归纳退回原因、生成汇总说明,但不能绕过权限,也不能替审核人自动做最终决定。输入给模型的数据要经过最小化和脱敏,模型输出仍然作为建议由人确认。
给 Codex 或 OpenCode 的任务也应分阶段:先根据状态表生成数据模型和迁移;再实现动作接口与权限测试;随后做页面;最后接通知和统计。每完成一层就用真实角色走一遍流程。这样生成式工具是在落实已经说清楚的规则,而不是一边写代码一边替学校猜规则。
校园里的小工具不需要一开始就成为“综合管理平台”。先让一件事从提交到归档走得清楚、可靠、可追溯,第二条流程才有可以复用的账号、权限、状态和审计基础。CRUD 只是把数据放进去,状态流转才真正把工作接起来。
做一个收集审批小工具,先从状态流转开始
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法