Java SpringBoot WebSocket

一、首先创建一张表

记录一下扫码的用户。以及登录的用户。
User_Token表
字段如下:

  • uuid : 用于确保唯一性
  • userId :谁登录的
  • loginTime :登录时间
  • createTime :创建时间 用于判断是否过期
  • state:是否二维码失效 0有效 1失效

    二、角色都有哪些

    还需要分析一下扫码登录这个业务逻辑都有哪些角色

  • android端 or 微信Web端 :扫码

  • PC端 :被扫。登录
  • 服务端:掌控全局,提供接口。

    三、接口都需要哪些?

    有了角色。还需要2个接口!

  • 生成二维码接口:生成一个二维码。二维码中有UUID。

  • 确认身份接口:确定身份以及判断是否二维码过期等

    四、步骤

  • PC端打开。调用生成二维码接口 并与 服务端建立链接。链接使用uuid进行绑定

  • 微信Web端进行扫码。获取二维码中的uuid。
  • 微信Web端拿到uuid以后。显示是否登录页面。点击确定后 调用 确认身份接口。
  • 确认身份接口通过以后。服务端给PC端发送信息。完成登录。此时链接断开。

    五、代码实现

    首先需要获取二维码的代码

    1. //获取登录二维码、放入Token
    2. @RequestMapping(value = "/getLoginQr" ,method = RequestMethod.GET)
    3. public void createCodeImg(HttpServletRequest request, HttpServletResponse response){
    4. response.setHeader("Pragma", "No-cache");
    5. response.setHeader("Cache-Control", "no-cache");
    6. response.setDateHeader("Expires", 0);
    7. response.setContentType("image/jpeg");
    8. try {
    9. //这里没啥操作 就是生成一个UUID插入 数据库的表里
    10. String uuid = userService.createQrImg();
    11. response.setHeader("uuid", uuid);
    12. // 这里是开源工具类 hutool里的QrCodeUtil
    13. // 网址:http://hutool.mydoc.io/
    14. QrCodeUtil.generate(uuid, 300, 300, "jpg",response.getOutputStream());
    15. } catch (Exception e) {
    16. e.printStackTrace();
    17. }
    18. }

    有了获取二维码的接口。相对的前端需要调用。
    知识点:动态加载图片流并取出header中的参数
    这里使用了xmlhttp进行处理。
    为什么?
    因为后端返回的是一个流。
    那么流中。就是放置了二维码中的uuid。这个uuid作为一次会话的标识符使用。
    那么前端也需要拿到。跟后端进行webSocket链接。
    这样有人扫码后。服务端才可以使用webSocket的方式通知前端。有人扫码成功了。
    所以为了拿到请求中 header中放置的uuid 所以这样通过xmlhttp进行处理
    html代码

    1. <div class="qrCodeImg-box" id="qrImgDiv"></div>

    js代码 ```javascript $(document).ready(function(){ initQrImg(); });

function initQrImg(){ $(“#qrImgDiv”).empty();

var xmlhttp; xmlhttp=new XMLHttpRequest(); xmlhttp.open(“GET”,getQrPath,true); xmlhttp.responseType = “blob”; xmlhttp.onload = function(){ console.log(this); uuid = this.getResponseHeader(“uuid”);

  1. if (this.status == 200) {
  2. var blob = this.response;
  3. var img = document.createElement("img");
  4. img.className = 'qrCodeBox-img';
  5. img.onload = function(e) {
  6. window.URL.revokeObjectURL(img.src);
  7. };
  8. img.src = window.URL.createObjectURL(blob);
  9. document.getElementById("qrImgDiv").appendChild(img);
  10. initWebSocket();
  11. }

} xmlhttp.send(); }

var path = “://localhost:8085”; var getQrPath = “http” + path + “/user/getLoginQr”; var wsPath = “ws” + path + “/websocket/“;

function initWebSocket(){

if(typeof(WebSocket) == “undefined”) { console.log(“您的浏览器不支持WebSocket”); }else{ console.log(“您的浏览器支持WebSocket”); //实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接 //等同于socket = new WebSocket(“ws://localhost:8083/checkcentersys/websocket/20”); var wsPathStr = wsPath+uuid; socket = new WebSocket(wsPathStr); //打开事件 socket.onopen = function() { console.log(“Socket 已打开”); //socket.send(“这是来自客户端的消息” + location.href + new Date()); }; //获得消息事件 socket.onmessage = function(msg) { console.log(msg.data); var data = JSON.parse(msg.data); if(data.code == 200){ alert(“登录成功!”); //这里存放自己业务需要的数据。怎么放自己看 window.sessionStorage.uuid = uuid; window.sessionStorage.userId = data.userId; window.sessionStorage.projId = data.projId;

  1. window.location.href = "pages/upload.html"
  2. }else{
  3. //如果过期了,关闭连接、重置连接、刷新二维码
  4. socket.close();
  5. initQrImg();
  6. }
  7. //发现消息进入 开始处理前端触发逻辑
  8. };
  9. //关闭事件
  10. socket.onclose = function() {
  11. console.log("Socket已关闭");
  12. };
  13. //发生了错误事件
  14. socket.onerror = function() {
  15. alert("Socket发生了错误");
  16. //此时可以尝试刷新页面
  17. }

}

}

  1. 好了。上面已经提到了前端如何配置webSocket。<br />下面说一下
  2. <a name="PdwiP"></a>
  3. ## 六、SpringBoot中操作webSocket
  4. <a name="M9PHJ"></a>
  5. ### 1、增加pom.xml
  6. ```xml
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-websocket</artifactId>
  10. </dependency>

