关于分布式架构下接口的幂等性和并发性控制

关于分布式架构下接口的幂等性和并发性控制

废话不多说直接上代码

主要思路通过注解形式实现幂等,利用redis的lua脚本解决并发执行

定义注解

/**
 * 幂等注解
 * get请求直接 key
 * post的请求  body格式 user.name
 */
@Target(ElementType.METHOD) //注解目标(方法上,类上)
@Documented
@Retention(RetentionPolicy.RUNTIME)//编译生效
public @interface Idempotence {
    
    
    String value() default "";
}

根据注解进行环绕切面

我们可以跟前端约定一个页面的随机数 仅针对于页面的 idemKey 放入请求头里

@Aspect
@Component
public class IdempotenceAspect {
    
    

    private final static Integer IdemError = 91111;

    private final static String IdemWebHeader = "idemKey";//和H5约定的页面随机数

    private final static String IDEM = "IDEM:";

    private final static Integer ttl1 = 1000;
    private final static Integer ttl10 = 10000;


    @Autowired(required = false)//不强行注入
    private HttpServletRequest request;

    @Autowired(required = false)//不强行注入
    private HttpServletResponse response;



    //业务层日志
    @Around("@annotation(idempotence)")
    public void idem(ProceedingJoinPoint joinPoint, Idempotence idempotence) throws Throwable {
    
    
        if (request == null) {
    
    
            return;
        }
        String methodType = request.getMethod();//获取请求方法
        String idemWeb = request.getHeader(IdemWebHeader);//获取请求头参数
        String url = request.getRequestURI();//获取路径

        ArrayList<String> signList = new ArrayList<>();//参数集
        signList.add(url);
        if (StringUtils.isNotEmpty(idemWeb)) {
    
    
            signList.add(idemWeb);
        }

        //处理get和requestParam的参数集
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (parameterMap != null && parameterMap.size() > 0) {
    
    
            for (String key : parameterMap.keySet()) {
    
    
                signList.add(key + parameterMap.get(key)[0]);
            }
        }


        String signKey = "";
        if ("GET".equals(methodType)) {
    
    

        } else if ("POST".equals(methodType)) {
    
    
            signList.addAll(getBodyParam(joinPoint)); //处理post-body对象属性
        }

        Collections.sort(signList);//注:一定要用集合工具的排序方法排序,以保证这个参数集是按一定规则排序后的结果集
        for (String it : signList) {
    
    
            signKey = signKey + it; //拼接redis的key
        }
        String key = IDEM + getMD5(signKey);//以免key过长 做长度加密控制 32位
        if (idemRedis(key, signList)) return;//redis处理

        joinPoint.proceed();
    }

/**
     * 获取方法的body参数
     */
    List getBodyParam(ProceedingJoinPoint joinPoint) throws IllegalAccessException, InstantiationException {
    
    
        ArrayList<String> arrayList = new ArrayList<>();
        //获取进入的方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();//拿到方法
        for (Parameter parameter : methodSignature.getMethod().getParameters()) {
    
    //遍历参数
            if (parameter.isAnnotationPresent(RequestBody.class)) {
    
    
                for (Object arg : joinPoint.getArgs()) {
    
    //获取入参对象
                    if(arg.getClass().getName().equals(parameter.getParameterizedType().getTypeName())){
    
    
                        //将对象反射 处理期属性名和属性值
                        Field[] declaredFields = arg.getClass().getDeclaredFields();
                        for (Field field : declaredFields) {
    
    
                            field.setAccessible(true);
                            arrayList.add(field.getName() + field.get(arg));
                        }
                        break;
                    }
                }


            }
        }

        return arrayList;
    }
/**
     * MD532位加密并大写
     *
     * @param message
     * @return
     */
    public static String getMD5(String message) {
    
    
        MessageDigest messageDigest = null;
        StringBuffer md5StrBuff = new StringBuffer();
        try {
    
    
            messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.reset();
            messageDigest.update(message.getBytes("UTF-8"));

            byte[] byteArray = messageDigest.digest();
            for (int i = 0; i < byteArray.length; i++) {
    
    
                if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) {
    
    
                    md5StrBuff.append("0").append(Integer.toHexString(0xFF & byteArray[i]));
                } else {
    
    
                    md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
                }
            }
        } catch (Throwable t) {
    
    
            System.out.println("加密异常" + t.getMessage());
        }
        return md5StrBuff.toString().toUpperCase();// 字母大写
    }

 /***
     * redis幂等处理
     * @param signList 参数签名集
     * @param key 幂等缓存id
     * @return ture 重复提交  false 非重复提交
     */
    private boolean idemRedis(String key, ArrayList<String> signList) {
    
    

		//想简单化的实现 采用的lua脚本 
        String script = "if (redis.call('exists',KEYS[1])==0) then " +
                "redis.call('set', KEYS[1], ARGV[1]);" +
                "redis.call('pexpire', KEYS[1], ARGV[2]);" +
                "return 1 ;" +
                "end " +
                "return 0 ;";
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(script, Boolean.class);
        Boolean isHave = (Boolean) RedisUtils.redisTemplate.execute(redisScript, Collections.singletonList(key), signList, ttl10);
        if (isHave) {
    
    
            log.info("请求成功入参:{}", signList);
        } else {
    
    
            log.info("请求幂等处理,存在重复提交参数,入参:{}", signList);
            returnJson("该请求以重复提交");//这个里面可以封装成自己框架内的同一响应对象
            return true;
        }

        return false;
    }
  /**
     * 返回客户端数据
     *
     * @param json
     * @throws Exception
     */
    private void returnJson(String json) {
    
    
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        try {
    
    
            writer = response.getWriter();
            writer.print(json);

        } catch (IOException e) {
    
    
        } finally {
    
    
            if (writer != null) {
    
    
                writer.close();
            }
        }
    }

记录一次工作中的接口幂等处理形式,只要是一种思想分享,如果大家有更好的想法麻烦告诉我下,或者有什么可以改进的地方欢迎留言,知识一定越学越多,技术一定越磨越精~

猜你喜欢

转载自blog.csdn.net/weixin_38899094/article/details/115377607