IntelliJ IDE 插件开发 | (十一)解析 Mybatis 的控制台 SQL 日志

系列文章

本系列文章已收录到专栏,交流群号:689220994,也可点击链接加入。

前言

在前面的十个章节中主要介绍了关于插件开发的基础知识,从本节开始则会通过一个一个实战的 demo 样例来展示这些内容的综合运用,同时会偶尔夹杂一些进阶的理论内容。本文则是开发一个解析控制台中 MyBatis 的 SQL 日志,通过点击图标即可实现获取到完整 SQL 日志并复制到粘贴板中,效果如下图所示,另外本文所涉及到的完整代码也已上传到GitHub

实现思路

  1. 继承FilterConsoleFilterProvider来实现对控制台中的 SQL 文本进行处理。
  2. 继承EditorCustomElementRenderer实现在控制台中绘制图标。
  3. 继承InputHandler实现给 2 中绘制的图标添加点击事件,然后在点击事件中实现对 SQL 的解析。

增加控制台过滤器处理文本

这里先直接展示代码:

kotlin 复制代码
class MyBatisSQLFilter: Filter, ConsoleFilterProvider {

    override fun applyFilter(line: String, offset: Int): Filter.Result? {
        if (line.contains(Constants.SQL_PREFIX)) {
            return Filter.Result(listOf(
                MyBatisSQLResult(offset - line.length),
                Filter.ResultItem(0, 0, null)
            ))
        }
        return null
    }
    
    class MyBatisSQLResult(private val offset: Int) : Filter.Result(offset, offset, null), InlayProvider {
        override fun createInlayRenderer(editor: Editor): EditorCustomElementRenderer {
            return MybatisSQLRenderer(editor, offset)
        }
    }

    override fun getDefaultFilters(p0: Project): Array<Filter> {
        return arrayOf(this)
    }

}

其中applyFilter方法就是用来过滤需要处理文本,第一行的判断就是用来过滤行中包含==> Preparing:(MyBatis SQL 日志的固定格式)的内容,然后对文本行应用MybatisSQLRenderer这个自定义的渲染类,同时通过传递的 offset 参数就实现了在满足条件的文本行前添加图标。

在实现了该类后,还需要将其注册到plugin.xml中:

xml 复制代码
<extensions defaultExtensionNs="com.intellij">
	<consoleFilterProvider implementation="cn.butterfly.sqllog.filter.MyBatisSQLFilter" 
                           order="first"/>

</extensions>

过滤器使用扩展

控制台文本增加超链接样式

除了上述使用过滤器在控制台添加图标外,还可以通过使用过滤器给文本添加超链接样式,只需要将上述的applyFilter方法按如下修改:

kotlin 复制代码
override fun applyFilter(line: String, offset: Int): Filter.Result? {
    if (line.contains(Constants.SQL_PREFIX)) {
        return Filter.Result(listOf(MyBatisSQLResult(offset - line.length),
            Filter.ResultItem(offset - line.length, offset, HyperlinkInfo {  })// 修改行
        ))
    }
    return null
}

其中前两个参数用于控制超链接的起始范围,第三个参数则用于添加超链接样式,最终效果如下:

实现通义千问在异常后添加图标

这里先直接展示代码:

kotlin 复制代码
class ExceptionFilter: JvmExceptionOccurrenceFilter {
    
    override fun applyFilter(exceptionClassName: String, classes: MutableList<PsiClass>, offset: Int): Filter.ResultItem {
        return object : Filter.Result(offset, offset + exceptionClassName.length, null), InlayProvider { 
            override fun createInlayRenderer(editor: Editor): EditorCustomElementRenderer {
                return MybatisSQLRenderer(editor, offset)
            }}
    }

}

可以看到上述代码实现的的是JvmExceptionOccurrenceFilter接口,这个接口是 IDEA 专门提供用于处理控制台中产生的异常信息,除了代码实现外,还需要在plugin.xml中进行注册:

xml 复制代码
<extensions defaultExtensionNs="com.intellij">
	<jvm.exceptionFilter implementation="cn.butterfly.sqllog.filter.ExceptionFilter"
                         order="last"/>
</extensions>

最终效果如下图所示:

实现自定义渲染类并添加点击事件

这里先展示代码:

kotlin 复制代码
class MybatisSQLRenderer(private val editor: Editor, private val offset: Int): EditorCustomElementRenderer, InputHandler {

    /**
     * 用于判断鼠标是否在渲染的图标上停留
     */
    private var hovering = false
    
    override fun mouseMoved(event: MouseEvent, translated: Point) {
        if (hovering) return
        (editor as EditorImpl).setCustomCursor(this, Cursor.getPredefinedCursor(Cursor.HAND_CURSOR))
        hovering = true
    }

