package com.vci.starter.web.autoconfigure; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import com.vci.starter.web.interceptor.VciLocaleInterceptor; import com.vci.starter.web.interceptor.VciLogAfterInterceptor; import com.vci.starter.web.interceptor.VciLogBeforeInterceptor; import com.vci.starter.web.interceptor.VciSecurityInterceptor; import com.vci.starter.web.properties.CorsProperties; import com.vci.starter.web.toolmodel.String2DateConverterForSpringMvc; import com.vci.starter.web.util.LocalFileUtil; import com.vci.starter.web.util.VciDateUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.http.CacheControl; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.filter.FormContentFilter; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.*; import org.springframework.web.servlet.i18n.SessionLocaleResolver; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; /** * spring mvc的相关配置 * * @author weidy */ @Configuration @EnableWebMvc @ConditionalOnProperty(prefix = "vcispringmvc",name="enabled",havingValue = "true",matchIfMissing = true) @ConfigurationProperties(prefix = "vcispringmvc") public class SpringMVCConfig implements WebMvcConfigurer { /** * 日志对象 */ private Logger log = LoggerFactory.getLogger(SpringMVCConfig.class); /** * 外部文件夹的映射地址 */ private Map resourceFolderMap ; /** * 不校验安全的链接地址 */ private List unCheckUrls; /** * 不更新请求时间的链接地址 */ private List unStorageRequestTimeUrls; /** * 跨域的配置 */ @Autowired(required = false) private CorsProperties corsProperties; /** * 默认允许的域名 */ private String[] DEFAULT_ORIGINS = {"*"}; /** * 默认允许的头 */ private String[] DEFAULT_ALLOWED_HEADERS = {"*"}; /** * 默认允许的方法 */ private String[] DEFAULT_METHODS = {"PUT", "DELETE","GET","POST"}; /** * 默认暴露的头 */ private String[] DEFAULT_EXPOSEDHEADERS = {"access-control-allow-headers", "access-control-allow-methods", "access-control-allow-origin", "access-control-max-age", "X-Frame-Options"}; /** * 默认是否允许证书 */ private boolean DEFAULT_ALLOW_CREDENTIALS = true; /** * 默认的最大值 */ private long DEFAULT_MAX_AGE = 1800; public Map getResourceFolderMap() { return resourceFolderMap; } public void setResourceFolderMap(Map resourceFolderMap) { this.resourceFolderMap = resourceFolderMap; } /** * 增加PUT和DELETE的支持 * @return 过滤器 */ @Bean public FormContentFilter formContentFilter() { return new FormContentFilter(); } /** * 使用fastjson返回值 * @param converters 所有的消息转换器 */ @Override public void configureMessageConverters(List> converters){ converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8)); converters.add(createFastJsonConverter()); } /** * 获取fastjson的转换器,即springmvc和fegin都使用fastjson来序列化 * @return fastjson转换器对象,日期格式都是yyyy-MM-dd HH:mm:ss.SSS */ public static HttpMessageConverter createFastJsonConverter(){ FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); List supportedMediaTypes = new ArrayList<>(); supportedMediaTypes.add(MediaType.APPLICATION_JSON); supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML); supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED); supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM); supportedMediaTypes.add(MediaType.APPLICATION_PDF); supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML); supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML); supportedMediaTypes.add(MediaType.APPLICATION_XML); supportedMediaTypes.add(MediaType.IMAGE_GIF); supportedMediaTypes.add(MediaType.IMAGE_JPEG); supportedMediaTypes.add(MediaType.IMAGE_PNG); supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM); supportedMediaTypes.add(MediaType.TEXT_HTML); supportedMediaTypes.add(MediaType.TEXT_MARKDOWN); //supportedMediaTypes.add(MediaType.TEXT_PLAIN); supportedMediaTypes.add(MediaType.TEXT_XML); fastConverter.setSupportedMediaTypes(supportedMediaTypes); //创建配置类 FastJsonConfig fastJsonConfig = new FastJsonConfig(); //修改配置返回内容的过滤 //WriteNullListAsEmpty :List字段如果为null,输出为[],而非null //WriteNullStringAsEmpty : 字符类型字段如果为null,输出为"",而非null //DisableCircularReferenceDetect :消除对同一对象循环引用的问题,默认为false(如果不配置有可能会进入死循环) //WriteNullBooleanAsFalse:Boolean字段如果为null,输出为false,而非null //WriteMapNullValue:是否输出值为null的字段,默认为false fastJsonConfig.setSerializerFeatures( SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue ); fastJsonConfig.setDateFormat(VciDateUtil.DateTimeMillFormat); fastConverter.setFastJsonConfig(fastJsonConfig); return fastConverter; } /** * 设置格式转换器,在接收到参数封装到对象时使用 * @param registry 格式注册器 */ @Override public void addFormatters(FormatterRegistry registry) { //添加日期的转换器 registry.addConverter(new String2DateConverterForSpringMvc()); //registry.addConverter(new StringEscapeEditor()); } /** * 添加需要转换的路径,常用于读取tomcat外部的路径 * @param registry 资源转换注册器 */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry){ if(resourceFolderMap!=null){ for(String resourceUrl :resourceFolderMap.keySet() ){ String folder = resourceFolderMap.get(resourceUrl); if(folder.contains("$[projectPath]")){ folder = folder.replace("$[projectPath]", LocalFileUtil.getProjectFolder(LocalFileUtil.mainClass)); } log.info("注册了外部路径转换器, " + resourceUrl + ":" + folder); registry.addResourceHandler("/" + resourceUrl + "/**").addResourceLocations("file:" + folder + File.separator); } } String logs = "file:" + LocalFileUtil.getProjectFolder(LocalFileUtil.mainClass) + File.separator + "logs" +File.separator; log.info("日志的文件夹映射为" + logs); registry.addResourceHandler("/log/**").addResourceLocations( logs); registry.addResourceHandler("/doc/**").addResourceLocations( "classpath:/md/"); registry.addResourceHandler("/**") .addResourceLocations("classpath:/META-INF/resources/") .addResourceLocations("classpath:/resources/") .addResourceLocations("classpath:/static/") .addResourceLocations("classpath:/html/") .addResourceLocations("classpath:/public/").setCacheControl(CacheControl.noStore()); } /** * 配置index * @param registry 注册器 */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("forward:index.html"); } /** * 设置默认的语言为简体中文 * @return 默认语言解析器 */ @Bean public LocaleResolver localeResolver(){ SessionLocaleResolver localeResolver = new SessionLocaleResolver(); localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE); return localeResolver; } /** * 配置拦截器 * @param registry 拦截器注册器 */ @Override public void addInterceptors(InterceptorRegistry registry){ /* preHandle按拦截器定义顺序地调用 postHandle按拦截器定义逆序地调用 afterCompletion按拦截器定义逆序地调用 postHandle在截器链内所有拦截器返回成功调用 afterCompletion只有在preHandle返回true才调用*/ //1. 需要配置多语的拦截器,配置多语环境 //2. 需要配置token的拦截器,这个必须在权限拦截器之前,里面包括用户的session信息,日志的 //3. 配置权限的拦截器,如果不符合要求则返回异常。需要处理系统访问的权限,接口访问的权限,数据访问的权限三种类型 //4. 更新用户请求的最后时间 //添加日志的MDC registry.addInterceptor(vciLogBeforeInterceptor()); //多语言 registry.addInterceptor(vciLocaleInterceptor()); registry.addInterceptor(vciSecurityInterceptor()).excludePathPatterns("/error").excludePathPatterns("/**.*"); //移除日志的MDC registry.addInterceptor(vciLogAfterInterceptor()); } /** * 多语言相关的拦截器 * @return 多语言拦截器 */ @Bean public VciLocaleInterceptor vciLocaleInterceptor(){ return new VciLocaleInterceptor(); } /** * 安全相关的拦截器 * @return 安全相关的拦截器 */ @Bean public VciSecurityInterceptor vciSecurityInterceptor(){ return new VciSecurityInterceptor(); } /** * 日志相关的拦截器 * @return 日志拦截器 */ @Bean public VciLogBeforeInterceptor vciLogBeforeInterceptor(){ return new VciLogBeforeInterceptor(); } /** * 日志相关的拦截器 * @return 日志拦截器 */ @Bean public VciLogAfterInterceptor vciLogAfterInterceptor(){ return new VciLogAfterInterceptor(); } /** * 设置跨域 * @param registry 跨域注册器 */ @Override public void addCorsMappings(CorsRegistry registry) { log.info("进入跨域配置"); if(corsProperties !=null && "true".equalsIgnoreCase(corsProperties.getEnabled())) { log.info("打开了跨域开关"); String[] allowedOrigins = corsProperties.getAllowedOrigins(); String[] allowedHeaders = corsProperties.getAllowedHeaders(); String[] allowedMethods = corsProperties.getAllowedMethods(); String[] exposedHeaders = corsProperties.getExposedHeaders(); Boolean allowCredentials = corsProperties.getAllowCredentials(); Long maxAge = corsProperties.getMaxAge(); log.info("注册跨域内容 = [" + allowedOrigins + "]"); String mappings = corsProperties.getMappings(); if (allowedHeaders == null || allowedHeaders.length == 0) { allowedHeaders = DEFAULT_ALLOWED_HEADERS; } if (allowedOrigins == null || allowedOrigins.length == 0) { allowedOrigins = DEFAULT_ORIGINS; } if (exposedHeaders == null || exposedHeaders.length == 0) { exposedHeaders = DEFAULT_EXPOSEDHEADERS; } if (allowedMethods == null || allowedMethods.length == 0){ allowedMethods = DEFAULT_METHODS; } if (maxAge == null || maxAge == 0) { maxAge = DEFAULT_MAX_AGE; } if (allowCredentials == null) { allowCredentials = DEFAULT_ALLOW_CREDENTIALS; } if (mappings == null || mappings.trim() == "") { mappings = "/**"; } log.info("跨域映射的URL:" + mappings); registry.addMapping(mappings) .allowedOrigins(allowedOrigins) .allowedMethods(allowedMethods) .allowedHeaders(allowedHeaders) .exposedHeaders(exposedHeaders) .allowCredentials(allowCredentials).maxAge(maxAge); }else{ log.info("没有打开跨域开关"); } } public CorsProperties getCorsProperties() { return corsProperties; } public void setCorsProperties(CorsProperties corsProperties) { this.corsProperties = corsProperties; } public String[] getDEFAULT_ORIGINS() { return DEFAULT_ORIGINS; } public void setDEFAULT_ORIGINS(String[] DEFAULT_ORIGINS) { this.DEFAULT_ORIGINS = DEFAULT_ORIGINS; } public String[] getDEFAULT_ALLOWED_HEADERS() { return DEFAULT_ALLOWED_HEADERS; } public void setDEFAULT_ALLOWED_HEADERS(String[] DEFAULT_ALLOWED_HEADERS) { this.DEFAULT_ALLOWED_HEADERS = DEFAULT_ALLOWED_HEADERS; } public String[] getDEFAULT_METHODS() { return DEFAULT_METHODS; } public void setDEFAULT_METHODS(String[] DEFAULT_METHODS) { this.DEFAULT_METHODS = DEFAULT_METHODS; } public boolean isDEFAULT_ALLOW_CREDENTIALS() { return DEFAULT_ALLOW_CREDENTIALS; } public void setDEFAULT_ALLOW_CREDENTIALS(boolean DEFAULT_ALLOW_CREDENTIALS) { this.DEFAULT_ALLOW_CREDENTIALS = DEFAULT_ALLOW_CREDENTIALS; } public long getDEFAULT_MAX_AGE() { return DEFAULT_MAX_AGE; } public void setDEFAULT_MAX_AGE(long DEFAULT_MAX_AGE) { this.DEFAULT_MAX_AGE = DEFAULT_MAX_AGE; } public List getUnCheckUrls() { return unCheckUrls; } public void setUnCheckUrls(List unCheckUrls) { this.unCheckUrls = unCheckUrls; } public List getUnStorageRequestTimeUrls() { return unStorageRequestTimeUrls; } public void setUnStorageRequestTimeUrls(List unStorageRequestTimeUrls) { this.unStorageRequestTimeUrls = unStorageRequestTimeUrls; } }