有问题请联系管理员stephenking@fanruan.com

Page tree
Skip to end of metadata
Go to start of metadata

核心目标:掌握基本的FR取数逻辑,回顾前面教程中的IDEA基本使用。

学习时间:60分钟


        FR所有的产品线都依托于一个基础,就是数据,没有足够的数据支撑,就算再好的产品也无法给我们提供所需要的信息。FR产品本身支持常规各类数据库数据,但是我们实际分析中总是会遇到一些运行数据,网络数据,第三方数据等等的数据诉求。这时候就需要我们对FR的数据来源进行插件扩展。数据扩展在我们接触FR初期,我们最先想到的肯定就是程序数据集。我们先看一下我们的程序数据集的基本开发过程。它会给我们提供一个基础的脉络,让我们多FR是如何取数和“使用”数据有一个基本的认识。


首先要开发程序数据集我们最先需要了解的类是

com.fr.data.AbstractTableData

这个虚类一共给我们提供了四个方法如下:

public abstract int getColumnCount() throws TableDataException;
public abstract String getColumnName( int colIndex ) throws TableDataException;

public abstract int getRowCount() throws TableDataException;

public abstract Object getValueAt( int rowIndex, int colIndex );

从字面意思我们就能看出四个方法的用途,即:通过实现此接口方法,我们向FR引擎提供四个信息,这个数据集有多少行,多少列,列名分别是啥,每行每列的值是什么。这也暴露出FR底层数据的本质 “行/列/值” 纯二维表征方式。

这里程序数据集的demo我们就不再单独举例了,不清楚的同学可以查阅帮助文档:

简单程序数据集-http://help.finereport.com/doc-view-650.html

 

下面我们开始介绍本文的重点,以插件的形式提供数据集支撑。

首先我们要搞清楚一个问题:为什么我们要插件化数据集:为了体验!仅此而已!

程序数据集可以满足我们基本的扩展数据需求,但是它存在以下的问题:

1.对单元格动态参数注入支撑很不友好。

2.使用仅支持参数,不支持配置(当然如果你愿意把所有的配置都当作参数暴露到前端也可以)

3.界面交互很不友好,配置的可阅读性较差

 

回到我们的插件数据集,我们先看一下我们如何去实现一个数据集?有了程序数据集的经验,我们知道我们需要通过代码告诉FR的结果就是 我有多少行/列 列名和值都是啥。也就是

com.fr.data.AbstractParameterTableData

该虚类需要我们实现的方法就有些变化了

public DataModel createDataModel(Calculator calculator);

咦!是不是发现跟我们上面的信息不太一致了,它不再直接问我们要行/列/值等信息了,而是要了一个DataModel的对象。你想得没错,我们所需要的行/列/值这些信息被封装到DataModel这个对象了。

那么我们再看看这个“DataModel”到底是如何提供这些信息的呢

com.fr.general.data.DataModel

我们通过IDEA可以反编译这个类看到,这是一个接口类!

这里就要讲一个重点了:在我们插件开发中,除了极其特殊的情况,一般我们都不会直接去继承实现一个接口类,通常每一个接口A都会对应一个AbstractA的虚类,DataModel一样有一个虚类AbstractDataModel

com.fr.data.AbstractDataModel
同样这个类提供了与程序数据集接口一模一样的四个接口方法。

 

这样我们就可以把数据通过数据集给到FR的计算引擎了。不过到这里我们只说了如何通过代码向FR传递数据,但是我们的数据不是多是固定的,我们往往会需要外部给我们传递一些信息,我们再根据这些信息去提取数据。那么数据集中如何提取这些信息呢?

就要用到整个FR计算过程中的一个非常重要的对象

com.fr.script.Calculator

这是一个算子,他承载了整个计算的上下文和计算逻辑。我们计算需要用到的信息几乎都可以从中得到(当然事无绝对,这些特例未来我们会逐渐遇到,并单独讲解)。

关于算子我们先不介绍太多,大家先记住他是我们计算的核心即可。

 

