简介:一份面向企业、机关单位的微信小程序办公审批源码,覆盖请假、报销、外出、合同、采购、入职等常见审批场景,实现了从提交申请、审批流转到微信消息提醒的完整闭环。系统内置管理员、员工、审批人员三种角色权限,便于按照组织架构分配管理职责。压缩包共489个文件,其中js负责业务逻辑,wxss与wxml分别处理界面样式和页面结构,json为项目配置,png、jpg提供界面图标与示例图片,docx版安装使用手册可辅助快速完成配置部署;整个资源包仅2.12MB,非常轻量易维护。目前已有47人学习下载。代码中另附二维码生成、图表统计、数据库操作等工具模块,目录组织清晰,既适合直接改造成内部OA审批工具,也可作为微信小程序多角色开发、消息推送与表单状态管理的不错学习范例。
1. 企业办公审批小程序到底在解决什么:一张单据卡在三个部门手里的成本
某公司行政部的月度报销,从员工提交到财务打款,平均要经过直属领导、部门负责人、财务、总经理四个节点,每个节点平均等两天。月底对账时发现二十多张单据还在途中,有人离职后审批单直接变成“无主待办”。这不是某个人拖延,而是流程没有“时限感”——纸质单据和散落在聊天记录里的口头审批,既看不到节点状态,也没有超时机制,更没有代理规则。标题里的这套小程序要解决的就是把请假、报销、外出、合同、采购这几类高频办公审批搬到线上,让单据在状态机上自动流转,每个节点都有责任人、有耗时、有提醒。适合的企业不只是大公司,机关部门里被流程拖累的行政、财务、HR团队同样适用;技术团队则能看到一条从选型到落地、再到排障的完整路径。
我最初接触这类需求时,对方PPT上写着“OA系统”,可预算只够做一个小程序。后来想明白一件事:审批系统不需要做得像传统OA那样大而全,只需要抓住“单据、流程、记录”三个核心模型,五类审批都能跑通。下面从原理到代码,把这条路径拆开讲。
2. 选型先于编码:审批流的状态机、表单驱动与运行环境
2.1 审批流的本质:状态机加路由规则,不是一套增删改查
一张请假单从草稿到归档,会经历提交、审批中、通过、驳回、撤回、作废这些状态。每一次审批操作都是一个事件,事件驱动状态迁移,这本质上就是一个有限状态机。我见过很多初版代码把每类单据做成独立的接口,askLeave、reimburse、contractApprove各写一套,结果就是同样的“通过”逻辑复制了五遍,后来制度一改,五个地方都要改,漏一个就出线上事故。
正确做法是先抽象出通用状态迁移:单据状态分为草稿、审批中、已通过、已驳回、已撤回、已作废;流程实例状态分为运行中、已完成、已终止。每次操作只做三件事:校验当前状态是否允许该操作,执行状态迁移,记录审批日志。路由规则单独表达——满足金额条件走总经理节点,否则在财务节点结束。把这两层分开后,五类单据的差异就只是“路由规则不同”,代码层面只有一套。
再说要不要引入开源工作流引擎。坦白说,企业办公审批的流程深度有限,节点很少超过十个,但流程种类很多、调整频繁。重型引擎功能强,但学习成本高、部署重、配置复杂,一个小型审批项目用起来像高射炮打蚊子。我一般会先画一遍五类单据的状态迁移图,如果发现流程都是线性或带一两个分支,就选择自研轻量状态机:一张流程模板表加一个解释器,两百行代码就能覆盖绝大多数场景。
2.2 表单与流程解耦:配置化驱动还是硬编码流程
很多团队起步时把流程写死在代码里,请假走 leaveFlow(),报销走 reimburseFlow()。如果公司制度一年才改一次,硬编码没问题;但现实是审批制度半年就调一次:金额阈值从五千调到三千,合同审批要加一个法务会签。每次调整都要改代码、走发布流程,审批系统就成了上线最频繁的项目,早晚出乱子。
我的建议是表单与流程彻底解耦。表单数据统一存成 JSON,不针对每种单据建表;流程用模板表存储,节点列表、审批人规则、分支条件都放在配置里。代码只负责解释配置并驱动状态流转,业务上调整流程时只改数据不动代码。
需要提醒的是配置化要控制粒度。做到“节点级配置”就够了:每个节点定义审批人来源(指定人、角色、发起人的部门负责人)、通过后到哪个节点、驳回后回到哪个节点。不需要做成可视化拖拽流程设计器,那是个独立的复杂产品。配置化真正难的是建立配置校验机制,比如检查所有节点是否可达、是否存在死循环,这些校验代码比流程引擎本身更容易被忽视。
2.3 从开发框架到运行环境:推荐的技术栈与选型理由
审批系统是典型的读多写少、短事务场景,对实时性要求不高,但对数据一致性要求很高——单据状态和待办列表必须严格一致,不能出现“状态显示已通过但审批人还收到待办”的错乱。基于这个判断,我推荐的起步组合如下:
| 层级 | 选型 | 理由 |
|---|---|---|
| 后端 | Python FastAPI 或 Java Spring Boot | 开发效率高;审批逻辑简单,不需要重型中间件 |
| 前端 | 微信小程序原生 | 人员出入审计方便,真机调试直接;uni-app 多端编译是备选 |
| 数据库 | MySQL | 审批记录要联表查询,事务边界清晰 |
| 缓存 | Redis(可选) | 只用来做分布式锁和待办计数,不是核心依赖 |
| 部署 | Docker 单容器 | 内网服务器或云主机都能跑,起步成本低 |
| 消息触达 | 小程序订阅消息 | 一次性订阅需用户授权,审批提醒的主要通道 |
这套组合的关键决策是不引入消息队列和微服务。审批系统的并发量通常很低,峰值可能就是月底集中报销那几百单,单机加 MySQL 完全扛得住。引入 MQ 并没有解决问题,反而让“消息丢失导致待办漏生成”成了新风险。审批链路里的事务一致性,比削峰填谷重要得多。
3. 把请假审批流程跑通:三张表与后端流转代码
3.1 审批单据、流程实例、审批记录三张表如何设计
请假审批是五类流程里最简单的一种,正好用来建立最小闭环。我用三张核心表加一张待办表来描述。为什么拆三张:审批单据承载业务数据(请假的起止日期、事由、时长),流程实例承载当前流转位置,审批记录是审计日志,待办表是给审批人的任务清单。分开之后,查“某人发起过哪些单子”“某个节点是谁批的”“谁手头还有多少待办”都很直接。
建表 SQL 如下。
CREATE TABLE approval_bill ( id BIGINT PRIMARY KEY AUTO_INCREMENT, bill_no VARCHAR(32) NOT NULL COMMENT '单号,如 LV202506001', bill_type VARCHAR(20) NOT NULL COMMENT 'leave/reimburse/out/contract/purchase', title VARCHAR(200) NOT NULL COMMENT '标题,如 张三-请假3天', applicant_id BIGINT NOT NULL COMMENT '发起人', dept_id BIGINT COMMENT '发起人部门', amount DECIMAL(12,2) DEFAULT 0 COMMENT '金额,请假为0', form_data_json JSON COMMENT '表单数据统一存JSON', status VARCHAR(20) NOT NULL DEFAULT 'draft' COMMENT 'draft/approving/passed/rejected/recalled', flow_version INT NOT NULL DEFAULT 1 COMMENT '锁定的流程版本', current_node_id VARCHAR(50) COMMENT '当前所在节点ID', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, finish_time DATETIME NULL COMMENT '最终审批完成时间', KEY idx_bill_type_status (bill_type, status), KEY idx_applicant (applicant_id) ) COMMENT '审批单据表'; CREATE TABLE flow_instance ( id BIGINT PRIMARY KEY AUTO_INCREMENT, bill_id BIGINT NOT NULL, template_id BIGINT NOT NULL COMMENT '流程模板ID', current_node_id VARCHAR(50) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'running' COMMENT 'running/finished/terminated', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_bill (bill_id) ) COMMENT '流程实例表'; CREATE TABLE approval_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, bill_id BIGINT NOT NULL, node_id VARCHAR(50) NOT NULL COMMENT '审批节点ID', approver_id BIGINT NOT NULL COMMENT '审批人', action VARCHAR(20) NOT NULL COMMENT 'agree/reject/transfer/recall', comment TEXT COMMENT '审批意见', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_bill (bill_id) ) COMMENT '审批记录表'; CREATE TABLE todo_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, bill_id BIGINT NOT NULL, node_id VARCHAR(50) NOT NULL, approver_id BIGINT NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending/done/canceled', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_approver_status (approver_id, status) ) COMMENT '待办表';表单数据放进 JSON 字段是这套设计的关键。请假单和报销单的字段完全不同,如果每种单据建一张业务表,每加一种审批类型就要建表、写 CRUD、出联调接口。JSON 字段配合前端动态渲染表单,新单据类型只要后台配一个表单描述 JSON 就能上线。代价是无法用 SQL 直接按表单内的字段做聚合统计,需要时可以把高频统计字段冗余到 approval_bill 的独立列。
3.2 提交审批并创建流程实例的后端实现
提交请假审批的接口逻辑分三步:创建单据、创建流程实例并定位到第一个节点、给第一节点审批人生成待办。三个操作必须在同一个事务里提交,否则会出现“单子建好了但待办没生成”的数据错乱。
@app.post("/api/approval/submit") def submit_approval(req: SubmitReq, db: Session): # 1. 创建审批单据,初始状态为审批中 bill = ApprovalBill( bill_no=gen_bill_no(req.bill_type, today()), bill_type=req.bill_type, title=req.title, applicant_id=req.applicant_id, dept_id=req.dept_id, amount=req.amount, form_data_json=req.form_data, # 表单整体存JSON status="approving", flow_version=req.flow_version, # 锁定当前流程版本 current_node_id="", ) db.add(bill) db.flush() # 先拿到自增id # 2. 加载流程模板,定位第一个节点 template = get_flow_template(req.bill_type, req.flow_version) first_node = template["nodes"][0] # start节点,通常指向第一个审批节点 target_node = first_node.get("next") instance = FlowInstance( bill_id=bill.id, template_id=template["id"], current_node_id=target_node, status="running", ) db.add(instance) # 3. 解析该节点的审批人,逐人生成待办 approvers = resolve_approvers(template["node_map"][target_node], bill) for uid in approvers: db.add(TodoItem(bill_id=bill.id, node_id=target_node, approver_id=uid, status="pending")) # 同步更新单据上的当前节点 bill.current_node_id = target_node db.commit() return {"bill_no": bill.bill_no}这段代码里有两个容易被忽略的参数。flow_version是发起时就锁定的流程版本,后面制度改了也不会影响这笔已成单,历史单据按旧规则跑完。resolve_approvers负责把配置里的“角色表达式”解析成具体人,例如配置写role:dept_leader,运行时根据发起人的部门查出该部门当前负责人,这样人走了换负责人,已有单据的后续审批也能自动找到新审批人。
3.3 通过、驳回、转交:状态迁移与消息触达
审批操作是这套系统的核心风险点。并发场景下,同一节点如果有多个审批人(比如会签),两个人同时点“通过”,后一个操作可能覆盖前一个的流转结果。标准做法是乐观锁:更新单据时在 WHERE 条件里带上当前节点和状态,影响行数为 0 说明状态已被别人改过。
@app.post("/api/approval/action") def approval_action(req: ActionReq, db: Session): bill = db.get(ApprovalBill, req.bill_id) if not bill: raise BizError("单据不存在") # 乐观锁更新:必须满足“当前节点一致且审批中”才能更新 next_node_id, next_status = compute_next_state(bill, req.action, db) updated = db.execute( text(""" UPDATE approval_bill SET current_node_id = :next_node, status = :next_status WHERE id = :bill_id AND current_node_id = :node_id AND status = 'approving' """), {"next_node": next_node_id, "next_status": next_status, "bill_id": bill.id, "node_id": req.node_id} ) if updated.rowcount == 0: raise BizError("单据状态已变化,请刷新后重试") # 写入审批记录 db.add(ApprovalRecord( bill_id=bill.id, node_id=req.node_id, approver_id=req.approver_id, action=req.action, comment=req.comment )) if req.action == "reject": # 驳回:单据置为驳回,待办全部取消 cancel_pending_todo(db, bill.id) notify_applicant(bill, "你的审批单被驳回了") elif req.action == "agree": # 通过:把当前节点的待办置为完成,为下一节点生成待办 finish_todo(db, bill.id, req.node_id, req.approver_id) if next_node_id == "end": bill.finish_time = now() notify_applicant(bill, "审批已全部通过") else: approvers = resolve_approvers(node_map[next_node_id], bill) for uid in approvers: db.add(TodoItem(bill_id=bill.id, node_id=next_node_id, approver_id=uid, status="pending")) notify_approvers(approvers, bill) elif req.action == "transfer": # 转交:把当前待办转移给其他人 transfer_todo(db, bill.id, req.node_id, req.approver_id, req.transfer_to) db.commit()compute_next_state返回两个值:下一节点 ID 和单据状态。走到最终节点时状态置为 passed,否则保持 approving。这里有个容易翻车的地方:驳回后待办必须全量取消,不只是驳回人自己的那条。因为会签节点可能有多个审批人都收到了待办,一个人驳回,其他人的待办若不取消,就会出现“单据已驳回但另一个人还能点通过”的错乱。消息通知用小程序订阅消息,特点是每次发送前需要用户授权一次,所以要在审批人第一次登录时引导授权,授权次数用完就提醒重新授权,这是微信平台的限制,需要产品上做好提示。
4. 报销、采购、合同审批的共性拆解:一份流程配置覆盖五类单据
4.1 报销与采购审批的节点差异与前置校验
请假、外出、报销、合同、采购五类单据,流程结构的共同点是“发起 → 部门审批 → 财务/法务审批 → 结束”,真正的区别在分支条件和前置校验。报销和采购的差异尤其典型:报销的关键维度是金额和预算来源,采购还要多一个比价或收货确认环节。
我在配置层用条件节点表达这种差异。报销流程可以描述为:部门负责人审批后,进入条件节点,金额小于等于 5000 直接到财务;大于 5000 先到总经理再到财务。采购流程则在部门负责人和财务之间插入采购主管节点,金额超过阈值再加一轮比价确认。
{ "bill_type": "reimburse", "version": 3, "nodes": [ {"id": "start", "type": "start", "next": "dept_leader"}, {"id": "dept_leader", "type": "approve", "approvers": ["role:dept_leader"], "next": "branch_amount"}, {"id": "branch_amount", "type": "condition", "expression": "amount > 5000", "next_true": "gm_approve", "next_false": "finance"}, {"id": "gm_approve", "type": "approve", "approvers": ["role:gm"], "next": "finance"}, {"id": "finance", "type": "approve", "approvers": ["role:finance"], "next": "end"}, {"id": "end", "type": "end"} ] }条件表达式我用白名单方式解析,只允许amount、dept_id、bill_type这几个预置字段参与比较,绝不直接eval()用户输入。这个设计决定了制度调整的灵活度:改阈值只需要更新配置版本,不需要动代码。前置校验(比如报销单必须关联预算单)可以作为独立的校验钩子挂在节点进入时执行,如果校验不通过,流程直接驳回给发起人并附上失败原因。
4.2 合同审批的特殊处理:会签、加签与附件版本管理
合同审批和请假审批最大的不同是存在“会签”节点:一份合同可能同时需要法务、财务、业务三方都同意才算通过,而不是任何一个人点了就可以。请假和报销是或签——一个审批人通过即可推进;合同是“全部通过才推进”,有一方驳回则单据驳回。
会签的判定逻辑不能简单按“当前节点有人通过就前进”,要统计该节点下所有审批人的意见。
def on_approve_signoff(node, bill, db): # 会签节点:统计已同意人数,达到总人数才推进 total = len(node["approvers"]) agreed = count_agreed_approvers(db, bill.id, node["id"]) if agreed >= total: advance_to_next(bill, node["next"], db) # 未达到总人数时,流程保持在本节点,等待其余审批人处理加签则是审批过程中临时插入一个审批人,比如法务负责人觉得需要咨询外部律师意见,可以把律师加进来。实现上是在当前节点的审批人列表里动态追加记录,追加后待办表生成一条新待办,但总人数要同步增加,否则会签判定会漏。这个操作本身也要写入审批记录,便于审计追溯。
合同附件是另一个容易踩坑的点。合同文件会被修改、替换,如果附件表直接覆盖原文件,审批记录里就无法还原“当时批的是哪一版”。我的做法是附件表带版本号,每次上传生成新版本记录,审批记录关联具体版本 ID,任何时候都能回溯。审批人看到的永远是最新版本,但历史版本保留完整。
4.3 用 JSON 定义流程模板:让审批流变成可配置数据
五类单据共用一个流程引擎,核心就是一份 JSON 模板加一段解释执行代码。模板里我固定使用五种节点类型:start、approve、condition、cc、end。approve 节点支持会签和或签两种模式,cc 用于抄送通知,condition 用于分支路由。
解释器的核心逻辑是“根据当前节点和审批动作计算下一节点”。这一段代码是整个审批系统的中枢,生产环境里我建议为它单独写单元测试,覆盖每类单据的所有分支路径。
def compute_next_state(bill, action, db): template = get_flow_template(bill.bill_type, bill.flow_version) node_map = template["node_map"] current = node_map[bill.current_node_id] if action == "reject": return "end", "rejected" if action == "transfer": return bill.current_node_id, "approving" # action == "agree" if current["type"] == "approve": if current.get("mode") == "signoff": # 会签模式:未达到全部同意,停留当前节点 if not signoff_completed(current, bill, db): return bill.current_node_id, "approving" return current["next"], ("passed" if current["next"] == "end" else "approving") if current["type"] == "condition": branch = eval_condition(current["expression"], bill) return current["next_true"] if branch else current["next_false"], "approving" return current.get("next", "end"), "approving"解释器里要特别处理死循环。条件节点可能出现 A 条件指向 B、B 条件又指向 A 的情况,配置时应该做一次静态检查:构建节点图,用深度优先搜索验证从 start 到 end 的可达性,并打回含环的配置。这个校验我是做成发布流程的一步,配置更新必须过校验才能生效。做到这层,新增一种审批类型的工作量就变成“写一份 JSON + 配一个表单 + 定几个角色表达式”,不用写一行后端代码。
5. 审批小程序避坑指南:来自多个真实项目的血泪经验
5.1 坑一:并发审批导致节点状态被“重置”
现象:同一节点有两个审批人(会签场景),几乎同时点“通过”。前一个操作刚把流程推到了下一节点,后一个操作又把单据状态更新回当前节点,最终单据停在了错误的节点上,下一节点的审批人收到了待办,但发起人看到的状态是“审批中”,对不上账。
原因:代码先“查询当前状态”再“更新状态”,两步之间存在时间窗口,第二个请求读到的是旧状态,覆盖了第一个请求的写入,典型的读改写竞态。
解决:更新时用乐观锁,UPDATE 语句的 WHERE 条件带上 current_node_id 和 status,影响行数为 0 说明状态已被别人改过,直接报错让用户刷新。这条规则同样适用于撤回、转交操作。另外建议在关键操作的外层加事务,确保状态更新和待办生成要么都成功,要么都失败。血泪经验是:千万不要先删待办再插待办,中间崩了待办就永久丢了。
5.2 坑二:审批人调岗离职后,单据卡死在待办里
现象:某个节点的审批人离职了,他名下的待办永远没人处理。超时提醒定时任务每天照常触发,消息发给一个已经收不到通知的人。最严重的一次是合同审批在法务节点停了两周,业务方完全不知道。
原因:流程配置里审批人写成了具体的人名或用户 ID,人一变岗位,配置就失效了。审批人离职时也没有统一的待办回收机制。
解决:审批人配置一律抽象成角色或岗位表达式,例如role:dept_leader、role:hr_ops,运行时根据单据的部门、金额等字段动态解析。同时做一个“无主待办巡检”的定时任务,每天扫描超过 24 小时没有处理人的节点,自动把待办转交给该角色的代理审批人。这样人走了,单子不会死。角色表达式要允许一个角色对应多个后备人,并按顺序路由。
5.3 坑三:流程配置改了,存量单据全部失效
现象:公司把报销阈值从 5000 调到 3000,运营同学直接在流程配置里改了条件。结果所有已经走到分支节点的历史单据全部失效——它们按金额判断走到了新分支,但下一节点的审批人列表配置里根本没有对应角色,流程直接卡死。
原因:流程模板是实时读取的,历史单据没有锁定自己发起时的流程版本。配置一更新,存量单据全被“连坐”。
解决:流程模板表加版本号,单据发起时锁定 flow_version,流转过程始终按锁定版本解释。模板更新走“新增版本”而非“原地修改”,老单据跑老版本,新单据跑新版本。版本发布的校验脚本要跑一遍所有历史单据的当前节点,确认删掉的节点没有单据停在那里。我踩过这个坑之后养成的习惯是:任何模板变更先查存量单据分布,再决定是兼容还是迁移。
5.4 坑四:手机端上传的合同附件,PC 端打不开
现象:在微信小程序里上传合同 PDF,点提交后审批流程正常往下走;到了 PC 管理端,审批人打开附件,要么链接失效,要么文件无法预览。合同审批的法务反馈“这功能没法用”。
原因:小程序端直接上传到微信临时文件服务,拿到的是临时文件 ID 或临时 URL,有效期内可访问,过期就失效。虽然业务高峰时过期概率不大,但合同审批往往拖几天才处理,等法务看到时附件早已失效。
解决:后端拿到小程序上传后返回的临时文件,立即转存到自己的对象存储服务,数据库里只保存永久 URL。上传接口应该做到“同步转存、入库返回”,而不是把临时链接直接落库。附件表记录版本号的同事,还要记录文件大小和 MD5,避免传输过程中出现损坏。这条经验对报销发票上传、采购合同扫描件同样适用,凡是审批里带的文件,都要走中转转存,不能信任客户端给的临时地址。
6. 把审批做成可量化的事:超时提醒、代理审批与效率看板
6.1 超时提醒与代理审批:把堵点变成主动干预
审批系统上线后,第一个要补的能力是超时提醒。不同单据类型的时效预期不一样,请假 24 小时、报销 48 小时、合同 72 小时比较合理。用一个定时任务每分钟扫描一次待办表,把超过时限的待办筛选出来,推送提醒给审批人,同时抄送一条给发起人。
代理审批要解决的是休假和临时不在岗。做法是建一张代理关系表,包含原审批人、代理人、生效时间区间和单据类型范围。扫描待办时,如果发现原审批人处于代理生效期,待办自动在代理人名下生成一份,原待办标记 canceled。这样即使审批人休年假,单据也不会堵在某个节点。
6.2 效率看板:用一张 SQL 回答“流程慢在哪”
管理层每天关心两个问题:有多少单子积压,哪些节点最慢。用一张聚合 SQL 就能回答:
SELECT bill_type, AVG(TIMESTAMPDIFF(HOUR, create_time, finish_time)) AS avg_hours, COUNT(*) AS total_cnt, SUM(IF(status = 'rejected', 1, 0)) AS rejected_cnt FROM approval_bill WHERE create_time >= DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY bill_type;光看平均时长不够,建议按节点再拆一层,统计每个节点的平均停留时长,才能定位是法务慢还是财务慢。看板数据按天刷新即可,不要实时统计,审批场景没有这个必要。
| 指标 | 口径 | 建议阈值 |
|---|---|---|
| 单据平均审批时长 | 提交到最终通过的小时数 | 请假 <= 24h,报销 <= 48h |
| 节点平均停留时长 | 进入节点到审批操作的小时数 | 超过 24h 视为异常 |
| 超时率 | 超过时效单据占比 | 低于 5% 为健康 |
| 驳回率 | 驳回单据占比 | 高于 20% 要检查表单引导 |
我现在的习惯是系统上线第一周只做一件事:每天早会前跑一遍看板 SQL,盯着超时率变化。审批系统不是上线即结束,真正的价值在运行两三个月后沉淀下的流程数据——哪个节点是堵点、哪类单据驳回率最高、谁的待办积压最多。它让“优化审批流程”从感觉变成了数据。这几条经验来自我做过的一个模拟项目 X,从最初被测试环境暴露的并发问题折腾到半夜,到最后能把五类单据跑在同一套状态机上,最大的教训是先把状态迁移画清楚再写代码。希望帮到你。
本文还有配套的精品资源,点击获取