1. 基础结构设计
1.1 统一响应类
java
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ApiResponse<T> {
private Integer code; // 状态码
private String message; // 消息
private T data; // 数据
private Long timestamp; // 时间戳
public static <T> ApiResponse<T> success(T data) {
return ApiResponse.<T>builder()
.code(200)
.message("成功")
.data(data)
.timestamp(System.currentTimeMillis())
.build();
}
public static <T> ApiResponse<T> success() {
return success(null);
}
public static <T> ApiResponse<T> error(Integer code, String message) {
return ApiResponse.<T>builder()
.code(code)
.message(message)
.timestamp(System.currentTimeMillis())
.build();
}
}
1.2 错误码枚举
java
public enum ErrorCode {
SUCCESS(200, "成功"),
BAD_REQUEST(400, "请求参数错误"),
UNAUTHORIZED(401, "未授权"),
FORBIDDEN(403, "禁止访问"),
NOT_FOUND(404, "资源不存在"),
INTERNAL_ERROR(500, "服务器内部错误"),
// 业务错误码
USER_NOT_EXIST(1001, "用户不存在"),
INVALID_TOKEN(1002, "无效的token"),
DUPLICATE_DATA(1003, "数据重复"),
DATA_NOT_FOUND(1004, "数据不存在");
private final Integer code;
private final String message;
ErrorCode(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
2. 自定义异常类
java
/**
* 基础异常类
*/
public class BaseException extends RuntimeException {
private Integer code;
public BaseException(Integer code, String message) {
super(message);
this.code = code;
}
public BaseException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
public BaseException(ErrorCode errorCode, String message) {
super(message);
this.code = errorCode.getCode();
}
public Integer getCode() {
return code;
}
}
/**
* 业务异常
*/
public class BusinessException extends BaseException {
public BusinessException(ErrorCode errorCode) {
super(errorCode);
}
public BusinessException(ErrorCode errorCode, String message) {
super(errorCode, message);
}
public BusinessException(Integer code, String message) {
super(code, message);
}
}
/**
* 参数校验异常
*/
public class ValidationException extends BaseException {
private Map<String, String> errors;
public ValidationException(Map<String, String> errors) {
super(ErrorCode.BAD_REQUEST);
this.errors = errors;
}
public Map<String, String> getErrors() {
return errors;
}
}
3. 全局异常处理器
java
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理业务异常
*/
@ExceptionHandler(BusinessException.class)
public ApiResponse<?> handleBusinessException(BusinessException e) {
log.error("业务异常: {}", e.getMessage(), e);
return ApiResponse.error(e.getCode(), e.getMessage());
}
/**
* 处理参数校验异常
*/
@ExceptionHandler(ValidationException.class)
public ApiResponse<?> handleValidationException(ValidationException e) {
log.error("参数校验异常: {}", e.getMessage());
Map<String, Object> result = new HashMap<>();
result.put("message", e.getMessage());
result.put("errors", e.getErrors());
return ApiResponse.error(e.getCode(), "参数校验失败").data(result);
}
/**
* 处理参数绑定异常 (@Valid 触发的异常)
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResponse<?> handleMethodArgumentNotValidException(
MethodArgumentNotValidException e) {
log.error("参数校验异常: {}", e.getMessage());
Map<String, String> errors = e.getBindingResult()
.getFieldErrors()
.stream()
.collect(Collectors.toMap(
FieldError::getField,
fieldError -> fieldError.getDefaultMessage() != null ?
fieldError.getDefaultMessage() : "参数错误"
));
Map<String, Object> result = new HashMap<>();
result.put("message", "参数校验失败");
result.put("errors", errors);
return ApiResponse.error(ErrorCode.BAD_REQUEST.getCode(), "参数校验失败")
.data(result);
}
/**
* 处理请求参数异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public ApiResponse<?> handleConstraintViolationException(
ConstraintViolationException e) {
log.error("请求参数异常: {}", e.getMessage());
Map<String, String> errors = e.getConstraintViolations()
.stream()
.collect(Collectors.toMap(
violation -> violation.getPropertyPath().toString(),
ConstraintViolation::getMessage
));
Map<String, Object> result = new HashMap<>();
result.put("message", "请求参数错误");
result.put("errors", errors);
return ApiResponse.error(ErrorCode.BAD_REQUEST.getCode(), "请求参数错误")
.data(result);
}
/**
* 处理404异常
*/
@ExceptionHandler(NoHandlerFoundException.class)
public ApiResponse<?> handleNotFoundException(NoHandlerFoundException e) {
log.error("资源不存在: {}", e.getRequestURL());
return ApiResponse.error(ErrorCode.NOT_FOUND.getCode(),
"接口 [" + e.getRequestURL() + "] 不存在");
}
/**
* 处理权限异常
*/
@ExceptionHandler(AccessDeniedException.class)
public ApiResponse<?> handleAccessDeniedException(AccessDeniedException e) {
log.error("权限异常: {}", e.getMessage());
return ApiResponse.error(ErrorCode.FORBIDDEN.getCode(),
"无访问权限");
}
/**
* 处理认证异常
*/
@ExceptionHandler(AuthenticationException.class)
public ApiResponse<?> handleAuthenticationException(AuthenticationException e) {
log.error("认证异常: {}", e.getMessage());
return ApiResponse.error(ErrorCode.UNAUTHORIZED.getCode(),
"认证失败");
}
/**
* 处理所有未捕获的异常
*/
@ExceptionHandler(Exception.class)
public ApiResponse<?> handleGlobalException(Exception e) {
log.error("系统异常: ", e);
// 生产环境可以返回通用错误信息
String message = "系统繁忙,请稍后重试";
// 开发环境可以返回详细错误
// String message = e.getMessage();
return ApiResponse.error(ErrorCode.INTERNAL_ERROR.getCode(), message);
}
/**
* 处理HTTP请求方法不支持异常
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ApiResponse<?> handleHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException e) {
log.error("HTTP方法不支持: {}", e.getMethod());
return ApiResponse.error(405,
"不支持 [" + e.getMethod() + "] 请求方法");
}
}
4. 配置类
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureHandlerExceptionResolvers(
List<HandlerExceptionResolver> resolvers) {
// 确保自定义的异常处理器优先级最高
resolvers.add(0, new HandlerExceptionResolver() {
@Override
public ModelAndView resolveException(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
return null; // 返回null让@ControllerAdvice处理
}
});
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加全局拦截器示例
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/error");
}
}
5. 使用示例
5.1 Controller 示例
java
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
@GetMapping("/{id}")
public ApiResponse<User> getUser(@PathVariable @Min(1) Long id) {
// 模拟业务异常
if (id > 100) {
throw new BusinessException(ErrorCode.USER_NOT_EXIST);
}
return ApiResponse.success(new User(id, "张三"));
}
@PostMapping
public ApiResponse<User> createUser(@Valid @RequestBody UserCreateRequest request) {
// 参数校验由 @Valid 自动处理
return ApiResponse.success(new User(1L, request.getName()));
}
@GetMapping("/test")
public ApiResponse<String> testValidation(
@RequestParam @NotBlank String name,
@RequestParam @Min(1) @Max(100) Integer age) {
return ApiResponse.success("验证通过");
}
@GetMapping("/error")
public ApiResponse<String> testError() {
// 测试系统异常
throw new RuntimeException("测试系统异常");
}
}
5.2 DTO 类(使用参数校验)
java
@Data
public class UserCreateRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度必须在2-20之间")
private String name;
@NotNull(message = "年龄不能为空")
@Min(value = 1, message = "年龄不能小于1")
@Max(value = 150, message = "年龄不能大于150")
private Integer age;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}
5.3 业务层异常使用
java
@Service
public class UserService {
public User getUserById(Long id) {
User user = userRepository.findById(id);
if (user == null) {
throw new BusinessException(ErrorCode.USER_NOT_EXIST,
"用户ID: " + id + " 不存在");
}
// 其他业务校验
if (user.isDisabled()) {
throw new BusinessException(1005, "用户已被禁用");
}
return user;
}
public void createUser(UserCreateRequest request) {
// 检查用户是否存在
if (userRepository.existsByName(request.getName())) {
Map<String, String> errors = new HashMap<>();
errors.put("name", "用户名已存在");
throw new ValidationException(errors);
}
// 保存用户
userRepository.save(request);
}
}
6. 测试
6.1 测试Controller
java
@SpringBootTest
@AutoConfigureMockMvc
class GlobalExceptionHandlerTest {
@Autowired
private MockMvc mockMvc;
@Test
void testBusinessException() throws Exception {
mockMvc.perform(get("/api/users/101"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(1001))
.andExpect(jsonPath("$.message").value("用户不存在"));
}
@Test
void testValidationException() throws Exception {
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(400))
.andExpect(jsonPath("$.data.errors.name").exists());
}
}
7. 高级配置
7.1 自定义错误页面(可选)
yaml
# application.yml
server:
error:
whitelabel:
enabled: false
path: /error
7.2 日志配置
yaml
logging:
level:
com.example.exception: DEBUG
file:
name: logs/app.log
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
8. 最佳实践建议
- 异常分类清晰:业务异常、系统异常、参数异常等要区分清楚
- 错误信息友好:给用户的错误信息要友好,技术细节只在日志中记录
- 日志记录完整:异常发生时记录完整的堆栈信息
- 错误码统一:定义统一的错误码规范
- 安全考虑:生产环境不要返回敏感信息和堆栈信息
- 性能考虑:异常处理不要影响正常请求性能
这个全局异常处理器提供了:
- ✅ 统一响应格式
- ✅ 业务异常处理
- ✅ 参数校验异常处理
- ✅ 系统异常处理
- ✅ HTTP状态码映射
- ✅ 日志记录
- ✅ 可扩展性
你可以根据实际业务需求进行调整和扩展。