技術(shù)組件(三)-業(yè)務(wù)賬單(自定文件模版)工具

需求

  1. 靈活配置賬單,10行代碼批量生成商戶賬單.

場景概述

一個(gè)代理商,下面有n個(gè)收單商戶,要生成下面每個(gè)收單商戶的每天的交易流水賬單文件

實(shí)現(xiàn)能力

  1. 能通過模版文件配置修改賬單內(nèi)容
  2. 修改賬單內(nèi)容和結(jié)構(gòu)只需修改配置文件sql
  3. 數(shù)據(jù)讀取通過分頁實(shí)現(xiàn)
  4. 對不同數(shù)據(jù)源的支持
  5. 支持多庫數(shù)據(jù)組合生成賬單的場景
  6. 支持自定義特殊字段的轉(zhuǎn)換
  7. 支持文件的后置處理,可自定義存放位置

源碼地址:

https://gitee.com/kaiyang_taichi/bill-Plugins.git

使用方法:

  1. 導(dǎo)入pom,因?yàn)槲磀eploy到公有倉庫,需要使用,可以自行下載源碼編譯
<dependency>
            <groupId>cn.bese.bill.template.plugins</groupId>
            <artifactId>bill-plugins</artifactId>
            <version>1.0-SNAPSHOT</version>
   </dependency>
  1. 編寫配置文件:

例:

sql1: SELECT * FROM HUSKY2.MERCHANT where merchant_type in (${init.0})

sql2: select r,${sql3.Merchant_no} t,m.MERCHANT_NO,m.MERCHANT_NAME,m.POS_CATI,m.POS_SERIAL_NUMBER,m.TRX_TYPE,
 m.TRADE_SERIAL_NO,m.CREATE_TIME,m.CARD_NO,m.TRADE_AMOUNT,m.STATUS,m.CARD_TYPE,m.MERCHANT_FEE,'' shuangmian,m.AGENT_NO,'' AGENT_NAME,'' so  from (
  SELECT row_number() over(ORDER BY mr.id DESC) as r,mr.*
  FROM OFFLINE.TBL_OFFLINE_ORDER  mr
  where mr.MERCHANT_NO=${sql3.Merchant_no} and mr.status='SUCCESS'
  ) m where m.r>${sys.pageIndex} fetch first ${sys.pageSize}  rows only

sql3: select ym.* from (
      SELECT row_number() over(ORDER BY mr.id DESC) as r,m.* FROM HUSKY2.MERCHANT_RELA_NEW  mr
      inner join HUSKY2.MERCHANT m on mr.SUb_NO = m.merchant_no and m.merchant_type='MERCHANT'
      where mr.PARENT_NO=${file.2}) ym where ym.r>${sys.pageIndex} fetch first ${sys.pageSize} rows only

file-name: /Users/kai.yang/Desktop/yeepay/bill-plugins/bills/orders/${sys.yyyy}/${sys.MM}/${sql1.MERCHANT_NO}/${sql1.MERCHANT_NAME}/交易_${sys.yyyy}${sys.MM}${sys.dd}.csv


transfers:
   - AGENT_NAME->class:com.example.plugns.demoweb.config.bill.AgentNameHandler
   - STATUS->map:SUCCESS|成功

file-templates:
   - 標(biāo)題:商戶交易數(shù)據(jù)
   - 商戶名稱:${file.3}
   - 商戶編號|商戶名稱|終端編號|SN號|產(chǎn)品類型|交易號|交易日期|交易時(shí)間|交易對方銀行卡號|交易金額|交易狀態(tài)|卡類型|手續(xù)費(fèi)|小額雙免|代理商編號|代理商名稱|S0出款狀態(tài)
   - ${sql2.t}|${sql2.MERCHANT_NO}|${sql2.MERCHANT_NAME}|${sql2.POS_CATI}|${sql2.POS_SERIAL_NUMBER}|${sql2.PRODUCT_CODE}|${sql2.TRADE_SERIAL_NO}|${sql2.CREATE_TIME}|${sql2.CARD_NO}|${sql2.TRADE_AMOUNT}|${sql2.STATUS}|${sql2.CARD_TYPE}|${sql2.MERCHANT_FEE}|${sql2.INPUT_TYPE}|${sql2.AGENT_NO}|${sql2.AGENT_NAME}|${sql2.so}

null-file-templates: sql2 -> no data today!

file-content-format-class: com.example.plugns.demoweb.config.bill.FileContentTransferHandler

save-after-class: com.example.plugns.demoweb.config.bill.SaveBillConfig

