Spring Boot 统一返回格式 + 全局异常处理(实战版)

Spring Boot 统一返回格式 + 全局异常处理(实战版)

在Spring Boot项目开发中,接口返回格式混乱、异常处理分散是新手常踩的坑——不同接口返回不同结构的数据,前端解析繁琐;异常直接抛出到前端,暴露系统细节且不友好;重复编写返回结果封装代码,冗余低效。

本文结合实战场景,手把手实现「统一返回格式」+「全局异常处理」,整合Spring Boot内置的异常处理机制,规范接口返回标准,统一捕获项目中所有异常(自定义异常、系统异常、业务异常),让接口开发更规范、维护更高效,同时兼顾前端解析体验和系统安全性。

前置说明:本文基于Spring Boot 2.7.x版本开发,适配RESTful接口规范,可直接整合到之前的「Spring Boot + MyBatis-Plus」项目中,无缝衔接单表CRUD接口。

一、核心目标(为什么要做统一返回和全局异常)

在没有统一处理的情况下,项目接口会存在以下问题:

  • 返回格式混乱:成功时返回实体对象,失败时返回字符串/错误码,前端需单独处理每种情况,开发效率低;

  • 异常暴露敏感信息:系统报错(如空指针、数据库异常)时,直接返回堆栈信息,可能泄露数据库地址、表结构等敏感内容;

  • 异常处理分散:每个接口都要try-catch捕获异常,代码冗余,且容易遗漏异常场景;

  • 前端对接成本高:无统一的错误码规范,前端无法快速判断异常类型(如参数错误、权限不足、系统异常)。

因此,我们需要实现两个核心目标:

  1. 统一返回格式:无论接口成功/失败,都返回固定结构的JSON数据,包含状态码、提示信息、业务数据;

  2. 全局异常处理:集中捕获项目中所有异常,统一封装异常信息,隐藏敏感细节,返回规范的错误响应。

二、第一步:实现统一返回格式

统一返回格式的核心是「封装一个通用返回实体类」,搭配「静态工具方法」,简化接口返回代码,确保所有接口返回结构一致。

1. 定义返回状态码枚举(规范错误码)

先定义一个枚举类,管理所有接口的状态码和对应提示信息,避免硬编码错误码,便于后续维护(可根据项目需求扩展)。

package com.example.cruddemo.common;

/**
 * 统一返回状态码枚举
 * 规范:200=成功,4xx=客户端错误,5xx=服务端错误,自定义错误码从1000开始
 */
public enum ResultCode {
    // 成功状态
    SUCCESS(200, "操作成功"),

    // 客户端错误(4xx)
    PARAM_ERROR(400, "参数错误"),
    NOT_FOUND(404, "资源不存在"),
    METHOD_NOT_ALLOWED(405, "请求方法不允许"),

    // 服务端错误(5xx)
    SYSTEM_ERROR(500, "系统异常,请联系管理员"),
    DB_ERROR(501, "数据库操作异常"),

    // 自定义业务错误(1000+)
    USER_NOT_EXIST(1001, "用户不存在"),
    USER_NAME_DUPLICATE(1002, "用户名已存在"),
    PASSWORD_ERROR(1003, "密码错误");

    // 状态码
    private final Integer code;
    // 提示信息
    private final String msg;

