springboot websocket 原理

复制代码
@ServerEndpoint 会被websocket容器管理,不会被spring管理

每个实例连接由spring容器管理

实例的连接请求,由websocket容器本身管理,包括session

端点方式,不能正常注入bean,通过 SpringContext 去查找bean

基于端点的实现方式

java 复制代码
@Configuration
@EnableWebSocket
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}
java 复制代码
@Slf4j
@Component
@ServerEndpoint(value = "/websocket/{ai-podcast}", encoders = StringEncoder.class)
public class WebSocketServerEndpoint {

    private final Map<String, FileOutputStream> receive = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(Session session, @PathParam("ai-podcast") String flag) {
        log.info("websocket上线:{}", flag);
        WebSocketUtil.addSession(flag, session);
    }

    @OnMessage
    public void onMessageText(Session session, String message) throws IOException {
        log.info("websocket收到文本消息:{}", message);

        JSONObject jsonObject = JSONUtil.parseObj(message);
        if (jsonObject.containsKey("type") && jsonObject.getStr("type").equals("qa")) {
            if (jsonObject.get("status").equals("qa_done")) {
                this.getAiPodcastService().setAnswer(jsonObject.getStr("message_id"), jsonObject.getStr("answer"));
                return;
            }
            if (jsonObject.get("status").equals("stream_start")) {
                FileOutputStream fileOutputStream = new FileOutputStream("E:/pcm/" + jsonObject.get("message_id") + ".pcm");

                receive.put(session.getId(), fileOutputStream);
                return;
            }

            if (jsonObject.get("status").equals("stream_end")) {
                FileOutputStream fileOutputStream = receive.get(session.getId());
                fileOutputStream.flush();
                fileOutputStream.close();

                receive.remove(session.getId());
            }
        }
    }

    @OnMessage
    public void onMessageByte(Session session, byte[] message) throws IOException {
        log.info("websocket收到数组消息:{}", message.length);

        FileOutputStream fileOutputStream = receive.get(session.getId());
        fileOutputStream.write(message);
    }

    @OnClose
    public void onClose(Session session, @PathParam("ai-podcast") String flag) {
        log.info("websocket下线:{}", flag);

        WebSocketUtil.removeSession(flag);
        receive.remove(session.getId());
    }

    @OnError
    public void onError(Session session, Throwable throwable, @PathParam("ai-podcast") String flag) {
        log.error("websocket发生错误:{}", throwable.getMessage(), throwable);
        throwable.printStackTrace();

        WebSocketUtil.removeSession(flag);
        receive.remove(session.getId());
    }

    private AiPodcastService getAiPodcastService() {
        return SpringUtil.getBean(AiPodcastService.class);
    }

}
java 复制代码
@Slf4j
public class WebSocketUtil {

    private final static Map<String, Session> sessions = new ConcurrentHashMap<>();

    public static void addSession(String flag, Session session) {
        sessions.put(flag, session);
    }

    public static void removeSession(String flag) {
        sessions.remove(flag);
    }

    public static void sendMessage(String flag, String message) {
        Session session = sessions.get(flag);
        try {
            log.error("websocket发送消息:{},{}", flag, message);
            session.getBasicRemote().sendObject(message);
        } catch (IOException | EncodeException e) {
            log.error("发送消息失败:{}", e.getMessage(), e);
        }
    }
}
java 复制代码
public class StringEncoder implements Encoder.Text<String> {

    @Override
    public String encode(String message) throws EncodeException {
        // 直接返回字符串,不需要特殊编码
        return message;
    }
}

基于WebSocketHandler

java 复制代码
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private BinaryWebSocketHandler binaryWebSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(binaryWebSocketHandler, "/websocket/upload/{userId}")
                .setAllowedOrigins("*");
    }
}
java 复制代码
@Component
public class BinaryWebSocketHandler implements WebSocketHandler {

    @Resource
    private MyService myService;

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 连接建立
//        myService.logConnection(session.getId());

       // 获取 URI 模板变量
        Map<String, String> pathVariables = (Map<String, String>) 
            session.getAttributes().get(URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        
        String userId = pathVariables.get("userId");
        
        // 存储到 session 属性中
        session.getAttributes().put("userId", userId);
    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        if (message instanceof TextMessage textMessage) {
            // 处理文本消息(元数据)
            String metadata = textMessage.getPayload();
//            myService.processMetadata(metadata, session);
            
        } else if (message instanceof BinaryMessage binaryMessage) {
            // 处理二进制消息(文件数据)
            ByteBuffer buffer = binaryMessage.getPayload();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
//            myService.processBinaryData(data, session);
        }

    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // 处理传输错误
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        // 连接关闭
//        myService.cleanup(session.getId());
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}
相关推荐
fox_lht32 分钟前
第一章 不可变的变量
开发语言·后端·rust
计算机学姐3 小时前
基于微信小程序的高校班务管理系统【2026最新】
java·vue.js·spring boot·mysql·微信小程序·小程序·mybatis
舒一笑5 小时前
PandaCoder:致敬MyBatis Log Plugin,但我们做得更极致!
后端·程序员·intellij idea
简单点了7 小时前
go前后端项目的启动 、打包和部署
开发语言·后端·golang
雨夜之寂7 小时前
mcp java实战 第一章-第一节-MCP协议简介.md
java·后端
摇滚侠7 小时前
Spring Boot 3零基础教程,WEB 开发 Thymeleaf 核心语法 笔记39
spring boot·笔记·后端·thymeleaf
九丶弟8 小时前
SpringBoot的cache使用说明
java·spring boot·spring·cache
Code blocks9 小时前
GB28181视频服务wvp搭建(二)
后端·音视频
码事漫谈9 小时前
C++与C#布尔类型深度解析:从语言设计到跨平台互操作
后端
程序新视界9 小时前
在MySQL中,一条SQL语句的执行全流程是怎样的?
数据库·后端·mysql