那么到现在我们 数据集接口的 入参和返回以及功能就说明清楚了,我们做一下总结:

数据集接口方法就是根据算子提取相关参数和上下文信息,通过自定义的逻辑返回一个FR可识别(通过那四个方法)的数据模型

 

OK,了解了相关定义,我们来做一个例子:

我们先建一个插件模块(回想一下我们上一篇教程的介绍,如果不记得了,先翻回去看一下)

然后根据上面的介绍新建一个数据集类。

package com.fr.plugin.tabledata.demo;

import com.fr.config.Identifier;
import com.fr.config.holder.Conf;
import com.fr.config.holder.factory.Holders;
import com.fr.data.AbstractParameterTableData;
import com.fr.general.data.DataModel;
import com.fr.script.Calculator;
import com.fr.stable.ParameterProvider;
import com.fr.stable.StringUtils;
import com.fr.stable.xml.XMLPrintWriter;
import com.fr.stable.xml.XMLableReader;

public class DemoTableData extends AbstractParameterTableData {

    public final static String TAG = "others";

    @Override
    public DataModel createDataModel( Calculator calculator ) {
        //先获取参数面板上的全部参数
        ParameterProvider[] parameters = this.getParameters(calculator);
        //有些时候参数需要我们自己重新计算一次,否则获取到的就是默认值
        parameters = Calculator.processParameters(calculator,parameters);
        return null;
    }

    //假设我们除了参数之外,还有一些需要指定的配置项
    @Identifier(TAG)
    private Conf<String> others = Holders.simple(StringUtils.EMPTY);

    //作为服务器数据集需要对数据库进行读写
    public String getOthers(){
        return others.get();
    }

    public void setOthers( String others ){
        this.others.set(others);
    }

    //作为模板数据集需要对XML进行读写
    @Override
    public void readXML(XMLableReader reader) {
        super.readXML(reader);
        if (reader.isChildNode()) {
            if ("Attributes".equals(reader.getTagName())) {
                setOthers(reader.getAttrAsString(TAG,StringUtils.EMPTY));
            }
        }
    }

    @Override
    public void writeXML(XMLPrintWriter writer) {
        super.writeXML(writer);
        writer.startTAG("Attributes").attr(TAG,getOthers()).end();
    }

}

上面的代码中说明了,外界如何把取数需要的数据传递给我们。其中我们需要注意的是,除了本身的创建数据模型的方法外,若有其他配置项,我们自己还需要额外的实现 配置的XML的读写和数据库读写(分别对应模板数据集和服务器数据集)

关于配置文件的读写后面会有专门的章节介绍,这里大家先知道这么写就可以了。

 

然后我们需要根据这些传入的信息,去取数并通过接口返回给FR。

package com.fr.plugin.tabledata.demo;

import com.fr.data.AbstractDataModel;
import com.fr.general.data.TableDataException;
import com.fr.stable.ParameterProvider;
import com.fr.stable.StringUtils;

public class DemoDataModel extends AbstractDataModel {

    public final static DemoDataModel EMPTY = new DemoDataModel();

    private final static String [] COL_NAMES = new String[]{"KEY","VALUE","OTHERS"};

    private ParameterProvider[] parameters = new ParameterProvider[0];

    private String others = StringUtils.EMPTY;


    public DemoDataModel(){}

    public static DemoDataModel create(  ParameterProvider[] parameters , String others  ){
        DemoDataModel result = new DemoDataModel();
        result.parameters = null == parameters ? new ParameterProvider[0] : parameters;
        result.others= null == others ? StringUtils.EMPTY : others;
        return result;
    }

    @Override
    public int getColumnCount() throws TableDataException {
        return COL_NAMES.length;
    }

    @Override
    public String getColumnName( int colIndex ) throws TableDataException {
        return COL_NAMES[colIndex];
    }

    @Override
    public int getRowCount() throws TableDataException {
        return parameters.length;
    }

