用户注销前校验未完成业务,本质是退出前的业务守门人,通过自定义RuntimeException子类(如PendingPaymentOrderException)明确拦截支付订单、提现申请等导致数据不一致的场景,由Service层校验并抛出,Controller层不捕获,交由@ControllerAdvice统一返回409响应。

用户注销时校验未完成业务,本质是**在退出前做业务守门人**,不是靠异常驱动流程,而是用异常清晰表达“不能登出”的具体原因。自定义异常在这里起语义承载和统一响应作用,不是用来控制注销逻辑本身。
明确注销前需拦截的业务场景
不是所有“未完成”都该阻止注销,只拦截那些状态未清理会导致数据不一致或资源泄漏的情形:
- 存在进行中的支付订单(未支付、未超时关闭)
- 有已提交但未审核的提现申请
- 正在参与未结束的限时活动(如拼团未成团、抽奖未开奖)
- 有未读的重要系统通知且业务规则要求必须确认后才能退出(极少数强契约场景)
用 RuntimeException 子类表达拒绝理由
这类校验失败属于预期内的业务约束,不强制调用方处理,推荐继承 RuntimeException:
将后端 Java 接口文档转换为前端 TypeScript 类型定义(interface / type)。输入:飞书文档、Spring Boot 接口文档、Swagger/OpenAPI、手写 API 文档等。输出:包含请求参数与响应结果的 .md 文件,含完整 JSDoc 注释。触发关键词:Java 转 TS、接口转换、生成前端类型、接口文档转 TypeScript、DTO/VO/BO 转 interface、后端文档转前端类型、生成接口类型、类型定义生成。
- 异常名要直指业务:如
PendingPaymentOrderException、UnreviewedWithdrawalException - 构造器至少提供 message + errorCode,例如:
new PendingPaymentOrderException("当前有1笔待支付订单,无法注销", "LOGOUT_BLOCKED_PAYMENT") - 避免在异常里塞订单列表、时间戳等动态数据——这些应在 Service 层校验时查出,作为参数传入异常构造器或记录到日志上下文(MDC)
校验逻辑放在 Service 层,异常由 Controller 或 Filter 抛出
不要在前端或拦截器里写一堆 if 判定再 throw new Exception;应封装为可复用的校验方法:
- Service 层提供
checkCanLogout(Long userId)方法,内部查询数据库并判断业务状态 - 若不可注销,直接抛出自定义异常,如
throw new PendingPaymentOrderException(...) - Controller 层调用该方法,不 try-catch —— 让全局异常处理器(@ControllerAdvice)统一捕获并返回标准 JSON(如 HTTP 409 Conflict + 错误码 + 提示文案)
配合全局处理器统一响应格式
Spring 中通过 @ExceptionHandler 按类型匹配,确保前端拿到结构化错误:
- 为
PendingPaymentOrderException单独写一个 handler,返回{"code":"LOGOUT_BLOCKED_PAYMENT","msg":"当前有1笔待支付订单,无法注销"} - 不要让所有业务异常共用一个 handler 并靠 if-else 区分——失去类型语义优势
- HTTP 状态码建议用 409(Conflict)或 403(Forbidden),而非 400,体现“操作被策略拒绝”而非“请求格式错误”
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










