Web核心开发:拦截器、过滤器、跨域、统一返回

📅 2026/7/30 2:14:26
Web核心开发:拦截器、过滤器、跨域、统一返回
Web核心开发拦截器、过滤器、跨域、统一返回后端接口写多了就知道业务逻辑只占三成剩下七成是防呆设计——拦截没登录的、处理跨域的、统一返回格式的。这篇把Web层的基础设施一次搭好。一、SpringBoot Web开发全景1.1 DispatcherServlet流程所有请求进入SpringBoot第一站是DispatcherServlet前端控制器核心流程如下客户端请求 → DispatcherServlet → HandlerMapping找到对应的Controller方法 → HandlerInterceptor拦截器 preHandle → Controller执行业务逻辑 → HandlerInterceptor拦截器 postHandle → 视图渲染/结果处理 → HandlerInterceptor拦截器 afterCompletion → 返回响应注意过滤器Filter在DispatcherServlet之前就执行了这是拦截器和过滤器的关键区别。二、拦截器(Interceptor)2.1 HandlerInterceptor接口拦截器是Spring MVC提供的机制核心接口publicinterfaceHandlerInterceptor{// Controller执行前返回false则中断请求booleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler);// Controller执行后视图渲染前voidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView);// 整个请求完成后视图渲染后通常用于资源清理voidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex);}三个阶段的执行时机和用途方法执行时机典型用途preHandleController前登录校验、权限检查、日志记录postHandleController后统一修改返回数据afterCompletion请求结束资源清理、异常处理2.2 登录拦截器实战ComponentpublicclassLoginInterceptorimplementsHandlerInterceptor{OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{// 从请求头获取tokenStringtokenrequest.getHeader(Authorization);if(StringUtils.isBlank(token)){response.setStatus(401);response.setContentType(application/json;charsetUTF-8);response.getWriter().write({\code\:401,\message\:\未登录\});returnfalse;}// 校验token逻辑...returntrue;// 放行}}2.3 注册拦截器光写了拦截器不够得注册到Spring MVC里才能生效ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{AutowiredprivateLoginInterceptorloginInterceptor;OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(loginInterceptor).addPathPatterns(/api/**)// 拦截路径.excludePathPatterns(/api/login,// 排除登录接口/api/register,/api/captcha);// 排除验证码接口}}三、过滤器(Filter)3.1 Filter接口过滤器是Servlet规范中的机制在请求到达Spring MVC之前就执行ComponentWebFilter(urlPatterns/*,filterNamerequestLogFilter)publicclassRequestLogFilterimplementsFilter{OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{HttpServletRequestreq(HttpServletRequest)request;longstartSystem.currentTimeMillis();log.info(请求开始: {} {},req.getMethod(),req.getRequestURI());chain.doFilter(request,response);// 放行到下一个过滤器或目标资源longcostSystem.currentTimeMillis()-start;log.info(请求结束, 耗时: {}ms,cost);}}3.2 过滤链执行顺序多个Filter组成一条链按顺序执行。可以用Order注解控制顺序ComponentOrder(1)// 数字越小越先执行publicclassFirstFilterimplementsFilter{...}ComponentOrder(2)publicclassSecondFilterimplementsFilter{...}四、拦截器 vs 过滤器对比对比项过滤器(Filter)拦截器(Interceptor)所属规范Servlet规范Spring MVC执行时机DispatcherServlet前DispatcherServlet内能否访问Spring Bean需额外处理直接注入能否获取Handler信息不能能知道哪个Controller方法典型场景编码设置、日志、安全过滤登录校验、权限控制简单记过滤器管请求进出拦截器管Controller前后。五、跨域问题解决5.1 什么是跨域浏览器同源策略协议、域名、端口任一不同就是跨域。前端localhost:5173调后端localhost:8080就跨域了浏览器会拦截响应。5.2 三种解决方案方案一CrossOrigin注解粒度最细单个接口RestControllerRequestMapping(/api)CrossOrigin(originshttp://localhost:5173)publicclassApiController{...}方案二WebMvcConfigurer全局配置ConfigurationpublicclassCorsConfigimplementsWebMvcConfigurer{OverridepublicvoidaddCorsMappings(CorsRegistryregistry){registry.addMapping(/api/**).allowedOrigins(http://localhost:5173).allowedMethods(GET,POST,PUT,DELETE,OPTIONS).allowedHeaders(*).allowCredentials(true)// 允许携带Cookie.maxAge(3600);// 预检缓存时间秒}}方案三CorsFilter全局过滤器推荐优先级最高BeanpublicCorsFiltercorsFilter(){CorsConfigurationconfignewCorsConfiguration();config.addAllowedOrigin(http://localhost:5173);config.addAllowedMethod(*);config.addAllowedHeader(*);config.setAllowCredentials(true);UrlBasedCorsConfigurationSourcesourcenewUrlBasedCorsConfigurationSource();source.registerCorsConfiguration(/**,config);returnnewCorsFilter(source);}5.3 跨域常见坑点坑一OPTIONS预检请求被拦截非简单请求如POSTJSON浏览器会先发OPTIONS请求探路。如果登录拦截器把OPTIONS请求拦了前端直接报跨域错误。解决拦截器中放行OPTIONSOverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){if(OPTIONS.equals(request.getMethod())){returntrue;// 预检请求直接放行}// 正常校验逻辑...}坑二Cookie跨域跨域携带Cookie需要同时满足后端设置allowCredentials(true)allowedOrigins不能用*必须指定具体域名前端请求设置withCredentials: true六、统一返回封装6.1 Result类设计别让每个接口返回格式都不一样统一规范DataAllArgsConstructorNoArgsConstructorpublicclassResultT{privateintcode;privateStringmessage;privateTdata;publicstaticTResultTsuccess(Tdata){returnnewResult(200,操作成功,data);}publicstaticTResultTerror(intcode,Stringmessage){returnnewResult(code,message,null);}}6.2 ResponseAdvice全局统一返回用ResponseBodyAdvice实现全局自动包装Controller里直接返回业务数据框架自动包成ResultRestControllerAdvicepublicclassGlobalResponseAdviceimplementsResponseBodyAdviceObject{Overridepublicbooleansupports(MethodParameterreturnType,ClassconverterType){// 排除Result类型本身和String类型避免序列化问题return!returnType.getParameterType().equals(Result.class)!returnType.getParameterType().equals(String.class);}OverridepublicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,MediaTypeselectedContentType,ClassselectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){returnResult.success(body);}}Controller代码变得极简RestControllerRequestMapping(/api/user)publicclassUserController{GetMapping(/{id})publicUsergetUser(PathVariableLongid){returnuserService.getById(id);// 直接返回User框架自动包装成Result}}返回结果统一是{code:200,message:操作成功,data:{id:1,name:张三}}6.3 String返回的坑ResponseBodyAdvice处理String返回值时会有序列化问题。因为Spring MVC对String类型默认用StringHttpMessageConverter而返回Result对象后会报类型转换异常。解决方案给String返回值单独处理手动转成JSONOverridepublicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,MediaTypeselectedContentType,ClassselectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){if(bodyinstanceofString){response.getHeaders().setContentType(MediaType.APPLICATION_JSON);returnJSONUtil.toJsonStr(Result.success(body));}returnResult.success(body);}这样全局统一返回就稳了接口层只管业务逻辑返回格式交给框架兜底。