
SpringBoot 集成WebSocket
发布日期:2021-05-04 20:40:54
浏览次数:27
分类:精选文章
本文共 8907 字,大约阅读时间需要 29 分钟。
什么是WebSocket
WebSocket 是一种网络通信协议,很多高级功能都需要它。
我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处? 因为 HTTP 协议有一个缺陷:通信只能由客户端发起。 如果我们想要服务器给客户端发信息,只能由客户端建立长连接这种消耗性能的操作。 WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。 它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。详细的WebSocket可以参考阮一峰的博客 :http://www.ruanyifeng.com/blog/2017/05/websocket.html
Maven 依赖
org.springframework.boot spring-boot-starter-websocket
SpringBoot配置
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.server.standard.ServerEndpointExporter;/** * Created with IntelliJ IDEA. * * @Auther: zlf * @Date: 2021/04/30/16:58 * @Description: */@Configuration//@EnableWebSocketMessageBrokerpublic class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter(); }}
WebSokect 通信
import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import javax.websocket.*;import javax.websocket.server.PathParam;import javax.websocket.server.ServerEndpoint;import java.io.IOException;import java.util.Objects;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.CopyOnWriteArraySet;/** * Created with IntelliJ IDEA. * * @Auther: zlf * @Date: 2021/04/30/17:48 * @Description: */@Slf4j@ServerEndpoint("/webSocket/{code}")@Componentpublic class WebSocketServer { /** * concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。 */ private static CopyOnWriteArraySetwebSocketSet = new CopyOnWriteArraySet<>(); /** * 与客户端的连接会话,需要通过它来给客户端发送数据 */ private Session session; /** * 接收识别码 */ private String code = ""; /** * 连接建立成功调用的方法 */ @OnOpen public void onOpen(Session session, @PathParam("code") String code) { this.session = session; //如果存在就先删除一个,防止重复推送消息,实际这里实现了set,不删除问题也不大 webSocketSet.removeIf(webSocket -> webSocket.code.equals(code)); webSocketSet.add(this); this.code = code; log.info("建立WebSocket连接,code:" + code+",当前连接数:"+webSocketSet.size()); } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { webSocketSet.remove(this); log.info("关闭WebSocket连接,code:" + this.code+",当前连接数:"+webSocketSet.size()); } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息 */ @OnMessage public void onMessage(String message, Session session) { log.info("收到来[" + code + "]的信息:" + message); } @OnError public void onError(Session session, Throwable error) { log.error("websocket发生错误"); error.printStackTrace(); } /** * 实现服务器主动推送 */ private void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 群发自定义消息 */ public void sendAll(String message) { log.info("推送消息到" + code + ",推送内容:" + message); for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } } /** * 定点推送 */ public void sendTo(String message, @PathParam("code") String code) { for (WebSocketServer item : webSocketSet) { try { if (item.code.equals(code)) { log.info("推送消息到[" + code + "],推送内容:" + message); item.sendMessage(message); } } catch (IOException e) { e.printStackTrace(); } } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } WebSocketServer that = (WebSocketServer) o; return Objects.equals(session, that.session) && Objects.equals(code, that.code); } @Override public int hashCode() { return Objects.hash(session, code); }}
这样前端发起相应的请求就可以建立双向的通信。
#前端Vue代码
websocket
关于权限认证
import com.baomidou.mybatisplus.core.toolkit.StringUtils;import com.youshe.mcp.utils.JwtTokenUtil;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;import org.springframework.stereotype.Component;import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.ArrayList;import java.util.List;/** * @Description: JWT登录授权过滤器 * @Author: zlf * @Date: 2021/3/30 */@Component@Slf4jpublic class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private JwtTokenUtil jwtTokenUtil; @Value("${jwt.tokenHeader}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; @Autowired private MyUserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String authHeader = request.getHeader(this.tokenHeader); if(StringUtils.isBlank(authHeader)){ // 由于webSocket 没有设置token在请求头里,而在url中 // websocket连接时,令牌放在url参数上, authHeader = request.getParameter(this.tokenHeader); } if (authHeader != null && authHeader.startsWith(this.tokenHead)) { String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer " String username = jwtTokenUtil.getUserNameFromToken(authToken); //log.info("checking username:{}", username); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, new BCryptPasswordEncoder().encode(userDetails.getPassword()), userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); //log.info("authenticated user:{}", username); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request, response); }}
测试
import com.youshe.commonutils.vo.ResultVo;import com.youshe.mcp.entity.User;import com.youshe.mcp.service.UserService;import com.youshe.mcp.service.WebSocketServer;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.Authentication;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import springfox.documentation.annotations.ApiIgnore;import sun.plugin.liveconnect.SecurityContextHelper;import javax.xml.transform.Result;import java.security.Security;/** * Created with IntelliJ IDEA. * * @Auther: zlf * @Date: 2021/04/30/18:03 * @Description: */@RestController@RequestMapping("/test")public class testController { @Autowired UserService userService; @Autowired WebSocketServer webSocketServer; // 向请求的用户 推送消息 @GetMapping("test") public ResultVo test(){ UserDetails userDetails = (UserDetails) org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication().getPrincipal(); String username = userDetails.getUsername(); User user = userService.getUserByName(username); webSocketServer.sendTo("向客户端推送实时消息",user.getId()); return ResultVo.ok(); }}