学校网站建设的软件环境,昆明网络营销公司哪家好,做文件的网站,虚拟币网站建设看本篇博客前应当先看完前面三篇#xff0c;这一篇是基于前面三篇的知识点的整合。所以很多重复的代码这里就不写出了 后台通过拦截器和redis实现防重复提交#xff0c;避免因为网络原因导致多次请求同时进入业务系统#xff0c;导致数据错乱#xff0c;也可以防止对外暴露…看本篇博客前应当先看完前面三篇这一篇是基于前面三篇的知识点的整合。所以很多重复的代码这里就不写出了 后台通过拦截器和redis实现防重复提交避免因为网络原因导致多次请求同时进入业务系统导致数据错乱也可以防止对外暴露给第三方的接口在业务尚未处理完的情况下重复调用。
首先引入fastjson
dependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion1.2.35/version
/dependency新增一个幂等校验的注解
package com.xxx.util.core.annotation;import javax.ws.rs.NameBinding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;Target({ElementType.TYPE, ElementType.METHOD})
Retention(value RetentionPolicy.RUNTIME)
NameBinding
public interface Idempotent
{/*** 是否把body数据用来计算幂等key。如果没有登录信息请设置这个值为true。主要用于第三方接入。** return*/boolean body() default false;/*** body里的哪些字段用来计算幂等key。body()为true时才有生效。如果这个为空则计算整个body。主要用于第三方接入。br/* p* 字段命名规则br/* path: Like xpath, to find the specific value via path. Use :(Colon) to separate different key name or index.* For example:* JSON content:* {* name: One Guy,* details: [* {education_first: xx school},* {education_second: yy school},* {education_third: zz school},* ...* ],* loan: {loanNumber:1234567810,loanAmount:1000000},* }** To find the value of name, the pathname.* To find the value of education_second, the pathdetails:0:education_second.* To find the value of loanNumber , the pathloan:loanNumber.* To find the value of name and loanNumber , the pathname,loan:loanNumber.** return*/String[] bodyVals() default {};/*** idempotent lock失效时间in milliseconds。一些处理时间较长或者数据重复敏感的接口可以适当设置长点时间。** return*/int expiredTime() default 60000;}默认不去读取body中的内容去做幂等可以Idempotent(body true) 将body设为true开启
实现拦截器
package com.xxx.core.filter;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.xxx.common.exception.FastRuntimeException;
import com.xxx.core.annotation.Idempotent;
import com.xxx.core.filter.request.HttpHelper;
import com.xxx.core.filter.request.RequestReaderHttpServletRequestWrapper;import com.xxx.util.core.utils.SpringContextUtil;
import com.xxx.util.redis.SimpleLock;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import redis.clients.jedis.JedisCluster;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Pattern;public class IdempotentFilter extends HandlerInterceptorAdapter {private final Logger logger LoggerFactory.getLogger(IdempotentFilter.class);private static final String IDEMPOTENT idempotent.info;private static final String NAMESPACE idempotent;private static final String NAMESPACE_LOCK idempotent.lock;Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {logger.info(request请求地址path[{}] uri[{}], request.getServletPath(),request.getRequestURI());HandlerMethod handlerMethod (HandlerMethod) handler;Method method handlerMethod.getMethod();Idempotent ra method.getAnnotation(Idempotent.class);if (Objects.nonNull(ra)) {logger.debug(Start doIdempotent);int liveTime getIdempotentLockExpiredTime(ra);String key generateKey(request, ra);logger.debug(Finish generateKey:[{}],key);JedisCluster jedisCluster getJedisCluster();//上分布式锁 避免相同的请求同时进入调用jedisCluster.get(key) 都为null的情况new SimpleLock(NAMESPACE_LOCK key,jedisCluster).wrap(new Runnable() {Overridepublic void run() {//判断key是否存在如存在抛出重复提交异常如果不存在 则新增if (jedisCluster.get(key) null){jedisCluster.setex(key,liveTime,true);request.setAttribute(IDEMPOTENT, key);}else {logger.debug(the key exist : {}, will be expired after {} mils if not be cleared, key, liveTime);throw new FastRuntimeException(20001,请勿重复提交);}}});}return true;}private int getIdempotentLockExpiredTime(Idempotent ra){return ra.expiredTime();}Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {}Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {try{//业务处理完成 删除redis中的keyafterIdempotent(request);}catch (Exception e){// ignore it when exceptionlogger.error(Error after Idempotent, e);}}private void afterIdempotent(HttpServletRequest request) throws IOException{Object obj request.getAttribute(IDEMPOTENT);if (obj ! null){logger.debug(Start afterIdempotent);String key obj.toString();JedisCluster jedisCluster getJedisCluster();if (StringUtils.isNotBlank(key) jedisCluster.del(key) 0){logger.debug(afterIdempotent error Prepared to delete the key:[{}] ,key);}logger.debug(End afterIdempotent);}}/*** generate key** param request* param ra* return*/public String generateKey(HttpServletRequest request, Idempotent ra){String requestURI request.getRequestURI();String requestMethod request.getMethod();StringBuilder result new StringBuilder(NAMESPACE);String token request.getHeader(H-User-Token);append(result, requestURI);append(result, requestMethod);append(result, token);appendBodyData( request, result, ra);logger.debug(The raw data to be generated key: {}, result.toString());return DigestUtils.sha1Hex(result.toString());}private void appendBodyData(HttpServletRequest request, StringBuilder src,Idempotent ra){if (Objects.nonNull(ra)){boolean shouldHashBody (boolean) ra.body();logger.debug(Found attr for body in Idempotent, the value is {}, shouldHashBody);if (shouldHashBody){String data null;try {data HttpHelper.getBodyString(new RequestReaderHttpServletRequestWrapper(request));} catch (IOException e) {logger.warn(Found attr for body in Idempotent, but the body is blank);return;}if (StringUtils.isBlank(data)){logger.warn(Found attr for body in Idempotent, but the body is blank);return;}String[] bodyVals ra.bodyVals();// bodyVals优先if (Objects.nonNull(bodyVals) bodyVals.length ! 0){logger.debug(Found attr for bodyVals in Idempotent, the value is {}, Arrays.asList(bodyVals));final String finalData data;Arrays.asList(bodyVals).stream().sorted().forEach(e - {String val getEscapedVal(finalData, e);append(src, val);});}else{append(src, data);}}}}private String getEscapedVal(String json, String path){String[] paths path.split(:);JSONObject jsonObject null;JSONArray jsonArray null;String nodeVal json;for (String fieldName : paths){if (isInteger(fieldName)){try {jsonArray JSONObject.parseArray(nodeVal);nodeVal jsonArray.get(Integer.parseInt(fieldName)).toString();} catch (JSONException e) {//如果无法转为jsonArray 则说明不是数组尝试转为jsonObject去取值logger.warn(getEscapedVal JSONObject.parseArray error nodeVal:[{}] fieldName:[{}],nodeVal,nodeVal);jsonObject JSONObject.parseObject(nodeVal);nodeVal jsonObject.get(fieldName).toString();}}else {jsonObject JSONObject.parseObject(nodeVal);nodeVal jsonObject.get(fieldName).toString();}}return nodeVal;}public static boolean isInteger(String str) {Pattern pattern Pattern.compile(^[-\\]?[\\d]*$);return pattern.matcher(str).matches();}private void append(StringBuilder src, String str){if (!StringUtils.isBlank(str)){src.append(#).append(str);}}//手动注入public JedisCluster getJedisCluster() {return SpringContextUtil.getBean(JedisCluster.class);}
}新建SpringContextUtil工具类
package com.xxx.util.core.utils;import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;Component
public class SpringContextUtil implements ApplicationContextAware {private static ApplicationContext applicationContext; // Spring应用上下文环境public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {SpringContextUtil.applicationContext applicationContext;}public static ApplicationContext getApplicationContext() {return applicationContext;}SuppressWarnings(unchecked)public static T T getBean(String name) throws BeansException {return (T) applicationContext.getBean(name);}SuppressWarnings(unchecked)public static T T getBean(Class? clz) throws BeansException {return (T) applicationContext.getBean(clz);}
}使用方式异常简单如果可以根据请求头的内容做区分是否重复提交则直接使用Idempotent 即可如果是提供给第三方的接口 请求头无法哦按段需要指定body则Idempotent(body true,bodyVals {“loan:loanNumber”})即可 案例代码如下 Idempotent(body true,bodyVals {loan:loanNumber})PostMapping(Urls.Test.V1_ADD)ResponseBodyApiOperation(value Urls.UserProfiles.V1_GET_USER_PROFILES_BY_PAGE_DESC)public Response add(RequestBody Test test) {return null;}