• MeterSphere 之 Idea插件开发


    前言:
    Metersphere 官网支持IDEA插件可以一键导入到MS当中,但一些项目当中自己定义的注解并不支持解析,所以基于这个场景的考虑,自己准备适配一下自定义插件的解析。

    官网插件地址:https://github.com/metersphere/metersphere-idea-plugin


    一 、MAC 安装gradle

    去官网看到插件的结构,是通过java编写,gradle作为构建工具。平时使用Maven比较多,所以这边从0开始搭建一个插件环境。

    1. 跟使用Maven一样,直接去官方 https://gradle.org/install/ 查看下载适合自己的方式。我使用安装包下载。安装之后解压,配置PATH环境变量即可。

    ![image.png](https://img-blog.csdnimg.cn/img_convert/3669572d76bc374c5767a50be4fcf838.png#clientId=u10e27521-f96f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=264&id=u15a5d437&margin=[object Object]&name=image.png&originHeight=264&originWidth=678&originalType=binary&ratio=1&rotation=0&showTitle=false&size=20721&status=done&style=none&taskId=u4b1f3662-830e-4a81-84ae-b1e11114527&title=&width=678)

    vi ~/.zshrc (我本地安装了item2,所以是这个文件。)
    ![image.png](https://img-blog.csdnimg.cn/img_convert/ef3d4bdde0ac04e6bcc8c3eb46740494.png#clientId=u10e27521-f96f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=178&id=u6f51b3c7&margin=[object Object]&name=image.png&originHeight=178&originWidth=782&originalType=binary&ratio=1&rotation=0&showTitle=false&size=39100&status=done&style=none&taskId=udd245d3d-01e0-4999-ab3a-bb7e4024b6b&title=&width=782)

    验证 gradle --version
    ![image.png](https://img-blog.csdnimg.cn/img_convert/f8c139a0328e21c5f83092d3c9405904.png#clientId=u10e27521-f96f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=692&id=u64d90d99&margin=[object Object]&name=image.png&originHeight=692&originWidth=736&originalType=binary&ratio=1&rotation=0&showTitle=false&size=76102&status=done&style=none&taskId=u923677c1-1a9b-4a7e-899c-452665eed48&title=&width=736)
    注意:安装gradle先安装jdk,安装jdk网上资料很多。
    具体的用法不在这里介绍了,直接查看官网即可。
    参考:
    https://docs.gradle.org/7.4.2/userguide/what_is_gradle.html
    https://www.iteye.com/blog/shmilyaw-hotmail-com-2345439

    二、使用gradle构建IDEA插件环境

    2.1 跟着官网走

    1. IDEA 官方是有支持的:https://plugins.jetbrains.com/docs/intellij/welcome.html
    2. 官网有插件模板:https://github.com/JetBrains/intellij-platform-plugin-template

    ![image.png](https://img-blog.csdnimg.cn/img_convert/51006e44b6b8ca10d6bcaae900369e6f.png#clientId=u10e27521-f96f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=598&id=ub29d539b&margin=[object Object]&name=image.png&originHeight=598&originWidth=915&originalType=binary&ratio=1&rotation=0&showTitle=false&size=130965&status=done&style=none&taskId=uef761640-a770-4e19-b457-098a661a649&title=&width=915)
    官网模板是kotlin写的。

    可以先看下整体的目录结构

    .
    ├── .github/                GitHub Actions workflows and Dependabot configuration files
    ├── .run/                   Predefined Run/Debug Configurations
    ├── gradle
    │   └── wrapper/            Gradle Wrapper
    ├── build/                  Output build directory
    ├── src                     Plugin sources
    │   └── main
    │       ├── kotlin/         Kotlin production sources  存放项目的源码
    │       └── resources/      Resources - plugin.xml, icons, messages  存放项目的资源配置,图标,信息等
    │   └── test
    │       ├── kotlin/         Kotlin test sources
    │       └── testData/       Test data used by tests
    ├── .gitignore              Git ignoring rules
    ├── build.gradle.kts        Gradle configuration
    ├── CHANGELOG.md            Full change history
    ├── gradle.properties       Gradle configuration properties
    ├── gradlew                 *nix Gradle Wrapper script
    ├── gradlew.bat             Windows Gradle Wrapper script
    ├── LICENSE                 License, MIT by default
    ├── qodana.yml              Qodana configuration file
    ├── README.md               README
    └── settings.gradle.kts     Gradle project settings
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    plunig.xml

    ![image.png](https://img-blog.csdnimg.cn/img_convert/0457f1225a8f78ba154c0608938e23a4.png#clientId=u10e27521-f96f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=706&id=u1da3d7a2&margin=[object Object]&name=image.png&originHeight=706&originWidth=1619&originalType=binary&ratio=1&rotation=0&showTitle=false&size=170116&status=done&style=none&taskId=uf82281f2-628c-43f3-bd57-8bef5dc9ca0&title=&width=1619)

    <idea-plugin>
      <id>org.jetbrains.plugins.template</id>
      <name>Template</name>
      <vendor>JetBrains</vendor>
      <depends>com.intellij.modules.platform</depends>
    
      <extensions defaultExtensionNs="com.intellij">
        <applicationService serviceImplementation="..."/>
        <projectService serviceImplementation="..."/>
      </extensions>
    
      <projectListeners>
        <listener class="..." topic="..."/>
      </projectListeners>
    </idea-plugin>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    了解了项目架构之后,开始自己搭建一个环境。

    2.2本地搭建环境

    1. 前面我们已经安装好了gradle,然后我们打开idea. 新建项目,选择gradle。
    2. 注意,因为我们是要用java开发的插件,所以我们要勾选两个,java和intelliJ platform plugin

    ![image.png](https://img-blog.csdnimg.cn/img_convert/c771cab3d42c81ba6b2de63e2b877346.png#clientId=u10e27521-f96f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=764&id=u5a38f55c&margin=[object Object]&name=image.png&originHeight=764&originWidth=823&originalType=binary&ratio=1&rotation=0&showTitle=false&size=122313&status=done&style=none&taskId=u6e8c8d34-a8e9-460a-8631-525f0532f81&title=&width=823)

    新建之后的目录结构是这样子的:

    ![image.png](https://img-blog.csdnimg.cn/img_convert/71e67bc60626e62ac9008167f76aa5c7.png#clientId=u10e27521-f96f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=399&id=u24c91a51&margin=[object Object]&name=image.png&originHeight=399&originWidth=381&originalType=binary&ratio=1&rotation=0&showTitle=false&size=30610&status=done&style=none&taskId=u738d367a-1d19-4ef3-a9dc-953ae89ab13&title=&width=381)

    src
      --main
        --java //   一般我们写的java文件都会放到这个里面。
        --resources 
          --META-INF
            --plugin.xml   // 插件的配置文件
            
    build.gradle // 构建gradle的配置文件,相当于maven里面的pom.xml文件,我们所需要的依赖信息都会放到这里面来。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    **plugin.xml **

    <idea-plugin>
    
      <!-- 插件名称,别人在官方插件库搜索你的插件时使用的名称 -->
      <name>MyPlugin</name>
    
      <!-- 插件唯一id,不能和其他插件项目重复,所以推荐使用com.xxx.xxx的格式
           插件不同版本之间不能更改,若没有指定,则与插件名称相同 -->
      <id>com.example.plugin.myplugin</id>
    
      <!-- 插件的描述 -->
      <description>my plugin description</description>
    
      <!-- 插件版本变更信息,支持HTML标签;
           将展示在 settings | Plugins 对话框和插件仓库的Web页面 -->
      <change-notes>Initial release of the plugin.</change-notes>
    
      <!-- 插件版本 -->
      <version>1.0</version>
    
      <!-- 供应商主页和email-->
      <vendor url="http://www.jetbrains.com" email="support@jetbrains.com" />
    
      <!-- 插件所依赖的其他插件的id -->
      <depends>MyFirstPlugin</depends>
    
      <!-- 插件兼容IDEA的最大和最小 build 号,两个属性可以任选一个或者同时使用
           官网详细介绍:http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html-->
      <idea-version since-build="3000" until-build="3999"/>
    
      <!-- application components -->
      <application-components>
        <component>
          <!-- 组件接口 -->
          <interface-class>com.foo.Component1Interface</interface-class>
          <!-- 组件的实现类 -->
          <implementation-class>com.foo.impl.Component1Impl</implementation-class>
        </component>
      </application-components>
    
      <!-- project components -->
      <project-components>
        <component>
          <!-- 接口和实现类相同 -->
          <interface-class>com.foo.Component2</interface-class>
        </component>
      </project-components>
    
      <!-- module components -->
      <module-components>
        <component>
          <interface-class>com.foo.Component3</interface-class>
        </component>
      </module-components>
    
      <!-- Actions -->
      <actions>
        ...
      </actions>
    
      <!-- 插件定义的扩展点,以供其他插件扩展该插件 -->
      <extensionPoints>
        ...
      </extensionPoints>
    
      <!-- 声明该插件对IDEA core或其他插件的扩展 -->
      <extensions xmlns="com.intellij">
        ...
      </extensions>
    </idea-plugin>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    到此,插件的环境已经搭建好了。

    三、基于MS官方插件适配自定义注解

    IDEA 插件官方提供了一些PSI接口,可以方便我们分析源代码文件。

    3.1 PSI介绍

    ![image.png](https://img-blog.csdnimg.cn/img_convert/883f22489a4da2f51b71edb1a1f0c53b.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=524&id=u56693021&margin=[object Object]&name=image.png&originHeight=524&originWidth=891&originalType=binary&ratio=1&rotation=0&showTitle=false&size=61072&status=done&style=none&taskId=ue0fd94f4-5ef6-44f6-9dbc-c8cf9eae1dc&title=&width=891)
    官方介绍: 程序结构接口,通常被称为PSI,是IntelliJ 平台中负责解析文件和创建语法和语义代码模型的层,改模型支持平台的许多功能。

    简单理解就是我们要想分析源代码文件的内容,就需要PSI的帮助。

    我们知道,JVM在加载类之前,首先需要读取Class文件,并将Class文件解析成一个结构体对象,对应的是Class文件结构。与JVM解析Class文件不同的是,IDEA解析的Java的源代码,但IDEA也是将Java文件解析为一个结构体对象。
    请记住一句话,对于任何拥有固定结构的文件或者代码,都可以使用访问者模式。
    不仅Java文件,任何代码文件都会有一定的结构,否则编译器也不能识别,也是因为如此,IDEA实现的PSI与Java字节码操作工具ASM有非常多的相似之处,除了都是将文件解析成结构外,也都支持使用访问者模式编辑文件,一个大的结构下面包含许多小的结构,小的结构也支持使用访问者模式编辑。
    因为很相似,所以我们可以用学习使用 ASM 工具分析、创建、或改写Class文件的思维去学习PSI。
    由于不同的编程语言编写的代码文件有不同的结构,IDEA将文件结构抽象为接口,叫程序结构接口文件(PSI File),不同类型的文件解析后生成不同的PsiFile接口的实现类实例,这也是IDEA能够扩展支持多语言的基础。

    3.1.1 PsiFile 接口

    一个文件就是一个PsiFile,也是一个文件的结构树的根节点,PsiFile是一个接口,如果文件是一个.java文件,那么解析生成的PsiFile就是PsiJavaFile对象,如果是一个Xml文件,则解析后生成的是XmlFile对象。

    3.1.2 PsiElement接口

    Class文件结构包含字段表、属性表、方法表等,每个字段、方法也都有属性表,但在PSI中,总体上只有PsiFile和PsiElement。
    Element即元素,一个PsiFile(本身也是PsiElement)由许多的PsiElement构成,每个PsiElement也可以由许多的PsiElement构成。
    PsiElement用于描述源代码的内部结构,不同的结构对应不同的实现类。
    对应Java文件的PsiElement种类有:PsiClass、PsiField、PsiMethod、PsiCodeBlock、PsiStatement、PsiMethodCallExpression等等。其中,PsiField、PsiMethod都是PsiClass的子元素,PsiCodeBlock是PsiMethod的子元素,PsiMethodCallExpression是PsiCodeBlock的子元素,正是这种关系构造成了一棵树。

    解析一个Java文件有上百种类型的PsiElement,对于一个新手,我们如何才能快速的认识对应Java代码文件中的每行代码都会解析生成呢?好在IDEA提供了PSI视图查看器。
    如果你正在编写插件,那么IDEA会自动在“工具”菜单中显示“查看PSI结构”的选项,否则,我们需要修改IDEA的配置文件才能在“工具”菜单中看到这个选项。
    工具
    ![image.png](https://img-blog.csdnimg.cn/img_convert/9ff52646579175364b8f57b926b4f173.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=232&id=u773fd499&margin=[object Object]&name=image.png&originHeight=232&originWidth=1080&originalType=binary&ratio=1&rotation=0&showTitle=false&size=44279&status=done&style=none&taskId=u8c581cb9-d367-462f-9e0f-73092145907&title=&width=1080)
    配置文件在IDEA安装路径的bin目录下,找到idea.properties文件,如下图所示。
    ![image.png](https://img-blog.csdnimg.cn/img_convert/04dd6a99d859649e5667add5a67ff2b6.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=866&id=u68a3f9c6&margin=[object Object]&name=image.png&originHeight=866&originWidth=1670&originalType=binary&ratio=1&rotation=0&showTitle=false&size=537693&status=done&style=none&taskId=uf9dd44fd-2628-44b9-a7ea-07554ba4f6a&title=&width=1670)

    添加配置后重启IDEA就能看到tools菜单下新加了两个选择,如下图所示。
    ![image.png](https://img-blog.csdnimg.cn/img_convert/651d77c73f2a770c27a3579d514ecf63.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=532&id=u8af15b18&margin=[object Object]&name=image.png&originHeight=532&originWidth=948&originalType=binary&ratio=1&rotation=0&showTitle=false&size=502552&status=done&style=none&taskId=u5c38f75c-b5cc-4e92-852e-56f9e5b4f99&title=&width=948)
    其中View PSI Structure of Current File是将当前查看的文件解析为结构树,选中选项后弹出如下图所示的窗口。

    ![image.png](https://img-blog.csdnimg.cn/img_convert/7a56cafd86307c351124d43abd8ca07e.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=813&id=ub0e82473&margin=[object Object]&name=image.png&originHeight=813&originWidth=1380&originalType=binary&ratio=1&rotation=0&showTitle=false&size=143767&status=done&style=none&taskId=u886de55c-548e-4e77-aa38-2042a4e711b&title=&width=1380)
    ![image.png](https://img-blog.csdnimg.cn/img_convert/7fbb507620283b8cd8f11ffcc46753dc.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=827&id=u6f0d475b&margin=[object Object]&name=image.png&originHeight=827&originWidth=1388&originalType=binary&ratio=1&rotation=0&showTitle=false&size=138855&status=done&style=none&taskId=ubbbe7d94-c1f1-4076-ba29-1dcff86d66a&title=&width=1388)

    • Show PSI structure for:选择PsiFile类型;
    • Show PsiWhiteSpace:去掉勾选后可以隐藏表示连续空格(包括换行符)的元素PsiElement;

    当我们选中源码时,IDEA会找到对应的PsiElement标志为选中状态,如上图左侧的PSI Tree窗口所示
    这个小工具可以帮我们刚好的理解PSI的源码

    3.1.3 PsiReference 接口

    一个PsiReference表示代码中某个PsiElement链接到相应的声明。
    简单理解,PsiReference就是我们选中鼠标右键弹出菜单中Go To的Declaration or Usages、或者按住command键+鼠标点击后能够跳转到相应声明的依据。
    ![image.png](https://img-blog.csdnimg.cn/img_convert/d6f8672cb60a77e92c16f3f6eb7d3e28.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=348&id=uf62d2b9f&margin=[object Object]&name=image.png&originHeight=348&originWidth=1080&originalType=binary&ratio=1&rotation=0&showTitle=false&size=96035&status=done&style=none&taskId=ue0772e6d-af7b-4945-9a9e-5b0d680a5e6&title=&width=1080)

    理解了核心的几个接口,在IDEA的PSI中,有很多工具类可以帮助我们在写插件的时候事半功倍。

    ![image.png](https://img-blog.csdnimg.cn/img_convert/681eb19ff47f24e53fa9255f4667b7c5.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=683&id=u85be3658&margin=[object Object]&name=image.png&originHeight=683&originWidth=478&originalType=binary&ratio=1&rotation=0&showTitle=false&size=101227&status=done&style=none&taskId=ue5d44e50-b808-4b4a-830e-f45a25f537c&title=&width=478)
    源码当中写的都比较清楚。具体的可以研究下。

    3.2 MS插件源码解析

    在介绍MS分析源码之前先看下效果。
    具体用法参考官网
    https://github.com/metersphere/metersphere-idea-plugin
    ![image.png](https://img-blog.csdnimg.cn/img_convert/9f92bfc76ca2a2dd13b02d24fd9a8982.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=729&id=ud7b5e8df&margin=[object Object]&name=image.png&originHeight=729&originWidth=833&originalType=binary&ratio=1&rotation=0&showTitle=false&size=119500&status=done&style=none&taskId=u4410f8ce-c90c-41e2-b4f3-9c905b0b37f&title=&width=833)

    ![image.png](https://img-blog.csdnimg.cn/img_convert/371edec532cf4402b99ea5326795de35.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=685&id=ueb67ae19&margin=[object Object]&name=image.png&originHeight=685&originWidth=1519&originalType=binary&ratio=1&rotation=0&showTitle=false&size=75238&status=done&style=none&taskId=u3895d3e4-bcd0-4b69-abca-89494fe2ea4&title=&width=1519)

    ![image.png](https://img-blog.csdnimg.cn/img_convert/da28ec1082fb644332d123aa5f4f13e0.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=852&id=u739054ee&margin=[object Object]&name=image.png&originHeight=852&originWidth=892&originalType=binary&ratio=1&rotation=0&showTitle=false&size=232892&status=done&style=none&taskId=u5859b66f-afaf-4f1b-8f7a-09682f4569f&title=&width=892)
    结果
    ![image.png](https://img-blog.csdnimg.cn/img_convert/548e88b57992276f6263a2014b39214f.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=640&id=ufdec84f5&margin=[object Object]&name=image.png&originHeight=640&originWidth=445&originalType=binary&ratio=1&rotation=0&showTitle=false&size=80863&status=done&style=none&taskId=u999a19ea-d444-4bf6-9dfb-6f549354a74&title=&width=445)
    针对业务开发人员来讲,就是一个idea的插件,通过对接Metersphere,将项目中的Controller 一键导出到Metersphere当中,完美的融化了开发部门和测试部门的配合。

    • 效果看完之后我们开始解析

    首先去官网clone 到本地 https://github.com/metersphere/metersphere-idea-plugin
    项目结构:
    ![image.png](https://img-blog.csdnimg.cn/img_convert/0d9d018f927df5d24844fafd4311a10f.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=984&id=uf01533e0&margin=[object Object]&name=image.png&originHeight=984&originWidth=404&originalType=binary&ratio=1&rotation=0&showTitle=false&size=89880&status=done&style=none&taskId=ufd9115e2-b30c-41cb-85c9-cfc7fad3f09&title=&width=404)
    目前MS支持两种导出方式:1. postman 2. metersphere

    • action : 触发事件的类。
    • gui: gui的配置页面
    • exporter:两种类型的导出代码
    • 其他目录:都是其它工具类和配置

    3.2.1 看一个插件从 plugin.xml 开始

    ![image.png](https://img-blog.csdnimg.cn/img_convert/3fbd5b87265f05dd1a4acd8d0325bbc4.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=871&id=u196e080b&margin=[object Object]&name=image.png&originHeight=871&originWidth=1761&originalType=binary&ratio=1&rotation=0&showTitle=false&size=290636&status=done&style=none&taskId=uf75a24f4-fffe-452a-aa71-9fc0582f996&title=&width=1761)

    这个配置文件是这个插件的整体配置信息,可以从中获取到插件的ID,名称,邮箱等基本信息和Action以及扩展插件的信息,具体意思参考上面的本地环境搭建。

    • 从MS 当中可以看到,这个插件有两个action和两个service 。
      • Service 是一个Component,可以理解成一个容器。
      • Action 可以理解成是一个事件触发器,比如在IDEA中导出的动作,就可以是一个action。

    3.2.2 AppSettingService

    �![image.png](https://img-blog.csdnimg.cn/img_convert/71a6193fd3d64e2d680de0bd5ce8c5b1.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=772&id=uf4c3976a&margin=[object Object]&name=image.png&originHeight=772&originWidth=1368&originalType=binary&ratio=1&rotation=0&showTitle=false&size=193674&status=done&style=none&taskId=u234678a4-a3cc-4c89-9351-e212d3931ba&title=&width=1368)

    1. PersistentStateComponent 这个是个接口,是用来持久化存储数据的。上面的注解是指,在运行插件的时候储存的类型。

    3.2.3 AppSettingConfigurable


    ![image.png](https://img-blog.csdnimg.cn/img_convert/3c639d67f607d981b7e669431b8edb3b.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=893&id=u4be3e427&margin=[object Object]&name=image.png&originHeight=893&originWidth=1387&originalType=binary&ratio=1&rotation=0&showTitle=false&size=236721&status=done&style=none&taskId=uca0d9bcb-3dd1-495f-b1d4-b8534009e16&title=&width=1387)�

    1. 实现了Configurable接口,初始化配置,这里初始化的是gui的配置文件信息。

    3.2.4 AppSettingComponent和AppSettingComponent.form

    ![image.png](https://img-blog.csdnimg.cn/img_convert/ca0db8f1d266e17de74e2b7d4a7201fc.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=849&id=u080b9824&margin=[object Object]&name=image.png&originHeight=849&originWidth=1705&originalType=binary&ratio=1&rotation=0&showTitle=false&size=201461&status=done&style=none&taskId=u2b46d721-d6dc-4a2a-a8bc-20abc853424&title=&width=1705)

    ![image.png](https://img-blog.csdnimg.cn/img_convert/dbaafad427c4769e989f89265814e66d.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=835&id=u69421449&margin=[object Object]&name=image.png&originHeight=835&originWidth=1601&originalType=binary&ratio=1&rotation=0&showTitle=false&size=252002&status=done&style=none&taskId=u5d526681-eebd-4cbe-b058-769e911288b&title=&width=1601)

    1. 这两个是一一对应的,在from当中,只有写了fieldName的值,才会在AppSettingComponent类中显示属性,例如:JPanel,JTextField,JPasswordField,JTabbedPane,JComboBox等。

    ![image.png](https://img-blog.csdnimg.cn/img_convert/900c5f93fb3b58857cdcc473b2eec86e.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=830&id=u1ced80cb&margin=[object Object]&name=image.png&originHeight=830&originWidth=1199&originalType=binary&ratio=1&rotation=0&showTitle=false&size=160485&status=done&style=none&taskId=uaa5af9c9-ac2f-4001-9823-863edd44a9c&title=&width=1199)
    �1. AppSettingComponent() 这个方法是个构造方法,其中initData(appSettingState)是初始化配置信息的方法,可以看到里面很多Listener,这些个Listener是对gui填写内容的监听,每个类型的Listener不同,但监听原理是相同的。都是当内容发生的时候,触发的动作。

    3.2.5 以MS的导出解析:ExportToMSAction

    ![image.png](https://img-blog.csdnimg.cn/img_convert/1907d63f0922570dd234e3c00266ecb8.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=367&id=uaf2167fb&margin=[object Object]&name=image.png&originHeight=367&originWidth=1307&originalType=binary&ratio=1&rotation=0&showTitle=false&size=71684&status=done&style=none&taskId=u27a5d67e-0234-491a-a98e-068c05f177f&title=&width=1307)�

    继承了�CommonAction
    �![image.png](https://img-blog.csdnimg.cn/img_convert/1ae28e2332acb50da65d5200f4b44cea.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=665&id=u3af70da6&margin=[object Object]&name=image.png&originHeight=665&originWidth=972&originalType=binary&ratio=1&rotation=0&showTitle=false&size=131181&status=done&style=none&taskId=uf9b59e48-d069-4a18-97bd-dcb519e8fe2&title=&width=972)
    ![image.png](https://img-blog.csdnimg.cn/img_convert/c9a41161a7d5bc92117b787fb477a95b.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=418&id=u6dca968a&margin=[object Object]&name=image.png&originHeight=418&originWidth=1067&originalType=binary&ratio=1&rotation=0&showTitle=false&size=84348&status=done&style=none&taskId=ub7daa61b-da30-47f0-92a9-7ac978b64e6&title=&width=1067)
    所以核心类就是MeterSphereExporter这个类
    ![image.png](https://img-blog.csdnimg.cn/img_convert/5d243c5bdbf0118e3cc99909282b13a5.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=874&id=u7a66796c&margin=[object Object]&name=image.png&originHeight=874&originWidth=1367&originalType=binary&ratio=1&rotation=0&showTitle=false&size=212659&status=done&style=none&taskId=u46194604-9917-4da6-b6d3-eb7b447bd02&title=&width=1367)

    这个方法的整体逻辑就是

    1. 先过滤出所有的PsiJavaFile文件,然后通过调用postmanExporter.transform() 方法去进行解析,然后再构建需要导出的文件格式,通过调用MS的API接口导入到MS当中。所以我们的重点在transform这个方法。

    3.2.6 Transform

    ![image.png](https://img-blog.csdnimg.cn/img_convert/43d98217988e81cc16faa8a8d467a01e.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=883&id=u6b653d4c&margin=[object Object]&name=image.png&originHeight=883&originWidth=1460&originalType=binary&ratio=1&rotation=0&showTitle=false&size=187564&status=done&style=none&taskId=u50e55415-f78d-4516-88be-be6f3c11adf&title=&width=1460)

    1. 这边必须要注意:导出的接口要符合restful api 风格,要不然不支持的。

    ![image.png](https://img-blog.csdnimg.cn/img_convert/c787e0550b9f8f55a8dde915ca0416eb.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=775&id=u1a12c145&margin=[object Object]&name=image.png&originHeight=775&originWidth=1515&originalType=binary&ratio=1&rotation=0&showTitle=false&size=161681&status=done&style=none&taskId=u0fbd23d7-55e8-4a09-bb8c-fbec402d18f&title=&width=1515)

    1. 先判断类上面是否有RequestMapping,用来构建该Controller的访问路径。
    2. 然后迭代该类中所有方法,进行构建json schema。

    ![image.png](https://img-blog.csdnimg.cn/img_convert/f92cfe3ea3903dc7fe4e3acafa8ab917.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=884&id=ucefecb06&margin=[object Object]&name=image.png&originHeight=884&originWidth=1412&originalType=binary&ratio=1&rotation=0&showTitle=false&size=183241&status=done&style=none&taskId=u1a685844-4d79-412b-a8e2-985a8aa07b6&title=&width=1412)

    1. 其实源码当中注释的挺清楚的,按照一个接口需要哪些部分组成的顺序往下读代码就行了:url, header,body等
    2. 从注解开始,所以在每个方法解析之前获取注解上面的请求类型比如:GET,POST等。

    ![image.png](https://img-blog.csdnimg.cn/img_convert/947721b9ab55c52c270733e875cf6efc.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=813&id=u5f7ad926&margin=[object Object]&name=image.png&originHeight=813&originWidth=1194&originalType=binary&ratio=1&rotation=0&showTitle=false&size=171672&status=done&style=none&taskId=ucf651936-4f2a-4694-8fe2-63227c6276e&title=&width=1194)

    1. 前面的都很好理解,主要是body的解析。
    2. 校验注解只要含有 RequestBody、RequestParam、RequestPart、RequestBody、RequestPart还有servlet的httpRequest和httpResponse 的。都会被解析。
    3. 这边主要说@RequestBody 的解析方法 getRaw()

    ![image.png](https://img-blog.csdnimg.cn/img_convert/f0944df4903a722c50df6e2404ff0cab.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=953&id=ue61c01d4&margin=[object Object]&name=image.png&originHeight=953&originWidth=1442&originalType=binary&ratio=1&rotation=0&showTitle=false&size=246181&status=done&style=none&taskId=u506d0750-4c5d-4771-a552-5cbadd3b6c2&title=&width=1442)

    1. 构建导出对象,然后开始渲染,有一个点需要注意,这个使用的语法是Json Scheam 的语法,熟悉了Json Scheam 会对整个构建过程有一个很清晰的认识。

    参考链接:Json Schema 官方网站 中文文档 生成Json Schema 工具
    ![image.png](https://img-blog.csdnimg.cn/img_convert/40057f50ea35c5d48acec4ea29ebae24.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=729&id=uae73e3a3&margin=[object Object]&name=image.png&originHeight=729&originWidth=1567&originalType=binary&ratio=1&rotation=0&showTitle=false&size=210295&status=done&style=none&taskId=u8642ea32-fafa-4ec5-a1ac-14744a4313d&title=&width=1567)

    1. 红框标注的是意思为:根据当前类型的全限定名,在全局project当中获取对应的类型PsiClass。
    2. 注意:此方法只能解析对象,数组,基本数据类型等。如果是泛型,例如:Request这种,解析不了,PsiClass为null。

    ![image.png](https://img-blog.csdnimg.cn/img_convert/52c50e9d897220f0089df57aac90464d.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=375&id=u04d03b03&margin=[object Object]&name=image.png&originHeight=375&originWidth=1406&originalType=binary&ratio=1&rotation=0&showTitle=false&size=76563&status=done&style=none&taskId=udd2c50b4-e36f-4c94-91d8-f99da92da1c&title=&width=1406)

    1. 构建json schema 的数据类型方法,如果有javadoc的注释,以javadoc的注释为主。
    2. 如果PsiClass为null,则走else的方法。

    ![image.png](https://img-blog.csdnimg.cn/img_convert/d26baf9f55b33f3f1c909debb0f9bd83.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=561&id=ufd8e0c2a&margin=[object Object]&name=image.png&originHeight=561&originWidth=1189&originalType=binary&ratio=1&rotation=0&showTitle=false&size=111366&status=done&style=none&taskId=u53cc07cb-e09b-479a-921c-df86a00b66a&title=&width=1189)
    ![image.png](https://img-blog.csdnimg.cn/img_convert/d3684c067d2f0952228b582994b8d075.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=948&id=u2d65a80e&margin=[object Object]&name=image.png&originHeight=948&originWidth=1489&originalType=binary&ratio=1&rotation=0&showTitle=false&size=248172&status=done&style=none&taskId=u947c12d8-2f78-4db9-a917-ba612e4b66c&title=&width=1489)

    1. 获取类的所有字段,然后循环开始读取,构建schema数据。
    2. 以此类推,获取字段,判断类型,构建json schema数据,递归循环。

    ![image.png](https://img-blog.csdnimg.cn/img_convert/061cb98269ec4f1d1722b69560b76169.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=756&id=u44a78202&margin=[object Object]&name=image.png&originHeight=756&originWidth=1572&originalType=binary&ratio=1&rotation=0&showTitle=false&size=172767&status=done&style=none&taskId=u782872ae-a7af-4295-9d2e-68b63228b15&title=&width=1572)

    1. 前面解析完之后,接下来构建导入MS所需要的数据格式,然后生成一个临时文件。

    ![image.png](https://img-blog.csdnimg.cn/img_convert/2d8539af0b57a9f48597a200e7bbf1c0.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=775&id=u81d73afe&margin=[object Object]&name=image.png&originHeight=775&originWidth=1345&originalType=binary&ratio=1&rotation=0&showTitle=false&size=173660&status=done&style=none&taskId=u8df8060d-8796-46c8-a164-94361d7a9a0&title=&width=1345)

    1. 调用MS的导入接口,导入该文件即可。

    3.2.7 整体逻辑

    1. 获取所有的PsiJavaFile。
    2. 解析符合restful风格的类。
    3. 判断javadoc,判断controller 的访问路径,
    4. 获取url
    5. 获取header
    6. 获取body
      1. 循环方法参数。
      2. 判断类型。
        1. 简单类型
          1. 循环所有属性。
            1. 判断类型
            2. 构建json schema
            3. … 不断递归循环
        2. 复杂类型
          1. 循环属性。
          2. 是否为泛型。
            1. 分层解析。先解析外层,在解析内层
              1. 都是判断类型,构建json schema .
              2. …不断递归循环。
    7. 构建需要导出的格式,生成导入文件。
    8. 请求MS导入接口。
    9. 返回上传结果。
    10. 刷新MS接口定义列表,查看结果。

    3.3 添加自定义注解

    当前测试下来,自定义注解是不支持的,还有复杂的泛型嵌套,也是不支持的。所以基于客户的需求,重构了一下代码。

    1. 新增加一个resolver 文件夹。写了一个构建自定义注解的工厂。

    ![image.png](https://img-blog.csdnimg.cn/img_convert/3608bab411c104c298ef0b41fae37fe5.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=978&id=u954940aa&margin=[object Object]&name=image.png&originHeight=978&originWidth=1763&originalType=binary&ratio=1&rotation=0&showTitle=false&size=302585&status=done&style=none&taskId=uf57424a7-7afa-4d78-9027-3a3ab58ea05&title=&width=1763)

    1. 重构了getRaw中的 对象解析

    ![image.png](https://img-blog.csdnimg.cn/img_convert/c1b23c081d449ffe53e3e218afaba290.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=828&id=u5eb879d9&margin=[object Object]&name=image.png&originHeight=828&originWidth=1360&originalType=binary&ratio=1&rotation=0&showTitle=false&size=226853&status=done&style=none&taskId=u73000708-ac00-4da7-97ea-608bb03b694&title=&width=1360)

    1. 在构建properties 的scheam 中添加解析自定义注解方法

    ![image.png](https://img-blog.csdnimg.cn/img_convert/3a98c3872b3329669806a05ad8311a52.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=595&id=ucaacb2e3&margin=[object Object]&name=image.png&originHeight=595&originWidth=1197&originalType=binary&ratio=1&rotation=0&showTitle=false&size=99019&status=done&style=none&taskId=u86d15b1b-58a3-4859-b34e-9a86c28bcb1&title=&width=1197)![image.png](https://img-blog.csdnimg.cn/img_convert/33d89c507f8eb077f80b0ed031d6c5f5.png#clientId=uc20870b0-2696-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=276&id=u52317765&margin=[object Object]&name=image.png&originHeight=276&originWidth=715&originalType=binary&ratio=1&rotation=0&showTitle=false&size=35601&status=done&style=none&taskId=uead7a2d8-2dc6-42e8-a073-d38c1e6defb&title=&width=715)

    经过一番改造后,最后终于支持自定义注解了,并且支持复杂的泛型嵌套。比如

    1. Request
    2. Response
    3. 还有自定义注解:CommonAnnotation、CustNoAnnotation、CertNoAnnotation、CertTypeAnnotation

    四、写在最后

    1. 初次上手中间还是遇到了很多坑的,不过等写完之后,还是有一点点成就感的。
    2. 静下心来,多研究下源码,去github也找一些插件研究一下。
    3. 虽然当前插件功能不是很完善,但目前内部业务基本都支持了。
    4. 以上内容仅个人学习心得,如有错误,欢迎指正。
    5. 整理下参考的资料:
      1. 官网插件地址:https://github.com/metersphere/metersphere-idea-plugin
      2. gradle官方: https://gradle.org/install/
      3. IDEA 官方是有支持的:https://plugins.jetbrains.com/docs/intellij/welcome.html
      4. 官网有插件模板:https://github.com/JetBrains/intellij-platform-plugin-template
      5. idea插件介绍:https://cloud.tencent.com/developer/article/1348741
      6. 参考插件:
        1. https://github.com/docer-savior/docer-savior-idea-plugin
        2. https://github.com/tangcent/easy-yapi
      7. 源码地址:https://github.com/hao65103940/metersphere-idea-plugin/tree/master-fg
  • 相关阅读:
    Win10编译chrome
    hive创建hbase表映射
    【EPLAN】统一修改项目中字体大小
    浏览器插件不会装?保姆级教程分享!
    JavaEE项目的数据分析师、软件工程师
    COM组件中调用JavaScript函数的实例讲解
    MySQL高频面试题
    【附源码】Python计算机毕业设计民宿预定系统
    如何将数据库某列的值(如日期)作为表格的列名
    超标量处理器设计 姚永斌 第6章 指令解码 摘录
  • 原文地址:https://blog.csdn.net/hao65103940/article/details/126246536