You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring后端WebSocket无法从外部类触发的问题求助

问题:Spring WebSocket外部触发推送无响应

我用Spring后端搭配Angular+Ionic前端搭建了WebSocket,目的是Moodle中教师修改成绩时触发前端通知。前端已正确订阅WebSocket:

constructor(private apiUserService: ApiUserService) { 
  this.webSocket = new WebSocket('ws://localhost:8080/newGrade');
  this.webSocket.onmessage = (event) => {
    console.log(event.data)
  }
}

前端能正常接收连接建立后的第一条消息,但更新数据库并调用Handler触发推送时,前端无响应。已知数据库更新成功,Moodle插件及相关方法运行正常。

后端相关代码如下:

控制层代码

@Autowired
private NewGradeHandler newGradeHandler; 

private final ObjectMapper objectMapper = new ObjectMapper();

@PostMapping("/updateGrade")
public ResponseEntity<String> miEndpoint(@RequestBody Map<String, Object> datos) throws Exception {
    try {
        // 获取请求数据
        Long userId = Long.parseLong(datos.get("userid").toString());
        if (userId != -1) {
            Long courseId = Long.parseLong(datos.get("courseid").toString());
            int mark = Integer.parseInt(datos.get("grade").toString());
            String assignmentName = datos.get("assignmentname").toString();

            // 查询用户和科目
            User student = userService.findById(userId);
            Subject subject = subjectService.getSubject(courseId);

            // 创建新成绩或更新已有成绩
            if (!subjectMarkService.existsByStudentIdAndSubjectIdAndNameMark(student, subject, assignmentName)) {
                Subject_Mark newMark = new Subject_Mark(student, subject, mark, "Ordinaria", assignmentName); 
                subjectMarkService.save(newMark);
                Subject_Mark updatedGrade = subjectMarkService.getLastSubjectMarkAdded();
                sendMessageToWebSocket(updatedGrade);
            } else {
                updateExistingMark(student, subject, mark, assignmentName);
            }

            return ResponseEntity.ok("Nota creada/actualizada correctamente");
        }
        return ResponseEntity.ok("ok");
    } catch (NumberFormatException e) {
        return ResponseEntity.badRequest().body("Error al convertir los datos");
    } catch (RuntimeException e) {
        return ResponseEntity.badRequest().body(e.getMessage());
    }
}

private void updateExistingMark(User student, Subject subject, int mark, String assignmentName) throws Exception {
    Subject_Mark existingMark = subjectMarkService.findByStudentIdAndSubjectIdAndNameMark(student, subject, assignmentName).get();
    existingMark.setMark(mark);
    subjectMarkService.save(existingMark);
}

private void sendMessageToWebSocket(Subject_Mark grade) throws Exception {
TextMessage message = new TextMessage(objectMapper.writeValueAsString(grade));
for (WebSocketSession session : newGradeHandler.getSessions()) {
    session.sendMessage(message);
}

WebSocket配置类

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    registry.addHandler(new NewGradeHandler(), "/newGrade").setAllowedOrigins("*");
}

@Bean
public WebSocketHandler NewGradeHandler() {
    return new NewGradeHandler(); 
}}

WebSocket处理器类

@Component
public class NewGradeHandler extends TextWebSocketHandler {
private final ObjectMapper objectMapper = new ObjectMapper();
private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
@Autowired
private SubjectMarkService subjectMarkService;

@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {

    Subject_Mark newGrade = subjectMarkService.getLastSubjectMarkAdded();
    
        TextMessage message = new TextMessage(objectMapper.writeValueAsString(newGrade));
        session.sendMessage(message);
    sessions.add(session);
}

public List<WebSocketSession> getSessions() {
    return sessions;
}}

已查阅相关指南,但大多是类内部更新WebSocket的场景(这类场景可正常运行),现在需要外部类触发WebSocket的解决方案。


问题分析与解决方案

核心问题1:更新已有成绩时未触发推送

控制层的updateExistingMark方法执行完成绩更新后,没有调用sendMessageToWebSocket发送更新后的成绩数据,这是更新操作前端无响应的直接原因。

修改updateExistingMark方法,添加推送逻辑:

private void updateExistingMark(User student, Subject subject, int mark, String assignmentName) throws Exception {
    Subject_Mark existingMark = subjectMarkService.findByStudentIdAndSubjectIdAndNameMark(student, subject, assignmentName).get();
    existingMark.setMark(mark);
    subjectMarkService.save(existingMark);
    // 添加推送逻辑
    sendMessageToWebSocket(existingMark);
}

核心问题2:WebSocket处理器实例不一致

WebSocketConfig中,registerWebSocketHandlers方法直接new NewGradeHandler()创建实例,而@Bean方法又返回一个全新的NewGradeHandler对象。这导致控制层注入的newGradeHandler和实际处理WebSocket连接的实例不是同一个,sessions列表为空,推送时遍历不到有效连接。

修改WebSocketConfig,使用@Bean返回的实例注册handler:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private NewGradeHandler newGradeHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 使用注入的实例,而非新建对象
        registry.addHandler(newGradeHandler, "/newGrade").setAllowedOrigins("*");
    }

    @Bean
    public WebSocketHandler newGradeHandler() {
        return new NewGradeHandler(); 
    }
}

额外优化建议

  1. 推送消息时添加异常捕获,避免单个session发送失败导致整个推送中断:
private void sendMessageToWebSocket(Subject_Mark grade) throws Exception {
    TextMessage message = new TextMessage(objectMapper.writeValueAsString(grade));
    for (WebSocketSession session : newGradeHandler.getSessions()) {
        try {
            if (session.isOpen()) {
                session.sendMessage(message);
            } else {
                // 移除已关闭的session
                newGradeHandler.getSessions().remove(session);
            }
        } catch (IOException e) {
            // 记录日志,不中断其他推送
            e.printStackTrace();
        }
    }
}
  1. 在NewGradeHandler中重写afterConnectionClosed方法,移除关闭的session,避免无效连接占用资源:
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
    sessions.remove(session);
    super.afterConnectionClosed(session, status);
}

内容的提问来源于stack exchange,提问作者Jesús Mariscal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 08:17:02