MyWebMvcConfigurer.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. package com.txz.mall.configurer;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.serializer.SerializeConfig;
  4. import com.alibaba.fastjson.serializer.SerializerFeature;
  5. import com.alibaba.fastjson.serializer.ToStringSerializer;
  6. import com.alibaba.fastjson.support.config.FastJsonConfig;
  7. import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
  8. import com.txz.mall.core.ProjectConstant;
  9. import com.txz.mall.core.Result;
  10. import com.txz.mall.core.ResultCode;
  11. import com.txz.mall.core.ServiceException;
  12. import com.txz.mall.core.cache.CacheKey;
  13. import com.txz.mall.core.cache.CacheType;
  14. import org.apache.commons.codec.digest.DigestUtils;
  15. import org.apache.commons.lang3.StringUtils;
  16. import org.slf4j.Logger;
  17. import org.slf4j.LoggerFactory;
  18. import org.springframework.context.annotation.Configuration;
  19. import org.springframework.data.redis.core.RedisTemplate;
  20. import org.springframework.http.MediaType;
  21. import org.springframework.http.converter.HttpMessageConverter;
  22. import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
  23. import org.springframework.web.method.HandlerMethod;
  24. import org.springframework.web.servlet.HandlerExceptionResolver;
  25. import org.springframework.web.servlet.ModelAndView;
  26. import org.springframework.web.servlet.NoHandlerFoundException;
  27. import org.springframework.web.servlet.config.annotation.CorsRegistry;
  28. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  29. import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
  30. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  31. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
  32. import javax.annotation.Resource;
  33. import javax.servlet.ServletException;
  34. import javax.servlet.http.HttpServletRequest;
  35. import javax.servlet.http.HttpServletResponse;
  36. import java.io.IOException;
  37. import java.math.BigInteger;
  38. import java.util.ArrayList;
  39. import java.util.Collections;
  40. import java.util.List;
  41. /**
  42. * Spring MVC 配置
  43. */
  44. @Configuration
  45. public class MyWebMvcConfigurer implements WebMvcConfigurer {
  46. private final Logger logger = LoggerFactory.getLogger(MyWebMvcConfigurer.class);
  47. @Resource
  48. private Parameters parameters;
  49. @Resource
  50. private RedisTemplate redisTemplate;
  51. @Override
  52. public void addCorsMappings(CorsRegistry registry) {
  53. registry.addMapping("/**")
  54. .allowedOriginPatterns("*")
  55. .allowedMethods("GET", "POST", "PUT", "DELETE")
  56. .allowedHeaders("*")
  57. .allowCredentials(true)
  58. .maxAge(3600);
  59. }
  60. @Override
  61. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  62. registry.addResourceHandler("swagger-ui.html")
  63. .addResourceLocations("classpath:/META-INF/resources/");
  64. registry.addResourceHandler("docs.html")
  65. .addResourceLocations("classpath:/META-INF/resources/");
  66. registry.addResourceHandler("doc.html")
  67. .addResourceLocations("classpath:/META-INF/resources/");
  68. registry.addResourceHandler("/webjars/**")
  69. .addResourceLocations("classpath:/META-INF/resources/webjars/");
  70. }
  71. // 使用阿里 FastJson 作为JSON MessageConverter
  72. @Override
  73. public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  74. for (int i = converters.size() - 1; i >= 0; i--) {
  75. if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {
  76. converters.remove(i);
  77. }
  78. }
  79. FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
  80. //自定义fastjson配置
  81. FastJsonConfig config = new FastJsonConfig();
  82. config.setDateFormat("yyyy-MM-dd HH:mm:ss");
  83. config.setSerializerFeatures(
  84. SerializerFeature.WriteMapNullValue, // 是否输出值为null的字段,默认为false,我们将它打开
  85. SerializerFeature.WriteNullListAsEmpty, // 将Collection类型字段的字段空值输出为[]
  86. SerializerFeature.WriteNullStringAsEmpty, // 将字符串类型字段的空值输出为空字符串
  87. SerializerFeature.WriteNullNumberAsZero, // 将数值类型字段的空值输出为0
  88. SerializerFeature.WriteDateUseDateFormat,
  89. SerializerFeature.DisableCircularReferenceDetect // 禁用循环引用
  90. );
  91. SerializeConfig serializeConfig = SerializeConfig.globalInstance;
  92. serializeConfig.put(BigInteger.class, ToStringSerializer.instance);
  93. serializeConfig.put(Long.class, ToStringSerializer.instance);
  94. serializeConfig.put(Long.TYPE, ToStringSerializer.instance);
  95. config.setSerializeConfig(serializeConfig);
  96. fastJsonHttpMessageConverter.setFastJsonConfig(config);
  97. // 添加支持的MediaTypes;不添加时默认为*/*,也就是默认支持全部
  98. // 但是MappingJackson2HttpMessageConverter里面支持的MediaTypes为application/json
  99. // 参考它的做法, fastjson也只添加application/json的MediaType
  100. List<MediaType> fastMediaTypes = new ArrayList<>();
  101. fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
  102. fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
  103. converters.add(fastJsonHttpMessageConverter);
  104. // 设置项目名称
  105. ProjectConstant.application = parameters.getApplication();
  106. }
  107. // 统一异常处理
  108. @Override
  109. public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
  110. exceptionResolvers.add((request, response, handler, e) -> {
  111. Result result = new Result();
  112. if (e instanceof ServiceException) {// 业务失败的异常,如“账号或密码错误”
  113. result.setCode(ResultCode.FAIL.getCode()).setMessage(e.getMessage());
  114. logger.info(e.getMessage());
  115. } else if (e instanceof NoHandlerFoundException) {
  116. result.setCode(ResultCode.NOT_FOUND.getCode()).setMessage(
  117. "接口 [" + request.getRequestURI() + "] 不存在");
  118. } else if (e instanceof ServletException) {
  119. result.setCode(ResultCode.FAIL.getCode()).setMessage(e.getMessage());
  120. } else {
  121. result.setCode(ResultCode.INTERNAL_SERVER_ERROR.getCode()).setMessage(
  122. "接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
  123. String message;
  124. if (handler instanceof HandlerMethod) {
  125. HandlerMethod handlerMethod = (HandlerMethod) handler;
  126. message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
  127. request.getRequestURI(), handlerMethod.getBean().getClass().getName(),
  128. handlerMethod.getMethod().getName(), e.getMessage());
  129. } else {
  130. message = e.getMessage();
  131. }
  132. logger.error(message, e);
  133. }
  134. responseResult(response, result);
  135. return new ModelAndView();
  136. });
  137. }
  138. // 解决跨域问题
  139. // @Override
  140. // public void addCorsMappings(CorsRegistry registry) {
  141. // registry.addMapping("/**");
  142. // }
  143. // 添加拦截器
  144. @Override
  145. public void addInterceptors(InterceptorRegistry registry) {
  146. // 接口签名认证拦截器,该签名认证比较简单,实际项目中可以使用Json Web Token或其他更好的方式替代。
  147. if ("true".equals(parameters.getSignature())) { // 开发环境忽略签名认证
  148. registry.addInterceptor(new HandlerInterceptorAdapter() {
  149. @Override
  150. public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
  151. Object handler) throws Exception {
  152. // 验证签名
  153. boolean pass = validateSign(request);
  154. if (pass) {
  155. return true;
  156. } else {
  157. logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}", request.getRequestURI(),
  158. getIpAddress(request), JSON.toJSONString(request.getParameterMap()));
  159. Result result = new Result();
  160. result.setCode(ResultCode.UNAUTHORIZED.getCode()).setMessage("签名认证失败");
  161. responseResult(response, result);
  162. return false;
  163. }
  164. }
  165. });
  166. }
  167. }
  168. private void responseResult(HttpServletResponse response, Result result) {
  169. response.setCharacterEncoding("UTF-8");
  170. response.setHeader("Content-type", "application/json;charset=UTF-8");
  171. response.setStatus(200);
  172. try {
  173. response.getWriter().write(JSON.toJSONString(result));
  174. } catch (IOException ex) {
  175. logger.error(ex.getMessage());
  176. }
  177. }
  178. /**
  179. * 一个简单的登录认证
  180. */
  181. private boolean validateLogin(HttpServletRequest request) {
  182. String userId = request.getParameter("userId");
  183. if (StringUtils.isBlank(userId)) {
  184. return false;
  185. }
  186. CacheKey key = CacheKey.generateKey(CacheType.AppSecretKey, userId);
  187. boolean exists = redisTemplate.hasKey(key.toString());
  188. if (exists) {
  189. return true;
  190. } else {
  191. return false;
  192. }
  193. }
  194. /**
  195. * 一个简单的签名认证,规则: 1. 将请求参数按ascii码排序 2. 拼接为a=value&b=value...这样的字符串(不包含sign)
  196. * 3. 混合密钥(secret)进行md5获得签名,与请求的签名进行比较
  197. */
  198. private boolean validateSign(HttpServletRequest request) {
  199. String requestSign = request.getParameter("sign");// 获得请求签名,如sign=19e907700db7ad91318424a97c54ed57
  200. if (StringUtils.isEmpty(requestSign)) {
  201. return false;
  202. }
  203. List<String> keys = new ArrayList<String>(request.getParameterMap().keySet());
  204. keys.remove("sign");// 排除sign参数
  205. Collections.sort(keys);// 排序
  206. StringBuilder sb = new StringBuilder();
  207. for (String key : keys) {
  208. sb.append(key).append("=").append(request.getParameter(key)).append("&");// 拼接字符串
  209. }
  210. String linkString = sb.toString();
  211. linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);// 去除最后一个'&'
  212. String secret = "Potato";// 密钥,自己修改
  213. String sign = DigestUtils.md5Hex(linkString + secret);// 混合密钥md5
  214. return StringUtils.equals(sign, requestSign);// 比较
  215. }
  216. private String getIpAddress(HttpServletRequest request) {
  217. String ip = request.getHeader("x-forwarded-for");
  218. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  219. ip = request.getHeader("Proxy-Client-IP");
  220. }
  221. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  222. ip = request.getHeader("WL-Proxy-Client-IP");
  223. }
  224. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  225. ip = request.getHeader("HTTP_CLIENT_IP");
  226. }
  227. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  228. ip = request.getHeader("HTTP_X_FORWARDED_FOR");
  229. }
  230. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  231. ip = request.getRemoteAddr();
  232. }
  233. // 如果是多级代理,那么取第一个ip为客户端ip
  234. if (ip != null && ip.indexOf(",") != -1) {
  235. ip = ip.substring(0, ip.indexOf(",")).trim();
  236. }
  237. return ip;
  238. }
  239. }