WebSocket實現app掃描二維碼登入
阿新 • • 發佈:2019-01-05
後臺框架採用SpringMVC,不同的框架可根據邏輯更改即可:
【思路】- PC端生成二維碼,二維碼包含uuid(全域性唯一識別符號),且打通websocket通道,等待伺服器返回登入成功資訊;APP掃描二維碼,獲取uuid及登入資訊,推送給服務端,處理後的登入資訊通過websocket返回給PC端,PC端得到登入資訊後儲存即登入成功。APP掃描確認登入的資訊可以採用ActiveMQ進行推送。
生成二維碼部分引入依賴檔案
<dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>3.1.0</version> </dependency>
二維碼登入後臺控制層Controller
/** * 專案名稱:dream_user * 專案包名:org.fore.user.controller * 建立時間:2017年8月8日下午5:29:41 * 建立者:Administrator-宋發元 * 建立地點:杭州 */ package org.fore.user.controller; import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.util.UUID; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.fore.model.user.UserAccount; import org.fore.model.user.UserModel; import org.fore.user.qrcode.websocket.WebSocketHandler; import org.fore.user.service.UserAccountService; import org.fore.user.service.UserService; import org.fore.utils.jms.JmsSender; import org.fore.utils.mvc.TokenUtil; import org.fore.utils.mvc.annotation.LimitLess; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.alibaba.fastjson.JSONObject; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.WriterException; import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; /** * 描述:控制層 * @author songfayuan * 2017年8月8日下午5:29:41 */ @Controller @RequestMapping("/qrcodelogin") public class QrCodeLoginController { private Logger logger = LoggerFactory.getLogger(QrCodeLoginController.class); public static int defaultWidthAndHeight=260; @Autowired private WebSocketHandler webSocketHandler; @Autowired private UserService userService; @Autowired private UserAccountService userAccountService; @Autowired @Qualifier(value = "qrCodeLoginSender") private JmsSender jmsSender; /** * 描述:PC獲取二維碼 * @param uuid * @param request * @param response * @throws ServletException * @throws IOException * @author songfayuan * 2017年8月11日上午9:04:43 */ @RequestMapping("/getLoginQrCode") @ResponseBody @LimitLess public void getLoginQrCode(String uuid, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //生成引數 //String uuid = generateUUID(); String host = request.getHeader("Host"); JSONObject data = new JSONObject(); data.put("code", 200); data.put("msg", "獲取二維碼成功"); data.put("uuid", uuid); data.put("host", host); logger.info("【二維碼內容】:{}",data); //生成二維碼 Map<EncodeHintType, Object> hints=new HashMap<EncodeHintType, Object>(); // 指定糾錯等級 hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); // 指定編碼格式 hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); hints.put(EncodeHintType.MARGIN, 1); try { BitMatrix bitMatrix = new MultiFormatWriter().encode(data.toString(),BarcodeFormat.QR_CODE, defaultWidthAndHeight, defaultWidthAndHeight, hints); OutputStream out = response.getOutputStream(); MatrixToImageWriter.writeToStream(bitMatrix, "png", out);//輸出二維碼 out.flush(); out.close(); } catch (WriterException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 描述:app確認請求處理 * @param uuid * @param host * @param userid * @author songfayuan * 2017年8月11日上午9:05:56 */ @RequestMapping("/sendCodeLoginInfo") @ResponseBody @LimitLess public void sendCodeLoginInfo(String uuid, String host, Integer userid) { // 註冊成功後 或 登入,需要同步賬戶資訊,獲取使用者基本資訊 UserAccount account = userAccountService.findCurrentUserAccount(userid); userAccountService.syncAccount(account); UserModel userModel = userService.findUserById(userid); userModel = changeUserForShow(userModel); JSONObject token = TokenUtil.generateTokenByQrCodeLogin(userid, host); JSONObject object = new JSONObject(); object.put("code", 10086); object.put("uuid", uuid); object.put("userinfo", userModel); object.put("token", token); object.put("msg", "登入成功"); //this.webSocketHandler.forwardQrCode(object.toString()); jmsSender.sendMessage(object.toString()); //採用ActiveMQ進行推送,也可以直接注入websocket進行傳送 } //處理使用者登入資訊 private UserModel changeUserForShow(UserModel userModel) { UserModel user = new UserModel(); user.setId(userModel.getId()); user.setUserName(userModel.getUserName()); user.setUserSex(userModel.getUserSex()); user.setUserPortrait(userModel.getUserPortrait()); return user; } /** * 描述:唯一識別符號 * @return * @author songfayuan * 2017年8月11日上午9:06:12 */ public static String generateUUID() { String uuid = UUID.randomUUID().toString(); uuid = uuid.replace("-", ""); Long currentTime = System.currentTimeMillis(); String currentDate = String.valueOf(currentTime); return uuid + currentDate; } }
websocket實現(本案例採用Spring自帶的websocket)
package org.fore.sms.qrcode.websocket; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; @Configuration @EnableWebMvc @EnableWebSocket public class QrCodeLoginWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer { @Autowired private QrCodeLoginWebSocketEndPoint endPoint; @Autowired private QrCodeLoginHandshakeInterceptor interceptor; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(endPoint, "/qrcodelogin.do").addInterceptors(interceptor).setAllowedOrigins("*"); // registry.addHandler(endPoint, // "/sockjs.do").addInterceptors(interceptor).setAllowedOrigins("*") // .withSockJS(); } /** * Each underlying WebSocket engine exposes configuration properties that * control runtime characteristics such as the size of message buffer sizes, * idle timeout, and others. */ /** * For Tomcat, WildFly, and GlassFish add a * ServletServerContainerFactoryBean to your WebSocket Java config: */ @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); container.setMaxTextMessageBufferSize(8192); container.setMaxBinaryMessageBufferSize(8192); return container; } /** * For Jetty, you’ll need to supply a pre-configured Jetty * WebSocketServerFactory and plug that into Spring’s * DefaultHandshakeHandler through your WebSocket Java config: */ // @Bean // public DefaultHandshakeHandler handshakeHandler() { // // WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); // policy.setInputBufferSize(8192); /* 設定訊息緩衝大小 */ // policy.setIdleTimeout(600000); /* 10分鐘read不到資料的話,則斷開該客戶端 */ // // return new DefaultHandshakeHandler(new JettyRequestUpgradeStrategy(new // WebSocketServerFactory(policy))); // } }
package org.fore.sms.qrcode.websocket;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
@Component
public class QrCodeLoginHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
private Logger logger = LoggerFactory.getLogger(QrCodeLoginHandshakeInterceptor.class);
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
super.afterHandshake(request, response, wsHandler, ex);
}
}
package org.fore.sms.qrcode.websocket;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.fore.model.quota.tcp.ReqCode;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
@Component
public class QrCodeLoginWebSocketEndPoint extends TextWebSocketHandler {
private Logger logger = LoggerFactory.getLogger(QrCodeLoginWebSocketEndPoint.class);
private static Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();
private static Map<WebSocketSession,String > sessionMap2 = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.info("WebSocketHandler:客戶端{}上線", session.getRemoteAddress());
String uuid = generateUUID();
sessionMap.put(uuid,session);
sessionMap2.put(session,uuid);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String msg = message.getPayload();
String ipAddress = session.getRemoteAddress().toString();
JSONObject requestData = JSON.parseObject(msg);
Integer code = requestData.getInteger("code");
JSONObject result = new JSONObject();
String uuid = sessionMap2.get(session);
result.put("code", 200);
result.put("uuid", uuid);
switch (code) {
case ReqCode.REQ_QR_CODE:
logger.info("WebSocketHandler:客戶端{}傳送訊息{}...", ipAddress, msg);
if(session.isOpen())
session.sendMessage(new TextMessage(result.toString()));
logger.info("WebSocketHandler:客戶端{}傳送訊息{}完成", ipAddress, msg);
break;
default:
break;
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String ipAddress = session.getRemoteAddress().toString();
logger.info("WebSocketHandler:客戶端{}下線", ipAddress);
logger.info("WebSocketHandler:刪除客戶端{}的session...", ipAddress);
logger.info("WebSocketHandler:刪除sessionMap的客戶端{}連線...", ipAddress);
String uuid = sessionMap2.get(session);
sessionMap.remove(uuid);
sessionMap2.remove(session);
logger.info("WebSocketHandler:刪除sessionMap的客戶端{}連線完成", ipAddress);
logger.info("WebSocketHandler:刪除WebSocket客戶端{}連線...", ipAddress);
// logger.info("{}", sessionMap);
sessionMap.remove(session);
// logger.info("{}", sessionMap);
logger.info("WebSocketHandler:刪除WebSocket客戶端{}連線完成", ipAddress);
logger.info("WebSocketHandler:刪除客戶端{}的session完成", ipAddress);
if(session.isOpen())
session.close();
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
logger.info("WebSocketHandler:客戶端{}異常", session.getRemoteAddress(), exception);
}
//傳送訊息
public void sendMessage(String userInfo) throws Exception {
JSONObject json = JSONObject.parseObject(userInfo);
String uuid = json.getString("uuid");
WebSocketSession session = sessionMap.get(uuid);
if (session == null) {
logger.info("app傳送給PC的登入資訊:{}引數不正確!",userInfo);
}else {
logger.info("app傳送給PC的登入資訊:{}",userInfo);
session.sendMessage(new TextMessage(userInfo));
}
}
//唯一識別符號
public static String generateUUID() {
String uuid = UUID.randomUUID().toString();
uuid = uuid.replace("-", "");
Long currentTime = System.currentTimeMillis();
String currentDate = String.valueOf(currentTime);
return uuid + currentDate;
}
}
JMS實現
package org.fore.sms.qrcode.jms;
import org.fore.utils.jms.Listener;
import org.fore.sms.qrcode.websocket.QrCodeLoginWebSocketEndPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSONObject;
@Component
public class QrCodeLoginListener implements Listener {
private Logger logger = LoggerFactory.getLogger(QrCodeLoginListener.class);
@Autowired
private QrCodeLoginWebSocketEndPoint qrCodeLoginWebSocketEndPoint;
@Override
public void onMessage(String message) {
logger.info("app確認登入資訊:接收app推送的確定PC登入訊息{}", message);
JSONObject object = JSONObject.parseObject(message);
try {
qrCodeLoginWebSocketEndPoint.sendMessage(object.toJSONString());
} catch (Exception e) {
logger.info("app確認登入資訊:接收app推送的確定PC登入訊息異常", e);
}
}
}
核心程式碼就醬......