2019独角兽企业重金招聘Python工程师标准>>>
注意:必须要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;
}
}
效果:
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
添加后:
然后在配置文件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的解决办法