WebMvcConfigurer 是一个接口,用于配置全局的SpringMVC的相关属性,采用JAVABean的方式来代替传统的XML配置文件,提供了跨域设置、静态资源处理器、类型转化器、自定义拦截器、页面跳转等能力。
WebMvcConfigurationSupport是webmvc的配置类,如果在springboot项目中,有配置类继承了WebMvcConfigurationSupport,那么webmvc的自动配置类WebMvcAutoConfiguration就会失效。
官方推荐直接实现WebMvcConfigurer 或者 继承WebMvcConfigurationSupport,
方式一:实现WebMvcConfigurer接口(推荐),方式二:继承WebMvcConfigurationSupport(/səˈpɔːt/)类。
package com.syh.pdd.config.web;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.*;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 用户头像
@Value("${file.userImage.writePath}")
private String userImageWritePath;
@Value("${file.userImage.readPath}")
private String userImageReadPath;
/**
* 映射文件路径配置
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
registry.addResourceHandler("BJTPReadpath")
.addResourceLocations("file:"+ "BJTPSavepath");
// 图片回显路径,“/**”是当前文件夹以及子文件夹
registry.addResourceHandler(userImageReadPath)
// 图片存放路径
.addResourceLocations("file:" + userImageWritePath);
}
/**
* 跨域配置添加
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
// 设置允许跨域请求的域名
// .allowedOrigins("*")
.allowedOriginPatterns("*")
// 是否允许证书 不再默认开启
.allowCredentials(true)
// 设置允许的方法
.allowedMethods("*")
// 跨域允许时间
.maxAge(3600);
}
//解决中文乱码问题
@Override
public void configureMessageConverters(List> converters) {
//解决中文乱码
converters.add(responseBodyConverter());
//解决 添加解决中文乱码后 上述配置之后,返回json数据直接报错 500:no convertter for return value of type
converters.add(messageConverter());
}
@Bean
public HttpMessageConverter responseBodyConverter(){
StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
return converter;
}
@Bean
public MappingJackson2HttpMessageConverter messageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(getObjectMapper());
return converter;
}
@Bean
public ObjectMapper getObjectMapper() {
return new ObjectMapper();
}
/**
* 格式化返回的内容
* https://my.oschina.net/u/3681868/blog/3075150
* */
@Override
public void extendMessageConverters(List> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = converter.getObjectMapper();
// 时间格式化
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 设置格式化内容
converter.setObjectMapper(objectMapper);
converters.add(0, converter);
}
/**
* 添加Web项目的拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//放行路径
List jwtExcludePatterns = new ArrayList();
// 登录接口放行
jwtExcludePatterns.add("/api/user/login");
// 验证码放行
jwtExcludePatterns.add("/api/user/getVerify/**");
// 前端更换头像请求,没有走拦截器,此处放行
jwtExcludePatterns.add("/api/user/updatePicture");
// 对所有api开头的访问路径,都通过MyInterceptor拦截器进行拦截,MyInterceptor代码在下面
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/api/**")
.excludePathPatterns(jwtExcludePatterns);
}
/*解析器 使用方法在文章末尾*/
@Override
public void addArgumentResolvers(List resolvers) {
WebMvcConfigurer.super.addArgumentResolvers(resolvers);
}
}
什么是拦截器:在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略
为什么需要拦截器:在做身份认证或者是进行日志的记录时,我们需要通过拦截器达到我们的目的。最常用的登录拦截、或是权限校验、或是防重复提交、或是根据业务像12306去校验购票时间,总之可以去做很多的事情
net.minidev
json-smart
com.vaadin.external.google
android-json
0.0.20131108.vaadin1
compile
package com.syh.pdd.config.web;
import com.syh.pdd.Utils.token.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 自定义拦截器类token认证
* 注意:可以实现多个拦截器,只需要继续实现HandlerInterceptor即可
*/
@Slf4j
public class MyInterceptor implements HandlerInterceptor {
/**
* 访问控制器方法前执行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
// ==========!!!!!!注意注意注意 注意注意 注意注意 ========================
/*
* 前端在请求的时候会发送一个OPTION请求来验证本次请求是否安全,
* 但是springboot的拦截器会拦截所有请求。因为第一次是OPTION没有携带JWT,所以验证失败
* */
if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
return true;
}
// 获取token
String token = request.getHeader("token");
// 校验token
if (JwtUtil.checkToken(token)) {
log.info(request.getRequestURL() + ">>>" +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "通过token验证");
return true; // 放行
} else{
//设置response状态
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
//设置失败响应数据
JSONObject res = new JSONObject();
res.put("status","101010");
res.put("msg","登录过期,请重新登录");
PrintWriter out = null ;
out = response.getWriter();
out.write(res.toString());
out.flush();
out.close();
return false; // 拦截
}
}
/**
* 访问控制器方法后执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
}
/**
* postHandle方法执行完成后执行,一般用于释放资源
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
}
}
/**
* Web配置类
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 添加Web项目的拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//放行路径
List jwtExcludePatterns = new ArrayList();
// 登录接口放行
jwtExcludePatterns.add("/system/user/login");
// 验证码放行
jwtExcludePatterns.add("/system/user/getVerify/**");
// 对所有图片资源放行
jwtExcludePatterns.add("/Project/saveFile/**");
// 拦截路径:对所有访问路径,都通过MyInterceptor类型的拦截器进行拦截
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**")
.excludePathPatterns(jwtExcludePatterns);
}
}
package com.hssmart.config.web;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.List;
@Configuration
public class WebConfig extends WebMvcConfigurationSupport{
// 自定义一个拦截器
@Autowired
UserArgumentResolver userArgumentResolver;
/**
* 映射文件路径配置
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
/**
* 说明:增加虚拟路径(经过本人测试:在此处配置的虚拟路径,用springboot内置的tomcat时有效,
用外部的tomcat也有效;所以用到外部的tomcat时不需在tomcat/config下的相应文件配置虚拟路径了,阿里云linux也没问题)
*/
//registry.addResourceHandler("/pic/**").addResourceLocations("file:E:/pic/");
registry.addResourceHandler("BJTPReadpath")
.addResourceLocations("file:"+ "BJTPSavepath");
//阿里云(映射路径去除盘符)
//registry.addResourceHandler("/ueditor/image/**").addResourceLocations("/upload/image/");
//registry.addResourceHandler("/ueditor/video/**").addResourceLocations("/upload/video/");
//用户图片路径
registry.addResourceHandler("/Project/saveFile/user/userImg/**"(网络路径,其实可以任意定义))
.addResourceLocations("file:D:/JAVA/Project/saveFile/user/userImg/"(储存路径));
super.addResourceHandlers(registry);
}
/**
* 跨域配置添加
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOrigins("*")
// 是否允许证书 不再默认开启
.allowCredentials(true)
// 设置允许的方法 springboot较高版本不能使用*号
// .allowedMethods("GET", "POST", "PUT", "OPTIONS", "DELETE", "PATCH")
.allowedMethods("*")
// 跨域允许时间
.maxAge(3600);
}
//解决中文乱码问题
@Override
public void configureMessageConverters(List> converters) {
super.configureMessageConverters(converters);
//解决中文乱码
converters.add(responseBodyConverter());
//解决 添加解决中文乱码后 上述配置之后,返回json数据直接报错 500:no convertter for return value of type
converters.add(messageConverter());
}
@Bean
public HttpMessageConverter responseBodyConverter(){
StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
return converter;
}
@Bean
public MappingJackson2HttpMessageConverter messageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(getObjectMapper());
return converter;
}
@Bean
public ObjectMapper getObjectMapper() {
return new ObjectMapper();
}
/**
* 格式化返回的内容(格式转换器)
* https://my.oschina.net/u/3681868/blog/3075150
* */
@Override
public void extendMessageConverters(List> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = converter.getObjectMapper();
// 生成JSON时,将所有Long转换成String
//SimpleModule simpleModule = new SimpleModule();
//simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
//simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
//objectMapper.registerModule(simpleModule);
// 时间格式化
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 设置格式化内容
converter.setObjectMapper(objectMapper);
converters.add(0, converter);
}
/**
* 解析器,该方法可实现可不实现,需要自定义。作用在调用Controller方法的参数传入之前,有返回值
*/
@Override
public void addArgumentResolvers(List resolvers) {
// userArgumentResolver该类对Controller传入的参数做了具体处理
resolvers.add(userArgumentResolver);
}
}
(1)自定义解析器
@Configuration
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
Userservice userservice;
// 此方法返回true,下面的参数才会执行
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
// 获取传入参数的类型
Class> type = methodParameter.getParameterType();
// 如果参数类型有为User类的则符合,进入resolveArgument方法
if (UserPojo.class == type) {
return true;
}
return false;
}
// 该方法为拦截方法,将结果返回给controller
@Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class);
String userTick = CookieUtil.getCookieValue(request, "userTicket");
if (StringUtils.isBlank(userTick)) {
return null;
}
UserPojo userPojo = userservice.getUserByCookie(userTick, request, response);
if (userPojo == null) {
return null;
}
return userPojo;
}
}
(2)在controller中使用
@GetMapping("goods")
public Result showGoods(UserPojo user){
// 注意:这里的User参数不是由前端传入的,而是由addArgumentResolvers方法处理之后传进来的
log.info(user.toString());
// 根据处理之后传入的参数判断是否登录
if (user == null)
return Result.error();
return Result.ok();
}