    // 构造方法
    ResultCode(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    // getter方法(无需setter,枚举值不可修改)
    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

2. 封装通用返回实体类

定义一个Result类,作为所有接口的统一返回载体,包含3个核心字段:状态码(code)、提示信息(msg)、业务数据(data),搭配静态工具方法,快速构建返回结果。

package com.example.cruddemo.common;

import lombok.Data;

import java.io.Serializable;

/**
 * 统一返回格式实体类
 * 所有接口返回结果都封装为此类,确保格式统一
 * Serializable:实现序列化,便于跨服务传输(可选,单体项目可省略)
 */
@Data
public class Result<T> implements Serializable {

    // 状态码(对应ResultCode枚举)
    private Integer code;

    // 提示信息
    private String msg;

    // 业务数据(泛型T,适配不同类型的返回数据:实体、列表、字符串等)
    private T data;

    // 私有构造方法,禁止外部直接new(通过静态方法构建)
    private Result() {}

    // 私有构造方法,用于快速赋值
    private Result(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    // -------------------------- 静态工具方法(快速构建返回结果)--------------------------

    /**
     * 成功返回(无业务数据)
     */
    public static <T> Result<T> success() {
        return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg(), null);
    }

    /**
     * 成功返回(有业务数据)
     * @param data 业务数据(泛型,可传实体、列表等)
     */
    public static <T> Result<T> success(T data) {
        return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg(), data);
    }

    /**
     * 成功返回(自定义提示信息+业务数据)
     * @param msg 自定义提示信息
     * @param data 业务数据
     */
    public static <T> Result<T> success(String msg, T data) {
        return new Result<>(ResultCode.SUCCESS.getCode(), msg, data);
    }

    /**
     * 失败返回(根据ResultCode枚举)
     * @param resultCode 状态码枚举
     */
    public static <T> Result<T> fail(ResultCode resultCode) {
        return new Result<>(resultCode.getCode(), resultCode.getMsg(), null);
    }

    /**
     * 失败返回(根据ResultCode枚举+自定义提示信息)
     * @param resultCode 状态码枚举
     * @param msg 自定义提示信息(覆盖枚举默认msg)
     */
    public static <T> Result<T> fail(ResultCode resultCode, String msg) {
        return new Result<>(resultCode.getCode(), msg, null);
    }

    /**
     * 失败返回(自定义状态码+提示信息)
     * @param code 自定义状态码
     * @param msg 提示信息
     */
    public static <T> Result<T> fail(Integer code, String msg) {
        return new Result<>(code, msg, null);
    }
}

3. 接口改造(使用统一返回格式)

修改之前的UserController接口,将所有接口的返回值改为Result,通过静态工具方法构建返回结果,替代原来的字符串、实体类返回,实现格式统一。

package com.example.cruddemo.controller;

import com.example.cruddemo.common.Result;
import com.example.cruddemo.common.ResultCode;
import com.example.cruddemo.entity.User;
import com.example.cruddemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

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

    @Autowired
    private UserService userService;

    // 1. 新增用户(POST请求)
    @PostMapping("/add")
    public Result<Void> addUser(@RequestBody User user) {
        // 简单业务校验(后续可通过全局异常处理优化)
        if (user.getName() == null || user.getName().isEmpty()) {
            // 失败返回:参数错误
            return Result.fail(ResultCode.PARAM_ERROR, "用户名不能为空");
        }
        boolean success = userService.save(user);
        return success ? Result.success() : Result.fail(ResultCode.DB_ERROR, "新增用户失败");
    }

    // 2. 根据ID查询用户(GET请求)
    @GetMapping("/{id}")
    public Result<User> getUserById(@PathVariable Long id) {
        User user = userService.getById(id);
        // 若用户不存在,返回自定义业务错误
        if (user == null) {
            return Result.fail(ResultCode.USER_NOT_EXIST);
        }
        // 成功返回,携带用户数据
        return Result.success(user);
    }

    // 3. 查询所有用户(GET请求)
    @GetMapping("/list")
    public Result<List<User>> getUserList() {
        List<User> userList = userService.list();
        // 成功返回,携带用户列表数据
        return Result.success(userList);
    }

    // 4. 根据ID修改用户(PUT请求)
    @PutMapping("/update")
    public Result<Void> updateUser(@RequestBody User user) {
        if (user.getId() == null) {
            return Result.fail(ResultCode.PARAM_ERROR, "用户ID不能为空");
        }
        boolean success = userService.updateById(user);
        return success ? Result.success() : Result.fail(ResultCode.DB_ERROR, "修改用户失败");
    }

    // 5. 根据ID删除用户(DELETE请求)
    @DeleteMapping("/{id}")
    public Result<Void> deleteUser(@PathVariable Long id) {
        boolean success = userService.removeById(id);
        return success ? Result.success() : Result.fail(ResultCode.DB_ERROR, "删除用户失败");
    }
}

4. 统一返回格式效果演示

