多步审批流「已完成」是假的——状态校验只检查了第一步
某次上线后,用户反馈自己明明还没完成入驻,却能正常切换角色、使用只有已入驻用户才有的功能。排查之后发现——不是权限漏洞,是「完成判断」本身就写错了。
现象
后台管理系统里,入驻流程分四步:提交申请 → 流程审核通过 → 签署合同 → 入驻成功。
用户完成审核(步骤二)之后,弹窗里的入驻状态已经变绿、按钮已经可以点击,但合同根本没签(步骤三)。直接切换角色,服务端也不拦截,成功进入了本应限制的角色模式。
根因
一个关键代码路径——角色切换弹窗——只检查了第一个状态位:
// 角色切换弹窗 (role-switch/index.js)
const isSettled = flowStatus === '3'; // 只判断审核通过而在另一处「入驻状态页」,早已有完整的检查逻辑:
// 状态页 (pages/role/status.js)
const isSettled =
flowStatus === '3' &&
contracts.some(c => c.status == '1'); // 还需判断合同已签署两处代码对「已完成」的定义不一致,状态页是正确的,但弹窗是错的,且后端也没有守住。
坑:多步流程只检查了最前面那步
开发时通常先写「审核通过 = done」,业务后来加了签约/验证步骤,新逻辑只更新了主状态页,漏掉了所有其他入口(弹窗、卡片角标、背景任务……)。QA 测试时顺序完成全部步骤,永远不会触发这个 bug。
正解
1. 先找「黄金实现」
在 codebase 里用 grep 找所有引用该状态的地方:
grep -rn "flowStatus.*===.*'3'" --include="*.js"
grep -rn "isSettled\|is_settled" --include="*.js" --include="*.php"通常会发现某处(主状态页、详情页)已经有完整的校验,这就是「黄金实现」。把它提取成公共函数,替换所有残缺的判断。
2. 前端补全所有入口
// BEFORE — 只看审核
const isSettled = flowStatus === '3';
// AFTER — 审核 + 合同双重确认
async function loadSettleStatus(record) {
if (record.status !== '3') return false;
const res = await api.contract.list({ id: record.dataId });
const contractSigned = (res?.data || []).some(c => c.status == '1');
return contractSigned;
}3. 后端同样要守住(纵深防御)
// BEFORE — 只查审核记录
$flowRecord = Db::name('p_flow_record')
->where('status', '3')->find();
if (!$flowRecord) { $this->error('未完成审核'); }
// 缺少后续校验,直接放行
// AFTER — 审核通过 + 合同签署双重校验
$flowRecord = Db::name('p_flow_record')
->where('status', '3')->find();
if (!$flowRecord) { $this->error('未完成审核'); }
$signedContract = Db::name('common_service_contract')
->where('id', $flowRecord['data_id'])
->where('status', '1')->find();
if (!$signedContract) { $this->error('请先签署合同'); }4. UI 增加中间态,引导用户完成缺失步骤
<!-- 完全完成 -->
<block wx:if="{{isFullySettled}}">
<text>您已完成入驻</text>
<button>进入角色模式</button>
</block>
<!-- 审核通过但合同未签 -->
<block wx:elif="{{flowApproved && !contractSigned}}">
<text>审核已通过,请先签署合同</text>
<button>查看并签署合同</button>
</block>
<!-- 尚未申请 -->
<block wx:else>
<text>您还未完成入驻</text>
<button>立即入驻</button>
</block>正解:找黄金实现 → grep 全部入口 → 前后端同步补全
不要只修「发现问题的那一处」。先用 grep 找出所有校验点,以最完整的那份为标准,逐一对齐。后端必须独立守住,前端的 UI 逻辑不能作为唯一防线。
一句话外卖
多步流程的「完成」判断,永远要从业务流程的最后一步向前数,而不是从代码里最早写下的那个条件向后猜。