[Практический проект Spring] SpringBoot3 интегрирует перехватчик WebSocket+ для проверки входа в систему! От принципа к практике
[Практический проект Spring] SpringBoot3 интегрирует перехватчик WebSocket+ для проверки входа в систему! От принципа к практике

На базе видеочата также необходимо реализовать украшение, отключение проверки пульса и оптимизацию пропуска кадров. Бизнес-потребности на уровне предприятия, такие как автономное переподключение

1. Обзор WebSocket:

WebSocket — это сетевой протокол, основанный на протоколе TCP.,Он реализует браузер и серверЗенсуко Коммуникация,Поддержка отправки информации друг другу между клиентом и сервером. До появления WebSockets,Если данные сервера меняются,клиент Если хочешь знать,Его можно получить только с сервера посредством запланированного опроса.,Этот метод значительно увеличивает частичное давление,После наличия WebSocket,Если данные сервера меняются,Можно немедленно уведомить клиента,клиент Для обмена не нужно проводить опрос,Снижает нагрузку на сервер. В настоящее время все основные браузеры уже поддерживают протокол WebSocket. WebSocket использует ws и wss в качестве идентификаторов ресурсов. TSL Есть 4 основных события:

  • onopen срабатывает при создании соединения
  • onclose срабатывает, когда соединение разрывается
  • onmessage срабатывает при получении сообщения
  • onerror срабатывает при возникновении сбоя связи

Этапы реализации

Сначала введите зависимости
Язык кода:javascript
копировать
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>


        <dependency>
            <!-- websocket -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <!-- fastjson -->
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
Настройте перехватчики и настройте отчеты об ошибках
Язык кода:javascript
копировать
@Slf4j
@RestControllerAdvice
public class WebExceptionAdvice {
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<Result> handleRuntimeException(HttpServletRequest request, RuntimeException e) {
        log.error(e.toString(), e);
        Result result = Result.fail(e.getMessage());
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;//500
        if (e instanceof UnAuthorException) {
            //Это код состояния, устанавливаемый, когда перехватчик сообщает об ошибке.
            status = HttpStatus.UNAUTHORIZED;//401
        }
        ResponseEntity<Result> resultResponseEntity = new ResponseEntity<>(result, status);
        log.error(resultResponseEntity.toString());
        return resultResponseEntity;
    }
}
Это собственный тип, который я сделал, вы можете изменить его по своему усмотрению.
Язык кода:javascript
копировать
public class UnAuthorException extends RuntimeException {
    public UnAuthorException(String message) {
        super(message);
    }
}
Конфигурация перехватчика
Язык кода:javascript
копировать
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    //Добавляем перехватчик  InterceptorRegistry registry Регистратор-перехватчик  ignorePathPatterns исключает ненужные перехваченные пути
    // Пока это не имеет отношения к Авторизоваться, перехватывать не надо.  Функция перехватчика – только проверка статуса Авторизоваться.
    public void addInterceptors(InterceptorRegistry registry) {

            registry.addInterceptor(new LoginInterceptor())
                    .excludePathPatterns(
                    "/index/**",
                    "/user/wechat/login",
                    "/user/zfb/login",
                    //...установите это самостоятельно здесь Страницы, которые вы не хотите блокировать Остальные перехвачены

            ).order(1);
//        order — это последовательность настроек
//        Обновить перехватчик токенов
        registry.addInterceptor(new RefreshTokeninterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
    }
}
Реализация перехватчика
Язык кода:javascript
копировать
public class LoginInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            //1. Определяем, нужен ли перехват (есть ли пользователь в ThreadLocal)
            if (UserHolder.getUser() == null&&ListenerHolder.getListener()==null) {
                System.out.println("Перехватчик сообщил об ошибке!!!");
                //response.getHeader("erro");
                throw new UnAuthorException("Пользователь не имеет Авторизоваться");            }
            return true;
        }
}
Язык кода:javascript
копировать
/*/**
 *@author suze
 *@date 2023-10-25
 *@time 15:23
 **/
public class RefreshTokeninterceptor implements HandlerInterceptor {

