ludc
2024-12-04 e405b861b9521f5ea38c5402203a5b05988f9de2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
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 boolean enabled;
 
    /**
     * 外部文件夹的映射地址
     */
    private Map<String,String> resourceFolderMap ;
 
    /**
     * 不校验安全的链接地址
     */
    private List<String> unCheckUrls;
 
    /**
     * 不更新请求时间的链接地址
     */
    private List<String> 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 boolean isEnabled() {
        return enabled;
    }
 
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }
 
    public Map<String, String> getResourceFolderMap() {
        return resourceFolderMap;
    }
 
    public void setResourceFolderMap(Map<String, String> resourceFolderMap) {
        this.resourceFolderMap = resourceFolderMap;
    }
 
    /**
     * 安全相关的拦截器
     * @return 安全相关的拦截器
     */
    @Bean
    public VciSecurityInterceptor vciSecurityInterceptor(){
        return new VciSecurityInterceptor();
    }
 
    /**
     * 增加PUT和DELETE的支持
     * @return 过滤器
     */
    @Bean
    public FormContentFilter formContentFilter() {
        return new FormContentFilter();
    }
 
    /**
     * 使用fastjson返回值
     * @param converters 所有的消息转换器
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> 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<MediaType> 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 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<String> getUnCheckUrls() {
        return unCheckUrls;
    }
 
    public void setUnCheckUrls(List<String> unCheckUrls) {
        this.unCheckUrls = unCheckUrls;
    }
 
    public List<String> getUnStorageRequestTimeUrls() {
        return unStorageRequestTimeUrls;
    }
 
    public void setUnStorageRequestTimeUrls(List<String> unStorageRequestTimeUrls) {
        this.unStorageRequestTimeUrls = unStorageRequestTimeUrls;
    }
 
    public String[] getDEFAULT_EXPOSEDHEADERS() {
        return DEFAULT_EXPOSEDHEADERS;
    }
 
    public void setDEFAULT_EXPOSEDHEADERS(String[] DEFAULT_EXPOSEDHEADERS) {
        this.DEFAULT_EXPOSEDHEADERS = DEFAULT_EXPOSEDHEADERS;
    }
}