近期术浪,公司需要新增即時(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锦庸;