(блог) 9. Мгновенная разработка группового чата, записи чата и т. д.

Spring Boot

Публичный аккаунт: MarkerHub (подпишитесь, чтобы узнать больше о ресурсах проекта)

репозиторий кода блога:GitHub.com/маркер-хаб/о…

видео проекта в блоге:воооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооо


Каталог документации по разработке:

(блог) 1. Построение архитектуры проекта, инициализация домашней страницы

(блог) 2. Интегрируйте Redis и элегантную обработку исключений проекта и инкапсуляцию возвращаемых результатов.

(блог) 3. Используйте упорядоченную коллекцию Redis zset для реализации горячо обсуждаемой функции недели

(блог) 4. Настройте метку Freemaker, чтобы реализовать заполнение данными главной страницы блога.

(блог) 5. Заполнение классификации блога, логика регистрации входа

(блог) 6. Публикация в блогах коллекций, настройки пользовательского центра

(блог) 7. Асинхронное уведомление о сообщениях, настройка деталей

(блог) 8. Разработка поисковой системы для блогов, выбор фона

(блог) 9. Мгновенная разработка группового чата, записи чата и т. д.


Чтобы просмотреть vueblog проекта разделения клиентской и серверной части, нажмите здесь:Супер подробно! 4 часа на разработку блог-проекта SpringBoot+vue, разделяющего интерфейс и серверную часть! !


Разработка группового чата.

Сегодня мы доделаем функцию чата. В курсе мы говорили о примере springLayIM, который также интегрирует layim для реализации функции чата веб-версии. То, что мы написали в этот раз, не так уж и сложно, в основном мы изучаем процесс взаимодействия фронтенда и бэкенда.

Технический отбор:

  • Внешний интерфейс, веб-сокет

  • Backend t-io websocekt версия

Прежде всего, давайте сначала запустим интерфейс layim. layim — это платный модуль layui.Сначала мы помещаем пакет статических ресурсов layui в static.Что касается layim, так как это не полностью открытый продукт, я не буду приводить конкретный пакет.

официальный сайт лейм

Представьте себе layIM

Во-первых, представьте соответствующий подключаемый модуль модуля layim. Этот подключаемый модуль не является открытым исходным кодом. Для использования в Интернете требуются пожертвования. Если студентам необходимо использовать его в бизнесе, лучше сделать пожертвование.

Тогда по примеру, приведенному на официальном сайте, составим простейшее приветственное слово. Перед этим нам нужно сначала получить плагин.В принципе, мы должны получить его за пожертвование, но только для обучения, поэтому мы искали его непосредственно в Интернете.Соответствующий js выглядит следующим образом:

Затем смотрим официальную документацию:woohoo.lay UI.com/doc/modules…

Где мы поместим этот js? Я думаю, эффект будет таким. Чуть ниже главной страницы есть кнопка группового чата. После нажатия вы можете открыть окно группового чата для группового чата. Итак, чтобы общаться на всех страницах, я написал js в глобальном шаблоне.

  • templates/inc/layout.ftl
<script type="application/javascript">
    $(function () {
        layui.use('layim', function(layim){
            //先来个客服模式压压精
            layim.config({
                brief: true //是否简约模式(如果true则不显示主面板)
                ,min: true
            }).chat({
                name: '客服姐姐'
                ,type: 'friend'
                ,avatar: 'http://tp1.sinaimg.cn/5619439268/180/40030060651/1'
                ,id: -2
            });
            layim.setChatMin(); //收缩聊天面板
        });
    });
</script>

Эффект от этого js следующий, давайте проанализируем его: layim.config означает первоначальную настройку, кратко: true означает простой режим, есть только одно окно чата, .chat должен объявить и открыть окно чата, layim.setChatMin() ; означает уменьшение панели чата. 

Эффект после нажатия:

хорошо, выше это наше самое простое окно чата, которое можно отобразить, но пока нет функции, и мы не можем общаться друг с другом.Далее мы присвоим каждому окну идентификатор, а затем пообщаемся друг с другом.

