Java案例怎么统一返回结果?

wen java案例 9

本文目录导读:

Java案例怎么统一返回结果?

  1. 最基础的实现:通用响应类
  2. 使用枚举管理状态码
  3. 利用 Spring Boot 的全局异常处理
  4. 使用 Spring 的 ResponseBodyAdvice 自动包装
  5. 结合 DTO 与 Jackson 注解(精细化控制)
  6. 完整企业级示例(整合以上所有技术)
  7. 最佳实践建议

最基础的实现:通用响应类

这是最直接的方式,定义一个泛型类包装所有返回数据。

public class ApiResult<T> {
    private int code;       // 状态码
    private String message; // 提示信息
    private T data;         // 数据主体
    // 构造方法私有化,强制使用静态工厂方法
    private ApiResult() {}
    public static <T> ApiResult<T> success(T data) {
        ApiResult<T> result = new ApiResult<>();
        result.code = 200;
        result.message = "success";
        result.data = data;
        return result;
    }
    public static <T> ApiResult<T> success(String message, T data) {
        ApiResult<T> result = new ApiResult<>();
        result.code = 200;
        result.message = message;
        result.data = data;
        return result;
    }
    public static <T> ApiResult<T> error(int code, String message) {
        ApiResult<T> result = new ApiResult<>();
        result.code = code;
        result.message = message;
        result.data = null;
        return result;
    }
    // getter/setter 省略
}

使用示例:

@RestController
public class UserController {
    @GetMapping("/user/{id}")
    public ApiResult<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        if (user == null) {
            return ApiResult.error(404, "用户不存在");
        }
        return ApiResult.success(user);
    }
}

使用枚举管理状态码

定义统一的状态码枚举,避免硬编码数字。

public enum ResultCode {
    SUCCESS(200, "操作成功"),
    FAIL(500, "操作失败"),
    NOT_FOUND(404, "资源不存在"),
    UNAUTHORIZED(401, "未授权");
    private final int code;
    private final String message;
    ResultCode(int code, String message) {
        this.code = code;
        this.message = message;
    }
    public int code() { return code; }
    public String message() { return message; }
}

然后修改 ApiResult

public static <T> ApiResult<T> success(T data) {
    return success(ResultCode.SUCCESS.message(), data);
}
public static <T> ApiResult<T> error(ResultCode resultCode) {
    return error(resultCode.code(), resultCode.message());
}

利用 Spring Boot 的全局异常处理

结合 @RestControllerAdvice@ExceptionHandler,实现所有异常统一响应

@RestControllerAdvice
public class GlobalExceptionHandler {
    // 处理业务异常
    @ExceptionHandler(BusinessException.class)
    public ApiResult<?> handleBusinessException(BusinessException e) {
        return ApiResult.error(e.getCode(), e.getMessage());
    }
    // 处理参数校验失败
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResult<?> handleValidation(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult()
                      .getFieldErrors().stream()
                      .map(er -> er.getField() + ": " + er.getDefaultMessage())
                      .collect(Collectors.joining(", "));
        return ApiResult.error(400, msg);
    }
    // 兜底处理所有未捕获异常
    @ExceptionHandler(Exception.class)
    public ApiResult<?> handleException(Exception e) {
        log.error("系统异常", e);
        return ApiResult.error(500, "服务器内部错误");
    }
}

自定义业务异常类:

public class BusinessException extends RuntimeException {
    private int code;
    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }
    public int getCode() { return code; }
}

这样控制器只需正常抛出异常即可,无需手动包装:

@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    if (user == null) {
        throw new BusinessException(404, "用户不存在");
    }
    return user;
}

Spring 会自动将返回的 User 对象包装进 ApiResult,异常也按配置的格式返回。


使用 Spring 的 ResponseBodyAdvice 自动包装

如果你不想在每个控制器方法上都写 ApiResult.success(...),可以实现 ResponseBodyAdvice 对返回结果统一包装。

@RestControllerAdvice
public class ResponseWrapper implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        // 对所有返回值都生效(可排除某些类或包)
        return !returnType.getParameterType().equals(ApiResult.class);
    }
    @Override
    public Object beforeBodyWrite(Object body, 
                                  MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {
        // 如果已经是 ApiResult 类型,不再包装
        if (body instanceof ApiResult) {
            return body;
        }
        // 返回 void 的方法 body 为 null,可以改为成功
        if (body == null) {
            return ApiResult.success(null);
        }
        return ApiResult.success(body);
    }
}

优点: 控制器方法可以返回纯粹的实体对象,框架自动包装。 注意: 需要妥善处理返回 String 类型时的序列化问题(因为 StringHttpMessageConverter 会优先处理)。


结合 DTO 与 Jackson 注解(精细化控制)

可以使用 @JsonInclude@JsonProperty 控制序列化行为:

@Data
@JsonInclude(JsonInclude.Include.NON_NULL) // null字段不序列化
public class ApiResult<T> {
    private int code;
    private String message;
    private T data;
    private Long timestamp; // 可加时间戳
    public ApiResult() {
        this.timestamp = System.currentTimeMillis();
    }
    // 静态工厂方法同上...
}

完整企业级示例(整合以上所有技术)

// ========= 1. 状态码枚举 =========
public enum ResultCode {
    SUCCESS(200, "成功"),
    BAD_REQUEST(400, "请求参数错误"),
    UNAUTHORIZED(401, "未登录"),
    FORBIDDEN(403, "权限不足"),
    NOT_FOUND(404, "资源不存在"),
    INTERNAL_ERROR(500, "服务器内部错误");
    final int code;
    final String message;
    // 构造、getter...
}
// ========= 2. 统一响应类 =========
public class R<T> {
    private int code;
    private String message;
    private T data;
    private long timestamp;
    private R() { this.timestamp = System.currentTimeMillis(); }
    public static <T> R<T> ok(T data) {
        R<T> r = new R<>();
        r.code = ResultCode.SUCCESS.code;
        r.message = ResultCode.SUCCESS.message;
        r.data = data;
        return r;
    }
    public static <T> R<T> fail(ResultCode resultCode) {
        return fail(resultCode.code, resultCode.message);
    }
    public static <T> R<T> fail(int code, String message) {
        R<T> r = new R<>();
        r.code = code;
        r.message = message;
        return r;
    }
    // getter...
}
// ========= 3. 全局异常处理 =========
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R<?> handle(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining("; "));
        return R.fail(ResultCode.BAD_REQUEST.code, msg);
    }
    @ExceptionHandler(BusinessException.class)
    public R<?> handle(BusinessException e) {
        return R.fail(e.getCode(), e.getMessage());
    }
    @ExceptionHandler(Exception.class)
    public R<?> handle(Exception e) {
        log.error("异常", e);
        return R.fail(ResultCode.INTERNAL_ERROR);
    }
}
// ========= 4. 控制器示例 =========
@RestController
@RequestMapping("/api/users")
public class UserController {
    @GetMapping("/{id}")
    public R<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        if (user == null) {
            return R.fail(ResultCode.NOT_FOUND);
        }
        return R.ok(user);
    }
    @PostMapping
    public R<Void> createUser(@Valid @RequestBody UserCreateDTO dto) {
        userService.create(dto);
        return R.ok(null);
    }
}

前端接收到的 JSON 格式统一为:

{
  "code": 200,
  "message": "成功",
  "data": { ... },
  "timestamp": 1695000000000
}

最佳实践建议

场景 推荐方案
小型项目 方案一 + 方案三(纯手工包装)
中型项目 方案二(枚举) + 方案三(全局异常处理)
大型微服务 方案四(ResponseBodyAdvice自动包装)+ 方案三
前后端分离 方案五(Null控制) + 详细的状态码文档
需要国际化 消息从配置文件读取,结合 locale 动态设置

核心原则:

  1. 所有接口响应格式完全一致(code + message + data)
  2. 成功和异常都要统一(异常由全局处理器接管)
  3. 状态码要有明确的业务含义,不要仅用 200/500
  4. 避免暴露内部实现细节(如数据库错误堆栈)

这样前端可以写一个统一的 request.js 拦截器,根据 code 做出全局响应处理(如 401 跳登录、500 弹错误提示等)。

抱歉,评论功能暂时关闭!