美文网首页
技术组件(三)-业务账单(自定文件模版)工具

技术组件(三)-业务账单(自定文件模版)工具

作者: 爱编程的凯哥 | 来源:发表于2019-11-03 17:09 被阅读0次

    需求

    1. 灵活配置账单,10行代码批量生成商户账单.

    场景概述

    一个代理商,下面有n个收单商户,要生成下面每个收单商户的每天的交易流水账单文件

    实现能力

    1. 能通过模版文件配置修改账单内容
    2. 修改账单内容和结构只需修改配置文件sql
    3. 数据读取通过分页实现
    4. 对不同数据源的支持
    5. 支持多库数据组合生成账单的场景
    6. 支持自定义特殊字段的转换
    7. 支持文件的后置处理,可自定义存放位置

    源码地址:

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

    使用方法:

    1. 导入pom,因为未deploy到公有仓库,需要使用,可以自行下载源码编译
    <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:
       - 标题:商户交易数据
       - 商户名称:${file.3}
       - 商户编号|商户名称|终端编号|SN号|产品类型|交易号|交易日期|交易时间|交易对方银行卡号|交易金额|交易状态|卡类型|手续费|小额双免|代理商编号|代理商名称|S0出款状态
       - ${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
    

    参数:

    1. 模版key配置方法:

      1. sql*: 模版主要内容,就是我们平时的sql语句,你可以根据所用数据库语言自己规范sql方言.多个sql可以组合使用,key为sql+(自定义数码,只用来区分sql没有特殊先后顺序)例子中:
        sql1--> 查询出指定类型的所有商户,本例中为了查出所有代理商
        sql3(先跳过sql2,因为sql2以sql3的结果作为了查询条件)-->遍历sql1的每个代理商,分页查出每个代理商对应的所有子商户
        sql2-->在每个文件中,分页查询sql3中每个子商户的交易数据,汇总生成文件内容
        sql1、sql2、sql3 其实就是我们平时写账单的三个步骤的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
        其中所有${*}定义的参数,都可以在模版中通过${file.*}获取到,这里的index 从0开始.

      3. transfers:定义的转换器,可以对一些特殊字段进行后置处理.默认有两种转换器:
        1. map型: map:SUCCESS|成功,定义你SUCCESS到成功的映射,自动替换,场景如数据库枚举值,文件中转换为中文.
        2. class型: class:com.example.plugns.demoweb.config.bill.AgentNameHandler自定义转换类,只要出现你指定的字段,就会根据你定义的转换类进行替换.此类要继承TransferValueHandler接口

      4. file-templates: 文件模版,最终的csv文件模版定义.用yml文件的 -表示换行,注意点,最终的文件内容暂时只能通过一个sql主体出数据,否则系统无法组合分页.如本例中,最终数据从sql2中产出,本行模版不能有其他sql替换符,但可以有其他系统内置参数.

        image.png
      5. null-file-templates: 空文件模版配置,指获取的主sql数据为空时,文件展示的内容,不配的话只展示表头,否则根据你配置写文件.如例子中,当sql2数据为空时,文件内容为:
        no data today!

      6.file-content-format-class ,整行内容处理类,使用较少.作用是你可以对每一行数据都可以做整体的特殊处理,不过场景不多.

      1. save-after-class :文件后置处理类,如果你需要对最后的文件做相应的处理,如发送邮件,或保存到其他服务器的,可以通过此配置实现,继承SaveAfterProcessConfig接口:
    public class SaveBillConfig implements SaveAfterProcessConfig {
    
        @Override
        public boolean afterProcess(File file, String fileName, Object[] fileParams) {
            System.out.println("文件存储后置处理");
            return true;
        }
    }
    
    
    1. 系统内置参数说明:
      1. ${init.*}:以init开头的参数为,executer启动时传入的初始化参数,单个 executer上下文全局唯一,不会更改.可用于一些固定的外部参数,如时间范围、业务类型等等.

      2. ${sys.*}: 为系统内定参数模式,不需要外不指定,有自己的实现逻辑,可直接使用,其中:
        ${sys.pageIndex}: 分页页码参数,在sql中使用,系统会自动从0开始自增
        ${sys.pageSize}: 分页每页数据条数默认配置,默认200,也可自定义
        ${sys.yyyy}: 系统年份获取参数,取系统年份,格式如:2019
        ${sys.MM}:系统年份获取月份,取系统年份,格式如:09
        ${sys.dd}: 系统天:格式:23
        处理代码在cn.base.bill.template.plugins.config.SysParamConfig中,有需要可自行调整:

      3. ${file.*}:获取最终文件名中的指定参数,在单个文件不变的参数上下文传递时可以使用(但缺陷是文件目录会多出此参数,后续有机会可以优化,加入文件级别的上下文).例如:
        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参数只取file-name配置中的${}中的参数,所以此例汇总 ${file.2}就是对应的${MERCHANT_NO}获取当前文件中的月份字段值(小标从0开始).

      4. ${sql*.*}: 重点的sql参数,在文件模版key中,已经说过sqln就是对应指定的sql,如${sql2.MERCHANT_NO}就是对应的sql2中的MERCHNAT_NO字段.

    1. 代码启动:
      配置文件配好后,10来行代码就可以生产你需要的账单了.
    public class DemoController implements InitializingBean {
    
       /**
        * 配置的一个数据源
        */
       @Resource(name = "posDataSource")
       DataSource posDataSource;
    
       /**
        * 配置的第二个数据源
        */
       @Resource(name = "huskyDataSource")
       DataSource huskyDataSource;
    
       /**
        * 对应的执行器构造者,通过afterPropertiesSet方法初始化
        */
       BillPluginsExecuteBuilder orderBillPluginsExecuteBuilder;
    
    
       @Override
       public void afterPropertiesSet() {
           //初始化构造者,
           //1。setBillConfigFilePath 指定配置文件路径
           //2。setDataSource指定数据源配置,参数(DataSource dataSource, String... keys),指定哪些sql的key对应哪个数据源
           // 本例子中配置了两个数据源,sql1、sql3对应huskyDataSource,sql2对应posDataSource数据源
           //3。最后调用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为配置执行器上下文的初始化参数,可通过${init.n}获得
           Object[] params = new Object[]{"MIDDLE_AGENT", "10040041322"};
           //最后执行generate生产所有文件
           orderBillPluginsExecuteBuilder.build(params).generate();
           return "ok";
       }
    }
    
    

    生产的账单例子,生成这个代理商下每个子商户的数据:


    image.png

    源码简介

    此处先简单介绍下代码结构,有需要以后再细说.


    image.png

    看下源码机构图:

    1. config是对应上面说的系统内置参数的处理逻辑
    2. context 为组件上下文定义,里边有全局的一些缓存
    3. dao为数据库交互层,封装了sql的执行过程、分页实现都在这里
    4. format为对应参数格式化实现,默认有时间、和空值的处理
    5. model里定义的是实体模型
    6. parse是对yml配置文件的解析过程
    7. transfer为对应个别字段的特殊转换处理
    8. BillPluginsExecuteBuilder是对文件解析的入口,是Executor的构造者
      9 BillPluginsExecutor 是最终的执行类,所有核心逻辑的入口 从generate方法开始.

    generate主要执行时序图:

    image.png

    其中主要流程分为两步:
    第一步: 对文件名的解析;
    第二步:针对每个文件,对file-templates文件模版的解析

    原则就是,解析过程中如果有sql依赖,就先执行sql依赖(文件名目前执行1层sql依赖,内容支持两层,基本满足大多数场景).

    对于sql的执行通过DefaultSqlCallerImpl进行封装,然后类似于jdbc的流式读取,在ResultRows结果集中处理分页逻辑

    
      /**
         * 遍历行,获取数据
         * 1。 对数据进行参数格式化,可用户自定义格式
         * 2。对于特殊参数进行转换处理,用户可自定义
         */
        public Map<String, String> next() throws SQLException {
    
            if (index >= rowMaps.size()) {
                if (isHasNext() && pageNoIndex != -1) {
                    //存在下一页情况,先进行页码替换
    
                    Object[] newParams = Arrays.copyOf(parsms, parsms.length);
    
                    newParams[pageNoIndex] = Integer.valueOf(newParams[pageNoIndex].toString()) + pageSize;
    
                    //更换下页码参数换成
                    this.parsms=newParams;
    
                    //当前页数据,索引清零
                    index = 0;
    
                    //下页查询
                    ResultRows call = ((DefaultSqlCallerImpl) sqlCaller).call(newParams);
                    this.rowMaps = call.getRowMaps();
                    call.close(); //帮助gc
                }
    
                //此时只能返回null,说明没有值了
                if (index >= rowMaps.size()) {
                    return null;
                }
            }
    
            return formartResult(rowMaps.get(index++));
        }
    

    并通过formartResult方法进行参数的自定义格式化

      /**
         * 映射格式化
         */
        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。对于特殊参数的转换处理
                    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;
        }
    

    总结

    写的有点急,细节处理有很多没处理到位,但已基本实现了大多数生成账单的场景.

    相关文章

      网友评论

          本文标题:技术组件(三)-业务账单(自定文件模版)工具

          本文链接:https://www.haomeiwen.com/subject/hfjfuctx.html