Skip to content

异常处理(错误码) ​

这篇解决什么 ​

管理端接口要让前端知道:成功时 data 是什么,失败时提示什么。读完能改 CommonResult 返回、在 Service 里抛 ServiceException、给模块补错误码。

默认端口 48080,前缀 /admin-api。PC 端按 code !== 0 当成失败,见 ruoyi-office-vben/apps/web-antd/src/api/request.ts。

统一响应 ​

业务错误很多,没法一一映射成 HTTP 状态码。状态放在响应体里:成功 code = 0 加 data,失败非 0 加 msg。

CommonResult 三个字段

示意图:响应体只有 code / msg / data。

名称说明仓库路径
codeInteger。0 成功,其它失败ruoyi-office/yudao-framework/yudao-common/.../pojo/CommonResult.java
msg用户可读提示。成功时是 ""同上
data成功载荷,泛型 T同上
isSuccess()判断 code == 0,@JsonIgnore 不进 JSON同上
json
{
  "code": 0,
  "msg": "",
  "data": { "id": 1, "username": "admin" }
}
json
{
  "code": 1002003000,
  "msg": "用户账号已经存在"
}

要不要再加 success 字段

JSON 里没有 success。前端用 code === 0。isSuccess() / isError() 只给 Java 用。

成功返回 ​

Controller 返回类型写成 CommonResult<T>,调用 success(data)。data 用 VO,不要用 Map。

Controller 返回 success

示意图:UserController#createUser 返回新建用户编号。

java
@RestController
@RequestMapping("/system/user")
public class UserController {

    @PostMapping("/create")
    public CommonResult<Long> createUser(@Valid @RequestBody UserSaveReqVO reqVO) {
        Long id = userService.createUser(reqVO);
        return success(id);
    }
}

路径:ruoyi-office/yudao-module-system/yudao-module-system-server/.../controller/admin/user/UserController.java。

失败不要在 Controller 里拼 CommonResult.error,让 Service 抛异常,由全局处理器翻译。

不要自动包装 ​

有的项目用 @ControllerAdvice 把任意返回值包成统一结构。这里不这么做:方法签名必须是 CommonResult,Swagger 才对得上。

GlobalResponseBodyHandler 只在返回类型已是 CommonResult 时,把结果记到请求属性,给访问日志用,不改结构。

路径:ruoyi-office/yudao-framework/yudao-spring-boot-starter-web/.../handler/GlobalResponseBodyHandler.java。

哪些接口不包 CommonResult

文件下载、Excel 导出、第三方回调往往直接写字符串或流。这些方法不要改成 CommonResult。

异常处理 ​

MVC 里的异常由 @RestControllerAdvice 收口。Filter 在 DispatcherServlet 之前,必须自己 try / catch,再复用同一套翻译。

请求到 CommonResult

示意图:Filter 与 MVC 最终都落到 CommonResult。

Spring MVC ​

GlobalExceptionHandler 扫描 cn.iocoder.yudao。参数校验、404、405、无权限、ServiceException 各有 @ExceptionHandler。未识别的进 defaultExceptionHandler,返回 500「系统异常」,并异步写错误日志。

名称说明仓库路径
GlobalExceptionHandler异常 → CommonResultruoyi-office/yudao-framework/yudao-spring-boot-starter-web/.../handler/GlobalExceptionHandler.java
serviceExceptionHandler业务码原样回前端同上
defaultExceptionHandler兜底 INTERNAL_SERVER_ERROR同上
allExceptionHandlerFilter 调用的总入口同上
java
@ExceptionHandler(value = ServiceException.class)
public CommonResult<?> serviceExceptionHandler(ServiceException ex) {
    return CommonResult.error(ex.getCode(), ex.getMessage());
}

Filter ​

TokenAuthenticationFilter 校验 Token 时若抛错,不能指望 @ExceptionHandler。

Filter 捕获异常

示意图:catch 后走 allExceptionHandler,再 writeJSON。

java
} catch (Throwable ex) {
    CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
    ServletUtils.writeJSON(response, result);
    return;
}

路径:ruoyi-office/yudao-framework/yudao-spring-boot-starter-security/.../filter/TokenAuthenticationFilter.java。ApiEncryptFilter、TenantSecurityWebFilter 同样调用 allExceptionHandler。

Filter 进不了 MVC 异常处理

Filter 里的异常必须自己捕获。漏掉 catch,前端拿到的不是 CommonResult,页面无法弹 msg。

业务异常 ​

Service 里用户名重复、库存不足这类错误,不要 return CommonResult.error。

声明式事务按异常回滚;层层返回 CommonResult 还要不断判断 isError()。做法是抛 ServiceException。RPC 若拿到别人的 CommonResult,可调用 checkError() / getCheckedData()。

ServiceException ​

继承 RuntimeException,带 code 和 message。多数情况不用 try / catch,交给 GlobalExceptionHandler。

名称说明仓库路径
ServiceException业务异常,非受检ruoyi-office/yudao-framework/yudao-common/.../exception/ServiceException.java
ErrorCodecode + msg 常量对象ruoyi-office/yudao-framework/yudao-common/.../exception/ErrorCode.java
ServiceExceptionUtil按 {} 占位符格式化后抛出ruoyi-office/yudao-framework/yudao-common/.../exception/util/ServiceExceptionUtil.java

