自定义插件

本文为您介绍自定义插件功能及不同类型插件的开发指南。

使用限制

仅独立部署支持自定义插件。

能力概述

自定义插件,是将资源的关键动作进行标准定义,比如资源包含了数据源、数据集、报表、权限等等,而关键动作则包含了创建、删除、编辑、查看等,然后将资源+操作的前置处理、后置处理的业务逻辑通过开放标准对外暴露,允许用户按需制定自定义业务逻辑,来完成一些特定的功能场景。

Quick BI的自定义插件具有以下特性:

  • 提供开放、灵活的自定义插件能力,方便外部开发人员快速构建应用,实现在自身的业务系统中,对Quick BI的资源、权限、登录等进行相应的管理。

  • 提供统一的插件配置页面,实现插件代码的快速上传、激活、更新等操作。

image.png

重要

需要注意的是:

插件启用后,被插件拦截的功能可能存在影响,建议在开发环境中进行调试验证通过后再发布到正式环境;若无开发环境,请提前知晓使用插件的风险:Quick BI作为一款工具产品,仅提供通道能力,用户在使用产品所作的任何插件上传操作,属于用户自主行为,由此产生的任何风险和问题由用户自行承担。

使用场景

目前,我们支持在以下关键动作处增加自定义处理逻辑:文件上传报表管理登录管理数据查询管理数据建模数据导出数据连接。以其中两个最常见的插件使用场景为例:

场景一:报表密级管控

  • 企业已有密级管控策略,需自定义访问规则

    企业已经拥有一套成熟的密级管控规则,需要依据自己制定的规则来控制员工对资源的访问权限,而这些规则往往可能超出了标准BI产品的权限管理范畴。

  • 企业密级策略可能动态调整

    企业已经对报表和人员进行了密级划分,且这些分类可能会随时间而变化。BI产品需要能够根据这些动态变化的密级分类,灵活地决定是否对访问进行管控。

场景二:登录插件

  • 企业级整合

    大型企业通常有复杂的身份管理系统,如Active Directory或自定义的IAM系统,甚至有自建的SSO认证规范,不支持让各三方应用按照行业规范直接对接。自定义登录插件可以让BI产品无缝集成到这些现有系统中,用户一次认证就能访问多个系统。

  • B2B集成的身份认证

    在复杂的B2B环境中,软件集成商需要同时为多个企业客户提供服务。这些企业客户往往拥有各自的身份认证机制,有些甚至采用非标准的登录协议。集成商需要在快速响应多样化的客户需求的同时保持系统的稳定性和一致性。

  • 特定行业安全需求

    某些行业(如金融、医疗)可能需要更严格的认证流程或安全措施,需要集成短信验证码、指纹识别等多因素认证方式,登录对接也需要定制化的解决方案。

  • 日志和安全审计需求

    某些企业有特殊的日志记录或审计要求,需要自定义插件来更精确地控制并记录用户的登录过程。

前提条件

只有组织管理员才能进入自定义插件页面,并上传、修改、删除驱动文件,若其他角色想要拥有相应权限,可自定义角色并赋予相应的权限。

image

自定义插件框架

整个自定义插件框架按照运行阶段可以分成开发态和运行态:

  • 开发态:由业务开发人员根据企业内部的业务场景对Quick BI插件进行选型,然后基于Quick BI提供的插件SDK进行二次开发,定义个性化业务逻辑,在开发完成后,业务人员可以在Quick BI的自定义插件管理页面进行插件的创建、上传和发布操作。

  • 运行态:由插件控制器对业务开发人员上传的插件进行注册,并将插件资源分发到Quick BI服务运行的各个机器中,以供程序执行时能够对插件进行热加载。当指定的业务逻辑被触发时,Quick BI会动态加载对应的插件资源,同时对插件的模型、参数进行解析,并最终执行插件定义的业务逻辑。

image.png

以报表密级管控这个业务场景为例,假设业务开发人员在《报表访问鉴权》这个热点上注册了自定义插件后,当业务人员查看报表时,就会触发鉴权逻辑,并且动态加载自定义插件,插件内会远程访问第三方业务系统的密级访问规则接口,并获取到接口返回值,根据返回值可以知道当前用户是否有权限打开报表,从而完成整个报表密级管控的业务逻辑。

插件使用说明

注册自定义插件

  1. 您可以按照图示步骤进入自定义插件管理界面。image

  2. 单击注册插件,勾选插件使用承诺函

    image

  3. 单击确定后进入注册插件界面并进行以下配置。

    image

    • 插件名称

      自定义填写插件名称,不得超过50个字符以及特殊字符。

    • 插件类型

      支持文件上传报表管理登录管理数据查询管理数据建模数据导出数据连接的插件,具体类型和说明请参考下面图表:

      插件类型

      功能细项

      说明

      文件上传

      数据源文件上传

      用于在数据源文件上传前的校验,如对上传的加密文件进行解密。

      批量导入用户文件上传

      用于添加用户-批量导入文件前的校验,如对上传的加密用户文件进行解密。

      报表管理

      报表访问管理

      用于报表访问前的校验,如判断某用户是否可访问,生效范围包括仪表板、电子表格、即席分析、大屏等。

      报表发布管理

      用于报表发布前的校验,如检测是否有发布权限,生效范围包括仪表板、电子表格、即席分析、大屏等。

      报表删除管理

      用于报表删除前的校验,如检测某用户是否可删除报表,生效范围包括仪表板、电子表格、即席分析、大屏等。

      登录管理

      登录方式扩展

      用于扩展行业标准登录协议外的其他登录方式,包括登录、登出的对接。

      登录策略控制

      用于增加登录策略限制,如指定IP访问、指定账号访问等。

      数据查询管理

      数据库查询结果处理

      用于对SQL查询返回的数据库原始数据结果进行处理,比如数据结果脱敏、二次加工等。

      数据建模

      自定义SQL创建数据集校验

      用于通过自定义SQL创建数据集前的校验,如校验SQL中是否存在敏感字符、控制数据查询范围等。

      创建数据集计算字段校验

      用于在数据集中新建计算字段前的校验,如校验计算字段写法的合法性及准确性。

      数据导出

      自助取数下载校验

      用于自助取数任务创建或数据下载前的校验,比如校验用户是否可下载数据。

      数据连接

      数据源连接校验

      用于数据源连接创建前的校验,比如校验数据源连接串的合法性。

    • 插件上传

      支持 jar 包和 zip 包类型的插件文件上传。

      • 插件包大小不得超过50M。

      • 插件资源存储位置可选为OSS、本地服务器、Minio、数据库等,根据您的Quick BI服务当前的配置而定。

    • 插件执行类名

      插件自定义类名,用于识别您的插件包中需要在Quick BI中执行的核心逻辑位置。例如:org.example.ExampleFileUploadHandler。

    • 上下文依赖信息

      设置插件依赖的上下文内容,用于在Quick BI中透传必要信息到插件代码中。

      例如:{"verifyCode":"f9677df9"}。

    • 插件执行顺序

      默认为1,若不修改该信息,默认按照1执行。

      • A插件设置为1,B插件设置为1,此时按照插件最新修改时间排序,后修改先执行。(修改时间逆向排序)。

      • A插件设置为1,B插件设置为2,按照插件设置的排序执行,先执行A插件,后执行B插件。

  4. 单击确定,成功注册插件。

    说明

    插件在注册后,默认是失效的状态,需要进行激活操作后才可以使用。

管理自定义插件

您可以对自定义插件进行编辑(①)激活/失效插件(②)删除(③)操作。

image

  • 单击操作列的image.png图标,更新插件

    说明

    需要先失效插件后才可以进行插件的更新操作。

    image

    插件更新支持编辑注册时的各项内容,插件在更新后默认为失效的状态,需要再次手动激活后才可以使用。

  • 单击操作列的眼睛图标,激活或失效插件。

    说明

    当插件的运行状态为失效状态时,单击image图标,激活插件;

    当插件的运行状态为激活状态时,单击image图标,失效插件。

  • 单击操作列的image.png图标,在弹出的二次确认框中单击确定后删除插件。

    image

插件开发指南

文件上传

数据源文件上传

该插件用于在数据源文件上传前的校验,如对上传的加密文件进行解密。示例代码:

file-plugin-demo.zip

这是一个文件插件开发的简单例子,它在数据源中的上传文件中使用,插件会在使用之后在您每一个上传的文件名之后添加_plg后缀。

它的主要实现代码如下:

# 实现FileUploadCustomHandler接口
public class ExampleFileUploadHandler implements FileUploadCustomHandler {

    # 日志逻辑,固定代码
    private static final Logger _log = LoggerFactory.getLogger("thirdPlugin");

    @Override
    public FileUploadParameter process(FileUploadParameter fileUploadParameter, CustomContext context) throws Throwable {
        MultipartFile file = fileUploadParameter.getFile();
        long size = file.getSize();
        _log.info("文件大小为"+size);
        fileUploadParameter.setIdentifyName(fileUploadParameter.getIdentifyName()+"_plg");
        return fileUploadParameter;
    }
}

在实现文件插件的开发中,您需要通过实现插件门面接口来构造插件功能,首先,您需要实现FileUploadCustomHandler接口,对其中的process方法重写,process方法中两个参数,分别为

FileUploadParameter:

它为插件扩展点上游传递下来的上传文件内容以及元信息,类其中包含了这些参数,您可以按需更改后,将类继续传递给下游。

/** 服务端临时文件 */
private MultipartFile file;

/** 文件类型 */
private String fileType;

/** 自定义标识名称 */
private String identifyName;

/** 分隔符 */
//private Integer delimeter = 1; // 暂时无效

/** 数据编码 */
//private String charset; // 暂时无效

/** 是否包含表头, 默认包含 */
private Boolean withHead = true;

/** 是否需要截取空格, 默认需要 */
private Boolean trim = true;

/**
 * 上传文件指定的列类型
 */
private String tableColumnModelList;

/**
 * 用户自定义物理表名
 */
private String physicalTableName;

CustomContext:

他包含了设置的上下文参数,也就是您在开放平台-自定义插件中设置的上下文依赖信息(JSON格式设置),您可以使用context.getValue("xxx")取到这些信息进行更加灵活的配置。

将参数进行处理后,您需要return改造之后的FileUploadParameter,以便扩展点下游进行处理。

这就是一个完整的文件插件开发过程。

批量导入用户文件上传

image

它的作用范围主要在此处,用于添加用户-批量导入文件前的校验,如对上传的加密用户文件进行解密。示例代码

user-plugin-context .zip

这是一个文件解码的例子,一些文件被zip压缩并重命名了xls形式,需要解码其中内容后录入。

主要实现代码如下:

# 实现UserFileUploadCustomHandler接口
public class ExampleContextHandler implements UserFileUploadCustomHandler {

    private static final Logger _log = LoggerFactory.getLogger("thirdPlugin");

    @Override
    public MultipartFile process(MultipartFile fileInfo, CustomContext context) throws Throwable {
        _log.info("ExampleContextHandler process");
        return unzipMultipartFile(fileInfo);
    }

    public MultipartFile unzipMultipartFile(MultipartFile multipartFile) throws IOException {
        List<MultipartFile> unzippedFiles = new ArrayList<>();
        ZipInputStream zipInputStream = new ZipInputStream(multipartFile.getInputStream());
        ZipEntry zipEntry = zipInputStream.getNextEntry();

        byte[] buffer = new byte[1024];
        while (zipEntry != null) {
            String fileName = zipEntry.getName();
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

            int length;
            while ((length = zipInputStream.read(buffer)) > 0) {
                byteArrayOutputStream.write(buffer, 0, length);
            }

            // Use the MockMultipartFile to create a new MultipartFile from the unzipped bytes
            MockMultipartFile unzippedFile = new MockMultipartFile(
                    "file",
                    fileName,
                    multipartFile.getContentType(),
                    byteArrayOutputStream.toByteArray());

            unzippedFiles.add(unzippedFile);

            byteArrayOutputStream.close();
            zipEntry = zipInputStream.getNextEntry();
        }
        zipInputStream.closeEntry();
        zipInputStream.close();

        return unzippedFiles.get(0);
    }
}

它其中仅包含了两个参数:

fileInfo:

此项为上传的MultipartFile文件,可以根据需要对它处理或校验后传递给下游

CustomContext:

他包含了设置的上下文参数,也就是您在开放平台-自定义插件中设置的上下文依赖信息(JSON格式设置),您可以使用context.getValue("xxx")获取这些信息进行更加灵活的配置。

将参数进行处理后,您需要return true或者false,为true的话可以正常访问,为false的话会提示用户“您无当前报表的访问权限,请申请权限”。

这就是一个完整的批量导入用户文件上传插件开发过程。

报表管理

报表访问管理

该插件用于报表访问前的校验,如判断某用户是否可访问,生效范围包括仪表板、电子表格、即席分析、大屏等。示例代码:

report-demo .zip

这是一个报表访问插件开发的简单例子,它的功能为当报表的名称前缀为密级且用户昵称的前缀为非密时,拦截用户的访问,提示您无访问当前报表的权限。

报表访问管理的主要实现代码如下

# 实现ReportAuthCustomHandler接口
public class ExampleReportViewPluginHandler implements ReportAuthCustomHandler {
    private static final Logger _log = (Logger) LoggerFactory.getLogger("thirdPlugin");

    @Override
    public ReportAuthParameter process(ReportAuthParameter reportAuthParameter, CustomContext context) throws Throwable {
        String reportName = reportAuthParameter.getReportTreeParameter().getName();
        String nick = reportAuthParameter.getUserInfoParameter().getNickName();
        if (reportName.startsWith("密级") && nick.startsWith("非密")) {
            reportAuthParameter.setAllowed(false);
        }
        return reportAuthParameter;
    }
}

在实现报表访问插件的开发中,您需要通过实现插件门面接口来构造插件功能,首先,您需要实现ReportAuthCustomHandler接口,对其中的process方法重写,process方法中两个参数,分别为:

ReportAuthParameter:

它包含了插件扩展点上游传递下来的报表信息以及用户身份信息,您可以根据这些信息判断是否将当前报表的查看权限开放给当前用户:

ReportTreeParameter是其中的报表信息类,有如下属性:

public class ReportTreeParameter {
    /**
     * 报表id
     */
    private String treeId;
    /**
     * 名称 		db_column: name
     */
    private String name;
    /**
     * owner工号 		db_column: owner_num
     */
    private String ownerNum;
    /**
     * owner名字 		db_column: owner_name
     */
    private String ownerName;
    /**
     * 创建日期 		db_column: gmt_create
     */
    private Date gmtCreate;
    /**
     * 修改日期 		db_column: gmt_modified
     */
    private Date gmtModified;
    /**
     * 工作空间id 		db_column: workspace_id
     */
    private String workspaceId;
    /**
     * 授权级别
     */
    private Integer authLevel ;

    /**
     * 子类型: DashboardSubTypeEnum
     */
    private Integer subType;

    /**
     * 描述
     */
    private String description;
    /**
     * 是否分享给所有工作空间成员
     */
    private Integer shareToWorkspace;
}

UserInfoParameter是其中的用户信息类,有如下属性:

public class UserInfoParameter {
    /**
     * email
     */
    private String userEmail;
    /**
     * phone
     */
    private String userPhone;
    /**
     * 昵称
     */
    private String nickName;
    /**
     * 用户名 登录名
     */
    private String userName;

    /**
     * 用于前端显示
     */
    private String displayName;

    /**
     * baseId
     */
    private String baseId;

    /**
     * 平台通信标识
     */
    private String userId;

    /**
     * 主子账号类型
     */
    private Integer accountType;

    /**
     * 若加入了组织 须 设置组织id
     */
    private String organizationId;

    /**
     * 账号是否被删除。
     */
    private Boolean isDeleted;
}

CustomContext:

他包含了设置的上下文参数,也就是您在开放平台-自定义插件中设置的上下文依赖信息(JSON格式设置),您可以使用context.getValue("xxx")获取这些信息进行更加灵活的配置。

将参数进行处理后,您需要return true或者false,为true的话可以正常访问,为false的话会提示用户“您无当前报表的访问权限,请申请权限”。

这就是一个完整的报表访问插件开发过程。

报表发布管理

该插件用于报表发布前的校验,如检测是否有发布权限,生效范围包括仪表板、电子表格、即席分析、大屏等。示例代码:

report_publish_plugin .zip

重要

注意:如需实现openapi的代码块,需要自行引入openapiSDK依赖包,自行调整代码才能生效。

这是一个报表发布插件开发的简单例子,它的功能为当前操作人与报表所有者不是同一个人时,提示用户权限不足。

报表访问管理的主要实现代码如下:

# 实现ReportPublishCustomHandler接口
public class ExampleReportPublishHandler implements ReportPublishCustomHandler {
    private static final Logger _log = (Logger) LoggerFactory.getLogger("thirdPlugin");
    // 您的开放平台->组织识别码的ak/sk
    final static String ak = "2fe4f*****************7af083ea";
    final static String sk = "9ee1*******************015f";

    // 您的qbi服务host 域名
    final static String host = "http://local.****.test/";
    // 初识化openapi客户端
//    private static IOpenAPIClient client =
//            HttpClientFactory.buildDefaultClient(host, rootPath, encodeRootPath,
//                    ak, sk, true);

    /**
     * 模拟调用openapi
     * @param
     * @return
     * @throws SDKException
     */
//    private String testGet(String worksId) throws SDKException {
//        HttpRequest request = HttpRequest.build()
//                .setUri("/openapi/v2/works/query")
//                .setMethod(HttpMethod.GET);
//        request.addParameter("worksId", worksId);
//        return JSON.parseObject(client.syncExecute(request)).getString("ownerId") ;
//    }
  
    @Override
    public ReportPublishParameter process(ReportPublishParameter reportPublishParameter, CustomContext customContext) {
//        if (reportPublishParameter.getWorksId() != null) {
//            String s = testGet(reportPublishParameter.getWorksId());
//            if (!reportPublishParameter.getUserId().equals(s)){
//                throw new PluginCustomException(ReportPublishError.USER_NO_PERMISSION);
//            }
//        }
        //校验通过return null即可
        return null;
    }
}

在实现报表发布插件的开发中,您需要通过实现插件门面接口来构造插件功能,首先,您需要实现ReportPublishCustomHandler接口,对其中的process方法重写,process方法中两个参数,分别为:

ReportPublishParameter

它包含了插件扩展点上游传递下来的报表ID信息以及用户ID身份信息,您可以根据这些信息再配合OpenAPI判断是否将当前报表的发布权限开放给当前用户:

ReportPublishParameter有如下属性:

public class ReportPublishParameter {
    /**
     * 当前操作者的userId
     */
    private String userId;

    /**
     * 报表id
     */
    private String worksId;
}

CustomContext

它包含了设置的上下文参数,也就是您在开放平台-自定义插件中设置的上下文依赖信息(JSON格式设置),您可以使用context.getValue("xxx")获取这些信息进行更加灵活的配置。

将参数进行相关校验后,您只需要return null即可,如果中途不通过,则直接throw new PluginCustomException(ReportPublishError.USER_NO_PERMISSION)就可以让报表发布失败,并提示相关失败原因。

这就是一个完整的报表发布插件开发过程。

报表删除管理

该插件用于报表删除前的校验,如检测某用户是否可删除报表,生效范围包括仪表板、电子表格、即席分析、大屏等。示例代码

report_delete_plugin.zip

重要

注意:如需实现openapi的代码块,需要自行引入OpenAPISDK依赖包,自行调整代码才能生效。

这是一个报表删除插件开发的简单例子,它的功能为当前操作人与报表所有者不是同一个人时,提示用户权限不足。

报表删除管理的主要实现代码如下:

# 实现ReportDeleteCustomHandler接口
public class ExampleReportDeleteHandler implements ReportDeleteCustomHandler {

    private static final Logger _log = (Logger) LoggerFactory.getLogger("thirdPlugin");
    // 您的开放平台->组织识别码的ak/sk
    final static String ak = "2fe4f*****************7af083ea";
    final static String sk = "9ee1*******************015f";

    // 您的qbi服务host 域名
    final static String host = "http://local.****.test/";
    // 初识化openapi客户端
//    private static IOpenAPIClient client =
//            HttpClientFactory.buildDefaultClient(host, rootPath, encodeRootPath,
//                    ak, sk, true);

    /**
     * 模拟调用openapi
     * @param
     * @return
     * @throws SDKException
     */
//    private String testGet(String worksId) throws SDKException {
//        HttpRequest request = HttpRequest.build()
//                .setUri("/openapi/v2/works/query")
//                .setMethod(HttpMethod.GET);
//        request.addParameter("worksId", worksId);
//        return JSON.parseObject(client.syncExecute(request)).getString("ownerId") ;
//    }
  
    @Override
    public ReportDeleteParameter process(ReportDeleteParameter reportDeleteParameter, CustomContext customContext) throws PluginCustomException {
//        if (reportDeleteParameter.getWorksId() != null) {
//            String s = testGet(reportDeleteParameter.getWorksId());
//            if (!reportDeleteParameter.getUserId().equals(s)){
//                throw new PluginCustomException(ReportDeleteError.USER_NO_PERMISSION);
//            }
//        }
//        校验通过return null即可
        return null;
    }
}

在实现报表发布插件的开发中,您需要通过实现插件门面接口来构造插件功能,首先,您需要实现ReportDeleteCustomHandler接口,对其中的process方法重写,process方法中两个参数,分别为:

ReportDeleteParameter

它其中包含了插件扩展点上游传递下来的报表ID信息以及用户ID身份信息,您可以根据这些信息再配合OpenAPI判断是否将当前报表的删除权限开放给当前用户。

ReportDeleteParameter有如下属性:

public class ReportDeleteParameter {
    /**
     * qbi的userId
     */
    private String userId;

    /**
     * 报表id
     */
    private String worksId;
}

CustomContext

它包含了设置的上下文参数,也就是您在开放平台-自定义插件中设置的上下文依赖信息(JSON格式设置),您可以使用context.getValue("xxx")取到这些信息进行更加灵活的配置。

将参数进行相关校验后,您只需要return null即可,如果中途不通过,则直接throw new PluginCustomException(ReportDeleteError.USER_NO_PERMISSION)就可以让报表删除失败,并提示相关失败原因。

这就是一个完整的报表删除插件开发过程。

登录管理

登录方式扩展

实现步骤说明

login_plugin_demo.zip

这是一个登录插件开发的简单例子,用于扩展行业标准登录协议外的其他登录方式,包括登录、登出的对接。以企业拥有自定义的登录流程为例:

企业使用登录插件时需要先重定向至身份认证服务器地址,后续通过企业身份认证服务器回调Quick BI侧的插件登录回调接口([Quick BI域名]/login/plugin/verify传递认证成功的用户信息,最后在登出时需要重定向至指定地址。针对这种场景,登录管理插件的核心代码实现如下:

public class ExampleLoginCustomHandler extends AbstractLoginCustomHandler {

    public static final String AUTH_HOST = "http://example.auth.login.com";
    public static final String REDIRECT_HOST = "http://example.reidrect.com";
    private static final Logger _log = LoggerFactory.getLogger("thirdPlugin");
  
    @Override
    public String getAuthServerUrl(HttpServletRequest request) {
        //您域名下的身份认证服务器地址,一般为您系统的登录页
        //当您与Quick BI进行身份认证时,无需跳转至您的登录页,则直接返回「插件登录回调接口」即可
        return AUTH_HOST;
    }
    @Override
    public PluginUserAccount login(HttpServletRequest request, CustomContext customContext) throws PluginLoginRedirectException, PluginCustomException{
        //获取您身份认证服务器回调传递的用户身份信息
        String accountName = Optional.ofNullable(request.getParameter("accountName")).orElse("插件用户名");
        String accountId = Optional.ofNullable(request.getParameter("accountId")).orElse("123456");
        String nick = Optional.ofNullable(request.getParameter("nick")).orElse("插件用户昵称");
  
        _log.info("[custom login] query user info with: account name = {}", accountName);
        PluginUserAccount userAccount = new PluginUserAccount();
        //设置用户信息
        userAccount.setAccoutId(accountId);
        userAccount.setAccountName(accountName);
        userAccount.setNick(accountName);
        return userAccount;
    }
    @Override
    public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, CustomContext customContext) throws PluginLoginRedirectException {
        //用户登出操作
        _log.info("执行用户态登出操作");
        throw new PluginLoginRedirectException(null, REDIRECT_HOST);
    }
}

当用户使用插件登录时,Quick BI会自动读取插件内部登录方法传来的用户身份信息并作为账户登录Quick BI。在具体实现插件时,需要关注以下细节:

首先,需要继承AbstractLoginCustomHandler抽象类,对其中的getAuthServerUrl、login、logout方法重写,可以通过类的常量PLUGIN_VERIFY_URI获取插件登录的回调URI。

  1. getAuthServerUrl方法

入参有一个参数HttpServletRequest:它提供了一系列的方法,以便在Servlet中获取请求的信息和处理请求的参数。

用户需要在getAuthServerUrl方法中指定想要跳转的身份认证服务地址,该地址会在Quick BI登录页选择插件登录时进行跳转。

  1. login方法

入参中有两个参数,分别为:

  • HttpServletRequest:用来处理HTTP请求,它提供了一系列的方法,以便在Servlet中获取请求的信息和处理请求的参数。

  • CustomContext :这个参数类封装了用户在开放平台-自定义插件设置时定义的上下文参数,这些参数以JSON格式存在。用户可以使用 context.getValue("xxx") 方法获取这些设置,从而实现更为灵活和定制化的配置。

返回参数是PluginUserAccount,它是返回的用户信息类,包含如下属性:

public class PluginUserAccount {
    /**
     * 用户唯一id
     */
    private String accoutId;
    /**
     * 用户名
     */
    private String accountName;
    /**
     * 用户昵称
     */
    private String nick;
    /**
     * 用户其他信息
     */
    private Map<String, String> extInfo;
}
  1. logout方法

入参中有三个参数,分别为:

  • HttpServletRequest:用来处理HTTP请求,它提供了一系列的方法,以便在Servlet中获取请求的信息和处理请求的参数。

  • HttpServletResponse: 描述HTTP响应消息的对象,主要用于在服务器端处理 HTTP 响应。

  • CustomContext :这个参数类封装了用户在开放平台-自定义插件设置时定义的上下文参数,这些参数以JSON格式存在。用户可以使用 context.getValue("xxx") 方法获取这些设置,从而实现更为灵活和定制化的配置。

异常和重定向

可以通过实现BasicError接口定义一些自定义登录错误枚举配合插件重定向异常PluginLoginRedirectException进行抛出,抛出的异常会在Quick BI登录层进行捕获跳转。

实现BasicError接口的代码例子:

public enum PluginLoginError implements BasicError {
    /**
     * 登录校验异常
     */
    LOGIN_ACCOUNT_PASSWORD_ERROR("PLUGIN_002001", "账号或密码错误"),
    LOGIN_ACCOUNT_NOT_EXIST("PLUGIN_002002", "账号不存在"),
    LOGIN_CIPHER_ERROR("PLUGIN_002003", "验证码错误"),;

    private final String code;
    private final String message;


    PluginLoginError(String code, String message) {
        this.code = code;
        this.message = message;
    }

    @Override
    public String getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    @Override
    public String getMessage(Locale locale) {
        return null;
    }

    @Override
    public Class<? extends BasicException> getExceptionClass() {
        return null;
    }

    @Override
    public Class<? extends BasicThrowException> getThrowExceptionClass() {
        return null;
    }
}

通过抛出PluginLoginRedirectException可以让Quick BI重定向到指定的地址,具体代码如下:

public class PluginLoginRedirectException extends BasicException {
    private String redirectUrl;

    public PluginLoginRedirectException(BasicError error) {
        super((BasicError)(null == error ? PluginAuthxError.REDIRECT_URL_DEBUG : error));
    }

    public PluginLoginRedirectException(BasicError error, String redirectUrl) {
        super((BasicError)(null == error ? PluginAuthxError.REDIRECT_URL_DEBUG : error));
        this.redirectUrl = redirectUrl;
    }

