一、需求描述
使用者在PC端用微信掃描二維碼實現後臺登入
圖示:
二、實現原理
此處採用socket實現,當然也可以透過輪詢去監測微信掃碼狀態
三、實現步驟
1. 微信公眾平臺小程式後臺申請跳轉連結
開發管理->開發設定->掃普通連結二維碼開啟小程式
2. PC請求websocket連線獲取二維碼key
客戶端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--登入介面-->
<img id="qrCode" src="" alt="掃碼">
</body>
</html>
<script>
let socket;
let timer;
if (typeof WebSocket === "undefined") {
alert("您的瀏覽器不支援socket");
} else {
// 例項化socket
socket = new WebSocket(
"wss://ip:port/platform-system/socket/wx/qrLogin");
// 監聽socket連線
socket.onopen = () => {
console.log('連線成功.......')
//模擬心跳
timer = setInterval(() => {
socket.send(1)
}, 30000)
};
// 監聽socket錯誤資訊
socket.onerror = () => {
console.log('連線發生錯誤.......')
};
// 監聽socket訊息
socket.onmessage = ({data}) => {
//判斷狀態
//此處0代表剛建立連線,返回來的是二維碼key
if (data.code === 0) {
//請求服務端獲取二維碼
document.querySelector('#qrCode').src = `https://ip:port/platform-system/auth/wx/miniprogram/qrCode?pcKey=${data.data}`
} else if (data.code === 1) {
//關閉定時器
clearInterval(timer)
//關閉socket
socket.close()
//此處1代表登入成功
location.href = '後臺主頁'
} else {
//其他情況代表小程式登入出現問題
console.log(`登入出錯:${data.msg}`)
//關閉定時器
clearInterval(timer)
//關閉socket
socket.close()
}
};
}
</script>
服務端:
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
public class JsonWebSocketEncoder implements Encoder.Text<Result> {
@Override
public String encode(Result object) throws EncodeException {
try {
return JSONUtil.toJsonStr(object);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void init(EndpointConfig endpointConfig) {
}
@Override
public void destroy() {
}
}
@Slf4j
@Component
@ServerEndpoint(value = "/socket/wx/qrLogin", encoders = JsonWebSocketEncoder.class)
public class WxQrLoginWebSocket {
public static final ConcurrentMap<String, Session> sockets = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session) {
log.info("客戶端連線成功:{}", session.getId());
//將連線結果告訴客戶端用於攜帶進入小程式(此處直接用的雪花演算法,可使用其他方式生成key)
String key = IdUtil.getSnowflake().nextIdStr();
//存放session
sockets.put(key, session);
log.info("當前線上客戶端:{}個", sockets.size());
//通知客戶端
sendMessage(key, Result.result("0", null, key));
}
/**
* 連結關閉呼叫的方法
*/
@OnClose
public void onClose(Session session) {
try {
sockets.entrySet().removeIf(entry -> session.getId().equals(entry.getValue().getId()));
log.info("【websocket訊息】連線斷開,sessionId:" + session.getId());
log.info("當前線上客戶端:{}個", sockets.size());
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
}
}
/**
* 收到客戶端訊息後呼叫的方法
*
* @param message
* @param session
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("【websocket訊息】收到客戶端心跳:{},{}", message, session.getId());
log.info("當前線上客戶端:{}個", sockets.size());
}
/**
* 傳送錯誤時的處理
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
try {
//列印錯誤
log.error("使用者錯誤,原因:" + error.getMessage());
error.printStackTrace();
//關閉連結
session.close();
sockets.entrySet().removeIf(entry -> session.getId().equals(entry.getValue().getId()));
log.info("當前線上客戶端:{}個", sockets.size());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 訊息
*
* @param key key
* @param data 資料
**/
public static void sendMessage(String key, Object data) {
sockets.get(key).getAsyncRemote().sendObject(data);
}
}
3. 小程式獲取key並授權登入
客戶端:
獲取key
onLoad(option) { if(option.q){ //微信掃描掃描二維碼進來的 let url = decodeURIComponent(option.q) let params = this.getUrlParam(url) console.log('從微信掃碼過來params') //此處需要儲存這個pcKey後面會用到 console.log(params.pcKey) } } getUrlParam(url){ let params = url.split("?")[1].split("&"); let obj = {}; params.map(v => (obj[v.split("=")[0]] = v.split("=")[1])); return obj }
請求服務端登入並通知PC端
wx.request({ url: 'https://ip:port/...', //僅為示例,並非真實的介面地址 method: 'POST', header: { 'content-type': 'application/x-www-form-urlencoded' }, data: { username: 'username', password: 'password' }, success: function (res) { //獲取token var token = res.data.accessToken; //通知服務端告訴PC登入 wx.request({ url: 'https://ip:port/auth/notifyPcLogin', //僅為示例,並非真實的介面地址 method: 'GET', header: { 'Authorization': token }, data: { pcKey: '之前儲存的key' }, success: function (res) { console.log(res.data) } }) } })
服務端:
@RestController @RequestMapping("/auth") @RequiredArgsConstructor public class LoginController { private final WxLoginService wxLoginService; @ApiOperation(value = "通知PC登入介面") @GetMapping("/notifyPcLogin") public Result<Boolean> notifyPcLogin( @ApiParam(name = "pcKey", value = "PC端後端給的唯一標識", required = true) @NotBlank(message = "唯一標識不能為空") @RequestParam(value = "pcKey") String pcKey ) { return Result.success(wxLoginService.notifyPcLogin(pcKey)); } }
@Service @RequiredArgsConstructor public class WxLoginService { private final ISysUserService sysUserService; /** * 喚醒PC登入 * * @param pcKey pc唯一key * @return java.lang.Boolean * @author Guo Shuai * @since 1.0 **/ public Boolean notifyPcLogin(String pcKey) { //獲取socket session Session session = WxQrLoginWebSocket.sockets.get(pcKey); //判斷是否存在 if (ObjectUtil.isNull(session)) { return Boolean.FALSE; } //獲取當前登入使用者手機號 String phone = sessionService.getLoginUser().getPhone(); //透過手機號查詢使用者 SysUser sysUser = sysUserService.lambdaQuery().eq(SysUser::getMobile, phone).one(); //獲取使用者pc端token LoginUserVO loginUserVO = sysUserService.getUserToken(sysUser, sysUser.getMobile(), sessionService.getTenantId(), "10"); //通知pc登入 WxQrLoginWebSocket.sendMessage(pcKey, Result.result("1", null, loginUserVO)); //返回 return Boolean.FALSE; } }
至此整個流程結束
四、注意事項
小程式申請的跳轉連結只有正式版可以攜帶自定義的動態引數,否則只能用配置的測試地址,測試地址可以配置最多5個