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

2022-01-08  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读