欢迎光临
我们一直在努力

Spring Boot异常处理与跨域实战

🧩 一、统一异常处理:让错误有“标准答案”

❓ 为什么需要?

  • 如果不处理,异常会直接抛给前端,返回 HTML 错误页或裸露的堆栈信息(不安全、不友好)。
  • 每个 Controller 都写 try-catch 重复且难以维护。

✅ 核心机制:@ControllerAdvice + @ExceptionHandler

在当前开发中本人倾向于使用@RestControllerAdvice与@Exception的搭配

1. 定义统一返回格式

// Result.java
@Data
@AllArgsConstructor
public class Result<T> {
private int code;
private String msg;
private T data;

public static <T> Result<T> success(T data) {
return new Result<>(200, "success", data);
}

public static <T> Result<T> error(int code, String msg) {
return new Result<>(code, msg, null);
}
}

2. 自定义业务异常(可选但推荐)

通过继承并实现某个类型的异常类拦截业务异常

// BizException.java
public class BizException extends RuntimeException {
private final int code;
public BizException(int code, String message) {
super(message);
this.code = code;
}
// getter…
}

3. 全局异常处理器

拦截所有的异常

@RestControllerAdvice // = @ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {

// 捕获自定义业务异常
@ExceptionHandler(BizException.class)
public ResponseEntity<Result<?>> handleBiz(BizException e) {
return ResponseEntity.badRequest()
.body(Result.error(e.getCode(), e.getMessage()));
}

// 捕获参数校验异常(如 @Valid 失败)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Result<?>> handleValidation(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldError().getDefaultMessage();
return ResponseEntity.badRequest().body(Result.error(400, msg));
}

// 兜底:捕获所有未处理的异常(如 NullPointerException)
@ExceptionHandler(Exception.class)
public ResponseEntity<Result<?>> handleGeneral(Exception e) {
// 生产环境不要暴露具体异常信息!
return ResponseEntity.status(500).body(Result.error(500, "服务器内部错误"));
}
}

💡 关键点:

  • @RestControllerAdvice 作用于 所有 Controller。
  • 异常处理方法按 异常类型精确匹配,找不到则走兜底。
  • 生产环境务必隐藏真实异常堆栈,防止信息泄露。

🔒 二、Filter vs lnterceptor

特性过滤器(Filter)拦截器(Interceptor)
所属规范 Java Servlet 规范 Spring MVC 框架
作用范围 所有请求(包括静态资源 /static/、/favicon.ico) 仅 Spring MVC 的 Controller 请求
能否注入 Spring Bean ❌ 不能(非 Spring 管理) ✅ 可以(是 Spring Bean)
执行时机 在 DispatcherServlet 之前/之后 在 Controller 方法 调用前后(preHandle / postHandle / afterCompletion)
典型用途 设置字符编码、CORS 预检、请求日志、安全头 登录鉴权(Token 校验)、权限控制、接口耗时统计

✅ 使用建议:

  • 协议层/底层处理 → 用 Filter (如:强制 UTF-8 编码、全局 CORS 响应头)
  • 业务逻辑层处理 → 用 Interceptor (如:检查用户是否登录、是否有权限访问)
拦截器示例(登录校验):

在项目开发中,通常会使用这个拦截器模版,拦截除登录/注册意外的所有路径,获取token(Jwt令牌)后携带着进行访问

@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization");
if (token == null || !isValid(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false; // 拦截,不放行
}
return true; // 放行
}
}

// 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/api/**") // 拦截哪些路径
.excludePathPatterns("/api/login"); // 排除哪些路径
}
}


🌐 三、CORS

❓ 什么是跨域?

浏览器出于安全考虑,禁止前端(如 http://localhost:3000)向不同源(协议/域名/端口任一不同)的后端(如 http://localhost:8080)发起 AJAX 请求。

✅ 解决方案

@CrossOrigin(开发调试用)。但是需在每个 Controller 上加,不适合生产。

@RestController
@CrossOrigin(origins = "http://localhost:3000")
public class UserController {
// 此 Controller 下所有接口都允许来自 localhost:3000 的跨域请求
}

全局配置

@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 匹配的路径
.allowedOrigins("http://localhost:3000", "https://yourdomain.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true); // 允许携带 Cookie
}
}

⚠️ 重要注意事项:

  • allowCredentials(true) 时,allowedOrigins 不能为 "\\*",必须指定具体域名。
  • 更优方案:Nginx 反向代理 将前端和后端部署在同一域名下(如 nginx 代理 /api 到后端),彻底规避跨域问题,更安全高效。

📤📥 四、文件上传与下载

1. 文件上传(MultipartFile)

后端代码:

@PostMapping("/upload")
public Result<String> upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
throw new IllegalArgumentException("文件为空");
}
try {
String filename = UUID.randomUUID() + "_" + file.getOriginalFilename();
Path path = Paths.get("/tmp/uploads/" + filename);
Files.createDirectories(path.getParent()); // 确保目录存在
file.transferTo(path.toFile());
return Result.success(filename);
} catch (IOException e) {
throw new RuntimeException("上传失败", e);
}
}

前端要求:
  • 表单 enctype="multipart/form-data"
  • 使用 FormData 对象提交(Axios/Vanilla JS)
配置最大文件大小(application.yml):

spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB

2. 文件下载

@GetMapping("/download/{filename}")
public void download(@PathVariable String filename, HttpServletResponse response) throws IOException {
Path file = Paths.get("/tmp/uploads/" + filename);
if (!Files.exists(file)) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在");
return;
}

// 设置响应头
response.setContentType("application/octet-stream"); // 二进制流
response.setHeader("Content-Disposition",
"attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));

// 写入响应流
try (InputStream is = Files.newInputStream(file);
OutputStream os = response.getOutputStream()) {
is.transferTo(os);
}
}

💡 关键点:

  • 上传:用 MultipartFile,注意空文件校验和存储路径安全。
  • 下载:设置 Content-Type 和 Content-Disposition,用 Files.copy() 或 transferTo() 高效传输。
  • 路径安全:避免用户通过 filename 参数遍历服务器文件(如 ../../../etc/passwd),应对文件名做白名单或 UUID 映射。

✅ 总结:新手复盘 Checklist

模块你是否能回答?
统一异常 能否写出一个全局异常处理器?知道如何区分业务异常和系统异常?
Filter vs Interceptor 能否说出两者区别?知道 Token 校验该用哪个?
跨域 能否配置全局 CORS?知道 allowCredentials 的限制?
文件操作 能否写出上传/下载接口?知道如何防路径遍历?

🌟 记住:这些不是“魔法”,而是 Spring Boot 对 Servlet 规范和 Spring MVC 的封装与增强。理解其背后的 HTTP 协议、请求生命周期,才能真正掌握。

赞(0)
未经允许不得转载:171主机测评 » Spring Boot异常处理与跨域实战
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址