改造后,所有接口返回格式完全统一,前端可根据code判断接口状态,无需适配多种返回结构:

  • 成功返回(有数据):
    { "code": 200, "msg": "操作成功", "data": { "id": 1, "name": "张三", "age": 22, "email": "zhangsan@163.com", "createTime": "2024-05-20 10:30:00", "updateTime": "2024-05-20 10:30:00" } }

  • 成功返回(无数据):
    { "code": 200, "msg": "操作成功", "data": null }

  • 失败返回(参数错误):
    { "code": 400, "msg": "用户名不能为空", "data": null }

  • 失败返回(业务错误):
    { "code": 1001, "msg": "用户不存在", "data": null }

三、第二步:实现全局异常处理

统一返回格式解决了“返回结构混乱”的问题,但接口中仍可能抛出异常(如空指针、数据库异常、自定义业务异常)。全局异常处理的核心是「集中捕获所有异常」,通过Spring Boot提供的@RestControllerAdvice和@ExceptionHandler注解,无需在每个接口中try-catch,自动封装异常为统一返回格式。

1. 编写全局异常处理器

创建GlobalExceptionHandler类,添加@RestControllerAdvice注解(标识为全局异常处理器,适配RESTful接口),然后通过@ExceptionHandler注解,针对不同类型的异常,编写对应的处理方法,实现“不同异常,不同响应”。

package com.example.cruddemo.common;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import java.sql.SQLException;

/**
 * 全局异常处理器
 * 集中捕获项目中所有异常,统一封装返回格式,避免异常直接暴露给前端
 * @RestControllerAdvice:等同于@ControllerAdvice + @ResponseBody,返回JSON格式异常响应
 * @Slf4j:Lombok注解,用于打印异常日志(便于排查问题)
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    // -------------------------- 1. 自定义业务异常(最常用,手动抛出)--------------------------
    /**
     * 捕获自定义业务异常(BusinessException)
     * 业务中需要手动抛出异常时,使用此类,如:throw new BusinessException(ResultCode.USER_NOT_EXIST);
     */
    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusinessException(BusinessException e) {
        // 打印异常日志(仅打印提示信息,不打印堆栈,避免日志冗余)
        log.warn("业务异常:{}", e.getMessage());
        // 封装异常信息,返回统一格式
        return Result.fail(e.getCode(), e.getMessage());
    }

    // -------------------------- 2. 系统异常(无需手动抛出,自动捕获)--------------------------
    /**
     * 捕获数据库异常(SQLException)
     * 如:数据库连接失败、SQL语法错误、主键冲突等
     */
    @ExceptionHandler(SQLException.class)
    public Result<Void> handleSQLException(SQLException e) {
        // 打印异常堆栈(系统异常,需要详细日志排查问题)
        log.error("数据库异常:", e);
        // 隐藏敏感信息,返回统一的数据库异常提示
        return Result.fail(ResultCode.DB_ERROR);
    }

    /**
     * 捕获空指针异常(NullPointerException)
     * 项目中最常见的系统异常,统一捕获,避免返回堆栈信息
     */
    @ExceptionHandler(NullPointerException.class)
    public Result<Void> handleNullPointerException(NullPointerException e) {
        log.error("空指针异常:", e);
        return Result.fail(ResultCode.SYSTEM_ERROR, "系统异常,请联系管理员");
    }

    /**
     * 捕获通用系统异常(Exception)
     * 兜底异常处理器,捕获所有未被上面单独处理的异常(避免遗漏)
     */
    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception e) {
        log.error("系统异常:", e);
        return Result.fail(ResultCode.SYSTEM_ERROR);
    }

    // -------------------------- 3. 请求参数异常(客户端传入参数错误)--------------------------
    /**
     * 捕获参数校验异常(MethodArgumentNotValidException)
     * 配合@Valid注解使用,用于校验请求参数(如:用户名不能为空、年龄必须大于0)
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        // 获取参数校验失败的详细信息
        BindingResult bindingResult = e.getBindingResult();
        // 拼接所有错误提示(如:用户名不能为空;年龄必须大于0)
        StringBuilder errorMsg = new StringBuilder();
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            errorMsg.append(fieldError.getDefaultMessage()).append(";");
        }
        // 截取最后一个“;”,避免格式冗余
        String msg = errorMsg.substring(0, errorMsg.length() - 1);
        log.warn("参数校验异常:{}", msg);
        return Result.fail(ResultCode.PARAM_ERROR, msg);
    }

    /**
     * 捕获参数类型不匹配异常(MethodArgumentTypeMismatchException)
     * 如:接口要求传入Long类型的ID,但客户端传入了字符串
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public Result<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        String msg = String.format("参数类型不匹配:%s 应为 %s 类型", e.getName(), e.getRequiredType().getSimpleName());
        log.warn(msg);
        return Result.fail(ResultCode.PARAM_ERROR, msg);
    }

    // -------------------------- 4. HTTP请求异常(客户端请求方式/路径错误)--------------------------
    /**
     * 捕获404异常(资源不存在)
     */
    @ExceptionHandler(org.springframework.web.servlet.NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND) // 设置HTTP响应状态码为404
    public Result<Void> handleNoHandlerFoundException(org.springframework.web.servlet.NoHandlerFoundException e) {
        log.warn("资源不存在:{}", e.getRequestURL());
        return Result.fail(ResultCode.NOT_FOUND);
    }

    /**
     * 捕获405异常(请求方法不允许)
     */
    @ExceptionHandler(org.springframework.web.HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) // 设置HTTP响应状态码为405
    public Result<Void> handleHttpRequestMethodNotSupportedException(org.springframework.web.HttpRequestMethodNotSupportedException e) {
        String msg = String.format("请求方法不允许:%s,支持的方法:%s", e.getMethod(), e.getSupportedHttpMethods());
        log.warn(msg);
        return Result.fail(ResultCode.METHOD_NOT_ALLOWED, msg);
    }
}