встроенный веб-сокет t-io

После того, как мы представили layim выше, мы можем увидеть окно чата. Давайте посмотрим, какую функцию мы собираемся выполнить:

необходимость

  • Реализовать неизбирательную функцию группового чата

  • Пользователи, вошедшие на сайт, могут начать групповой чат

  • Пользователи, которые не вошли в систему, могут общаться анонимно

Функции

  • Групповой чат

  • запись истории сообщений

  • Чат анонимно

Далее мы будем выполнять его шаг за шагом.

Во-первых, давайте интегрируем t-io и websocket в серверную часть. Просмотрите содержание курсов t-io, которые вы прошли.

Сначала приветственное слово, используемое t-io:

(инициализация сервера)

(процесс связи между клиентом и сервером)

Затем интегрируйте логическую обработку сообщений, которая будет реализована после t-io:

Общее описание класса:

Изучив приведенный выше контент, мы узнали несколько ключевых классов, которые также являются несколькими ключевыми классами для инициализации и запуска службы t-io. Теперь приступим к т-ио.

В t-io код, который мы, наконец, вызываем для запуска службы: tioServer.start();

Потому что функция, которую мы хотим реализовать на этот раз, — это интегрированный веб-сокет t-io. И t-io уже интегрировала для нас набор кодов, что избавляет нас от таких шагов, как обновление протокола, поэтому нам не нужно вручную писать множество протоколов обновления и других кодов, подобных примерам в нашем курсе.

Вы можете сначала почувствовать пример tio-websocket-showcase, приведенный на официальном сайте:

В примере нам нужно регулярно встречать код под пакетом http, что аналогично использованию springmvc, а также t-io помог нам написать набор кода mvc. Как начался этот проект?

Много простоты: запустите основной метод ShowcaseWebsocketStarter напрямую, чтобы запустить проект, а затем посетите локальный хост, чтобы увидеть интерфейс.Прежде чем приступить к нашему домашнему проекту, вы можете сначала посмотреть, как реализована эта базовая функция чата, а затем мы вернемся и делать свои собственные проекты будет намного проще.

Давайте перейдем к нашему домашнему заданию: интеграции веб-сокета t-io. Поскольку есть набор интегрированных фреймворков, я сразу представляю здесь последнюю версию:Внутри репозитория MV.com/artifact/or…

<!-- https://mvnrepository.com/artifact/org.t-io/tio-websocket-server -->
<dependency>
    <groupId>org.t-io</groupId>
    <artifactId>tio-websocket-server</artifactId>
    <version>3.2.5.v20190101-RELEASE</version>
</dependency>

Затем в сочетании с только что увиденным примером проекта давайте сначала объясним еще несколько важных классов.

  • IWsMsgHandler (рукопожатие, класс обработки сообщений)

  • Это интерфейс для обработки сообщений, включая методы во время рукопожатия, после рукопожатия и обработки сообщений.

  • Будет вызываться в org.tio.websocket.server.WsServerAioHandler, который реализует ServerAioHandler. Есть три метода декодирования, кодирования и обработчика, с которыми мы знакомы.

  • WsServerStarter (класс запуска службы ws)

  • Для ws tio инкапсулирует множество связанных вещей, что упрощает настройку.Из его метода запуска видно, что на самом деле это знакомый tioServer.start.

  • ServerGroupContext (класс конфигурации)

  • Мы более знакомы с этим, класс конфигурации сервера может настроить время сердцебиения и т. д.

Это 3 класса, которые нам нужно очистить. С этими тремя классами мы можем запустить наш сервис и подключиться к ws.

Что ж, давайте напишем класс конфигурации. com.homework.im.config.ImServerAutoConfig

Во-первых, вам нужно указать порт, поэтому добавьте код, и вы можете добавить конфигурацию в yml самостоятельно.

@Value("${im.server.port}")
private Integer imPort;

  • application.yml
im:
  server:
    ip: 127.0.0.1
    port: 9326