    @Override
    public Object getValueAt( int rowIndex, int colIndex ) throws TableDataException {
        if( 2 == colIndex ){
            return others;
        }
        ParameterProvider p = parameters[rowIndex];
        return 0==colIndex ? p.getName() : p.getValue() ;
    }
}


在DemoTableData中调用该模型的构造方法即可

1
public DataModel createDataModel( Calculator calculator ) {
    //先获取参数面板上的全部参数
    ParameterProvider[] parameters = this.getParameters(calculator);
    //有些时候参数需要我们自己重新计算一次,否则获取到的就是默认值
    parameters = Calculator.processParameters(calculator,parameters);
    return DemoDataModel.create( parameters, getOthers() );
}

 

到这里我们的数据集的生成就完成了,接下来我们要想另外一个问题了,代码有了,但是我们在哪里配置这个”others“怎么通过界面给数据集配置参数呢?

下面就是我们遇到的有一个与程序数据集不同的地方了,我们需要为自己的数据集实现自己的配置界面,我们需要继承我们的数据集UI类

com.fr.design.data.tabledata.tabledatapane.AbstractTableDataPane

这个方法本质就是一个JPanel只是做了一些封装而已,需要我们实现的方法有3个

1
public abstract void populateBean(T var1);

public abstract T updateBean();


protected abstract String title4PopupWindow();

方法中的模板T就是我们的数据集对象,也即是这三个方法,分别是讲数据集的配置展现到界面上,将界面的配置保存到数据集中,对此面板命名,具体可看下面的例子

package com.fr.plugin.tabledata.demo;

import com.fr.base.BaseUtils;
import com.fr.base.Parameter;
import com.fr.design.data.datapane.preview.PreviewTablePane;
import com.fr.design.data.tabledata.tabledatapane.AbstractTableDataPane;
import com.fr.design.gui.ibutton.UIButton;
import com.fr.design.gui.itableeditorpane.ParameterTableModel;
import com.fr.design.gui.itableeditorpane.UITableEditorPane;
import com.fr.design.gui.itableeditorpane.UITableModelAdapter;
import com.fr.design.gui.itextfield.UITextField;
import com.fr.design.layout.TableLayout;
import com.fr.design.layout.TableLayoutHelper;
import com.fr.script.Calculator;
import com.fr.stable.ParameterProvider;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class DemoTableDataPane extends AbstractTableDataPane<DemoTableData> {

    private final static double P = TableLayout.PREFERRED;
    private final static double F = TableLayout.FILL;

    private UITableEditorPane<ParameterProvider> parameterTableEditorPane;
    private UITextField tEditor;

    public DemoTableDataPane(){
        init();
    }

    private void init(){
        //创建参数面板
        UITableModelAdapter<ParameterProvider> model = new ParameterTableModel();
        parameterTableEditorPane = new UITableEditorPane<ParameterProvider>(model);
        //创建Others的配置控件
        tEditor = new UITextField();
        //创建预览按钮
        UIButton preview = new UIButton(BaseUtils.readIcon("/com/fr/design/images/m_file/preview.png"));
        preview.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        PreviewTablePane.previewTableData(DemoTableDataPane.this.updateBean());
                    }
                });
            }
        });
        JPanel one = TableLayoutHelper.createTableLayoutPane(
                new Component[][] {{
                        tEditor,preview
                }},
                new double[] { P },
                new double[] { F,P  }
        );
        //创建2行1列
        TableLayoutHelper.createTableLayoutPane(
                new Component[][] {{
                        one
                },{
                        parameterTableEditorPane
                }},
                new double[] { P,F },
                new double[] { F }
        );
    }

    @Override
    public void populateBean( DemoTableData demoTableData ) {
        if( null == demoTableData ){
            return;
        }
        String others = demoTableData.getOthers();
        ParameterProvider[] parameters = demoTableData.getParameters( Calculator.createCalculator() );
        tEditor.setText( others );
        parameterTableEditorPane.populate( parameters );
    }

    @Override
    public DemoTableData updateBean() {
        DemoTableData ob = new DemoTableData();
        ob.setOthers(tEditor.getText());
        java.util.List<ParameterProvider> parameterProviderList = parameterTableEditorPane.update();
        Parameter[] parameters = parameterProviderList.toArray(new Parameter[parameterProviderList.size()]);
        ob.setParameters(parameters);
        return ob;
    }

    @Override
    protected String title4PopupWindow() {
        return "Demo";
    }
}



