一、通用基础代码
一套规范的异常处理方案 + 通用基础代码,能解决这些问题:
- 统一错误码、响应 / 请求格式,前后端交互更高效
- 集中捕获异常、配置跨域,减少重复代码
- 通用工具类 “一次编写终身复用”,节省开发时间
- 便于问题排查,日志记录、服务监控更规范
二、核心组件实现详解
1. 错误码枚举(ErrorCode)—— 统一错误定义
作用:管理所有业务错误类型,避免错误码分散在代码里。
代码实现:
@Getter
public enum ErrorCode {
// 成功状态
SUCCESS(0, "ok"),
// 客户端错误
PARAMS_ERROR(40000, "请求参数错误"),
NOT_LOGIN_ERROR(40100, "未登录"),
NO_AUTH_ERROR(40101, "无权限"),
NOT_FOUND_ERROR(40400, "请求数据不存在"),
FORBIDDEN_ERROR(40300, "禁止访问"),
// 服务端错误
SYSTEM_ERROR(50000, "系统内部异常"),
OPERATION_ERROR(50001, "操作失败");
// 状态码(建议遵循HTTP状态码语义,扩展业务码)
private final int code;
// 错误描述
private final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
}
关键说明:
- 按 “成功 / 客户端错误 / 服务端错误” 分类,逻辑清晰
- 用@Getter简化 getter(需引入 Lombok),不用手动写
- 集中管理错误信息,改错误描述时只需改枚举,不用改业务代码
2. 自定义业务异常(BusinessException)—— 业务异常专属
作用:和 Java 内置异常(如 RuntimeException)区分开,方便携带错误码。
代码实现:
@Getter
public class BusinessException extends RuntimeException {
// 错误码(与ErrorCode对应)
private final int code;
// 三种构造方法,满足不同使用场景
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
public BusinessException(ErrorCode errorCode, String message) {
super(message);
this.code = errorCode.getCode();
}
}
关键说明:
- 继承RuntimeException:不用强制在方法上写throws,更灵活
- 保留code字段:全局异常处理器靠这个码返回正确响应
- 多构造方法:想直接传枚举、想加自定义信息都支持
3. 异常抛出工具类(ThrowUtils)—— 简化条件判断
作用:把 “条件判断 + 抛异常” 的重复代码缩成一行,不用每次写if-else。
代码实现:
public class ThrowUtils {
/**
* 条件成立则抛异常(支持任意RuntimeException)
*/
public static void throwIf(boolean condition, RuntimeException runtimeException) {
if (condition) {
throw runtimeException;
}
}
public static void throwIf(boolean condition, ErrorCode errorCode) {
throwIf(condition, new BusinessException(errorCode));
}
public static void throwIf(boolean condition, ErrorCode errorCode, String message) {
throwIf(condition, new BusinessException(errorCode, message));
}
}
使用示例:
// 原来要写两行:判断+抛异常
if (user == null) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户不存在");
}
// 现在一行搞定
ThrowUtils.throwIf(user == null, ErrorCode.NOT_FOUND_ERROR, "用户不存在");
4. 全局异常处理器(GlobalExceptionHandler)—— 集中抓异常
作用:所有接口的异常都在这处理,不用每个接口单独写try-catch。
代码实现:
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 捕获业务异常(BusinessException)
*/
@ExceptionHandler(BusinessException.class)
public BaseResponse<?> businessExceptionHandler(BusinessException e) {
log.error("BusinessException: ", e); // 记录异常堆栈,便于排查
return ResultUtils.error(e.getCode(), e.getMessage());
}
/**
* 捕获通用运行时异常(RuntimeException)
*/
@ExceptionHandler(RuntimeException.class)
public BaseResponse<?> runtimeExceptionHandler(RuntimeException e) {
log.error("RuntimeException: ", e); // 记录异常堆栈
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误");
}
}
核心注解说明:
- @RestControllerAdvice:全局异常处理注解,作用于所有 @RestController
- @ExceptionHandler:指定捕获的异常类型(如 BusinessException、RuntimeException)
- @Slf4j:Lombok 注解,简化日志记录(需引入 Lombok)
处理逻辑:
- 优先抓业务异常:返回对应的错误码和信息(如 “用户不存在”)
- 再抓系统异常:返回通用 “系统错误”,避免暴露技术细节给用户
- 所有异常都记日志:出问题时能通过日志定位哪里错了
5. 响应包装类(BaseResponse + ResultUtils)—— 统一接口返回格式
作用:所有接口都返回相同结构(code+data+message),前端不用适配多种格式。
5.1 统一响应体(BaseResponse)
@Data
public class BaseResponse<T> implements Serializable {
private int code;
private T data;
private String message;
public BaseResponse(int code, T data, String message) {
this.code = code;
this.data = data;
this.message = message;
}
public BaseResponse(int code, T data) {
this(code, data, "");
}
public BaseResponse(ErrorCode errorCode) {
this(errorCode.getCode(), null, errorCode.getMessage());
}
}
5.2 响应工具类(ResultUtils)
作用:不用每次手动new BaseResponse,用静态方法直接返回结果。
public class ResultUtils {
/**
* 成功
* @param data 数据
* @param <T> 数据类型
* @return 响应
*/
public static <T> BaseResponse<T> success(T data) {
return new BaseResponse<>(0, data, "ok");
}
/**
* 失败
* @param errorCode 错误码
* @return 响应
*/
public static BaseResponse<?> error(ErrorCode errorCode) {
return new BaseResponse<>(errorCode);
}
/**
* 失败
* @param code 错误码
* @param message 错误信息
* @return 响应
*/
public static BaseResponse<?> error(int code, String message) {
return new BaseResponse<>(code, null, message);
}
/**
* 失败
* @param errorCode 错误码
* @return 响应
*/
public static BaseResponse<?> error(ErrorCode errorCode, String message) {
return new BaseResponse<>(errorCode.getCode(), null, message);
}
}
使用示例:
// 成功返回用户列表
@GetMapping("/list")
public BaseResponse<List<User>> getUserList() {
List<User> list = userService.list();
return ResultUtils.success(list); // 一行搞定
}
// 失败返回错误
@GetMapping("/detail")
public BaseResponse<User> getUserDetail(Long id) {
User user = userService.getById(id);
ThrowUtils.throwIf(user == null, ErrorCode.NOT_FOUND_ERROR, "用户不存在");
return ResultUtils.success(user);
}
6. 请求包装类 —— 减少重复参数定义
作用:把常用请求参数(如分页、删除)封装成类,不用每个接口都写相同字段。
6.1 分页请求包装类(PageRequest)
场景:用户列表、商品列表等需要分页的接口,统一接收 “页号、页大小、排序” 参数。
@Data
public class PageRequest {
/**
* 当前页号
*/
private int current = 1;
/**
* 页面大小
*/
private int pageSize = 10;
/**
* 排序字段
*/
private String sortField;
/**
* 排序顺序(默认降序)
*/
private String sortOrder = "descend";
}
使用示例:
// 分页查用户,直接用PageRequest接收参数
@GetMapping("/list")
public BaseResponse<Page<User>> getUserList(PageRequest request) {
// 转成MyBatis-Plus的Page对象(根据框架调整)
Page<User> page = new Page<>(request.getCurrent(), request.getPageSize());
Page<User> userPage = userService.page(page, new LambdaQueryWrapper<>());
return ResultUtils.success(userPage);
}
6.2 删除请求包装类(DeleteRequest)
场景:删除用户、删除订单等接口,都需要 “id” 参数,统一封装避免重复写。
@Data
public class DeleteRequest implements Serializable {
/**
* id
*/
private Long id;
private static final long serialVersionUID = 1L;
}
使用示例:
// 删除用户,用DeleteRequest接收id
@PostMapping("/delete")
public BaseResponse<Boolean> deleteUser(@RequestBody DeleteRequest request) {
ThrowUtils.throwIf(request.getId() == null, ErrorCode.PARAMS_ERROR, "id不能为空");
boolean success = userService.removeById(request.getId());
ThrowUtils.throwIf(!success, ErrorCode.OPERATION_ERROR, "删除失败");
return ResultUtils.success(true);
}
7. 全局跨域配置(CorsConfig)—— 解决前后端访问问题
先通俗讲跨域:
前端跑在localhost:8080,后端跑在localhost:8081,浏览器觉得 “不同端口不安全”,会拦截请求 —— 这就是跨域。配置后所有接口都支持跨域,不用每个接口单独处理。
代码实现:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 覆盖所有请求
registry.addMapping("/**")
// 允许发送 Cookie
.allowCredentials(true)
// 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突)
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("*");
}
}
核心价值:一次配置终身用,开发时不用再跟前端纠结 “为什么调接口报跨域错”。
8. 健康检查接口(MainController)—— 监控服务是否活着
作用:项目部署后,运维或监控工具(如 Prometheus)需要知道服务是否正常,访问/health返回 “ok” 就说明服务活着。
代码实现:
@RestController
@RequestMapping("/")
public class MainController {
/**
* 健康检查
*/
@GetMapping("/health")
public BaseResponse<String> health() {
return ResultUtils.success("ok");
}
}
使用场景:
- 运维定时访问http://你的服务地址/health,如果返回{"code":0,"data":"ok","message":""},说明服务正常;
- 云服务器(如阿里云)的健康检查功能,靠这个接口判断是否需要重启服务。
三、实战使用示例(整合通用代码)
完整接口示例:分页查用户 + 删除用户
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 分页查用户(用PageRequest接收参数)
*/
@GetMapping("/list")
public BaseResponse<Page<User>> getUserList(PageRequest request) {
// 1. 参数校验(用ThrowUtils简化)
ThrowUtils.throwIf(request.getCurrent() < 1, ErrorCode.PARAMS_ERROR, "页号不能小于1");
ThrowUtils.throwIf(request.getPageSize() > 100, ErrorCode.PARAMS_ERROR, "每页最多100条");
// 2. 业务逻辑(分页查询)
Page<User> page = new Page<>(request.getCurrent(), request.getPageSize());
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
// 排序(如果传了sortField)
if (StringUtils.hasText(request.getSortField())) {
queryWrapper.orderBy(true, "ascend".equals(request.getSortOrder()), request.getSortField());
}
Page<User> userPage = userService.page(page, queryWrapper);
// 3. 返回结果(用ResultUtils)
return ResultUtils.success(userPage);
}
/**
* 删除用户(用DeleteRequest接收参数)
*/
@PostMapping("/delete")
public BaseResponse<Boolean> deleteUser(@RequestBody DeleteRequest request) {
// 1. 参数校验
ThrowUtils.throwIf(request.getId() == null, ErrorCode.PARAMS_ERROR, "id不能为空");
// 2. 业务逻辑
boolean success = userService.removeById(request.getId());
ThrowUtils.throwIf(!success, ErrorCode.OPERATION_ERROR, "删除失败(用户可能已不存在)");
// 3. 返回结果
return ResultUtils.success(true);
}
}
四、注意事项(避坑指南)
- 错误码不要乱加:新增错误时先看 ErrorCode 有没有现成的,避免重复(如 “参数错误” 不用再定义 40001);
- 跨域配置生产环境优化:allowedOriginPatterns("*")在生产环境要改成具体域名(如"https://yourdomain.com"),更安全;
- 序列化不要忘:涉及网络传输(如响应体、请求体)的类,要实现Serializable,避免反序列化报错;
- 日志要记堆栈:log.error("异常:", e)要带e,否则看不到异常堆栈,没法定位问题;
- 默认值要合理:分页请求的current=1、pageSize=10,符合大多数场景,减少前端传参压力。
859

被折叠的 条评论
为什么被折叠?



