day05-離線留言和離線文件

多用戶即時通訊系統05

4.編碼實現04(拓展)

拓展功能:

  1. 實現離線留言,如果某個用戶不在線 ,當登陸後,可以接收離線的消息
  2. 實現離線發文件,如果某個功能沒有在線,當登錄後,可以接收離線的文件

4.8功能實現-離線留言&離線文件

4.8.1思路分析

在服務端中使用ConcurrentHashMap集合來存放離線message(後期可以連資料庫)

ConcurrentHashMap存放形式為:

key = getterid   [接收者id]
value = ArrayList<Message>   [message對象]
  1. 當有客戶發送消息或者文件
  2. 如果接收者不在線就把message對象存放到服務端的db中(ConcurrentHashMap)
  3. 當身為接收者的用戶登錄後,就到服務端的db中查找,如果有getterid = userid,就取出ArrayList的一個或者多個Message對象,發送給接收者,然後在該db中刪除對應的數據

image-20220923232748402

4.8.2功能實現

只需要修改服務端

1.修改QQServer類
  1. 在該類中創建一個ConcurrentHashMap集合 offlineMessage,用來存放用戶發送的離線消息或者文件
/*
同樣創建一個集合,存放多個用戶發送的離線消息或者文件
使用 ConcurrentHashMap,可以處理並發的集合,沒有執行緒安全問題
存放的形式為
key = getter id   [接收者id]
value = ArrayList<Message>   在一個ArrayList集合中可以存放多條 message對象,實現多條留言或者文件
*/
static ConcurrentHashMap<String, ArrayList<Message>> offlineMessage = new ConcurrentHashMap<>();
  1. 在該類中新增一個方法,用來判斷當用戶登錄時,是否需要發送離線留言給該用戶
/**
 * @param getterId 接收離線數據的用戶userId
 * @param socket 用戶對應的通訊socket
 * 寫一個方法,當有用戶登錄成功時,獲取該用戶id名,
 * 在離線集合中搜索該id,如果有,就返回給該用戶,然後刪除該離線留言或者文件
 */
public void isOfflineMessage(String getterId,Socket socket){
    //搜索 map 集合中是否有該用戶的id
    if (offlineMessage.containsKey(getterId)) {
        //如果有,就說明該用戶有離線留言或數據要接收
        //在map中獲取該 message數據集合arrayList
        ArrayList<Message> arrayListMessage = offlineMessage.get(getterId);
        //遍歷arrayList集合將一個或者多個message數據發送給該用戶
        for (Message message:arrayListMessage) {
            try {
                //獲得輸出對象
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                oos.writeObject(message);//發送數據
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        offlineMessage.remove(getterId);//遍歷完則在資料庫中刪除該數據集合
    }
}
  1. QQServer構造方法中的while循環中,在用戶驗證通過的if分支語句 的最後面調用isOfflineMessage方法
//調用方法,查看是否有離線數據
isOfflineMessage(u.getUserId(), socket);

image-20220924185809884

2.修改ServerConnectClientThread類
  1. 在該類中新增方法processOfData(),用來判斷接收用戶是否在線,發送的數據應該怎樣處理的問題
public void processOfData(Message message) throws IOException {
        //業務三or五:客戶請求給某個用戶發送-->普通的聊天消息or文件

        //當用戶給某用戶發送 message時,如果接收用戶不在線(即通過getterId找不到該用戶的通訊執行緒)
        if (ManageClientThreads.getServerConnectClientThread(message.getGetter()) == null) {
            //就將離線 message放入這個用戶對應的arraylist集合中,再把該arrayList集合放到 map中

            /*
             * 這裡有個缺陷,因為map的key值不允許重複(重複的話,value會覆蓋為最新值),
             * 所以當有多個用戶分別給某一個用戶進行留言,該接收用戶只能接收到最近一個人的離線留言
             * 為了解決這個問題,這裡每次添加留言之前 都會將該接收用戶的所有留言複製一份,再在 最後添加新的留言
             * 然後把所有的留言再放回 map集合中,這樣效果等於追加新留言
             */
            // 先判斷map中該接收用戶的userId是否存在
            // 如果有,就說明已經有人給該用戶留言了,就獲取map集合對應 getter id的arraylist留言表
            // 然後在該用戶的留言表中"追加"留言即可
            if (offlineMessage.containsKey(message.getGetter())) {//如果接收用戶的留言表已經存在
                //獲取 接收留言的用戶的 留言集合
                ArrayList<Message> arrayListMessage = offlineMessage.get(message.getGetter());
                //在該集合中追加新的留言
                arrayListMessage.add(message);//增加新的留言
                //留言表再覆蓋進去,這樣相當於在留言表中追加留言
                offlineMessage.put(message.getGetter(), arrayListMessage);
            } else {//如果接收用戶的留言表不存在
                //如果map集合中沒有接收用戶的id,說明還沒人給這個用戶留言,要先創建一個留言表arrayListMessage
                ArrayList<Message> arrayListMessage = new ArrayList<>();
                //把資訊添加到新創建的留言表中
                arrayListMessage.add(message);
                //將新留言表添加到 map集合中
                offlineMessage.put(message.getGetter(), arrayListMessage);
            }
        } else {//接收用戶在線,就直接發送數據
            //根據接收的message對象的getter id 獲取到對應的執行緒,將message對象進行轉發
            //先拿到執行緒
            ServerConnectClientThread serverConnectClientThread =
                    ManageClientThreads.getServerConnectClientThread(message.getGetter());
            //獲取socket,將socket輸出流轉為對象流
            ObjectOutputStream oos =
                    new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
            //轉發
            oos.writeObject(message);
        }
    }
  1. 分別修改業務三和業務五的else if分支,因為已經在方法processOfData中封裝了業務,所以這裡只需要調用該方法即可

業務三分支:

//業務三:客戶請求和某用戶私聊
//調用方法,判斷用戶是否在線,在線就直接發送,不在線就將message對象先存在集合中
processOfData(message);

image-20220924200646646

業務五分支:

//業務五:客戶請求給某用戶發送文件
//調用方法,判斷用戶是否在線,在線就直接發送,不在線就將message對象先存在集合中
processOfData(message);

image-20220924200853847

3.運行測試

1.運行服務端

image-20220924201114366

2.運行客戶端–用戶100 分別給用戶200 和用戶300 發送多條離線留言

2.1用戶100 登錄:

image-20220924201907939

2.2用戶100 給用戶200 發送兩條離線留言:

image-20220924201933861 image-20220924201951698



2.3用戶100 給用戶300 發送兩條離線留言:

image-20220924202028005 image-20220924202049736

3.分別登錄 用戶200和用戶300 ,兩個用戶都接收到了用戶100 的留言:

image-20220924202257296 image-20220924202320910

4.服務端監聽情況:

image-20220924202454674

5.多個用戶對一個用戶進行留言測試:

用戶200不在線時,用戶100和300分別給200發送了多條留言:

image-20220924202720608

功能實現完畢