MyWebMvcConfigurer.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. package com.txz.cif.configurer;
  2. import cn.hutool.core.util.StrUtil;
  3. import com.alibaba.fastjson.JSON;
  4. import com.alibaba.fastjson.serializer.SerializeConfig;
  5. import com.alibaba.fastjson.serializer.SerializerFeature;
  6. import com.alibaba.fastjson.serializer.ToStringSerializer;
  7. import com.alibaba.fastjson.support.config.FastJsonConfig;
  8. import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
  9. import com.txz.cif.configurer.interceptor.CorsInterceptor;
  10. import com.txz.cif.core.Result;
  11. import com.txz.cif.core.ResultCode;
  12. import com.txz.cif.core.ServiceException;
  13. import com.txz.cif.core.cache.CacheKey;
  14. import com.txz.cif.core.cache.CacheType;
  15. import org.apache.commons.codec.digest.DigestUtils;
  16. import org.apache.commons.lang3.StringUtils;
  17. import org.slf4j.Logger;
  18. import org.slf4j.LoggerFactory;
  19. import org.springframework.beans.factory.annotation.Autowired;
  20. import org.springframework.context.annotation.Bean;
  21. import org.springframework.context.annotation.Configuration;
  22. import org.springframework.data.redis.core.RedisTemplate;
  23. import org.springframework.http.MediaType;
  24. import org.springframework.http.converter.HttpMessageConverter;
  25. import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
  26. import org.springframework.web.servlet.HandlerExceptionResolver;
  27. import org.springframework.web.servlet.LocaleResolver;
  28. import org.springframework.web.servlet.ModelAndView;
  29. import org.springframework.web.servlet.NoHandlerFoundException;
  30. import org.springframework.web.servlet.config.annotation.CorsRegistry;
  31. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  32. import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
  33. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  34. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
  35. import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
  36. import org.springframework.web.servlet.i18n.SessionLocaleResolver;
  37. import javax.annotation.Resource;
  38. import javax.servlet.ServletException;
  39. import javax.servlet.http.HttpServletRequest;
  40. import javax.servlet.http.HttpServletResponse;
  41. import java.io.IOException;
  42. import java.math.BigInteger;
  43. import java.util.*;
  44. /**
  45. * Spring MVC 配置
  46. *
  47. * @author lxk
  48. */
  49. @Configuration
  50. public class MyWebMvcConfigurer implements WebMvcConfigurer {
  51. private final Logger logger = LoggerFactory.getLogger(MyWebMvcConfigurer.class);
  52. // @Value("${spring.profiles.active}")
  53. // private String env; // 当前激活的配置文件
  54. //
  55. // @Value("${signature}")
  56. // private String signature; // 是否启用签名
  57. //
  58. // @Value("${login.check}")
  59. // private String loginCheck; // 是否启用登录验证
  60. @Resource
  61. private RedisTemplate redisTemplate;
  62. // @Value("${spring.application.name}")
  63. // private String application;
  64. @Resource
  65. private Parameters parameters;
  66. @Autowired
  67. private CorsInterceptor corsInterceptor;
  68. @Override
  69. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  70. registry.addResourceHandler("swagger-ui.html")
  71. .addResourceLocations("classpath:/META-INF/resources/");
  72. registry.addResourceHandler("docs.html")
  73. .addResourceLocations("classpath:/META-INF/resources/");
  74. registry.addResourceHandler("doc.html")
  75. .addResourceLocations("classpath:/META-INF/resources/");
  76. registry.addResourceHandler("/webjars/**")
  77. .addResourceLocations("classpath:/META-INF/resources/webjars/");
  78. }
  79. // 使用阿里 FastJson 作为JSON MessageConverter
  80. @Override
  81. public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  82. for (int i = converters.size() - 1; i >= 0; i--) {
  83. if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {
  84. converters.remove(i);
  85. }
  86. }
  87. FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
  88. //自定义fastjson配置
  89. FastJsonConfig config = new FastJsonConfig();
  90. config.setDateFormat("yyyy-MM-dd HH:mm:ss");
  91. config.setSerializerFeatures(
  92. SerializerFeature.WriteMapNullValue, // 是否输出值为null的字段,默认为false,我们将它打开
  93. SerializerFeature.WriteNullListAsEmpty, // 将Collection类型字段的字段空值输出为[]
  94. SerializerFeature.WriteNullStringAsEmpty, // 将字符串类型字段的空值输出为空字符串
  95. SerializerFeature.WriteNullNumberAsZero, // 将数值类型字段的空值输出为0
  96. SerializerFeature.WriteDateUseDateFormat,
  97. SerializerFeature.DisableCircularReferenceDetect // 禁用循环引用
  98. );
  99. SerializeConfig serializeConfig = SerializeConfig.globalInstance;
  100. serializeConfig.put(BigInteger.class, ToStringSerializer.instance);
  101. // serializeConfig.put(Long.class, ToStringSerializer.instance);
  102. // serializeConfig.put(Long.TYPE, ToStringSerializer.instance);
  103. config.setSerializeConfig(serializeConfig);
  104. fastJsonHttpMessageConverter.setFastJsonConfig(config);
  105. // 添加支持的MediaTypes;不添加时默认为*/*,也就是默认支持全部
  106. // 但是MappingJackson2HttpMessageConverter里面支持的MediaTypes为application/json
  107. // 参考它的做法, fastjson也只添加application/json的MediaType
  108. List<MediaType> fastMediaTypes = new ArrayList<>();
  109. fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
  110. fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
  111. converters.add(fastJsonHttpMessageConverter);
  112. // 设置项目名称
  113. ProjectConstant.application = parameters.getApplication();
  114. }
  115. // 统一异常处理
  116. @Override
  117. public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
  118. exceptionResolvers.add(new HandlerExceptionResolver() {
  119. @Override
  120. public ModelAndView resolveException(HttpServletRequest request,
  121. HttpServletResponse response, Object handler,
  122. Exception e) {
  123. Result result = new Result();
  124. if (e instanceof ServiceException) {// 业务失败的异常,如“账号或密码错误”
  125. result.setCode(ResultCode.FAIL.getCode()).setMessage(e.getMessage());
  126. logger.info(e.getMessage());
  127. } else if (e instanceof NoHandlerFoundException) {
  128. result.setCode(ResultCode.NOT_FOUND.getCode()).setMessage(
  129. "接口 [" + request.getRequestURI() + "] 不存在");
  130. } else if (e instanceof ServletException) {
  131. result.setCode(ResultCode.FAIL.getCode()).setMessage(e.getMessage());
  132. } else {
  133. result.setCode(ResultCode.INTERNAL_SERVER_ERROR.getCode()).setMessage(
  134. "接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
  135. String args = "";
  136. String requestAccept = request.getHeader("Content-Type");
  137. String contentType = "text/html";
  138. //判断请求类型
  139. if (StringUtils.isNotEmpty(requestAccept)) {
  140. if (StringUtils.contains(requestAccept, "application/json") || StringUtils.contains(requestAccept, "text/javascript")
  141. || StringUtils.contains(requestAccept, "text/json")) {
  142. contentType = "application/json";
  143. }
  144. }
  145. StringBuilder sb = new StringBuilder();
  146. for (Map.Entry<String, String[]> stringEntry : request.getParameterMap().entrySet()) {
  147. sb.append(stringEntry.getKey()).append(Arrays.toString(stringEntry.getValue()));
  148. }
  149. args = sb.toString();
  150. StackTraceElement stackTraceElement = e.getStackTrace()[0];
  151. String fileName = stackTraceElement.getFileName();
  152. int lineNumber = stackTraceElement.getLineNumber();
  153. logger.error("接口异常 URL: {} HTTP_METHOD : {} VERSION: {} ARGS : {} token : {} file: {} lineNum: {} ERROR_MSG {} ERROR {}",
  154. request.getRequestURL().toString(), request.getMethod(), request.getHeader("version"), args, request.getHeader("token"), fileName, lineNumber, e.getMessage(), e);
  155. }
  156. String msg = LocalUtil.get(ResultCode.getEnumByStatusCode(result.getCode()).name());
  157. if (StrUtil.isNotBlank(msg) ){
  158. result.setMessage(msg);
  159. }
  160. responseResult(response, result);
  161. return new ModelAndView();
  162. }
  163. });
  164. }
  165. // 解决跨域问题
  166. // @Override
  167. // public void addCorsMappings(CorsRegistry registry) {
  168. // registry.addMapping("/**");
  169. // }
  170. // // 添加拦截器
  171. // @Override
  172. // public void addInterceptors(InterceptorRegistry registry) {
  173. // // 跨域
  174. //// registry.addInterceptor(corsInterceptor);
  175. // // wxJava框架 ThreadLocal的remove问题
  176. //
  177. // // 接口签名认证拦截器,该签名认证比较简单,实际项目中可以使用Json Web Token或其他更好的方式替代。
  178. // if ("true".equals(parameters.getSignature())) { // 开发环境忽略签名认证
  179. // registry.addInterceptor(new HandlerInterceptorAdapter() {
  180. // @Override
  181. // public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
  182. // Object handler) throws Exception {
  183. // // 验证签名
  184. // boolean pass = validateSign(request);
  185. // if (pass) {
  186. // return true;
  187. // } else {
  188. // logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}", request.getRequestURI(),
  189. // getIpAddress(request), JSON.toJSONString(request.getParameterMap()));
  190. //
  191. // Result result = new Result();
  192. // result.setCode(ResultCode.UNAUTHORIZED.getCode()).setMessage("签名认证失败");
  193. // responseResult(response, result);
  194. // return false;
  195. // }
  196. // }
  197. // });
  198. // }
  199. // }
  200. @Bean
  201. public LocaleResolver localeResolver() {
  202. // 使用SessionLocaleResolver或CookieLocaleResolver持久化Locale
  203. SessionLocaleResolver resolver = new SessionLocaleResolver();
  204. resolver.setDefaultLocale(Locale.ENGLISH); // 设置默认Locale
  205. return resolver;
  206. }
  207. @Bean
  208. public LocaleChangeInterceptor localeChangeInterceptor() {
  209. LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
  210. interceptor.setParamName("lang"); // 支持通过?lang=zh_CN参数切换Locale
  211. return interceptor;
  212. }
  213. @Override
  214. public void addInterceptors(InterceptorRegistry registry) {
  215. registry.addInterceptor(localeChangeInterceptor()); // 注册拦截器
  216. }
  217. private void responseResult(HttpServletResponse response, Result result) {
  218. response.setCharacterEncoding("UTF-8");
  219. response.setHeader("Content-type", "application/json;charset=UTF-8");
  220. response.setStatus(200);
  221. try {
  222. response.getWriter().write(JSON.toJSONString(result));
  223. } catch (IOException ex) {
  224. logger.error(ex.getMessage());
  225. }
  226. }
  227. /**
  228. * 一个简单的登录认证
  229. */
  230. private boolean validateLogin(HttpServletRequest request) {
  231. String userId = request.getParameter("userId");
  232. if (StringUtils.isBlank(userId)) {
  233. return false;
  234. }
  235. CacheKey key = CacheKey.generateKey(CacheType.AppSecretKey, userId);
  236. boolean exists = redisTemplate.hasKey(key.toString());
  237. if (exists) {
  238. return true;
  239. } else {
  240. return false;
  241. }
  242. }
  243. /**
  244. * 一个简单的签名认证,规则: 1. 将请求参数按ascii码排序 2. 拼接为a=value&b=value...这样的字符串(不包含sign)
  245. * 3. 混合密钥(secret)进行md5获得签名,与请求的签名进行比较
  246. */
  247. private boolean validateSign(HttpServletRequest request) {
  248. String requestSign = request.getParameter("sign");// 获得请求签名,如sign=19e907700db7ad91318424a97c54ed57
  249. if (StringUtils.isEmpty(requestSign)) {
  250. return false;
  251. }
  252. List<String> keys = new ArrayList<String>(request.getParameterMap().keySet());
  253. keys.remove("sign");// 排除sign参数
  254. Collections.sort(keys);// 排序
  255. StringBuilder sb = new StringBuilder();
  256. for (String key : keys) {
  257. sb.append(key).append("=").append(request.getParameter(key)).append("&");// 拼接字符串
  258. }
  259. String linkString = sb.toString();
  260. linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);// 去除最后一个'&'
  261. String secret = "Potato";// 密钥,自己修改
  262. String sign = DigestUtils.md5Hex(linkString + secret);// 混合密钥md5
  263. return StringUtils.equals(sign, requestSign);// 比较
  264. }
  265. private String getIpAddress(HttpServletRequest request) {
  266. String ip = request.getHeader("x-forwarded-for");
  267. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  268. ip = request.getHeader("Proxy-Client-IP");
  269. }
  270. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  271. ip = request.getHeader("WL-Proxy-Client-IP");
  272. }
  273. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  274. ip = request.getHeader("HTTP_CLIENT_IP");
  275. }
  276. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  277. ip = request.getHeader("HTTP_X_FORWARDED_FOR");
  278. }
  279. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  280. ip = request.getRemoteAddr();
  281. }
  282. // 如果是多级代理,那么取第一个ip为客户端ip
  283. if (ip != null && ip.indexOf(",") != -1) {
  284. ip = ip.substring(0, ip.indexOf(",")).trim();
  285. }
  286. return ip;
  287. }
  288. }