netty-websocket扩展协议及token鉴权补充

文章源码:gitee
源码部分可以看上一篇文章中的源码分析netty-websocket 鉴权token及统一请求和响应头(鉴权控制器)

最近刚好没事,看到有朋友说自定义协议好搞,我就想了想,发现上面那种方式实现确实麻烦,而且兼容性还不行,后来我对照着WebSocketServerProtocolHandler试了试扩展一下,将WebSocketServerProtocolHandler中handlerAdded添加的握手逻辑换成自己的,终于测通了,我用postman测试时,请求头也可以自定义,下面上代码

1.(userEventTriggered): 鉴权成功后可以抛出自定义事件,业务channel中实现 事件监听器userEventTriggered,这样就可以在鉴权成功后,握手成功前执行某个方法,比如验证权限啥的,具体可看SecurityHandler中的例子
2. (exceptionCaught): 异常捕获
3. channel设置attr实现channel上下文的数据属性
4. …等等

扩展WebSocketProtocolHandler

这个协议有很多私有方法外部引用不了,所以只能copy一份出来,主要是把handlerAdded这个方法重写了,将原有的‘WebSocketServerProtocolHandshakeHandler’替换为‘自己的(SecurityHandler)’

package com.chat.nettywebsocket.handler.test;

import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.Utf8FrameValidator;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.util.AttributeKey;

import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/***
 *
 * @author qb
 * @date 2024/2/5 8:53
 * @version 1.0
 */
public class CustomWebSocketServerProtocolHandler extends WebSocketServerProtocolHandler {

    public enum ServerHandshakeStateEvent {
        /**
         * The Handshake was completed successfully and the channel was upgraded to websockets.
         *
         * @deprecated in favor of {@link WebSocketServerProtocolHandler.HandshakeComplete} class,
         * it provides extra information about the handshake
         */
        @Deprecated
        HANDSHAKE_COMPLETE
    }

    /**
     * The Handshake was completed successfully and the channel was upgraded to websockets.
     */
    public static final class HandshakeComplete {
        private final String requestUri;
        private final HttpHeaders requestHeaders;
        private final String selectedSubprotocol;

        HandshakeComplete(String requestUri, HttpHeaders requestHeaders, String selectedSubprotocol) {
            this.requestUri = requestUri;
            this.requestHeaders = requestHeaders;
            this.selectedSubprotocol = selectedSubprotocol;
        }

        public String requestUri() {
            return requestUri;
        }

        public HttpHeaders requestHeaders() {
            return requestHeaders;
        }

        public String selectedSubprotocol() {
            return selectedSubprotocol;
        }
    }

    public CustomWebSocketServerProtocolHandler(String websocketPath, String subprotocols, boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch, boolean checkStartsWith) {
        super(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch, checkStartsWith);
        this.websocketPath = websocketPath;
        this.subprotocols = subprotocols;
        this.allowExtensions = allowExtensions;
        maxFramePayloadLength = maxFrameSize;
        this.allowMaskMismatch = allowMaskMismatch;
        this.checkStartsWith = checkStartsWith;
    }

    private final String websocketPath;
    private final String subprotocols;
    private final boolean allowExtensions;
    private final int maxFramePayloadLength;
    private final boolean allowMaskMismatch;
    private final boolean checkStartsWith;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        System.err.println("handlerAdded");
        ChannelPipeline cp = ctx.pipeline();
        if (cp.get(SecurityHandler.class) == null) {
            // Add the WebSocketHandshakeHandler before this one.
            // 增加协议实现handler
            ctx.pipeline().addBefore(ctx.name(), SecurityHandler.class.getName(),
                    new SecurityHandler(websocketPath, subprotocols,
                            allowExtensions, maxFramePayloadLength, allowMaskMismatch, checkStartsWith));
        }
        if (cp.get(Utf8FrameValidator.class) == null) {
            // Add the UFT8 checking before this one.
            ctx.pipeline().addBefore(ctx.name(), Utf8FrameValidator.class.getName(),
                    new Utf8FrameValidator());
        }
    }

    private static final AttributeKey<WebSocketServerHandshaker> HANDSHAKER_ATTR_KEY =
            AttributeKey.valueOf(WebSocketServerHandshaker.class, "HANDSHAKER");


    static WebSocketServerHandshaker getHandshaker(Channel channel) {
        return channel.attr(HANDSHAKER_ATTR_KEY).get();
    }

    static void setHandshaker(Channel channel, WebSocketServerHandshaker handshaker) {
        channel.attr(HANDSHAKER_ATTR_KEY).set(handshaker);
    }

    static ChannelHandler forbiddenHttpRequestResponder() {
        return new ChannelInboundHandlerAdapter() {
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                if (msg instanceof FullHttpRequest) {
                    ((FullHttpRequest) msg).release();
                    FullHttpResponse response =
                            new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.FORBIDDEN);
                    ctx.channel().writeAndFlush(response);
                } else {
                    ctx.fireChannelRead(msg);
                }
            }
        };
    }



}

SecurityHandler

复制的WebSocketServerProtocolHandshakeHandler的方法,就是改了请求头逻辑和发布事件的相关类调整

package com.chat.nettywebsocket.handler.test;

import com.chat.nettywebsocket.handler.test.CustomWebSocketServerProtocolHandler;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.ssl.SslHandler;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import static com.chat.nettywebsocket.handler.AttributeKeyUtils.SECURITY_CHECK_COMPLETE_ATTRIBUTE_KEY;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
import static io.netty.handler.codec.http.HttpUtil.isKeepAlive;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/***
 *
 * @author qb
 * @date 2024/2/5 8:37
 * @version 1.0
 */
@Slf4j
@ChannelHandler.Sharable
public class SecurityHandler extends ChannelInboundHandlerAdapter {


    private final String websocketPath;
    private final String subprotocols;
    private final boolean allowExtensions;
    private final int maxFramePayloadSize;
    private final boolean allowMaskMismatch;
    private final boolean checkStartsWith;

    SecurityHandler(String websocketPath, String subprotocols,
                                            boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch) {
        this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch, false);
    }

    SecurityHandler(String websocketPath, String subprotocols,
                                            boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch, boolean checkStartsWith) {
        this.websocketPath = websocketPath;
        this.subprotocols = subprotocols;
        this.allowExtensions = allowExtensions;
        maxFramePayloadSize = maxFrameSize;
        this.allowMaskMismatch = allowMaskMismatch;
        this.checkStartsWith = checkStartsWith;
    }

    @Override
    public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
        final FullHttpRequest req = (FullHttpRequest) msg;
        if (isNotWebSocketPath(req)) {
            ctx.fireChannelRead(msg);
            return;
        }
        try {
            if (req.method() != GET) {
                sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN));
                return;
            }
            // 比如 此处极权成功就抛出成功事件
            SecurityCheckComplete complete = new SecurityHandler.SecurityCheckComplete(true);
            // 设置 channel属性,相当于channel固定的上下文属性
            ctx.channel().attr(SECURITY_CHECK_COMPLETE_ATTRIBUTE_KEY).set(complete);
            ctx.fireUserEventTriggered(complete);
            final WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                    getWebSocketLocation(ctx.pipeline(), req, websocketPath), subprotocols,
                    allowExtensions, maxFramePayloadSize, allowMaskMismatch);
            final WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req);
            if (handshaker == null) {
                WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
            } else {
                String s = req.headers().get("Sec-token");
                HttpHeaders httpHeaders = null;
                if(StringUtils.hasText(s)){
                    httpHeaders = new DefaultHttpHeaders().add("Sec-token",s);
                }else {
                    httpHeaders = new DefaultHttpHeaders();
                }
                // 设置请求头
                final ChannelFuture handshakeFuture = handshaker.handshake(ctx.channel(),req, httpHeaders,ctx.channel().newPromise());
                System.err.println("handshakeFuture: "+handshakeFuture.isSuccess());
                handshakeFuture.addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            ctx.fireExceptionCaught(future.cause());
                        } else {
                            // Kept for compatibility
                            ctx.fireUserEventTriggered(
                                    CustomWebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE);
                            ctx.fireUserEventTriggered(
                                    new CustomWebSocketServerProtocolHandler.HandshakeComplete(
                                            req.uri(), req.headers(), handshaker.selectedSubprotocol()));
                        }
                    }
                });
                CustomWebSocketServerProtocolHandler.setHandshaker(ctx.channel(), handshaker);
                ctx.pipeline().replace(this, "WS403Responder",
                        CustomWebSocketServerProtocolHandler.forbiddenHttpRequestResponder());
            }
        } finally {
            req.release();
        }
    }

    private boolean isNotWebSocketPath(FullHttpRequest req) {
        return checkStartsWith ? !req.uri().startsWith(websocketPath) : !req.uri().equals(websocketPath);
    }

    private static void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if (!isKeepAlive(req) || res.status().code() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

    private static String getWebSocketLocation(ChannelPipeline cp, HttpRequest req, String path) {
        String protocol = "ws";
        if (cp.get(SslHandler.class) != null) {
            // SSL in use so use Secure WebSockets
            protocol = "wss";
        }
        String host = req.headers().get(HttpHeaderNames.HOST);
        return protocol + "://" + host + path;
    }


    // 自定义事件实体
    @Getter
    @AllArgsConstructor
    public static final class SecurityCheckComplete {


        private Boolean isLogin;

    }
}


ChatHandler

package com.chat.nettywebsocket.handler;

import com.alibaba.fastjson.JSONObject;
import com.chat.nettywebsocket.domain.Message;
import com.chat.nettywebsocket.handler.test.CustomWebSocketServerProtocolHandler;
import com.chat.nettywebsocket.handler.test.SecurityHandler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.StandardCharsets;

/**
 * 自定义控制器
 * @author qubing
 * @date 2021/8/16 9:26
 */
@Slf4j
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    /**
     * 为channel添加属性  将userid设置为属性,避免客户端特殊情况退出时获取不到userid
     */
    AttributeKey<Integer> userid = AttributeKey.valueOf("userid");

    /**
     * 连接时
     * @param ctx 上下文
     * @throws Exception /
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        log.info("与客户端建立连接,通道开启!");

        // 添加到channelGroup通道组
        MyChannelHandlerPool.channelGroup.add(ctx.channel());
    }

    /**
     * 断开连接时
     * @param ctx /
     * @throws Exception /
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("与客户端断开连接,通道关闭!");
        // 从channelGroup通道组移除
//        MyChannelHandlerPool.channelGroup.remove(ctx.channel());
//        Integer useridQuit = ctx.channel().attr(userid).get();
//        MyChannelHandlerPool.channelIdMap.remove(useridQuit);
        log.info("断开的用户id为");
    }

    // 监听事件
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // 自定义鉴权成功事件
        if (evt instanceof SecurityHandler.SecurityCheckComplete){
            // 鉴权成功后的逻辑
            log.info("鉴权成功  SecurityHandler.SecurityCheckComplete");

        }
        // 握手成功
        else if (evt instanceof CustomWebSocketServerProtocolHandler.HandshakeComplete) {
            log.info("Handshake has completed");
            // 握手成功后的逻辑  鉴权和不鉴权模式都绑定channel
            log.info("Handshake has completed after binding channel");
        }
        super.userEventTriggered(ctx, evt);
    }

    /**
     * 获取消息时
     * @param ctx /
     * @param msg 消息
     * @throws Exception /
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        String mssage = msg.content().toString(StandardCharsets.UTF_8);
        ctx.channel().writeAndFlush(mssage);
        System.err.println(mssage);
    }


    /**
     * 群发所有人
     */
    private void sendAllMessage(String message){
        //收到信息后,群发给所有channel
        MyChannelHandlerPool.channelGroup.writeAndFlush( new TextWebSocketFrame(message));

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.info("exceptionCaught 异常:{}",cause.getMessage());
        cause.printStackTrace();
        Channel channel = ctx.channel();
        //……
        if(channel.isActive()){
            log.info("手动关闭通道");
            ctx.close();
        };
    }
}

AttributeKeyUtils

public class AttributeKeyUtils {

    /**
     * 为channel添加属性  将userid设置为属性,避免客户端特殊情况退出时获取不到userid
     */
    public static final AttributeKey<String> USER_ID = AttributeKey.valueOf("userid");

    public static final AttributeKey<SecurityHandler.SecurityCheckComplete> SECURITY_CHECK_COMPLETE_ATTRIBUTE_KEY =
            AttributeKey.valueOf("SECURITY_CHECK_COMPLETE_ATTRIBUTE_KEY");


}

WsServerInitializer

@Slf4j
@ChannelHandler.Sharable
public class WsServerInitializer extends ChannelInitializer<SocketChannel> {

//    @Override
//    protected void initChannel(SocketChannel socketChannel) throws Exception {
//        log.info("有新的连接");
//        ChannelPipeline pipeline = socketChannel.pipeline();
//        //netty 自带的http解码器
//        pipeline.addLast(new HttpServerCodec());
//        //http聚合器
//        pipeline.addLast(new HttpObjectAggregator(8192));
//        pipeline.addLast(new ChunkedWriteHandler());
//        //压缩协议
//        pipeline.addLast(new WebSocketServerCompressionHandler());
//        //http处理器 用来握手和执行进一步操作
        pipeline.addLast(new NettyWebsocketHttpHandler(config, listener));
//
//    }

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        log.info("有新的连接");
        //获取工人所要做的工程(管道器==管道器对应的便是管道channel)
        ChannelPipeline pipeline = ch.pipeline();
        //为工人的工程按顺序添加工序/材料 (为管道器设置对应的handler也就是控制器)
        //1.设置心跳机制
        pipeline.addLast(new IdleStateHandler(5,0,0, TimeUnit.SECONDS));
        //2.出入站时的控制器,大部分用于针对心跳机制
        pipeline.addLast(new WsChannelDupleHandler());
        //3.加解码
        pipeline.addLast(new HttpServerCodec());
        //3.打印控制器,为工人提供明显可见的操作结果的样式
        pipeline.addLast("logging", new LoggingHandler(LogLevel.INFO));
        pipeline.addLast(new ChunkedWriteHandler());
        pipeline.addLast(new HttpObjectAggregator(8192));
        // 扩展的websocket协议
        pipeline.addLast(new CustomWebSocketServerProtocolHandler(
                "/ws","websocket",true,65536 * 10,false,true));
        //7.自定义的handler针对业务
        pipeline.addLast(new ChatHandler());

    }
}

上截图

postman测试怎增加自定义请求头

在这里插入图片描述

点击链接查看控制台

postman链接成功
在这里插入图片描述
根据日志可以看出,链接成功并且相应和请求的头是一致的
在这里插入图片描述

发送消息

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/379488.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 笔记 003 Bean注册

使用Idea导入第三方jar包 在porn.xml种添加的第三方jar包依赖&#xff0c;并刷新 可以在启动类中尝试调用 以上放到启动类中&#xff0c;不推荐&#xff0c;建议创建一个专门定义的类 package com.geji.config;import cn.itcast.pojo.Country; import cn.itcast.pojo.Province;…

python烟花绘制,春节祝福

春节将至&#xff0c;写一个烟花程序给亲近的人 核心逻辑 烟花类&#xff1a; 定义烟花的颜色&#xff0c;更新烟花的轨迹&#xff0c;爆炸&#xff0c;消失等功能&#xff0c;在烟花爆炸的同时也涉及到粒子的创建 class Firework:def __init__(self):# 随机颜色self.colou…

深度优先搜索(DFS):探索图与树的深度之旅

引言 在图论和计算机科学中&#xff0c;深度优先搜索&#xff08;DFS&#xff09;是一种用于遍历或搜索树或图的算法。与广度优先搜索&#xff08;BFS&#xff09;不同&#xff0c;DFS沿着树的深度遍历树的节点&#xff0c;尽可能深地搜索树的分支。在图中&#xff0c;这种策略…

文心一言 VS 讯飞星火 VS chatgpt (197)-- 算法导论14.3 5题

五、用go语言&#xff0c;对区间树 T 和一个区间 i &#xff0c;请修改有关区间树的过程来支持新的操作 INTERVALSEARCH-EXACTLY(T&#xff0c;i) &#xff0c;它返回一个指向 T 中结点 x 的指针&#xff0c;使得 x.int. lowi.low 且 x.int.high i.high ;或者&#xff0c;如果…

华为第二批难题五:AI技术提升六面体网格生成自动化问题

有CAE开发商问及OCCT几何内核的网格方面的技术问题。其实&#xff0c;OCCT几何内核的现有网格生成能力比较弱。 HybridOctree_Hex的源代码&#xff0c;还没有仔细去学习。 “HybridOctree_Hex”的开发者说&#xff1a;六面体网格主要是用在数值模拟领域的&#xff0c;比如汽车…

LabVIEW网络测控系统

LabVIEW网络测控系统 介绍了基于LabVIEW的网络测控系统的开发与应用&#xff0c;通过网络技术实现了远程的数据采集、监控和控制。系统采用LabVIEW软件与网络通信技术相结合&#xff0c;提高了系统的灵活性和扩展性&#xff0c;适用于各种工业和科研领域的远程测控需求。 随着…

8个简约精美的WordPress外贸网站主题模板

Simplify WordPress外贸网站模板 Simplify WordPress外贸网站模板&#xff0c;简洁实用的外贸公司wordpress外贸建站模板。 查看演示 Invisible Trade WP外贸网站模板 WordPress Invisible Trade外贸网站模板&#xff0c;做进出口贸易公司官网的wordpress网站模板。 查看演…

微信小程序(基本操作)

概念&#xff1a; 小程序&#xff1a;就是小程序&#xff0c;mini program。现在市面上有微信小程序&#xff0c;百度智能小程序等等。 微信小程序&#xff0c;简称小程序&#xff0c;英文名Mini Program&#xff0c;是一种不需要下载安装即可使用的应用&#xff0c;它实现了…

Python进程之串行与并行

串行和并行 串行指的是任务的执行方式。串行在执行多个任务时&#xff0c;各个任务按顺序执行&#xff0c;完成一个之后才能进行下一个。&#xff08;早期单核CPU的情况下&#xff09; 并行指的是多个任务在同一时刻可以同时执行&#xff08;前提是多核CPU&#xff09;&#…

【蓝桥杯单片机记录】IO基础与LED控制

目录 一、IO基础 1.1 IAP15F2K61S2芯片原理图 1.2不同工作模式 二、新建工程的一些补充 2.1 keil中没有IAP15F2K61S2的头文件 解决&#xff1a;在isp软件中找到如下​编辑 2.2keil中的芯片选择 2.3推荐字体 三、sbit关键字 四、LED控制 4.1原理图 4.2不能直接通过IO…

电脑多出一个虚拟驱动器又无法删除怎么办

下载解压UltraISO https://wwb.lanzoum.com/i8vY71nqnp4d 右键UltraISO.exe以管理员身份运行 点击选项 点击配置 91fddbd892a0.png) 点击虚拟光驱&#xff0c;把设备数量改成无&#xff0c;点击确定即可

Spring是怎么解决循环依赖的

首先先解释一下什么叫循环依赖 循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环.比如A依赖于B,B依赖于A 循环依赖在spring中是允许存在的,spring框架依据三级缓存已经解决了大部分的循环依赖 一级缓存:单例池,缓存已经经历了完整的…

门诊单据打印用什么软件,线下处方单生成系统教程

门诊单据打印用什么软件&#xff0c;线下处方单生成系统教程 一、前言 以下软件教程以 佳易王诊所电子处方管理系统软件V17.3为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 如上图&#xff0c;电子处方或病历记录开单生成保存后&#xff0c;可…

3分钟部署完成Docker Registry及可视化管理工具Docker-UI

安装docker-registry 由于镜像文件会非常占用空间&#xff0c;因此需要选择一个磁盘充裕的位置来存放镜像数据。 这里设置为&#xff1a;-v /data/registry:/var/lib/registry&#xff0c;其中/data/registry是宿主机存放数据的位置。 docker run -d -p 5000:5000 --restart…

95.网游逆向分析与插件开发-游戏窗口化助手-窗口化助手显示与大小调整

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;地图数据获取的逆向分析与C代码还原 码云地址&#xff08;游戏窗口化助手 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&#xff1a;e85c0fc8b85895c8c…

深度分析一款新型Linux勒索病毒

前言 DarkRadiation勒索病毒是一款全新的Linux平台下的勒索病毒&#xff0c;2021年5月29日首次在某平台上发布了此勒索病毒的相关的信息&#xff0c;6月中旬趋势科技针对这个新型的勒索病毒进行了相关的分析和报道。 DarkRadiation勒索病毒采用Bash脚本语言编写实现&#xff0…

运维高级篇-分库分表(拆分策略详解)

分库分表 介绍 问题分析 随着互联网及移动互联网的发展&#xff0c;应用系统的数据量也是成指数式增长&#xff0c;若采用单数据库进行数据存 储&#xff0c;存在以下性能瓶颈&#xff1a; IO瓶颈&#xff1a;热点数据太多&#xff0c;数据库缓存不足&#xff0c;产生大量磁盘…

Leetcode 30天高效刷数据结构和算法 Day1 两数之和 —— 无序数组

两数之和 —— 无序数组 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现…

SCI 1区论文:Segment anything in medical images(MedSAM)[文献阅读]

基本信息 标题&#xff1a;Segment anything in medical images中文标题&#xff1a;分割一切医学图像发表年份: 2024年1月期刊/会议: Nature Communications分区&#xff1a; SCI 1区IF&#xff1a;16.6作者: Jun Ma; Bo Wang(一作&#xff1b;通讯)单位&#xff1a;加拿大多…

「Mybatis实战五」:Mybatis核心文件详解 - MyBatis常用配置environments、properties

一、MyBatis核心配置文件层级关系 ​ 本文代码在 Mybatis初体验&#xff1a;一小时从入门到运行你的第一个应用 所构建的基础代码结构之上&#xff0c;进行修改。 MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下&#xff1a; 二…