1. 程式人生 > >WebSocket實現app掃描二維碼登入

WebSocket實現app掃描二維碼登入

後臺框架採用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);
		}
	}

}

核心程式碼就醬......