首先不妨先看下Android:扫描二维码登陆原理:  大概总结下这个过程就是:
- 服务器生成全局唯一会话
ID ,并返回二维码、过期时间; - 用户扫描二维码,提交改会话
ID ,用户基本信息到服务器; PC 端在过期时间内一直轮询,如果用户扫码,服务器记录状态,PC 端跳转到已登录页面;
那不妨按照这个思路来写一个简单的Demo 。
1. 后台
首先,我们需要搭建一个简易版的后台。在IDEA中创建SpringBoot项目,然后添加thymeleaf和session的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
然后,我们这里模拟Demo,不需要数据库,故而模拟系统中已注册用户,可以使用Set集合来存储。模拟记录系统登录用户来使用Map存储。然后将之封装到一个Bean对象中。同样二维码这里也直接使用一张静态二维码,内容为:
http://192.168.1.102:8080/scanlogin?uuid=Wxfsfdfskfj==
@Component
public class UserMap {
private static Map<String, User> loginmap = new HashMap<String, User>();
private static Set<User> users = new HashSet<>();
项目结构如下: 
添加对应的html页面,在loginpage.html中显示二维码,并进行轮询:
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<center>
<div>login page!</div>
<img src="code.png" style="width=300px; height=300px;">
<div style="color:red;" id="info"></div>
</center>
<script>
var getting = {
url:'http://192.168.1.102:8080/checkscan',
data:{
"uuid": "Wxfsfdfskfj=="
},
dataType:'text',
success:function(res) {
if(JSON.parse(res)){
$('#info').html("已授权");
setTimeout(function(){
window.location.href = 'http://192.168.1.102:8080/home';
},1000);
}
else{
$('#info').html("未授权");
}
setTimeout(function(){
$.ajax(getting);
},1000);
},
error:function(res){
$('#info').html("请求发生了未知错误!");
}
};
$.ajax(getting)
</script>
</body>
</html>
然后就是Controller的Android端扫码后确认授权接口:
@GetMapping(value="/scanlogin")
@ResponseBody
public String scanLogin(HttpSession session,
@RequestParam("uuid") String uuid,
@RequestParam("userid") String userid,
@RequestParam("username") String name){
String UUID = "Wxfsfdfskfj==";
User user = new User(name, userid);
if(uuid.equals(UUID) && userService.checkUserInfo(user)){
UserMap.loginUser(UUID, new User(name, userid));
return "SUCCESS LOGIN!";
}else {
return "ERROR UserInfo!";
}
}
在请求主页home 的时候,需要判断用户是否登录,即校验Session 。
@GetMapping(value="/home")
public String getHomePage(HttpSession session){
String username = (String) session.getAttribute("name");
if(username == null){
return "redirect:/notlogin";
}
return "homepage";
}
前端ajax 轮询接口:
@GetMapping(value="/checkscan")
@ResponseBody
public boolean hasScan(HttpSession session,
@RequestParam("uuid") String uuid){
User scanedUser = UserMap.getScanUserByUUID(uuid);
if(scanedUser == null)
return false;
if(userService.hasUser(scanedUser.getUserID())){
String username = (String) session.getAttribute("name");
if(username == null){
session.setAttribute("name", uuid);
}
return true;
}
return false;
}
2. Android端
首先添加两个依赖:
implementation 'cn.yipianfengye.android:zxing-library:2.2'
implementation 'com.squareup.okhttp3:okhttp:3.2.0'
zxing是一个使用非常简单的扫码封装。项目地址:here
按照文档说明进行集成即可。页面非常简单,两个页面: MainActivity中显示一个按钮,模拟扫码入口,调用后扫码后返回结果; 
LoginActivity中进行提示用户确认授权,进行确认接口请求;  MainActivity中添加权限申请:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.INTERNET" />
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.CAMERA}, 1);
}
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.INTERNET}, 1);
}
进行点击按钮的跳转
Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
startActivityForResult(intent, 1);
复写onActivityResult 方法(CaptureActivity中调用setResult,然后finish()):
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 1){
Toast.makeText(this, String.valueOf(null != data), Toast.LENGTH_LONG).show();
if (null != data) {
Bundle bundle = data.getExtras();
if (bundle == null) {
return;
}
if (bundle.getInt(CodeUtils.RESULT_TYPE) == CodeUtils.RESULT_SUCCESS) {
String result = bundle.getString(CodeUtils.RESULT_STRING);
Intent intent = new Intent(MainActivity.this, LoginActivity.class);
intent.putExtra("url", result);
startActivity(intent);
} else if (bundle.getInt(CodeUtils.RESULT_TYPE) == CodeUtils.RESULT_FAILED) {
Toast.makeText(MainActivity.this, "解析二维码失败", Toast.LENGTH_LONG).show();
}
}
}
}
在启动的LoginActivity中进行确认授权接口进行GET请求:
public void requestLogin() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
client.newCall(request).enqueue(new Callback() {
Message msg = new Message();
@Override
public void onFailure(Call call, IOException e) {
msg.obj="请求失败";
handler.sendMessage(msg);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
if (response.code() == 200) {
String result = response.body().string();
if(result.equals("SUCCESS LOGIN!")){
msg.obj = "授权成功!";
}
}
}else{
msg.obj = "出现了未知错误: " + response.code();
}
handler.sendMessage(msg);
}
});
}
3. 效果:
扫码前,我们直接home;扫码后Ajax处理直接跳转到主页home。 
Demo地址
https://github.com/baiyazi/ScanQrCodeAndAutoLoginDemo
|