SpringBoot整合Websocket實(shí)現(xiàn)即時(shí)聊天功能

近期,公司需要新增即時(shí)聊天的業(yè)務(wù),于是用websocket 整合到Springboot完成業(yè)務(wù)的實(shí)現(xiàn)。

一、我們來(lái)簡(jiǎn)單的介紹下websocket的交互原理:

1.客戶端先服務(wù)端發(fā)起websocket請(qǐng)求;

2.服務(wù)端接收到請(qǐng)求之后,把請(qǐng)求響應(yīng)返回給客戶端;

3.客戶端與服務(wù)端只需要一次握手即可完成交互通道;


? 二、webscoket支持的協(xié)議:基于TCP協(xié)議下,http協(xié)議和https協(xié)議;

? http協(xié)議 springboot不需要做任何的配置?

? https協(xié)議則需要配置nignx代理,注意證書有效的問(wèn)題? ---在這不做詳細(xì)說(shuō)明

? 三、開始我們的實(shí)現(xiàn)java后端的實(shí)現(xiàn)

? 1.添加依賴

? <!-- websocket -->

? ? ? ? <dependency>

? ? ? ? ? ? <groupId>org.springframework.boot</groupId>

? ? ? ? ? ? <artifactId>spring-boot-starter-websocket</artifactId>

? ? ? ? </dependency>

? ? ? ? <dependency>

? ? ? ? ? ? <groupId>org.springframework</groupId>

? ? ? ? ? ? <artifactId>spring-websocket</artifactId>

? ? ? ? ? ? <version>${spring.version}</version>

? ? ? ? </dependency>

? ? ? ? <dependency>

? ? ? ? ? ? <groupId>org.springframework</groupId>

? ? ? ? ? ? <artifactId>spring-messaging</artifactId>

? ? ? ? ? ? <version>${spring.version}</version>

? ? ? ? </dependency>

? ? ? ? <!-- WebSocket -->

? 2.配置config

@ConditionalOnWebApplication

@Configuration

@EnableWebSocketMessageBroker

public class WebSocketConfig extends AbstractSessionWebSocketMessageBrokerConfigurer {

? ? @Bean

? ? public ServerEndpointExporter serverEndpointExporter(){

? ? ? ? return? new ServerEndpointExporter();

? ? }

? ? @Bean

? ? public CustomSpringConfigurator customSpringConfigurator() {

? ? ? ? return new CustomSpringConfigurator();

? ? }

? ? @Override

? ? protected void configureStompEndpoints(StompEndpointRegistry registry) {

? ? ? ? registry.addEndpoint("/websocket").setAllowedOrigins("*")

? ? ? ? ? ? ? ? .addInterceptors(new HttpSessionHandshakeInterceptor()).withSockJS();

? ? }


public class CustomSpringConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware {

? ? private static volatile BeanFactory context;

? ? @Override

? ? public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {

? ? ? ? return context.getBean(clazz);

? ? }

? ? @Override

? ? public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

? ? ? ? CustomSpringConfigurator.context = applicationContext;

? ? }

? ? @Override

? ? public void modifyHandshake(ServerEndpointConfig sec,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? HandshakeRequest request, HandshakeResponse response) {

? ? ? ? super.modifyHandshake(sec,request,response);

? ? ? ? HttpSession httpSession=(HttpSession) request.getHttpSession();

? ? ? ? if(httpSession!=null){

? ? ? ? ? ? sec.getUserProperties().put(HttpSession.class.getName(),httpSession);

? ? ? ? }

? ? }

}

@SpringBootApplication

@EnableCaching

@ComponentScan("com")

@EnableWebSocket

public class Application extends SpringBootServletInitializer {

static final Logger logger = LoggerFactory.getLogger(Application.class);

? ? @Override

? ? protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {

? ? ? ? return application.sources(Application.class);

? ? }

需要注意的是: @EnableWebSocket? 一定要加在啟動(dòng)類上,不然springboot無(wú)法對(duì)其掃描進(jìn)行管理;

@SeverEndpoint --將目標(biāo)類定義成一個(gè)websocket服務(wù)端,注解對(duì)應(yīng)的值將用于監(jiān)聽用戶連接的終端訪問(wèn)地址,客戶端可以通過(guò)URL來(lái)連接到websocket服務(wù)端。

四、設(shè)計(jì)思路:用map<房間id, 用戶set>來(lái)保存房間對(duì)應(yīng)的用戶連接列表,當(dāng)有用戶進(jìn)入一個(gè)房間的時(shí)候,就會(huì)先檢測(cè)房間是否存在,如果不存在那就新建一個(gè)空的用戶set,再加入本身到這個(gè)set中,確保不同房間號(hào)里的用戶session不串通!

/**

* Create by wushuyu

* on 2020/4/30 13:24

*

*/

@ServerEndpoint(value = "/websocket/{roomName}", configurator = CustomSpringConfigurator.class)

@Component

public class WebSocketRoom {

? ? //連接超時(shí)--一天

? ? private static final long MAX_TIME_OUT = 24*60*60*1000;

? ? // key為房間號(hào),value為該房間號(hào)的用戶session

? ? private static final Map<String, Set<Session>> rooms = new ConcurrentHashMap<>();

? ? //將用戶的信息存儲(chǔ)在一個(gè)map集合里

? ? private static final Map<String, Object> users = new ConcurrentHashMap<>();

/**

*{roomName} 使用通用跳轉(zhuǎn),實(shí)現(xiàn)動(dòng)態(tài)獲取房間號(hào)和用戶信息? 格式:roomId|xx|xx

*/

? ? @OnOpen?

? ? public void connect(@PathParam("roomName") String roomName, Session session) {

? ? ? ? String roomId = roomName.split("[|]")[0];

? ? ? ? String nickname = roomName.split("[|]")[1];

? ? ? ? String loginId = roomName.split("[|]")[2];

? ? ? ? //設(shè)置連接超時(shí)時(shí)間

? ? ? ? ? ? session.setMaxIdleTimeout(MAX_TIME_OUT);

? ? ? ? try {


? ? ? ? ? //可實(shí)現(xiàn)業(yè)務(wù)邏輯

? ? ? ? ? ? }

? ? ? ? ? ? // 將session按照房間名來(lái)存儲(chǔ),將各個(gè)房間的用戶隔離

? ? ? ? ? ? if (!rooms.containsKey(roomId)) {

? ? ? ? ? ? ? ? // 創(chuàng)建房間不存在時(shí),創(chuàng)建房間

? ? ? ? ? ? ? ? Set<Session> room = new HashSet<>();

? ? ? ? ? ? ? ? // 添加用戶

? ? ? ? ? ? ? ? room.add(session);

? ? ? ? ? ? ? ? rooms.put(roomId, room);

? ? ? ? ? ? } else { // 房間已存在,直接添加用戶到相應(yīng)的房間? ? ? ? ? ? ?

? ? ? ? ? ? ? ? if (rooms.values().contains(session)) {//如果房間里有此session直接不做操作

? ? ? ? ? ? ? ? } else {//不存在則添加

? ? ? ? ? ? ? ? ? ? rooms.get(roomId).add(session);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? JSONObject jsonObject = new JSONObject();

? ? ? ? ? ? -----

? ? ? ? ? ? //根據(jù)自身業(yè)務(wù)情況實(shí)現(xiàn)業(yè)務(wù)

? ? ? ? ? ? -----

? ? ? ? ? ? users.put(session.getId(), jsonObject);

? ? ? ? ? ? //向在線的人發(fā)送當(dāng)前在線的人的列表? ? -------------可有可無(wú),看業(yè)務(wù)需求

? ? ? ? ? ? List<ChatMessage> userList = new LinkedList<>();

? ? ? ? ? ? rooms.get(roomId)

? ? ? ? ? ? ? ? ? ? .stream()

? ? ? ? ? ? ? ? ? ? .map(Session::getId)

? ? ? ? ? ? ? ? ? ? .forEach(s -> {

? ? ? ? ? ? ? ? ? ? ? ? ChatMessage chatMessage = new ChatMessage();

? ? ? ? ? ? ? ? ? ? ? ? chatMessage.setDate(new Date());

? ? ? ? ? ? ? ? ? ? ? ? chatMessage.setStatus(1);

? ? ? ? ? ? ? ? ? ? ? ? chatMessage.setChatContent(users.get(s));

? ? ? ? ? ? ? ? ? ? ? ? chatMessage.setMessage("");

? ? ? ? ? ? ? ? ? ? ? ? userList.add(chatMessage);

? ? ? ? ? ? ? ? ? ? });

//? ? ? ? session.getBasicRemote().sendText(JSON.toJSONString(userList));

? ? ? ? ? ? //向房間的所有人群發(fā)誰(shuí)上線了

? ? ? ? ? ? ChatMessage chatMessage = new ChatMessage();? ----將聊天信息封裝起來(lái)。

? ? ? ? ? ? chatMessage.setDate(new Date());

? ? ? ? ? ? chatMessage.setStatus(1);

? ? ? ? ? ? chatMessage.setChatContent(users.get(session.getId()));

? ? ? ? ? ? chatMessage.setMessage("");

? ? ? ? ? ? broadcast(roomId, JSON.toJSONString(chatMessage));

? ? ? ? ? ? broadcast(roomId, JSON.toJSONString(userList));

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

? ? @OnClose

? ? public void disConnect(@PathParam("roomName") String roomName, Session session) {

? ? ? ? String roomId = roomName.split("[|]")[0];

? ? ? ? String loginId = roomName.split("[|]")[2];

? ? ? ? try {

? ? ? ? ? ? rooms.get(roomId).remove(session);

? ? ? ? ? ? ChatMessage chatMessage = new ChatMessage();

? ? ? ? ? ? chatMessage.setDate(new Date());

? ? ? ? ? ? chatMessage.setUserName(user.getRealname());

? ? ? ? ? ? chatMessage.setStatus(0);

? ? ? ? ? ? chatMessage.setChatContent(users.get(session.getId()));

? ? ? ? ? ? chatMessage.setMessage("");

? ? ? ? ? ? users.remove(session.getId());

? ? ? ? ? ? //向在線的人發(fā)送當(dāng)前在線的人的列表? ----可有可無(wú),根據(jù)業(yè)務(wù)要求

? ? ? ? ? ? List<ChatMessage> userList = new LinkedList<>();

? ? ? ? ? ? rooms.get(roomId)

? ? ? ? ? ? ? ? ? ? .stream()

? ? ? ? ? ? ? ? ? ? .map(Session::getId)

? ? ? ? ? ? ? ? ? ? .forEach(s -> {

? ? ? ? ? ? ? ? ? ? ? ? ChatMessage chatMessage1 = new ChatMessage();

? ? ? ? ? ? ? ? ? ? ? ? chatMessage1.setDate(new Date());

? ? ? ? ? ? ? ? ? ? ? ? chatMessage1.setUserName(user.getRealname());

? ? ? ? ? ? ? ? ? ? ? ? chatMessage1.setStatus(1);

? ? ? ? ? ? ? ? ? ? ? ? chatMessage1.setChatContent(users.get(s));

? ? ? ? ? ? ? ? ? ? ? ? chatMessage1.setMessage("");

? ? ? ? ? ? ? ? ? ? ? ? userList.add(chatMessage1);

? ? ? ? ? ? ? ? ? ? });

? ? ? ? ? ? broadcast(roomId, JSON.toJSONString(chatMessage));

? ? ? ? ? ? broadcast(roomId, JSON.toJSONString(userList));

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

? ? @OnMessage

? ? public void receiveMsg( String msg, Session session) {

? ? ? ? try {

? ? ? ? ? ? ? ? ChatMessage chatMessage = new ChatMessage();

? ? ? ? ? ? ? ? chatMessage.setUserName(user.getRealname());

? ? ? ? ? ? ? ? chatMessage.setStatus(2);

? ? ? ? ? ? ? ? chatMessage.setChatContent(users.get(session.getId()));

? ? ? ? ? ? ? ? chatMessage.setMessage(msg);

? ? ? ? ? ? ? ? // 按房間群發(fā)消息

? ? ? ? ? ? ? ? broadcast(roomId, JSON.toJSONString(chatMessage));

? ? ? ? ? ? }

? ? ? ? } catch (IOException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

? ? // 按照房間名進(jìn)行群發(fā)消息

? ? private void broadcast(String roomId, String msg) {

? ? ? ? rooms.get(roomId).forEach(s -> {

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? s.getBasicRemote().sendText(msg);? -----此還有一個(gè)getAsyncRemote()?

? ? ? ? ? ? } catch (IOException e) {

? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? }

? ? ? ? });

? ? }

? ? @OnError

? ? public void onError(Throwable error) {

? ? ? ? error.printStackTrace();

? ? }

}

友情提示:此session是websocket里的session,并非httpsession;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容