【app扫码登录】Java app扫码登录功能实现

发布于:2022-11-27 ⋅ 阅读:(474) ⋅ 点赞:(0)

一、实现原理

  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<>();
        // uuid
        String 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_token
        if ((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_token
            CommonResult<?> 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);
    }
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到