基于服务器轮询实现扫码功能
之前的扫码功能是基于轮询和websocket长连接实现的,详情可见:基于轮询或长连接的扫码亮码功能
轮询有个缺点就是扫码端和被扫码端都要一直发起请求查询状态,直到扫码修改了状态才停止请求,这样其实会造成前后端需要大量的网络IO交互。为了避免这种大量的请求,我们可以把这种轮询的请求放到服务器上面去,这里我们修改扫码端的轮询。
实现流程
扫码端扫码之后,后端服务器阻塞主线程,开启一个新的线程轮询读取码的状态,如果码状态修改为已确认,则结束新的线程,释放主线程,返回结果给前端。
具体实现
调用接口之后把状态修改为2,表示已扫描
redisTemplate.opsForValue().set(code, "2", 2, TimeUnit.MINUTES);
用CountDownLatch阻塞主线程,创建一个子线程轮询查询状态
CountDownLatch countDownLatch = new CountDownLatch(1);
CheckThread thread = new CheckThread(countDownLatch, redisTemplate, code);
封装一个FutureTask拿到子线程执行之后返回的结果
FutureTask<String> futureTask = new FutureTask<>(thread);
new Thread(futureTask).start();
阻塞主线程,等待主线程释放后拿到返回结果
try {
countDownLatch.await();
String result = futureTask.get();
//略
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
子线程的执行过程
private static class CheckThread implements Callable<String> {
int i = 0;
private CountDownLatch latch;
private RedisTemplate<String, String> redisTemplate;
private String code;
public CheckThread(CountDownLatch countDownLatch, RedisTemplate<String, String> redisTemplate, String code) {
this.latch = countDownLatch;
this.redisTemplate = redisTemplate;
this.code = code;
}
@Override
public String call() {
String result = null;
while (i++ < 15) {
String status = redisTemplate.opsForValue().get(code);
//码已过期:null 已确认授权:3 已拒绝:4
if (StringUtils.isBlank(status) || "3".equals(status) || "4".equals(status)) {
result = status;
//countDownLatch计数器减1,前面设置为1,所以这里执行之后为0,子线程执行结束,释放主线程
latch.countDown();
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//用户15秒内未授权扫码
if (i == 16) {
result = "2";
latch.countDown();
}
return result;
}
}
整个过程就是利用CountDownLatch阻塞主线程,等待子线程执行结束释放主线程。
阻塞主线程:countDownLatch.await();
释放主线程:latch.countDown();