DEFECTS-assessment-appointment.md 11 KB

缺陷单 · 测评预约模块(代码级 + 实测验证)

来源:tests/test-cases/deep-dive-assessment-appointment.md 的 F1–F7 + 2026-10-27 live 验证新增的 F8。 阅读对象:研发 / 测试 / 技术负责人。所有缺陷均附「缺陷五要素」+ 严重程度 + 优先级 + 修复建议。 实测环境:127.0.0.1:9082(后端存活;其依赖的测试库 192.168.16.251 本机不可达,故种子数据由测试经 direct-register 自写入)。 真实后端码值约定(实测校正):成功 code=200,业务错误 code=500(无家庭 5001)。与原深挖文档假设的「0/非0」不同。


缺陷清单速览

编号 标题 严重度 优先级 状态
CFC-AP-001 取消预约接口未校验归属(越权取消) 高 P0 待修复
CFC-AP-002 确认预约无状态守卫(非法回退) 中 P1 待修复
CFC-AP-003 已完/未到状态无入口,状态机不完整 中 P2 待澄清
CFC-AP-004 创建预约无幂等(重复提交双预约+双能量) 中 P1 待修复
CFC-AP-005 assessor-list 无 role 校验(与 guide-list 不一致) 低 P3 待修复
CFC-AP-006 预约日期/时间无格式与合理性校验 低 P3 待修复
CFC-AP-007 额度扣减非原子(并发超售风险) 低 P2 待修复
CFC-AP-008 额度不存在时创建预约抛未捕获异常(500 内部错误) 中 P1 待修复

CFC-AP-001 取消预约接口未校验归属(越权取消)

  • 模块/位置:AssessmentAppointmentController.cancelAppointment(controller 第 112–116 行);AssessmentAppointmentService.cancelAppointment(service 第 88–94 行)
  • 严重程度:高(安全/越权)
  • 优先级:P0
  • 状态:待修复

缺陷五要素

  • 前置条件:存在预约 id=X(归属用户 A);当前登录用户 B(与 A 无家庭/指派关系)。
  • 复现步骤:
    1. 用户 B 登录,拿到合法 JWT。
    2. POST /api/assessment/appointment/cancel/{X}(X 为用户 A 的预约 id)。
  • 预期结果:服务端校验 X 的归属者/被指派者 ≠ B → 拒绝(401/403 或业务错误)。
  • 实际结果:cancelAppointment 仅接收 @PathVariable Long id,不接收调用者身份,任何登录用户凭 id 即可取消他人预约,返回成功(status=3)。
  • 补充:影响面广——只要知道(或枚举)他人预约 id 即可恶意取消,属越权操作;建议作为 P0 优先修复。

修复建议(五要素摘要)

  • 改什么:cancelAppointment 增加归属校验。
  • 怎么改:从 JWT 取 userId(@RequestAttribute("userId")),校验 appointment.getUserId().equals(userId) || 是指派者(assessorId/guideId);否则返回业务错误。
  • 为什么:防越权取消,符合最小权限原则。
  • 验证用例:TC-DV-01(跨用户取消越权);复测应拒绝。

CFC-AP-002 确认预约无状态守卫(非法回退)

  • 模块/位置:AssessmentAppointmentService.confirmAppointment(service 第 57–66 行)
  • 严重程度:中
  • 优先级:P1
  • 状态:待修复

缺陷五要素

  • 前置条件:预约 id=Y 已被取消(status=3)或已完成(status=2)。
  • 复现步骤:对被取消/已完成的 Y 调 POST /api/assessment/appointment/confirm/{Y}。
  • 预期结果:状态机非法跃迁(如 3→1)应被拒绝并返回错误。
  • 实际结果:confirmAppointment 仅校验「是否是被指派者」,不校验当前 status,被取消/已完成预约再 confirm 仍被置为 1(已确认),破坏状态机。
  • 补充:能量与消息可能重复触发。