2. 编写自定义业务异常类

系统异常(如空指针、数据库异常)会自动抛出,但业务异常(如用户不存在、用户名重复)需要我们手动抛出。编写BusinessException类,继承RuntimeException,配合全局异常处理器,实现业务异常的统一捕获。

package com.example.cruddemo.common;

import lombok.Getter;

/**
 * 自定义业务异常类
 * 业务中需要抛出异常时,使用此类(如:用户不存在、密码错误)
 * 继承RuntimeException:无需强制try-catch,交给全局异常处理器自动捕获
 */
@Getter // 提供getter方法,全局异常处理器需要获取code和msg
public class BusinessException extends RuntimeException {

    // 异常状态码(对应ResultCode枚举)
    private final Integer code;
    // 异常提示信息
    private final String message;

    // 构造方法1:传入ResultCode枚举,直接获取code和msg
    public BusinessException(ResultCode resultCode) {
        this.code = resultCode.getCode();
        this.message = resultCode.getMsg();
    }

    // 构造方法2:传入ResultCode枚举 + 自定义提示信息(覆盖枚举默认msg)
    public BusinessException(ResultCode resultCode, String message) {
        this.code = resultCode.getCode();
        this.message = message;
    }

    // 构造方法3:自定义code和msg(不使用枚举,灵活适配特殊场景)
    public BusinessException(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    // 重写getMessage方法,返回自定义提示信息
    @Override
    public String getMessage() {
        return message;
    }
}

3. 开启404异常捕获(可选)

默认情况下,Spring Boot不会将404异常(资源不存在)抛给全局异常处理器,需要在application.yml中添加配置,开启404异常捕获,确保404异常也能返回统一格式。

# 开启404异常捕获,交给全局异常处理器处理
spring:
  mvc:
    throw-exception-if-no-handler-found: true
  web:
    resources:
      add-mappings: false  # 关闭静态资源映射(避免静态资源404被拦截)

4. 优化接口:手动抛出业务异常

修改UserController接口,删除原来的if-else参数校验,改用「@Valid注解校验参数」+「手动抛出BusinessException」,简化代码,同时让异常处理更集中。

package com.example.cruddemo.controller;

import com.example.cruddemo.common.BusinessException;
import com.example.cruddemo.common.Result;
import com.example.cruddemo.common.ResultCode;
import com.example.cruddemo.entity.User;
import com.example.cruddemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;

@RestController
@RequestMapping("/api/user")
@Validated // 开启参数校验(用于方法参数单独校验,如@PathVariable、@RequestParam)
public class UserController {

    @Autowired
    private UserService userService;

