账务实时交易系统设计思考-【第三节】-功能设计

【思考点滴】

作者 : 杨考  微信号 : devin_cn_hd_09_16

本文是【讲解篇】和【技术分享篇】结合起来,由于CSDN文章图片丢失,又补了一次图片。同时进行了章节拆分。

全量版             https://blog.csdn.net/yk200808/article/details/80755459
第一节:业务简介    https://blog.csdn.net/yk200808/article/details/81624677
第二节:业务分析    https://blog.csdn.net/yk200808/article/details/81624779
第三节:功能设计    https://blog.csdn.net/yk200808/article/details/81624826
第四节:热点问题    https://blog.csdn.net/yk200808/article/details/81624861
第五节:准确性      https://blog.csdn.net/yk200808/article/details/81624899
第六节:使用建议    https://blog.csdn.net/yk200808/article/details/81624917
第七节:思考总结    https://blog.csdn.net/yk200808/article/details/81624934

 

3. 功能设计

设计的几个原则,

1) 功能、准确、实时是基础,

2) 性能、效率是关键,

3) 健壮和可扩展是平台化所需。

3.1 接口数据设计

3.2 资金操作

资金操作,用来承接业务的配置,生成资金流转状态的记录,保证资金准确分配。

3.3 数据记录

对内使用:生成交易明细、提供交易记录实时查询

对外输出:为对账、查账、打款等提供数据支持

3.4 算法选择

3.4.1数据唯一性(四元组)

数据唯一性是数据准确性的保证

数据唯一性是数据关系、数据关联的前提

账户操作的唯一性实现,如下四元组详述

1)订单ID           如用户订单ID,商户订单ID,物流订单ID,各业务选择自己的订单ID,订单不一定局限在百度外卖,可以是任何平台的订单【如下示例的 order_id】

2)业务描述       如"百度外卖用户订单","百度外卖商户订单","百度糯米", ... 通过业务准确描述,可以完成不同公司不同业务的订单描述和区别 【如下示例的business_desc】

3)账户ID           平台为主体创建的账户 【如下示例的 account_id】

4)账务资金操作描述    结合业务场景和财务需求进行规定,如"点击消费扣费","转账入账","转账出账","充值入账" 等 【如下示例的 trade_desc】

订单ID+业务描述,保证整体账务系统中,业务订单ID的唯一性

账户ID+账务资金操作描述,保证同一账户ID在订单中多次出现时的资金操作唯一,(如上 3.3.1 中列举的账户 1111111111777777,同一个资金流中,存在多笔不同的入账、出账操作,需要通过账务资金操作描述进行唯一性区分)

保证了资金操作的最小可查询粒度和资金监控的最小粒度

order_no  : 业务订单号

order_type : 业务类型,区分业务类型,防止不同业务order_no重复,且方便按业务维度查数据

account_id : 账户ID

op_code : 操作码,去重;该订单该账户的唯一标识;可配置;可控;

如下图示,就是典型的,同账户通过op_code去重的方式。

3.4.2 一棵整树和多可相对子树(绝对父子关系和相对父子关系并存)

绝对父子关系和相对父子关系并存

1) 提升操作效率(有效树在一棵绝对树上)

2) 提供按业务分段存储(查询子树即可)

3) 维护一棵整树(资金流完整统一,资金关系简单化)

4) 子树用来承接不同的业务,即资金流是一棵整树,不同的业务在资金流这棵整树上的一个子树。

3.4.3 数据唯一性支撑了图到树的转换

资金流是有向图,需要完成图到树的转换处理

3.4.4 分账模型表述

3.4.4.1 分账模型

3.4.4.2 分账配置命令格式(分账模型表述)

$postData = array(
    'trade_commands' => array(   // trade_commands 批量操作方法配置
        array( // 第一组分账操作命令
            'trade_method' => 'splitAccount', // 金额操作方法-分账
                // order_id, business_desc, account_id, trade_desc 四元组,本次操作出账的唯一标识,在整个数据表的唯一标识
                'order_id' => 'userOrder_150121548745', // 业务订单ID,用户订单
                'business_desc' => '百度外卖用户外卖订单', // 业务描述
                'account_id' => 98989777783780001, //余额出账账户的账户ID
                'trade_desc' => '用户下单', //金额操作描述
                'amount' => 1200, // 出账金额,单位(分)         1200=2500+1000-1500-800   会校验资金平衡关系
                'remark' => '商户接单', // 备注信息
                'split_formula' => array(// 入账账户信息配置
                    // order_id, business_desc, account_id, trade_desc 四元组,本次操作入账的唯一标识,在整个数据表的唯一标识
                    '55555666666666' => array( // 入账账户1的账户ID,以及配置信息
                        'order_id' => 'shopOrder_jljljljlljl', // 商户订单ID,没有生成商户订单前,可考虑使用资金出方向(用户订单ID),后者创建一个公共的该资金流的私有ID
                        'business_desc' => '百度外卖商户外卖订单', // 业务描述
                        'account_id' => 55555666666666, //入账账户1的账户ID
                        'trade_desc' => '商户营业输入', //金额操作描述, 如果账户 55555666666666 在分账流中只有一次流入,则这里trade_desc可以不设置,如果有多次,则trade_desc不能重复
                        'amount' => 2500,
                        'remark' => '',
                    ),
                    // order_id, business_desc, account_id, trade_desc 四元组,本次操作入账的唯一标识,在整个数据表的唯一标识
                    '222222233333333' => array( // 入账账户2的账户ID,以及配置信息
                        'order_id' => 'logistics_5245787854745', // 物流订单ID,没有生成物流订单前,可考虑使用资金出方向(用户订单ID),后者创建一个公共的该资金流的私有ID
                        'business_desc' => '百度外卖物流冻结账户', // 业务描述
                        'account_id' => 222222233333333, //入账账户2的账户ID
                        'trade_desc' => '物流接单', //金额操作描述, 如果账户 222222233333333 在分账流中只有一次流入,则这里trade_desc可以不设置,如果有多次,则trade_desc不能重复
                        'amount' => 1000,
                        'remark' => '',
                    ),
                     // order_id, business_desc, account_id, trade_desc 四元组,本次操作入账的唯一标识,在整个数据表的唯一标识(本资金流中,1111111111777777两次操作的trade_desc不同)
                    '1111111111777777' => array( // 入账账户3的账户ID,以及配置信息
                        'order_id' => 'platform_xxxxxxxx', // 业务订单ID,没有生成平台补贴订单前,可考虑使用资金出方向(用户订单ID),后者创建一个公共的该资金流的私有ID
                        'business_desc' => '百度外卖新用户补贴', // 业务描述
                        'account_id' => 1111111111777777, //入账账户3的账户ID
                        'trade_desc' => '新用户补贴出账', //金额操作描述, 如果账户 1111111111777777 在分账流中出现了两次,因此该账户在本次交易流中的四元素必须不同
                        'amount' => -1500,
                        'remark' => '',
                    ),
                     // order_id, business_desc, account_id, trade_desc 四元组,本次操作入账的唯一标识,在整个数据表的唯一标识(本资金流中,1111111111777777两次操作的trade_desc不同)
                    '1111111111777777' => array( // 入账账户3的账户ID,以及配置信息
                        'order_id' => 'platform_xxxxxxxx', // 业务订单ID,没有生成平台补贴订单前,可考虑使用资金出方向(用户订单ID),后者创建一个公共的该资金流的私有ID
                        'business_desc' => '百度外卖新用户补贴', // 业务描述
                        'account_id' => 1111111111777777, //入账账户3的账户ID
                        'trade_desc' => '优质用户嘉奖', //金额操作描述, 如果账户 1111111111777777 在分账流中出现了两次,因此该账户在本次交易流中的四元素必须不同
                        'amount' => -800,
                        'remark' => '',
                    ),
                ),
            ),
            array( // 第二组分账操作命令
                'trade_method' => 'splitAccount', // 金额操作方法-分账
                // ....
            ),

       ),
);


 

3.4.5 资金关系存储算法选择:

3.4.5.1 资金关系存储模型

3.4.5.2 资金关系存储示例:

array(
    // 资金交易上游账户唯一信息
    'absolute_parent' => array(   // 绝对上游信息
        // order_id, business_desc, account_id, trade_desc 四元组的唯一性

        'order_id' => 'userOrder_150121548745', // 业务订单ID,用户订单

        'business_desc' => '百度外卖用户外卖订单', // 业务描述
        'account_id' => 98989777783780001, //余额出账账户的账户ID
        'trade_desc' => '用户下单', //金额操作描述
    ),
    // 本业务资金的入口,在本业务属于分账根节点,相对上游为0,方便业务块操作
    'relative_parent' => array(),    // 相对上游信息
        'amount' => 2500,    // 分账金额  2500 = 2300+200
        'is_split' => 1, // 0:可以分账 1:已分账
        'child_details' => array(     // 绝对下游信息
            // order_id, business_desc, account_id, trade_desc 四元组的唯一性
            '8888888888881111' => array( // 入账账户
                'order_id' => 'shop_xxxxxxxx', // 业务订单ID,没有生成平台补贴订单前,可考虑使用资金出方向(用户订单ID),后者创建一个公共的该资金流的私有ID
                'business_desc' => '百度外卖商户净收', // 业务描述
                'account_id' => 8888888888881111, //入账账户3的账户ID
                'trade_desc' => '商户净收入', //金额操作描述
                'amount' => 2300,
                'remark' => '',
            ),
            // order_id, business_desc, account_id, trade_desc 四元组的唯一性
            '8888888888883333' => array( // 入账账户
                'order_id' => 'platform_xxxxxxxx', // 业务订单ID,没有生成平台补贴订单前,可考虑使用资金出方向(用户订单ID),后者创建一个公共的该资金流的私有ID
                'business_desc' => '百度外卖商户抽佣', // 业务描述
                'account_id' => 8888888888883333, //入账账户3的账户ID
                'trade_desc' => '平台抽佣金额', //金额操作描述, 
                'amount' => 200,
                'remark' => '',
            ),
        )
);

