一、实现原理

  1. PC 端发送 “扫码登录” 请求,服务端生成二维码 uuid,并存储二维码的过期时间、状态等信息。
  2. PC 端获取二维码并显示。
  3. PC 端开始轮询检查二维码的状态(2s一次),二维码最初为 “待扫描” 状态。
  4. 手机端扫描二维码,获取二维码 uuid。
  5. 手机端向服务端发送 “扫码” 请求,请求中携带二维码 uuid、手机端 access_token。
  6. 服务端验证手机端用户的合法性,验证通过后将二维码状态置为 “待确认”,并将用户信息与二维码关联在一起。
  7. PC 端轮询时检测到二维码状态为 “待确认”。
  8. 手机端向服务端发送 “确认登录” 请求,请求中携带着二维码 uuid、 access_token。
  9. 服务端验证 access_token和uuid绑定的access_token是否一致,验证通过后将二维码状态置为 “已确认”。
  10. PC 端轮询时检测到二维码状态为 “已确认”,并获取到了 PC 端 access_token,之后 PC 端不再轮询。
  11. PC 端通过 PC 端 access_token 访问服务端。

二、PC端接口

1.二维码状态

public enum QrCodeStatusEnum {WAITING(10000,"待扫描"),SCANNED(20000,"待确认"),CONFIRMED(30000,"已确认"),INVALID(40000,"二维码无效"),CANCEL(50000,"已取消");private Integer statusCode;private String statusValue;QrCodeStatusEnum(Integer statusCode, String statusValue) {this.statusCode = statusCode;this.statusValue = statusValue;}public Integer getStatusCode() {return statusCode;}public String getStatusValue() {return statusValue;}

2.获取二维码

public CommonResult createQrImg() {Map<Object, Object> resultMap = new HashMap<>();// uuidString uuid = UUID.randomUUID().toString();LoginTicket loginTicket = new LoginTicket();// 二维码最初为 WAITING 状态loginTicket.setStatusCode(QrCodeStatusEnum.WAITING.getStatusCode());loginTicket.setUuid(uuid);//存入 redis 过期时间5分钟redisUtils.set(uuid, loginTicket, UUID_EXPIRE_TIME);QrConfig config = new QrConfig(300, 300);//附带logo//config.setImg(file);// 设置边距,既二维码和背景之间的边距//config.setMargin(3);// 高纠错级别//config.setErrorCorrection(ErrorCorrectionLevel.H);// 设置前景色,既二维码颜色(青色)//config.setForeColor(new Color(0, 60, 130).getRGB());// 设置背景色(灰色)//config.setBackColor(new Color(242, 242, 242).getRGB());//二维码内容Map<Object, Object> values = new HashMap<>();values.put("uuid", uuid);byte[] bytes = QrCodeUtil.generatePng(JSON.toJSONString(values), config);String qrCode = Base64.getEncoder().encodeToString(bytes);resultMap.put("uuid", uuid);resultMap.put("QrCode", qrCode);resultMap.put("statusCode", QrCodeStatusEnum.WAITING.getStatusCode());return CommonResult.ok(resultMap);}

3.扫描二维码

public Map<String, Object> scanQrCodeImg(String uuid) {// 避免多个移动端同时扫描同一个二维码lock.lock();Map<String, Object> data = new HashMap<>();try {LoginTicket loginTicket = (LoginTicket) redisUtils.get(uuid);// redis 中 key 过期后也可能不会立即删除Long expired = redisUtils.getExpireForSeconds(uuid);boolean valid = loginTicket != null &&QrCodeStatusEnum.WAITING.getStatusCode().equals(loginTicket.getStatusCode()) &&expired != null &&expired >= 0;if (valid) {User user = UserUtil.getCurrentUser();if (user == null) {throw new RuntimeException("用户未登录");}// 修改扫码状态loginTicket.setStatusCode(QrCodeStatusEnum.SCANNED.getStatusCode());// 将二维码与用户进行关联loginTicket.setUserId(user.getUserid());redisUtils.set(uuid, loginTicket, expired);data.put("statusCode", QrCodeStatusEnum.SCANNED.getStatusCode());} else {data.put("statusCode", QrCodeStatusEnum.INVALID.getStatusCode());}return data;} finally {lock.unlock();}}

4.确认登录

//operationType:操作类型  confirm:确认 ;cancel:取消登录
public CommonResult confirmLogin(String uuid, String operationType) {Map<String, Object> data = new HashMap<>();User currentUser = UserUtil.getCurrentUser();//校验"确认登录"和"扫码登录"是同一个人if (currentUser == null) {throw new RuntimeException("请先登录");}Long expired = redisUtils.getExpireForSeconds(uuid);if (expired == null || expired == 0 || expired ==-2) {data.put("statusCode", QrCodeStatusEnum.INVALID.getStatusCode());return CommonResult.ok(data);} else {LoginTicket loginTicket = (LoginTicket) redisUtils.get(uuid);if (!(currentUser.getUserid()).equals(loginTicket.getUserId())) {throw new RuntimeException("非法操作!");}if (operationType.equals("confirm")) {lock.lock();try {loginTicket.setStatusCode(QrCodeStatusEnum.CONFIRMED.getStatusCode());redisUtils.set(uuid, loginTicket, expired);data.put("statusCode", QrCodeStatusEnum.CONFIRMED.getStatusCode());} finally {lock.unlock();}} else if (operationType.equals("cancel")) {lock.lock();try {loginTicket.setStatusCode(QrCodeStatusEnum.CANCEL.getStatusCode());redisUtils.set(uuid, loginTicket, expired);data.put("statusCode", QrCodeStatusEnum.CANCEL.getStatusCode());} finally {lock.unlock();}}}return CommonResult.ok(data);}

5.PC 端轮询获取二维码状态(2s一次)

//currentStatus: 当前二维码状态
public CommonResult getQrCodeStatus(HttpServletRequest request, HttpServletResponse response,@RequestParam String uuid ,@RequestParam Integer currentStatus) {Map<Object, Object> resultMap = new HashMap<>();LoginTicket loginTicket = (LoginTicket) redisUtils.get(uuid);if (loginTicket ==null) {resultMap.put("statusCode", QrCodeStatusEnum.INVALID.getStatusCode());return CommonResult.ok(resultMap);}Integer statusCode = loginTicket.getStatusCode();//二维码状态没有更新if ((currentStatus).equals(statusCode)) {resultMap.put("statusCode", statusCode);}User user = userService.selectByUserId(loginTicket.getUserId());//二维码状态为待扫描if ((statusCode).equals(QrCodeStatusEnum.WAITING.getStatusCode())) {resultMap.put("statusCode", statusCode);}//二维码状态为已扫描if ((statusCode).equals(QrCodeStatusEnum.SCANNED.getStatusCode())) {resultMap.put("statusCode", statusCode);}//二维码状态为已取消if ((statusCode).equals(QrCodeStatusEnum.CANCEL.getStatusCode())) {resultMap.put("statusCode", statusCode);redisUtils.delete(uuid);}//二维码状态为已确定 用户确认后为 PC 端生成 access_tokenif ((statusCode).equals(QrCodeStatusEnum.CONFIRMED.getStatusCode())) {Map<String, String> parameters=new HashMap<>();parameters.put("client_id","android");parameters.put("grant_type","password");parameters.put("password",user.getPassword());if (user.getIsEnterprise() != null && user.getIsEnterprise()==1) {//企业主用户parameters.put("userType","enterprise");}else if (user.getIsEnterprise() != null && user.getIsEnterprise()==0){//普通用户parameters.put("userType","user");}parameters.put("username",user.getUsername());parameters.put("client_id","android");//调用登录接口生成access_tokenCommonResult<?> loginResult = usersController.login(request, response, parameters);if (loginResult != null && loginResult.getCode()==0) {//获取access_token成功DefaultOAuth2AccessToken data = (DefaultOAuth2AccessToken) loginResult.getData();resultMap.put("access_token",data.getValue());resultMap.put("token_type",data.getTokenType());resultMap.put("refresh_token",data.getRefreshToken());resultMap.put("expires_in",data.getExpiresIn());resultMap.put("scope",data.getScope());resultMap.put("username",data.getAdditionalInformation().get("username"));resultMap.put("statusCode", statusCode);}else {//获取access_token失败throw  new RuntimeException("获取accessToken失败");}}return CommonResult.ok(resultMap);}

【app扫码登录】Java app扫码登录功能实现相关推荐

  1. Redis源码和java jdk源码中hashcode的不同实现

    一.redis实际上是使用了siphash 这个比较简单,我说的简单是指redis代码比较少不像jdk一样调用C++代码调用栈非常深. 先看这个rehashing.c 主要就是dictKeyHash函 ...

  2. ios 登录 java 后台,IOS苹果登录sign in with apple后端校验

    IOS苹果登录sign in with apple后端校验 最近新开发的app在IOS平台app store connent提审的时候,被拒了,原因是app上如果有接第三方登陆(比如微信,微博,fac ...

  3. java加按钮_如何从零开始对接第三方登录(Java版):QQ登录和微博登录

    阅读本文约需要8分钟 大家好,我是你们的导师,我每天都会在这里给大家分享一些干货内容(当然了,周末也要允许老师休息一下哈).上次老师跟大家分享了HashMap和TreeMap的知识,今天跟大家分享下对 ...

  4. 如何从零开始对接第三方登录(Java版):QQ登录和微博登录

    前言 个人网站最近增加了评论功能,为了方便用户不用注册就可以评论,对接了QQ和微博这2大常用软件的一键登录,总的来说其实都挺简单的,可能会有一点小坑,但不算多,完整记录下来方便后来人快速对接. 后台设 ...

  5. 深深的码丨Java ArrayList 源码透析

    ArrayList 本质是通过一个可变长的数组来存储不定量的元素,且当元素通过不同方式被添加存储时,总是先计算自身容量,若符合扩容标准则对数组进行扩容并拷贝原有元素 本文将基于 JDK8 对 Arra ...

  6. 深深的码丨Java HashMap 源码透析

    Hashmap 的数据结构基础是基于一维数组实现的,向其添加元素时通过计算key的hash值来确定具体存储位置.添加元素过程中若出现hash冲突,也就是N个元素key的hash值相等,处理方式为:将元 ...

  7. java桌面通讯录源码_TONGXUNLU JAVA通讯录源码 JAVA课程设计源码 讯友桌面通讯录 通讯录管理 - 下载 - 搜珍网...

    讯友桌面通讯录/.classpath 讯友桌面通讯录/.project 讯友桌面通讯录/.settings/org.eclipse.jdt.core.prefs 讯友桌面通讯录/bin/com/zzk ...

  8. java中如何实现qq登录与微博登录,如何从零开始对接第三方登录(Java版):QQ登录和微博登录(2)...

    到了这一步基本上涉及第三方的就结束了,是不是很简单?后面无非就是如何插入.如何存放token.写入session等. 有几点注意事项: 相关文档官网已经写得非常细了,但是相当乱: 这个我就不详细讲了, ...

  9. 易语言注册码生成以及验证源码_短信验证码的登录流程

    点击上方"Java基基",选择"设为星标" 做积极的人,而不是积极废人! 源码精品专栏 原创 | Java 2020 超神之路,很肝~ 中文详细注释的开源项目 ...

  10. java如何输出ascll码_Java如何打印ASCII码

    Java如何打印ASCII码 1 Java打印ASCII码的方式 美国信息交换标准代码的ASCII缩写.它是一个7位字符集,包含128个(0到127)个字符.它代表一个字符的数值.例如,ASCII值的 ...

最新文章

  1. AI破解脑电波,准确率超80%!高度还原你眼中最美的ta
  2. 牡丹江2021高考成绩查询,2020年黑龙江牡丹江成人高考成绩查询入口(已开通)...
  3. java代码连接数据库
  4. JavaSist之ClassPool
  5. 牛客题霸 NC9 二叉树中是否存在节点和为指定值的路径
  6. 设置progressbar进度条颜色
  7. redis 慢查询日志
  8. Oracle 游标的练习
  9. vue 利用axios请求接口下载excel
  10. Android 自己主动化測试之------ Monkey工具
  11. python清空画布_关于python:如何清除Tkinter画布?
  12. linux用dd命令刻录u盘,巧用linux dd命令刻录启动U盘
  13. 自用软件系列之:迅雷X ,无广告,纯净版
  14. 机械专业热门选题 论文+图纸+PPT
  15. linux卷影复制功能,vssadmin 卷影复制服务管理命令行工具
  16. 工业大数据:中国智造下的“数字新基建”
  17. OSChina 周四乱弹 —— 神转折段子?
  18. 如何快速的转发别人的CSDN博客(转载)
  19. 写给想加入淘宝的在校同学--广州实习生招聘感想
  20. java操作storm_Storm Trident

热门文章

  1. React框架使用Element ui 组件
  2. 九联UNT401H-Hi3798MV310-零配置/芒果/南传/百视通等-通刷固件包
  3. webpack是干什么用的,以及它的优点
  4. 图像在计算机中通过什么方式表示_机器视觉新突破,神经网络让图像处理速度提升2万倍...
  5. 【高等数学笔记】f(x)=(x-1)(x-2)²(x-3)³(x-4)⁴的拐点是?
  6. r java_R语言rJava包安装载入及JAVA环境配置
  7. android 五彩纸屑动画,HTML5-Canvas五彩纸屑飘落动画特效
  8. WinSCP 使用教程
  9. 编程之美之中国象棋问题
  10. wwise移植到linux平台,设立Wwise项目 - Lumberyard 用户指南