自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

CTO要我搞個(gè)微信IM系統(tǒng),嚇出冷汗!

系統(tǒng) 開發(fā)工具
經(jīng)常聽到有人抱怨這知識學(xué)的,根本沒有忘的快呀?感覺很多資料,點(diǎn)收藏起來爽、看視頻時(shí)候嗨、讀文章當(dāng)時(shí)會(huì),只要過了那個(gè)勁,根本不記得這里面都講了啥。時(shí)間浪費(fèi)了,東西還沒學(xué)到手,這是為啥?

[[418874]]

圖片來自 包圖網(wǎng)

其實(shí)因?yàn)閷W(xué)習(xí)也分為上策、中策和下策:

  • 下策:眼睛看就行,坐著、窩著、躺著,都行,反正也不累,還能一邊回復(fù)下吹水的微信群。
  • 中策:看完的資料做筆記整理歸納,長期積累資料。
  • 上策:實(shí)踐、上手、應(yīng)用、調(diào)試、歸納、整理資料,總結(jié)經(jīng)驗(yàn)輸出文檔。

[[418875]]

綜上,下策學(xué)起來很快感覺自己好像會(huì)了不少,中策有點(diǎn)要?jiǎng)邮至藨胁幌雱?dòng),上策就很耗時(shí)耗力了要自己對每一個(gè)知識點(diǎn)都能事必躬親到親力親為。就這樣你在學(xué)習(xí)的時(shí)候不自覺的就選擇了下策,因此其實(shí)并沒有學(xué)到什么。

學(xué)習(xí)能把知識學(xué)到手,講究的是實(shí)踐,從小我就喜歡動(dòng)手,就以一個(gè)即時(shí)通信的項(xiàng)目為例,已經(jīng)基于不同技術(shù)方案實(shí)現(xiàn)了 5、6 次,僅為了實(shí)踐技術(shù)。

如上圖:

  • 有些是剛學(xué)完 Socket 和 Swing 的時(shí)候,想動(dòng)手試試這些技術(shù)能不能寫個(gè) QQ 出來。
  • 也有的是因?yàn)閷?shí)習(xí)培訓(xùn)需要完成的項(xiàng)目,不過在有了一些基礎(chǔ)后,一周時(shí)間就能寫完全部功能。
  • 雖然這些項(xiàng)目在現(xiàn)在看上去還是丑丑的界面,以及代碼邏輯可能也不是那么完善。但放在學(xué)習(xí)階段的每一次實(shí)現(xiàn)中,都能為自己帶來很多技術(shù)上的成長。

那么,這次 IM 實(shí)踐的機(jī)會(huì)給你,希望你能用的上!接下來我會(huì)給你介紹一個(gè) IM 的系統(tǒng)架構(gòu)、通信協(xié)議、單聊群聊、表情發(fā)送、UI 事件驅(qū)動(dòng)等各項(xiàng)內(nèi)容,以及提供全套的源碼讓你可以上手學(xué)習(xí)。

演示

在開始學(xué)習(xí)之前,先給大家演示下這套仿照 PC 端微信界面的 IM 系統(tǒng)運(yùn)行效果。

聊天頁面:

添加好友:

系統(tǒng)設(shè)計(jì)

在這套 IM 中,服務(wù)端采用 DDD 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)模式進(jìn)行搭建。將 Netty 的功能交給 SpringBoot 進(jìn)行啟??刂?,同時(shí)在服務(wù)端搭建控制臺(tái)可以非常方便的操作通信系統(tǒng),進(jìn)行用戶和通信管理。

在客戶端的建設(shè)上采用 UI 分離的方式進(jìn)行搭建,以保證業(yè)務(wù)代碼與 UI 展示分離,做到非常易于擴(kuò)展的控制。

另外在功能實(shí)現(xiàn)上包括;完美仿照微信桌面版客戶端、登錄、搜索添加好友、用戶通信、群組通信、表情發(fā)送等核心功能。如果有對于實(shí)際需要使用的功能,可以按照這套系統(tǒng)框架進(jìn)行擴(kuò)展。