关于FR设计器的UI类相关使用,暂时不做过多介绍,后面会单独开章节说明。

 

那么到这里我们配置界面也做好了,数据集也做好了,改怎么封装成插件使用呢?

 

这也是我们今天真正的主角:模板数据集插件接口和服务器数据集插件接口(我这边就直接说虚类了,接口类有兴趣的可以自己反编译看一下)

com.fr.design.fun.impl.AbstractTableDataDefineProvider
com.fr.design.fun.impl.AbstractServerTableDataDefineProvider

两个接口类需要实现的方法都是一样的(我就只贴一个了,另外一个照着写即可)

package com.fr.plugin.tabledata.demo;

import com.fr.base.TableData;
import com.fr.design.data.tabledata.tabledatapane.AbstractTableDataPane;
import com.fr.design.fun.impl.AbstractTableDataDefineProvider;

public class DemoTableDataBridge extends AbstractTableDataDefineProvider {
    @Override
    public Class<? extends TableData> classForTableData() {
        return DemoTableData.class;
    }

    @Override
    public Class<? extends TableData> classForInitTableData() {
        return DemoTableData.class;
    }

    @Override
    public Class<? extends AbstractTableDataPane> appearanceForTableData() {
        return DemoTableDataPane.class;
    }

    @Override
    public String nameForTableData() {
        return "Demo";
    }

    @Override
    public String prefixForTableData() {
        return "DM";
    }

    @Override
    public String iconPathForTableData() {
        return "/com/fr/plugin/tabledata/demo/demo.png";
    }
}


上面的方法都比较直观,就是声明我们的一些配置和类。就不单独说明了。

 

然后就是重复我们之前教程的操作,编写plugin.xml和build.xml打包插件了

因为:

com.fr.design.fun.TableDataDefineProvider接口的XML_TAG是TableDataDefineProvider,
com.fr.design.fun.ServerTableDataDefineProvider接口的XML_TAG是ServerTableDataDefineProvider,
而我们数据集接口属于设计器模块,所以对应的XML注册为
<extra-designer>
   <TableDataDefineProvider class="com.fr.plugin.tabledata.demo.DemoTableDataBridge"/>
   <ServerTableDataDefineProvider class="com.fr.plugin.tabledata.demo.DemoServerTableDataBridge"/>
</extra-designer>

打包后,我们就完成了我们插件的开发(关于plugin.xml和build.xml的相关说明如果不记得的,可以返回我们前面的教程查看即可)

为了督促大家养成良好的开发习惯,所以我在代码里面埋了很多坑(关键位置无坑),直接用肯定是不行的,需要大家先理解教程的基础上再基于代码加强理解。包括代码质量的要求,请大家尽量养成好习惯!


学习完成了!赶紧练习一下吧:活动链接-http://bbs.fanruan.com/task-81.html

  • No labels

8 Comments

  1. Anonymous

    Pane无法添加到设置界面是什么大坑?

     

    1. 仔细阅读代码~不是什么大坑~是很明显的问题~你自己动手写一遍就清楚了~直接copy不容易看到~

  2. Anonymous

     哎 初学者会都不会你埋那么多坑 

    1. 额~如果这个坑都看不出来~请先去学习一下java基础和swing的基础~没有java基础和swing基础基本上是正不起来的这个接口

  3. 把panel加出来就可以了吧,其他还有哪些坑

    1. 添加功能点记录   2. add()       
  4. 注意千万不要乱改plugin.xml里面的jartime !!!  

    1. 老哥,1.添加功能点记录   2. add() 这2步都写了 怎么还是不行啊?

Write a comment…