간단한 스캔 코드 로그인

기인

선생님이 로그인하려면 QR 코드를 스캔해야 하기 때문입니다. 내 룸메이트의 요청에 따라 수문 그래프.

생각의 기차

컴퓨터가 로그인하면 전역적으로 고유한 것이 생성됩니다 id. 그리고 idQR코드에 정보를 저장합니다. 서버는 또한 합계를 id저장 합니다 . 휴대폰이 코드를 스캔하면 특수 웹페이지로 이동합니다. 웹 페이지는 자동으로 사용자 정보를 함께 서버로 보냅니다. 에 따라 서버를 찾았습니다 . 사용자 정보를 컴퓨터의 브라우저로 보내고 해제합니다 .javax.websocket.Sessionmap
ididjavax.websocket.Sessionjavax.websocket.Session

물론 여기서는 중요하지 않습니다 websocket. websocket이점은 이중 통신입니다. 서버는 브라우저에 요청을 보낼 수 있습니다. js그렇지 않으면 폴링 서버 만 사용할 수 있습니다 .

코드

후단

UUIDWebSocket언제든지 교체 및 구현이 편리한 인터페이스를 직접 추상화했습니다.
@OnMessage메시지를 보낼 때 호출되는 메서드
@OnClose연결이 닫힐 때 호출되는 메서드


관심이 없으시면 바로 건너뛰셔도 됩니다.

mapQR 코드의 무효화를 실현하기 위해 여기에서 회전이 사용됩니다 . 날짜 필드를 추가하지 않는 이유는 무엇입니까? 날짜 필드를 추가하려면 각각에 대한 타이머를 추가해야 합니다. 성능이 좋지 않은 것 같습니다. 코드를 스캔할 때 만료 여부를 판단한다고 할 수 있습니다. 이것은 또한 작동하지 않습니다. 누군가 코드를 스캔하지 않고 로그인만 하면 메모리가 낭비됩니다.

여기서 회전은 map성능과 크기를 모두 갖춘 타이머만 필요합니다. 유일한 단점은 유효 기간이 고정된 것이 아니라 간격이라는 것입니다.

회전 map내부에는 두 개가 있습니다 map. 삽입될 때만 삽입됩니다 newMap. oldMapDelete는 , then oldMap및 swap 만 삭제합니다 newMap.

@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);
        }
    }
}

또한 구성bean

@Configuration
public class WebSocketConfig {
    
    

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

}

스캔 코드 인터페이스

@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);
        }
    }

}

휴대 전화에서 코드를 스캔하면 viewQRCode기능에 들어갑니다.
먼저 session.getAttribute(USR);휴대폰에서 사용자 정보를 가져옵니다.
그렇지 않은 경우 모바일 단말기를 호출하여 로그인하십시오. 그리고 id부정적이고 유효하지 않은 것을 컴퓨터에 반환하고 있는 경우 user사용자 정보를 반환합니다.
QRWebSocket.callbackuser

프런트엔드 코드

여기 [[${QR_SOCKET}]]대기는 thymeleaf템플릿에 있으므로 들어가지 마십시오.
layer라이브러리에 있으니 걱정하지 마세요
chain. json2form직접 작성한 도구 기능도 걱정하지 마세요.
다음 코드의 의미는 WebSocket통신을 설정하는 것입니다.
반환할 때 사용자가 오류를 보고 id합니다 성공하면 로그인 인터페이스를 방문하여 사용자 정보를 전송하고 로그인에 성공하면 사용자 홈페이지로 이동합니다.-1

// 向后端发送一个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();
}

툴킷

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));
    });
}

추천

출처blog.csdn.net/qq_45256489/article/details/121106487