跳转至

4.4 完成业务:实现核心流程与异常处理

把分散功能连接成一条真正完成的业务流程

多个管理页面,不等于完整业务

“用户管理、信息管理、记录管理”都能增删改查,并不代表项目已经解决了用户问题。完整业务必须有明确的发起人、处理过程、状态变化和最终结果。

本节只选择项目最重要的一条流程,把不同角色、页面、接口和数据连接起来,并同时处理失败、越权和重复操作。

本节学习目标

根据需求和系统设计,实现一条跨页面或跨角色的核心业务流程,确保每一步具有明确权限、状态和数据变化,并能够演示正常流程和关键异常流程。

返回上一节:开发基础 返回第四篇导读 进入下一节:增加特色


🎯 本节完成后,你要交付

成果 要求
核心流程实现 能够从用户发起连续操作到获得最终结果
业务状态实现 页面、接口和数据库使用一致的状态及转换规则
权限与异常处理 未登录、越权、状态不允许和数据异常得到明确处理
数据一致性验证 关键操作成功时一起完成,失败时不留下错误数据
流程测试记录 至少覆盖正常、业务失败和权限失败流程
阶段演示版本 不依赖手工修改数据库即可连续演示

本节不要求把项目所有功能做完。先完成一条最有价值的核心流程,再补充其他功能。


🧭 第一步:选定唯一的核心流程

从《需求分析说明书》中选择一条最能体现项目价值的流程。

项目 核心流程示例
宿舍报修 学生提交 → 管理员分配 → 维修人员处理 → 学生确认
活动报名 管理员发布活动 → 学生报名 → 管理员查看名单 → 学生查看结果
图书管理 用户查询图书 → 提交借阅 → 办理归还 → 库存恢复
校园集市 卖家发布商品 → 买家联系或下单 → 卖家处理 → 交易完成
预约管理 用户选择服务 → 提交预约 → 管理员确认 → 用户查看结果

核心流程必须回答

  1. 谁发起?
  2. 什么时候允许发起?
  3. 谁负责处理?
  4. 处理过程中状态怎样变化?
  5. 哪些数据被新增或修改?
  6. 用户最终得到什么结果?
  7. 失败时数据和页面怎样处理?

本节只选择一条主线

不要同时实现报修、通知、统计、评价和推荐等多条流程。先把最重要的一条流程做完整,再把其他能力作为后续任务。


🗺️ 第二步:把流程拆成可验证的小步骤

以“宿舍报修”为例:

1
2
3
4
5
学生提交报修
→ 管理员查看待处理报修
→ 管理员分配维修人员
→ 维修人员提交处理结果
→ 学生确认完成

把每一步写成任务表:

步骤 操作角色 页面操作 接口 前置状态 数据变化 成功结果
1 学生 提交报修 【填写】 无 新增报修记录 状态为待分配
2 管理员 分配人员 【填写】 待分配 写入维修人员 状态为待处理
3 维修人员 开始或完成处理 【填写】 待处理 写入处理结果 状态为待确认
4 学生 确认结果 【填写】 待确认 写入确认时间 状态为已完成

一次只实现一步

推荐顺序:

1
2
3
4
完成第 1 步 → 运行验证 → Git 提交
完成第 2 步 → 运行验证 → Git 提交
完成第 3 步 → 运行验证 → Git 提交
完成第 4 步 → 完整流程验证

每一步都应有独立的成功结果。不要等整条流程全部写完后才第一次启动项目。


🔄 第三步:用状态控制业务操作

状态表示业务对象当前处于哪个阶段,也决定哪些操作可以执行。

状态转换表

当前状态 允许操作 操作角色 前置条件 下一状态 不允许时
待分配 分配维修人员 管理员 维修人员有效 待处理 返回状态或人员错误
待处理 提交处理结果 被分配的维修人员 已填写结果 待确认 返回权限或状态错误
待确认 确认完成 报修学生本人 结果已提交 已完成 返回权限或状态错误
已完成 无核心操作 — — — 拒绝重复处理

项目应使用自己的真实状态,不要照抄示例。

状态实现的三个一致

1
2
3
数据库状态值
= 后端业务判断
= 前端显示和按钮状态

例如数据库使用 PENDING,后端却判断 WAITING,页面再显示“处理中”,流程一定会出现混乱。状态名称和含义应以《系统设计说明书》为准。

不允许前端任意修改状态

错误做法:

前端提交 status=COMPLETED
后端直接更新数据库

正确思路:

1
2
3
4
5
用户执行“确认完成”
→ 后端确认当前用户
→ 检查当前状态是否为待确认
→ 检查用户是否为记录所有者
→ 更新为已完成

状态变化应由具有业务含义的操作触发,而不是由前端自由填写。