Service 抛业务异常

示意图:throw exception(...) → 处理器回 error(code, msg)。

java
if (id == null) {
    throw exception(USER_USERNAME_EXISTS);
}

路径:ruoyi-office/yudao-module-system/yudao-module-system-server/.../service/user/AdminUserServiceImpl.java。静态导入 ServiceExceptionUtil.exception。

不要抛裸 Exception

throw new Exception("失败") 或未捕获的 NPE 会进兜底,前端只看到「系统异常」,并记一条错误日志。业务失败一律 throw exception(错误码常量)。

ServiceExceptionUtil ​

提示支持 {} 占位,不要用 String.format(参数对不上会再抛异常)。

java
public static ServiceException exception(ErrorCode errorCode) { /* ... */ }
public static ServiceException exception(ErrorCode errorCode, Object... params) { /* ... */ }

ROLE_NAME_DUPLICATE 的文案是 已经存在名为【{}】的角色,调用 exception(ROLE_NAME_DUPLICATE, name)。参数个数不对时打错误日志,尽量拼出可读句子。

错误码 ​

ErrorCode 全局唯一,用来定位是哪个模块、哪条规则。系统码 0–999,业务码从 1_000_000_000 起。

系统错误码 ​

和常见 HTTP 语义对齐。成功不用 200,继续用 0。

名称说明仓库路径
SUCCESS0 成功ruoyi-office/yudao-framework/yudao-common/.../enums/GlobalErrorCodeConstants.java
BAD_REQUEST400 参数不正确同上
UNAUTHORIZED401 未登录同上
FORBIDDEN403 无权限同上
NOT_FOUND404同上
METHOD_NOT_ALLOWED405同上
TOO_MANY_REQUESTS429同上
INTERNAL_SERVER_ERROR500 系统异常同上
NOT_IMPLEMENTED501 未实现 / 未开同上
REPEATED_REQUESTS900 重复请求同上
DEMO_DENY901 演示模式禁写同上

业务错误码 ​

一共 10 位、四段。ServiceErrorCodeRange 只写注释、不参与运行。

业务错误码四段

示意图:1_002_003_000 = 业务 / system / 用户 / 序号 000。

名称说明仓库路径
第 1 段 1 位1 业务异常ruoyi-office/yudao-framework/yudao-common/.../enums/ServiceErrorCodeRange.java
第 2 段 3 位系统,如 002 system同上
第 3 段 3 位模块,如 003 用户同上
第 4 段 3 位该模块内自增同上

ServiceErrorCodeRange 注释里的区间:

名称说明仓库路径
infra[1-001-000-000, 1-002-000-000)ServiceErrorCodeRange
system[1-002-000-000, 1-003-000-000)同上
report[1-003-000-000, 1-004-000-000)同上
member[1-004-000-000, 1-005-000-000)同上
mp[1-006-000-000, 1-007-000-000)同上
pay[1-007-000-000, 1-008-000-000)同上
product[1-008-000-000, 1-009-000-000)同上
bpm[1-009-000-000, 1-010-000-000)同上
trade[1-011-000-000, 1-012-000-000)同上
promotion[1-013-000-000, 1-014-000-000)同上
crm[1-020-000-000, 1-021-000-000)同上
ai(注释)[1-022-000-000, 1-023-000-000)同上

各模块真正使用的段号写在自己的 ErrorCodeConstants 文件头,新增时以该文件为准。

名称说明仓库路径
system1-002-000-000ruoyi-office/yudao-module-system/yudao-module-system-api/.../enums/ErrorCodeConstants.java
infra1-001-000-000yudao-module-infra-api/.../ErrorCodeConstants.java
bpm1-009-000-000yudao-module-bpm-api/.../ErrorCodeConstants.java
oa1-101-000-000yudao-module-oa-api/.../ErrorCodeConstants.java
hrm1-050-000-000yudao-module-hrm-api/.../ErrorCodeConstants.java
contract1-102-000-000yudao-module-contract-api/.../ErrorCodeConstants.java
crm1-020-000-000yudao-module-crm-api/.../ErrorCodeConstants.java
erp1-030-000-000yudao-module-erp-api/.../ErrorCodeConstants.java

段号可能重叠

ErrorCode 必须全局唯一。个别模块文件头都写了 1-040 或 1-050。加新码先全仓搜数字,不要只看 ServiceErrorCodeRange 注释。

模块常量 ​

system 按子功能再切三段,例如 AUTH 1-002-000-000、菜单 1-002-001-000、用户 1-002-003-000。

java
ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1_002_000_000, "登录失败,账号密码不正确");
ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1_002_000_004, "验证码不正确,原因:{}");
ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1_002_003_000, "用户账号已经存在");

新业务:在对应 *-api 的 ErrorCodeConstants 追加常量,Service 里 throw exception(常量)。不要在代码里写裸数字。

配置与操作 ​

新错误在对应 *-api 的 ErrorCodeConstants 追加,Service 里 throw exception(常量)。不要写裸数字。全局处理在 Web Starter,页面无「错误码」菜单。

开启见 框架层。

相关篇 ​

联系我们

获取报价、演示和二开方案

微信咨询二维码

微信咨询

17156169080

添加时备注「RuoYi Office」

在线体验商业版