环境
1. 服务a,端口8080
2. 服务b,端口8020
3. 自定义@Idempotent注解
4. aop环绕通知
服务a
/**
* 服务a
*
* @param id 用户id
* @param f 循环调用服务b次数
* @return
*/
@RequestMapping("/masterTest")
public String masterTest(Integer id, Integer f) {
// 生成token
String token = UUID.randomUUID().toString();
String body = null;
for (int a = 0; a < f; a++) {
// RPC调用slave-node服务
body = HttpRequest.get("http://127.0.0.1:8020/slave-node/rpc_b?id=" + id)
.header("idempotent-token", token)
.timeout(2000)
.execute().body();
Console.log(body);
}
return body;
}
服务b
@Idempotent()
@GetMapping("/rpc_b")
public String testB(Integer id) {
rpc = id;
Console.log("执行业务逻辑:{}", rpc);
return rpc + "";
}
自定义注解
package com.chuangqi.core.rpc;
import java.lang.annotation.*;
/**
* 在RPC被调用方方法上添加@Idempotent注解用于解决幂等性问题
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Idempotent {
}
定义aop拦截
package com.chuangqi.core.rpc;
import cn.hutool.core.lang.Console;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* @Description 解决幂等性
* @Author qizhentao
* @Date 2020/3/10 16:23
* @Version 1.0
*/
@Component
@Aspect
@Log4j2
public class IdempotentAspect {
//@Around("@annotation(com.chuangqi.core.rpc.Idempotent)")
@Around("@annotation(Idempotent)")
public Object around(ProceedingJoinPoint jp) throws Throwable {
try {
//Class clazz = jp.getTarget().getClass();
//String methodName = jp.getSignature().getName();
Class[] parameterTypes = ((MethodSignature) jp.getSignature()).getMethod().getParameterTypes();
Method methdo = jp.getTarget().getClass().getMethod(jp.getSignature().getName(), parameterTypes);
if (methdo.getAnnotation(Idempotent.class) != null) {
// String value = methdo.getAnnotation(ApiOperation.class).value();
// 1.获取request对象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
// 获取token
String token = request.getHeader("idempotent-token");
//Console.log("idempotent-token:{}", token);
// 2.获取session或redis
HttpSession session = request.getSession();
// 3.判断是否重复提交
String key = jp.getSignature() + "/" + Arrays.toString(jp.getArgs()); // 定义key名称
//Console.log("方法key:{}", key);
if (session.getAttribute(key) == null || !session.getAttribute(key).equals(token)) {
session.setAttribute(key, token);
} else {
Console.error("重复提交:{}", key);
JSONObject obj = JSONUtil.createObj();
obj.put("code", "202");
obj.put("msg", "重复提交");
return obj.toString();
}
}
} catch (Exception ex) {
ex.toString();
}
// 正常执行目标业务
return jp.proceed();
}
}
测试
被调用方服务b控制台打印: