起因
教師はログインするために QR コードをスキャンする必要があるためです。ルームメイトのリクエストで、ハイドログラフを作成します。
一連の考え
コンピュータがログインすると、グローバルに一意のものが生成されますid
。そしてid
情報をQRコードに保存します。サーバーは合計もそこにid
保存します。携帯電話がコードをスキャンすると、特別な Web ページにジャンプします。ウェブページはユーザー情報を一緒に自動的にサーバーに送信します。サーバーはに従って見つかります。ユーザー情報をコンピュータ上のブラウザに送信し、公開することによって。javax.websocket.Session
map
id
id
javax.websocket.Session
javax.websocket.Session
もちろんここでは関係ありませんwebsocket
。websocket
利点は二重通信です。サーバーはブラウザにリクエストを送信できます。js
それ以外の場合は、ポーリング サーバーのみを使用できます。
コード
後部
UUIDWebSocket
インターフェースを自分で抽象化したので、いつでも置き換えや実装ができて便利です。
@OnMessage
メッセージの送信時に呼び出されるメソッドです
@OnClose
接続が閉じられたときに呼び出されるメソッドです
興味がない場合は、直接スキップしてください。
ここでは回転を利用してmap
QRコードの無効化を実現しています。なぜ日付フィールドを追加しないのでしょうか? 日付フィールドを追加するには、それぞれにタイマーを追加する必要があります。パフォーマンスが悪いように聞こえます。コードをスキャンするときに有効期限が切れているかどうかを判断していると言えるでしょうか。これも機能しません。コードをスキャンせずにログインするだけだと、メモリが無駄に消費されます。
ここでの回転にはmap
、パフォーマンスとサイズの両方を備えたタイマーのみが必要です。唯一の欠点は、有効期間が一定ではなく一定期間であることです。
回転map
内には 2 つありますmap
。挿入された場合のみ挿入されますnewMap
。oldMap
削除は を削除し、次にoldMap
と を交換するだけです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.callback
ユーザー情報を返しますuser
フロントエンドコード
ここでの[[${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));
});
}