Затем смотрим, что нужно настроить:

  • Первый шаг — инициализировать IWsMsgHandler и ServerGroupContext, затем настроить его на WsServerStarter и вызвать метод запуска для запуска службы.

  • Второй шаг, так как существует много типов сообщений (отправка сообщений, сообщения сердцебиения, добавление друзей и т. д.), нам также необходимо инициализировать контейнер класса обработки Карта, соответствующая типу сообщения, чтобы мы могли напрямую найти соответствующий тип в соответствии с типом при обработке сообщения.Класс обработки сообщения вызывает на линии.

Что ж, после того, как у нас есть идея, давайте ее реализовывать.

  • com.example.config.ImServerAutoConfig
@Slf4j
@Data
@Configuration
@Order(value = Integer.MAX_VALUE)
public class ImServerAutoConfig {
    @Value("${im.server.port}")
    private Integer imPort;
    @Bean
    public ImServerStarter imServerStarter() {
        try {
            ImServerStarter imServerStarter = new ImServerStarter(imPort);
            imServerStarter.start();
            //初始化消息处理器工程
            MsgHandlerFactory.init();
            log.info("---------> im server started !");
            return imServerStarter;
        } catch (IOException e) {
            log.error("im server 启动失败~~", e);
        }
        return null;
    }
}

Давайте сначала взглянем на приведенный выше код. Я сделал два шага. Во-первых, я определил собственный класс ImServerStarter и передал порт. Логика здесь, вероятно, заключается в том, чтобы инициализировать IWsMsgHandler, ServerGroupContext, а затем настроить его на WsServerStarter и так далее. Затем start() фактически вызывает tio после завершения инициализации.wsServerStarter.Начало(). Таким образом, мы завершили только что упомянутый первый шаг. Давайте посмотрим на конкретный код пользовательского класса ImServerStarter:

@Slf4j
public class ImServerStarter {
    private ImWsMsgHandler imWsMsgHandler;
    private WsServerStarter wsServerStarter ;
    private ServerGroupContext serverGroupContext;
    public ImServerStarter(int imPort) throws IOException {
        imWsMsgHandler = new ImWsMsgHandler();
        wsServerStarter = new WsServerStarter(imPort, imWsMsgHandler);
        serverGroupContext = wsServerStarter.getServerGroupContext();
        serverGroupContext.setHeartbeatTimeout(1000 * 60);
    }
    public void start() throws IOException {
        this.wsServerStarter.start();
    }
}

Это кажется очень простым, я могу настроить его несколько раз и написать, ха-ха. Если вы не знакомы с использованием tio, вернитесь и просмотрите содержание нашего курса. Использование tio не сложно. Есть только несколько основных классов, и нижний слой был инкапсулирован для нас, так что это довольно просто использовать. Затем посмотрите на второй шаг:

//初始化消息处理器工程
MsgHandlerFactory.init()

Вот процессор сообщений инициализации.Мы сказали, что может быть несколько типов сообщений, соответствующих разным процессорам, поэтому здесь мы сначала инициализируем их, помещаем в статическую карту и вызываем это напрямую, если вам нужен процессор в будущем. , Фабричный метод можно получить из контейнера карты.

  • com.example.im.handler.MsgHandlerFactory
/**
 * 1、消息处理器初始化工程
 * 2、根据类型获取消息处理器
 */
public class MsgHandlerFactory {
    private static boolean isInit = false;
    private static Map<String, MsgHandler> handlerMap = new HashMap<>();
    /**
     * 得预先初始化消息处理器
     */
    public static void init(){
        if(isInit){ return; }
        handlerMap.put(Constant.IM_MESS_TYPE_CHAT, new ChatMsgHandler());
        handlerMap.put(Constant.IM_MESS_TYPE_PING, new PingMsgHandler());
        isInit = true;
    }
    public static MsgHandler getMsgHandler(String type) {
        return handlerMap.get(type);
    }
}

