webSocket实现扫码登录

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

注意:必须要spring4.0 以上的版本的才能整合websocket

          本篇文章中使用spring mvc作为视图框架

实现扫码登录设计了三个接口,其中两个是 HTTP协议的:

http接口1  获取二维码图片

/**
	 * 
	 * @Title:获取登录二维码图片
	 * @Description:Comment for non-overriding methods
	 * @author 张颖辉
	 * @date 2018年3月5日下午1:57:44
	 * @param wsSessionId
	 *            websocket 会话id (通知页面登录成功的连接)
	 * @param request
	 */
	@RequestMapping("getQrCodeImg")
	public void getQrCodeImg(String wsSessionId, HttpServletRequest request, HttpServletResponse response) {
		ServletOutputStream outputStream=null;
		// http://ip:80/LoginMS/qrLogin?appid=?&json={"data":[{ED:"rigour2046"}]}
		try {
			String serverUrl = ServerUtil.getServerUrl(request);
			String qrLoginUrl = serverUrl + "/qrLogin";// http://192.168.1.102:8080/ssoServer/auth/qrCodeLogin
			qrLoginUrl += "?appid=?";
			String clientId = request.getSession().getId() + ":" + wsSessionId;
			qrLoginUrl += "&json={\"data\":[{ED:\"" + clientId + "\"}]}";// 生成的二维码有data,但是APP发送验证的时候没有data这一层,很尴尬
			// http://192.168.1.102:8080/ssoServer/auth/qrCodeLogin?appid=?&json={"data":[{ED:"B49FBCF0B12A06899BF338F9ACE0CC52"}]}
			logger.debug("扫码登录地址:" + qrLoginUrl);
			// 内容所使用编码(有中文则必须指定编码)
			Map hints = new HashMap();
			hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
			try {
				BitMatrix bitMatrix = new MultiFormatWriter().encode(qrLoginUrl, BarcodeFormat.QR_CODE, 250, 250,
						hints);
				outputStream = response.getOutputStream();
				EWCodeUtil.writeToStream(bitMatrix, "jpg", outputStream);
				outputStream.flush();
				outputStream.close();
			} catch (WriterException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}finally {
				try {
					outputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
	}

依赖:


		
		
			com.google.zxing
			core
			3.3.0
		

 其中二维码工具类:

package rg.sso.util;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
/**
 * 二维码工具类
 * @Title:EWCodeUtil
 * @Description:Comment for created type
 * @author 张颖辉
 * @date 2016-12-13下午03:00:33
 * @version 1.0
 */
public class EWCodeUtil {
	private static final int BLACK = 0Xff000000;
	private static final int WHITE = 0xffffffff;

	public EWCodeUtil() {

	}

	public static BufferedImage toBufferedImage(BitMatrix matrix) {
		int width = matrix.getWidth();
		int height = matrix.getHeight();
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				image.setRGB(x, y, (matrix.get(x, y) ? BLACK : WHITE));
			}
		}
		return image;
	}

	/**
	 * 
	 * @param matrix 字节阵列
	 * @param format 图片格式
	 * @param file 图片文件(传入文件名称,路径)
	 * @throws IOException
	 */
	public static void writeToFile(BitMatrix matrix, String format, File file) throws IOException {
		BufferedImage image = toBufferedImage(matrix);
		if (!ImageIO.write(image, format, file)) {
			throw new IOException("Could not write an image of format " + format + " to " + file);
		}
	}
	/**
	 * 
	 * @param matrix 字节阵列
	 * @param format 图片格式
	 * @param stream 图片文件流
	 * @throws IOException
	 */
	public static void writeToStream(BitMatrix matrix, String format, OutputStream stream) throws IOException {
		BufferedImage image = toBufferedImage(matrix);
		if (!ImageIO.write(image, format, stream)) {
			throw new IOException("Could not write an image of format " + format);
		}
	}

	public static void main(String[] args) {
		try {
			String content = "http://jingyan.baidu.com/article/915fc4149e1f9a51394b2007.html";

			// String path = "D:";
			String path = "D:/";
			MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
			Map hints = new HashMap();
			// 内容所使用编码(有中文则必须指定编码)
			hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
			BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, 200, 200, hints);
			// 生成二维码
			File outputFile = new File(path, "张颖辉.jpg");
			EWCodeUtil.writeToFile(bitMatrix, "jpg", outputFile);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

其中 ServerUtil

package rg.sso.util;

import java.net.UnknownHostException;

import javax.servlet.http.HttpServletRequest;

public class ServerUtil {
	/**
	 * 获取本机的服务器地址(以客户端访问的ip为主)
	 * 
	 * @Title:函数
	 * @Description:Comment for non-overriding methods
	 * @author 张颖辉
	 * @date 2018年3月5日上午11:13:37
	 * @return
	 * @throws UnknownHostException
	 */
	public static String getServerUrl(HttpServletRequest request) throws UnknownHostException {
		String serverUrl = null;
		StringBuffer requestURL = request.getRequestURL();
		serverUrl = requestURL.substring(0, requestURL.lastIndexOf("/"));
		return serverUrl;
	}
}

效果:

webSocket实现扫码登录_第1张图片

http接口2  APP发送验证登录信息

/**
	 * 
	 * @Title:二维码登录接口
	 * @Description:APP扫一扫后 请求服务器 二维码登录接口
	 * @author 张颖辉
	 * @date 2018年3月5日上午10:38:57
	 * @param userid
	 *            用户主键
	 * @param secretType
	 *            秘钥类型:1:手机号 2:邮箱
	 * @param secretKey
	 *            秘钥 Userid+手机号/邮箱的md5
	 * @param equipId
	 *            设备号,二维码唯一标识(来自二维码的回传)
	 * @return
	 */
	// http://192.168.1.110:8080/ssoServer/auth/qrCodeLogin?appid=8a7d0ec2c8184d2aad329c55259bdee4&json={ED:"111111",userid:"00126ac4091749fe8da5e6745d155e7a"}
	@RequestMapping("qrLogin") // CBSA要求必须使用这个名称
	public void qrCodeLogin(String appid, String json, HttpServletRequest request, HttpServletResponse response) {
		response.setHeader("Content-type", "text/html;charset=UTF-8");
		// TODO 关于appid登录渠道暂时不处理

		try {
			// 解析json参数
			ServletOutputStream outputStream = response.getOutputStream();
			// JSONObject 可以解析key缺少双引号的jaon
			JSONObject jsonObject = JSONObject.parseObject(json);
			if (json == null || jsonObject == null) {
				// 因为APP已经完成智能接收这样格式的信息,这里就不单独分装返回的类了
				outputStream.write("{success:false,msg:'json不能为空'}".getBytes());
				outputStream.flush();
				outputStream.close();
				return;
			}
			String userid = jsonObject.getString("userid");
			String equipmentId = jsonObject.getString("ED");

			if (StringUtil.isEmpty(userid)) {
				outputStream.write("{success:false,msg:'userId不能为空'}".getBytes());
				outputStream.flush();
				outputStream.close();
				return;
			}
			if (StringUtil.isEmpty(equipmentId)) {
				outputStream.write("{success:false,msg:'ED不能为空'}".getBytes());
				outputStream.flush();
				outputStream.close();
				return;
			}
			// 保存user到http会话
			User user = userService.selectUserById(userid);
			if (user == null) {
				outputStream.write("{success:false,msg:'user不存在'}".getBytes());
				outputStream.flush();
				outputStream.close();
				return;
			}
			String sessionId = equipmentId.split(":")[0];
			String wsId = equipmentId.split(":")[1];
			GlobalSessionCache.getInstance().get(sessionId).setAttribute("user", user);
			// request.getSession().setAttribute("user", user);
			// 【推送】通知页面跳转
			Msg4Ws msg4Ws = new Msg4Ws(10001, "登录成功", true);
			String message = new ObjectMapper().writeValueAsString(msg4Ws); // 转为json
			websocketHandler.sendMsgToUser(wsId, new TextMessage(message));
			logger.info("推送发送成功");

			// 返回给手机端
			outputStream.write("{success:true,msg:'验证成功啦!'}".getBytes());
			outputStream.flush();
			outputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

【重点】websocket接口


		
			org.springframework
			spring-websocket
		
		
			org.springframework
			spring-messaging
		

在spring的配置文件头部beans标签属性中添加xsd引用:

xmlns:websocket="http://www.springframework.org/schema/websocket"

和 

http://www.springframework.org/schema/websocket 
http://www.springframework.org/schema/websocket/spring-websocket.xsd

添加后:

webSocket实现扫码登录_第2张图片

然后在配置文件beans标签内添加websocket配置的节点:


      
      
          
          
              
          
    

说明:WebsocketHandler 类是处理消息的类,

        HandshakeInterceptor 是握手的拦截器

        path="/websocket" 表示请求路径

        allowed-origins="*" 表示允许使用任何IP 域名来访问

HandshakeInterceptor 类:

package rg.sso.websocket;

import java.util.Map;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
/**
 * 
 * @Title:HandshakeInterceptor 握手拦截器
 * @Description:Comment for created type
 * @author 张颖辉
 * @date 2018年3月5日下午2:31:35
 * @version 1.0
 */
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {
	@Override
	public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
			Map attributes) throws Exception {
		System.out.println("Before Handshake");
		return super.beforeHandshake(request, response, wsHandler, attributes);
	}

	@Override
	public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
			Exception ex) {
		System.out.println("After Handshake");
		super.afterHandshake(request, response, wsHandler, ex);
	}
}

WebsocketHandler  类:

package rg.sso.websocket.handler;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import com.fasterxml.jackson.databind.ObjectMapper;

import rg.sso.vo.Msg4Ws;

/**
 * 
 * @Title:WebsocketEndPoint 消息处理根类
 * @Description:Comment for created type
 * @author 张颖辉
 * @date 2018年3月5日下午2:31:48
 * @version 1.0
 */
public class WebsocketHandler extends TextWebSocketHandler {
	private static Logger logger = LoggerFactory.getLogger(WebsocketHandler.class);
	// 保存所有的用户session
	private static Map SESSION_MAP = new HashMap();

	// 处理信息
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		//super.handleTextMessage(session, message);
		String msgStr = message.getPayload();
		logger.info("收到消息:" + message);
		logger.info("收到消息内容:" + msgStr);
		String resultMsg = msgStr + " received at server";
		logger.info("返回信息:" + resultMsg);
		TextMessage returnMessage = new TextMessage(resultMsg);
		session.sendMessage(returnMessage);
	}

	// 连接 就绪时
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		logger.debug("[{} : {}] has be connected...", session.getUri(), session.getId());
		SESSION_MAP.put(session.getId(), session);
		logger.info("返回id");
		Msg4Ws msg4Ws=new Msg4Ws(10000, "ws的sessionId", session.getId());
		String message = new ObjectMapper().writeValueAsString(msg4Ws); // 转为json
		TextMessage returnMessage = new TextMessage(message);
		session.sendMessage(returnMessage);
	}

	// 关闭 连接时
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		logger.debug("[{} : {}]", session.getUri(), session.getId());
		SESSION_MAP.remove(session.getId());
	}

	// 处理传输时异常
	@Override
	public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
		// TODO Auto-generated method stub
		super.handleTransportError(session, exception);
	}

	// 支持局部消息
	@Override
	public boolean supportsPartialMessages() {
		// TODO Auto-generated method stub
		return super.supportsPartialMessages();
	}

	/**
	 * 
	 * @Title:向所有用户推送消息
	 * @Description:Comment for non-overriding methods
	 * @author 张颖辉
	 * @date 2018年3月5日下午3:47:50
	 * @param message
	 * @throws Exception
	 */
	public void sendMsgToAllUsers(WebSocketMessage message) throws Exception {
		for (WebSocketSession user : SESSION_MAP.values()) {
			user.sendMessage(message);
		}
	}

	/**
	 * @Title:向一个用户推送消息
	 * @Description:Comment for non-overriding methods
	 * @author 张颖辉
	 * @date 2018年3月5日下午3:47:32
	 * @param wsSessionId
	 * @param message
	 * @throws IOException
	 */
	public void sendMsgToUser(String wsSessionId, WebSocketMessage message) throws IOException {
		WebSocketSession session = SESSION_MAP.get(wsSessionId);
		if (session == null) {
			throw new RuntimeException("对应sessionid(" + wsSessionId + ")的session不存在");
		}
		session.sendMessage(message);
	}
}

这样,当tomcat 启动后,websocket就启动监听客户端连接了。

前端测试页面中的关键js:

//websocket  ---START
				var basePath = '${basePath}';//http://192.168.1.110:8080/ssoServer/
				//var url = "ws://localhost:8080/ssoServer/websocket";
				var url = basePath.replace('http', 'ws') + "websocket";
				//alert(url);

				var ws = new WebSocket(url);

				ws.addEventListener('open', function(ev) {
					console.log('websocket已经连接');
					ws.send('Hello');
				});

				ws.onmessage = function(evt) {
					console.log("Received Message: " + evt.data);
					//{"code":10001,"msg":"登录成功","data":true}
					var json = evt.data;
					var hasCode = json.match("code");
					if (hasCode == null) {
						return;
					}
					var dataObj = eval("(" + json + ")");
					var code = dataObj.code;
					console.log("code=" + code);

					if (code == "10000") {
						//document.getElementById("loginQrImg").src="auth/getQrCodeImg?wsSessionId="+;
						$("#loginQrImg").attr("src",
								"auth/getQrCodeImg?wsSessionId=" + dataObj.data);
					} else if (code == "10001") {
						ws.close();
						location.reload();
					}
				};
				//websocket  ---END

websocket的消息分装 

package rg.sso.vo;

import java.io.Serializable;

/**
 * @Title:WebSocket 消息
 * @Description:Comment for non-overriding methods
 * @author 张颖辉
 * @date 2018年2月9日下午1:35:49
 */

public class Msg4Ws implements Serializable {

	private static final long serialVersionUID = 1L;
	
	private int code;//消息编号
	private String msg;//消息描述
	private T data;//消息实体

	public Msg4Ws(int code) {
		this.code = code;
	}

	public Msg4Ws(int code, T data) {
		this.code = code;
		this.data = data;
	}

	public Msg4Ws(int code, String msg, T data) {
		this.code = code;
		this.msg = msg;
		this.data = data;
	}

	public Msg4Ws(int code, String msg) {
		this.code = code;
		this.msg = msg;
	}

	public int getCode() {
		return code;
	}

	public void setCode(int code) {
		this.code = code;
	}

	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}

	public T getData() {
		return data;
	}

	public void setData(T data) {
		this.data = data;
	}
	
	
}

以上是使用了spring 分装之后的webcoket

还有 原生的 javax.websocket

另外 spring webSocket 中获取 httpsession的解决办法

转载于:https://my.oschina.net/iyinghui/blog/1630321

你可能感兴趣的:(webSocket实现扫码登录)