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

前言

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

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

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

创建项目

111.jpg

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

生成的项目结构

111.jpg

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

src 下编写代码

设计ui界面

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

111.jpg

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

111.jpg

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

111.jpg

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

111.jpg

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

核心代码

话不多说,先上代码

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的包路径他会找到生成窗口

111.jpg

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

111.jpg

效果

111.jpg

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

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

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

链接:pan.baidu.com/s/1brgYxQ4q… 提取码:n9f6

猜你喜欢

转载自juejin.im/post/7050291563498307598