Kotlin 实现 STOMP 聊天服务启动报错及功能实现问题咨询
问题1:启动报错修复方案
报错根本原因是你直接把携带不可序列化WebSocketSession的DTO类交给JPA作为持久化实体使用,且没有添加JPA实体相关注解,JPA无法识别该类为托管类型。修复步骤如下:
- 拆分实体类和DTO类:新建
entity包,创建JPA持久化实体类ChatRoom,仅保留需要存入数据库的字段:
- 拆分实体类和DTO类:新建
package com.example.stompserver.chat.entity import jakarta.persistence.Entity import jakarta.persistence.Id @Entity class ChatRoom { @Id var roomId: String = "" var name: String = "" }
- 修改
ChatRoomRepository关联实体类,不再关联DTO:
- 修改
package com.example.stompserver.chat.repository import com.example.stompserver.chat.entity.ChatRoom import org.springframework.data.jpa.repository.JpaRepository interface ChatRoomRepository: JpaRepository<ChatRoom, String> { }
- 调整
ChatRoomService,内存存储房间会话集合(WebSocketSession属于运行时临时对象,不可序列化存入数据库):
- 调整
package com.example.stompserver.chat.service import com.example.stompserver.chat.entity.ChatRoom import com.example.stompserver.chat.repository.ChatRoomRepository import org.springframework.stereotype.Service import org.springframework.web.socket.WebSocketSession import java.util.UUID import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArraySet @Service class ChatRoomService( private val chatRoomRepository: ChatRoomRepository ) { // 内存存储房间对应的会话集合,线程安全实现 private val roomSessions = ConcurrentHashMap<String, CopyOnWriteArraySet<WebSocketSession>>() fun findAllRooms(): List<ChatRoom> { return chatRoomRepository.findAll() } fun createChatRoom(name: String): ChatRoom { val room = ChatRoom().apply { roomId = UUID.randomUUID().toString() this.name = name } chatRoomRepository.save(room) // 初始化该房间的会话集合 roomSessions[room.roomId] = CopyOnWriteArraySet() return room } // 可按需添加会话增删方法 fun addSession(roomId: String, session: WebSocketSession) { roomSessions[roomId]?.add(session) } }
修改完成后启动报错即可解决。
问题2:现有代码优化点
- 给
RoomController的create方法添加请求映射注解,原代码没有绑定路由,无法接收请求:
- 给
@PostMapping("/create") fun create(@RequestParam name: String): ChatRoom { return chatRoomService.createChatRoom(name) }
删除原代码中的重定向逻辑,API服务不需要返回服务端渲染视图,直接返回JSON格式数据即可。
- 删除原
ChatRoomDTO中冗余的create实例方法,改为伴生对象工厂方法或者直接用构造函数初始化,避免不必要的对象创建开销。
- 删除原
- 所有DTO类改用
data class定义,自动生成equals/hashCode/toString方法,更适合Kotlin数据传输场景:
- 所有DTO类改用
data class ChatMessageDTO( var roomId: String = "", var writer: String = "", var message: String = "" )
- 给接口请求添加参数校验,避免非法参数导致的服务端异常。
- WebSocket端点可按需关闭SockJS支持,方便原生STOMP客户端连接:
registry.addEndpoint("/ws").setAllowedOriginPatterns("*"),生产环境请严格配置跨域地址。
- WebSocket端点可按需关闭SockJS支持,方便原生STOMP客户端连接:
问题3:服务端主动发消息实现与Postman 500错误修复
Postman 500错误原因及修复
- 原
create方法没有添加@PostMapping注解,请求无法匹配路由,接口不存在导致500。 - 原代码启动报错导致服务未正常运行,请求无法处理。
修复上述两个问题后,Postman发送POST请求到/chat/create?name=测试房间即可正常创建房间,返回房间信息。
如果需要调用STOMP消息接口,Postman需使用WebSocket类型的请求,先连接ws://localhost:8080/ws,再按照STOMP协议格式发送消息到/pub/chat/message路径,不要用普通HTTP请求调用@MessageMapping的接口,该类接口仅接收STOMP协议消息,不响应HTTP请求。
服务端主动发消息实现
直接在任意Spring管理的Bean中注入SimpMessagingTemplate,调用convertAndSend方法即可向指定路径的订阅者推送消息,示例如下:
@Service class SystemNotifyService( private val template: SimpMessagingTemplate ) { // 示例:向指定房间发送系统通知 fun sendSystemNotify(roomId: String, content: String) { val message = ChatMessageDTO( roomId = roomId, writer = "系统通知", message = content ) template.convertAndSend("/sub/chat/room/${roomId}", message) } }
不管是定时任务触发、其他业务逻辑触发,都可以直接调用该类方法实现主动推送,不需要客户端主动发起请求。
内容的提问来源于stack exchange,提问作者pijyuuuuu
相关产品推荐
相关产品推荐