    // 1. 新增用户(POST请求)- 优化:参数校验+手动抛异常
    @PostMapping("/add")
    public Result<Void> addUser(@Valid @RequestBody User user) {
        // 业务校验:用户名是否重复(模拟业务逻辑)
        boolean exists = userService.lambdaQuery().eq(User::getName, user.getName()).exists();
        if (exists) {
            // 手动抛出业务异常,全局异常处理器会自动捕获并封装返回格式
            throw new BusinessException(ResultCode.USER_NAME_DUPLICATE);
        }
        userService.save(user);
        return Result.success();
    }

    // 2. 根据ID查询用户(GET请求)- 优化:手动抛异常
    @GetMapping("/{id}")
    public Result<User> getUserById(@NotNull(message = "用户ID不能为空") @PathVariable Long id) {
        User user = userService.getById(id);
        if (user == null) {
            throw new BusinessException(ResultCode.USER_NOT_EXIST);
        }
        return Result.success(user);
    }

    // 3. 查询所有用户(GET请求)
    @GetMapping("/list")
    public Result<List<User>> getUserList() {
        return Result.success(userService.list());
    }

    // 4. 根据ID修改用户(PUT请求)- 优化:参数校验
    @PutMapping("/update")
    public Result<Void> updateUser(@Valid @RequestBody User user) {
        userService.updateById(user);
        return Result.success();
    }

    // 5. 根据ID删除用户(DELETE请求)- 优化:参数校验
    @DeleteMapping("/{id}")
    public Result<Void> deleteUser(@NotNull(message = "用户ID不能为空") @PathVariable Long id) {
        userService.removeById(id);
        return Result.success();
    }

    // 新增:根据姓名查询用户(参数校验示例)
    @GetMapping("/list/name")
    public Result<List<User>> getUserByName(@NotBlank(message = "用户名不能为空") @RequestParam String name) {
        List<User> userList = userService.lambdaQuery().eq(User::getName, name).list();
        return Result.success(userList);
    }
}

5. 实体类参数校验(配合@Valid注解)

在User实体类中添加参数校验注解(如@NotBlank、@NotNull),配合接口中的@Valid注解,实现请求参数的自动校验,无需手动编写if-else校验逻辑。

package com.example.cruddemo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import java.util.Date;

@Data
@TableName("user")
public class User {

    @TableId(type = IdType.AUTO)
    private Long id;

    // 用户名:不能为空,长度1-30
    @NotBlank(message = "用户名不能为空")
    @Length(min = 1, max = 30, message = "用户名长度必须在1-30之间")
    private String name;

    // 年龄:不能为null,且必须为正数
    @NotNull(message = "年龄不能为空")
    @Positive(message = "年龄必须为正数")
    private Integer age;

