栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

用Android和node.js实现扫码登录

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

用Android和node.js实现扫码登录

实现思路

step 1: 网页端提供二维码

step 2: 手机端登录,并保存token,手机扫码后向网页端发送token

step 3: 网页端通过jstoken解析token,向后端服务器获取用户信息

step 4: 最总实现扫码登录

即:需要被扫码客户端向服务端请求创建websocket(后称ws),服务端返回房间号也就是id,然后让可以扫码的设备登录,获取token(登录靠token实现,不用账号和密码,只有在生成token时才需要这些隐私信息),再通过创建的房间号加入ws,手动授权后向加入的房间发送token,最终实现扫码登录

搭建服务端

websocket.js

const WebSocket = require('ws')

const wss = new WebSocket.Server({
  port: 3001
})

var id = 1 // ws 链接的最开始id
wss.on('connection', function connection (ws){
  // 服务器广播
  ws.on('message', function message(data) {
    // 处理消息
    try{
      data = JSON.parse(data)
    }catch(e){
      console.log('拦截到一条消息')
      return
    }
    // 是连接状态
    if (ws.readyState === WebSocket.OPEN) {
      switch(data.event){ // 判断消息事件
        case 'heartbeat': // 心跳检测
          if(data.msg === 'ping'){ // 客户端检测
            ws.send(JSON.stringify({
              event: 'heartbeat',
              msg: 'pong'
            }))
          }
          else if(data.msg === 'pong'){ // 服务端检测
            ws.isAlive = true
          }
          return; // 事件处理完成
        case 'succeedLogin':
          console.log('收到登录成功请求')
          wss.clients.forEach((client) => {
            // 给客户端发送登录成功提示!
            if(client.id + ''=== data.id + ''){
              client.send(JSON.stringify({
                event: 'succeedLogin',
                id: data.id
              }))
              console.log('服务器将消息发给了' + client.id)
            }
          })
          return; // 事件处理完成
      }
    }
    
    console.log("服务器收到消息:")
    console.log(data)
    wss.clients.forEach((client) => {
      // 是连接状态
      if(client.readyState === WebSocket.OPEN) {
         // 发给自己
        if(ws === client){
          switch(data.event){
            // 创建房间
            case 'setUp':
              ws.send(JSON.stringify({
                event: 'setUp',
                id: id
              }))
              ws.isAlive = true
              ws.id = id + ''
              ws.type = 'serve'
              console.log('已经创建ID为: '+id+' 链接WS')
              id++
              return
          }
        }
        // 不发给自己,发给指定id
        else if(ws !== client && client.id ===data.id){
          switch (data.event){
            case 'login':
              console.log('开始发送登陆信息')
              client.send(JSON.stringify({
                event: 'login',
                token: data.token
              }))
              console.log('服务器将消息发给了' + client.id)
              return
            case 'scanfed': // 用户扫码登录入口
              ws.type = 'client' // 设置扫描端为客户端
              ws.id = data.id + ''
              ws.isAlive = true
              console.log('开始发送扫描事件信息')
              client.send(JSON.stringify({
                event: 'scanfed',
                id: data.id
              }))
              console.log('服务器将消息发给了' + client.id)
              return
            
          }
        }
      }
    })
    console.log('服务器发送此消息成功!')
  });
  ws.on('close', function() {
    console.log(ws.id + ' exit')
    console.log('现在还有一下客户端:')
    wss.clients.forEach((client) => {
      console.log('id: ' + client.id + 'type: ' + client.type)
    })
  })
})

const timeIntervl = 3000 // 发送心跳检测的时间

setInterval(() => {
  wss.clients.forEach((ws) =>{
    if(!ws.isAlive){
      console.log(ws.id + '无反应,已结束了与它的链接')
      return ws.terminate()
    }
    ws.isAlive = false // 不收到pong就会断开与客户端的链接
    // console.log("向ws: "+ws.id+"发送心跳")
    ws.send(JSON.stringify({
      event: 'heartbeat',
      msg: 'ping'
    }))
  })
}, timeIntervl);

export default wss

Router.js

import Router from 'koa-router'
const qr = require('qr-image');

class PublicController {
  constructor() { }
  async getQR(ctx){
    var text = ctx.query.text;
    var img = qr.image(text, {size: 100})
    try {
        ctx.type= 'image/png';
        ctx.body = img;
    } catch (e) {
        ctx.type='text/html;charset=utf-8';
        ctx.body='Text Too Large';
    }
  }
}

const router = new Router()
router.get('/getQR', PublicController.getQR)

export default router

index.js

import koa from 'koa'
import JWT from 'koa-jwt'
import statics from 'koa-static'

const app = new koa()

// 定义公共路径,不需要jwt鉴权
const jwt = JWT({ 
  secret: config.JWT_SECRET,//相当于解析token需要的密码
}).unless({
  path: [
    //getQR/
  ]})


const middleware = compose([
  statics(path.join(__dirname, '../public')), // 静态文件开始路径
  jwt //
])

app.use(middleware)
app.use(router())

app.listen(3000)
需要登录端

我使用的是iview-admin 2.0 的框架,我就只写我修改的部分,大家可以按照这个思路自己编写一个页面.因为我自己写的页面太丑了,就不展示了展示别人优秀的页面

LoginScanfQRForm.vue



.qr {
  padding: auto auto;
  margin: auto auto;
}

 使用方法,在需要添加扫码登录的地方导入这个组件,实列

父组件接收子组件的参数

演示代码


@import 'login.less';




 效果

 现在就差实现一个扫码的移动端

移动端实现 在需要添加此功能的Android项目中导入以下依赖:

 代码:

// websocket
compile "org.java-websocket:Java-WebSocket:1.3.7"
// 相机识别二维码
implementation('com.journeyapps:zxing-android-embedded:4.1.0')

页面实现

1,扫码登录页面

(1)界面(很简陋),后期优化一下

解析: 真机可通过开始扫描扫描二维码加入,模拟器可以手动输入房间号

(2),layout代码




    

        

    

(3),java代码

public class ScanfQR extends AppCompatActivity {
    private Context context = ScanfQR.this;
    private Activity activity = ScanfQR.this;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scanf_q_r);

        findViewById(R.id.begainScan).setonClickListener(new View.onClickListener() {
            @Override
            public void onClick(View v) {
                // 创建IntentIntegrator对象
                IntentIntegrator intentIntegrator = new IntentIntegrator(ScanfQR.this);
                // 开始扫描
                intentIntegrator.initiateScan();
            }
        });
        findViewById(R.id.ScanOfQr_id_submit).setonClickListener(new View.onClickListener() {
            @Override
            public void onClick(View v) {
                setUpConnection();
            }
        });
    }
    // 处理扫码结果
    private boolean handleScanResult(int requestCode, int resultCode, Intent data){
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if (result != null) { // 获取拍照结果并处理
            if (result.getContents() == null) {
                Toast.makeText(this, "取消扫描", Toast.LENGTH_LONG).show();
            }else {
                JsonHelper jsonHelper = new JsonHelper(result.getContents());
                if(jsonHelper.getStringValue("tag").equals("QRLogin")){
                    User user = (User) getApplication();
                    if(user.isLogin()){
                        Toast.makeText(context, "正在跳转到登录授权", Toast.LENGTH_SHORT).show();
                        String host = jsonHelper.getStringValue("host");
                        int id = jsonHelper.getIntValue("id");
                        qr_websocket = new QR_Websocket(host,user,id+"");
                        initScanQR();

                    }else { // 未登录
                        //tip
                        Toast.makeText(context, "此二维码为扫码登录码,需要登录后才能扫码,请登录后重新扫描!", Toast.LENGTH_SHORT).show();
                        User.alertLogin(this);
                    }
                    return true;
                }
            }
        }
        return false;
    }
    // 初始化扫码,并监听扫码链接结果,如果成功链接则跳转到授权页面
    private void initScanQR(){
        qr_websocket.connect();
        qr_websocket.getIsConnect().observe(this, new Observer() {
            @Override
            public void onChanged(Boolean aBoolean) {
                if(aBoolean){
                    User.getAuth(activity); // 跳转用户授权界面
                }
            }
        });
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        super.onActivityResult(requestCode, resultCode, data);
        // 获取二维码解析结果并处理
        boolean isHandle = handleScanResult(requestCode, resultCode, data);
        if(isHandle){
            return;
        }
//        //登录结果
//       boolean isLogin = User.handleLoginState(context,requestCode,resultCode);
//       if(isLogin){
//           setUpConnection();
//           return;
//       }
        // 授权状态
       String token = User.handleAuthState(context,requestCode,resultCode);
       if(token!=null){
           qr_websocket.sendLoginInfo(token);
           return;
       }
    }
    QR_Websocket qr_websocket;
    // 通过输入框链接ws,因为模拟器无法打开相机,可以通过手动输入房间id加入
    public void setUpConnection() {
        User user = (User)getApplication(); // 获取全局用户信息
        EditText editText = findViewById(R.id.ScanOfQr_id);
        String id = editText.getText().toString();
        if(id.length()!=0){
            qr_websocket = new QR_Websocket( SettingsActivity.getHost(context), user,id);
            initScanQR();
        }else {
            Toast.makeText(context, "no id", Toast.LENGTH_SHORT).show();
        }
    }

    // 重写返回方法,关闭开启的ws
    @Override
    public void onBackPressed() {
        if(qr_websocket.isOpen()){
            qr_websocket.closeConnection(1,"返回关闭");
        }
        super.onBackPressed();
    }
}

2,注我将授权跳转和处理授权全写在了一个类中,方便调用

3,授权界面

(1) 布局 

 (2) Layout代码




    

        
                
            
        
    

(3)Java代码

public class ScanLoginSubmit extends AppCompatActivity {
    public static final int SUBMIT = 0;
    public static final int SUBJECT = 1;
    public static final int CANCEL = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scan_login_submit);
        User user = (User) getApplication();
        ImageView avatar = findViewById(R.id.ScanLoginSubmit_avatar);
        Glide.with(this).load(user.getUserData().avatar).into(avatar);

        TextView username = findViewById(R.id.ScanLoginSubmit_username);
        username.setText(user.getUserName());

        findViewById(R.id.ScanLoginSubmit_Exit).setonClickListener(new View.onClickListener() {
            @Override
            public void onClick(View v) {
                setResult(CANCEL);
                finish();
            }
        });

        findViewById(R.id.ScanLoginSubmit_submit).setonClickListener(new View.onClickListener() {
            @Override
            public void onClick(View v) {
                setResult(SUBMIT);
                finish();
            }
        });
        findViewById(R.id.ScanLoginSubmit_subject).setonClickListener(new View.onClickListener() {
            @Override
            public void onClick(View v) {
                setResult(SUBJECT);
                finish();
            }
        });

    }

    @Override
    public void onBackPressed() {
        setResult(CANCEL);
        super.onBackPressed();
    }
}

终于写完了,感谢大家看到这里,我后期会专门出这三个模块的demo,有兴趣可以关注一下哦~,今天除夕夜,祝大家新年快乐!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/719524.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号