參數(shù):

  1. 模版key配置方法:

    1. sql*: 模版主要內(nèi)容,就是我們平時(shí)的sql語句,你可以根據(jù)所用數(shù)據(jù)庫語言自己規(guī)范sql方言.多個(gè)sql可以組合使用,key為sql+(自定義數(shù)碼,只用來區(qū)分sql沒有特殊先后順序)例子中:
      sql1--> 查詢出指定類型的所有商戶,本例中為了查出所有代理商
      sql3(先跳過sql2,因?yàn)閟ql2以sql3的結(jié)果作為了查詢條件)-->遍歷sql1的每個(gè)代理商,分頁查出每個(gè)代理商對應(yīng)的所有子商戶
      sql2-->在每個(gè)文件中,分頁查詢sql3中每個(gè)子商戶的交易數(shù)據(jù),匯總生成文件內(nèi)容
      sql1、sql2吁讨、sql3 其實(shí)就是我們平時(shí)寫賬單的三個(gè)步驟的sql語句,此處通過模版key的方式靈活替換

    2. file-name : 最后生成的文件名稱,如例子;
      file-name: /Users/kai.yang/Desktop/yeepay/bill-plugins/bills/orders/${sys.yyyy}/${sys.MM}/${sql1.MERCHANT_NO}/${sql1.MERCHANT_NAME}/交易_${sys.yyyy}${sys.MM}${sys.dd}.csv
      其中所有${*}定義的參數(shù),都可以在模版中通過${file.*}獲取到,這里的index 從0開始.

    3. transfers:定義的轉(zhuǎn)換器,可以對一些特殊字段進(jìn)行后置處理.默認(rèn)有兩種轉(zhuǎn)換器:
      1. map型: map:SUCCESS|成功,定義你SUCCESS到成功的映射,自動替換,場景如數(shù)據(jù)庫枚舉值,文件中轉(zhuǎn)換為中文.
      2. class型: class:com.example.plugns.demoweb.config.bill.AgentNameHandler自定義轉(zhuǎn)換類,只要出現(xiàn)你指定的字段,就會根據(jù)你定義的轉(zhuǎn)換類進(jìn)行替換.此類要繼承TransferValueHandler接口

    4. file-templates: 文件模版,最終的csv文件模版定義.用yml文件的 -表示換行,注意點(diǎn),最終的文件內(nèi)容暫時(shí)只能通過一個(gè)sql主體出數(shù)據(jù),否則系統(tǒng)無法組合分頁.如本例中,最終數(shù)據(jù)從sql2中產(chǎn)出,本行模版不能有其他sql替換符,但可以有其他系統(tǒng)內(nèi)置參數(shù).

      image.png

    5. null-file-templates: 空文件模版配置,指獲取的主sql數(shù)據(jù)為空時(shí),文件展示的內(nèi)容,不配的話只展示表頭,否則根據(jù)你配置寫文件.如例子中,當(dāng)sql2數(shù)據(jù)為空時(shí),文件內(nèi)容為:
      no data today!

    6.file-content-format-class ,整行內(nèi)容處理類,使用較少.作用是你可以對每一行數(shù)據(jù)都可以做整體的特殊處理,不過場景不多.

    1. save-after-class :文件后置處理類,如果你需要對最后的文件做相應(yīng)的處理,如發(fā)送郵件,或保存到其他服務(wù)器的,可以通過此配置實(shí)現(xiàn),繼承SaveAfterProcessConfig接口:
public class SaveBillConfig implements SaveAfterProcessConfig {

