第 6 章:B 端中后台系统
本章定位
B 端系统是权限、复杂表格、配置化表单、工作流和审计的组合。本章要求从 schema 和状态机视角设计系统,而不是继续堆叠页面。
必须掌握清单
必须掌握
能区分 RBAC、数据权限和按钮权限。
相关答案: RBAC 根据用户、角色和权限判断能否执行某类操作;数据权限进一步限定能操作哪些记录;按钮权限只是后端授权结果在 UI 上的展示。前端隐藏按钮不能替代服务端鉴权与授权。
具体例子: “销售”角色拥有查看客户权限,但数据范围只能是本人负责的客户;页面可以隐藏“删除”按钮,用户即使手工调用删除接口,服务端仍必须重新校验权限。
能设计 schema 驱动的表格和表单。
相关答案: schema 应描述字段、组件、校验、布局和联动等稳定元数据,渲染器负责把声明转换为 UI;复杂业务逻辑仍应放在可测试的函数或组件中,不能把 schema 变成难以维护的脚本语言。
具体例子: 用户和订单页面共用表单引擎,各自提供字段 schema;“省份变化后刷新城市选项”通过受控联动函数实现,而富文本编辑器保留为显式自定义组件。
能处理复杂编辑器的脏状态和撤销。
相关答案: 脏状态表示当前可持久化数据与最近保存基线存在差异,应由可比较状态推导;撤销需要记录命令、补丁或状态历史,并明确哪些临时 UI 状态不进入历史。保存成功后要更新基线。
具体例子: 流程编辑器移动节点时记录一条可逆 patch,
Ctrl+Z应用反向 patch;缩放画布不算业务修改,而修改节点名称会触发离开页面确认。能设计工作流审批和审计日志。
相关答案: 审批流应把状态、允许的转换、参与者和条件显式建模,并由服务端保证转换合法;审计日志是只追加的事实记录,至少包含主体、动作、对象、时间、前后值和原因。
具体例子: 报销单只能由
draft提交到pending,审批人再转为approved或rejected;每次转换都记录操作者、意见和字段差异,前端据当前状态显示可用操作。能为长列表和大表单做性能边界设计。
相关答案: 先确定数据规模、交互频率和响应目标,再选择分页、虚拟化、分区订阅和延迟校验。虚拟化解决 DOM 数量,不解决昂贵计算或一次下载全部数据;大表单也应避免任一字段变化导致全表重渲染。
具体例子: 十万行记录使用服务端筛选分页,当前页再做行虚拟化;两百字段表单按字段订阅状态,并把跨字段校验放到防抖或提交阶段。
原理讲解
RBAC 决定用户能不能执行某个操作,数据权限决定用户能看见哪些数据,按钮权限是 UI 层对权限的投影。前端展示权限不能替代后端校验。
schema 驱动是把字段、校验、布局和联动写成可配置数据。这样相同表单引擎可以服务不同业务,也能减少重复组件。复杂编辑器的脏状态应该基于初始值和当前值的结构化比较,而不是简单布尔值。
审计日志需要记录主体、动作、对象、时间和变更原因。它不是为了打印日志,而是为了回答“谁在什么时候改了什么”。
代码示例或模板
type FieldSchema = {
key: string;
label: string;
required?: boolean;
};
type FormSchema = {
fields: FieldSchema[];
permission: "read" | "write";
};
function renderForm(schema: FormSchema): string {
if (schema.permission === "read") {
return schema.fields.map((field) => field.label).join("、");
}
return schema.fields
.map((field) => `${field.label}${field.required ? "*" : ""}`)
.join("、");
}这个例子只展示 schema 如何同时驱动只读和可写状态,实际表单还需要联动、校验和布局。
AI Agent 概念对照
- 工具审批 UI 对应权限系统。
- 工具状态对应工作流状态机。
- 会话审计对应 B 端审计日志。
- 危险操作确认对应按钮权限和风险提示。
面试追问
1. RBAC 和数据权限分别在哪里控制?
查看深度解析
标准回答: 服务端是最终控制点:RBAC 判断主体能否执行某动作,数据权限把查询和写入限制到允许的资源范围。前端只负责根据授权结果改善体验,不能靠隐藏按钮或过滤已返回数据保证安全。
原理展开: 功能权限通常在路由或服务入口校验,数据权限应进入查询条件和对象级写入检查;两者还要结合租户边界。
工程示例: 销售角色可查看客户,但查询自动加负责人或部门条件;修改客户时服务端再次检查目标记录归属,前端仅隐藏无权操作。
常见误区: 接口先返回全量数据再由前端过滤,敏感信息已经泄露;或有角色就默认拥有同租户全部数据。
继续追问: 权限变化后缓存怎么办?
回答方向: 权限版本进入缓存键或变更时主动失效,敏感写操作始终实时校验,不信任过期前端状态。
2. 为什么大型表单更适合 schema 驱动?
查看深度解析
标准回答: 大型表单有大量重复的字段、校验、权限、布局和联动元数据,schema 能统一描述并由引擎复用,降低页面复制和规则漂移。但复杂交互仍应使用显式组件与可测试函数。
原理展开: schema 的价值是声明稳定变化点,不是把所有业务逻辑改写成配置语言;需定义版本、扩展槽和错误边界。
工程示例: 同一客户 schema 可渲染新增、编辑和只读模式,字段权限由上下文决定;富文本协同编辑器通过 custom renderer 接入。
常见误区: 在 JSON 中塞字符串表达式和任意脚本,结果失去类型、调试和安全性。
继续追问: schema 如何做跨字段联动?
回答方向: 用声明依赖加受控函数注册表,构建依赖图并检测循环;不要让字段直接通过全局变量互相修改。
3. 虚拟表格快速滚动时如何保持选择状态?
查看深度解析
标准回答: 选择状态必须按稳定业务 row key 存在表格数据模型或外部 store 中,不能依赖当前渲染行索引或 DOM。虚拟行卸载和复用时,根据 key 重新派生选中状态。
原理展开: 虚拟化只保留视口节点,索引会因排序、过滤和分页变化;全选还需定义是当前页、当前筛选结果还是全部数据集。
工程示例: 使用 Set<OrderId> 保存已选 ID;服务端全选采用“筛选条件 + 排除 ID”模型,避免把百万个 ID 放进浏览器。
常见误区: 使用数组下标当 key,滚动复用后勾选跑到另一行;或切换筛选后悄悄保留不可见选择。
继续追问: 数据刷新后已选记录被删除怎么办?
回答方向: 与最新数据或服务端校验选择集,移除失效 ID并提示;批量提交时服务端仍要返回逐项结果。
4. 工作流审批和普通状态机有什么区别?
查看深度解析
标准回答: 工作流审批以状态机为基础,但还增加参与者、权限、条件路由、并行会签、超时、委托、撤回和审计等组织语义。普通状态机只关心状态与合法转换,不一定处理人和流程治理。
原理展开: 审批的转换必须由服务端原子执行并记录原因;前端根据服务端返回的可用动作渲染,不能自行推导最终权限。
工程示例: 报销从 pending 进入 finance_review,金额超过阈值时增加总监节点,所有审批人通过后才 approved。
常见误区: 用一个 status 字段加大量 if 表达所有流程,缺少版本和历史,流程变化后旧单据无法解释。
继续追问: 流程定义升级如何处理在途实例?
回答方向: 实例绑定定义版本;新实例用新版本,在途实例继续旧版或执行显式迁移并留下审计记录。
5. 审计日志应该记录哪些关键信息?
查看深度解析
标准回答: 至少记录谁在何时、通过什么入口、对哪个对象执行了什么动作、结果如何,以及必要的变更前后值、原因、请求/trace ID 和策略版本。日志应只追加、防篡改并控制敏感字段。
原理展开: 审计目标是可追责和重建事实,不等于普通调试日志;必须使用服务端可信身份和时间,定义保留、访问与脱敏策略。
工程示例: 权限变更记录管理员 ID、目标用户、旧角色、新角色、工单原因、IP、request ID 和成功状态,但不记录令牌或完整隐私字段。
常见误区: 只写“更新成功”,无法回答改了什么;或保存完整请求体导致密码、密钥进入长期日志。
继续追问: 批量操作怎么记录?
回答方向: 记录批次级事件和逐项结果,用共同 operation ID 关联;避免一条巨大不可查询日志,也不能只记总体成功。
实践任务
实现一个 schema 驱动的只读表单或权限表格,加入数据权限过滤和操作审计字段。
验收标准
- 同一个 schema 能渲染只读和可写状态。
- 前端隐藏按钮不等于后端权限通过。
- 能说明审计日志的关键字段。