2、增加一个Bean

  1. /**
  2. * WebSocket的支持
  3. * @return
  4. */
  5. @Bean
  6. public ServerEndpointExporter serverEndpointExporter() {
  7. return new ServerEndpointExporter();
  8. }

3、定义WebSocketServer

  1. import java.io.IOException;
  2. import java.util.concurrent.CopyOnWriteArraySet;
  3. import javax.websocket.OnClose;
  4. import javax.websocket.OnError;
  5. import javax.websocket.OnMessage;
  6. import javax.websocket.OnOpen;
  7. import javax.websocket.Session;
  8. import javax.websocket.server.PathParam;
  9. import javax.websocket.server.ServerEndpoint;
  10. import org.springframework.stereotype.Component;
  11. import cn.hutool.log.Log;
  12. import cn.hutool.log.LogFactory;
  13. @ServerEndpoint("/websocket/{sid}")
  14. @Component
  15. public class WebSocketServer {
  16. static Log log=LogFactory.get(WebSocketServer.class);
  17. //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
  18. private static int onlineCount = 0;
  19. //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
  20. private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
  21. //与某个客户端的连接会话,需要通过它来给客户端发送数据
  22. private Session session;
  23. //接收sid
  24. private String sid="";
  25. /**
  26. * 连接建立成功调用的方法*/
  27. @OnOpen
  28. public void onOpen(Session session,@PathParam("sid") String sid) {
  29. this.session = session;
  30. webSocketSet.add(this); //加入set中
  31. addOnlineCount(); //在线数加1
  32. log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount());
  33. this.sid=sid;
  34. /*try {
  35. sendMessage("连接成功");
  36. } catch (IOException e) {
  37. log.error("websocket IO异常");
  38. }*/
  39. }
  40. /**
  41. * 连接关闭调用的方法
  42. */
  43. @OnClose
  44. public void onClose() {
  45. webSocketSet.remove(this); //从set中删除
  46. subOnlineCount(); //在线数减1
  47. log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
  48. }
  49. /**
  50. * 收到客户端消息后调用的方法
  51. *
  52. * @param message 客户端发送过来的消息*/
  53. @OnMessage
  54. public void onMessage(String message, Session session) {
  55. log.info("收到来自窗口"+sid+"的信息:"+message);
  56. //群发消息
  57. for (WebSocketServer item : webSocketSet) {
  58. try {
  59. item.sendMessage(message);
  60. } catch (IOException e) {
  61. e.printStackTrace();
  62. }
  63. }
  64. }
  65. /**
  66. *
  67. * @param session
  68. * @param error
  69. */
  70. @OnError
  71. public void onError(Session session, Throwable error) {
  72. log.error("发生错误");
  73. error.printStackTrace();
  74. }
  75. /**
  76. * 实现服务器主动推送
  77. */
  78. public void sendMessage(String message) throws IOException {
  79. this.session.getBasicRemote().sendText(message);
  80. }
  81. /**
  82. * 群发自定义消息
  83. * */
  84. public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException {
  85. log.info("推送消息到窗口"+sid+",推送内容:"+message);
  86. for (WebSocketServer item : webSocketSet) {
  87. try {
  88. //这里可以设定只推送给这个sid的,为null则全部推送
  89. if(sid == null) {
  90. item.sendMessage(message);
  91. }else if(item.sid.equals(sid)){
  92. item.sendMessage(message);
  93. }
  94. } catch (IOException e) {
  95. continue;
  96. }
  97. }
  98. }
  99. public static synchronized int getOnlineCount() {
  100. return onlineCount;
  101. }
  102. public static synchronized void addOnlineCount() {
  103. WebSocketServer.onlineCount++;
  104. }
  105. public static synchronized void subOnlineCount() {
  106. WebSocketServer.onlineCount--;
  107. }
  108. }