    //И используется в MvcConfig LoginInterceptor Итак, нам нужно перейти в MvcConfig, чтобы внедрить
    private StringRedisTemplate stringRedisTemplate;
    //Потому что этот класс не Spring Он создается при загрузке, но это класс, созданный вручную, поэтому внедрение зависимостей невозможно внедрить с помощью аннотаций. Нам нужно вручную использовать конструктор для внедрения этой зависимости.
    public RefreshTokeninterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token2=request.getHeader("token2");
        String ListenerKey = LOGIN_LISTENER_KEY + token2;
        //Здесь информация о слушателе вводится в функцию слушателя Авторизоваться
        String LisStr = stringRedisTemplate.opsForValue().get(ListenerKey);
        if(LisStr== null || LisStr.isEmpty()){
            System.err.println("Токен прослушивателя пуст");
        }
        else {
            Listener listener = JSON.parseObject(LisStr, Listener.class);
            ListenerHolder.saveListener(listener);
            stringRedisTemplate.expire(ListenerKey,15, TimeUnit.MINUTES);
            return true;
        }
        //Получаем токен в заголовке запроса  Подробности смотрите в авторизации во внешнем коде.
        String token = request.getHeader("token");
        if(StrUtil.isBlank(token)){//Определить, пуст ли он
            System.err.println("токен пуст");
            return  true;
        }
        // Получить пользователей Redis на основе токена
        String key =LOGIN_USER_KEY+token;
        String userstr = stringRedisTemplate.opsForValue().get(key);
        //System.err.println("Получить пользователей Redis на основе токена:"+userstr);
        //Определяем, существует ли пользователь  Если он не существует, проверьте, является ли он прослушивателем.
        if(userstr== null || userstr.isEmpty()){
            System.err.println("Пользователь пуст");
            return  true;
        }
        // Преобразование строки json запрашиваемого пользователя в объект пользователя.
        User user = JSON.parseObject(userstr, User.class);
        //существовать Сохраните информацию о пользователе в TheadLocal.
        UserHolder.saveUser(user);
        System.out.println("Сохранить пользователя"+user.getOpenId()+"Информация в TheadLocal");
        //Обновляем срок действия токена
        stringRedisTemplate.expire(key,15, TimeUnit.MINUTES);
        //выпускать
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //удаляем пользователя
        UserHolder.removeUser();
        ListenerHolder.removeListener();
    }
}

В соответствии с вашими потребностями я могу удалить некоторые части своего бизнеса. Ничего страшного, если я не удалю его. Его все равно можно использовать, но это немного медленно.

реализация службы вебсокетов

Язык кода:javascript
копировать
@ServerEndpoint(value = "/imserver/{userId}")
@Component
public class WebSocketServer {

