1、背景:
集群下,多台服务器并发接受请求并执行三方接口的认证和请求操作,容易出现:本地token失效,集群下的多台服务器,同时去三方进行认证,先后返回多个token,然而,最后一个返回的token才是最新的且是有效的。
2、方案:
集群模式下使用
1)使用redis缓存认证token,设置超时时间;
2)请求接口前,先重本地redis获取token;
3)接口请求;
4)请求提示认证失败,则走重新认证机制;
5)无论认证信息来自reids,还是来自三方,则都走三方服务进行重新认证;
6)接口请求。
理论上,将可能的三方认证次数减少最多一次,也将接口请求次数降到最多两次。
一下代码示例:
/**
* 请求360打款
* 1.资格认证
* 2.请求打款
* 3.保存日志
*
* @param vo
* @return
*/
@Override
public RPCResult<Boolean> exchangeMoney(ExchangeMoneyVO vo) {
LOGGER.info("360打款,调用服务,参数:{}", gson.toJson(vo));
if (StringUtils.isEmpty(vo.getCash())
|| StringUtils.isEmpty(vo.getPayId())
|| StringUtils.isEmpty(vo.getAccountName())
|| StringUtils.isEmpty(vo.getBankName())
|| StringUtils.isEmpty(vo.getBankBranch())
|| StringUtils.isEmpty(vo.getBankCardNum())) {
return RPCResult.error("参数不全");
}
synchronized (vo.getPayId().intern()) {
SanExchangeMoney sanExchangeMoney = sanExchangeMoneyService.findSuccess(vo.getPayId());
if (null != sanExchangeMoney) {
return RPCResult.error("360已打款成功");
}
sanExchangeMoney = sanExchangeMoneyService.findUnBack(vo.getPayId());
if (null != sanExchangeMoney) {
return RPCResult.error("360打款结果未回写,不可重复申请");
}
/*1.资格认证*/
AuthenticationRes authRes = creditExchangeMoneyService.getAuth();
/*AuthenticationRes authRes = new AuthenticationRes("appId123", "token4556", "1");*/
if ("0".equals(authRes.getStatus())) {
LOGGER.error("360首次认证失败");
return RPCResult.error("360首次认证失败!");
}
String batchNumber = sanExchangeMoneyService.getSanBatchNo();
vo.setBatchNumber(batchNumber);
/*2.请求打款*/
ExchangeMoneyReq req = new ExchangeMoneyReq();
BeanUtils.copyProperties(vo, req);
req.setAppId(authRes.getAppId());
req.setToken(authRes.getToken());
/**
* 执行请求打款
*/
ExchangeMoneyRes res = creditExchangeMoneyService.exchangeMoney(req);
/* ExchangeMoneyRes res = new ExchangeMoneyRes(req.getAppId(), req.getPayId(),
req.getBatchNumber(), "1", "申请成功");*/
//请求打款,提示认证失败,重新认证一次
if ("-1".equals(res.getStatus())) {
authRes = creditExchangeMoneyService.getAuthFrom360();
if ("0".equals(authRes.getStatus())) {
LOGGER.error("360二次认证失败");
return RPCResult.error("360二次认证失败!");
}
req.setAppId(authRes.getAppId());
req.setToken(authRes.getToken());
/**
* 二次执行请求打款
* 1、redis缓存token信息失效
* 2、集群环境下多实例发生同时从360获取token
* 3、首次获取token,360技术问题导致失效
*/
res = creditExchangeMoneyService.exchangeMoney(req);
}
/*3.保存日志*/
SanExchangeMoney sExMoneyLog = new SanExchangeMoney();
BeanUtils.copyProperties(vo, sExMoneyLog);
if ("0".equals(res.getStatus())) {
sExMoneyLog.setStatus(-1);
sExMoneyLog.setBackTime(sExMoneyLog.getExchangeTime());
sExMoneyLog.setReason(StringUtils.isEmpty(res.getMes()) ? "360打款请求失败" : res.getMes());
} else if ("-1".equals(res.getStatus())) {
sExMoneyLog.setStatus(-1);
sExMoneyLog.setBackTime(sExMoneyLog.getExchangeTime());
sExMoneyLog.setReason("360认证失败,申请打款失败");
}
sanExchangeMoneyService.save(sExMoneyLog);
if ("-1".equals(res.getStatus())) {
LOGGER.error("360认证失败,申请打款失败");
return RPCResult.error("360认证失败,申请打款失败");
}
if ("0".equals(res.getStatus())) {
LOGGER.error("向360申请打款失败");
return RPCResult.error("向360申请打款失败");
}
return RPCResult.success();
}
}
三方接口类:
import com.alibaba.fastjson.JSONObject;
import com.ddyunf.cloud.common.utils.HttpRequestUtil;
import com.ddyunf.cloud.credit.third.CreditExchangeMoneyService;
import com.ddyunf.cloud.credit.third.vo.ExchangeMoneyRes;
import com.ddyunf.cloud.enums.RedisKeyType;
import com.ddyunf.cloud.credit.third.vo.AuthenticationReq;
import com.ddyunf.cloud.credit.third.vo.AuthenticationRes;
import com.ddyunf.cloud.credit.third.vo.ExchangeMoneyReq;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Created by Liuxd on 2018-08-08.
*/
@Service("creditExchangeMoneyService")
public class CreditExchangeMoneyServiceImpl implements CreditExchangeMoneyService {
private static final Logger LOGGER = LoggerFactory.getLogger(CreditExchangeMoneyServiceImpl.class);
private final static Gson gson = new Gson();
@Resource
private StringRedisTemplate stringRedisTemplate;
@Value("${san.appId}")
private String appId;
@Value("${san.appSecret}")
private String appSecret;
@Value("${san.url}")
private String url;
/**
* 获取认证信息
*
* @return
*/
@Override
public AuthenticationRes getAuth() {
AuthenticationRes authRes = getAuthFromRedis();
if ("0".equals(authRes.getStatus())) {
authRes = getAuthFrom360();
}
return authRes;
}
@Override
public AuthenticationRes getAuthFromRedis() {
Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(RedisKeyType.SAN_AUTH.getValue());
if (null != map && !map.isEmpty()
&& null != map.get("appId") && !StringUtils.isEmpty(map.get("appId").toString())
&& null != map.get("token") && !StringUtils.isEmpty(map.get("token").toString())) {
return new AuthenticationRes(0,map.get("appId").toString(), map.get("token").toString());
}
return new AuthenticationRes("空");
}
@Override
public AuthenticationRes getAuthFrom360() {
AuthenticationReq req = new AuthenticationReq(appId, appSecret);
String reqJson = JSONObject.toJSONString(req);
LOGGER.info("360认证,请求报文:{}", reqJson);
String resStr = null;
try {
resStr = HttpRequestUtil.sendPostString(url, reqJson);
} catch (Exception e) {
e.printStackTrace();
}
LOGGER.info("360认证,响应报文:{}", resStr);
if (null == resStr) {
LOGGER.info("360认证返回为空,请求报文:{}", reqJson);
return new AuthenticationRes("360认证返回为空!");
}
AuthenticationRes res = gson.fromJson(resStr, AuthenticationRes.class);
if ("1".equals(res.getStatus())) {
Map<Object, Object> map = new HashMap<>();
map.put("appId", res.getAppId());
map.put("token", res.getToken());
stringRedisTemplate.opsForHash().putAll(RedisKeyType.SAN_AUTH.getValue(), map);
stringRedisTemplate.expire(RedisKeyType.SAN_AUTH.getValue(), 30, TimeUnit.MINUTES);
}
res.setFrom(1);
return res;
}
@Override
public ExchangeMoneyRes exchangeMoney(ExchangeMoneyReq req) {
String reqJson = JSONObject.toJSONString(req);
LOGGER.info("360打款,请求报文:{}", reqJson);
String resStr = null;
try {
resStr = HttpRequestUtil.sendPostString(url, reqJson);
} catch (Exception e) {
e.printStackTrace();
}
LOGGER.info("360打款,响应报文:{}", resStr);
if (null == resStr) {
LOGGER.info("360打款返回为空,请求报文:{}", reqJson);
return new ExchangeMoneyRes("360打款返回为空!");
}
ExchangeMoneyRes res = gson.fromJson(resStr, ExchangeMoneyRes.class);
return res;
}
}