国际惯例:先上图:
tip,用ssm做的后台。
具体思路:
1.准备一个bean(存放用户信息的类和记录是否同意登录的Boolean属性) 和 一个map,存放类型为bean(存放用户信息的类)
2.后台生成二维码信息 和 生成二维码信息的时间,返回给web端,便于前端生成二维码和记录其生成的时间 和记录二维码信息
3.后台写一个esauCode方法,供app端调用,当app端扫码把扫的二维码信息返回的后台,并用该信息作为map的key,创建一个key
4.后台写ifEsauCode方法,根据情况返回结果(0代表未扫码,1代表已经扫码,-1代表二维码信息过期),web端一直轮询该方法,判断用户是否扫码
(ps:ifEsauCode形参有二维码信息和其生成的时间,当app端扫码后,就用该信息作为key,放进map里,然后该方法判断是否存在该key,就知道是否扫码)
5.后台写makeSelection,供app调用,当用户允许或不允许登录后,就在刚才map对应的key值里的Boolean属性赋值,做是否同意登录。
6.当扫码后,web再来一个轮询调用后台写agreeOrNot方法,形参有二维码的信息,用该信息获取map对应key值,当该值里的Boolean属性为true时,说明同意登录,反之则反之。
下面为具体代码:
后台:
Step 1 :创建bean,存放用户信息的类和记录是否同意登录的Boolean属性
public class UserLoginVo implements Serializable{
private static final long serialVersionUID = 1L;
private Boolean agreeOrNot; //记录用户是否同意登录
private String userName;//记录用户名
public Boolean getAgreeOrNot() {
return agreeOrNot;
}
public void setAgreeOrNot(Boolean agreeOrNot) {
this.agreeOrNot = agreeOrNot;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
还要一个bean,是记录二维码信息和其生成的时间,返回给web用的:
public class TokenVo implements Serializable{
private static final long serialVersionUID = 1L;
private String qrMessage;//二维码信息
private String date;//生成时间
public String getQrMessage() {
return qrMessage;
}
public void setQrMessage(String qrMessage) {
this.qrMessage = qrMessage;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
}
Step 2 :声明一个map,记录用户是否扫码和是否同意登录:
private Map<String, UserLoginVo> userLoginMap=new HashMap<String, UserLoginVo>();
Step 3 :创建二维码信息,和其创建的时间
@ResponseBody
@RequestMapping(value = "creatQRMessage",produces = "application/json;charset=UTF-8")
public String creatQRMessage() {
//创建UUID
String uuid=UUID.randomUUID().toString().replace("-", "").toUpperCase();
/**
* Path.QR_SUBMIT:扫码后要提交地址(例如:"http://192.168.43.9:8080/CarTaketing/User/esauCode.do",即是要执行服务端的方法,
* 这是为了方便app端扫码后是执行登录进而做自己需要的操作,若扫到其它地址,app端直接跳转到该地址)。
* uuid:二维码唯一的标志符
*/
String qrMessage=Path.QR_SUBMIT+"?k="+uuid;//二维码信息,是一个带参数的地址
//返回qrMessage 和 qrMessage 创建的时间给客户端
String dateStr=String.valueOf(new Date().getTime());
TokenVo tokenVo=new TokenVo();
tokenVo.setQrMessage(qrMessage);
tokenVo.setDate(dateStr);
return JSON.toJSONString(tokenVo);
}
Step 4 :web启动轮询不断执行该方法, 判断用户是否扫码,没有则返回 “0” ,扫了则返回 “1”,验证码过期了返回 "-1"
/**
* 轮询 判断用户是否扫码,没有则返回 "0" ,扫了则返回 "1",验证码过期了返回 "-1"
* @param qrMessage 二维码信息
* @param dateStr 创建二维码信息的时间
* @return
*/
@ResponseBody
@RequestMapping(value = "ifEsauCode",produces = "application/json;charset=UTF-8")
public String ifEsauCode(String qrMessage,String dateStr) {
long newDate=new Date().getTime();
long date=Long.valueOf(dateStr);
String result="0";
if(userLoginMap.get(qrMessage)!=null) {//判断是否扫码
result="1";
}else if(newDate-date>=60000){//判断是否过期,过期将重新生成二维码
result="-1";
}
return JSON.toJSONString(result);
}
Step 5 :app端调用:userLoginMap添加键值对,标记该二维码已经被扫码(执行后,Step 4 就知道是否已经扫码了):
//app端调用:userLoginMap添加键值对,标记该二维码已经被扫码
@ResponseBody
@RequestMapping(value = "esauCode")
public void esauCode(String k) {
if(k!=null) {
userLoginMap.put(k, new UserLoginVo());
}
}
Step 6 :当扫码后,web启动轮询不断执行该方法,判断是否同意登录
/**
* 判断是否同意登录
* @return 返回1则同意登录,0为反之,-1则未做出选择
*/
@ResponseBody
@RequestMapping(value = "agreeOrNot",produces = "application/json;charset=UTF-8")
public String agreeOrNot(HttpServletRequest request,String qrMessage) {
String result="-1";
UserLoginVo uLoginVo=userLoginMap.get(qrMessage);
if(uLoginVo!=null && uLoginVo.getAgreeOrNot()!=null) {
if(uLoginVo.getAgreeOrNot()) {
//说明app端已经同意登录
User user=new User();
user.setUsername(uLoginVo.getUserName());
request.getSession().setAttribute("user", user);//保存用户
result = "1";
}else {
//不同意登录
result = "0";
}
//清除qrMessage在userLoginMap里的信息
userLoginMap.remove(qrMessage);
}
return JSON.toJSONString(result);
}
Step 7 :app端点击同意或不同意登录后要执行
/**app端调用:
* 是否同意web登录
* @param request
* @param qrMessage 二维码信息
* @param agreeOrNot 是否允许登录
* @param user 用户信息
* @return
*/
@ResponseBody
@RequestMapping(value = "makeSelection",produces = "application/json;charset=UTF-8")
public String makeSelection(String qrMessage,Boolean agreeOrNot,User user) {
Message message = new Message(400,"信息异常");
if(agreeOrNot!=null) {
//同意登录
if(agreeOrNot) {
if(user!=null && userLoginMap.get(qrMessage) !=null) {
//先判断app端传过来的信息是否对应
User user2=iUserService.selectUserByKey(user.getUserid());
if(user2.getPasord().equals(MD5Util.getMD5(user.getPasord()))) {
//标记用户同意登录
userLoginMap.get(qrMessage).setAgreeOrNot(true);
userLoginMap.get(qrMessage).setUserName(user.getUsername());
message=new Message(200,"已授权");
}
}
//不同意
}else {
//标记用户不同意登录
userLoginMap.get(qrMessage).setAgreeOrNot(false);
message=new Message(200,"取消登录");
}
}
return JSON.toJSONString(message);
}
web端
Step 1 :引入生成二维码的插件:QRCode.js
GitHub地址
Step 2 :body部分,有3个页面,分别为密码登录界面 和 二维码界面和 提醒用户同意登录界面(布局和样式根据自己的情况而定);
还有隐藏的存放 qrMessage 和 生成qrMessage的时间的 input 标签
<!-- 密码登录界面-->
<div id="loginByPassword" class="loginbox" style="margin:auto;background-color: #61abfd;height: 270px">
<i class="loginheader">客 运 管 理 系 统</i>
<ul style="margin:auto" id="dengLvUl">
<li class="li1">账号:<input id="username" name="username" class="zhanghao" type="text" placeholder="请输入账号" value="${cookie.userName.value}" /></li>
<li class="li2">密码:<input id="pasord" name="pasord" class="mima" type="password" placeholder="请输入密码" value="${cookie.userPassword.value}" /></li>
<li class="li2">验证码:<input style="width: 130px;" name="code" id="code" id="yzm" type="text" class="mima" placeholder="请输入验证码" autocomplete="off" /><img id="img" style="width: 90px;margin-left: 72px" alt="" src="${ctx}/User/pict.do"><li/>
<li><input type="checkbox" id="isRememb" name="isRememb" style="margin-left: 20px">记住密码<button class="denglu" onclick="check()" style="margin-left: 50px" id="dengLv">登录</button><button id="erWeiMa" type="button" style="margin-left: 30px">二维码登录</button><li/>
</ul>
</div>
<!-- 二维码界面 -->
<div id="loginByCode" class="loginbox" style="margin:auto;background-color: #61abfd;height: 270px;display: none">
<div style="margin-top: 10px">
<!-- 存放二维码 -->
<div id="image" style="margin:auto;height: 200px;width:200px;"></div>
</div>
<div style="margin-top:10px;margin-left:140px">
<button id="miMaDengLv" type="button" style="margin-left: 40px;display: inline">密码登录</button>
</div>
<!--存放 qrMessage 和 生成qrMessage的时间的 input 标签 -->
<input id="qrMessage" type="hidden">
<input id="date" type="hidden">
</div>
<!-- 提示用户确认登录界面 -->
<div id="sureLogin" class="loginbox" style="margin:auto;background-color: #61abfd;line-height: 200px;display: none">
<p style="color:white;text-align:center" >手机请确认登录</p>
</div>
Step 3:script 标签 里的代码(javascript代码或js):
//Step 1 :声明 二维码
var qrcode=new QRCode(document.getElementById("image"), {
width:200,
height:200,
colorDark:"#000000",//二维码颜色
colorLight:"#ffffff",//二维码背景颜色
correctLevel : QRCode.CorrectLevel.H//容错级别
});
//是否执行轮询(用来启动或停止轮询)
var b=false;
//Step 2 :点击二维码登录时,隐藏密码登录 显示扫码登录 并生成二维码
$("#erWeiMa").click(function(){
//隐藏密码登录显示扫码登录
document.getElementById("loginByCode").style.display = "block";
document.getElementById("loginByPassword").style.display = "none";
//获取服务端的qrMessage 和 其生成的时间 存放好,并根据该qrMessage生成二维码
$.post('${ctx}/User/creatQRMessage.do',function(data){
//存放信息
$("#qrMessage").val(data.qrMessage);
$("#date").val(data.date);
//生成二维码
qrcode.makeCode(data.qrMessage);
//启动轮询的方法
b=true;
checkQR();
},"json");
})
//Step 3 :轮询,不断判断用户是否已经扫码
function checkQR(){
if(b){//true为启动轮询
//获取信息
var qrMessage=$("#qrMessage").val();
var date=$("#date").val();
//执行服务端的ifEsauCode方法,判断是否已经扫码或过期
$.post('${ctx}/User/ifEsauCode.do?qrMessage='+qrMessage+"&dateStr="+date,function(data){
if(data=="1"){//说明已经扫码
//显示确认登录界面 隐藏扫码界面
document.getElementById("sureLogin").style.display = "block";
document.getElementById("loginByCode").style.display = "none";
//新轮询 监听用户是否确认登录
checkAgreeOrNot();
}else if(data=="-1"){//说明二维码过期,更新二维码
//重新生成二维码
$.post('${ctx}/User/creatQRMessage.do',function(data){
//存放信息
$("#qrMessage").val(data.qrMessage);
$("#date").val(data.date);
//生成二维码
qrcode.makeCode(data.qrMessage);
//启动轮询的方法
checkQR();
},"json");
}else if(data=="0"){
checkQR();
}
},"json");
}
}
//Step 4 :轮询 ,判断用户是否同意登录
function checkAgreeOrNot(){
var qrMessage=$("#qrMessage").val();
//执行服务端agreeOrNot方法,判断是否同意登录
$.post('${ctx}/User/agreeOrNot.do?qrMessage='+qrMessage,function(data){
if(data=="1"){//同意登录
//跳转到成功登录界面
window.location.href ="${ctx}/User/toMain.do";
}else if(data=="0"){//取消登录
//隐藏确认登录界面 显示扫码界面
document.getElementById("loginByCode").style.display = "block";
document.getElementById("sureLogin").style.display = "none";
//重新启动判断是否扫码的轮询 监听是否扫码
checkQR();
}else if(data=="-1"){//用户为做出选择,继续轮询
checkAgreeOrNot();
}
},"json");
}
//此处是点击密码登录,显示密码登录和隐藏验证码登录,不是重点
$("#miMaDengLv").click(function(){
document.getElementById("loginByCode").style.display = "none";
document.getElementById("loginByPassword").style.display = "block";
b=false;//停止轮询
})
app端:
Step 1 :这里只从扫码得到的结果开始写,具体如何扫码,请看自己的扫码笔记 或 查看自己的博客 :
安卓扫码例子的地址
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
//==扫码登录
IntentResult result = IntentIntegrator.parseActivityResult(requestCode,resultCode,data);
if(result!=null){
//==是否扫到内容
if (result.getContents()!=null){
//处理扫码后要执行的方法(result.getContents()为扫到的内容)
webLogin(result.getContents());
}else{
Toast.makeText(MainActivity.this,"取消扫码",Toast.LENGTH_LONG).show();
}
}else{
super.onActivityResult(requestCode, resultCode, data);
}
Step 2 :根据扫描的结果做相关的操作
(ps:OkHttpTool为发送网络请求的工具,具体使用请查看自己的笔记(OkHttp3 发送网络请求服务器)
或访问该地址:地址)
//执行服务端的扫码登录
private void webLogin(String contents) {
//扫码登录方法
String url=ServiceUtil.getUserUrl("esauCode");
//判断是否为自己的扫码登录方法,当二维码信息(即contents)包含url,就说明是执行扫码登录
if(contents.contains(url)){
//==为自己的扫码登录,告诉服务器已经扫到码了
OkHttpTool.httpPost(url+"?k="+contents, null, new OkHttpTool.ResponseCallback() {
@Override
public void onResponse(boolean isSuccess, int responseCode, String response, Exception exception) {
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
if(isSuccess&&responseCode==200){
//提示用户是否确认登录web端
AlertDialog.Builder builder=new AlertDialog.Builder(MainActivity.this);
builder.setTitle("确认登录电脑?");
//确认登录
builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
makeSelection(contents,true);//确认登录要执行的方法
dialog.dismiss();
}
});
//取消登录
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
makeSelection(contents,false);//取消登录要执行的方法
dialog.dismiss();
}
});
builder.show();
}else{
Toast.makeText(MainActivity.this,"连接服务器失败,请检查网络或稍后重试",Toast.LENGTH_LONG).show();
}
}
});
}
});
}else{
//==不是自己的扫码登录
//判断是否是地址,是则跳转,否则,提示用户二维码的信息
String value=contents.substring(0,4);
if (value.equals("http")){
Uri uri = Uri.parse(contents);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}else{
Toast.makeText(MainActivity.this,"二维码信息为:"+contents,Toast.LENGTH_LONG).show();
}
}
}
Step 3 :确认登录或取消登录
public void makeSelection(String qrMessage,boolean b){
String url=ServiceUtil.getUserUrl("makeSelection");
Map<String,Object> map=new HashMap<>();
map.put("qrMessage",qrMessage);
map.put("agreeOrNot",b);
map.put("userid",myApplication.getUserBean().getUserid());
map.put("username",myApplication.getUserBean().getUsername());
map.put("pasord",myApplication.getUserBean().getPasord());
OkHttpTool.httpPost(url, map, new OkHttpTool.ResponseCallback() {
@Override
public void onResponse(boolean isSuccess, int responseCode, String response, Exception exception) {
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
if (isSuccess&&responseCode==200){
Message message=JSON.parseObject(response,Message.class);
Toast.makeText(MainActivity.this,message.getText(),Toast.LENGTH_LONG).show();
}else{
Toast.makeText(MainActivity.this,"连接服务器失败,请检查网络后稍后重试",Toast.LENGTH_LONG).show();
}
}
});
}
});
}
ok!!!打完收工