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ō)明

? 三陕赃、開(kāi)始我們的實(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)聽(tīng)用戶連接的終端訪問(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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蒲祈,隨后出現(xiàn)的幾起案子甘萧,更是在濱河造成了極大的恐慌,老刑警劉巖梆掸,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扬卷,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡酸钦,警方通過(guò)查閱死者的電腦和手機(jī)怪得,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)钝鸽,“玉大人汇恤,你說(shuō)我怎么就攤上這事“吻。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵基括,是天一觀的道長(zhǎng)颜懊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)风皿,這世上最難降的妖魔是什么河爹? 我笑而不...
    開(kāi)封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮桐款,結(jié)果婚禮上咸这,老公的妹妹穿的比我還像新娘。我一直安慰自己魔眨,他們只是感情好媳维,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著遏暴,像睡著了一般侄刽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上朋凉,一...
    開(kāi)封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天州丹,我揣著相機(jī)與錄音,去河邊找鬼。 笑死墓毒,一個(gè)胖子當(dāng)著我的面吹牛吓揪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播所计,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼磺芭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了醉箕?” 一聲冷哼從身側(cè)響起钾腺,我...
    開(kāi)封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎讥裤,沒(méi)想到半個(gè)月后放棒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡己英,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年间螟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片损肛。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡厢破,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出治拿,到底是詐尸還是另有隱情摩泪,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布劫谅,位于F島的核電站见坑,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏捏检。R本人自食惡果不足惜荞驴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贯城。 院中可真熱鬧熊楼,春花似錦、人聲如沸能犯。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)悲雳。三九已至评也,卻和暖如春纪铺,著一層夾襖步出監(jiān)牢的瞬間盲赊,已是汗流浹背杨凑。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人顿苇。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓峭咒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親纪岁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子凑队,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354