    public String getRedirectUrl() {
        return this.redirectUrl;
    }
}

登录策略控制

示例代码:

login_access_plugin.zip

登录策略插件旨在扩展额外的登录校验行为,比如对用户名匹配某些规则的用户拦截访问,或者对符合某些规则的IP地址拦截访问,这些规则可以灵活自定义。

这是一个登录策略控制插件的简单例子,它的功能是对账户名(account_name)为zhangsan的用户,或者IP192.168.12.21的用户拦截访问。

它的主要实现代码如下:

public class LoginAccessPluginHandler implements LoginAccessCustomHandler {

    private static final Logger _log = (Logger) LoggerFactory.getLogger("thirdPlugin");

    @Override
    public LoginAccessParameter process(LoginAccessParameter loginAccessParameter, CustomContext customContext) throws Throwable {
        if (loginAccessParameter.getAccountName().equals("zhangsan")) {
            loginAccessParameter.setAllowed(false);
        }
        if (loginAccessParameter.getRealIp().equals("192.168.12.21")){
            loginAccessParameter.setAllowed(false);
        }
        return loginAccessParameter;
    }
}

在实现文件插件的开发中,您需要通过实现插件门面接口来构造插件功能,首先,您需要实现LoginAccessCustomHandler接口,对其中的process方法重写,process方法中两个参数,分别为:

loginAccessParameter:

为插件扩展点上游传递下来的当前用户身份信息、访问信息,您可以根据这些信息在插件中对访问的行为进行控制。

// quickbi获取到的ip,是否能准确获取也和您的负载均衡配置有关
private String realIp;
// 当前命中的登录策略id
private String strategyId;
// 当前用户的accountId
private String accountId;
// 当前用户的昵称
private String nick;
// 当前用户的账号名
private String accountName;
// 账号类型 3:自建账号 6:三方账号
private int accountType;
// 是否允许访问(用于向下游传递)
private boolean isAllowed;

CustomContext:

它包含了设置的上下文参数,也就是您在开放平台-自定义插件中设置的上下文依赖信息(JSON格式设置),您可以使用context.getValue("xxx")取到这些信息进行更加灵活的配置。

将参数进行处理后,您需要return改造之后的LoginAccessParameter,以便扩展点下游进行处理。

这就是一个完整的登录策略控制插件的开发过程。

数据查询管理

数据库查询结果处理

该插件用于对SQL查询返回的数据库原始数据结果进行处理,比如数据结果脱敏、二次加工等。

注意:该插件仅在直连数据库查询时才会生效,例如某次请求查询经过了缓存,则该次查询不会被插件拦截处理。

示例代码:

db-query-result-plugin.zip

这是一个数据库查询结果处理插件开发的简单例子,它的功能是对SQL查询返回的数据库原始数据结果进行处理,当返回结果出现火车时,则将火车进行MD5加密。

它的主要实现代码如下:

public class DemoRawQueryResultProcessV2Handler implements DataOriginalResultCustomHandler {
    private static final Logger _log = (Logger) LoggerFactory.getLogger("thirdPlugin");

    @Override
    public DataOriginalResultParameter process(DataOriginalResultParameter parameter, CustomContext customContext) throws Throwable {
        try {
            List<List<Object>> rows = parameter.getRows();
            // // 没有行值时,直接返回
            if (rows.isEmpty()) {
                _log.info("DemoRawQueryResultProcessV2Handler:没有行值");
                return parameter;
            }
            List<List<Object>> rowsNew = new ArrayList<>(rows.size());
            for (List<Object> row : rows) {
                for (Object obj : row) {
                    if (obj instanceof String) {
                        String rowValue = (String) obj;
                        if (rowValue.equals("火车")) {
                            rowValue = DigestUtils.md5Hex(rowValue);
                            row.set(row.indexOf(obj), rowValue);
                        }
                    }
                }
                rowsNew.add(row);
            }
            parameter.setRows(rowsNew);
            _log.info("执行插件逻辑成功");

        } catch (Exception e) {
            _log.error("执行插件逻辑失败", e);
            throw new PluginCustomException(CustomHandlerError.PRE_CHECK_ERROR.getCode(), "插件执行失败");
        }
        return parameter;

    }
}

在实现文件插件的开发中,您需要通过实现插件门面接口来构造插件功能,首先,您需要实现DataOriginalResultCustomHandler接口,对其中的process方法重写,process方法中两个参数,分别为:

DataOriginalResultParameter:

它为插件扩展点上游传递下来的数据库原始查询结果信息及当前用户身份信息,您可以根据这些信息在插件中对数据库查询结果进行处理:

/**
 * QBI用户ID
 */
private String userId;

/**
 * 行头
 */
private List<HeaderItem> header;

/**
 * 数据
 */
private List<List<Object>> rows;

/**
 * 总条数
 */
private Long total;

/**
 * 查询结果的额外信息,辅助排查问题
 */
private ExtraInfo extraInfo;

其中ExtraInfo的参数值如下:

/**
 * 查询的数据源ID
 */
private String dsId;

/**
 * 查询的数据集ID
 */
private String cubeId;

/**
 * 如果是odps数据源会有logview
 */
private String logView;

/**
 * 执行的具体SQL
 */
private String sql;

/*
 * 数据源类型
 */
private String dsType;

HeaderItem参数值如下:

/**
 * 行头(别名)
 */
private String value;
/**
 * 类型
 */
private String type;

CustomContext:

它包含了设置的上下文参数,也就是您在开放平台-自定义插件中设置的上下文依赖信息(json格式设置),您可以使用context.getValue("xxx")取到这些信息进行更加灵活的配置.

将参数进行处理后,您需要return改造之后的DataOriginalResultParameter,以便扩展点下游进行处理。

这就是一个完整的数据库查询结果处理插件的开发过程。

数据建模

自定义SQL创建数据集校验

该插件用于通过自定义SQL创建数据集前的校验,如校验SQL中是否存在敏感字符、控制数据查询范围等。示例代码

customizesql_plugin.zip

这是一个自定义SQL创建数据集校验插件开发的简单例子,它的功能为当sqlselect * from 222时提示插件参数校验不合法。

自定义SQL创建数据集校验插件的主要实现代码如下:

# 实现CustomizeSqlCustomHandler接口
/**
 * @author demo
 * 重写process即可,该插件仅做校验使用,不可改sql参数
 * @date 2024/9/20
 */
public class ExampleCustomizeSqlHandler implements CustomizeSqlCustomHandler {

    private static final Logger _log = (Logger) LoggerFactory.getLogger("thirdPlugin");


    @Override
    public CustomizeSqlParameter process(CustomizeSqlParameter customizeSqlParameter, CustomContext customContext) throws Throwable {
        if (customizeSqlParameter.getSql().equalsIgnoreCase("select * from 222")){
            throw new PluginCustomException(CustomizeSqlError.PARAM_ERROR);
        }
        return customizeSqlParameter;
    }
}

在实现自定义SQL创建数据集校验插件的开发中,您需要通过实现插件门面接口来构造插件功能,首先,您需要实现CustomizeSqlCustomHandler接口,对其中的process方法重写,process方法中两个参数,分别为

CustomizeSqlParameter:

它包含了插件扩展点上游传递下来的SQL信息,您可以根据SQL信息做相应的鉴权:

CustomizeSqlParameter有如下属性:

public class CustomizeSqlParameter {

    /**
     * 自定义sql
     */
    private String sql;
}

CustomContext:

它包含了设置的上下文参数,也就是您在开放平台-自定义插件中设置的上下文依赖信息(JSON格式设置),您可以使用context.getValue("xxx")取到这些信息进行更加灵活的配置。

将参数进行相关校验后,您只需要return null;即可,如果中途不通过,则直接throw new PluginCustomException(CustomizeSqlError.PARAM_ERROR)就可以让自定义sql保存失败,并提示相关失败原因;

这就是一个完整的自定义SQL创建数据集校验插件开发过程。

创建数据集计算字段校验

该插件用于在数据集中新建计算字段前的校验,如校验计算字段写法的合法性及准确性。示例代码:

calculated_field_plugin.zip

这是一个创建数据集计算字段校验插件开发的简单例子,它的功能为当计算字段备注为123456时提示插件参数校验不合法。

创建数据集计算字段校验插件的主要实现代码如下:

# 实现CalculatedFieldCustomHandler接口
/**
 * @author demo
 * 重写process即可,该插件仅做校验使用,相关参数
 * @date 2024/11/14
 */
public class ExampleCalculatedFieldHandler implements CalculatedFieldCustomHandler {

    private static final Logger _log = (Logger) LoggerFactory.getLogger("thirdPlugin");


    @Override
    public CalculatedFieldParameter process(CalculatedFieldParameter calculatedFieldParameter, CustomContext customContext) throws Throwable {
        Thread.sleep(10000);
        if (calculatedFieldParameter.getComments().equalsIgnoreCase("123456")) {
            throw new PluginCustomException(CalculatedFieldError.PARAM_ERROR);
        }
        return calculatedFieldParameter;
    }
}

在实现创建数据集计算字段校验插件的开发中,您需要通过实现插件门面接口来构造插件功能,首先,您需要实现CalculatedFieldCustomHandler接口,对其中的process方法重写,process方法中两个参数,分别为:

CalculatedFieldParameter:

它包含了插件扩展点上游传递下来的计算字段的相关信息,您可以根据这些信息做相应的鉴权。

CalculatedFieldParameter有如下属性:

public class CalculatedFieldParameter {

    /**
     * 计算字段名称
     */
    private String caption;
    /**
     * 计算字段描述
     */
    private String comments;
    /**
     * 计算字段表达式
     */
    private String expression;
}

CustomContext:

它包含了设置的上下文参数,也就是您在开放平台-自定义插件中设置的上下文依赖信息(JSON格式设置),您可以使用context.getValue("xxx")取到这些信息进行更加灵活的配置。

将参数进行相关校验后,您只需要return null;即可,如果中途不通过,则直接throw new PluginCustomException(CalculatedFieldError.PARAM_ERROR)就可以让计算字段保存失败,并提示相关失败原因。

这就是一个完整的创建数据集计算字段校验插件开发过程。

数据导出

自助取数下载校验

该插件用于自助取数任务创建或数据下载前的校验,比如校验用户是否可下载数据。示例代码

downloads_plugin.zip

重要

注意:如需实现openapi的代码块,需要自行引入OpenAPISDK依赖包,自行调整代码才能生效。

这是一个自助取数下载校验插件开发的简单例子,它的功能为当文件名以wkb结尾时提示插件校验参数错误,当前操作人与报表所有者为同一个人时,提示用户权限不足。

自助取数下载校验插件的主要实现代码如下:

# 实现DownloadsCustomHandler接口
public class ExampleDownloadsHandler implements DownloadsCustomHandler {

    private static final Logger _log = (Logger) LoggerFactory.getLogger("thirdPlugin");
    // 您的开放平台->组织识别码的ak/sk
    final static String ak = "2fe4f*****************7af083ea";
    final static String sk = "9ee1*******************015f";

    // 您的qbi服务host 域名
    final static String host = "http://local.****.test/";
    // 初识化openapi客户端
//    private static IOpenAPIClient client =
//            HttpClientFactory.buildDefaultClient(host, rootPath, encodeRootPath,
//                    ak, sk, true);