    @Override
    public boolean afterProcess(File file, String fileName, Object[] fileParams) {
        System.out.println("文件存儲后置處理");
        return true;
    }
}

  1. 系統(tǒng)內(nèi)置參數(shù)說明:
    1. ${init.*}:以init開頭的參數(shù)為,executer啟動時(shí)傳入的初始化參數(shù),單個(gè) executer上下文全局唯一,不會更改.可用于一些固定的外部參數(shù),如時(shí)間范圍认臊、業(yè)務(wù)類型等等.

    2. ${sys.*}: 為系統(tǒng)內(nèi)定參數(shù)模式,不需要外不指定,有自己的實(shí)現(xiàn)邏輯,可直接使用,其中:
      ${sys.pageIndex}: 分頁頁碼參數(shù),在sql中使用,系統(tǒng)會自動從0開始自增
      ${sys.pageSize}: 分頁每頁數(shù)據(jù)條數(shù)默認(rèn)配置,默認(rèn)200,也可自定義
      ${sys.yyyy}: 系統(tǒng)年份獲取參數(shù),取系統(tǒng)年份,格式如:2019
      ${sys.MM}:系統(tǒng)年份獲取月份,取系統(tǒng)年份,格式如:09
      ${sys.dd}: 系統(tǒng)天:格式:23
      處理代碼在cn.base.bill.template.plugins.config.SysParamConfig中,有需要可自行調(diào)整:

    3. ${file.*}:獲取最終文件名中的指定參數(shù),在單個(gè)文件不變的參數(shù)上下文傳遞時(shí)可以使用(但缺陷是文件目錄會多出此參數(shù),后續(xù)有機(jī)會可以優(yōu)化,加入文件級別的上下文).例如:
      file-name: /Users/kai.yang/Desktop/yeepay/bill-plugins/bills/orders/${sys.yyyy}/${sys.MM}/${sql1.MERCHANT_NO}/${sql1.MERCHANT_NAME}/交易_${sys.yyyy}${sys.MM}${sys.dd}.csv
      但這里的fiile參數(shù)只取file-name配置中的${}中的參數(shù),所以此例匯總 ${file.2}就是對應(yīng)的${MERCHANT_NO}獲取當(dāng)前文件中的月份字段值(小標(biāo)從0開始).

    4. ${sql*.*}: 重點(diǎn)的sql參數(shù),在文件模版key中,已經(jīng)說過sqln就是對應(yīng)指定的sql,如${sql2.MERCHANT_NO}就是對應(yīng)的sql2中的MERCHNAT_NO字段.

  1. 代碼啟動:
    配置文件配好后,10來行代碼就可以生產(chǎn)你需要的賬單了.
public class DemoController implements InitializingBean {

   /**
    * 配置的一個(gè)數(shù)據(jù)源
    */
   @Resource(name = "posDataSource")
   DataSource posDataSource;

   /**
    * 配置的第二個(gè)數(shù)據(jù)源
    */
   @Resource(name = "huskyDataSource")
   DataSource huskyDataSource;

   /**
    * 對應(yīng)的執(zhí)行器構(gòu)造者檬输,通過afterPropertiesSet方法初始化
    */
   BillPluginsExecuteBuilder orderBillPluginsExecuteBuilder;


   @Override
   public void afterPropertiesSet() {
       //初始化構(gòu)造者存皂,
       //1宾符。setBillConfigFilePath 指定配置文件路徑
       //2踢星。setDataSource指定數(shù)據(jù)源配置统刮,參數(shù)(DataSource dataSource, String... keys),指定哪些sql的key對應(yīng)哪個(gè)數(shù)據(jù)源
       // 本例子中配置了兩個(gè)數(shù)據(jù)源污抬,sql1汞贸、sql3對應(yīng)huskyDataSource,sql2對應(yīng)posDataSource數(shù)據(jù)源
       //3印机。最后調(diào)用init()方法啟動builder
       orderBillPluginsExecuteBuilder = new BillPluginsExecuteBuilder()
           .setBillConfigFilePath("/biil-template/order-templates-demo-db2.yml")
           .setDataSource(huskyDataSource, "sql1", "sql3").setDataSource(posDataSource, "sql2").init();

   }

   @GetMapping("/test2")
   public String test2() throws SQLException {
       //params為配置執(zhí)行器上下文的初始化參數(shù)矢腻,可通過${init.n}獲得
       Object[] params = new Object[]{"MIDDLE_AGENT", "10040041322"};
       //最后執(zhí)行g(shù)enerate生產(chǎn)所有文件
       orderBillPluginsExecuteBuilder.build(params).generate();
       return "ok";
   }
}

生產(chǎn)的賬單例子,生成這個(gè)代理商下每個(gè)子商戶的數(shù)據(jù):


image.png

源碼簡介

此處先簡單介紹下代碼結(jié)構(gòu),有需要以后再細(xì)說.


image.png

看下源碼機(jī)構(gòu)圖:

  1. config是對應(yīng)上面說的系統(tǒng)內(nèi)置參數(shù)的處理邏輯
  2. context 為組件上下文定義,里邊有全局的一些緩存
  3. dao為數(shù)據(jù)庫交互層,封裝了sql的執(zhí)行過程、分頁實(shí)現(xiàn)都在這里
  4. format為對應(yīng)參數(shù)格式化實(shí)現(xiàn),默認(rèn)有時(shí)間射赛、和空值的處理
  5. model里定義的是實(shí)體模型
  6. parse是對yml配置文件的解析過程
  7. transfer為對應(yīng)個(gè)別字段的特殊轉(zhuǎn)換處理
  8. BillPluginsExecuteBuilder是對文件解析的入口,是Executor的構(gòu)造者
    9 BillPluginsExecutor 是最終的執(zhí)行類,所有核心邏輯的入口 從generate方法開始.

generate主要執(zhí)行時(shí)序圖:

image.png

其中主要流程分為兩步:
第一步: 對文件名的解析;
第二步:針對每個(gè)文件,對file-templates文件模版的解析

