@

目录

前言



一、建项目

1. 在父项目ams-cloud下建立maven子项目ams-websocket

2.pom文件添加常用依赖,另外添加redis依赖等,我这里直接引用common模块

    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>com.ams</groupId>
            <artifactId>ams-common</artifactId>
            <version>${ams.version}</version>
        </dependency>

    </dependencies>

3.添加bootstrap.yml文件

server:
  port: 21000

spring:
  application:
    name: ams-websocket
  cloud:
    nacos:
      # 注册中心
      discovery:
        server-addr: http://192.168.132.129:8848
      # 配置中心
      config:
        server-addr: ${spring.cloud.nacos.discovery.server-addr}
        file-extension: yaml
        shared-configs[0]:
          data-id: ams-common.yaml
          refresh: true
          
logging:
  level:
    spring.: DEBUG

4.创建application

@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@ComponentScan("com.ams")
public class WebsocketApp {
    public static void main(String[] args) {
        SpringApplication.run(WebsocketApp.class,args);
    }
}

二、添加config类、拦截器类、处理器类等

1.添加config类


@Configuration
@EnableWebSocket
public class WebsocketConfig implements WebSocketConfigurer {

    @Autowired
    private RedisUtils redisUtils;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册websocket组件 添加处理器和拦截器
        // websocket是websocket服务器的请求路径可以自己定义
        registry.addHandler(new WebsocketHandler(redisUtils),"/websocket")
                //指定自定义拦截器
                .addInterceptors(new WebsocketInterceptor(redisUtils))
                //允许跨域
                .setAllowedOrigins("*");

        //在某些低版本的浏览器中不支持websocket可以用sock-js替代
        registry.addHandler(new WebsocketHandler(redisUtils),"/sock-js")
                // 指定自定义拦截器
                .addInterceptors(new WebsocketInterceptor(redisUtils))
                // 允许跨域
                .setAllowedOrigins("*")
                // 开启sockJs支持
                .withSockJS();

    }

}

2.添加拦截器类

@Slf4j
public class WebsocketInterceptor extends HttpSessionHandshakeInterceptor  {

    private final RedisUtils redisUtils;
    public WebsocketInterceptor(RedisUtils redisUtils){
        this.redisUtils=redisUtils;
    }
    
	/** 管理握手过程,存入用户信息 */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request,
                                   ServerHttpResponse response,
                                   WebSocketHandler handler,
                                   Map<String, Object> map) throws Exception {
        String header_key = "Sec-WebSocket-Protocol";
        HttpHeaders headers = request.getHeaders();
        String token = headers.getFirst(header_key);
        log.info("token = [{}]", token);
        HttpHeaders responseHeaders = response.getHeaders();
        UserAuthDTO userAuthDTO = getUserInfo(token);
        if(userAuthDTO == null){
            log.error("socket连接失败 ---> token过期 ---> [{}]", token);
            response.setStatusCode(HttpStatus.NETWORK_AUTHENTICATION_REQUIRED);
            return false;
        }
        map.put("userAuthDTO",userAuthDTO);
        responseHeaders.add(header_key,token);
        return super.beforeHandshake(request, response, handler, map);

    }

    /** 通过redis读取用户信息 **/
    public UserAuthDTO getUserInfo(String token) {
        String key = token;
        if(StrUtil.isEmpty(key)){
            return null;
        }
        UserAuthDTO userAuthDTO = (UserAuthDTO) redisUtils.get(key);
        if(userAuthDTO==null){
            log.error("redis用户信息空 ---> 登录过期/token不正确");
            return null;
        }
        return userAuthDTO;
    }
}

3.添加处理器类

@Slf4j
public class WebsocketHandler extends AbstractWebSocketHandler  {

    //定义全局变量用于保存所有用户的会话
    /** 系统管理员 **/
    public static final Map<String, WebSocketSession> SYSUSER_SOCKETS = new HashMap<>();

    private static RedisUtils redisUtils;
    public WebsocketHandler(RedisUtils redisUtils){
        WebsocketHandler.redisUtils = redisUtils;
    }

    /**
     * webSocket连接创建后调用
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        Map<String, Object> attrMap = session.getAttributes();
        UserAuthDTO userAuthDTO = (UserAuthDTO) attrMap.get("userAuthDTO");
        SYSUSER_SOCKETS.put(userAuthDTO.getUserId().toString(),session);
        log.info("管理员[{}]连接成功,当前在线人数[{}]", userAuthDTO.getUsername(), SYSUSER_SOCKETS.size());
    }

    /**
     * 接收到消息会调用
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        try {
       		log.info("收到客户端消息[{}]", message);
            if(session.isOpen()){
                session.sendMessage(message);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 连接关闭会调用
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("客户端关闭连接....");
        Map<String, Object> attrMap = session.getAttributes();
        // 删除缓存
        deleteSocket(attrMap);
        // 关闭连接
        session.close();
        log.info("已关闭socket连接");
    }

    /**
     * 删除socket缓存
     */
    public static void deleteSocket(Map<String, Object> attr) {
        UserAuthDTO userAuthDTO = (UserAuthDTO) attr.get("userAuthDTO");
        if (!SYSUSER_SOCKETS.isEmpty()) {
            SYSUSER_SOCKETS.remove(userAuthDTO.getUserId().toString());
            log.info("管理员[{}]关闭连接了,当前在线人数[{}]", userAuthDTO.getUsername(), SYSUSER_SOCKETS.size());
        }
    }

    /**
     * 连接出错会调用
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        log.error("socket连接出错...");
        exception.printStackTrace();
        Map<String, Object> attrMap = session.getAttributes();
        //删除缓存
        deleteSocket(attrMap);
        // 关闭连接
        session.close();
        log.error("已关闭socket连接");
    }
}

三、添加controller

1.controller如下:

@RestController
@RequestMapping("/websocket")
@Slf4j
@RequiredArgsConstructor
public class WebsocketController {

    /** 获取在线管理用户 */
    @GetMapping(value = "getLoginSysUser")
    public R<List<UserAuthDTO>> getLoginSysUser(){
        Map<String, WebSocketSession> userMap = WebsocketHandler.SYSUSER_SOCKETS;
        List<UserAuthDTO> list = new ArrayList<>();
        userMap.forEach((k,v)->{
            Map<String, Object> attrMap = v.getAttributes();
            UserAuthDTO userAuthDTO = (UserAuthDTO) attrMap.get("userAuthDTO");
            list.add(userAuthDTO);
        });
        return R.ok(list);
    }
    
}


2.运行结果
2022-03-04 16:58:56.822  INFO 15272 --- [nio-21000-exec-2] c.c.f.m.w.config.WebsocketInterceptor    : token = [83868c09-a744-4a7a-a16e-24dc3d7e8cdc]
2022-03-04 16:58:59.335  INFO 15272 --- [nio-21000-exec-2] c.c.f.m.w.handler.WebsocketHandler       : 管理员[admin]连接成功,当前在线人数[1]

*随心所往,看见未来。Follow your heart,see night!*
**欢迎点赞、关注、留言,一起学习、交流!**