idea插件开发——Generate Resource SQL
插件预览:
一、开发环境配置
1、idea社区版(Community Edition)
2、IntelliJ Plateform Plugin SDK
3、安装Plugin Devkit插件
在项目Project Structure添加Intellij IDEA SDK
二、开发插件
新建项目,选择Intellij Platform Plugin,SDK选择刚才添加的IDEA SDK,然后点击next
默认项目结构如下:
src表示插件代码目录,resources表示插件资源目录,plugin.xml为插件的描述文件,和一些配置信息
plugin.xml文件默认如下:
<idea-plugin> <id>com.your.company.unique.plugin.id</id> <name>Plugin display name here</name> <version>1.0</version> <vendor email="support@yourcompany.com" url="http://www.yourcompany.com">YourCompany</vendor> <description><![CDATA[ Enter short description for your plugin here.<br> <em>most HTML tags may be used</em> ]]></description> <change-notes><![CDATA[ Add change notes here.<br> <em>most HTML tags may be used</em> ]]> </change-notes> <!-- please see https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html for description --> <idea-version since-build="173.0"/> <!-- please see https://plugins.jetbrains.com/docs/intellij/plugin-compatibility.html on how to target different products --> <depends>com.intellij.modules.platform</depends> <extensions defaultExtensionNs="com.intellij"> <!-- Add your extensions here --> </extensions> <actions> <!-- Add your actions here --> </actions> </idea-plugin>
新创建会看到description和change-notes内容报红,可不用管,修改内容之后会恢复正常,
其中id表示插件唯一的id,不可与其他插件冲突,插件不同版本之间不可修改
name表示插件的名称,发版成功别人可以在插件市场根据名称进行搜索
version 插件的版本号
vendor 插件的供应商 也就是作者名称
description 插件的描述,不能使用默认值,必须修改成自己的,并且需要大于40字符
change-notes 插件的修改日志,支持html标签
然后开始创建一个action,如果安装了Devki插件,可以快速生成Action,在new的时候选择Plugin Devkit Action
点击可以进行快速创建Action,其中Name表示Action的name,这里Group选择EditorPopupMenu表示右击出现GenerateResource选项。下面KeyBoard Shortcuts表示触发的快捷键,这里除了右击出现GenerateResource会触发外我们可以使用快捷键Ctrl S+B.
点击OK,会自动创建一个类继承AnAction,重写方法actionPerforned表示触发之后执行的操作。我们需要在这里编写代码
在plugin.xml会自动添加Action的配置信息
然后开始编写actionPerformed方法,比如这里我们在执行操作之后输出一条信息
@Override public void actionPerformed(AnActionEvent e) { Editor editor = e.getData(PlatformDataKeys.EDITOR); Messages.showMessageDialog(editor.getProject(), "输出一条提示信息", "提示", Messages.getInformationIcon()); }
三、调试、部署
编写完成,需要进行测试,跟正常java代码一样。我们可以debug
点击run或者debug来启动插件项目
启动完成,会重新打开idea的一个窗口,在新开的窗口可以调试自己的插件,
这里我们右击编辑窗口,可以看到刚才添加的action
点击可以看到输出一条信息
开发完成,需要我们打包供自己或别人使用
点击上方菜单build -> Prepare Plugin Module xxx For Deployment。可以在项目生成一个插件的jar包
在使用时,可以在plugins选择从磁盘安装刚才的插件,导入生成的jar包重启idea可使用
当需要发布到插件市场别人可以搜索到时,我们需要注册jetbrains账号,点击upload plugin
https://plugins.jetbrains.com/plugin/add#intellij
需要等待1-2个工作日等待审核通过就可以在插件市场搜索到了
四、开发插件
在许多项目中,需要将接口的地址放入resource数据库的表中。来进行细粒度的权限控制,类似这种,需要在resource表中添加资源url,资源描述,资源名称等字段,而这个url对应controller的@RequestMapping的value值,需要我们一个一个复制并手动书写插入的sql语句,在实际开发中,我们无需做这种额外的费时费力的重复无用操作,可以将精力放到其他工作中,所以我们可以开发一个插件,来自动完成这些操作,来输出数据库的脚本。
实现思路:获取@RequestMapping(@GetMapping、@PostMapping、@PutMapping、@DeleteMapping)注解的value值,也就是访问时的url,资源名称(RES_XXX)我们可以将url进行大小写转换,并缩短至数据库规定大小来进行改造。资源描述:在一般开发中,我们应该按照规范在每个接口上填写注释,所以我们可以获取到每个方法上的注释,并进行简单的匹配以及分割就可以得到这个方法的描述,也就是资源描述的信息。代码如下:
package com.liufuqiang.packages; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.ui.Messages; import com.intellij.psi.*; import com.intellij.psi.util.PsiUtilBase; import org.apache.commons.lang3.StringUtils; import javax.swing.tree.DefaultMutableTreeNode; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @date 2021/10/21 * @author liufuqiang */ public class GenerateResourceAction extends AnAction { private static final String PREFIX = "/"; @Override public void actionPerformed(AnActionEvent event) { Editor editor = event.getData(PlatformDataKeys.EDITOR); PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, editor.getProject()); //只读文件直接返回 if( psiFile.getFileType().isReadOnly()){ return; } String fileName = psiFile.getVirtualFile().getName(); // 判断文件后缀是不是Controller String fileSuffix = "Controller.java"; if (!fileName.endsWith(fileSuffix)) { return; } String baseUrl = ""; Document document = PsiDocumentManager.getInstance(event.getProject()).getDocument(psiFile); DefaultMutableTreeNode fileNode = new DefaultMutableTreeNode(fileName); for (PsiElement psiElement : psiFile.getChildren()) { if (psiElement instanceof PsiClass){ // 获取类上面的RequestMapping注解信息 PsiClass psiClass = (PsiClass) psiElement; for (PsiAnnotation annotation : psiClass.getAnnotations()) { if (StringUtils.equals(annotation.getQualifiedName(), "org.springframework.web.bind.annotation.RequestMapping")) { baseUrl = annotation.findAttributeValue("value").getText().replaceAll("\"", "").trim(); } } if (StringUtils.isNotBlank(baseUrl) && !baseUrl.startsWith("/")) { baseUrl = PREFIX.concat(baseUrl); } // 方法列表 List<Map<String, String>> resourceList = new ArrayList<>(20); PsiMethod[] methods = psiClass.getMethods(); for (PsiMethod method : methods) { PsiAnnotation[] annotations = method.getAnnotations(); for (PsiAnnotation annotation : annotations) { String qualifiedName = annotation.getQualifiedName(); if (!StringUtils.equals(qualifiedName, "org.springframework.web.bind.annotation.RequestMapping") && !StringUtils.equals(qualifiedName, "org.springframework.web.bind.annotation.GetMapping") && !StringUtils.equals(qualifiedName, "org.springframework.web.bind.annotation.PostMapping") && !StringUtils.equals(qualifiedName, "org.springframework.web.bind.annotation.PutMapping") && !StringUtils.equals(qualifiedName, "org.springframework.web.bind.annotation.DeleteMapping")) { continue; } Map<String, String> params = new HashMap<>(3); PsiAnnotationMemberValue annotationMemberValue = annotation.findAttributeValue("value"); String memberValue = annotationMemberValue.getText().replaceAll("\"", "").trim(); if (StringUtils.isNotBlank(memberValue) && !memberValue.startsWith("/")) { memberValue = PREFIX.concat(memberValue); } String resourceUrl = baseUrl.concat(memberValue); // resource_url params.put("resource_url", resourceUrl); // resource_name String resourceName = humpToUnderline(resourceUrl); if (resourceName.length() > 50) { resourceName = resourceName.substring(0, 50); } params.put("resource_name", resourceName); // resource_desc String resourceDesc = checkMethodComment(document, method); params.put("resource_des", resourceDesc); resourceList.add(params); continue; } } if (resourceList.size() == 0) { return; } outputSqlInfo(editor, resourceList); } } } /** * 输出sql语句 * @param editor * @param resourceList */ public void outputSqlInfo(Editor editor, List<Map<String, String>> resourceList) { StringBuilder sb = new StringBuilder(); sb.append("-- sa_resource"); sb.append("SET @parent_id = \"0\";\n"); for (Map<String, String> param : resourceList) { String resourceSql = "INSERT INTO `sa_resource` (`id`, `p_id`, `resource_name`, `resource_des`, `resource_type`, `resource_url`, `curr_status`, `relation`, `company_id`, `create_user`, `create_time`,`update_user`, `update_time`, `status`) \n" + "VALUES (CONCAT(UUID_SHORT(),''), @parent_id, '%s', '%s', NULL, '%s', NULL, NULL, '', NULL, NOW(), NULL, NOW(), NULL);\n"; sb.append(String.format(resourceSql, param.get("resource_name"), param.get("resource_des"), param.get("resource_url"))); sb.append("\n"); } Messages.showMessageDialog(editor.getProject(), sb.toString(), "总共有方法" + resourceList.size() + "个", Messages.getInformationIcon()); } /** * 小写转大写 * @param var1 * @return */ public static String humpToUnderline(String var1) { StringBuilder result = new StringBuilder(); if (var1 != null || var1.length() > 0) { result.append("RES_"); result.append(var1.substring(0, 1).toUpperCase()); for (int i = 1; i < var1.length(); i++) { String var2 = var1.substring(i, i + 1); // 在大写字母前添加下划线 if (var2.equals(var2.toUpperCase()) && !Character.isDigit(var2.charAt(0))) { result.append("_"); } result.append(var2.toUpperCase()); } } return result.toString().replaceAll("/", ""); } /** * 获取注释 * @param document * @param psiMethod * @return */ private String checkMethodComment(Document document, PsiMethod psiMethod){ String comment = ""; PsiComment classComment = null; for (PsiElement tmpEle : psiMethod.getChildren()) { if (tmpEle instanceof PsiComment){ classComment = (PsiComment) tmpEle; // 注释的内容 String tmpText = classComment.getText(); String pattern = "[\\u4E00-\\u9FA5A-Za-z0-9]+"; Pattern r = Pattern.compile(pattern); Matcher m = r.matcher(tmpText); while (m.find()) { comment = m.group(0); break; } } } return comment; } }
开发完成,我们可以进行sql语句的模板替换并进行输出,最后输出结果如下:
比如这个方法
我们可以看到最后生成的sql语句为
INSERT INTO `sa_resource` (`id`, `p_id`, `resource_name`, `resource_des`, `resource_type`, `resource_url`, `curr_status`, `relation`, `company_id`, `create_user`, `create_time`,`update_user`, `update_time`, `status`) VALUES (CONCAT(UUID_SHORT(),''), @parent_id, 'RES_COMPANY_POLICY_REPORT_INIT', '主页面', NULL, '/companyPolicyReport/init', NULL, NULL, '', NULL, NOW(), NULL, NOW(), NULL);
满足我们当时的要求,自此可以进行一键生成所需要的sql语句,所以此插件名Generate Resource SQL,
可以在idea插件市场搜索Generate Resource SQL,重启idea。在controller类里右击鼠标,点击Generate Resource SQL进行使用
项目已上传至Github: https://github.com/LiuFqiang/GeneratePlugin
插件主页:https://plugins.jetbrains.com/plugin/17843-generate-resource-sql