🔐 第四步:在每一步检查权限和数据范围

跨角色流程不仅要判断“是不是管理员”,还要判断“能不能操作这一条数据”。

操作 功能权限 数据权限
学生提交报修 已登录学生 创建人只能是当前用户
管理员分配任务 管理员 只能处理管理范围内的记录
维修人员提交结果 维修人员 只能处理分配给自己的任务
学生确认完成 已登录学生 只能确认自己提交的报修

当前用户从登录状态获取

Spring Boot + Vue:JWT → @CurrentUser 或后端请求身份
Servlet:Session → 当前登录用户

不要信任前端提交的 userId、role 或“是否管理员”。即使页面没有显示按钮,后端仍要拒绝直接调用接口的越权请求。

权限失败后的结果

  • 未登录:返回 401,页面引导重新登录;
  • 角色不允许:返回 403,页面提示没有权限;
  • 无权操作该数据:拒绝操作,不返回敏感内容;
  • 数据不存在:返回明确业务错误;
  • 拒绝操作时,数据库内容保持不变。

🧠 第五步:把业务规则放在后端

页面可以提前提示,但最终规则必须由后端业务层执行。

示例:活动报名

1
2
3
4
5
6
7
用户已经登录
且角色允许报名
且活动已经发布
且当前时间未超过截止时间
且剩余名额大于 0
且当前用户未重复报名
→ 才允许创建报名记录

各层职责

位置 适合处理
前端页面 按状态显示按钮、进行基础格式校验、展示失败原因
Controller/Servlet 接收参数、获取当前用户、调用业务服务
Service 权限、业务状态、重复操作和核心规则
Mapper/DAO 按条件查询和更新数据库
数据库 主键、唯一、非空、外键等最后约束

不要把全部判断写在页面中,也不要让 Controller 或 Servlet 堆积整条业务流程。


🛡️ 第六步:处理失败、重复和数据一致性

课程项目不需要设计复杂的高并发架构,但必须处理会明显造成错误数据的情况。

必须考虑的异常

异常 应有结果
对象不存在 返回明确提示,数据不变
当前状态不允许 拒绝操作并提示当前状态
重复点击提交 不创建多条重复记录
重复确认或归还 不重复修改状态、库存或数量
操作他人数据 返回无权限,不能泄露数据
多步写入中途失败 已完成的部分能够回滚
登录过期 清除失效状态并重新登录

事务只解决“一起成功或一起失败”

以下操作通常需要放在同一个事务中:

1
2
3
4
创建借阅记录 + 扣减可借库存
创建订单 + 创建订单明细
确认报名 + 扣减剩余名额
归还图书 + 更新借阅记录 + 恢复库存

如果其中一步失败,其他步骤也不能保留。

事务不能代替业务检查

开启事务并不会自动阻止重复提交或越权操作。仍然需要检查当前用户、当前状态、唯一约束和业务条件。

最后一个库存或名额

对于库存、名额等有限资源,至少做到:

  1. 在后端重新读取和检查当前数量;
  2. 使用数据库条件更新、唯一约束或适合当前技术的控制方式;
  3. 更新失败时返回“库存不足”或“名额已满”;
  4. 不让数量变成负数;
  5. 用两个快速或并行请求验证结果。

如果项目没有库存、名额或类似竞争资源,不必为了使用“锁”而增加复杂设计。


🖥️ 第七步:让页面跟随业务结果变化

完整流程不仅是后端状态变化,用户还需要知道下一步做什么。

页面需要处理

  • 当前记录处于什么状态;
  • 当前用户可以执行什么操作;
  • 不允许操作时为什么;
  • 操作进行中时防止重复点击;
  • 操作成功后刷新列表、详情或数量;
  • 操作失败后保留原表单并显示原因;
  • 登录过期后跳转登录;
  • 空数据、加载和请求失败状态。

不同角色看到不同入口

1
2
3
学生:我的报修 → 提交 → 查看进度 → 确认完成
管理员:待分配报修 → 选择维修人员 → 查看处理进度
维修人员:我的任务 → 填写处理结果

前端应根据角色和状态调整按钮,但最终能否操作仍由后端判断。


🧪 第八步:连续走查完整流程

准备不同角色测试账号和固定测试数据,不依赖手工修改数据库推进流程。

最低测试集合

正常流程

1
2
3
4
角色 A 发起
→ 角色 B 处理
→ 角色 C 确认
→ 发起人看到最终结果

业务失败流程

选择一个最重要的失败情况:

  • 重复报名;
  • 库存不足;
  • 已关闭活动继续报名;
  • 已完成任务重复处理;
  • 不存在的对象被操作。

权限失败流程

选择一个越权情况:

  • 普通用户直接调用管理接口;
  • 用户修改 ID 操作他人数据;
  • 未分配的处理人员提交结果;
  • 退出登录后继续提交。

数据一致性检查

操作后检查:

  • 业务记录状态是否正确;
  • 关联字段是否正确;
  • 库存、名额或数量是否正确;
  • 操作失败时数据是否保持不变;
  • 页面显示是否与数据库一致。

核心流程测试记录

场景 初始数据 操作步骤 预期结果 实际结果 证据 结论
正常流程 【填写】 【填写】 最终状态正确 【填写】 页面/接口/数据库 通过/未通过
业务失败 【填写】 【填写】 拒绝且数据不变 【填写】 接口/数据库 通过/未通过
权限失败 【填写】 【填写】 返回 401/403 【填写】 Network/接口 通过/未通过
重复操作 【填写】 【填写】 不产生重复结果 【填写】 数据库 通过/未通过

需要手工改数据库才能演示,说明流程还没有完成

状态变化应通过页面和接口完成。初始化测试数据可以使用脚本,但不能在演示过程中手工把状态从“待处理”改成“已完成”。


🤖 第九步:让 AI 围绕一条流程工作

先要求 AI 读取当前实现并检查设计对应关系:

请先阅读 AGENTS.md、需求分析说明书、系统设计说明书,
以及与“【核心流程名称】”相关的页面、接口、Service、
数据访问、数据库脚本和已有测试。

本次只实现这条核心流程:
【角色 A 做什么 → 角色 B 做什么 → 最终结果】

不要增加需求之外的角色、状态和功能,
不要修改脚手架基础设施。

请先输出:
1. 当前已经完成的流程步骤;
2. 尚未完成或互相不一致的步骤;
3. 每一步对应的页面、接口、权限、状态和数据变化;
4. 分步实施计划和准备修改的文件;
5. 正常、业务失败、越权和重复操作测试;
6. 需要人工确认的问题。

先不要修改代码。

实现时一次只处理一个状态转换:

现在只实现“【当前状态】通过【操作】变为【目标状态】”。

必须检查:
- 当前登录角色;
- 当前用户的数据范围;
- 当前业务状态;
- 输入和关联数据;
- 成功后的数据变化;
- 失败时数据保持不变。

完成后运行相关验证,并说明改动文件和实际结果。

遇到错误时提供完整证据,不要只说“流程不对”:

1
2
3
4
5
6
7
8
初始状态:
操作角色:
操作步骤:
预期结果:
实际结果:
接口请求与响应:
后端关键日志:
数据库实际数据:

👀 第十步:集成审查与提交

完整流程跑通后,进行一次集中审查:

  • 每一步是否对应已确认需求;
  • 页面、接口和数据库状态是否一致;
  • 后端是否检查角色和数据权限;
  • 是否允许非法状态跳转;
  • 重复操作是否造成重复数据;
  • 多表更新失败时是否留下部分数据;
  • 页面是否正确刷新和提示;
  • 是否仍依赖模拟数据或手工改库;
  • 是否修改了无关模块或公共基础设施;
  • 测试记录能否复现。

按流程步骤提交更容易定位问题:

1
2
3
4
feat(workflow): support user submission
feat(workflow): support admin assignment
feat(workflow): support processing and confirmation
test(workflow): cover failure and permission cases

提交前运行相关测试、重新走查完整流程,并检查:

git status
git diff

✅ 本节验收清单

  • 已选择项目最重要的一条核心流程;
  • 流程具有明确发起人、处理过程和最终结果;
  • 已拆分为可以逐步验证的小步骤;
  • 每一步明确页面、接口、角色、状态和数据变化;
  • 状态值在页面、后端和数据库中一致;
  • 状态只能通过明确业务操作转换;
  • 后端检查登录、角色和数据权限;
  • 当前用户身份不依赖前端提交;
  • 关键业务规则在后端执行;
  • 重复操作不会产生重复业务结果;
  • 相关多表更新具有正确的事务边界;
  • 页面能够展示当前状态和失败原因;
  • 正常流程能够从开始连续完成到结束;
  • 至少验证一条业务失败流程;
  • 至少验证一条权限失败流程;
  • 操作失败时数据库保持正确;
  • 演示过程不需要手工修改数据库;
  • 已保存测试证据和清楚的 Git 提交。

📝 总结

  • 完整业务不是页面数量:要有发起、处理、状态变化和最终结果。
  • 状态决定当前能做什么:每次转换都要检查角色、条件和目标状态。
  • 权限要检查具体数据:不仅判断角色,还要判断能否操作这一条记录。
  • 失败不能破坏数据:重复、越权和中途失败都应得到明确处理。
  • 演示必须连续完成:不依赖模拟数据和手工改库,才算真正形成业务闭环。

返回上一节:开发基础 进入下一节:增加特色