Simple scan code login

origin

Because the teacher is mandatory to scan the QR code to log in. At the request of my roommate, hereby hydrograph.

train of thought

When the computer logs in, a globally unique one will be generated id. And store idthe information in the QR code. The server will also idstore javax.websocket.Sessionthe sum in mapit.
When the mobile phone scans the code, it will jump to a special webpage. The web page automatically idsends the user information to the server together. The server idis found according to javax.websocket.Session. By javax.websocket.Sessionsending user information to the browser on the computer and releasing it.

Of course it doesn't matter here websocket. websocketThe benefit is duplex communication. The server can send a request to the browser. Otherwise, you can only use jsthe polling server.

the code

rear end

UUIDWebSocketI abstracted an interface by myself, which is convenient for replacement and implementation at any time.
@OnMessageIt is the method called when the message is sent
@OnCloseIt is the method called when the connection is closed


If you are not interested, you can skip it directly.

A rotation is used here mapto realize the invalidation of the QR code. Why not just add a date field? To add a date field, you have to add a timer for each. Sounds like poor performance. You might say that it is judged whether it has expired when scanning the code? This also does not work. If someone just logs in without scanning the code, it will waste memory.

The rotation here maponly needs a timer, which has both performance and size. The only disadvantage is that the validity period is an interval, not fixed.

mapThere are two inside the rotation map. It will only insert when inserted newMap. Delete will only delete oldMap, then oldMapand newMapexchange.

@Component
@ServerEndpoint(QR_SOCKET)
public class QRCodeWebSocket implements UUIDWebSocket {
    
    
    public QRCodeWebSocket() {
    
    
        new Timer().scheduleAtFixedRate(new TimerTask() {
    
    
            @Override
            public void run() {
    
    
                uuid2session.clear();
                session2uuid.clear();
            }
            // 二维码有效期5到10分钟
        }, 0, 300_000);
    }

    // 轮换map
    private static class RotationMap<T, R> {
    
    
        private Map<T, R> newMap = new ConcurrentHashMap<>(), oldMap = new ConcurrentHashMap<>();

        R get(T t) {
    
    
            return newMap.containsKey(t) ? newMap.get(t) : oldMap.get(t);
        }

        void put(T t, R r) {
    
    
            newMap.put(t, r);
        }

        void clear() {
    
    
            oldMap.clear();
            Map map = oldMap;
            oldMap = newMap;
            newMap = map;
        }

        void remove(T t) {
    
    
            if (newMap.containsKey(t)) {
    
    
                newMap.remove(t);
            } else
                oldMap.remove(t);
        }

    }

    final static RotationMap<String, Session> uuid2session = new RotationMap<>();
    final static RotationMap<Session, String> session2uuid = new RotationMap<>();

    @OnMessage
    @Override
    public void onMessage(String uuid, Session session) {
    
    
        uuid2session.put(uuid, session);
        session2uuid.put(session, uuid);
    }

    @Override
    public boolean callback(String uuid, String data) {
    
    
        Session session = uuid2session.get(uuid);
        if (session != null) {
    
    
            try {
    
    
                session.getBasicRemote().sendText(data);
                session.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
                return false;
            }
        }
        return true;
    }

    @OnClose
    @Override
    public void onClose(Session session) {
    
    
        String uuid = session2uuid.get(session);
        if (uuid != null) {
    
    
            uuid2session.remove(uuid);
            session2uuid.remove(session);
        }
    }
}

Also configure abean

@Configuration
public class WebSocketConfig {
    
    

    /**
     * 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
     * 会导致测试不通过。应该是测试环境没配全的原因
     * 打包时直接跳过测试
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
    
    
        return new ServerEndpointExporter();
    }

}

Scan code interface

@Controller
public class QRCodeController {
    
    
    ThemeConfig themeConfig;
    UUIDWebSocket QRWebSocket;

    public QRCodeController(ThemeConfig themeConfig, UUIDWebSocket QRWebSocket) {
    
    
        this.themeConfig = themeConfig;
        this.QRWebSocket = QRWebSocket;
    }

    // 回调
    @GetMapping(QR_LOGIN + "/{uuid}")
    String viewQRCode(@PathVariable("uuid") String uuid, HttpSession session, Model model) {
    
    
        User user = (User) session.getAttribute(USR);
        if (user != null) {
    
    
            QRWebSocket.callback(uuid, objectToString(user));
            return themeConfig.render(USER_INDEX);
        } else {
    
    
            user=new User();
            user.setId(-1);
            QRWebSocket.callback(uuid, objectToString(user));
            model.addAttribute(MSG, "请先登录");
            return themeConfig.render(LOGIN);
        }
    }

}

When scanning the code on the mobile phone, it will enter viewQRCodethe function.
First, session.getAttribute(USR);get the user information on the mobile phone.
If not, call the mobile terminal to log in. And return a idnegative and invalid one to the computer user;
if there is, QRWebSocket.callbackreturn the user informationuser

front-end code

[[${QR_SOCKET}]]The waiting here is thymeleafin the template, so don't go into it.
layerIt’s in the library, don’t worry about it
chain, and json2formit’s a tool function written by yourself, don’t worry about it.
The meaning of the following code is to establish a WebSocketcommunication.
When returning the user idis-1 reporting an error.
When successful, visit the login interface and send the user information to it. If
the login is successful, it will jump to the user's homepage.

// 向后端发送一个websocket连接请求
let ws = new WebSocket('ws://' + root_path + '[[${QR_SOCKET}]]');
ws.onmessage = function (event) {
    
    
    let data = JSON.parse(event.data);
    console.dir(data)
    if (data.id != -1) {
    
    
        chain({
    
    
            url: '[[${LOGIN}]]',
            method: 'post',
            data: json2form(data)
        }).then(
            () => {
    
    
                window.location.href = "[[${USER_INDEX}]]"
            }
        );
    } else {
    
    
        alert('扫码失败');
    }
}

function connect() {
    
    
    ws.send('[[${uuid}]]');
}

function qr_login() {
    
    
    layer.open({
    
    
        type: 1
        , area: ['300px', '300px']
        , title: '扫码登录'
        , anim: 1
        , content: '<center><img style="width: 100%" src="//api.pwmqr.com/qrcode/create/?url=http://' + root_path +
            '[[@{|${QR_LOGIN}/${uuid}|}]]"></center>'
    });
    connect();
}

Toolkit

const root_path = "[[${ #httpServletRequest.getServerName() + ':' + #request.getServerPort()  + #request.getContextPath() } ]]"

function json2form(json) {
    
    
    let ks = Object.keys(json), i = 0;
    ks.forEach((ele, i, arr) => arr[i] += "=" + json[arr[i]]);
    return ks.join("&");
}

function chain(option) {
    
    
    return new Promise((resolve, reject) => {
    
    
        axios(option).then(r => resolve(r.data)).catch(e => reject(e));
    });
}

Guess you like

Origin blog.csdn.net/qq_45256489/article/details/121106487
Recommended