跳转至

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 → 当前登录用户

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

权限失败后的结果

  • 未登录:返回 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 提交。

📝 总结

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

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