Commit a1b6e8e7 authored by matianhao's avatar matianhao

[瑞成接口] <refactor> 解决一定并发下秘钥过期问题

 - 将刷新秘钥的动作从各线程中提取成独立方法,并使用redis分布式锁控制线程独占
 - 未获取到分布式锁的线程等待 1s 后重新发起请求
parent 5ee8f19e
......@@ -32,4 +32,11 @@ public class Constants {
* sign参数错误:14
*/
public static final String RESPONSE_CODE_14 = "14";
// 其他
//-----------------------------------
/**
* redis分布式锁的key
*/
public static final String SJJ_DISTRIBUTED_LOCK = "sjj.distributed.lock";
}
......@@ -18,7 +18,6 @@ import org.springframework.web.client.RestTemplate;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashMap;
......@@ -108,17 +107,8 @@ public class RequestSecretSchedulerTask implements SchedulingConfigurer {
requestSecretEndTime = Long.parseLong(datas.getString("requestSecretEndTime"));
refreshSecret = datas.getString("refreshSecret");
refreshSecretEndTime = Long.parseLong(datas.getString("refreshSecretEndTime"));
// 设置过期时间,30秒内不重新刷新秘钥
String expireTime = String.valueOf(requestSecretEndTime - (14 * 60 * 1000 + 30 * 1000));
// redis保存 请求秘钥 和 过期时间
log.info("请求秘钥:{};请求秘钥过期时间:{};30s内不刷新秘钥:{}",
requestSecret,
LocalDateTime.ofInstant(Instant.ofEpochMilli(requestSecretEndTime), ZoneId.systemDefault()),
LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(expireTime)), ZoneId.systemDefault())
);
// 存入redis
redisUtil.set(Constants.SJJ_REQUEST_SECRET_PREFIX, requestSecret);
redisUtil.set(Constants.SJJ_REQUEST_EXPIRE_TIME_PREFIX, expireTime);
}
}
......
......@@ -19,6 +19,8 @@ import java.time.ZoneOffset;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static com.mth.requestsecret.constant.Constants.*;
......@@ -78,7 +80,7 @@ public class RestTemplateService {
* @param apiMethod
* @return
*/
public ResponseEntity<String> commonSendRequest(MultiValueMap<String, Object> paramMap, final String apiMethod) {
public ResponseEntity<String> commonSendRequestV1(MultiValueMap<String, Object> paramMap, final String apiMethod) {
// 请求url拼接api签名和公共参数
StringBuilder url = new StringBuilder()
.append(pathUrl)
......@@ -138,6 +140,94 @@ public class RestTemplateService {
return responseEntity;
}
/**
* 瑞成平台接口发送请求 V2.0
* <p>
* 将获取秘钥的动作从请求线程中拆分出来,并使用独占形式
*
* @param paramMap
* @param apiMethod
* @return
*/
public ResponseEntity<String> commonSendRequest(MultiValueMap<String, Object> paramMap, final String apiMethod) {
// 请求url拼接api签名和公共参数
StringBuilder url = new StringBuilder()
.append(pathUrl)
.append("/interface/public/service/risen-api/")
.append(apiMethod);
// 请求时间
String requestTime = DSLUtils.dateToLong(new Date()) + "";
// redis中获取秘钥
String requestSecret = redisUtil.get(SJJ_REQUEST_SECRET_PREFIX);
log.info("redis中请求秘钥:{}", requestSecret);
// 签名字符串
String signStr = appKey + requestSecret + requestTime;
// 组装请求参数
paramMap.set("appKey", appKey);
paramMap.set("sign", MD5Utils.encoderByMd5(signStr));
paramMap.set("requestTime", requestTime);
// 日志记录
log.info("纪委api:{}", apiMethod);
log.info("api url:{}", url);
log.info("api params:{}", paramMap);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 发送请求
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(paramMap, headers);
ResponseEntity<String> responseEntity = restTemplate.exchange(url.toString(), HttpMethod.POST, request, String.class);
log.info("api response:{}", responseEntity);
try {
// 接口返回码
String code = JSONObject.parseObject(responseEntity.getBody()).getString("code");
Integer count = threadLocal.get();
String threadName = Thread.currentThread().getName();
// 如非成功返回码,则重新获取秘钥并发送请求,限制重新请求次数两次
if (!RESPONSE_CODE_00.equals(code) && count < 2) {
threadLocal.set(++count);
log.info("线程名称:{},重新请求次数:{}", threadName, count);
// 刷新秘钥
getRequestSecret();
// 重新发起调用请求
return commonSendRequest(paramMap, apiMethod);
}
} finally {
threadLocal.remove();
}
return responseEntity;
}
/**
* 刷新瑞成接口秘钥
*/
private void getRequestSecret() {
// 唯一值,用于校验是否为同一个线程的锁
String randomValue = UUID.randomUUID().toString().replace("-", "");
// 获取锁
boolean lock = redisUtil.getLock(SJJ_DISTRIBUTED_LOCK, randomValue, 1000);
if (lock) {
log.info("获取到锁 - 刷新秘钥");
try {
// 刷新秘钥
schedulerTask.getRequestSecret();
} finally {
// 释放该线程的锁
redisUtil.releaseLock(SJJ_DISTRIBUTED_LOCK, randomValue);
}
}else {
log.info("未获取到锁 - 等待1000毫秒");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 招必得平台接口发送请求
......
......@@ -2,9 +2,11 @@ package com.mth.requestsecret.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
......@@ -852,4 +854,39 @@ public class RedisUtils {
}
}
// 分布式锁
/**
* 获得锁
*/
public boolean getLock(String lockId, String value, long millisecond) {
Boolean success = redisTemplate.opsForValue().setIfAbsent(lockId, value, millisecond, TimeUnit.MILLISECONDS);
return success != null && success;
}
/**
* 释放锁
*
* @param lockId
*/
public void releaseLock(String lockId) {
redisTemplate.delete(lockId);
}
/**
* 释放锁
*
* @param lockId
* @param value
* @return
*/
public Long releaseLock(String lockId, String value) {
// 查询如果是同一个锁则释放
String script = "if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = RedisScript.of(script, Long.class);
// 执行Lua脚本
return redisTemplate.execute(redisScript, Arrays.asList(lockId, value));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment