基于Netty实现的简单聊天服务组件
目录
- 基于Netty实现的简单聊天服务组件
- 效果展示
- 技术选型:
- 功能分析
- 聊天服务基础设施配置(基于Netty)
- 定义组件基础的配置(`ChatProperties`)
- 定义聊天服务类(`ChatServer`)
- 定义聊天服务配置初始化类(`ChatServerInitializer`)
- 用户上线、下线处理
- 客户端绑定服务处理类(`ClientInboundHandler`)
- 用户消息发送、接收处理
- 定义一个文本消息处理器(`TextWebSocketFrameHandler`)
- 用户登录凭证校验
- 定义一个凭证处理器接口(`AuthorizationProcessor`)
- 定义 `ChatAutoConfiguration` 自动化配置类
- 定义 `ChatServerApplication` 服务启动类
- 参考资料
基于Netty实现的简单聊天服务组件
本文摘自Quan后台管理服务框架中的
quan-chat
工具,该工具仅实现了非常简单服务模型。后期本人会视情况扩展更多复杂的业务场景。
如果本文对您解决问题有帮助,欢迎到Gitee或Github点个star 🤝
quan-chat 是一个基于 Netty 实现的服务端即时消息通讯组件,组件本身不具备业务处理能力,主要的作用是提供服务端消息中转; 通过实现组件中的接口可以完成与项目相关的业务功能, 例如:点对点消息收发、权限校验、聊天记录保存等。
web展示层ui基于layim。layim展示的功能较为丰富。为演示服务组件,仅实现点对点聊天功能。其它功能视情况扩展。
本组件仅用于学习交流使用,本文应用到的 layim 来自互联网,如果您想将 layim 框架用于其它用途,必须取得原作者授权: layui ,否则产生的一切法律责任与本作者无关。
效果展示
技术选型:
spring-boot-2.7.16
netty-4.1.97
layim-3.9.8
功能分析
- 聊天服务基础设施配置(基于Netty)
- 用户上线、下线处理
- 用户消息发送、接收处理
- 用户登录凭证校验
完整的组件代码开源地址:https://gitee.com/quan100/quan/tree/main/quan-tools/quan-chat
下面仅展示部分代码
聊天服务基础设施配置(基于Netty)
Netty 是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。
定义组件基础的配置(ChatProperties
)
ChatProperties 主要用于定义组件内部使用到的配置参数。
package cn.javaquan.tools.chat.autoconfigure;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert;/*** Configuration properties for im support.** @author javaquan* @since 1.0.0*/
@ConfigurationProperties(prefix = "quan.im")
public class ChatProperties {/*** 默认数据包最大长度* 64kb*/private final static int MAX_FRAME_SIZE = 65536;/*** 默认的消息体最大长度* 64kb*/private final static int MAX_CONTENT_LENGTH = 65536;/*** 空闲检查时间,单位:秒*/private final static long READER_IDLE_TIME = 600L;/*** 开启IM服务的端口*/private Integer port;/*** SSL配置*/private Ssl ssl;/*** websocket 路径*/private String websocketPath;/*** 数据包最大长度* 单位:字节*/private Integer maxFrameSize;/*** 消息体最大长度* 单位:字节*/private Integer maxContentLength;/*** 允许连接空闲的最大时间* <p>* 当空闲超过最大时间后,强制下线*/private Long readerIdleTime;public Integer getPort() {return port;}public void setPort(Integer port) {this.port = port;}public int determineDefaultPort() {Assert.notNull(this.port, "[Assertion failed chat server port] - this numeric argument must have value; it must not be null");return this.port;}public Ssl getSsl() {return ssl;}public void setSsl(Ssl ssl) {this.ssl = ssl;}public String getWebsocketPath() {return websocketPath;}public void setWebsocketPath(String websocketPath) {this.websocketPath = websocketPath;}public String determineDefaultWebsocketPath() {Assert.hasText(this.websocketPath, "[Assertion failed chat server websocketPath] - it must not be null or empty");return this.websocketPath;}public Integer getMaxFrameSize() {return maxFrameSize;}public void setMaxFrameSize(Integer maxFrameSize) {this.maxFrameSize = maxFrameSize;}public Integer determineDefaultMaxFrameSize() {if (null == maxFrameSize) {this.setMaxFrameSize(MAX_FRAME_SIZE);}return this.maxFrameSize;}public Integer getMaxContentLength() {return maxContentLength;}public void setMaxContentLength(Integer maxContentLength) {this.maxContentLength = maxContentLength;}public Integer determineDefaultMaxContentLength() {if (null == maxContentLength) {this.setMaxContentLength(MAX_CONTENT_LENGTH);}return this.maxContentLength;}public Long getReaderIdleTime() {return readerIdleTime;}public void setReaderIdleTime(Long readerIdleTime) {this.readerIdleTime = readerIdleTime;}public Long determineDefaultReaderIdleTime() {if (null == readerIdleTime) {this.setReaderIdleTime(READER_IDLE_TIME);}return this.readerIdleTime;}/*** ssl properties.*/public static class Ssl {private boolean enabled = false;private String protocol = "TLS";/*** an X.509 certificate chain file in PEM format*/private String keyCertChainFilePath;/*** a PKCS#8 private key file in PEM format*/private String keyFilePath;public boolean isEnabled() {return enabled;}public void setEnabled(boolean enabled) {this.enabled = enabled;}public String getProtocol() {return protocol;}public void setProtocol(String protocol) {this.protocol = protocol;}public String getKeyCertChainFilePath() {return keyCertChainFilePath;}public void setKeyCertChainFilePath(String keyCertChainFilePath) {this.keyCertChainFilePath = keyCertChainFilePath;}public String determineDefaultKeyCertChainFilePath() {Assert.hasText(this.keyCertChainFilePath, "[Assertion failed chat server keyCertChainFilePath] - it must not be null or empty");return this.keyCertChainFilePath;}public String getKeyFilePath() {return keyFilePath;}public void setKeyFilePath(String keyFilePath) {this.keyFilePath = keyFilePath;}public String determineDefaultKeyFilePath() {Assert.hasText(this.keyFilePath, "[Assertion failed chat server keyFilePath] - it must not be null or empty");return this.keyFilePath;}}public void afterPropertiesSet() {determineDefaultPort();determineDefaultWebsocketPath();determineDefaultMaxFrameSize();determineDefaultMaxContentLength();determineDefaultReaderIdleTime();}
}
yml 配置示例:
quan: im:port: 10000 # 配置chat服务端口websocket-path: /chat # 配置chat服务websocket访问的urireader-idle-time: 1800 #允许连接空闲的时间,单位:秒。超时后强制下线
定义聊天服务类(ChatServer
)
用于实现客户端与服务器建立连接,状态维护
package cn.javaquan.tools.chat.server;import cn.javaquan.tools.chat.autoconfigure.ChatProperties;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.ImmediateEventExecutor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;import java.net.InetSocketAddress;/*** 默认的聊天服务** @author javaquan* @since 1.0.0*/
public class ChatServer {private static final Log logger = LogFactory.getLog(ChatServer.class);private final ChannelGroup channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);private final EventLoopGroup group = new NioEventLoopGroup();private Channel channel;public ChannelFuture start(InetSocketAddress address, ChatProperties properties) {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(group).channel(NioServerSocketChannel.class).childHandler(createInitializer(channelGroup, properties));ChannelFuture future = bootstrap.bind(address);future.syncUninterruptibly();channel = future.channel();return future;}protected ChannelInitializer<Channel> createInitializer(ChannelGroup group, ChatProperties properties) {return new ChatServerInitializer(group, properties);}public void destroy() {if (channel != null) {channel.close();}channelGroup.close();group.shutdownGracefully();}public void start(ChatProperties properties) {ChannelFuture future = this.start(new InetSocketAddress(properties.getPort()), properties);addShutdownHook(this);future.addListener((listener) -> {Assert.isTrue(listener.isSuccess(), logMessageFormat(properties.getPort(), "error"));logger.info(logMessageFormat(properties.getPort(), "success"));});}/*** Registers a new virtual-machine shutdown hook.** @param chatServer*/private void addShutdownHook(ChatServer chatServer) {Runtime.getRuntime().addShutdownHook(new Thread(chatServer::destroy));}private String logMessageFormat(Integer port, String state) {return String.format("%s started %s on port(s): %s", this.getClass().getSimpleName(), state, port);}
}
定义聊天服务配置初始化类(ChatServerInitializer
)
主要用于初始化聊天服务应用到的处理器。
package cn.javaquan.tools.chat.server;import cn.javaquan.tools.chat.autoconfigure.ChatProperties;
import cn.javaquan.tools.chat.context.ClientInboundHandler;
import cn.javaquan.tools.chat.context.TextWebSocketFrameHandler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;import java.util.concurrent.TimeUnit;/*** 初始化服务配置** @author javaquan*/
public class ChatServerInitializer extends ChannelInitializer<Channel> {private final ChannelGroup group;private final ChatProperties properties;public ChatServerInitializer(ChannelGroup group, ChatProperties properties) {this.group = group;this.properties = properties;}@Overrideprotected void initChannel(Channel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new HttpServerCodec());pipeline.addLast(new ChunkedWriteHandler());pipeline.addLast(new HttpObjectAggregator(properties.getMaxContentLength()));pipeline.addLast(new IdleStateHandler(properties.getReaderIdleTime(), 0, 0, TimeUnit.SECONDS));pipeline.addLast(new ClientInboundHandler(group, properties.getWebsocketPath()));pipeline.addLast(new TextWebSocketFrameHandler());pipeline.addLast(new WebSocketServerProtocolHandler(properties.getWebsocketPath(), null, true, properties.getMaxFrameSize()));}
}
用户上线、下线处理
客户端绑定服务处理类(ClientInboundHandler
)
主要用于处理用户上线、下线状态处理。
package cn.javaquan.tools.chat.context;import cn.javaquan.tools.chat.core.ChannelPool;
import cn.javaquan.tools.chat.core.support.AuthorizationProcessor;
import cn.javaquan.tools.chat.util.SpringUtils;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;/*** 客户端用户状态处理** @author javaquan*/
@Sharable
public class ClientInboundHandler extends ChannelInboundHandlerAdapter {private static final Log logger = LogFactory.getLog(ClientInboundHandler.class);private final ChannelGroup group;private final String websocketPath;public ClientInboundHandler(ChannelGroup group, String websocketPath) {this.group = group;this.websocketPath = websocketPath;}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {if (msg instanceof FullHttpRequest) {FullHttpRequest request = (FullHttpRequest) msg;String uri = request.uri();Map<String, String> queryParams = paramsParser(uri);online(ctx.channel(), queryParams);request.setUri(websocketPath);}super.channelRead(ctx, msg);}@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {if (evt instanceof IdleStateEvent) {IdleStateEvent event = (IdleStateEvent) evt;if (event.state() == IdleState.READER_IDLE) {logger.info(String.format("用户[%s]闲置时间超过最大值,将关闭连接!", ChannelPool.getSessionState(ctx.channel())));ctx.channel().close();}} else {super.userEventTriggered(ctx, evt);}}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {group.add(ctx.channel());}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {Channel channel = ctx.channel();group.remove(channel);offline(channel);}/*** 异常时调用** @param ctx* @param cause*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {logger.error("服务器错误", cause);offline(ctx.channel());// 发生异常之后关闭连接(关闭channel)ctx.channel().close();}/*** url参数解析** @param uriParams* @return* @throws URISyntaxException*/private Map<String, String> paramsParser(String uriParams) throws URISyntaxException {URI uri = new URI(uriParams);Map<String, String> paramsMap = new HashMap<>();String queryParam = uri.getQuery();String[] queryParams = queryParam.split("&");for (String param : queryParams) {String[] urlParam = param.split("=");paramsMap.put(urlParam[0], urlParam[1]);}return paramsMap;}/*** 用户上线** @param channel* @param urlParams url参数*/private void online(Channel channel, Map<String, String> urlParams) {String userId = urlParams.get("userId");String authorization = urlParams.get("authorization");AuthorizationProcessor authorizationProcessor = SpringUtils.getBean(AuthorizationProcessor.class);if (!authorizationProcessor.checkAuth(authorization)) {channel.close();logger.info(String.format("用户[%s]凭证校验失败,连接被服务器拒绝", userId));return;}logger.info(String.format("用户[%s]上线", userId));channel.attr(ChannelPool.SESSION_STATE).set(userId);ChannelPool.addChannel(userId, channel);/// TODO 若用户上线,则通知好友已上线。kafka发送上线事件}/*** 用户离线** @param channel*/private void offline(Channel channel) {ChannelPool.removeChannel(channel);logger.info(String.format("用户[%s]下线", ChannelPool.getSessionState(channel)));/// TODO 若用户下线,则通知好友已下线。kafka发送下线事件}}
用户消息发送、接收处理
定义一个文本消息处理器(TextWebSocketFrameHandler
)
用于将用户发送的文本消息转换为服务端使用的模版消息。
通过模版将消息转发给接收者。
package cn.javaquan.tools.chat.context;import cn.javaquan.tools.chat.core.MessageHandlerFactory;
import cn.javaquan.tools.chat.core.message.MessageTemplate;
import cn.javaquan.tools.chat.util.JsonUtils;
import cn.javaquan.tools.chat.util.SpringUtils;
import cn.javaquan.tools.chat.core.support.IMessageHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;/*** 消息处理器** @author javaquan*/
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {@Overridepublic void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {MessageTemplate messageTemplate = messageConvertor(msg);messageHandler(ctx, messageTemplate);}/*** 消息处理* <p>* 根据消息类型处理消息* <p>* 需要自定义实现{@link IMessageHandler}接口。** @param ctx* @param messageTemplate*/private void messageHandler(ChannelHandlerContext ctx, MessageTemplate messageTemplate) {MessageHandlerFactory messageHandlerFactory = SpringUtils.getBean(MessageHandlerFactory.class);messageHandlerFactory.getService(messageTemplate.getType()).handler(ctx, messageTemplate);}/*** 将字符串信息转换为模版信息格式** @param msg* @return*/private MessageTemplate messageConvertor(TextWebSocketFrame msg) {return JsonUtils.parseObject(msg.text(), MessageTemplate.class);}
}
用户登录凭证校验
定义一个凭证处理器接口(AuthorizationProcessor
)
将处理器定义成接口,主要目的是将组件与业务解耦。
因为不同的业务,实现的权限业务都可能不一样。
只需业务端实现该接口,当权限校验不通过时,组件内部就会拒绝客户端连接。
package cn.javaquan.tools.chat.core.support;/*** 授权凭证处理器** @author wangquan*/
public interface AuthorizationProcessor {/*** 检查权限** @param authorization 登录凭证* @return*/boolean checkAuth(String authorization);}
定义 ChatAutoConfiguration
自动化配置类
ChatAutoConfiguration
是quan-chat
组件中最重要的一项配置,通过该配置来定义组件是否生效。
当引入quan-chat
组件时,不需要对组件进行扫描。服务启动时会自动发现该配置。
通过该配置初始化聊天服务所依赖的相关功能。若未按照配置要求配置属性,quan-chat
组件引入将无效。
package cn.javaquan.tools.chat.autoconfigure;import cn.javaquan.tools.chat.ChatServerApplication;
import cn.javaquan.tools.chat.core.ChannelPool;
import cn.javaquan.tools.chat.core.support.AbstractAuthorizationCheckProcessor;
import cn.javaquan.tools.chat.core.support.AuthorizationProcessor;
import cn.javaquan.tools.chat.server.ChatServer;
import cn.javaquan.tools.chat.server.SecureChatServer;
import io.netty.channel.Channel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;import java.io.File;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** im聊天sdk配置** @author javaquan* @since 1.0.0*/
@AutoConfiguration
@EnableConfigurationProperties(ChatProperties.class)
public class ChatAutoConfiguration {@Import(ChatServerApplication.class)@Configuration(proxyBeanMethods = false)@Conditional(ChatCondition.class)protected static class ChatConfiguration {@ConditionalOnProperty(prefix = "quan.im.ssl", name = "enabled", havingValue = "false", matchIfMissing = true)@ConditionalOnMissingBean@BeanChatServer chatServer() {return new ChatServer();}@ConditionalOnMissingBean@BeanChannelPool channelPool() {Map<String, Channel> channelContainer = new ConcurrentHashMap<>();return new ChannelPool(channelContainer);}@ConditionalOnMissingBean@BeanAuthorizationProcessor authorizationProcessor() {return new AbstractAuthorizationCheckProcessor();}}static class ChatCondition extends AnyNestedCondition {ChatCondition() {super(ConfigurationPhase.PARSE_CONFIGURATION);}@ConditionalOnProperty(prefix = "quan.im", name = "port")static class PortProperty {}@ConditionalOnProperty(prefix = "quan.im.ssl", name = "enabled", havingValue = "true")@ConditionalOnMissingBean@BeanSslContext sslContext(ChatProperties properties) throws Exception {ChatProperties.Ssl ssl = properties.getSsl();File keyCertChainFile = new File(ssl.determineDefaultKeyCertChainFilePath());File keyFile = new File(ssl.determineDefaultKeyFilePath());return SslContextBuilder.forServer(keyCertChainFile, keyFile).build();}@ConditionalOnProperty(prefix = "quan.im.ssl", name = "enabled", havingValue = "true")@ConditionalOnMissingBean@BeanChatServer secureChatServer(SslContext context) {return new SecureChatServer(context);}}}
定义 ChatServerApplication
服务启动类
当引入 quan-chat 组件时,并正确配置
ChatProperties
属性,服务启动时则会自动扫描ChatServerApplication
类,用于启动 聊天服务端。
package cn.javaquan.tools.chat;import cn.javaquan.tools.chat.autoconfigure.ChatProperties;
import cn.javaquan.tools.chat.server.ChatServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;/*** chat服务启动** @author javaquan* @since 1.0.0*/
public class ChatServerApplication implements ApplicationRunner {@Autowiredprivate ChatServer chatServer;@Autowiredprivate ChatProperties properties;@Overridepublic void run(ApplicationArguments args) throws Exception {properties.afterPropertiesSet();chatServer.start(properties);}
}
参考资料
如果本文对您解决问题有帮助,欢迎到Gitee或Github点个star 🤝
quan-chat 工具文档:https://doc.javaquan.cn/pages/tools/chat/
quan-chat 工具开源地址:https://gitee.com/quan100/quan/tree/main/quan-tools/quan-chat
相关文章:

基于Netty实现的简单聊天服务组件
目录 基于Netty实现的简单聊天服务组件效果展示技术选型:功能分析聊天服务基础设施配置(基于Netty)定义组件基础的配置(ChatProperties)定义聊天服务类(ChatServer)定义聊天服务配置初始化类&am…...

视频封面:从视频中提取封面,轻松制作吸引人的视频
在当今的数字时代,视频已成为人们获取信息、娱乐和交流的重要方式。一个吸引人的视频封面往往能抓住眼球,提高点击率和观看率。今天将介绍如何从视频中提取封面,轻松制作吸引人的视频封面。 一、准备素材选择合适的视频片段 首先࿰…...

CICD 持续集成与持续交付——gitlab
部署 虚拟机最小需求:4G内存 4核cpu 下载:https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/ 安装依赖性 [rootcicd1 ~]# yum install -y curl policycoreutils-python openssh-server perl[rootcicd1 ~]# yum install -y gitlab-ce-15.9.3-ce.0…...

Linux - 驱动开发 - RNG框架
说明 公司SOC上有一个新思的真随机数(TRNG)模块,Linux平台上需要提供接口给外部使用。早期方式是提供一个独立的TRNG驱动,实现比较简单的,但是使用方式不open,为了加入Linux生态环境,对接linux…...

qsort使用举例和qsort函数的模拟实现
qsort使用举例 qsort是C语言中的一个标准库函数,用于对数组或者其他数据结构中的元素进行排序。它的原型如下: void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); 我们可以去官网搜来看一看:…...

AttributeError: module ‘gradio‘ has no attribute ‘ClearButton‘解决方案
大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…...

Kafka 集群如何实现数据同步?
哈喽大家好,我是咸鱼 最近这段时间比较忙,将近一周没更新文章,再不更新我那为数不多的粉丝量就要库库往下掉了 T﹏T 刚好最近在学 Kafka,于是决定写篇跟 Kafka 相关的文章(文中有不对的地方欢迎大家指出)…...

一本了解生成式人工智能
上周,发了一篇关于大语言模型图数据库技术相结合的文章,引起了很多朋友的兴趣。当然了,这项技术本身就让俺们很兴奋,比如我就是从事图研发的,当然会非常关注它在图领域的应用与相互促就啦。 纵观人类文明历史ÿ…...

git 相关指令总结(持续更新中......)
文章目录 一、git clone 相关指令1.1 clone 指定分支的代码 一、git clone 相关指令 1.1 clone 指定分支的代码 git clone -b 分支名 仓库地址...

windows 安装 Oracle Database 19c
目录 什么是 Oracle 数据库 下载 Oracle 数据库 解压文件 运行安装程序 测试连接 什么是 Oracle 数据库 Oracle数据库是由美国Oracle Corporation(甲骨文公司)开发和提供的一种关系型数据库管理系统,它是一种强大的关系型数据库管理系统…...

【数据结构】图的存储结构(邻接矩阵)
一.邻接矩阵 1.图的特点 任何两个顶点之间都可能存在边,无法通过存储位置表示这种任意的逻辑关系。 图无法采用顺序存储结构。 2.如何存储图? 将顶点与边分开存储。 3.邻接矩阵(数组表示法) 基本思想: 用一个一维数…...

kubernetes--Pod控制器详解
目录 一、Pod控制器及其功用: 二、pod控制器的多种类型: 1、ReplicaSet: 1.1 ReplicaSet主要三个组件组成: 2、Deployment: 3、DaemonSet: 4、StatefulSet: 5、Job: 6、Cronjob: …...

九、Linux用户管理
1.基本介绍 Linux系统是一个多用户多任务的操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,让后以这个账号的身份进入系统 2.添加用户 基本语法 useradd 用户名 应用案例 案例1:添加一个用户 m…...

springboot项目中没有识别到yml文件解决办法
springboot项目中没有识别到yml文件解决办法 ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传] 1、这个意思就是没有配置数据库的数据源路径。所以需要配置数据源,比如mysql的驱动和路径。检查是否在properties或者yml文件中是否已经配置好。…...

[管理与领导-125]:一个IT人的思考:职场中、人际交往中,不要为他人的不良行为和言语买单,不要让自己的情绪被外界影响或掌控。
目录 前言: 一、是什么What 二、为什么Why? 三、怎么办How? 前言: 无论是职场中,还是人际交往中,我们的难免受到他人的影响,有积极正面的情绪影响,有消极负面的情绪影响。为什么我们自身的情绪会受到…...

【FPGA】IP核
一.IP核是什么 IP:知识产权,半导体产业中:在ASIC和FPGA中定义为预先设计好的电路功能模块。 在使用的时候其他用户可以直接调用IP核心。 二. 为什么要是有IP核 提高开发效率,减小设计和调试的时间,加速开发进程&am…...

吾爱破解置顶的“太极”,太好用了吧!
日常工作和娱乐,都需要用到不同类型的软件,哪怕软件体积不大,也必须安装,否则到用时找不到就非常麻烦了。 其实,很多软件不一定一样不剩地全部安装一遍,一方面原因是用的不多,另一方面多少有点…...

Postman接收列表、数组参数@RequestParam List<String> ids
示例如下: 接口定义如下: GetMapping(value "/queryNewMoviePath")public List<Map<String, Object>> queryNewMoviePath(RequestParam List<String> ids ) {return service.queryNewMoviePath(ids);}postman中测试如下: http://loc…...

qemu + busybox + 内核实验环境搭建(2023-11)
主要是参考网上的例子,网上的一些例子可能用的busybox 老旧,编译各种问题,以及rootfs hda的方式或者ramfs的方式。可能有些概念还是不清楚,以下是最终完成测试成功的案例。 下载kernel https://cdn.kernel.org/pub/linux/kernel…...

JavaScript管理HTMLDOM元素(增删改查)
本文主要讲解JavaScript如何通过管理HTML上的DOM元素,其中包括如何查询、创建、修改以及删除具体功能和源码讲解。 增加 首先我们准备一个HTML框架和简单CSS样式,我对其中元素作用和关系进行一个简单说明。 <!DOCTYPE html> <html><he…...

RE2文本匹配实战
引言 今天我们来实现RE2进行文本匹配,模型实现参考了官方代码https://github.com/alibaba-edu/simple-effective-text-matching-pytorch。 模型实现 RE2模型架构如上图所示。它的输入是两个文本片段,所有组件参数除了预测层和对齐层外都是共享的。上图…...

实在智能携手中国电信翼支付,全球首款Agent智能体亮相2023数字科技生态大会
11月10日-13日,中国电信与广东省人民政府联合主办的“2023数字科技生态大会”在广州隆重举行。本届大会以“数字科技焕新启航”为主题,邀请众多生态合作伙伴全方位展示数字科技新成果,包括数字新消费、产业数字化、智能电子、人工智能大模型等…...

安全框架springSecurity+Jwt+Vue-1(vue环境搭建、动态路由、动态标签页)
一、安装vue环境,并新建Vue项目 ①:安装node.js 官网(https://nodejs.org/zh-cn/) 2.安装完成之后检查下版本信息: ②:创建vue项目 1.接下来,我们安装vue的环境 # 安装淘宝npm npm install -g cnpm --registryhttps:/…...

React整理总结(三)
1.props和state的更新 父组件重新render时,所有的子组件也会调用render()函数。shouldComponentUpdate(nextProp, nextState) shouldComponentUpdate(nextProps, nextState) {if (equal(nextProps, this.props) && equa…...

天气这么好,都外出了。顺便了解一下漏桶算法
看到标题,你想到了些什么呢? 又是一个阳光明媚的周末,大家都外出了,路上到处堵车,尤其是各桥梁、隧道入口处,很多车排队等着进入,而出口处就像一个漏桶一样,一辆车接着一辆车有序且…...

【FPGA】Verilog:实现 RS 触发器 | Flip-Flop | 使用 NOR 的 RS 触发器 | 使用 NAND 的 RS 触发器
目录 0x00 RS 触发器(RS Flip-Flop) 0x01 实现 RS 触发器 0x02 使用 NOR 的 RS 触发器 0x03 使用 NAND 的 RS 触发器 0x00 RS 触发器(RS Flip-Flop) 触发器(Flip-Flop)是一种带有时钟的二进制存储设备…...

【技术追踪】SAM(Segment Anything Model)代码解析与结构绘制之Mask Decoder
论文:Segment Anything 代码:https://github.com/facebookresearch/segment-anything 系列篇: (1)【技术追踪】SAM(Segment Anything Model)代码解析与结构绘制之Image Encoder &am…...

认识Tomcat
文章目录 什么是tomcat?tomcat的使用tomcat的下载tomcat的目录结构tomcat的启动在tomcat上部署页面通过浏览器访问部署的页面 学习servlet的原因 什么是tomcat? 盖棺定论:Tomcat是一个HTTP服务器。 我们接下来要长期学习的东西都是关于前后…...

c语言通信之串口通信
在C语言中,可以使用串口通信、网络通信等多种方式实现计算机之间的通信。其中,串口通信通常用于近距离、低速率的通信,而网络通信则适用于远距离、高速率的通信。 下面以串口通信为例,介绍在C语言中如何实现串口通信。 1.打开串…...

软考-高级-系统架构设计师教程(清华第2版)【第16章 嵌入式系统架构设计理论与实践(P555~613)-思维导图】
软考-高级-系统架构设计师教程(清华第2版)【第16章 嵌入式系统架构设计理论与实践(P555~613)-思维导图】 课本里章节里所有蓝色字体的思维导图...