整个流程:
1.pc进入登录页面,点击扫码登录,点击事件向服务器发送请求获取唯一标识,(服务器生成唯一标识,以该标识为key,以创建一个新码对象为value,放入redis)
2.页面根据获取的唯一标识生成二维码(用qrcode.js),然后开始轮询( 将唯一标识发送到服务器,以唯一标识为key,从redis查询对应的码对象,查看二维码(码对象的是否被扫描字段)是否被扫描 )
3.用户用app扫描二维码,识别出唯一标识,(判断用户是否处于登录状态,如果未登录,跳转app登录,如果已登录接着走)并将唯一标识/用户名以及用户uuid(这个字段在用户登录后存入app缓存中)发送到服务器
4.服务器根据传送过来的唯一标识作为key到redis中查询是否有对应的码对象,如果有则将码对象的是否被扫描字段改为true,并将接收到的用户名和用户uuid放入码对象,之后放入redis更新该对象,
5.接下来回看第2步的轮询,这时码对象已经被扫描了,返回success,之后页面接收success,开始发送扫码成功请求(将uuid发过去),后台接收uuid,然后从redis中取出码对象,从中取出用户名和用户uuid,数据库查询用户名以及用户uuid是否对应,如果对应则执行登录逻辑.
接下来是代码
页面:
<button onclick="getCreateScanLoginUuid()">二维码登录</button>
<input type="hidden" id="uuid">
<div class="pc_qr_code" id="qrcode">
</div>
<div id="result">请使用手机扫码</div>
<script type="text/javascript" src="/js/jquery-1.12.3.min.js"></script>
<script src="/js/qrcode/qrcode.min.js"></script>
<script src="/js/qrcode/qrcode.js"></script>
<script type="text/javascript">
var t1 = "";
function getCreateScanLoginUuid(){
var url = "/qrcontroller/createScanLoginUuid.html";
$.ajax({
url:url,
data:{},
dataType:'json',//服务器返回json格式数据
type:'post',//HTTP请求类型
success:function(data){
if(data.success == "1"){
var uuid = data.uuid;
$("#uuid").val(uuid);
var content = "scanLogin:uuid="+uuid;
// 设置参数方式
var qrcode = new QRCode('qrcode', {
text: content,
width: 256,
height: 256,
colorDark : '#000000',
colorLight : '#ffffff',
correctLevel : QRCode.CorrectLevel.H
});
window.setInterval(keepPool, 5000);//开启轮询,5秒轮询一次
}else if(data.success == "0"){
alert("生成二维码失败,请刷新重试");
}
},
error:function(xhr,type,errorThrown){
console.log(type+errorThrown);
alert("生成二维码失败,请刷新重试!!!");
}
});
}
//轮询函数
function keepPool(){
var uuid = $("#uuid").val();
$.get("/qrcontroller/pool.html",{uuid:uuid},function(msg){
var res = JSON.parse(msg);
if(res.successFlag == '1'){
console.log("扫码成功.....")
$("#result").html("<font color='red'>扫码成功</font>");
window.clearInterval(t1);//清除定时器
window.location.href = "/qrcontroller/success.html?uuid="+uuid;//如果扫描成功跳转登录方法,让其登录
}else if(res.successFlag == '0'){
$("#result").html(res.msg);
$("#result").css({
"color":"red"
})
}
});
}
</script>
/**
* 创建一个扫描登录的uuid
*/
@
@RequestMapping("")
@ResponseBody
public JSONObject createScanLoginUuid(){
JSONObject json = new JSONObject();
try {
HttpSession session = getRequest().getSession();
String uuid = UUID.randomUUID().toString();
ScanPool scanPool = new ScanPool();
scanPool.setSession(session.getId());//这一步没用
redisTemplate.boundHashOps(SCAN_LOGIN_HASH).put(uuid, scanPool);//放入redis
json.put("uuid", uuid);
json.put("success", "1");
} catch (Exception e) {
json.put("success", "0");
e.printStackTrace();
}
return json ;
}
/**这个是手机端扫码时候确认登录,然后发送ajax到这个方法,
* 从缓存中查uuid,如果uuid存在,则改变uuid的状态,通过sessionid将uuid和用户绑定到一起
* 扫码登录,对应登录成功跳转首页面
* @param uuid
* @return
*/
@RequestMapping("scanLogin")
@ResponseBody
public JSONObject scanLogin(){
String uuid = getRequest().getParameter("uuid");
String account = getRequest().getParameter("account");
String accountUuid = getRequest().getParameter("accountUuid");
// String sessionId= getRequest().getSession().getId();//浏览器请求的
JSONObject obj = new JSONObject();
ScanPool pool = (ScanPool)redisTemplate.boundHashOps(SCAN_LOGIN_HASH).get(uuid);
if (pool == null) {//二维码为空则二维码失效
obj.put("successFlag","0");
obj.put("msg","该二维码已经失效,请重新获取");
} else {//表明已经有人扫过码了
Long timeOutSecond = 10 * 60 * 1000L;
//二维码超时
if(System.currentTimeMillis() - pool.getCreateTime() > timeOutSecond){
redisTemplate.boundHashOps(SCAN_LOGIN_HASH).delete(uuid);
obj.put("successFlag","0");
obj.put("msg","该二维码已经失效,请重新获取");
}else{
pool.setAccount(account);
pool.setAccountUuid(accountUuid);
pool.scanSuccess();//设置扫码状态为成功
redisTemplate.boundHashOps(SCAN_LOGIN_HASH).put(uuid, pool);//更新redis
obj.put("msg","扫码成功!");
obj.put("successFlag","1");
}
}
return obj;
}
/**
* 轮询二维码是否被扫描
* @return
*/
@RequestMapping("pool")
@ResponseBody
public JSONObject pool(){
String uuid = getRequest().getParameter("uuid");
System.out.println("检测[ " + uuid + " ]是否登录");
JSONObject obj = new JSONObject();
ScanPool pool = (ScanPool)redisTemplate.boundHashOps(SCAN_LOGIN_HASH).get(uuid);
try {
if (pool == null) {//码已被清除
// 扫码超时,进线程休眠
//Thread.sleep(10 * 1000L);//睡10秒
obj.put("successFlag","0");
obj.put("msg","该二维码已经失效,请刷新页面重新获取");
} else {//码还在
//这里得到的ScanPool(时间靠前)和用户使用手机扫码后得到的不是一个,用户扫码后又重新更新了ScanPool对象,
//并重新放入了redis中,,所以这里要等待上面的计时器走完,才能获得最新的ScanPool
boolean scanFlag = pool.getScanStatus();
if (scanFlag) {//已经被扫描了,前台跳转登录方法,让其执行登录逻辑
// 根据uuid从redis中获取pool对象,得到对应的sessionId,返给页面,通过js存cookie中
obj.put("successFlag","1");
//obj.put("cname", "SESSIONKEY");
//obj.put("cvalue", pool.getSession());
} else {
obj.put("successFlag","2");
obj.put("msg","等待扫描");
}
}
} catch (Exception e) {
obj.put("successFlag","0");
obj.put("msg","网络错误,请稍后重试");
e.printStackTrace();
}
return obj ;
}
/**
* 这个是扫码成功后,要跳转到这里执行登录逻辑,然后跳转首页面
* @return
* @throws IOException
*/
@RequestMapping("success")
public String success() throws IOException{
//执行登录逻辑,清除码.......
HttpServletRequest request = getRequest();
String uuid = request.getParameter("uuid");
ScanPool pool = (ScanPool)redisTemplate.boundHashOps(SCAN_LOGIN_HASH).get(uuid);
String account = pool.getAccount();
String accountUuid = pool.getAccountUuid();
redisTemplate.boundHashOps(SCAN_LOGIN_HASH).delete(uuid);
//因为uuid是唯一的,所以pool.sessionId和request.getSession().getId()的值一定是一样的
return scanSuccessLogin(request.getSession(),account,accountUuid,request);//登录逻辑
}
码对象
import java.io.Serializable;
/**
* 扫描类,创建时间,扫描状态,
* @author admin
*
*/
public class ScanPool implements Serializable{
private static final long serialVersionUID = -9117921544228636689L;
private Object session ;//其实这个没啥用
//创建时间
private Long createTime = System.currentTimeMillis();
//扫描状态
private boolean scanFlag = false;
public boolean isScan(){
return scanFlag;
}
public void setScan(boolean scanFlag){
this.scanFlag = scanFlag;
}
private String account;
private String accountUuid;
public String getAccountUuid() {
return accountUuid;
}
public void setAccountUuid(String accountUuid) {
this.accountUuid = accountUuid;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public synchronized boolean getScanStatus(){
if (isScan()){
return true;
}
return false;
}
/**
* 扫码之后设置扫码状态
* @param token
* @param id
*/
public synchronized void scanSuccess(){
try{
setScan(true);
} catch (Exception e){
e.printStackTrace();
}
}
public Long getCreateTime()
{
return createTime;
}
public void setCreateTime(Long createTime)
{
this.createTime = createTime;
}
public Object getSession() {
return session;
}
public void setSession(Object session) {
this.session = session;
}
}