库存与报价后台:把批量操作做成可追溯的业务流程
本文目录
本文依据个人管理台默认分支中认证组件、请求封装、库存页、报价页与 API 模块做静态源码审阅。仓库标识、产品名称、接口地址、私有依赖和部署细节均省略。观察范围是 React/Vite 客户端,不是后端源码;所以本文只陈述页面明确呈现的流程,不推断数据库事务、真实库存一致性或服务端授权已经实现。
登录过期时,路由和会话状态要一起收口
应用把管理台挂在 /admin basename 下,并用私有路由组件区分未登录与角色不足的页面。React Router 的 Navigate 会按 Router basename 生成站内路径;这和请求拦截器使用浏览器硬跳转不是一回事。当前请求拦截器遇到非认证接口的 401 时,直接把浏览器地址设为 /login,同时只删除本地 token。这个硬跳转会离开 /admin 路径;而持久化的 Zustand 用户与认证状态没有通过统一 logout action 清理,下一次启动可能读到 token 与持久化状态不一致的组合。
修复方向是把“会话过期”收敛到一个幂等动作:清理 token 和持久化 store、取消或隔离旧请求,再通过 basename-aware 的 Router 导航到登录页,并安全处理回跳地址。登录页加载后仍要向服务端确认当前身份;UI 的管理员标记和路由守卫不是授权依据,后端每个管理 API 都必须独立验证身份、角色和资源范围。源码静态检查不能证明当前部署一定触发该问题,需用真实管理台验证 401、刷新、退出和过期 token 的完整路径。
搜索筛选要防止旧请求覆盖新结果
库存页的搜索提交先更新关键词状态,又立即调用闭包中的 fetchLogs;该函数捕获本次渲染的关键词,随后状态变化触发的 effect 还会再次请求。若两个响应乱序返回,旧结果可能覆盖新筛选。这里是从 React 状态闭包与请求时序得出的静态风险,不是已在浏览器复现的 bug。
可以让筛选条件成为唯一查询状态,由 effect 派生请求;输入提交时只更新查询状态,不另行调用捕获旧值的函数。请求增加 AbortController,或用递增序号丢弃过期响应;切换关键词、日期、类型时同步重置页码,并让加载/错误状态与当前查询绑定。回归测试应模拟“旧查询慢、新查询快”,断言最终仍显示新查询结果,并覆盖快速连续输入、清空条件和组件卸载。
页面流程围绕库存变更组织
库存工作台包含批量入库和出库条目编辑、条码输入、库存日志、搜索分页、编辑/删除弹窗,以及对批次内容的校验和警告。相比每个商品一条一条重复提交,批量编辑能减少操作成本;但它也把校验和部分成功语义变成核心业务设计。
一批操作至少要回答:是否整批原子成功;如果其中一行无效,是全部拒绝还是只处理有效行;重复扫码和网络重试会不会重复扣增库存;操作人员、理由、时间、商品版本如何留痕;库存不足或商品被停用时客户端如何恢复。仅在 React 表单里验证不能保护数据,因为请求可以被脚本直接构造。
一个更可控的服务端契约会显式提交请求 ID、操作类型、原因与明细行:
{
"requestId": "unique-retry-key",
"operation": "stock-in",
"reason": "verified-receipt",
"items": [
{ "itemId": "catalog-id", "quantity": 3 }
]
}
服务端应重新读取商品与权限,验证数量为有限正整数并满足上限,在事务中更新库存与审计记录;重复 requestId 返回原结果或明确冲突,不能再次应用。批次结果应逐行可解释,或者坚持整批原子语义,不能出现 UI 显示“失败”但部分库存已改变的模糊状态。
条码只是输入,不是可信身份
条码扫描降低手工录入成本,但解析出的文本仍是外部输入。页面应处理扫描器重复按键、尾随回车、慢速输入、无匹配结果、多个候选、重复条目和焦点丢失。扫描成功后要展示匹配商品的可辨识名称与单位,不能只闪一下就把数量静默累加。手工输入与扫描输入最终应走同一套字段校验和业务命令。
快速连续扫码时应区分“同一商品再次增加数量”和“同一设备重复发出同一次扫描”;这两者业务含义不同。可以使用短时去重作为交互优化,但服务端幂等键和库存操作审计才是可靠性边界。客户端超时重试不得无意地产生第二次入库或出库。
日志是库存功能的一部分
源码页面把库存日志和编辑/删除操作纳入工作台,这为追查差异提供了入口。可靠审计至少记录操作者、业务时间、关联商品、变更前后数量、原因、请求标识和结果;日志应由服务端生成且不可由普通客户端改写。若允许撤销库存动作,更稳妥的做法通常是新增一条反向调整记录,而不是抹掉原历史,以保留完整时间线。
日志列表的筛选和分页还需要稳定排序与一致的总数口径;并发变更时,页面上的库存快照可能已经过期。提交前可显示当前值,提交时服务端再核对版本或条件,冲突后刷新并说明差异,不能把旧页面覆盖为新事实。
报价列表与库存域不要混成一个状态
客户端报价页包含查询、分页和状态变化入口;API 模块按摩托车、配件、服务、用户、库存、报价等域分组。这样的模块边界能让页面知道自己依赖哪个业务接口,但菜单隐藏或 PrivateRoute 只控制前端导航,不是 API 授权。请求层从浏览器本地存储读取 token 并在请求中附加凭证,也意味着任意同源脚本执行会扩大令牌风险;高敏感后台需评估 HttpOnly Secure Cookie、SameSite 与 CSRF 的组合,并持续做 XSS 防护。
报价状态应由服务端定义合法状态机。例如草稿、待确认、已接受、已过期之间哪些转换允许、由谁操作、是否需要原因;前端收到非法状态或过期版本时要刷新,而不是只更新颜色标签。报价总金额应由服务端可信目录和规则计算,不能依赖浏览器提交的价格。当前源码观察只能确认页面状态操作存在,不能证明这些服务端规则已具备。
工程验收清单
- 模拟 401、过期 token、显式退出、刷新与登录回跳,验证 token、持久化 store、路由 basename 和请求队列一致。
- 模拟旧搜索慢、新搜索快,断言过期响应不能覆盖当前关键词、页码与筛选结果。
- 直接构造请求测试越权、非法数量、停用商品、超大批次和库存不足。
- 用相同 requestId 并发重试,确认库存只变化一次,并检查审计记录。
- 测试批次原子性、部分失败语义、版本冲突与网络中断恢复。
- 验证扫描器连发、重复码、无匹配商品、键盘操作和移动设备焦点。
- 对报价每种状态转换验证角色、合法前置状态、并发更新和过期处理。
- 服务端逐请求授权;前端路由、按钮和本地 token 都不能作为权限证据。
本次只读审阅确认客户端具备批量入/出库表单、扫描处理、库存日志筛选与分页、报价状态和分页入口;未检查该项目服务端实现,未安装、构建、测试或运行任何远端代码。所有事务、幂等、审计和状态机内容是上线前应验证的设计约束,不是现状认证。
参考:React 管理状态、OWASP Authorization Cheat Sheet、Prisma 事务设计示例。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。