如上圖:

  • UI 開發(fā):使用 JavaFx 與 Maven 搭建 UI 桌面工程,逐步講解登錄框體、聊天框體、對話框、好友欄等各項(xiàng) UI 展示及操作事件。從而在這一章節(jié)中讓 Java 程序員學(xué)會(huì)開發(fā)桌面版應(yīng)用。
  • 架構(gòu)設(shè)計(jì):在這一章節(jié)中我們會(huì)使用 DDD 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的四層模型結(jié)構(gòu)與 Netty 結(jié)合使用,架構(gòu)出合理的分層框架。同時(shí)還有相應(yīng)庫表功能的設(shè)計(jì)。相信這些內(nèi)容學(xué)習(xí)后,你一定也可以假設(shè)出更好的框架。
  • 功能實(shí)現(xiàn):這部分我們主要將通信中的各項(xiàng)功能逐步實(shí)現(xiàn),包括;登錄、添加好友、對話通知、消息發(fā)送、斷線重連等各項(xiàng)功能。最終完成整個(gè)項(xiàng)目的開發(fā),同時(shí)也可以讓你從實(shí)踐中學(xué)會(huì)技能。

UI 開發(fā)

①整體結(jié)構(gòu)定義、側(cè)邊欄

聊天窗體,相對于登陸窗體來說,聊天窗體的內(nèi)容會(huì)比較多,同時(shí)也會(huì)相對復(fù)雜一些。因此我們會(huì)分章節(jié)的逐步來實(shí)現(xiàn)這些窗體以及事件和接口功能。

在本篇文章中我們會(huì)主要講解聊天框體的搭建以及側(cè)邊欄 UI 開發(fā):

  • 首先是我們整個(gè)聊天主窗體的定義,是一塊空白面板,并去掉默認(rèn)的邊框按鈕(最小化、退出等)。
  • 之后是我們左側(cè)邊欄,我們稱之為條形 Bar,功能區(qū)域的實(shí)現(xiàn)。
  • 最后添加窗體事件,當(dāng)點(diǎn)擊按鈕時(shí)變換 內(nèi)容面板 中的填充信息。

②對話聊天框

對話框選中后的內(nèi)容區(qū)域展現(xiàn),也就是用戶之間信息發(fā)送和展現(xiàn)。從整體上看這是一個(gè)聯(lián)動(dòng)的過程,點(diǎn)擊左側(cè)的對話框用戶,右側(cè)就有相應(yīng)內(nèi)容的填充。

那么右側(cè)被填充對話列表 ListView 需要與每一個(gè)對話用戶關(guān)聯(lián),點(diǎn)擊聊天用戶的時(shí)候,是通過反復(fù)切換填充的過程:

  • 點(diǎn)擊左側(cè)的每一個(gè)對話框體,右側(cè)聊天框填充內(nèi)容即隨之變化。同時(shí)還有相應(yīng)的對話名稱也會(huì)也變化。
  • 對話框中左側(cè)展示好友發(fā)送的信息,右側(cè)展示個(gè)人發(fā)送的信息。同時(shí)消息內(nèi)容會(huì)隨著內(nèi)容的增多而增加高度和寬度。
  • 最下面是文本輸入框,在后面的實(shí)現(xiàn)里我們文本輸入框采用公用的方式進(jìn)行設(shè)計(jì),當(dāng)然你也可以設(shè)計(jì)為單獨(dú)的個(gè)人使用。

③好友欄

大家都經(jīng)常使用 PC 端的微信,可以知道在好友欄里是分了幾段內(nèi)容的,其中包含;新的朋友、公眾號、群組和最下面的好友。

如上圖:

  • 最上面的搜索框這部分內(nèi)容不變,和前面的一樣。我們目前使用的方式是 fxml 設(shè)計(jì),例如這部分是通用功能,可以抽取出來放到代碼中,設(shè)計(jì)成一個(gè)組件元素類。
  • 經(jīng)過我們的分析,在使用 JavaFx 組件開發(fā)為基礎(chǔ)下,這部分是一種嵌套 ListView,也就是最底層的面板是一個(gè) ListView,好友和群組有各是一個(gè) ListView,這樣處理后我們會(huì)很方便的進(jìn)行數(shù)據(jù)填充。
  • 另外這樣的結(jié)構(gòu)主要有利于在我們程序運(yùn)行過程中,如果你添加了好友,那么我們需要將好友信息刷新到好友欄中,而在數(shù)據(jù)填充的時(shí)候,為了更加便捷高效,所以我們設(shè)計(jì)了嵌套的 ListView。如果還不是特別理解,可以從后續(xù)的代碼中獲得答案。

