1. 首页
  2. 后端

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

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

==============================================

系列文章

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

前言

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

动画

实现思路

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

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

这里先直接展示代码:

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中:

<extensions defaultExtensionNs="com.intellij">
    <consoleFilterProvider implementation="cn.butterfly.sqllog.filter.MyBatisSQLFilter" 
                           order="first"/>

</extensions>

过滤器使用扩展

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

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

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
}

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

image-20240629135225924

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

这里先直接展示代码:

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中进行注册:

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

最终效果如下图所示:

image-20240629141253162

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

这里先展示代码:

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 日志进行解析的:

==> 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 日志解析也是可以参考这种方式去实现,这里就不再介绍,如果有问题也欢迎在评论区或者讨论群进行交流讨论。

原文链接: https://juejin.cn/post/7385215732818722850

文章收集整理于网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除,如若转载,请注明出处:http://www.cxyroad.com/17712.html

QR code