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

❓ 为什么需要?

  • 如果不处理,异常会直接抛给前端,返回 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-TypeContent-Disposition,用 Files.copy()transferTo() 高效传输。
  • 路径安全:避免用户通过 filename 参数遍历服务器文件(如 ../../../etc/passwd),应对文件名做白名单或 UUID 映射。

✅ 总结:新手复盘 Checklist

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

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

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