需求很简单,就是为了实现PC站点的扫码登陆,方案有很多,比如WebSocket搞一个长链接,或者短链接轮训,考虑来考虑去,都感觉不是很好,最后选择的伪长链接方案,既可以解决短链接的实时问题,有可以降低服务器长链接资源占用问题,居于两者之间
检索了一下SpringBoot的文档,实现异步接口方便的很,使用@Async或者返回Callable接口都可以快速的解决问题,但是最终没有选择这两个方案,这两个方案封装无法做到按需返回,都是在子线程实现事物完成后,返回,对于主线程来说是异步的,但是无法做到按需返回结果,所以最终选择了HttpServletRequest对象中的startAsync();实现如下
AsyncContext context = request.startAsync();
context.getResponse().setCharacterEncoding("utf-8");
context.getResponse().setContentType("text/html;charset=UTF-8");
//缓存AsyncContext,方便扫码后的处理
map.put(key, context);
context.addListener(new AsyncListener() {
@Override
public void onTimeout(AsyncEvent event) throws IOException {
System.out.println("超时了...");
PrintWriter writer = context.getResponse().getWriter();
writer.print(JSON.toJSONString(CommonResult.errorReturn(400006, "超时")));
writer.flush();
context.complete();
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException {
System.out.println("线程开始");
}
@Override
public void onError(AsyncEvent event) throws IOException {
log.error("error", event.getThrowable());
clearQrCode(key);
}
@Override
public void onComplete(AsyncEvent event) throws IOException {
clearQrCode(key);
}
});
//设置超时时间
context.setTimeout(20000);
这里的异步被设置了20秒超时,并且在超时的方法内实现了complete(),主要就是为了解决线程超时后正常返回结果,比如在20秒内扫码后,会执行如下操作
@Override
public void setQrCodeState(String key, String appId, String openId) {
AsyncContext context = map.get(key);
if (context == null) {
return;
}
context.start(() -> {
try {
//扫码登陆逻辑处理
sleep(1000);
} catch (Exception e) {
log.error("exception:", e);
} finally {
context.complete();
//移除缓存map中的AsyncContext,防止资源泄漏
map.remove(key);
}
});
}