Из приведенного выше видно, что существует несколько интерфейсов MsgHandler, два класса реализации ChatMsgHandler, PingMsgHandler, вызов getMsgHandler (тип String) может вернуть конкретную реализацию. Интерфейс MsgHandler имеет обработчик метода, поэтому все классы реализации могут реализовать этот метод (то есть логику обработки сообщений). О логике обработки поговорим позже.После вышеперечисленных шагов мы уже можем инициализировать службу запуска tio, а так же у нас есть обработчик сообщений.Написанная нами задача - интегрировать front-end ws и back-end, а затем обработать соответствующие сообщения (большинство последних разработаны вокруг ImWsMsgHandler).

Реализовать совместную отладку интерфейса и сервера

1. Установите соединение ws между передним и задним концами

Серверная часть использует версию tio для ws, а внешний интерфейс также использует ws для установления соединений. Оглядываясь назад на содержание курса, в котором мы говорили о веб-сокетах, установить ws-соединение на внешнем интерфейсе очень просто:

var
 socket 
=
 
new
 
WebSocket
(
'ws://localhost:9326'
);

Вот и все, тогда у сокета есть несколько методов обратного вызова, а именно socket.onopen, socket.onmessage, socket.onclose и т. д., которые также являются методами, которые мы в основном используем. Мы написали простой пример layim, прежде чем он сможет отображать окно чата.Мы устанавливаем ws-соединение после того, как layim.config инициализирован и настроен. Поскольку логическая обработка, такая как сердцебиение, отключение и повторное подключение, также разработана в будущем, мне нужно скорректировать структуру js, чтобы она больше соответствовала нашему мышлению о Java.

Сначала создаю и импортирую файл js im.js, а потом создаю новый в jsтио класс, тио.ws, давайте сначала последуем этому утверждению, я не знаю, правильно ли оно. = {} означает, что это определяет объект.

if (typeof(tio) == "undefined") {
    tio = {};
}
tio.ws = {};

Затем мы пишем метод в tio.ws, и мы можем создать новый, где нам нужно будет использовать его позже.tio.ws, а затем вызовите соответствующий метод. Соответствует ли это объектно-ориентированному мышлению? Ха-ха

//这个相当于构造函数吧
tio.ws = function ($, layim) {
}

Здесь у нас есть несколько методов

  • Установите метод подключения, прослушивая прием, закрытие, исключение и т. д. сообщения ws.

  • Сердцебиение, отключение и повторное подключение

  • отправлять сообщения

  • Инициализировать данные окна чата (например, заголовок окна, аватар и т. д., записи автономного чата)

Давайте объясним их один за другим. Во-первых, давайте посмотрим на установление соединения:

  • static/js/im.js

Фактически, логика вокруг нескольких методов ws. layim.getMessage — это интерфейс layim, этот интерфейс позволяет нам отображать диалоговую информацию json в нижнем окне.

2. Сердцебиение и отключение

Каждый раз, когда мы отправляем сообщение, мы будем записывать время последнего отправленного сообщения.Когда оно используется для сердцебиения, если сообщение не отправляется слишком долго, сердцебиение будет указывать на то, что оно живо, так что соединение ws будет не быть уничтоженным сервером.

Когда сервер выходит из строя, внешний интерфейс всегда будет пытаться автоматически переподключиться и будет вызывать onclose, поэтому нам нужно переподключиться только в методе onclose im.

Тогда посмотрите на логику пинга

По сути, он запускает таймер, а затем автоматически отправляет пакет сердцебиения, если сообщение не отправляется в течение длительного времени.Обратите внимание, что указанный тип сообщения сердцебиения — pingMessage, чтобы сервер мог получить соответствующий процессор для обработки.

Когда происходит исключение ws, нам нужно удалить этот таймер, а затем запустить пульсацию при повторном подключении.

3. Отправить сообщение

Тогда давайте посмотрим, как отправить сообщение

this.sendChatMessage = function(res) {
    //监听到上述消息后,就可以轻松地发送socket了
    this.socket.send(JSON.stringify({
        type: 'chatMessage' //随便定义,用于在服务端区分消息类型
        ,data: res
    }));
}