    /**
     * 模拟调用openapi
     * @param
     * @return
     * @throws SDKException
     */
//    private String testGet(String worksId) throws SDKException {
//        HttpRequest request = HttpRequest.build()
//                .setUri("/openapi/v2/works/query")
//                .setMethod(HttpMethod.GET);
//        request.addParameter("worksId", worksId);
//        return JSON.parseObject(client.syncExecute(request)).getString("ownerId") ;
//    }
  
     @Override
    public DownloadsParameter process(DownloadsParameter downloadsParameter, CustomContext customContext) throws Throwable {
        if (downloadsParameter.getDownloadName().endsWith("wkb")) {
            throw new PluginCustomException(DownloadsError.PARAM_ERROR);
        }
//        else if (downloadsParameter.getUserId().equals(testGet(downloadsParameter.getPageId()))) {
//            throw new PluginCustomException(DownloadsError.USER_NO_PERMISSION);
//        }
        return null;
    }
}

在实现自助取数下载校验插件的开发中,您需要通过实现插件门面接口来构造插件功能,首先,您需要实现DownloadsCustomHandler接口,对其中的process方法重写,process方法中两个参数,分别为:

DownloadsParameter:

它其中包含了插件扩展点上游传递下来的报表ID信息以及用户ID身份信息等,您可以根据这些信息再配合OpenAPI判断是否允许当前用户进行自助取数下载:

ReportDeleteParameter有如下属性:

public class DownloadsParameter {
    /**
     * 取数任务名称
     */
    private String downloadName;
    /**
     * 操作人userId
     */
    private String userId;
    /**
     * 操作人账号
     */
    private String accountName;
    /**
     * 数据集id
     */
    private String cubeId;
    /**
     * 自助取数id
     */
    private String pageId;
    /**
     * 文件类型
     */
    private String fileType;
}

CustomContext:

它包含了设置的上下文参数,也就是您在开放平台-自定义插件中设置的上下文依赖信息(JSON格式设置),您可以使用context.getValue("xxx")获取这些信息进行更加灵活的配置。

将参数进行相关校验后,您只需要return null即可,如果中途不通过,则直接throw new PluginCustomException(DownloadsError.USER_NO_PERMISSION)就可以让自助取数任务创建失败,并提示相关失败原因。

这就是一个完整的自助取数下载校验插件开发过程。

数据连接

数据源连接校验

该插件用于数据源连接创建前的校验,比如校验数据源连接串的合法性。示例代码

connection_plugin.zip

这是一个数据源连接校验插件开发的简单例子,它的功能为当密码为123456时将密码改写为1234567;当密码为2211时将密码改写为123456,其他情况时则抛错提示插件参数校验不合法。

数据源连接校验插件的主要实现代码如下:

# 实现ConnectionCustomHandler接口
/**
 * @author demo
 * 重写process即可,可改写ConnectionParameter类里的所有参数
 * 改写完后返回即可,返回后qbi会执行qbi相关的合法性校验
 * @date 2024/9/20
 */
public class ExampleConnectionHandler implements ConnectionCustomHandler {

    private static final Logger _log = (Logger) LoggerFactory.getLogger("thirdPlugin");


    @Override
    public ConnectionParameter process(ConnectionParameter connectionParameter, CustomContext customContext) throws Throwable {
        if (connectionParameter.getPassword().equalsIgnoreCase("123456")) {
            connectionParameter.setPassword("1234567");
        }else if (connectionParameter.getPassword().equalsIgnoreCase("2211")) {
            connectionParameter.setPassword("123456");
        }else {
            throw new PluginCustomException(ConnectionError.PARAM_ERROR);
        }
        return connectionParameter;
    }
}

在实现数据源连接校验插件的开发中,您需要通过实现插件门面接口来构造插件功能,首先,您需要实现ConnectionCustomHandler接口,对其中的process方法重写,process方法中两个参数,分别为:

ConnectionParameter:

它包含了插件扩展点上游传递下来的数据源配置的相关信息以及用户ID身份信息等,您可以根据这些信息做一些相应的调整以及鉴权:

ConnectionParameter有如下属性:

public class ConnectionParameter {
    /**
     * 操作用户
     */
    private String creatorId;

    /**
     * 修改人
     */
    private String modifyUser;

    /**
     * 用户所属空间ID
     */
    private String workspaceId;

    /**
     * 数据库连接串地址(域名或ip)
     */
    private String address;

    /**
     * 端口
     */
    private String port;

    /**
     * 连接数据库用户名(加密存储)
     */
    private String userName;

    /**
     * 连接用户名对应的登录密码(加密存储)
     */
    private String password;

    /**
     * 数据库schema,仅对支持schema的数据库需要设置<br>
     * example:sqlserver 默认使用dbo;mysql不支持schema
     */
    private String schema;

    /**
     * 数据库实例,对应数据库名称,ODPS为project
     */
    private String instance;

    /**
     * 获取连接串详情时,odps供前端展示使用
     */
    private String project;

    /**
     * 数据源前端展示名称
     */
    private String showName;

    /**
     * 购买实例的accessId, (加密存储)
     */
    private String accessId;
    /**
     * 购买实例的accessKey, (加密存储)
     */
    private String accessKey;

    /**
     * 实例id
     */
    private String instanceId;

    /**
     * region
     */
    private String region;
}

CustomContext:

它包含了设置的上下文参数,也就是您在开放平台-自定义插件中设置的上下文依赖信息(JSON格式设置),您可以使用context.getValue("xxx")取到这些信息进行更加灵活的配置。

将参数进行相关校验或者调整后,您只需要return connectionParameter;即可,如果中途不通过,则直接throw new PluginCustomException(ConnectionError.PARAM_ERROR)就可以让数据源连接失败,并提示相关失败原因。

这就是一个完整的数据源连接校验插件开发过程。