【Spring实战项目】SpringBoot3整合WebSocket+拦截器实现登录验证!从原理到实战
🎉🎉欢迎光临,终于等到你啦🎉🎉
🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀
🌟持续更新的专栏《Spring 狂野之旅:从入门到入魔》 🚀
本专栏带你从Spring入门到入魔
这是苏泽的个人主页可以看到我其他的内容哦👇👇
努力的苏泽http://suzee.blog.csdn.net/
本文给大家带来的是SpringBoot整合WebSocket 实现一个简单的聊天功能 然后再进阶到语音的聊天 视频聊天
目录
在视频聊天的基础上 还要再实现 美颜、心跳检查掉线、掉帧优化。掉线重连等企业级业务需求
一、WebSocket概述:编辑
实现步骤
首先引入依赖
设置拦截器 自定义报错
这是我做的自定义类型 可以根据自己的修改
拦截器配置
拦截器实现
websocket服务实现
在视频聊天的基础上 还要再实现 美颜、心跳检查掉线、掉帧优化。掉线重连等企业级业务需求
一、WebSocket概述:
WebSocket是基于TCP协议的一种网络协议,它实现了浏览器与服务器全双工通信,支持客户端和服务端之间相互发送信息。在有WebSocket之前,如果服务端数据发生了改变,客户端想知道的话,只能采用定时轮询的方式去服务端获取,这种方式很大程度上增大了服务器端的压力,有了WebSocket之后,如果服务端数据发生改变,可以立即通知客户端,客户端就不用轮询去换取,降低了服务器的压力。目前主流的浏览器都已经支持WebSocket协议了。
WebSocket使用ws和wss作资源标志符,它们两个类似于http和https,wss是使用TSL的ws。主要有4个事件:
- onopen 创建连接时触发
- onclose 连接断开时触发
- onmessage 接收到信息时触发
- onerror 通讯异常时触发
实现步骤
首先引入依赖
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine org.springframework.boot spring-boot-starter-websocket com.alibaba fastjson 1.2.47 org.projectlombok lombok
设置拦截器 自定义报错
@Slf4j @RestControllerAdvice public class WebExceptionAdvice { @ExceptionHandler(RuntimeException.class) public ResponseEntity handleRuntimeException(HttpServletRequest request, RuntimeException e) { log.error(e.toString(), e); Result result = Result.fail(e.getMessage()); HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;//500 if (e instanceof UnAuthorException) { //这个是拦截器报错才设置的状态码 status = HttpStatus.UNAUTHORIZED;//401 } ResponseEntity resultResponseEntity = new ResponseEntity(result, status); log.error(resultResponseEntity.toString()); return resultResponseEntity; } }
这是我做的自定义类型 可以根据自己的修改
public class UnAuthorException extends RuntimeException { public UnAuthorException(String message) { super(message); } }
拦截器配置
@Configuration public class MvcConfig implements WebMvcConfigurer { @Resource private StringRedisTemplate stringRedisTemplate; @Override //添加拦截器 InterceptorRegistry registry 拦截器的注册器 excludePathPatterns排除不需要的拦截的路径 // 只要跟登录无关就不需要拦截 拦截器的作用只是校验登录状态 public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .excludePathPatterns( "/index/**", "/user/wechat/login", "/user/zfb/login", //...这里自己去设置 不想被拦截的页面 剩下的就是被拦截的 ).order(1); // order是设置先后 // 刷新token的拦截器 registry.addInterceptor(new RefreshTokeninterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0); } }
拦截器实现
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //1.判断是否需要拦截(ThreadLocal中是否有用户) if (UserHolder.getUser() == null&&ListenerHolder.getListener()==null) { System.out.println("拦截器报错啦!!!"); //response.getHeader("erro"); throw new UnAuthorException("用户未登录"); } return true; } }
/*/** *@author suze *@date 2023-10-25 *@time 15:23 **/ public class RefreshTokeninterceptor implements HandlerInterceptor { //而MvcConfig中使用了 LoginInterceptor 所以我们要去到MvcConfig进行注入 private StringRedisTemplate stringRedisTemplate; //因为这个类不是spring boot构建的,而是手动创建的类,所以依赖注入不能用注解来注入,要我们手动使用构造函数来注入这个依赖 public RefreshTokeninterceptor(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token2=request.getHeader("token2"); String ListenerKey = LOGIN_LISTENER_KEY + token2; //这里的倾听者信息是在倾听者登录的函数里面把倾听者信息录入进去 String LisStr = stringRedisTemplate.opsForValue().get(ListenerKey); if(LisStr== null || LisStr.isEmpty()){ System.err.println("倾听者token为空"); } else { Listener listener = JSON.parseObject(LisStr, Listener.class); ListenerHolder.saveListener(listener); stringRedisTemplate.expire(ListenerKey,15, TimeUnit.MINUTES); return true; } //获取请求头中的token 在前端代码中详见authorization String token = request.getHeader("token"); if(StrUtil.isBlank(token)){//判断是否为空 System.err.println("token为空"); return true; } // 基于token获取Redis用户 String key =LOGIN_USER_KEY+token; String userstr = stringRedisTemplate.opsForValue().get(key); //System.err.println("基于token获取Redis用户:"+userstr); //判断用户是否存在 不存在的话就查询是否是倾听者的情况 if(userstr== null || userstr.isEmpty()){ System.err.println("用户为空"); return true; } // 将查询到的user的json字符串转化为user对象 User user = JSON.parseObject(userstr, User.class); //存在 保存用户信息到TheadLocal UserHolder.saveUser(user); System.out.println("保存用户"+user.getOpenId()+"信息到TheadLocal了"); //刷新token有效期 stringRedisTemplate.expire(key,15, TimeUnit.MINUTES); //放行 return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //移除用户 UserHolder.removeUser(); ListenerHolder.removeListener(); } }
根据自己需求 删掉一些我这边业务的部分 不删也行 也能用 就是有点慢
websocket服务实现
@ServerEndpoint(value = "/imserver/{userId}") @Component public class WebSocketServer { private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class); /** * 记录当前在线连接数 */ public static final Map sessionMap = new ConcurrentHashMap(); //public static final Map UserMap = new ConcurrentHashMap();这里没有需要知道对方名字的需求 所以不需要加 需要再加 /** *
文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。