Как видите, на самом деле используется метод socket.send, обратите внимание на тип сообщения chatMessage. Внутри res находится сообщение, которое нужно отправить. Когда это срабатывает? Это должно включать интерфейс layim. Давайте подождем и посмотрим.

4. Завершите инициализацию всего внешнего процесса.

Выше мы определили множество методов в объекте tio.ws, устанавливая соединения, тактовые импульсы, отправляя сообщения и так далее. Итак, давайте использовать этот объект сейчас.

  • static/js/index.js
layui.use('layim', function (layim) {
    var $ = layui.jquery;
    //初始化layim
    layim.config({
        brief: true //是否简约模式(如果true则不显示主面板)
        ,voice: false
        ,members: {
            url: '/chat/getMembers'
        },
        chatLog: layui.cache.dir + 'css/modules/layim/html/chatlog.html'
});
    //建立ws链接,监听消息
    var tiows = new tio.ws($, layim);
    tiows.connect();
    //初始化群聊离线信息
    tiows.initHistroyMess();
    //打开群聊窗口并初始化个人信息
    tiows.openChatWindow();
    //发送消息
    layim.on('sendMessage', function(res){
        tiows.sendChatMessage(res);
    });
});

В приведенном выше коде мы инициализируем layim, устанавливаем ws-соединение, инициализируем сообщения группового чата, личные сообщения, затем открываем окно группового чата и, наконец, отслеживаем метод обратного вызова для отправки сообщения.Здесь запускается время для вызова отправки сообщения.

5. Инициализация данных

Мы также назвали соответствующий интерфейс для инициализации данных выше, который добавлен здесь. У layim есть определенные требования к формату. Вы можете посмотреть документацию:

Включая личную информацию, информацию о временном окне и т. д.

this.initChatData = function () {
    $.ajax({
        url: '/chat/getMineAndGroupData',
        async: false,
        success: function (data) {
            mine = data.data.mine;
            group = data.data.group;
        }
    });
}

Здесь задействованы два внутренних интерфейса:

  • Получить информацию о пользователе и информацию о групповом чате getMineAndGroupData

  • Получить список участников группы getMembers

Нам нужно установить формат, описанный в официальной документации, чтобы возвращать соответствующие данные json, поэтому я сделал некоторые классы инкапсуляции данных (VO), такие как ImUser и т. д.

  • com.example.controller.ChatController
@RestController
@RequestMapping("/chat")
public class ChatController extends BaseController {
    @Autowired
    ChatService chatService;
    @GetMapping("/getMineAndGroupData")
    public Result getMineAndGroupData(HttpServletRequest request) {
        //获取用户信息
        ImUser user = chatService.getCurrentImUser();
        //默认群
        Map<String, Object> group = new HashMap<>();
        group.put("name", "社区群聊");
        group.put("type", "group");
        group.put("avatar", "http://tp1.sinaimg.cn/5619439268/180/40030060651/1");
        group.put("id", Constant.IM_DEFAULT_GROUP_ID);
        group.put("members", 0);
        return Result.succ(MapUtil.builder()
                .put("mine", user)
                .put("group", group)
                .map());
    }
}

Пользователь ImUser = chatService.getCurrentImUser(); здесь нужно получить текущую информацию о пользователе, которая делится на два случая: вошел в систему и не вошел в систему, поэтому я сделал различие, анонимный пользователь дал случайный идентификатор, а затем сохранил это в сеансе. Таким образом, анонимный сеанс останется идентификатором, пока окно не будет закрыто.

  • com.example.service.impl.ChatServiceImpl
