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

相关推荐
坐忘3GQ4 分钟前
65.Python-web框架-Django-免费模板django-datta-able的admin_datta
后端·python·django·templates·datta able·admin_datta
LightOfNight22 分钟前
【后端面试题】【中间件】【NoSQL】ElasticSearch的优化方案1(分页查询、刷新间隔、批量提交)
数据库·redis·后端·elasticsearch·缓存·中间件·nosql
eclipsercp27 分钟前
《每天5分钟用Flask搭建一个管理系统》 第7章:用户认证
后端·python·flask
经海路大白狗1 小时前
开启IT世界的第一步:高考新生的暑期学习指南
前端·后端·python·学习·高考
concisedistinct1 小时前
Perl 语言开发(三):运算符和表达式
开发语言·后端·perl
云艺卿尘2 小时前
Vue初体验——了解属性和指令
前端·vue.js·后端
北野桜桃2 小时前
使用 infisical 管理和同步环境变量
前端·后端
写bug写bug4 小时前
11个提高生产力的Go小技巧
后端·go
markix4 小时前
springboot 配置加密,jasypt加解密命令
java·spring boot·后端·jasypt
nbplus_0074 小时前
golang go-bindata打包配置文件嵌入到二进制文件
开发语言·后端·golang·个人开发·go配置文件