这样就增加了webSocket的支持。
那么回到刚才的步骤。
1、首先PC端调用接口展示出来了二维码。
2、请求二维码中的http请求。就有uuid在 header中。直接取到uuid 作为webSocket的标识sid进行连接。
3、然后手机端使用相机拿到二维码中的uuid。使用uuid + userid 请求 扫码成功接口。
扫码成功接口
Controller代码:

  1. /**
  2. * 确认身份接口:确定身份以及判断是否二维码过期等
  3. * @param token
  4. * @param userId
  5. * @return
  6. */
  7. @RequestMapping(value = "/bindUserIdAndToken" ,method = RequestMethod.GET)
  8. @ResponseBody
  9. public Object bindUserIdAndToken(@RequestParam("token") String token ,
  10. @RequestParam("userId") Integer userId,
  11. @RequestParam(required = false,value = "projId") Integer projId){
  12. try {
  13. return new SuccessTip(userService.bindUserIdAndToken(userId,token,projId));
  14. } catch (Exception e) {
  15. e.printStackTrace();
  16. return new ErrorTip(500,e.getMessage());
  17. }
  18. }

Service代码

  1. @Override
  2. public String bindUserIdAndToken(Integer userId, String token,Integer projId) throws Exception {
  3. QrLoginToken qrLoginToken = new QrLoginToken();
  4. qrLoginToken.setToken(token);
  5. qrLoginToken = qrLoginTokenMapper.selectOne(qrLoginToken);
  6. if(null == qrLoginToken){
  7. throw new Exception("错误的请求!");
  8. }
  9. Date createDate = new Date(qrLoginToken.getCreateTime().getTime() + (1000 * 60 * Constant.LOGIN_VALIDATION_TIME));
  10. Date nowDate = new Date();
  11. if(nowDate.getTime() > createDate.getTime()){//当前时间大于校验时间
  12. JSONObject jsonObject = new JSONObject();
  13. jsonObject.put("code",500);
  14. jsonObject.put("msg","二维码失效!");
  15. WebSocketServer.sendInfo(jsonObject.toJSONString(),token);
  16. throw new Exception("二维码失效!");
  17. }
  18. qrLoginToken.setLoginTime(new Date());
  19. qrLoginToken.setUserId(userId);
  20. int i = qrLoginTokenMapper.updateById(qrLoginToken);
  21. JSONObject jsonObject = new JSONObject();
  22. jsonObject.put("code",200);
  23. jsonObject.put("msg","ok");
  24. jsonObject.put("userId",userId);
  25. if(ToolUtil.isNotEmpty(projId)){
  26. jsonObject.put("projId",projId);
  27. }
  28. WebSocketServer.sendInfo(jsonObject.toJSONString(),token);
  29. if(i > 0 ){
  30. return null;
  31. }else{
  32. throw new Exception("服务器异常!");
  33. }
  34. }

逻辑大概就是判断一下 token对不对
如果对的话。时间是否过期。如果没有过期进行业务逻辑操作

  1. //这句话比较关键
  2. WebSocketServer.sendInfo(jsonObject.toJSONString(),token);

就是通知前端 已经登录成功了。并且给他业务所需要的内容。
然后前端代码接收到了。就进行业务逻辑操作就可以了。