@Override
public ImUser getCurrentImUser() {
    AccountProfile profile = (AccountProfile)SecurityUtils.getSubject().getPrincipal();
    ImUser user = new ImUser();
    if(profile != null) {
        user.setId(profile.getId());
        user.setAvatar(profile.getAvatar());
        user.setUsername(profile.getUsername());
        user.setMine(true);
        user.setStatus(ImUser.ONLINE_STATUS);
    } else {
        user.setAvatar("http://tp1.sinaimg.cn/5619439268/180/40030060651/1");
        // 匿名用户处理
        Long imUserId = (Long) SecurityUtils.getSubject().getSession().getAttribute("imUserId");
        user.setId(imUserId != null ? imUserId : RandomUtil.randomLong());
        SecurityUtils.getSubject().getSession().setAttribute("imUserId", user.getId());
        user.setSign("never give up!");
        user.setUsername("匿名用户");
        user.setStatus(ImUser.ONLINE_STATUS);
    }
    return user;
}

Затем есть интерфейс для получения онлайн-пользователей, который в основном представляет собой метод:chatService.findAllOnlineMembers(), после того как пользователь пожмет руку для завершения обновления протокола и выйдет в сеть, мы сохраним информацию о текущем пользователе в redis, так что этот метод можно получить из redis, о чем мы поговорим позже. Выше описан процесс совместной отладки интерфейса и сервера. Запускаем сервер, открываем домашнюю страницу, видно окно чата, F12 видим, что ссылка ws установилась. После отправки сообщения мы можем получить его в методе com.homework.im.server.ImWsMsgHandler#onText.

Далее мы расскажем о приеме сообщений и их обработке.

Серверная обработка сообщений

При инициализации сервиса tio мы сказали, что обработка сообщений, рукопожатие и т.д. будут осуществляться вокруг класса com.example.im.server.ImWsMsgHandler. Здесь есть несколько методов, на которые нам нужно обратить внимание:

  • рукопожатие перед рукопожатием

  • onAfterHandshaked после рукопожатия

  • Обработка символьных сообщений onText

Что нам нужно сделать сейчас, так это функцию группового чата, давайте разберемся с требованиями:

  • После того, как пользователь успешно авторизуется, мы привязываем идентификатор пользователя к каналу tio, а затем напоминаем всем пользователям, что пользователь находится в сети.

  • Когда пользователь отправляет сообщение, все пользователи могут его получить

Что ж, давайте посмотрим, функция привязки id пользователя к tio может напомнить всем пользователям о том, что они должны пожать друг другу руки до или после рукопожатия.

Получив отправленное пользователем сообщение, мы найдем соответствующий обработчик, а затем отправим его всем пользователям после обработки.

Окей, сделано.

Давайте посмотрим на код:

  • перед рукопожатием

  • После рукопожатия здесь все еще есть проблема, системное сообщение не может отображаться в окне чата, что немного глючит. . . .

  • обработка сообщений

О типах сообщений:

Отправка сообщения — это chatMessage.

Находим процессор:

  • com.example.im.handler.impl.ChatMsgHandler
@Slf4j
@Component
public class ChatMsgHandler implements MsgHandler {
    @Override
    public void handler(String data, WsRequest wsRequest, ChannelContext channelContext) {
        ChatInMess chatMess = JSONUtil.toBean(data, ChatInMess.class);
        log.info("--------------> {}", chatMess.toString());
        ImUser mine = chatMess.getMine();
        ImTo to = chatMess.getTo();
        ImMess responseMess = new ImMess();
        responseMess.setContent(mine.getContent());
        responseMess.setAvatar(mine.getAvatar());
        responseMess.setMine(false);//是否我自己发的信息(自己不需要发送给自己)
        responseMess.setUsername(mine.getUsername());
        responseMess.setFromid(mine.getId());
        responseMess.setTimestamp(new Date());
        responseMess.setType(to.getType());
        responseMess.setId(Constant.IM_DEFAULT_GROUP_ID);//群组的id
        ChatOutMess chatOutMess = new ChatOutMess(Constant.IM_MESS_TYPE_CHAT, responseMess);
        String responseData = JSONUtil.toJsonStr(chatOutMess);
        log.info("群发消息 =========> {}", responseData);
        //用tio-websocket,服务器发送到客户端的Packet都是WsResponse
        WsResponse wsResponse = WsResponse.fromText(responseData, "utf-8");
        ChannelContextFilter filter = new ChannelContextFilterImpl();
        ((ChannelContextFilterImpl) filter).setCurrentContext(channelContext);
        //群发
        Tio.sendToGroup(channelContext.groupContext, Constant.IM_GROUP_NAME, wsResponse, filter);
        //保存群聊信息
        ChatService chatService = (ChatService) SpringUtil.getBean("chatService");
        chatService.setGroupHistoryMsg(responseMess);
    }
}

