一般来说,我们都是让接口异步来提高系统的吞吐能力,但是有些场景,我们不得不把异步回调接口变为同步。
那么,我们这里要把异步接口变为同步的业务场景是什么呢?我们有一个微信公众号的应用,用户需要进入到应用中签署协议。电子协议需要有法律效用,那么就只有找第三方权威、可信的电子合同产品,我们最终选择的厂商是易企签,他提供两种签署模式,一种是短信或其他非网页嵌入模式,一种是需要嵌入到自己的网页中。我们的业务就是后面这一种,现在问题来了,易企签发起签署流程后,并不会同步返回签署页面,而是通过接口异步回调的方式,这时候就需要我们自己来处理,把发起签署到获取嵌入页面同步返回。
这里是思路是,使用redis来做分布式锁以及临时存储回调地址,在发起信封发起成功后,另外起一个线程加锁,当前线程一直等待获取锁,当易企签回调时强制解锁,此时当前线程即可获取到锁并获取嵌入地址。
这里的分布式锁是基于Redisson
,所以先要引入相应的jar包
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.6</version>
</dependency>
此时,我们再来基于Redisson
实现锁,
public interface IDistributedLocker {
/**
* 带超时的锁
* 超时时间尽量不要太长,太长了容易死锁
*/
public RLock lock(String lockKey, int timeout);
/**
* 带超时的锁
* @param lockKey
* @param unit 时间类型,可以是秒,毫秒
* @param timeout
* @return
*/
public RLock lock(String lockKey, TimeUnit unit, int timeout);
/**
* 解锁,可以直接来key
*/
public void unlock(String lockKey);
void forceUnlock(String lockKey);
/**
* 解锁,也可以直接传加锁返回的参数
*/
public void unlock(RLock lock);
public void setRedissonClient(RedissonClient redissonClient);
}
@Service
public class RedissonDistributedLocker implements IDistributedLocker {
@Autowired
private RedissonClient redissonClient;
/**
* 默认加锁时间
*/
protected Integer defaultTime = 120;
@Override
public RLock lock(String lockKey, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
return lock;
}
@Override
public RLock lock(String lockKey, TimeUnit unit, int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
@Override
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
if (lock != null) {
lock.unlock();
}
}
@Override
public void forceUnlock(String lockKey){
RLock lock = redissonClient.getLock(lockKey);
if (lock != null) {
lock.forceUnlock();
}
}
@Override
public void unlock(RLock lock) {
lock.unlock();
}
@Override
public void setRedissonClient(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
public RedissonClient getRedissonClient() {
return redissonClient;
}
}
加锁的和获取地址的部分代码
ThreadUtil.execAsync(() -> {
locker.lock(customTag, 30); //30秒自动释放锁
});
signitService.createSignit(signitInfoVo, Lists.newArrayList(receiverVo), senderVo, null);
locker.lock(customTag, 30); //一直等待获取锁
locker.unlock(customTag);
return redisService.get(customTag).toString();
这里是ThreadUtil
类是Hutool
提供的工具类,接下来就是回调强制解锁
case PARTICIPANT_HANDLING:
ParticipantHandling rawData1 = (ParticipantHandling) ente.rawDataAsBean();
boolean s = rawData1.isSuccess();
try {
redisService.set(rawData1.getCustomTag(), rawData1.getActionUrl());
locker.forceUnlock(rawData1.getCustomTag());
log.info("强制解锁成功");
} catch (Exception e) {
e.printStackTrace();
}
log.info("现在该我签署了,请把我嵌入到客户方系统中就能实现免登录签署哦:" + rawData1.getActionUrl());
//postWebhookData(appInfo, restVo, 5);
break;
我们这里设置的锁等待时间是30秒,当然,有时候易企签回调非常慢,会超过30秒,甚至有时候还不回调,但是没关系,用户再重新发起即可,毕竟这种情况也是少数。