④事件定義

在桌面版 UI 開發(fā)中,為了能使 UI 與業(yè)務(wù)邏輯隔離,需要在我們把 UI 打包后提供出操作界面的展示效果的接口以及界面操作事件抽象類。

那么可以按照下圖理解:

以上這些接口就是我們目前 UI 為外部提供的所有行為接口,這些接口的一個(gè)鏈路描述就是:打開窗口、搜索好友、添加好友、打開對話框、發(fā)送消息。

通信設(shè)計(jì)

①系統(tǒng)架構(gòu)

如下圖:

在前面我們說到更適合的架構(gòu),才是符合你當(dāng)下需要最好的架構(gòu)。那么怎么設(shè)計(jì)這樣架構(gòu)呢,基本就是要找到符合點(diǎn)的目標(biāo)。

我們之所以這樣設(shè)計(jì)是為什么,那么在這個(gè)系統(tǒng)里有如下幾點(diǎn):

  • 我們系統(tǒng)在服務(wù)端要有 web 頁面進(jìn)行管理通信用戶以及服務(wù)端的控制和監(jiān)控。
  • 數(shù)據(jù)庫的對象類,不要被外部污染,要有隔離性。比如說你的數(shù)據(jù)庫類暴漏給外部做展示類使用了,那么現(xiàn)在需要增加一個(gè)字段,而這個(gè)字段又不是你數(shù)據(jù)庫存在的屬性。那么這個(gè)時(shí)候就已經(jīng)把數(shù)據(jù)庫類污染了。
  • 因?yàn)槟壳拔覀兌际窃?Java 語言下實(shí)現(xiàn) Netty 通信,那么服務(wù)端與客戶端都會(huì)需要使用到通信過程中的協(xié)議定義和解析。那么我們需要抽離這一層對外提供 Jar 包。
  • 接口、業(yè)務(wù)處理、底層服務(wù)、通信交互,要有明確的區(qū)分和實(shí)現(xiàn),避免造成混亂難以維護(hù)。

結(jié)合我們上面這四點(diǎn)的目標(biāo),你頭腦中有什么模型結(jié)構(gòu)體現(xiàn)了呢?以及相應(yīng)的技術(shù)棧選擇上是否有計(jì)劃了?

接下來我們會(huì)介紹兩種架構(gòu)設(shè)計(jì)的模型,一種是你非常熟悉的 MVC,另外一種是你可能聽說過的 DDD 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)。

②通信協(xié)議

如下圖:

從圖稿上來看,我們在傳輸對象的時(shí)候需要在傳輸包中添加一個(gè) 幀標(biāo)識 以此來判斷當(dāng)前的業(yè)務(wù)對象是哪個(gè)對象,也就可以讓我們的業(yè)務(wù)更加清晰,避免使用大量的 if 語句判斷。

協(xié)議框架:

  1. agreement 
  2. └── src 
  3.     ├── main 
  4.     │   ├── java 
  5.     │   │   └── org.itstack.naive.chat 
  6.     │   │       ├── codec 
  7.     │   │       │    ├── ObjDecoder.java 
  8.     │   │       │    └── ObjEncoder.java 
  9.     │   │       ├── protocol 
  10.     │   │       │    ├── demo 
  11.     │   │       │    ├── Command.java 
  12.     │   │       │    └── Packet.java 
  13.     │   │       └── util 
  14.     │   │             └── SerializationUtil.java 
  15.     │   ├── resources     
  16.     │   │   └── application.yml 
  17.     │   └── webapp 
  18.     │       └── chat 
  19.     │       └── res 
  20.     │       └── index.html 
  21.     └── test 
  22.          └── java 
  23.              └── org.itstack.demo.test 
  24.                  └── ApiTest.java 

