历史版本49 :数据工厂数据集 返回文档
编辑时间: 内容长度:图片数:目录数: 修改原因:

目录:

1. 概述编辑

1.1 版本

报表服务器版本
JAR 包版本插件版本
10.02020-12-30V2.5
11.02020-12-30V2.5

1.2 应用场景

对 FineReport 的程序数据集接口进行改善和封装。

支撑企业IT轻松进行混合数据集成,为restful等数据结构提供高可用的数据处理服务,具有高度扩展性,通过二次开发来适应更复杂的场景。

注:1.基于此插件进行扩展开发需要具备最基础的 FineReport 插件开发能力。

       2.使用子插件时升级,需要先禁用子插件,或升级后重启服务器。

1.3 功能介绍

插件提供了额外的取数接口,将 FineReport 的数据集逻辑再次切割为两个部分:

  • 装载器:从数据来源装载数据。

  • 解析器:将装载到的数据组装成 FineReport 支持的数据模型。

2. 插件介绍编辑

2.1 插件安装

点击下载插件:数据工厂数据集

设计器插件安装方法参照:设计器插件管理

服务器安装插件方法参照:服务器插件管理

2.2 操作方法

1)插件安装后,数据集管理面板新增数据工厂数据集,如下图所示:

Snag_111cc73f.png

2)点击打开数据工厂,进入主面板,使用主面板可以应对绝大多数的场景。主面板具体功能介绍如下:

①名字:在名字栏可以设置该数据集的名称。

②装载器和解析器:

插件内置了两个装载器和三个解析器,分别如下:

a)装载器:

  • 单参数装载器:数据通过参数直接传入。( 比如 JSON、XML 等结构化数据 )

  • HTTP装载器:通过配置相关请求,从 Web 端装载数据。( 比如 JSON 服务等 )

b)解析器:

  • JSON 解析器:对结构化的 JSON 文本进行解析,生成 FineReport 支持的数据模型。

注:只能解析结构型描述的 JSON,业务型描述的 JSON 无法解析,业务型 JSON 解析依赖具体的业务的描述,需要单独实现解析器。

  • JS解析器:通过JS脚本来解析文本,生成 FineReport 支持的数据模型。

注:当数据量较大时要谨慎使用。

  • 原始解析器:原样返回数据,供调试用。 

在下拉框中选择装载方式和解析方式,输入装载器、解析器内容,具体使用方法如本文档中第3节所示。

③高级设置:当内置的装载器和解析器无法满足用户需求时,可以使用高级设置功能,该功能将在下文第4节进行详细介绍。

④缓存:缓存的意思是指将外部读取的数据暂存到缓存中,直接从缓存里面取数,而不走接口取数,以提高数据读取的速度。

      缓存有 3 个选项:无、磁盘、内存。

  • 选择无表示不启用缓存功能

  • 选择磁盘表示缓存到本地磁盘

  • 选择内存表示缓存到内存

n分钟:表示n分钟内从缓存中读取静态数据,n分钟后再次从接口重新读取数据,重新读取的数据依然在缓存中保存n分钟。

⑤参数栏:参数栏里面的参数会在后台被分为两类:

  • 第一类是装载器界面出现过的参数( ${参数} ),该类参数可被参数面板自动识别,同时解析到装载器配置的字符串里。 

    参数语法:用 ${参数名}获得参数的值,支持文本、公式等多种形式的参数,也支持从报表中读取参数。

  • 第二类则是装载器界面未出现过的,用户可通过参数面板的插入按钮来添加新的参数作为请求参数。

    在参数面板中,“参数”列中记录的是参数名;“值”列中点击按钮可以选择参数数据类型(包括字符串、整型、双精度型、日期、布尔型、公式),可以在该列中输入参数的取值。分别可以实现参数所在行的删除、上移和下移。

⑥预览:点击预览按钮,可以查看获取的数据。

 

点击数据工厂主面板的确认按钮,设计器模板数据集中出现刚刚添加的数据集,如下图所示。

3. 装载器和解析器使用示例编辑

3.1 内置装载器

3.1.1 HTTP装载器

HTTP装载器内容是一个JSON(如下图所示),里面有4个字段,分别是

  • url:请求地址 

  • type:请求类型 可选GET,POST,JSON

  • charset:编码

  • header:请求头,以json键值对形式写入

当请求类型为JSON时,还有一个专有的body字段


GET示例

{
    url:"http://example.com/",
    type:"GET", 
    charset:"UTF-8",
    header:{
        aa:${bb}
    }
}

注:示例中的http://example.com/仅表示演示,实际请按照真实接口进行调整。

在装载器中输入如上的内容,如图所示,参数面板自动识别获取参数装载器界面出现过的参数名bb,用户手动为参数bb赋值为 p_BB,并添加参数cc,为其赋值为 p_CC。

对于GET,用户在参数面板中添加的第二类额外参数会作为query参数拼接到url后。

实际请求为

url: http://example.com/?cc=p_CC  

type: get

header:{aa:p_BB}


POST示例

{
    url:"http://example.com/",
    type:"POST", 
    charset:"UTF-8",
    header:{
        aa:${bb}
    }
}

POST和GET类似,不同之处就是第二类额外参数在请求体body里。

实际请求为

url: http://example.com/ 

type: post

header: {aa:p_BB}

body为x-www-form-urlencoded模式,内容为cc=p_CC

 

JSON示例

JSON 实际上是post raw模式,第二类参数会被装载成JSON的格式作为请求体发送。对于POST请求这种方式使用的更普遍。

body有两种装载方式:

一种是类似用post的。

{
    url:"http://example.com/",
    type:"JSON", 
    charset:"UTF-8",
    header:{
        aa:${bb}
    }
}

参数      值
bb       p_BB
cc        p_CC

实实际请求为 

url: http://example.com/ 

type: post 

header: {aa:p_BB,

              content-type: json} 

body为
{
    cc: p_CC
}


第二种是直接写body键。

{
    url:"http://example.com/",
    type:"JSON", 
    charset:"UTF-8",
    header:{
        aa:${bb}
    },
    body:{
        cc:${dd}
    }
}

参数      值
bb       p_BB

dd       p_DD

实际请求为 

url: http://example.com/ 

type: post

header: {aa:p_BB,

              content-type: json}
body为
{
    cc:p_DD
}

3.1.2 单参数装载器

单参数的逻辑就比较简单了,将面板的数据参数计算后原样传给解析器,主要是调试用。

示例

在装载方式下拉框中选择“单参数”装载方式,在装载器内容框中输入 ${data}${today()} ,在右侧参数面板中设置参数情况如下所示:

参数      值
data     myData

点击预览按钮,得到如下结果:

3.2 内置解析器

3.2.1 JSON解析器

提供了不完整的JPATH语法

{
    dataPath:"root",
    showmap:"path1,value1,path2,value2"
}


dataPath取值如果是默认root则就是处理全部数据;

如果是root.key1.key2.arr[1] 就表示定位到{ key1 : { key2 : { arr:[ {},{<这里>} ] }} },这种情况下只解析定位路径下的json内容。

showmap提供列名映射,将原始的列名(path1)转换为自定义的列名(value1)。

3.2.2 JS解析器

参照JSON数据集插件中JS语法,$data变量为接受到的原始数据,最终需要通过JS生成如下一个数据表对象

返回的对象样例,column为列名,content为二维数组,保存数据集单元格的值

{
    "column":["col1", "col2", "col3"],
    "content":[
        [1, 2, 3],
        [4, 5, 6]
    ]
}

示例:具体代码请按照实际场景开发

js解析器性能比较低,数据量大的时候要慎用。

插件2.5.13版本JS解析器开始使用JDK内置的Nashorn引擎,默认支持ES5语法,高版本语法暂不支持。

3.2.3 原始数据

类比单参数装载器,返回一个只有一个单元格的数据集,内容是从装载器接受到的数据,参数面板无意义。

3.3 实际案例

获取帆软市场的插件列表

装载方式HTTP装载器

{
    url:"https://market.fanruan.com/plugins/commodity?cid=&fee=&seller=&version=&language=&searchKeyword=&_=1650781600900",
    type:"GET",
    charset:"UTF-8",
    header:{}
}

解析方式 :JSON解析器

{
    dataPath:"root.result",
    showmap:"path1,value1,path2,value2",
}

具体的装载器逻辑可以单独客制,所以并没有什么统一的写法规范。

3.4 使用案例

假设有如下接口用来获取某城市天气 http://example.com?city=北京

如果我们希望根据模板控件内容动态获取城市的数据,可以在模板中设置一个控件 city

然后在数据集装载器中进行如下配置:

方式一:

装载方式:HTTP装载器 

在装载器中添加同名参数 参数默认值置空


{
    url:"http://example.com?city=${city}",
    type:"GET",
    charset:"UTF-8",
    header:{}
}

方式二:

{
    url:"http://example.com",
    type:"GET",
    charset:"UTF-8",
    header:{}
}

参数      值
city     {置空}

这样就可以取到原始的返回值了。

假设接口返回值如下

{
    result:[
        {
            district:海淀区,
            temperature:20,
        },
        {
            district:朝阳区,
            temperature:23,
        }
    ]
}

我们可以使用json解析器取result部分,让dataPath定位到result;如果需要把列名变为自己想要的可以使用showmap字段进行映射,如下示例

{
    dataPath:"root.result",
    showmap:"district,辖区,temperature,温度"
}

返回结果如下

4.高级设置功能介绍编辑

使用主面板的高级设置功能需要有一定的插件开发能力。

假如使用者认为内置的的装载方式、解析方式不足以满足使用场景,又不想重新开发一个装载器/解析器时,可以点击数据工厂主面板的“高级设置”按钮自定义辅助步骤。

高级设置面板的作用是用来添加装载前、装载后、解析后事件作为装载方式、解析方式的补足,支持多个同步骤事件的添加。

4.1 处理流程

在“高级设置”面板最上层下拉框中可以选择“为该数据集单独设置”/“使用模板设置”/“使用全局设置”。

选择“为该数据集单独设置”时下方会出现列表,支持事件的增删改。

选择“使用模板设置”/“使用全局设置”时下方不显示列表,具体设置项需打开右侧“全局设置”/“模板设置”进行编辑。


4.2 各个事件的作用与接口描述

目前各个事件并没有实装具体的应用,仅提供了接入框架,高级步骤是装载-解析模式的增强和补全,结合开发篇来使用。

装载前事件

提供额外的参数引入 比如说token获取的逻辑可以写在自定义装载前事件里。多个装载前事件参数可以累积,重复的参数前者会被后者覆盖。

在主面板使用,使用方法同常规参数调用,右侧参数栏内默认值无效。

场景示例:  获取token,可以开发一个token获取的装载前事件,将获取的token作为参数引入主逻辑中。

装载前事件preprocessor接口描述:

package com.tptj.plugin.hg.fun;
 
import com.fr.script.Calculator;
import com.fr.stable.ParameterProvider;
 
/**
 * 预处理阶段
 */
public interface Preprocessor extends Configuration {
    String XML_TAG = "TableDataPreprocessor";
    int CURRENT_LEVEL = 1;
 
    /**
     * 预处理  可以改变参数
     * @param calculator 算子
     * @param config 面板配置内容
     * @return 注入的参数
     * @throws Exception
     */
    ParameterProvider[] process(Calculator calculator, String config) throws Exception;
}


装载后事件

提供装载器获取的原始数据的整理 通常装载器返回的是一个字符串类型,可以在这个阶段进行额外的整理行为,多个事件从上到下顺序执行。

场景示例:返回的数据虽然是json但不适合直接用json解析器,用js解析器性能又不够好的时候,可以定制一个装载后事件,把结构格式化为json解析器能够识别的样式。

装载后事件formatter接口描述:

package com.tptj.plugin.hg.fun;
 
public interface Formatter extends Configuration{
 
    String XML_TAG = "TableDataFormatter";
 