修复建议(五要素摘要)

  • 改什么:confirmAppointment 增加状态前置校验。
  • 怎么改:进入时 if (status != 0) return false/抛错(仅 status=0 待确认可确认)。
  • 为什么:保证状态机合法跃迁 0→1→2,避免回退与重复发奖。
  • 验证用例:TC-DV-02(非 0 态确认拒绝)。

CFC-AP-003 已完/未到状态无入口,状态机不完整

  • 模块/位置:AssessmentAppointmentService.completeAppointment(service 第 68–86 行);status=4(未到) 全代码无 setter
  • 严重程度:中
  • 优先级:P2
  • 状态:待澄清(需研发确认入口)

缺陷五要素

  • 前置条件:一条待确认/已确认预约。
  • 复现步骤:尝试将预约置为「已完成(2)」或「未到(4)」。
  • 预期结果:存在明确的业务入口将预约推进到 2 或 4。
  • 实际结果:置 status=2 的方法在本控制器无对应 HTTP 接口;status=4(未到) 全代码无任何赋值点。状态机 0/1/3 可达,2/4 入口不明(疑由 DanAssessmentExecution / AssessmentResult 链路进入)。
  • 补充:若 2/4 真无入口,则「已完成/未到」状态不可达,状态机名存实亡,且与能量发放(+30)逻辑脱节。

修复建议(五要素摘要)

  • 改什么:明确「已完成/未到」的真实入口或补充接口。
  • 怎么改:核实 DanAssessmentExecution/AssessmentResult 是否写入 status=2;为「未到」补一个明确的业务动作(如规划师标记未到)。
  • 为什么:状态机需完整可观测、可审计。
  • 验证用例:TC-DV-09(确认完整状态机可达性)。

CFC-AP-004 创建预约无幂等(重复提交双预约+双能量)

  • 模块/位置:AssessmentAppointmentService.createAppointment(service 第 27–55 行)
  • 严重程度:中
  • 优先级:P1
  • 状态:待修复

缺陷五要素

  • 前置条件:用户拥有 1 条有效测评额度(quotaId=Q)。
  • 复现步骤:对同一 quotaId=Q 连续 POST /api/assessment/appointment/create 两次(参数相同)。
  • 预期结果:第二次应拒绝或返回既有预约(额度已消耗,不应重复建单)。
  • 实际结果:无幂等检查,两次提交生成 2 条预约、并发 2 次 +10 能量(共 20),且额度可能被重复/不一致扣减。
  • 补充:前端重复点击、弱网重试均会触发。

修复建议(五要素摘要)

  • 改什么:createAppointment 增加幂等控制。
  • 怎么改:建单前查「同 quotaId + memberId + status∈(0,1)」是否已存在;存在则返回既有(或拒绝)。建议对 (quotaId, memberId) 加唯一约束兜底。
  • 为什么:防重复预约与重复能量发放,保证额度与预约 1:1。
  • 验证用例:TC-DV-04(重复创建拒绝)。

CFC-AP-005 assessor-list 无 role 校验

  • 模块/位置:AssessmentAppointmentController.assessorList(controller 第 140–146 行)
  • 严重程度:低
  • 优先级:P3
  • 状态:待修复

缺陷五要素

  • 前置条件:任意登录用户。
  • 复现步骤:POST /api/assessment/appointment/assessor-list。
  • 预期结果:与 guide-list 一致做角色鉴权(仅 teacher 可见)。
  • 实际结果:无 role 校验,仅按自身 userId 查;影响面有限(按自身隔离),但与 guide-list 鉴权策略不一致,易误导维护。
  • 补充:属一致性/健壮性欠账。

修复建议(五要素摘要)

  • 改什么:统一鉴权策略。
  • 怎么改:assessor-list 增加 role == "teacher" 校验,或在文档中明确其「仅按自身 userId 隔离、无需 role」的设计意图。
  • 为什么:鉴权策略一致,降低越权面与维护歧义。
  • 验证用例:TC-DV-07。