原則就是,解析過程中如果有sql依賴,就先執(zhí)行sql依賴(文件名目前執(zhí)行1層sql依賴,內(nèi)容支持兩層,基本滿足大多數(shù)場景).

對于sql的執(zhí)行通過DefaultSqlCallerImpl進(jìn)行封裝,然后類似于jdbc的流式讀取,在ResultRows結(jié)果集中處理分頁邏輯


  /**
     * 遍歷行多柑,獲取數(shù)據(jù)
     * 1。 對數(shù)據(jù)進(jìn)行參數(shù)格式化咒劲,可用戶自定義格式
     * 2顷蟆。對于特殊參數(shù)進(jìn)行轉(zhuǎn)換處理,用戶可自定義
     */
    public Map<String, String> next() throws SQLException {

        if (index >= rowMaps.size()) {
            if (isHasNext() && pageNoIndex != -1) {
                //存在下一頁情況,先進(jìn)行頁碼替換

                Object[] newParams = Arrays.copyOf(parsms, parsms.length);

                newParams[pageNoIndex] = Integer.valueOf(newParams[pageNoIndex].toString()) + pageSize;

                //更換下頁碼參數(shù)換成
                this.parsms=newParams;

                //當(dāng)前頁數(shù)據(jù)腐魂,索引清零
                index = 0;

                //下頁查詢
                ResultRows call = ((DefaultSqlCallerImpl) sqlCaller).call(newParams);
                this.rowMaps = call.getRowMaps();
                call.close(); //幫助gc
            }

            //此時(shí)只能返回null帐偎,說明沒有值了
            if (index >= rowMaps.size()) {
                return null;
            }
        }

        return formartResult(rowMaps.get(index++));
    }

并通過formartResult方法進(jìn)行參數(shù)的自定義格式化

  /**
     * 映射格式化
     */
    private Map<String, String> formartResult(Map<String, Object> resultMap) {

        Map<String, String> result = new HashMap<>();
        if (MapUtils.isNotEmpty(resultMap)) {
            resultMap.forEach((k, v) -> {

                //1。固定類型格式化
                String formatValue = FormaterRegistry.getFormater(typeMaps.get(k)).format(v);

                //2蛔屹。對于特殊參數(shù)的轉(zhuǎn)換處理
                TransfersConfig transferConfig = BillPluginsContext.getTransferConfig(k);
                if (transferConfig != null) {
                    switch (transferConfig.getTransferTypeEnums()) {
                        case MAP:
                            String transferValue = transferConfig.getTransferMap().get(formatValue.toUpperCase());
                            result.put(k, StringUtils.isEmpty(transferValue) ? formatValue : transferValue);
                            break;
                        case Class_TRANSFER:
                            result.put(k, transferConfig.getTransferType().transfer(formatValue,resultMap));
                            break;
                        default:
                            result.put(k, formatValue);
                            break;
                    }
                } else {
                    result.put(k, formatValue);
                }
            });

        }
        return result;
    }

總結(jié)

寫的有點(diǎn)急,細(xì)節(jié)處理有很多沒處理到位,但已基本實(shí)現(xiàn)了大多數(shù)生成賬單的場景.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末削樊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子兔毒,更是在濱河造成了極大的恐慌漫贞,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件育叁,死亡現(xiàn)場離奇詭異迅脐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)豪嗽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門谴蔑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來豌骏,“玉大人隐锭,你說我怎么就攤上這事窃躲。” “怎么了钦睡?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵蒂窒,是天一觀的道長。 經(jīng)常有香客問我荞怒,道長洒琢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任挣输,我火速辦了婚禮纬凤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘撩嚼。我一直安慰自己,他們只是感情好挖帘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布完丽。 她就那樣靜靜地躺著,像睡著了一般拇舀。 火紅的嫁衣襯著肌膚如雪逻族。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天骄崩,我揣著相機(jī)與錄音聘鳞,去河邊找鬼。 笑死要拂,一個(gè)胖子當(dāng)著我的面吹牛抠璃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脱惰,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼搏嗡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拉一?” 一聲冷哼從身側(cè)響起采盒,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蔚润,沒想到半個(gè)月后磅氨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嫡纠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年烦租,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了决瞳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡左权,死狀恐怖皮胡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赏迟,我是刑警寧澤屡贺,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站锌杀,受9級特大地震影響甩栈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜糕再,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一量没、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧突想,春花似錦殴蹄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绑嘹,卻和暖如春稽荧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背工腋。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工姨丈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人擅腰。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓蟋恬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親惕鼓。 傳聞我的和親對象是個(gè)殘疾皇子筋现,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354