    override fun mouseExited() {
        hovering = false
        (editor as EditorImpl).setCustomCursor(this, null)
    }

    override fun mouseClicked(event: MouseEvent, translated: Point) {
        // 处理 SQL 日志和参数信息并将拼接结果保存到粘贴板中
        val doc = editor.document
        val sqlLineNumber = doc.getLineNumber(offset)
        val paramsLineNumber = sqlLineNumber + 1
        var sqlStr = getTextByLineNumber(sqlLineNumber)
        val paramsStr = getTextByLineNumber(paramsLineNumber)
        paramsStr.split(",").map {
            val param = it.trim()
            val value = param.substring(0, param.indexOf('('))
            val type = param.substring(param.indexOf('(') + 1, param.indexOf(')'))
            if (type == "String") "'$value'" else value
        }.forEach {
            sqlStr = sqlStr.replaceFirst("?", it)
        }
        val stringSelection = StringSelection(sqlStr)
        Toolkit.getDefaultToolkit().systemClipboard.setContents(stringSelection, null)
        Utils.info("复制成功")
    }

    /**
     * 获取指定行的日志信息
     */
    private fun getTextByLineNumber(lineNumber: Int): String {
        val doc = editor.document
        val start = doc.getLineStartOffset(lineNumber)
        val end = doc.getLineEndOffset(lineNumber)
        return doc.getText(TextRange(start, end)).substring(Constants.PARAMS_PREFIX.length)
    }

    override fun calcWidthInPixels(p0: Inlay<*>) = PluginIcons.MAPPER_ICON.iconWidth

    override fun calcHeightInPixels(inlay: Inlay<*>) = PluginIcons.MAPPER_ICON.iconHeight
    
    override fun paint(inlay: Inlay<*>, g: Graphics, r: Rectangle, textAttributes: TextAttributes) {
        val consoleIcon = PluginIcons.MAPPER_ICON
        val curX = r.x + r.width / 2 - consoleIcon.iconWidth / 2
        val curY = r.y + r.height / 2 - consoleIcon.iconHeight / 2
        if (curX >= 0 && curY >= 0) {
            consoleIcon.paintIcon(inlay.editor.component, g, curX, curY)
        }
    }

}

其中mouseMoved方法用于控制鼠标移入图标时将鼠标切换为手形图标,mouseExited则是在鼠标离开图标时恢复原始的样式,mouseClicked则是在鼠标点击图标时实现对 SQL 文本进行解析并复制到粘贴板中,这里要注意的是该方法内部是对如下格式的 SQL 日志进行解析的:

vbnet 复制代码
==> Preparing: SELECT id,name,age,email FROM user_demo WHERE (age >= ? AND name <> ?)
==> Parameters: 18(Integer), demo(String)

calcWidthInPixelscalcHeightInPixelspaint则是设置图标的宽高然后进行绘制。

以上代码就可以实现开篇所提到的功能,如果想要实现对 JPA 的 SQL 日志解析,也只需要修改上述mouseClicked中的逻辑即可。

总结

本文介绍了如何通过继承控制台过滤器类来实现自定义控制台的内容,然后以此来实现对 MyBatis SQL 日志的解析:根据打印的 SQL 和参数拼接得到完整的 SQL 然后保存到粘贴板中供用户使用。不过本文只是做简单处理,提供一个实现的思路,像 JPA 的 SQL 日志解析也是可以参考这种方式去实现,这里就不再介绍,如果有问题也欢迎在评论区或者讨论群进行交流讨论。

相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
2401_857610034 小时前
多维视角下的知识管理:Spring Boot应用
java·spring boot·后端
代码小鑫4 小时前
A027-基于Spring Boot的农事管理系统
java·开发语言·数据库·spring boot·后端·毕业设计
颜淡慕潇6 小时前
【K8S问题系列 | 9】如何监控集群CPU使用率并设置告警?
后端·云原生·容器·kubernetes·问题解决
独泪了无痕6 小时前
WebStorm 如何调试 Vue 项目
后端·webstorm
怒放吧德德7 小时前
JUC从实战到源码:JMM总得认识一下吧
java·jvm·后端
代码小鑫7 小时前
A025-基于SpringBoot的售楼管理系统的设计与实现
java·开发语言·spring boot·后端·毕业设计
前端SkyRain8 小时前
后端SpringBoot学习项目-项目基础搭建
spring boot·后端·学习
梦想画家8 小时前
理解Rust 生命周期、所有权和借用机制
开发语言·后端·rust
编程乐趣8 小时前
推荐一个.NetCore开源的CMS项目,功能强大、扩展性强、支持插件的系统!
后端