1. 概述
1.1 版本 報表伺服器版本 JAR 包最低版本支援 插件版本 功能變更 11.0 2023-08-27 2.5.24 內建HTTP裝載器支援GET透過BODY傳參 11.0 2023-08-27 2.5.22 適配參數元件聯動不重新計算的問題 11.0 2020-12-30 2.5 10.0 2020-12-30 2.5
1.2 應用場景 企業資料源格式多種多樣,部分資料格式無法直接應用於FineReport中。本插件提供資料格式轉換能力,可對接JSON、WebService、網路爬蟲、自主開發的資料中心等,並將其轉換成FineReport可用的資料類型。同時,該插件具有高度擴展性,也可以透過二次開發來適應更復雜的場景。
注:1.基於此插件進行擴展開發需要具備最基礎的 FineReport 插件開發能力。
2.使用子插件時升級,需要先禁用子插件,或升級後重啟伺服器。
1.3 功能介紹 插件透過不同的裝載器和解析器實現個性化的資料裝配,將從外部獲取的資料轉換成 FineReport 可用的資料格式。
2. 插件介紹
2.1 插件安裝 點選下載插件:資料工廠資料集
設計器插件安裝方法參照:設計器插件管理
伺服器安裝插件方法參照:伺服器插件管理
2.2 操作方法 1)插件安裝後,資料集管理面板新增資料工廠 資料集,如下圖所示:
2)點選進入資料工廠主面板,使用主面板可以應對絕大多數的場景。主面板具體功能介紹如下:
①名稱: 在名稱欄可以設定該資料集的名稱。
②裝載器和解析器: 裝載器負責從資料源裝載資料,解析器則負責將裝載的資料轉換成 FineReport 支援的資料模型。
插件內建了兩種裝載方式(Http、單參數)和三種解析方式(Json 、Js 解析器、原始資料)。
在下拉框中選擇裝載方式和解析方式,具體的裝載器、解析器配置方法將在下文第3節進行詳細介紹。
③進階設定: 當內建裝載器和解析器無法滿足使用者需求時,可以使用進階設定功能進行二次開發,以實現功能擴展。該功能將在下文第4節進行詳細介紹。
④快取: 快取的意思是指將外部讀取的資料暫存到快取中,直接從快取中取數,而不透過API取數,以提高資料讀取的速度。快取有 3 個選項:無、磁碟、記憶體。
選擇“無”表示不啟動快取功能
選擇“ 磁碟” 表示快取到本地磁碟
選擇“ 記憶體” 表示快取到記憶體
n分鐘: 表示n分鐘內從快取中讀取靜態資料,n分鐘後再次從API重新讀取資料,重新讀取的資料依然在快取中儲存n分鐘。
⑤參數欄: 參數欄裏面的參數會在後台被分為兩類:
第一類是裝載器介面出現過的參數( ${參數名} ),該類參數名可被參數面板自動識別,同時解析到裝載器配置的字串裏。 參數文法:用 ${參數名}獲得參數的值,支援正文、公式等多種形式的參數,也支援從報表中讀取參數。
第二類則是裝載器介面未出現過的,使用者可透過參數面板的插入按鈕來新增新的參數作為請求參數。
在參數面板中,“參數”列中記錄的是參數名;“值”列中點選按鈕可以選擇參數資料類型(包括字串、整數型態、雙精度型、日期、布林型、公式),可以在該列中輸入參數的取值。分別可以實現參數所在行的刪除、上移和下移。
⑥預覽: 點選預覽按鈕,可以預覽格式轉換後的資料。
3)完成配置後,在FineReport設計器中,可看到範本資料集中新增的資料工廠資料集,該資料集可直接應用於報表製作。
3. 裝載器和解析器使用範例
3.1 內建裝載器 插件內建了兩種裝載方式,分別為:
3.1.1 Http
當選擇Http裝載方式時,裝載器的內容是一個JSON,裏面有4個欄位,分別是
參數名稱 參數說明 備註 url 請求地址 type 請求類型 枚舉值:GET,POST,JSON charset 編碼 header 請求頭,以json鍵值對形式寫入 body 當請求類型為JSON時使用 connectTimeout 請求連結逾時時間 預設5秒,可透過配置該屬性調整連結逾時 readTimeout 等待回应逾時時間 預設60秒,可透過配置該屬性調整回应逾時時間
具體使用範例如表所示:
請求類型
裝載器內容
參數面板
取值
實際請求
備註
GET { url:"http://example.com/" , type:"GET" , charset:"UTF-8" , header:{ aa:${bb} } }
參數 值 bb p_BB cc p_CC
url: http://example.com/?cc=p_CC
type: get
header:{aa:p_BB}
使用者在參數面板中新增的第二類額外參數會作為query參數連接到url後。
POST { url:"http://example.com/" , type:"POST" , charset:"UTF-8" , header:{ aa:${bb} } }
參數 值 bb p_BB cc p_CC
url: http://example.com/
type: post
header: {aa:p_BB}
body: { cc: p_CC }
使用者在參數面板中新增的第二類額外參數在請求體body裏。body為x-www-form-urlencoded模式,內容為cc=p_CC。
JSON
{ url:"http://example.com/" , type:"JSON" , charset:"UTF-8" , header:{ aa:${bb} } }
參數 值
bb p_BB
cc p_CCurl: http://example.com/
type: post
header: {aa:p_BB,
content-type: json}
body: { cc: p_CC }
JSON 實際上是post raw模式,第二類參數會被裝載成JSON的格式作為請求體傳送。對於POST請求這種方式使用的更普遍。
body有兩種裝載方式:一種是類似用post的。第二種是直接寫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 }
注:範例中的http://example.com/僅為示範,實際請按照真實API進行調整。
2.5.24版本新增支援GET請求透過BODY傳遞參數(POST請求不適用),具體說明如下:
當請求類型為GET時,提供Content-Type參數及body配合使用
配置名稱 說明 範例 Content-Type 可選參數, 搭配body參數使用。枚舉值:application/x-www-form-urlencoded、application/json、multipart/form-data。如果存在body但是沒有傳遞Content-Type參數,則 Content-Type 預設使用x-www-form-urlencoded { "url": "http://example.com ", "type": "GET", "charset": "UTF-8", "Content-Type": "application/x-www-form-urlencoded", "header": {}, "body": { "age": "123" } } body 透過鍵值對形式配置,配置自動轉換為相應content-type的報文格式,比如x-www-form-urlencoded下報文會被轉換成age=123;參數面板的參數只會作為query傳遞
3.1.2 單參數 範例
在裝載方式下拉框中選擇“單參數”裝載方式,在裝載器內容框中輸入 ${data}${today()} ,在解析方式下拉框中選擇“原始資料”解析方式(表示原樣傳回資料,將在下文3.2.3進行介紹 ), 在參數面板配置參數如下:
參數 值 data myData
點選預覽按鈕,得到如下結果:
3.2 內建解析器 插件內建了三種解析方式,分別為:
注:只能解析結構型描述的 JSON,業務型描述的 JSON 無法解析,業務型 JSON 解析依賴具體的業務的描述,需要單獨實現解析器。
注:當資料量較大時要謹慎使用。
3.2.1 Json
提供了不完整的JPATH文法。欄位含義如下:
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 ] ] }
範例:具體程式碼請按照實際場景開發
裝載器傳回資料如下:
{ "monthFactoryWaitingRateDataList" : [ { "factory" : "HZC01" , "dayFactoryWaitingRateList" : [0 ,0 ,0 ], "days" : ["2023-04-01" ,"2023-04-02" ,"2023-04-03" ], "dayStrs" : ["04-01" ,"04-02" ,"04-03" ] }, { "factory" : "HZC02" , "dayFactoryWaitingRateList" : [0 ,0 ,0 ], "days" : ["2023-04-01" ,"2023-04-02" ,"2023-04-03" ], "dayStrs" : ["04-01" ,"04-02" ,"04-03" ] } ] }
JS解析器填寫範例:
var x =$data;var column = Object .keys(x.monthFactoryWaitingRateDataList[0 ]);var content = [];for (var i = 0 ; i < x.monthFactoryWaitingRateDataList.length; i++) { for (var j = 0 ; j < x.monthFactoryWaitingRateDataList[i].dayFactoryWaitingRateList.length; j++) { var contentLine = []; contentLine.push(x.monthFactoryWaitingRateDataList[i].factory, x.monthFactoryWaitingRateDataList[i].dayFactoryWaitingRateList[j], x.monthFactoryWaitingRateDataList[i].days[j], x.monthFactoryWaitingRateDataList[i].dayStrs[j]); content.push(contentLine); } }var result = {}; result.column = column; result.content = content;return result;
預覽資料,效果如下:
Js解析器效能比較低,資料量大的時候要慎用。
插件2.5.13版本Js解析器開始使用JDK內建Nashorn引擎,預設支援ES5文法,高版本文法暫不支援。
3.2.3 原始資料 類比單參數裝載器,傳回一個只有一個儲存格的資料集,資料集的內容是從裝載器接受到的原始資料,參數面板無意義。具體範例參照3.1.2。
3.3 使用案例 3.3.1 案例一 獲取帆軟市場的插件列表
裝載方式:Http
選擇Http裝載方式以實現從 Web 端裝載資料。
{ url:"https://market.fanruan.com/plugins/commodity?cid=&fee=&seller=&version=&language=&searchKeyword=&_=1650781600900" , type:"GET" , charset:"UTF-8" , header:{} }
注:1. 具體的裝載器邏輯可以單獨客制,並沒有什麼統一的寫法規範
2. API實際傳回Json格式的資料如下(此處做對比說明,實際插件使用程式中,不會出現該步驟)
解析方式 :Json
選擇Json解析方式以對結構化的 Json資料進行解析。
{ dataPath:"root.result" , showmap:"path1,value1,path2,value2" , }
即主面板配置如下:
點選預覽按鈕,傳回經格式轉換後的資料結果如下:
3.3.2 案例二 假設有如下API用來獲取某城市天氣 http://example.com?city=北京
如果我們希望根據範本元件內容動態獲取城市的資料,可以在範本中設定一個元件 city
然後在資料工廠面板中進行如下配置:
裝載方式:Http
方式一:在裝載器中新增同名參數 參數預設值置空
{ url:"http://example.com?city=${city}" , type:"GET" , charset:"UTF-8" , header:{} }
方式二:在參數面板中手動新增參數city,參數值置空
{ url:"http://example.com" , type:"GET" , charset:"UTF-8" , header:{} }
假設API回傳值如下:
{ result:[ { district:海淀區, temperature:20 , }, { district:朝陽區, temperature:23 , } ] }
解析方式:Json
{ dataPath:"root.result" , showmap:"district,轄區,temperature,溫度" }
即主面板配置如下:
點選預覽按鈕,傳回經格式轉換後的資料結果如下:
4.進階設定功能介紹
使用主面板的進階設定功能需要有一定的插件開發能力。
假如使用者認為內建的裝載方式、解析方式不足以滿足使用場景,又不想重新開發一個裝載器/解析器時,可以點選資料工廠主面板的“進階設定”按鈕自訂輔助步驟。
進階設定面板的作用是用來新增裝載前、裝載後、解析後事件作為裝載方式、解析方式的補足,支援多個同步驟事件的新增。
4.1 處理流程
在“進階設定”面板最上層下拉框中可以選擇“為該資料集單獨設定”/“使用範本設定”/“使用全局設定”。
選擇“為該資料集單獨設定”時下方會出現列表,支援事件的增刪改。
選擇“使用範本設定”/“使用全局設定”時下方不顯示列表,具體設定項需開啟右側“全局設定”/“範本設定”進行編輯。
4.2 各個事件的作用與API描述 目前各個事件並沒有實裝具體的應用,僅提供了接入框架,進階步驟是裝載-解析模式的增強和補全,結合開發篇來使用。
裝載前事件 提供額外的參數引入 比如說token獲取的邏輯可以寫在自訂裝載前事件裏。多個裝載前事件參數可以累積,重複的參數前者會被後者改寫。
在主面板使用,使用方法同標準參數呼叫,右側參數欄內預設值無效。
場景範例: 獲取token,可以開發一個token獲取的裝載前事件,將獲取的token作為參數引入主邏輯中。
裝載前事件preprocessorAPI描述:
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 ; ParameterProvider[] process(Calculator calculator, String config) throws Exception; }
裝載後事件 提供裝載器獲取的原始資料的整理 通常裝載器傳回的是一個字串類型,可以在這個階段進行額外的整理行為,多個事件從上到下順序執行。
場景範例: 傳回的資料雖然是json但不適合直接用json解析器,用js解析器效能又不夠好的時候,可以客製一個裝載後事件,把結構格式化為json解析器能夠識別的樣式。
裝載後事件formatter API描述:
package com.tptj.plugin.hg.fun; public interface Formatter extends Configuration{ String XML_TAG = "TableDataFormatter" ; int CURRENT_LEVEL = 1 ; Object format(Object data, String config) throws Exception; }
解析後事件 提供解析器傳回的資料集的進一步處理 比如增刪列,修改列名,重排序,條件過濾等,可以在這個階段進行額外的整理行為,多個事件從上到下順序執行。
場景範例: 排序,篩選,取前n項,取奇數項,調整資料格式等等自由運算元據集內容。
解析後事件filterAPI描述:
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 ; SimpleDataModel doAction(SimpleDataModel dataModel, String config) throws Exception; }
4.3 其他API描述 裝載事件loaderAPI描述 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 ; Object load( Calculator cal, ParameterProvider[] params, String others); String getName(); String getDefaultConfig(); }
解析事件resolverAPI描述 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 ; SimpleDataModel parse(Object data, String others ); String getName(); 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" )); 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); } 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方法即可向資料集寫入資料。 自訂面板API,很多時候,預設正文框對於配置來說過於簡陋了。如果想要更豐富的UI可以使用這個API更換默 認的正文框。
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(); void setValue(String config); 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; public interface ConfigTable<T extends Configuration> extends Mutable { String XML_TAG = "TableDataConfigTable" ; int CURRENT_LEVEL = 1 ; String getName(); void setValue(String config); String getValue(); JPanel getTable(FocusListener listener); 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 含義:–資料工廠執行結束
日誌中可見裝載器、解析器、資料工廠資料集整體的取數耗時,若裝載器執行時間較長時,一般為呼叫的API回应時間過長,可透過其他方式呼叫API查看回应時間進行問題定位
插件版本要求:2.5.21及以上