CFC-AP-006 预约日期/时间无校验

  • 模块/位置:AssessmentAppointmentController.create(controller 第 50–51 行,appointmentDate/Time 为自由字符串)
  • 严重程度:低
  • 优先级:P3
  • 状态:待修复

缺陷五要素

  • 前置条件:家长创建预约。
  • 复现步骤:create 传 appointmentDate="2000-01-01"(过去)或乱码时间字符串。
  • 预期结果:返回 400/业务错误(日期格式非法或早于今天)。
  • 实际结果:无格式/合理性校验,直接入库。
  • 补充:脏数据影响后续排期与提醒。

修复建议(五要素摘要)

  • 改什么:create 入参校验。
  • 怎么改:对 appointmentDate/Time 做日期格式解析 + 未来时间校验(不早于今天)。
  • 为什么:保证业务数据合法。
  • 验证用例:TC-DV-08(过去/乱码日期拒绝)。

CFC-AP-007 额度扣减非原子(并发超售风险)

  • 模块/位置:createAppointment 中 consumeQuota 调用顺序(先 consume 再取 quota 记录)
  • 严重程度:低
  • 优先级:P2
  • 状态:待修复

缺陷五要素

  • 前置条件:同一额度(quotaId=Q)剩余 1 次。
  • 复现步骤:并发两个 create(均带 quotaId=Q)。
  • 预期结果:至多一个成功消耗,另一个因额度不足失败。
  • 实际结果:consumeQuota 与后续读取非原子,可能都通过校验 → 超售(额度被用超)。
  • 补充:低概率但高影响(数据不一致)。

修复建议(五要素摘要)

  • 改什么:额度扣减原子化。
  • 怎么改:consumeQuota 内用 UPDATE ... SET remain=remain-1 WHERE id=? AND remain>0(受影响行数=1 才算成功);或加乐观锁/行锁。
  • 为什么:防并发超售,保证额度账实相符。
  • 验证用例:并发扣减专项(建议新增)。

CFC-AP-008 额度不存在时创建预约抛未捕获异常(500 内部错误)

  • 模块/位置:AssessmentAppointmentService.createAppointment → consumeQuota(quotaId 不存在分支)
  • 严重程度:中
  • 优先级:P1
  • 状态:待修复(2026-10-27 live 验证发现)

缺陷五要素

  • 前置条件:用户传一个不存在的 quotaId(如 999999999)。
  • 复现步骤:POST /api/assessment/appointment/create 带 quotaId=999999999, guideId=<规划师id>。
  • 预期结果:返回清晰业务错误,如「额度不足或已过期」(code=500,message 可读)。
  • 实际结果:code=500, message="服务器内部错误"——consumeQuota 对不存在的额度抛了未捕获异常,被全局异常处理器兜底为「服务器内部错误」。
  • 补充:暴露两点问题——(1) 错误信息不友好、不可运营;(2) 说明 consumeQuota 在额度查不到时未做前置判空/友好报错,可能与 CFC-AP-007 的并发竞态同源。原深挖文档预测的「额度不足或已过期」文案在 live 中并未出现。

修复建议(五要素摘要)

  • 改什么:consumeQuota / createAppointment 的额度查不到分支。
  • 怎么改:consumeQuota 先 selectById(quotaId);为 null 或 remain<=0 或过期 → 抛 IllegalArgumentException("额度不足或已过期") 并由 Result.error 返回(不要裸抛 NPE)。
  • 为什么:错误可读、可监控,避免把业务校验失败伪装成 500。
  • 验证用例:TC-DV-03 live 实测(message 应含「额度」,而非「服务器内部错误」)。

以上缺陷均为只读分析 + live 验证结论,修复由研发团队实施;本文件仅供测试与评审使用。