    private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);

    /**
     * Запишите текущее количество онлайн-соединений
     */
    public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();
    //public static final Map<String, Session> UserMap = new ConcurrentHashMap<>();Здесь нет необходимости знать имя другого человека Так что не надо добавлять Нужно добавить еще

    /**
     * <<<<<<< HEAD
     * установлен в статический режим Поделитесь картой сообщений ConcurrentMap — это потокобезопасная карта.  HashMap небезопасен
     */
    //MessageMap здесь хранит информацию о том, что пользователь находится в автономном режиме Коллекция сообщений, полученных, пока он был офлайн. Так что ключ здесь - это ключ получателя
    private static ConcurrentMap<String, List<String>> messageMap = new ConcurrentHashMap<>();

    /**
     *
     * Метод, вызываемый при успешном установлении соединения
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        sessionMap.put(userId, session);

//        stringRedisTemplate.opsForList().

        log.info("Присоединился новый пользователь, userId={}, Текущее количество людей онлайн: {}", userId, sessionMap.size());
        JSONObject result = new JSONObject();
        JSONArray array = new JSONArray();
        result.set("users", array);
        for (Object key : sessionMap.keySet()) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.set("userId", key);
            // {"userId": "aysgduiehfiuew", "userId": "admin"}
            array.add(jsonObject);
        }
        //Здесь вы получаете карту истории пользователя userMessage
        List<String> userMessage = messageMap.get(userId);
        //Загрузка истории  Этот процесс эквивалентен повторной отправке сообщения самому себе.
        if (userMessage!=null) {
            for (int i = userMessage.size() - 1; i >= 0; i--) {
                String message = userMessage.get(i);
                //Функция сеанса здесь — сообщить, кому отправлено sendMessage. Здесь вы хотите загрузить исторические сообщения, которые вы пропустили.
                // Поэтому я отправляю записи истории себе. Итак, toSession заполняет ваш собственный сеанс.
                this.sendMessage(message, session);
//                Thread.sleep(10000);
            }
            messageMap.remove(userId);
        }
//        {"users": [{"userId": "zhang"},{ "userId": "admin"}]}
        sendAllMessage(JSONUtil.toJsonStr(result));  // Отправлять сообщения всем клиентам в фоновом режиме
    }
    /**
     * Сервер отправляет сообщение клиенту
     */
    private void sendMessage(String message, Session toSession) {
        try {
            log.info("Сервер отправляет сообщение {} клиенту[{}]", toSession.getId(), message);
            toSession.getBasicRemote().sendText(message);
            //String from = JSONUtil.parseObj(message).getStr("from");
            if (!messageMap.get(toSession.getId()).isEmpty()) {
                List<String> list = messageMap.get(toSession.getId());
                log.info("Сообщения, которые будут отправлены, продолжают храниться");
                list.add(message);
                //toSession — идентификатор отправителя
                messageMap.put(toSession.getId(), list);
                return;
            } else {
                List<String> list = new ArrayList<>();
                //Коллекция офлайн-сообщений, отправленных этим пользователем
                list.add(message);
                messageMap.put(toSession.getId(), list);
                log.info("Пользователь не сохраняет информацию онлайн");
                return;
            }
        } catch (Exception e) {
            log.error("Сервер отправляет сообщение клиентунеудача", e);
        }
        //        {"users": [{"userId": "zhang"},{ "userId": "admin"}]}
    }


    /**
     * Метод, вызываемый при закрытии соединения
     */
    @OnClose
    public void onClose(Session session, @PathParam("userId") String userId) {
        sessionMap.remove(userId);
        log.info("Соединение закрыто, удалите сеанс пользователя с именем пользователя={}, Текущее количество людей онлайн: {}", userId, sessionMap.size());
    }

    /**
     * Метод, вызываемый после получения сообщения клиента
     * Сообщение, отправленное клиентом, было получено в фоновом режиме.
     * onMessage Это станция ретрансляции сообщений
     * принимать Сторона браузера socket.send Отправил данные JSON
     * @param message клиент Отправилинформация
     */
    @OnMessage
    public void onMessage(String message, Session session, @PathParam("userId") String userId) {
        log.info("Сервер получил сообщение от пользователя username={}:{}", userId, message);
        JSONObject obj = JSONUtil.parseObj(message);
        String toUserId = obj.getStr("to"); // to указывает, какому пользователю оно отправлено, например admin
        String text = obj.getStr("text"); // Текст отправленного сообщения  hello

        //Создаем массив Всегда кладите все затем ниже

        //TODO Напишите здесь Метод истории кэша для обработки Кроме теста123 Это для сердцебиения Нет необходимости кэшировать
        if(!toUserId.equals("test123")){
            Session toSession = sessionMap.get(toUserId); // в соответствии с to идентификатор пользователя, который нужно получить сеанс, а затем отправить текст сообщения через сеанс
            if (toSession != null) {
                // серверная часть Снова соберите сообщение. Собранное сообщение содержит отправителя и отправленное текстовое содержимое.
                // {"from": "zhang", "text": "hello"}
                JSONObject jsonObject = new JSONObject();
                jsonObject.set("from", userId);  // from да zhang
                jsonObject.set("text", text);  // text Тот же текст, что и выше
                this.sendMessage(jsonObject.toString(), toSession);
                log.info("Отправлено пользователю username={}, сообщение: {}", toUserId, jsonObject.toString());
            } else {
                log.info("Отправка не удалась, сеанс для пользователя username={}", не найден. toUserId);
            }
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("Произошла ошибка");
        error.printStackTrace();
    }

    /**
     * Сервер отправляет сообщение всем клиентам
     */
    private void sendAllMessage(String message) {
        try {
            for (Session session : sessionMap.values()) {
                log.info("Сервер отправляет сообщение {} клиенту[{}]", session.getId(), message);
                session.getBasicRemote().sendText(message);
            }
        } catch (Exception e) {
            log.error("Сервер отправляет сообщение клиентунеудача", e);
        }
    }
}

Было бы слишком много писать здесь о видеочате, я планирую разместить это в следующей статье. Если вам интересно, вы можете отправить мне личное сообщение, чтобы получить проект, или подписаться на мою следующую статью, чтобы объяснить это конкретно.

Дайте мне тройную ссылку, братья. Это непросто сделать.

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose