美文网首页
基于Netty手写一个远程连接Redis的IDEA插件

基于Netty手写一个远程连接Redis的IDEA插件

作者: 谁叫我土豆了 | 来源:发表于2022-01-08 16:07 被阅读0次

    前言

    前几天一直在学习Netty框架,写了几个Demo,然后就想着可以用它来写点什么,然后又对编写idea的插件有点兴趣,那么就准备写一个idea插件.

    写什么好呢,想起可以写一个Redis连接客户端的插件,这个也可以用上Netty,虽然市面上已经有很多redis的客户端,例如:Redis Desktop Manager这类的,不过很多是付费的,想白嫖需要找破解版,自己写的功能上虽然简陋,不过胜在使用方便,不用另开程序.很多时候也仅仅是想看看redis里数据有没有保存上,所以也够用了.

    想要完成这个插件 需要掌握Netty和java Gui的一些知识,如果你完全不了解的话可以先看一下这方面的内容

    创建项目

    可以看到idea可以直接选择创建插件项目

    生成的项目结构

    里面会有一个 plugin.xml文件,这个是插件的一个重要配置文件

    src 下编写代码

    设计ui界面

    我们要写一个侧边的工具窗口,那么就需要界面布局,idea里使用 swingUi

    按照图上选择 就会生成一个ui编辑器

    只需从右侧拖拽到中间的框内就可以完成ui布局,你完成的布局他会同时为你生成一个对应的实体类 你想为哪个组件生成实体类中的字段就要在 field name 这里指定字段名称

    最终会生成如下图的一个实体类

    然后就可以在实体类中编写业务代码了

    核心代码

    话不多说,先上代码

    public class RedisCliUi {
    
        //这里都是生成的组件字段
    
        private JButton connectButton;
        private JTextField portText;
        private JTextField commandText;
        private JButton commandButton;
        private JTextField ipText;
        private JTextArea textArea;
        private JLabel ipLabel;
        private JLabel portLabel;
        private JLabel commandLabel;
        private JPanel redisPanel;
        private JButton cleanButton;
        private JButton closeButton;
        private JScrollPane scrollPane;
    
        static String line = "\r\n";
    
        static ChannelHandlerContext Context;
    
        //实体类构造
        public RedisCliUi(Project project, ToolWindow toolWindow) {
            // 连接按钮监听
            connectButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (StringUtils.isNotBlank(ipText.getText()) && StringUtils.isNotBlank(portText.getText())) {
                        new Thread(() -> {
                            connect(ipText.getText().trim(), portText.getText().trim());
                        }).start();
    
                    }
                }
            });
            // 命令按钮监听
            commandButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    Context.writeAndFlush(getRedisMessage(Context, commandText.getText()));
                }
            });
            // 清空按钮监听
            cleanButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    textArea.setText("");
                }
            });
            // 关闭连接按钮监听
            closeButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    Context.close();
                }
            });
            // 文本框回车事件监听
            textArea.addKeyListener(new KeyAdapter() {
                @Override
                public void keyTyped(KeyEvent e) {
                    if ((char) e.getKeyChar() == KeyEvent.VK_ENTER) {
                        String text = textArea.getText();
                        System.out.println(text);
                        String[] split = text.split("\n");
                        String s = split[split.length - 1];
                        System.out.println(s);
                        Context.writeAndFlush(getRedisMessage(Context, s));
                    }
                }
            });
            // 端口号输入框回车事件监听
            portText.addKeyListener(new KeyAdapter() {
                @Override
                public void keyTyped(KeyEvent e) {
                    if ((char) e.getKeyChar() == KeyEvent.VK_ENTER) {
                        if (StringUtils.isNotBlank(ipText.getText()) && StringUtils.isNotBlank(portText.getText())) {
                            new Thread(() -> {
                                connect(ipText.getText().trim(), portText.getText().trim());
                            }).start();
    
                        }
                    }
                }
            });
        }
    
        // 整个ui最外层的panel提供get方法
        public JPanel getRedisPanel() {
            return redisPanel;
        }
    
        // 通过netty客户端连接Redis方法
        // 这里是netty的客户端代码
        public void connect(String ip, String port) {
            NioEventLoopGroup group = new NioEventLoopGroup();
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            ChannelPipeline pipeline = channel.pipeline();
                            // pipeline.addLast(new LoggingHandler(LogLevel.INFO));
                            pipeline.addLast(new RedisDecoder());
                            pipeline.addLast(new RedisBulkStringAggregator());
                            pipeline.addLast(new RedisArrayAggregator());
                            pipeline.addLast(new RedisEncoder());
                            pipeline.addLast(new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                    System.out.println("连接redis 成功");
                                    textArea.append("连接redis 成功");
                                    textArea.append(line);
                                    Context = ctx;
                                }
    
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    RedisMessage redisMessage = (RedisMessage) msg;
                                    // 打印响应消息
                                    printAggregatedRedisResponse(redisMessage);
                                }
    
                                @Override
                                public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                                    ctx.close();
                                    textArea.append("连接已关闭");
                                    textAreaFocus(textArea, line);
                                }
    
                                @Override
                                public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                                    cause.printStackTrace();
                                    ctx.close();
                                    textArea.append("连接出现异常已关闭");
                                    textAreaFocus(textArea, line);
                                }
                            });
                        }
                    });
            try {
                ChannelFuture channelFuture = bootstrap.connect(ip, Integer.parseInt(port)).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
                textArea.append("Redis连接 失败,请输入正确的ip与端口号");
                textAreaFocus(textArea, line);
            } finally {
                group.shutdownGracefully();
            }
    
        }
    
        // 处理redis返回数据
        private void printAggregatedRedisResponse(RedisMessage msg) {
    
            if (msg instanceof SimpleStringRedisMessage) {
                System.out.println(((SimpleStringRedisMessage) msg).content());
                textArea.append(((SimpleStringRedisMessage) msg).content());
                textAreaFocus(textArea, line);
            } else if (msg instanceof ErrorRedisMessage) {
                System.out.println(((ErrorRedisMessage) msg).content());
                textArea.append(((ErrorRedisMessage) msg).content());
                textAreaFocus(textArea, line);
            } else if (msg instanceof IntegerRedisMessage) {
                System.out.println(((IntegerRedisMessage) msg).value());
                textArea.append(String.valueOf(((IntegerRedisMessage) msg).value()));
                textAreaFocus(textArea, line);
            } else if (msg instanceof FullBulkStringRedisMessage) {
                System.out.println(getString((FullBulkStringRedisMessage) msg));
                textArea.append(getString((FullBulkStringRedisMessage) msg));
                textAreaFocus(textArea, line);
            } else if (msg instanceof ArrayRedisMessage) {
                for (RedisMessage child : ((ArrayRedisMessage) msg).children()) {
                    printAggregatedRedisResponse(child);
                }
            } else {
                throw new CodecException("unknown message type: " + msg + "\r\n");
            }
        }
    
        private static String getString(FullBulkStringRedisMessage msg) {
            if (msg.isNull()) {
                return "(null)";
            }
            return msg.content().toString(CharsetUtil.UTF_8);
        }
    
        // 处理文本框光标位置在最后一行
        public void textAreaFocus(JTextArea textArea, String line) {
            textArea.append(line);
            textArea.selectAll();
            textArea.setCaretPosition(textArea.getSelectedText().length());
            textArea.requestFocus();
        }
    
        // 将字符串处理成redis可以读取的消息
        public static RedisMessage getRedisMessage(ChannelHandlerContext ctx, String str) {
            // 匹配字符中空格分隔
            String[] commands = str.split("\s+");
            List<RedisMessage> children = new ArrayList<>(commands.length);
            for (String cmdString : commands) {
                children.add(new FullBulkStringRedisMessage(ByteBufUtil.writeUtf8(ctx.alloc(), cmdString)));
            }
            return new ArrayRedisMessage(children);
        }
    }
    

    上面代码的逻辑就是实体类开始构造会把事件监听器加载进去,通过connect()方法把输入的ip与端口号传入进去,通过netty与redis建立连接

    channelRead()方法读取redis返回的数据

    通过发送命令的按钮监听 和文本域的回车事件监听,给redis发送消息

    生成窗口

    要想生成窗口还需要实现 ToolWindowFactory接口的 createToolWindowContent方法

    public class RedisClientFactory implements ToolWindowFactory {
    
        // 生成右侧工具窗口
        @Override
        public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
            RedisCliUi redisClientUi = new RedisCliUi(project,toolWindow);
            // 获取内容工厂的实例
            ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
            // 获取 ToolWindow 显示的内容
            Content content = contentFactory.createContent(redisClientUi.getRedisPanel(), "", false);
            // 设置 ToolWindow 显示的内容
            toolWindow.getContentManager().addContent(content);
    
        }
    }
    

    redisPanel 是ui最外层的panel给他一个get方法 传给内容工场,他会把ui里所有的内容加载出来

    还需要配置 plugin.xml 把我们的窗口配置进去

    <extensions defaultExtensionNs="com.intellij">
      <!-- Add your extensions here -->
      <toolWindow id="RedisClient" secondary="false"  anchor="right"  factoryClass="window.RedisClientFactory"/>
    </extensions>
    

    id 只要不重复就可以 anchor 指定右边,就是工具在右侧边栏固定 factoryClass 填写RedisClientFactory的包路径他会找到生成窗口

    点击buiud 按上图 会生成插件的zip包 就可以本地安装插件了

    效果

    可以在Redis命令文本框发送命令,也可以直接在文本域发送命令 基本使用效果和在linux上使用redis_cli差不多

    到此这个插件的开发就完成了,很多地方还不太完美大家见谅.

    插件已上传到网盘,有兴趣的可以自己使用一下

    作者:徐小白
    链接:
    https://juejin.cn/post/7050291563498307598

    相关文章

      网友评论

          本文标题:基于Netty手写一个远程连接Redis的IDEA插件

          本文链接:https://www.haomeiwen.com/subject/eqjscrtx.html