На самом деле, это довольно просто, верно~ Это не более чем лоскутное одеяло данных. Следует отметить, что здесь есть фильтр каналов, а это значит, что сообщения, отправленные мной, не будут отправляться мне группами, а будут отображаться непосредственно интерфейсом, поэтому нам нужно написать фильтр:

  • com.example.im.handler.filter.ChannelContextFilterImpl
/**
 * 通道过滤器
 */
@Data
public class ChannelContextFilterImpl implements ChannelContextFilter {
    private ChannelContext currentContext;
    /**
     * 过滤掉自己,不需要发送给自己
     * @param channelContext
     * @return
     */
    @Override
    public boolean filter(ChannelContext channelContext) {
        if(currentContext.userid.equals(channelContext.userid)) {
            return false;
        }
        return true;
    }
}

Таким образом, когда идентификатор пользователя канала совпадает с идентификатором пользователя канала для отправки, возвращается false, что указывает на пропуск.

  • Пользователь вышел из системы

При выходе пользователя нам нужно закрыть канал, чтобы группа не отправляла его сюда, и в то же время мы могли подсчитывать количество людей онлайн в режиме реального времени. Код очень прост, просто вызовите Tio.remove напрямую:

@Override
public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
    Tio.remove(channelContext, channelContext.userid + " - 退出群聊了~");
    return null;
}

Подводя итог, мы использовали несколько интерфейсов tio.

  • Интерфейс групповой отправки Tio.sendToGroup

  • привязать пользовательский интерфейсTio.bindUser

    • Пользователь выходит *Tio.remove

По сути, мы используем вышеуказанные API только для группового чата, и это несложно. Давайте проверим это. Из-за проблемы с идентификатором пользователя здесь все еще есть небольшая путаница, но это не имеет значения, нам просто нужно сначала протестировать функцию отправки и получения групповой отправки сообщений.

После запуска проекта откройте ссылку, чтобы посетить чат.

Получить участников группы и историю чата

В итерации предыдущей версии мы завершили базовое общение между членами группы. В этой версии мы завершим его, получив участников группового чата, информацию о группе в автономном режиме и т. д.

1. Получите участников группы

Сбор информации о членах группы заключается в наблюдении за онлайн- и офлайн-пользователями, чтобы определить, находятся ли члены в сети.

  • В сети: сохраните информацию об участнике в Redis

  • offline: удалить информацию об участнике из кеша

Давайте взглянем на структуру данных, которую должен вернуть layim, чтобы получить членов группы.

Из вышеизложенного мы знаем, что на самом деле это просто добавление основной информации о членах группы в список.На самом деле в интерфейсе нужны только три элемента: id, имя пользователя и аватар, остальные не требуются.

Зная структуру, давайте подумаем, какую структуру должен использовать этот кеш? Очевидно, что это список, вы можете использовать list, set, чтобы не повторяться, лучше использовать set. То, что будет повторяться, когда три элемента идентификатора, имени пользователя и аватара абсолютно одинаковы, считается одним и тем же элементом и не повторяется. Если пользователь изменит аватар или имя пользователя, это будет повторяться, поэтому мы не можем допустить, чтобы эти факторы повлияли на список, только идентификатор пользователя не будет изменен, поэтому в этом списке мы помещаем только идентификатор. Но если есть только id, то где взять имя пользователя и аватарку?Мы можем использовать хеш-структуру для сохранения этой базовой информации о пользователе.

Итак, чтобы обобщить наш анализ выше, для завершения этой функции нам нужно использовать две структуры redis.

  • set

  • hash

Далее давайте напишем код для завершения функции.

Во-первых, давайте напишем интерфейс для получения членов группы:

/**
 * 应该从通道里面获取所有用户信息
 * @return
 */
@ResponseBody
@GetMapping("/getMembers")
public Result getMembers() {
    Set<Object> members = chatService.findAllOnlineMembers();
    log.info("获取群成员---------->" + JSONUtil.toJsonStr(members));
    return Result.ok(MapUtil.of("list", members));
}

  • Класс реализации: com.homework.im.service.impl.ChatServiceImpl#findAllOnlineMembers
@Override
public Set<Object> findAllOnlineMembers() {
    Set<Object> ids = redisUtil.sGet(Constant.ONLINE_MEMBERS_KEY);
    Set<Object> results = new HashSet<>();
    if(ids == null) return results;
    ids.forEach((id) -> {
        Map<Object, Object> map = redisUtil.hmget((String) id);
        results.add(map);
    });
    return results;
}

В приведенных выше шагах мы сначала получаем идентификаторы из заданного списка, а затем циклически получаем информацию о пользователе и возвращаем ее в список. Когда была сохранена информация? Как мы сказали выше, она предназначена для наблюдения за пользователем онлайн и офлайн. Давайте посмотрим:

  • онлайн

  • не в сети

  • основная логика

Выше мы использовали кеш для завершения этой функции.В нашей разработке мы можем использовать кеш Redis, чтобы помочь нам завершить разработку и повысить скорость доступа к веб-сайту.

2. Получить записи чата

Далее, давайте получим историю группового чата. Точно так же мы должны сначала понять дизайн интерфейса layim:

Взгляните на html страницы в каталоге css/modules/layim/html/chatlog.html, где js имеет описание формата json.

Предпочтительная конфигурация:

Далее анализируем:

Во-первых, нам нужно сохранить историю чата, которую также можно записать с помощью нашего кэша Redis. Можно указать срок годности, действует только 1 день и т.д. Здесь вы также можете использовать список списка для сохранения записей чата. Когда сохранять, на самом деле формат исторического сообщения совпадает с форматом отправленного сообщения, поэтому мы можем сохранить сообщение в кэш при отправке сообщения, когда мы открываем окно чата, читаем сообщение из cache, а затем будет отображаться в цикле в окне.

Сначала мы пишем два интерфейса для получения и сохранения сообщений:

  • Получить только самые последние записи подсчета

  • com.example.service.impl.ChatServiceImpl#getGroupHistoryMsg

  • Когда звонить: открыть интерфейс для получения исторических сообщений.

@Override
public List<Object> getGroupHistoryMsg(int count) {
    long length = redisUtil.lGetListSize(Constant.GROUP_HISTROY_MSG_KEY);
    return redisUtil.lGet(Constant.GROUP_HISTROY_MSG_KEY, length - count < 0 ? 0 : length - count, length);
}

  • Сохранение сообщений в течение 24 часов (возможен гибкий дизайн)

  • com.example.service.impl.ChatServiceImpl#setGroupHistoryMsg

  • Когда звонить: при отправке сообщения

@Override
public boolean setGroupHistoryMsg(ImMess imMess) {
    return redisUtil.lSet(Constant.GROUP_HISTROY_MSG_KEY, imMess, 24 * 60 * 60);
}

Я не буду публиковать конкретный код js, студенты могут сравнить изменения кода в процессе сборки.

Эффект:

резюме домашнего задания

хорошо, наконец наша курсовая работа подходит к концу, и студенты усердно работали. Мы надеемся, что вы сможете закрепить те знания, которые мы получили в ходе этой курсовой работы.

Увидимся в следующий раз~

-------------------------------------------------- ---------(над)-------------------------------------- --------------------

Сценарист: Лу Имин

Публичный номер: MarkerHub