基于四元组唯一性的定义,每笔账户操作在整表中都是唯一的,因此可以破解交易资金流向的网状关系(有向图),实现树状结构关系存储。

1、根据四元组唯一性,入账账户可以快速查到上游出账账户,且双向关系唯一

2、根据四元组唯一性,出账账户可以快速查到资金流入的所有账户,且出账账户和每个入账账户之间的关系唯一

优化点:建立绝对上下游关系相对上游关系

              绝对上游:跨业务账务的上游账务节点是一个真实的唯一节点

              相对上游:每个业务入口账务节点的上游账务节点是0,即该节点是本业务的入口节点

3.4.5.3 账单格式

array( // 账单1
    'order_id' => 'userOrder_150121548745', // 业务订单ID,用户订单
    'business_desc' => '百度外卖用户外卖订单', // 业务描述
    'account_id' => 98989777783780001, // 余额出账账户的账户ID
    'trade_desc' => '用户下单', // 金额操作描述
    'flow_type' => 'out', // 资金出账
    'trade_type' => '交易出账', // 交易类型
    'amount' => 2500,

    'remark' => '',
);

array( // 账单2
    'order_id' => 'shop_xxxxxxxx', // 业务订单ID,没有生成平台补贴订单前,可考虑使用资金出方向(用户订单ID),后者创建一个公共的该资金流的私有ID
    'business_desc' => '百度外卖商户净收', // 业务描述
    'account_id' => 8888888888881111, //入账账户3的账户ID
    'trade_desc' => '商户净收入', //金额操作描述, 如果账户 8888888888881111 在分账流中出现了两次,因此该账户在本次交易流中的四元素必须不同
    'flow_type' => 'in', // 资金入账
    'trade_type' => '交易入账', // 交易类型
    'amount' => 2300,
    'remark' => '',
);

array( // 账单3
    'order_id' => 'platform_xxxxxxxx', // 业务订单ID,没有生成平台补贴订单前,可考虑使用资金出方向(用户订单ID),后者创建一个公共的该资金流的私有ID
    'business_desc' => '百度外卖商户抽佣', // 业务描述
    'account_id' => 8888888888883333, //入账账户3的账户ID
    'trade_desc' => '平台抽佣金额', //金额操作描述, 如果账户 8888888888883333 在分账流中出现了两次,因此该账户在本次交易流中的四元素必须不同
    'flow_type' => 'in', // 资金入账
    'trade_type' => '交易入账', // 交易类型
    'amount' => 200,
    'remark' => '',
);

3.4.7 资金流按业务分区块

1、方便按业务块:每个业务作为一棵子树,存在于整体资金流中,方便资金按业务区块的简易、高效管理

                              业务需求:方便每个业务管理本业务的资金

2、方便完整资金流查询:所有业务共用一棵资金流树,整体资金流可以方便查询,且资金关系完整易维护。

                            财务需求:一棵完整的资金流树,方便财务跟踪整体资金流向

3.4.8 资金操作接口API

3.4.8.1 完善的API接口

入账、出账、分账、调账和撤销等,实现资金操作接口统一化,

3.4.8.2 规范、丰富的交易类型

交易类型场景化和统一化

交易类型可定制

丰富交易类型,实现按类型归纳、追踪、查询和统计等

3.4.8.3 资金分配状态机

资金操作API的背后,是规范化了的资金分配关系数据

资金分配关系数据,可以确保资金的准确分配

记录资金分配的完整过程

资金分配状态机的载体

同时通过资金分配关系,可以复盘、回放资金交易的完整过程

3.4.8.4 资金撤回

1、支持取消任意账户分账(单节点取消分账)

2、支持递归取消任意账户下的分账(按业务区块递归取消多节点分账)

3、支持递归取消整个资金流的分账(取消整棵资金分配过程)

3.5 资源管理

支持批量请求:支持单个请求处理,也支持批量请求处理,

统一事务管理:同一个请求中,无论是包含单个请求,还是多个请求,有一个请求失败,整个操作都需要回滚

统一资源管理:其中包括数据库连接、账户锁等资源,操作串行化,数据锁互斥。

统一异常处理:错误抛异常,异常格式统一化...

3.6 设计回顾总结

赘述一下交易回放的实现 : 所有的分账,都有唯一的操作记录,分账数据不复用不重用,这是交易记录可回放的前提,同时也是业务正确性的支撑(需要清除无效的历史数据)。

账户是一个概念,账户可以承载补偿、积分、优惠券等特殊的资金。

上面说了一大堆,如下图示简单总结,小清新一下。

猜你喜欢

转载自blog.csdn.net/yk200808/article/details/81624826