關于分布式架構下介面的冪等性和并發性控制
廢話不多說直接上代碼
主要思路通過注解形式實作冪等,利用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();
}
}
}
記錄一次作業中的介面冪等處理形式,只要是一種思想分享,如果大家有更好的想法麻煩告訴我下,或者有什么可以改進的地方歡迎留言,知識一定越學越多,技術一定越磨越精~
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/271556.html
標籤:其他
上一篇:Spring框架
下一篇:Ribbon粗淺理解