    // 邮箱:格式校验
    @Email(message = "邮箱格式不正确")
    private String email;

    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private Date createTime;

    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

四、全局异常处理效果演示

配置完成后,所有异常都会被全局异常处理器捕获,自动封装为统一返回格式,隐藏敏感信息,同时打印详细日志便于排查问题,以下是常见异常的响应效果:

1. 业务异常(用户不存在)

请求接口:GET /api/user/999(ID=999的用户不存在),手动抛出BusinessException,响应如下:

{
  "code": 1001,
  "msg": "用户不存在",
  "data": null
}

日志打印(仅打印提示信息,不打印堆栈):warn: 业务异常:用户不存在

2. 参数校验异常(用户名不能为空)

请求接口:POST /api/user/add(传入name=null),@Valid注解校验失败,响应如下:

{
  "code": 400,
  "msg": "用户名不能为空",
  "data": null
}

日志打印:warn: 参数校验异常:用户名不能为空

3. 参数类型不匹配异常

请求接口:GET /api/user/abc(ID要求Long类型,传入字符串abc),响应如下:

{
  "code": 400,
  "msg": "参数类型不匹配:id 应为 Long 类型",
  "data": null
}

4. 系统异常(空指针)

接口中出现空指针异常(如user.getEmail().length(),user为null),响应如下(隐藏堆栈信息):

{
  "code": 500,
  "msg": "系统异常,请联系管理员",
  "data": null
}

日志打印(打印完整堆栈,便于排查):error: 空指针异常:java.lang.NullPointerException: ...

5. 404异常(资源不存在)

请求接口:GET /api/user/abc123(接口路径不存在),响应如下:

{
  "code": 404,
  "msg": "资源不存在",
  "data": null
}

五、高频踩坑点汇总(实战必避)

踩坑1:全局异常处理器不生效

✅ 报错原因:1. 类未添加@RestControllerAdvice注解;2. 异常处理方法未添加@ExceptionHandler注解,或注解中指定的异常类型错误;3. 全局异常处理器类未被Spring Boot扫描到(包路径与启动类不一致)。

✅ 解决方案:确保添加@RestControllerAdvice和@ExceptionHandler注解;核对异常类型(如捕获自定义异常,需传入BusinessException.class);确保全局异常处理器的包路径在启动类扫描范围内(如启动类包是com.example.cruddemo,处理器包是com.example.cruddemo.common)。

踩坑2:404异常无法被捕获

✅ 报错原因:未在application.yml中添加开启404异常捕获的配置,Spring Boot默认不抛出404异常。

✅ 解决方案:添加配置spring.mvc.throw-exception-if-no-handler-found=true和spring.web.resources.add-mappings=false。

踩坑3:参数校验注解(@Valid)不生效

✅ 报错原因:1. 接口方法未添加@Valid注解;2. 实体类未添加参数校验注解(如@NotBlank);3. 控制层类未添加@Validated注解(用于方法参数单独校验,如@PathVariable);4. 未引入参数校验依赖(Spring Boot 2.7.x已内置,无需额外引入)。

✅ 解决方案:接口参数添加@Valid注解,实体类添加对应校验注解,控制层类添加@Validated注解。

踩坑4:自定义异常抛出后,全局处理器未捕获

✅ 报错原因:1. 自定义异常未继承RuntimeException(继承Exception会强制try-catch,无法自动抛出);2. @ExceptionHandler注解中指定的异常类型错误(如写成Exception.class,但实际抛出的是BusinessException)。

✅ 解决方案:自定义异常继承RuntimeException;确保@ExceptionHandler(BusinessException.class)指定的异常类型与抛出的异常一致。

踩坑5:返回格式不统一(部分接口未使用Result类)

✅ 报错原因:忘记修改部分接口的返回值,仍返回实体类、字符串等,未封装为Result。

✅ 解决方案:统一检查所有接口,确保返回值都是Result类型,通过Result.success()/Result.fail()构建返回结果。

六、实战优化建议(贴合企业开发)

  1. 错误码规范:严格遵循枚举类中的错误码规则(200=成功,4xx=客户端错误,5xx=服务端错误,1000+=业务错误),避免乱编错误码,便于前后端对接和问题排查;

  2. 日志规范:业务异常打印warn级别日志(仅提示信息),系统异常打印error级别日志(完整堆栈),避免日志冗余或缺失;

  3. 参数校验:尽量使用@Valid+校验注解,替代手动if-else校验,简化代码,提升可读性;

  4. 异常分类:按“自定义业务异常→参数异常→系统异常→兜底异常”的顺序编写处理器方法,兜底异常(Exception)放在最后,避免覆盖其他异常;

  5. 扩展场景:可在Result类中添加timestamp(时间戳)字段,便于前端记录请求时间;可添加traceId(链路追踪ID),用于分布式项目的异常链路排查。

七、总结

Spring Boot 统一返回格式 + 全局异常处理,是接口开发的“标准化配置”,核心价值在于「规范、高效、安全」:

  • 统一返回格式:通过Result类和ResultCode枚举,让所有接口返回结构一致,降低前端对接成本;

  • 全局异常处理:通过@RestControllerAdvice和@ExceptionHandler,集中捕获所有异常,避免重复try-catch,隐藏系统敏感信息;

  • 实战适配:无缝衔接MyBatis-Plus、RESTful接口,可直接整合到现有项目,同时提供踩坑点和优化建议,贴合企业开发规范。

本文实现的方案,兼顾了简洁性和实用性,新手可直接复制代码使用,后续可根据项目需求(如分布式、多模块),扩展错误码、日志链路追踪等功能。掌握这套方案,能让你的Spring Boot接口开发更规范、更高效,避免因返回格式混乱、异常处理不当导致的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码客日记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值