当前位置: 首页 > news >正文

即时通讯增加Redis渠道

情况说明

在本地和服务器分别启动im服务,当本地发送消息时,会发现服务器上并没有收到消息

初版im只支持单机版,不支持分布式的情况。此次针对该情况对项目进行优化,文档中贴出的代码非完整代码,可自行查看参考资料[2]

代码结构调整

本次调整需要增加一个redis的渠道,为了方便后续再进行渠道的增加,对现有代码结构进行调整

  • IBaseSendExecutor

渠道扩充接口,后续再增加渠道都可以实现该接口

package com.example.im.infra.executor.send;/*** @author PC* 通信处理*/
public interface IBaseSendExecutor {/*** 获取通信类型,预置的有默认和redis** @return 通讯类型*/String getCommunicationType();/*** 发送给指定人** @param sendUserName 发送人* @param message      消息*/void sendToUser(String sendUserName, String message);/*** 发送给全部人** @param sendUserName 发送人* @param message      消息*/void sendToAll(String sendUserName, String message);
}
  • AbstractBaseSendExecutor

通信处理抽象类,将一些预定义的渠道所需要的公有方法提取出来

package com.example.im.infra.executor.send;import com.example.im.config.WebSocketProperties;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;/*** @author PC*/
public abstract class AbstractBaseSendExecutor implements IBaseSendExecutor {protected WebSocketProperties webSocketProperties;@Autowiredpublic void setWebSocketProperties(WebSocketProperties webSocketProperties) {this.webSocketProperties = webSocketProperties;}/*** 获取接收人信息** @param sendUserName 发送人* @param message      消息* @return 接收人列表*/protected List<String> getReceiverName(String sendUserName, String message) {if (!StringUtils.contains(message, webSocketProperties.getReceiverSeparator())) {return new ArrayList<>();}String[] names = StringUtils.split(message, webSocketProperties.getReceiverSeparator());return Stream.of(names).skip(1).filter(receiver ->!(webSocketProperties.getReceiverExcludesHimselfFlag() && StringUtils.equals(sendUserName, receiver))).collect(Collectors.toList());}/*** 根据配置处理发送的信息** @param message 原消息* @return 被处理后的消息*/protected String generatorMessage(String message) {return BooleanUtils.isTrue(webSocketProperties.getExcludeReceiverInfoFlag()) ?StringUtils.substringBefore(message, webSocketProperties.getReceiverSeparator()) : message;}
}
  • DefaultSendExecutor

原有消息发送逻辑

package com.example.im.infra.executor.send;import com.example.im.endpoint.WebSocketEndpoint;
import com.example.im.infra.constant.ImConstants;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;/*** @author PC* 默认执行*/
@Component
public class DefaultSendExecutor extends AbstractBaseSendExecutor {private final static Logger logger = LoggerFactory.getLogger(DefaultSendExecutor.class);private TaskExecutor taskExecutor;@Autowiredpublic void setTaskExecutor(TaskExecutor taskExecutor) {this.taskExecutor = taskExecutor;}@Overridepublic String getCommunicationType() {return ImConstants.CommunicationType.DEFAULT;}@Overridepublic void sendToUser(String sendUserName, String message) {List<String> receiverNameList = getReceiverName(sendUserName, message);CountDownLatch countDownLatch = new CountDownLatch(receiverNameList.size());Set<String> notOnlineReceiverSet = ConcurrentHashMap.newKeySet();Set<String> finalNotOnlineReceiverSet = notOnlineReceiverSet;receiverNameList.forEach(receiverName -> taskExecutor.execute(() -> {try {if (WebSocketEndpoint.WEB_SOCKET_ENDPOINT_MAP.containsKey(receiverName)) {WebSocketEndpoint.WEB_SOCKET_ENDPOINT_MAP.get(receiverName).getSession().getBasicRemote().sendText(generatorMessage(message));} else {finalNotOnlineReceiverSet.add(receiverName);}} catch (IOException ioException) {logger.error("send error:" + ioException);} finally {countDownLatch.countDown();}}));try {countDownLatch.await();} catch (InterruptedException interruptedException) {logger.error("error.countDownLatch.await");}notOnlineReceiverSet = notOnlineReceiverSet.stream().filter(StringUtils::isNotEmpty).collect(Collectors.toSet());if (CollectionUtils.isNotEmpty(notOnlineReceiverSet)) {logger.info("not online number is " + notOnlineReceiverSet.size());logger.info("The user : {} is not online", String.join(",", notOnlineReceiverSet));}}@Overridepublic void sendToAll(String sendUserName, String message) {for (Map.Entry<String, WebSocketEndpoint> webSocketEndpointEntry : WebSocketEndpoint.WEB_SOCKET_ENDPOINT_MAP.entrySet()) {taskExecutor.execute(() -> {if (webSocketProperties.getReceiverExcludesHimselfFlag() && StringUtils.equals(sendUserName, webSocketEndpointEntry.getKey())) {return;}try {webSocketEndpointEntry.getValue().getSession().getBasicRemote().sendText(generatorMessage(message));} catch (IOException ioException) {logger.error("send error:" + ioException);}});}}
}
  • SendExecutorFactory