    int CURRENT_LEVEL = 1;
 
    /**
     * 数据格式化
     * @param data 装载器返回的数据
     * @param config 配置
     * @return 格式化后的数据
     * @throws Exception
     */
    Object format(Object data, String config) throws Exception;
}


解析后事件

提供解析器返回的数据集的进一步处理 比如增删列,修改列名,重排序,条件过滤等,可以在这个阶段进行额外的整理行为,多个事件从上到下顺序执行。

场景示例: 排序,筛选,取前n项,取奇数项,调整数据格式等等自由操作数据集内容。

解析后事件filter接口描述:

package com.tptj.plugin.hg.fun;
 
import com.tptj.plugin.hg.stable.SimpleDataModel;
 
/**
 *
 */
public interface Filter extends Configuration {
 
    String XML_TAG = "TableDataFilter";
 
    int CURRENT_LEVEL = 1;
 
    /**
     * 整理数据
     * @param dataModel 数据模型
     * @param config 配置
     * @return 整理后的数据
     * @throws Exception
     */
    SimpleDataModel doAction(SimpleDataModel dataModel, String config) throws Exception;
  
}


4.3 其他接口描述

装载事件loader接口描述

package com.tptj.plugin.hg.fun;
 
import com.fr.script.Calculator;
import com.fr.stable.ParameterProvider;
import com.fr.stable.fun.mark.Mutable;
 
public interface Loader extends Mutable {
     
    String XML_TAG = "TableDataLoader";
 
    int CURRENT_LEVEL = 1;
     
    /**
     * 装载数据
     * @param cal   当前算子
     * @param params    需要用到的参数
     * @param others        其他你可能用到的但是又不希望通过参数控制的配置项,即装载器面板内容
     * @return  保存数据的对象
     */
    Object load( Calculator cal, ParameterProvider[] params, String others);
     
    /**
     * 装载器的名字【显示在数据工厂配置界面的下拉列表里面的】,唯一,支持国际化的key
     * @return
     */
    String getName();
     
    /**
     *
     * @return 默认显示的配置【显示在数据工厂配置界面装载器配置文本域里面的,配置项自定义,其实就是放一些,不想写死在代码里,又不希望被参数篡改的部分】
     */
    String getDefaultConfig();
}

解析事件resolver接口描述

package com.tptj.plugin.hg.fun;
 
import com.fr.stable.fun.mark.Mutable;
import com.tptj.plugin.hg.stable.SimpleDataModel;
 
public interface Resolver extends Mutable {
     
    String XML_TAG = "TableDataResolver";
 
    int CURRENT_LEVEL = 1;
     
    /**
     * 解析数据
     * @param data 从装载器得到的原始数据
     * @param others  解析时会使用,但是不希望是参数的配置,即解析器面板内容
     * @return 解析出来的二维数据模型
     */
    SimpleDataModel parse(Object data, String others );
     
    /**
     * 解析器的名字【显示在数据工厂配置界面的下拉列表里面的】,唯一,支持国际化的key
     * @return
     */
    String getName();
      
    /**
     *
     * @return 默认显示的配置【显示在数据工厂配置界面解析器配置文本域里面的,配置项自定义,其实就是放一些,不想写死在代码里,又不希望被参数篡改的部分】
     */
    String getDefaultConfig();
     
}

功能点注册

     <extra-core>
       <TableDataLoader class="com.fr.plugin.tptj.tabledata.factory.demo.DemoLoader"/>
       <TableDataResolver class="com.fr.plugin.tptj.tabledata.factory.demo.DemoResolver"/>
       <TableDataPreprocessor class="com.fr.plugin.tptj.tabledata.factory.demo.DemoPreprocessor"/>
       <TableDataFormatter class="com.fr.plugin.tptj.tabledata.factory.demo.DemoFormatter"/>
       <TableDataFilter class="com.fr.plugin.tptj.tabledata.factory.demo.DemoFilter"/>
   </extra-core>
<dependence>
       <Item key="com.tptj.plugin.hg.tabledata.factory.v10" type="plugin"/>  <!-- 依赖于数据工厂主框架 -->
 </dependence>

