下载插件:

iidp-sonar-0.0.1.zip

1、需求分析

我们在这里均以 sie-snest-log 操作日志这个app为例来描述iidp-plugin的需求、设计和实现方案。

  • 我们定义一个model的时候,采取的注解格式是这样的:

    @Model(name = "operator_log", parent = "operator_log", displayName = "操作日志", isAutoLog = Bool.True,
            type = Model.ModelType.Buss)
    public class OperatorLog extends BaseModel<OperatorLog> {
    
        @Property(displayName = "id")
        private String id;
    
        @Property(displayName = "访问时间", dataType = DataType.DATE_TIME)
        private Date accessStartTime;
    
        @Property(displayName = "app名称")
        private String appName;
    
        @Property(displayName = "模型名称")
        private String logModelName;
    
        @Property(displayName = "服务名称")
        private String serviceName;
    
        @Property(displayName = "入参", dataType = DataType.TEXT, widget = "codeeditor")
        private String inParameterData;
    
        @Property(displayName = "出参", dataType = DataType.TEXT, widget = "codeeditor")
        private String outParameterData;
    
        // 用户id
        @ManyToOne(targetModel = "rbac_user", displayName = "用户")
        @JoinColumn(name = "caller_user_id")
        private Map<String, Object> callerUserId;
    
        // ip
        @Property(displayName = "ip")
        private String callerIp;
    
        // 耗时
        @Property(displayName = "耗时(ms)")
        private String takeTime;
    
        // 变更
        @Property(displayName = "数据变更")
        private Boolean modify;
    }
    比如说在上述模型定义的注解中有个 parent字段,它是一个string,意味着它可以写任何的字符串,在编译阶段是不知道parent是否存在,是否填写正确的, 这给开发阶段带来一些容易出错的地方,这些错误只能到运行阶段才能发现,而且还不容易发现。
  • 我们定义一个views的时候,采取的是json的描述方式:

    "operator_log_grid": {
          "body": {
            "buttons": [
              {
                "action": "preview",
                "auth": "read",
                "name": "详情"
              },
              {
                "action": "previewEr",
                "auth": "read",
                "name": "参数"
              }
            ],
            "columns": [
              {
                "displayName": "访问时间",
                "name": "accessStartTime"
              },
              {
                "displayName": "app名称",
                "name": "appName"
              },
              {
                "displayName": "模型名称",
                "name": "logModelName"
              },
              {
                "displayName": "服务名称",
                "name": "serviceName"
              },
              {
                "displayName": "调用者用户id",
                "name": "callerUserId"
              },
              {
                "displayName": "ip",
                "name": "callerIp"
              },
              {
                "displayName": "耗时",
                "name": "takeTime"
              },
              {
                "displayName": "数据变更",
                "name": "modify"
              }
            ],
            "tbar": [
              {
                "action": "delete",
                "auth": "delete",
                "name": "删除"
              }
            ],
            "type": "grid"
          },
          "mode": "primary",
          "model": "operator_log",
          "name": "操作日志-表格",
          "type": "grid"
        }
    结合model的定义,能够发现views中的json定义也是基于string,比如columns.name是跟model中定义的private String appName;定义的成员变量名称保持一致,如果在开发阶段编写错了, 也是无法发现的,只能在运行阶段前端获取view的时候出错。
  • 访问模型名称和模型字段等
    继续举例通过meta访问模型数据的方式:

      meta = new Meta(Meta.SUPERUSER, new HashMap<>());
      meta.get("operator_log").call("create", operatorLogList);
    
      RecordSet rs2 = meta.get("operator_details");
      for (OperatorLog ol : operatorLogList) {
          rs2.call("create", ol.getOperatorDetailsList());
      }
      OperatorLog operatorLog = DbUtils.select(filter, "operator_log", OperatorLog.class);
      Filter f = Filter.equal("traceID", filter.getFilterOp("id").getValue());
    

    同样地发现存在 operator_log、create、traceID 等都是string字符串的形式存在,在编译阶段也不会做任何的校验, 只能在运行阶段才发现问题,而且还不容易发现,比如 traceID 写错了,只是这个filter失效,不会报错,但查出的结果却是不对的。

  • 元模型get set方法

     // getter setter
      public String getID() {
          return this.getStr("id");
      }
    
      public void setID(String id) {
          this.set("id", id);
      }
    
      public Date getAccessStartTime() {
          return this.getDate("accessStartTime");
      }
    
      public void setAccessStartTime(Date accessStartTime) {
          this.set("accessStartTime", accessStartTime);
      }
    
      public Date getAccessEndTime() {
          return this.getDate("accessEndTime");
      }
    
      public void setAccessEndTime(Date accessEndTime) {
          this.set("accessEndTime", accessEndTime);
      }
    
      public String getAppName() {
          return this.getStr("appName");
      }
    
      public void setAppName(String appName) {
          this.set("appName", appName);
      }
    
      public String getLogModelName() {
          return this.getStr("logModelName");
      }
    
      public void setLogModelName(String logModelName) {
          this.set("logModelName", logModelName);
      }
    
      public String getServiceName() {
          return this.getStr("serviceName");
      }
    
      public void setServiceName(String serviceName) {
          this.set("serviceName", serviceName);
      }
    
      public String getInParameterData() {
          return this.getStr("inParameterData");
      }
    
      public void setInParameterData(String inParameterData) {
          this.set("inParameterData", inParameterData);
      }
    
      public String getOutParameterData() {
          return this.getStr("outParameterData");
      }
    
      public void setOutParameterData(String outParameterData) {
          this.set("outParameterData", outParameterData);
      }
    
      public int getResultDisplay() {
          return this.getInt("resultDisplay");
      }
    
      public void setResultDisplay(int resultDisplay) {
          this.set("resultDisplay", resultDisplay);
      }
    
      public String getAbnormalDisplay() {
          return this.getStr("abnormalDisplay");
      }
    
      public void setAbnormalDisplay(String abnormalDisplay) {
          this.set("abnormalDisplay", abnormalDisplay);
      }
    
      public String getCallerUserName() {
          return this.getStr("callerUserName");
      }
    
      public void setCallerUserName(String callerUserName) {
          this.set("callerUserName", callerUserName);
      }
    
      public String getCallerUserId() {
          return this.getStr("callerUserId");
      }
    
      public void setCallerUserId(String callerUserId) {
          this.set("callerUserId", callerUserId);
      }
    
      public String getCallerIp() {
          return this.getStr("callerIp");
      }
    
      public void setCallerIp(String callerIp) {
          this.set("callerIp", callerIp);
      }
    
      public String getTakeTime() {
          return this.getStr("takeTime");
      }
    
      public void setTakeTime(String takeTime) {
          this.set("takeTime", takeTime);
      }
    
      public Boolean getModify() {
          return this.getBoolean("modify");
      }
    
      public void setModify(Boolean modify) {
          this.set("modify", modify);
      }
    
      public List<OperatorDetails> getOperatorDetailsList() {
          return (List<OperatorDetails>) this.get("operatorDetailsList");
      }
    
      public void setOperatorDetailsList(List<OperatorDetails> operatorDetailsList) {
          this.set("operatorDetailsList", operatorDetailsList);
      }
    有时候我们需要从模型中get set对应成员变量的值,这些方法写起来也相对繁琐,而且很容易写错,因为这里的get其实会去查数据库, 而有时候我们需要的是从map直接获取值而已,但如果直接使用get方法并不是从map中获取值,所以这里会有一些容易混淆的地方。

总结:从上述的几种基于iidp编写app的过程中,我们发现有很多地方都是基于string字符串的形式来表述,很容易写错, 而且没办法在编译器进行校验,这给app开发者带来了很多麻烦,降低了开发的效率。如果我们能够提供一个插件,生成获取模型filed的字符串方法,生成获取模型名称字符串方法,更通用地说生成任何字符串的方法,那么在开发阶段,只需要调用className.getFiled(),就能够获取所需的字符串,这个获取是确定性的,IDE会进行静态检查,不存在不小心输入错误的可能,提高开发者开发效率,同时也能保持代码的精简。

2、方案设计

  • 原理
    Image not found

(1):准备过程,初始化插入式注解处理器
(2):词法分析,语法分析
(3):输入到符号表
(4):注解处理
(5):分析及字节码生成
(6):标注
(7):数据流分析
(8):解语法糖
(9):字节码生成

  • 参考lombok进行代码生成
    1. 基于注解的process处理
    2. 生成指定的代码
  • 生成符号表,对特定字符串进行校验和提示
    1. 对引用的字符串进行校验
    2. 对字符串进行智能跳转

3、方案实现