发送渠道工厂

package com.example.im.infra.executor.send;import com.example.im.config.WebSocketProperties;
import com.example.im.infra.executor.config.ExecutorConfiguration;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.Optional;/*** @author PC* 发送逻辑工厂*/
@Component
public class SendExecutorFactory {private final WebSocketProperties webSocketProperties;private ExecutorConfiguration executorConfiguration;@Autowiredpublic SendExecutorFactory(WebSocketProperties webSocketProperties) {this.webSocketProperties = webSocketProperties;}@Autowiredpublic void setExecutorConfiguration(ExecutorConfiguration executorConfiguration) {this.executorConfiguration = executorConfiguration;}public void onMessage(String sendUserName, String message) {IBaseSendExecutor iBaseSendExecutor = Optional.ofNullable(executorConfiguration.getBaseSendExecutorMap().get(webSocketProperties.getCommunicationType())).orElse(new DefaultSendExecutor());//包含@发给指定人,否则发给全部人if (StringUtils.contains(message, webSocketProperties.getReceiverSeparator())) {iBaseSendExecutor.sendToUser(sendUserName, message);} else {iBaseSendExecutor.sendToAll(sendUserName, message);}}
}
  • ExecutorConfiguration

加载

package com.example.im.infra.executor.config;import com.example.im.infra.executor.send.IBaseSendExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;import java.util.HashMap;
import java.util.Map;/*** @author PC* Executor配置*/
@Component
public class ExecutorConfiguration implements ApplicationContextAware {private final static Logger logger = LoggerFactory.getLogger(ExecutorConfiguration.class);private Map<String, IBaseSendExecutor> baseSendExecutorMap = new HashMap<>(16);private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {ExecutorConfiguration.applicationContext = applicationContext;//加载IBaseSendExecutor实现类this.initBaseSendExecutor(applicationContext);}/*** 加载IBaseSendExecutor实现类* 如果一个服务的发送渠道是固定的,可以使用@Bean搭配@ConditionalOnProperty的方式* 但是考虑到后续可能会有一个服务不同发送渠道的场景,采用当前加载方式** @param applicationContext 上下文*/private void initBaseSendExecutor(ApplicationContext applicationContext) {logger.info("Start loading IBaseSendExecutor");Map<String, IBaseSendExecutor> baseSendExecutorMap = applicationContext.getBeansOfType(IBaseSendExecutor.class);for (Map.Entry<String, IBaseSendExecutor> iBaseSendExecutorEntry : baseSendExecutorMap.entrySet()) {String communicationType = iBaseSendExecutorEntry.getValue().getCommunicationType();this.baseSendExecutorMap.put(communicationType, iBaseSendExecutorEntry.getValue());logger.info("initBaseSendExecutor>>>>>>>communicationType:{},className:{}", communicationType, iBaseSendExecutorEntry.getValue().getClass().getName());}logger.info("IBaseSendExecutor loading is complete");}public static ApplicationContext getApplicationContext() {return applicationContext;}public Map<String, IBaseSendExecutor> getBaseSendExecutorMap() {return baseSendExecutorMap;}public void setBaseSendExecutorMap(Map<String, IBaseSendExecutor> baseSendExecutorMap) {this.baseSendExecutorMap = baseSendExecutorMap;}
}

添加redis通信渠道

  • pom.xml
<dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId>
</dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId>
</dependency>
  • application.yml
server:port: 18080
cus:ws:exclude-receiver-info-flag: truereceiver-excludes-himself-flag: truecommunication-type: redis
spring:redis:host: 127.0.0.1port: 6379username: rootpassword: rootdatabase: ${SPRING_REDIS_DATABASE:1}# Redis连接超时时间connect-timeout: ${SPRING_REDIS_CONNECT_TIMEOUT:2000}# Redis读取超时时间timeout: ${SPRING_REDIS_READ_TIMEOUT:5000}lettuce:pool:# 资源池中最大连接数# 默认8,-1表示无限制;可根据服务并发redis情况及服务端的支持上限调整max-active: ${SPRING_REDIS_POOL_MAX_ACTIVE:50}# 资源池运行最大空闲的连接数# 默认8,-1表示无限制;可根据服务并发redis情况及服务端的支持上限调整,一般建议和max-active保持一致,避免资源伸缩带来的开销max-idle: ${SPRING_REDIS_POOL_MAX_IDLE:50}# 当资源池连接用尽后,调用者的最大等待时间(单位为毫秒)# 默认 -1 表示永不超时,设置5秒max-wait: ${SPRING_REDIS_POOL_MAX_WAIT:5000}
  • RedisSendExecutor

redis发送

package com.example.im.infra.executor.send.redis;import com.example.im.infra.constant.ImConstants;
import com.example.im.infra.executor.send.AbstractBaseSendExecutor;
import com.example.im.infra.executor.send.dto.MessageInfo;
import com.example.im.infra.executor.send.dto.ScopeOfSendingEnum;
import com.example.im.infra.util.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;/*** @author PC* redis执行*/
@Component
public class RedisSendExecutor extends AbstractBaseSendExecutor {private final static Logger logger = LoggerFactory.getLogger(RedisSendExecutor.class);private RedisTemplate<String, String> redisTemplate;@Autowiredpublic void setRedisTemplate(RedisTemplate<String, String> redisTemplate) {this.redisTemplate = redisTemplate;}@Overridepublic String getCommunicationType() {return ImConstants.CommunicationType.REDIS;}@Overridepublic void sendToUser(String sendUserName, String message) {MessageInfo messageInfo = new MessageInfo();messageInfo.setSendUserName(sendUserName);messageInfo.setMessage(message);messageInfo.setScopeOfSending(ScopeOfSendingEnum.USER);logger.debug("send to user redis websocket, channel is " + "redis-websocket");redisTemplate.convertAndSend("redis-websocket-user", JsonUtils.toJson(messageInfo));}@Overridepublic void sendToAll(String sendUserName, String message) {MessageInfo messageInfo = new MessageInfo();messageInfo.setSendUserName(sendUserName);messageInfo.setMessage(message);messageInfo.setScopeOfSending(ScopeOfSendingEnum.ALL);logger.debug("send to all redis websocket, channel is " + "redis-websocket");redisTemplate.convertAndSend("redis-websocket-all", JsonUtils.toJson(messageInfo));}
}
  • RedisMessageListener

redis监听

package com.example.im.infra.executor.send.redis;import com.example.im.infra.executor.send.DefaultSendExecutor;
import com.example.im.infra.executor.send.dto.MessageInfo;
import com.example.im.infra.util.JsonUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;import java.nio.charset.StandardCharsets;/*** @author PC* redis监听*/
@Component
public class RedisMessageListener implements MessageListener {private final static Logger logger = LoggerFactory.getLogger(RedisMessageListener.class);private DefaultSendExecutor defaultSendExecutor;@Autowiredpublic void setDefaultSendExecutor(DefaultSendExecutor defaultSendExecutor) {this.defaultSendExecutor = defaultSendExecutor;}@Overridepublic void onMessage(Message message, byte[] pattern) {//消息内容String messageJson = new String(message.getBody(), StandardCharsets.UTF_8);MessageInfo messageInfo = JsonUtils.toObjectByTypeReference(messageJson, new TypeReference<MessageInfo>() {});switch (messageInfo.getScopeOfSending()) {case USER:defaultSendExecutor.sendToUser(messageInfo.getSendUserName(), messageInfo.getMessage());break;case ALL:defaultSendExecutor.sendToAll(messageInfo.getSendUserName(), messageInfo.getMessage());break;default://一般来说不会出现该情况,除非用户覆盖了ScopeOfSending,后续可以开个扩展发送范围的口子logger.warn("invalid sending range:" + messageInfo.getScopeOfSending().getScopeCode());break;}}
}

测试

本地服务发送消息

服务器接收到了消息

常见问题

打包报错

执行mvn clean packages打包时出现以下错误

[ERROR] contextLoads  Time elapsed: 0.001 s  <<< ERROR!
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serverEndpoint' defined in class path resource [c
om/example/im/config/WebSocketConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available
Caused by: java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available

查看ServerContainer接口,发现其有两个接口实现类,其中有一个是test包的

将其排除后即可正常打包

jar包启动时no main manifest attribute问题

需将pom的plugin标签中的skip标签删除或设置为false

参考资料

[1].初版im文档

[2].im项目地址

相关文章:

即时通讯增加Redis渠道

情况说明 在本地和服务器分别启动im服务&#xff0c;当本地发送消息时&#xff0c;会发现服务器上并没有收到消息 初版im只支持单机版&#xff0c;不支持分布式的情况。此次针对该情况对项目进行优化,文档中贴出的代码非完整代码&#xff0c;可自行查看参考资料[2] 代码结构调…...

C++list

list简介 list是我们的链表&#xff0c;而且是带头双向循环链表&#xff0c;如下图 我们都知道&#xff0c;链表是由一个一个的节点组成的&#xff0c;它的成员由下面几个部分组成 通过对前面string,vector的学习&#xff0c;其实再来看我们的链表及其成员函数&#xff0c;是…...

设计模式 - 结构型

结构型 适配器模式&#xff0c;代理模式&#xff0c;桥接模式&#xff0c;装饰器模式&#xff0c;外观模式&#xff0c;组合模式&#xff0c;享元模式&#xff0c; 单一职责避免子类爆炸Bridge 模式对象的实现Decorator 模式对对象的职责&#xff0c;不生成子类接口隔离Adapt…...

STM32编码器接口

一、概述 1、Encoder Interface 编码器接口概念 编码器接口可接收增量&#xff08;正交&#xff09;编码器的信号&#xff0c;根据编码器旋转产生的正交信号脉冲&#xff0c;自动控制CNT自增或自减&#xff0c;从而指示编码器的位置、旋转方向和旋转速度每个高级定时器和通用…...

2024客户世界年度大会开幕,码号卫士赋能数字运营服务新升级

10月15日&#xff0c;2024年客户世界年度的大会在通州北投希尔顿酒店开幕。作为行业内的一个重要活动&#xff0c;本次大会以“数字运营支撑服务产业新升级”为主题&#xff0c;吸引了众多行业专家和企业代表。 据悉&#xff0c;本次大会以“数字运营支撑服务产业新升级”为主题…...

AcWing 802. 区间和(离散化算法,python)

本篇博客详细讲解一下离散化知识点&#xff0c;通过讲解和详细列题带大家掌握离散化。 题目&#xff1a; 原题链接&#xff1a;https://www.acwing.com/problem/content/description/804/ 假定有一个无限长的数轴&#xff0c;数轴上每个坐标上的数都是 0。 现在&#xff0c;…...

【网页设计】CSS 盒子模型

目标 能够准确阐述盒子模型的 4 个组成部分能够利用边框复合写法给元素添加边框能够计算盒子的实际大小能够利用盒子模型布局模块案例能够给盒子设置圆角边框能够给盒子添加阴影能够给文字添加阴影 1. 盒子模型 页面布局要学习三大核心, 盒子模型, 浮动 和 定位. 学习好盒子模…...

如何通过构建对应的api服务器使Vue连接到数据库

一、安装数据库驱动 在后端安装 MySQL 数据库驱动&#xff0c;比如在 Node.js 环境中可以使用 mysql2 包来连接 MySQL 数据库。在项目目录下运行以下命令安装&#xff1a; npm install mysql2或者使用 yarn&#xff1a; yarn add mysql2二、创建数据库连接模块 创建一个专门…...

新手给视频加字幕的方法有哪些?4种加字幕方法推荐!

在视频制作中&#xff0c;字幕不仅是传递信息的重要手段&#xff0c;还能增强视频的观感和专业性。对于新手来说&#xff0c;如何给视频添加字幕可能是一个挑战。本文将介绍字幕的类型、推荐添加字幕的工具&#xff0c;以及详细添加字幕方法&#xff0c;帮助新手轻松掌握视频字…...

Oracle实际需要用到但常常被忽略的函数

1、Oracle中nvl()与nvl2()函数 函数nvl(expression1,expression2)根据参数1是否为null返回参数1或参数2的值&#xff1b; 函数nvl2(expression1,expression2,expression3)根据参数1是否为null返回参数2或参数3的值 【函数格式】&#xff1a;nvl(expression1,expression2) 若…...

代码随想录算法训练营Day23

局部最优——>全局最优&无反例&#xff0c;试试贪心 455.分发饼干 力扣题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; class Solution {public int findContentChildren(int[] g, int[] s) {Arrays.sort(s);Arrays.sort(g);int gindex0;int count0;…...

vue使用table实现动态数据报表(行合并)

<template><div class"previewTable"><h2>***项目研发数据报告</h2><table id"previewTable" width"100%"><tr><th>项目名称</th><td colspan"6">{{ resultData.proName }}<…...

YARN调度原理详解

YARN&#xff08;Yet Another Resource Negotiator&#xff09;是 Hadoop 集群的资源管理和作业调度框架&#xff0c;它的设计旨在更好地管理和调度 Hadoop 集群中的资源。YARN 解决了传统 Hadoop MapReduce 中资源管理与作业调度紧耦合的问题&#xff0c;使得不同类型的计算任…...

Go-知识泛型

Go-知识泛型 1. 认识泛型1.1 不使用泛型1.2 使用泛型 2. 泛型的特点2.1 函数泛化2.2 类型泛化 3. 类型约束3.1 类型集合3.2 interface 类型集合3.2.1 内置interface类型集合3.2.2 自定义interface类型集合3.2.2.1 任意类型元素3.2.2.2 近似类型元素3.2.2.3 联合类型元素 3.2.3 …...

Qt 如何 发送与解析不定长报文以及数组不定长报文

文章目录 割方式一,采用QDataStream 解析,可直接设定大小端解析,无需自己转换方式二,采用结构体字节对齐方式解析发送接收方割 方式一,采用QDataStream 解析,可直接设定大小端解析,无需自己转换 需要注意的是结构体定义要去掉字节对齐,否则会崩溃,因为由自定义数据结…...

Rust默认使用UTF-8编码来解析源代码文件。如果在代码中包含无法用UTF-8编码表示的字符,编译器会报错!

文章目录 Rust默认编码示例在ANSI编码下中文显示正常的代码在UTF-8编码下将显示不正常在编译时&#xff0c;Rust使用UTF-8编码来解析代码&#xff0c;发现无法用UTF-8编码表示的字符&#xff0c;于是编译器报错 Rust默认编码 Rust 语言默认使用 UTF-8 编码来解析源代码文件。如…...

【jeston】torch相关环境安装

参考&#xff1a;玩转NVIDIA Jetson &#xff08;25&#xff09;— jetson 安装pytorch和torchvision 我的jeston信息&#xff1a; torch install 安装环境 conda create -n your_env python3.8 conda activate your_envpytorch_for_jeston 安装.whl文件 验证&#xff1…...

[CR]厚云填补_大型卫星影像去云数据集

AllClear: A Comprehensive Dataset and Benchmark for Cloud Removal in Satellite Imagery Abstract 卫星图像中的云对下游应用构成了重大挑战。当前云移除研究的一个主要挑战是缺乏一个全面的基准和一个足够大和多样化的训练数据集。为了解决这个问题&#xff0c;我们引入了…...

Langchain CharacterTextSplitter无法分割文档问题

在使用Langchain的文档分割器时&#xff0c;使用CharacterTextSplitter拆分文档是&#xff0c;发现返回的文档根本没有变化&#xff0c;即使设置了chunk_size&#xff0c;返回的大小也不符合参数设置。 CharacterTextSplitter设置了150&#xff0c;但是根本没有处理&#xff0…...

ros service不走是为什么

在ROS&#xff08;Robot Operating System&#xff09;中&#xff0c;如果ROS服务&#xff08;Service&#xff09;没有正常工作&#xff0c;可能有多种原因。你可以检查以下几点来排查问题&#xff1a; 服务是否正确启动 首先&#xff0c;确保服务节点已经启动并注册了相应的…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

k8s业务程序联调工具-KtConnect

概述 原理 工具作用是建立了一个从本地到集群的单向VPN&#xff0c;根据VPN原理&#xff0c;打通两个内网必然需要借助一个公共中继节点&#xff0c;ktconnect工具巧妙的利用k8s原生的portforward能力&#xff0c;简化了建立连接的过程&#xff0c;apiserver间接起到了中继节…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...