我们通常继承抽象类来开发

package com.fr.plugin.tptj.tabledata.factory.demo;
 
import com.fr.base.Parameter;
import com.fr.json.JSONObject;
import com.fr.script.Calculator;
import com.fr.stable.ParameterProvider;
import com.tptj.plugin.hg.impl.AbstractPreprocessor;
 
public class DemoPreprocessor extends AbstractPreprocessor {
    @Override
    public ParameterProvider[] process(Calculator calculator, String s) {
 
        JSONObject jo = new JSONObject(s);
        ParameterProvider[] results = new ParameterProvider[1];
        results[0] = new Parameter("token", jo.get("token"));
        //jo.get("token");
        return results;
    }
 
    @Override
    public String getName() {
        return "Demo_PreProcess";
    }
 
    @Override
    public String getDefaultConfig() {
        return "{\n" +
                "   token:token\n" +
                "}";
    }
}


package com.fr.plugin.tptj.tabledata.factory.demo;
 
import com.tptj.plugin.hg.impl.AbstractFormatter;
 
public class DemoFormatter extends AbstractFormatter {
    @Override
    public Object format(Object o, String s) {
        String data = (String) o;
        String[] lines = data.split("\n");
        StringBuilder result = new StringBuilder();
        for (int i = 1; i < lines.length; i++) {
            result.append(lines[i]).append("\n");
        }
        return result.toString();
    }
 
    @Override
    public String getName() {
        return "Demo_Formatter";
    }
 
    @Override
    public String getDefaultConfig() {
        return "remove first line";
    }
}


package com.fr.plugin.tptj.tabledata.factory.demo;
 
import com.fr.general.data.TableDataException;
import com.fr.log.FineLoggerFactory;
import com.tptj.plugin.hg.impl.AbstractFilter;
import com.tptj.plugin.hg.stable.SimpleDataModel;
 
import java.util.ArrayList;
import java.util.List;
 
public class DemoFilter extends AbstractFilter {
    @Override
    public SimpleDataModel doAction(SimpleDataModel simpleDataModel, String s) {
        try {
            simpleDataModel.removeColumn(simpleDataModel.getColumnCount() - 1);
            List<Object[]> newData = new ArrayList<Object[]>();
            for(Object[] row : simpleDataModel.getDatas()) {
                Object[] newRow = new Object[row.length - 1];
                System.arraycopy(row, 0, newRow, 0, row.length - 1);
                newData.add(newRow);
            }
            simpleDataModel.setDatas(newData);
            //simpleDataModel.getDatas().remove(simpleDataModel.getColumnCount() - 1);
        } catch (TableDataException e) {
            FineLoggerFactory.getLogger().error(e, "{}", e.getMessage());
        }
        return simpleDataModel;
    }
 
    @Override
    public String getName() {
        return "Demo_Filter";
    }
 
    @Override
    public String getDefaultConfig() {
        return "remove last col";
    }
}


package com.tptj.plugin.hg.tabledata.factory;
 
import com.tptj.plugin.hg.impl.AbstractResolver;
import com.tptj.plugin.hg.stable.SimpleDataModel;
 
public class DemoResolver extends AbstractResolver {
 
    @Override
    public SimpleDataModel parse(Object jsonStr, String others) {
        return new SimpleDataModel();
    }
 
    public static final String KEY = "Demo";
    @Override
    public String getName() {
        return KEY;
    }
 
    @Override
    public String getDefaultConfig() {
 
        return "Is a demo";
    }
 
}


package com.tptj.plugin.hg.tabledata.factory.core.loader;
 
import com.fr.json.JSONObject;
import com.fr.script.Calculator;
import com.fr.stable.ParameterProvider;
import com.tptj.plugin.hg.impl.AbstractLoader;
 
public class SingleParamLoader extends AbstractLoader {
 
    @Override
    public Object load( Calculator calculator, ParameterProvider[] parameters, String others ) {
        try{
            JSONObject obj = new JSONObject(others);
            if( obj.has("data") ){
                return obj.optString("data", "{}");
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        return "";
    }
    public static final String KEY = "Plugin-Factory_Data_Set_Loader_Type_Single_Parameter";
    @Override
    public String getName() {
        return KEY;
    }
 
    @Override
    public String getDefaultConfig() {
        StringBuilder sb = new StringBuilder();
        sb.append("{\r\n")
        .append("    data:\"待解析的json字符串,支持${p1}这样的参数和公式\",\r\n")
        .append("}\r\n");
        return sb.toString();
    }
 
}



SimpleDataModel继承的标准数据集格式。负责存储数据的结构为一个二维表datas和一个一维表cols,分别是具体内
容和列名。调用对应的set,add方法即可向数据集写入数据。


自定义面板接口,很多时候,默认的文本框对于配置来说过于简陋了。如果想要更丰富的UI可以使用这个接口替换默
认的文本框。


package com.tptj.plugin.hg.fun;
 
import com.fr.stable.fun.mark.Mutable;
 
import javax.swing.*;
import java.awt.event.FocusListener;
 
public interface ConfigTable<T extends Configuration> extends Mutable {
  
    String XML_TAG = "TableDataConfigTable";
 
    int CURRENT_LEVEL = 1;
 
    String getName();
 
    /**
     * 写入数据到JPanal
     * @param config
     */
    void setValue(String config);
 
    /**
     * 读取数据到
     * @return
     */
    String getValue();
 
    JPanel getTable(FocusListener listener);
}


使用时继承抽象类


package com.tptj.plugin.hg.fun;

import com.fr.stable.fun.mark.Mutable;

import javax.swing.*;
import java.awt.event.FocusListener;

/**
 * 需要泛型继承对应的Configuration进行绑定
 * @param <T>
 */
public interface ConfigTable<T extends Configuration> extends Mutable {


    String XML_TAG = "TableDataConfigTable";

    int CURRENT_LEVEL = 1;

    String getName();

    /**
     * 写入数据到JPanal
     * @param config
     */
    void setValue(String config);

    /**
     * 读取数据到
     * @return
     */
    String getValue();

    /**
     * 获取配置面板
     * @deprecated use {@link ConfigTable#getTable(ParameterRefreshAction action)} instead
     * FocusListener触发条件有限,不足以满足所有的需求,建议使用ParameterRefreshAction手动绑定事件
     * @param listener FocusListener 事件回调
     * @return
     */
    JPanel getTable(FocusListener listener);
    /**
     * 获取配置面板
     * @param action 通过{@link ParameterRefreshAction#doAction()} 进行获取刷新事件,自己绑定
     * @return
     */
    JPanel getTable(ParameterRefreshAction action);

}


功能点注册
<extra-designer>
      <TableDataConfigTable class="com.tptj.plugin.hg.tabledata.factory.core.filter.DefaultFilterTextTable"/>
  </extra-designer>


完整开发demo见 数据工厂二次开发demo示例

FR10.0插件开发 - 插件文档

5.数据工厂数据集取数耗时分析编辑

遇到模版加载时间长的问题时,如果怀疑是数据工厂数据集执行慢的话,可以通过分析日志来分析数据工厂数据集的执行时间,包括(装载器执行时间,解析器执行时间,单个数据工厂数据集的总执行时间)

测试步骤:

日志调整成debug级别,预览模版或者数据集,在fanruan.log中搜索如下日志:
1)table data factory eval start ...    含义:---开始执行数据工厂数据集
2)start loading data,config is    含义:----开始执行装载器
3)loading data end,cost  含义:---装载器执行结束
4)start json resolving data ...  含义:--开始执行JSON解析器
5)resolving data end,cost 含义:--解析器执行结束
6)table data factory eval end,cost 含义:–数据工厂执行结束

日志中可见装载器、解析器、数据工厂数据集整体的取数耗时,若装载器执行时间较长时,一般为调用的接口响应时间过长,可通过其他方式调用接口查看响应时间进行问题定位

插件版本要求:2.5.21及以上