继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

webSocket实现扫码登录

德玛西亚99
关注TA
已关注
手记 270
粉丝 92
获赞 557

摘要: 最近工作中需要完成一个APP(不是微信)扫码登录的功能,在APP请求服务器验证成功后,服务器需要推送消息到页面,通知页面登录跳转。 websocket是H5的新特性,虽然08年就已经诞生,但是到现在才被真正的推广。网页版的长连接 web的服务器推送技术 比以前使用ajax轮训性能更高                

注意:必须要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<EncodeHintType, String> hints = new HashMap<EncodeHintType, String>();
			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();
		}
	}

依赖:

		<!-- 二维码依赖 -->
		<dependency>
			<groupId>com.google.zxing</groupId>
			<artifactId>core</artifactId>
			<version>3.3.0</version>
		</dependency>

 其中二维码工具类:

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<EncodeHintType, String> hints = new HashMap<EncodeHintType, String>();			// 内容所使用编码(有中文则必须指定编码)
			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;
	}
}

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<Boolean> msg4Ws = new Msg4Ws<Boolean>(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接口

<!-- spring-websocket -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-websocket</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-messaging</artifactId>
		</dependency>

在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

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

<!-- websocket -->    <bean id="websocket" class="rg.sso.websocket.handler.WebsocketHandler"/>  
    <websocket:handlers allowed-origins="*">  
        <websocket:mapping path="/websocket" handler="websocket"/>  
        <websocket:handshake-interceptors>  
            <bean class="rg.sso.websocket.HandshakeInterceptor"/>  
        </websocket:handshake-interceptors>  
    </websocket:handlers>

说明: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<String, Object> 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<String, WebSocketSession> SESSION_MAP = new HashMap<String, WebSocketSession>();	// 处理信息
	@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<String> msg4Ws=new Msg4Ws<String>(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<T> 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


打开App,阅读手记
1人推荐
发表评论
随时随地看视频慕课网APP