協(xié)議包:

  1. public abstract class Packet { 
  2.  
  3.     private final static Map<Byte, Class<? extends Packet>> packetType = new ConcurrentHashMap<>(); 
  4.  
  5.     static { 
  6.         packetType.put(Command.LoginRequest, LoginRequest.class); 
  7.         packetType.put(Command.LoginResponse, LoginResponse.class); 
  8.         packetType.put(Command.MsgRequest, MsgRequest.class); 
  9.         packetType.put(Command.MsgResponse, MsgResponse.class); 
  10.         packetType.put(Command.TalkNoticeRequest, TalkNoticeRequest.class); 
  11.         packetType.put(Command.TalkNoticeResponse, TalkNoticeResponse.class); 
  12.         packetType.put(Command.SearchFriendRequest, SearchFriendRequest.class); 
  13.         packetType.put(Command.SearchFriendResponse, SearchFriendResponse.class); 
  14.         packetType.put(Command.AddFriendRequest, AddFriendRequest.class); 
  15.         packetType.put(Command.AddFriendResponse, AddFriendResponse.class); 
  16.         packetType.put(Command.DelTalkRequest, DelTalkRequest.class); 
  17.         packetType.put(Command.MsgGroupRequest, MsgGroupRequest.class); 
  18.         packetType.put(Command.MsgGroupResponse, MsgGroupResponse.class); 
  19.         packetType.put(Command.ReconnectRequest, ReconnectRequest.class); 
  20.     } 
  21.  
  22.     public static Class<? extends Packet> get(Byte command) { 
  23.         return packetType.get(command); 
  24.     } 
  25.  
  26.     /** 
  27.      * 獲取協(xié)議指令 
  28.      * 
  29.      * @return 返回指令值 
  30.      */ 
  31.     public abstract Byte getCommand(); 
  32.  

③添加好友

如上圖:

  • 從上面的流程中可以看到,這里包含了兩部分內(nèi)容;(1) 搜索好友,(2) 添加好友。當(dāng)天就完成好友后,好友會(huì)出現(xiàn)到我們的好友欄中。
  • 并且這里面我們采用的是單方面同意加好友,也就是你添加一個(gè)好友的時(shí)候,對方也同樣有你的好友信息。
  • 如果你的業(yè)務(wù)中是需要添加好友并同意的,那么可以在發(fā)起好友添加的時(shí)候,添加一條狀態(tài)信息,請求加好友。對方同意后,兩個(gè)用戶才能成為好友并進(jìn)行通信。

添加好友,案例代碼:

  1. public class AddFriendHandler extends MyBizHandler<AddFriendRequest> { 
  2.  
  3.     public AddFriendHandler(UserService userService) { 
  4.         super(userService); 
  5.     } 
  6.  
  7.     @Override 
  8.     public void channelRead(Channel channel, AddFriendRequest msg) { 
  9.         // 1. 添加好友到數(shù)據(jù)庫中[A->B B->A] 
  10.         List<UserFriend> userFriendList = new ArrayList<>(); 
  11.         userFriendList.add(new UserFriend(msg.getUserId(), msg.getFriendId())); 
  12.         userFriendList.add(new UserFriend(msg.getFriendId(), msg.getUserId())); 
  13.         userService.addUserFriend(userFriendList); 
  14.         // 2. 推送好友添加完成 A 
  15.         UserInfo userInfo = userService.queryUserInfo(msg.getFriendId()); 
  16.         channel.writeAndFlush(new AddFriendResponse(userInfo.getUserId(), userInfo.getUserNickName(), userInfo.getUserHead())); 
  17.         // 3. 推送好友添加完成 B 
  18.         Channel friendChannel = SocketChannelUtil.getChannel(msg.getFriendId()); 
  19.         if (null == friendChannel) return
  20.         UserInfo friendInfo = userService.queryUserInfo(msg.getUserId()); 
  21.         friendChannel.writeAndFlush(new AddFriendResponse(friendInfo.getUserId(), friendInfo.getUserNickName(), friendInfo.getUserHead())); 
  22.     } 
  23.  

④消息應(yīng)答

如上圖:

  • 從整體的流程可以看到,在用戶發(fā)起好友、群組通信的時(shí)候,會(huì)觸發(fā)一個(gè)事件行為,接下來客戶端向服務(wù)端發(fā)送與好友的對話請求。
  • 服務(wù)端收到對話請求后,如果是好友對話,那么需要保存與好友的通信信息到對話框中。同時(shí)通知好友,我與你要通信了。你在自己的對話框列表中,把我加進(jìn)去。
  • 那么如果是群組通信,是可以不用這樣通知的,因?yàn)椴豢赡馨堰€沒有在線的所有群組用戶全部通知(人家還沒登錄呢),所以這部分只需要在用戶上線收到信息后,創(chuàng)建出對話框到列表中即可。可以仔細(xì)理解下,同時(shí)也可以想想其他實(shí)現(xiàn)的方式。

消息應(yīng)答,案例代碼:

  1. public class MsgHandler extends MyBizHandler<MsgRequest> { 
  2.  
  3.     public MsgHandler(UserService userService) { 
  4.         super(userService); 
  5.     } 
  6.  
  7.     @Override 
  8.     public void channelRead(Channel channel, MsgRequest msg) { 
  9.         logger.info("消息信息處理:{}", JSON.toJSONString(msg)); 
  10.         // 異步寫庫 
  11.         userService.asyncAppendChatRecord(new ChatRecordInfo(msg.getUserId(), msg.getFriendId(), msg.getMsgText(), msg.getMsgType(), msg.getMsgDate())); 
  12.         // 添加對話框[如果對方?jīng)]有你的對話框則添加] 
  13.         userService.addTalkBoxInfo(msg.getFriendId(), msg.getUserId(), Constants.TalkType.Friend.getCode()); 
  14.         // 獲取好友通信管道 
  15.         Channel friendChannel = SocketChannelUtil.getChannel(msg.getFriendId()); 
  16.         if (null == friendChannel) { 
  17.             logger.info("用戶id:{}未登錄!", msg.getFriendId()); 
  18.             return
  19.         } 
  20.         // 發(fā)送消息 
  21.         friendChannel.writeAndFlush(new MsgResponse(msg.getUserId(), msg.getMsgText(), msg.getMsgType(), msg.getMsgDate())); 
  22.     } 
  23.  

⑤斷線重連

如上圖:

  • 從上述流程中我們看到,當(dāng)網(wǎng)絡(luò)連接斷開以后,會(huì)像服務(wù)端發(fā)送重新鏈接的請求。那么在這個(gè)發(fā)起鏈接的過程,和系統(tǒng)的最開始鏈接有所區(qū)別。

斷線重連是需要將用戶的 ID 信息一同- - 發(fā)送給服務(wù)端,好讓服務(wù)端可以去更新用戶與通信管道 Channel 的綁定關(guān)系。

  • 同時(shí)還需要更新群組內(nèi)的重連信息,把用戶的重連加入群組映射中。此時(shí)就可以恢復(fù)用戶與好友和群組的通信功能。

消息應(yīng)答,案例代碼:

  1. // Channel 狀態(tài)定時(shí)巡檢;3 秒后每 5 秒執(zhí)行一次 
  2. scheduledExecutorService.scheduleAtFixedRate(() -> {while (!nettyClient.isActive()) {System.out.println("通信管道巡檢:通信管道狀態(tài)" + nettyClient.isActive()); 
  3.         try {System.out.println("通信管道巡檢:斷線重連 [Begin]"); 
  4.             Channel freshChannel = executorService.submit(nettyClient).get(); 
  5.             if (null == CacheUtil.userId) continue
  6.             freshChannel.writeAndFlush(new ReconnectRequest(CacheUtil.userId)); 
  7.         } catch (InterruptedException | ExecutionException e) {System.out.println("通信管道巡檢:斷線重連 [Error]");} 
  8.     } 
  9. }, 3, 5, TimeUnit.SECONDS); 

⑥集群通信

如上圖:

  • 跨服務(wù)之間案例采用 redis 的發(fā)布和訂閱進(jìn)行傳遞消息,如果你是大型服務(wù)可以使用 zookeeper。
  • 用戶 A 在發(fā)送消息給用戶 B 時(shí)候,需要傳遞 B 的 channeId,以用于服務(wù)端進(jìn)行查找 channeId 所屬是否自己的服務(wù)內(nèi)。
  • 單臺(tái)機(jī)器也可以啟動(dòng)多個(gè) Netty 服務(wù),程序內(nèi)會(huì)自動(dòng)尋找可用端口。

源碼下載

本項(xiàng)目是我使用 JavaFx、Netty4.x、SpringBoot、MySQL 等技術(shù)棧和偏向于 DDD 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)方式,搭建的仿桌面版微信實(shí)現(xiàn)通信核心功能。

這套 IM 代碼分為了三組模塊;UI、客戶端、服務(wù)端。之所以這樣拆分,是為了將 UI 展示與業(yè)務(wù)邏輯隔離,使用事件和接口進(jìn)行驅(qū)動(dòng),讓代碼層次更加干凈整潔易于擴(kuò)展和維護(hù)。

總結(jié)

如下圖:

此 IM 系統(tǒng)涉及到的技術(shù)棧內(nèi)容較多,Netty4.x、SpringBoot、Mybatis、MySQL、JavaFx、layui 等技術(shù)棧的使用,以及整個(gè)系統(tǒng)框架結(jié)構(gòu)采用 DDD 四層架構(gòu)+Socket 模塊的方式進(jìn)行搭建。

所有的 UI 都以前后端分離事件驅(qū)動(dòng)方式進(jìn)行設(shè)計(jì),在這個(gè)過程中只要你能堅(jiān)持學(xué)習(xí)下來,那么一定會(huì)收獲非常多的內(nèi)容。足夠吹牛啦!

任何一個(gè)新技術(shù)棧的學(xué)習(xí)過程都會(huì)包括這樣一條路線;運(yùn)行 HelloWorld、熟練使用 API、項(xiàng)目實(shí)踐以及最后的深度源碼挖掘。

那么在聽到這樣一個(gè)需求時(shí)候,Java 程序員肯定會(huì)想到一些列的技術(shù)知識點(diǎn)來填充我們項(xiàng)目中的各個(gè)模塊。

例如界面用 JavaFx、Swing 等,通信用 Socket 或者知道 Netty 框架、服務(wù)端控制用 MVC 模型加上 SpringBoot 等。

但是怎么將這些各個(gè)技術(shù)棧合理的架設(shè)出我們的系統(tǒng)確是學(xué)習(xí)、實(shí)踐、成長過程中最重要的部分。

作者:小傅哥

編輯:陶家龍

出處:轉(zhuǎn)載自公眾號 bugstack 蟲洞棧(ID:bugstack)

 

責(zé)任編輯:武曉燕 來源: bugstack 蟲洞棧
相關(guān)推薦

2021-02-20 08:06:37

CTO灰度系統(tǒng)

2022-12-09 08:59:46

ChatGPT編程數(shù)據(jù)

2019-06-05 19:03:03

微信消息延遲

2021-03-01 08:05:09

慢查詢SQL

2015-07-30 09:27:04

2019-03-12 15:41:09

Facebook微信馬化騰

2014-05-19 15:58:06

WOT 微信

2025-04-15 09:00:00

2021-06-04 17:57:04

微信微信圈子騰訊

2021-04-26 05:39:03

微信輸入法騰訊

2013-11-18 17:55:57

微信微信支付

2021-09-07 08:26:07

微信微信收費(fèi)騰訊

2019-11-01 09:36:58

微信支付支付寶

2018-05-23 09:11:42

微信Android開發(fā)面試

2013-08-08 10:13:25

微信

2017-10-12 13:22:51

微信飛信阿里

2021-04-16 11:27:16

Python表情微信

2018-03-22 04:20:40

支付寶微信移動(dòng)支付

2021-05-14 07:18:07

監(jiān)控微信聊天
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號