origen
Porque el profesor es obligatorio escanear el código QR para iniciar sesión. A pedido de mi compañero de cuarto, por la presente hidrograma.
tren de pensamiento
Cuando la computadora inicie sesión, se generará uno globalmente único id
. Y almacena id
la información en el código QR. El servidor también id
almacenará javax.websocket.Session
la suma en map
él.
Cuando el teléfono móvil escanee el código, saltará a una página web especial. La página web id
envía automáticamente la información del usuario al servidor juntos. El servidor id
se encuentra según javax.websocket.Session
. Al javax.websocket.Session
enviar información del usuario al navegador de la computadora y liberarla.
Por supuesto que no importa aquí websocket
. websocket
El beneficio es la comunicación dúplex. El servidor puede enviar una solicitud al navegador. De lo contrario, solo puede utilizar js
el servidor de sondeo.
el código
extremo posterior
UUIDWebSocket
Abstraí una interfaz por mí mismo, lo cual es conveniente para el reemplazo y la implementación en cualquier momento.
@OnMessage
Es el método llamado cuando se envía el mensaje.
@OnClose
Es el método llamado cuando se cierra la conexión.
Si no está interesado, puede omitirlo directamente.
Aquí se utiliza una rotación map
para realizar la invalidación del código QR. ¿Por qué no simplemente agregar un campo de fecha? Para agregar un campo de fecha, debe agregar un temporizador para cada uno. Suena a bajo rendimiento. ¿Podría decir que se juzga si ha expirado al escanear el código? Esto tampoco funciona. Si alguien simplemente inicia sesión sin escanear el código, desperdiciará memoria.
La rotación aquí map
solo necesita un temporizador, que tiene tanto rendimiento como tamaño. La única desventaja es que el período de validez es un intervalo, no fijo.
map
Hay dos dentro de la rotación map
. Solo se insertará cuando se inserte newMap
. Eliminar solo eliminará oldMap
, luego oldMap
e newMap
intercambiará.
@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);
}
}
}
Configure también unbean
@Configuration
public class WebSocketConfig {
/**
* 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
* 会导致测试不通过。应该是测试环境没配全的原因
* 打包时直接跳过测试
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
Interfaz de escaneo de código
@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);
}
}
}
Al escanear el código en el teléfono móvil, entrará en viewQRCode
la función.
Primero, session.getAttribute(USR);
obtenga la información del usuario en el teléfono móvil.
De lo contrario, llame al terminal móvil para iniciar sesión. Y devolver uno id
negativo e inválido a la computadora user
;
si lo hay, QRWebSocket.callback
devolver la información del usuariouser
código de front-end
[[${QR_SOCKET}]]
La espera aquí está thymeleaf
en la plantilla, así que no entres en ella.
layer
Está en la biblioteca, no se preocupe por eso
chain
, y json2form
no se preocupe por la función de la herramienta que escribió usted mismo.
El significado del siguiente código es establecer una WebSocket
comunicación.
Al regresar el usuario id
está-1
reportando un error.
Cuando tenga éxito, visite la interfaz de inicio de sesión y envíele la información del usuario. Si
el inicio de sesión es exitoso, saltará a la página de inicio del usuario.
// 向后端发送一个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();
}
Caja de herramientas
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));
});
}