一、背景

笔者所属公司是一个电商公司,全国有很多的门店,所有业务系统都围绕着门店进行,门店原有一个营建系统用于选址开店、门店基础信息配置、门店业务信息配置。在门店营建系统重构之前,门店基础信息主要面临三方面问题:
1.门店基本信息分散在配送、门店、基础数据等多个系统中,每个系统除了存储门店的公用的基础信息(门店编码、门店名称、所属组织等)以外,也存储了自己系统所需要的一些独有信息,比如门店系统存储了门店作业的相关信息。由于门店信息的维护往往面对的用户群体是相同的,但是数据分散在多个系统,从而导致获取一份数据要跨多个系统进行线下的记录与整合,无法高效获取门店基础信息。

2.各个系统维护的门店基础信息权限管理比较粗放, 不能在门店属性级别上做权限管理。

3.对于门店基本信息的获取没有统一的标准接口供需求系统获取,对于获取同一份门店基本信息数据可以在多个系统中获取到,增加决策成本。

在解决上面问题之前,必须向大家解释一下上文提到的基础数据这个系统的作用,这个系统原来的定位就是把原来物流云平台的所有基础数据(大区信息、大仓信息、仓组信息、门店信息、SKU信息)等整合起来供业务系统使用。所以它主要有2个功能,第一、从各个业务方同步基础数据;第二、提供给各个业务方查询的RPC接口。这样看来基础数据已经解决上文问题1的大半和问题3的全部。其实并不是这样,基础数据只存储了门店的基本信息并没有提供编辑入口,同时在门店维度上各个业务系统对于门店信息的维护变化多端,所以以当前物流基础数据的数据结构,没办法支持动态扩展字段。

基于上面的考虑,面对上文中提出的3个问题,我们决定另建一个系统—营建系统。
1.建立一个更顶层的系统把门店信息的维护入口全部统一到这里,同时提供一个标准服务接口供外部系统获取门店基本信息。所以我们提出两步走策略:第一步,统一门店信息维护入口和门店信息查询接口,门店维护的门店基本信息依然同步到物流基础数据。第二步,彻底替换物流基础数据。

2.营建服务要建立一个动态数据模型,模块化管理各个业务方的门店数据,模块中的字段要可以动态配置避免未来业务方对于门店属性的新增编辑让作为基础服务的营建服务频繁修改发版,同时要对门店信息的权限能能够精细化管理精确到字段维度

解决方案中的第1点,是一个系统架构设计的问题,本文就不再赘述,下面我们来重点讲讲第2点如何实现一个动态数据模型。

二、动态数据模型的选择

目前市面上主流的动态数据模型有3种,他们各有优劣,下面我们就详细介绍他们的实现方式和优缺点,笔者在此声明这里的优缺点对比只是针对我当前所做业务的对比,这里提供的是一个技术选型的思路,大家在选择技术实现的时候可根据自己的业务进行。

2.1 JSON存储动态数据

除了一些基础字段外对于动态数据全部存储到mysql的json字段中,动态字段的编辑都通过修改json的属性实现。基本数据结构如下:

优势

1.使用SON存储代码实现简单,对于门店动态属性对象直接转为JSON后直接存储到数据库,开发效率高。
2.对于动态字段的查询修改mysql都有对应的函数支持。

劣势

1.mysql中对于json字段的函数对mysql版本敏感,对于mysql 5.7以下的版本就不要考虑使用这种方式了。
2.没办法进行join查询。
3.查询效率低。
营建服务并没有使用json存储动态字段,其主要原因也不是因为上面的三个劣势,营建服务的要求门店信息划分为两个维度,分别是模块和动态字段。一个门店有多个模块的动态数据,每个动态模块的数据下又有多个动态字段。如果我们要用JSON存储方案就会有两种数据结构,第一种是上图中的动态实体中增加模块属性字段,那么一个门店就会存在多个动态实体,其实上对于模块来说,这种方式反而变成了下文中要提到的行转列。第二种方案是我们把模块信息存储到JSON字段中,这样就会让json中字段是对象的情况,那么mysql的json查询编辑函数就不能够支撑我们的业务需求了。

2.2 预定义冗余列存储动态数据

预定义许多不同类型冗余的列作为动态字段的槽位,动态数据的值都放到对应的槽位中,数据结构如下:

优势

1.统计数据方便,因为相同的业务字段都在同一列查询和统计都非常方便。
2.支持join操作。

劣势

1.实现复杂,需要实现一套复杂的动态sql逻辑。
2.查询效率相对较低,无法直接在字段上添加索引。
3.前期需要预估好各个类型字段的最多数量,达到最多数量后扩展十分麻烦
营建服务并没有使用这种方案,主要出于三方面考虑,第一、当时营建服务的动态字段并不需要join查询,并且门店的统计数据不会涉及到动态字段;第二、当时工期比较赶,无法在短时间内实现这套方案;第三、营建服务未来要承接所有业务方的动态字段,无法预估各种类型字段的最大值。

2.3 行转列存储动态数据

通过建立两张表一张表存储公共数据,另一张表存储动态数据,公共数据表和动态数据表是一对多关系,数据结构如下:

优势

1.开发速度快,因为其基本数据结构依然是mysql的结构性数据研发速度快。
2.可以配置无数个不同属性,以应对应应对快速变化的业务需求。

劣势

1.没办法进行join查询。
2.如果遇到统计数据报表的时候实现会比较麻烦
3.通过动态字段查询比较慢
我们最终选择的方案是行转列,第一、我们的开发工期比较短;第二、业务方太多难以统计各方各个类型字段的最大值;第三、门店信息无join查询和报表导出;第四、当前营业中门店不超过600家,数量不大,所以我们用了分布式缓存的方案来解决查询慢的问题。

三、营建服务动态数据模型实践

基于上文中的思考,我们采用了行转列的技术方案,接下来我们来详细介绍行转列在营建服务中的具体实现细节。

3.1 数据模型

对于门店信息中的公共字段如门店编码、门店名称、门店位置等信息我们依然用传统数据格式存储在一张基础信息表中,对于动态字段如营业执照、房屋合同等信息存储在动态动态属性表中具体数据结构如下:

3.2 布局

布局包含两个部分模块布局和字段布局,目前营建的模块和字段的布局主要是控制各个模块和字段的展示顺序,当然我们当前的数据结构是支持字段的更精准的控制的比如每行显示几个字段,字段显示宽度和高度等都可以。由于当前模块的设计是所有门店共用同一个模块,所以模块的布局我们只是在模版表中添加了一个order_by字段来控制其显示顺序。

模版下的字段我们则是通过在模块表中添加一个JSON字段来控制模版字段的布局。由于当前业务中只需要控制模版字段的显示顺序,但是为了方便未来扩展我们并没有像模块布局一样在模块下字段中加一个字段来实现排序,我们在模版表中添加了一个JSON字段用来控制其下字段的布局,这样能够防止调整一个字段的顺序而引起模块下所有字段排序字段的更新同时也能够方便未来扩展控制更为复杂的字段布局。上图中门店base表中的字段也会在模块中配置,所以基础字段的布局也可以被控制。

3.3 字段

在字段的实现中如果从技术角度分类可以分为三大类:内部引用字段、引用字段和动态字段。从业务角度分为了:文本、链接、单选多选、日期、数字、条件字段以及组合字段等。

接下来从技术角度分类讲一下各种类型的实现方式:

3.3.1 内部引用字段

内部引用字段可以简单的理解为存储在门店base表中的公共字段。之所以把公共公共字段也要在模版中定义的原因是为了控制公共字段的布局以及字段的权限,同时把所有门店信息的业务字段校验统一逻辑。这种字段在模块字段表中有一个in_reference字段,这个字段标示着门店base表对应的类的一个属性。比如门店编码在门店base表对应的属性是warehouseCode那么in_reference字段的值就是warehouseCode。门店编码这个属性的值不会存储在微仓属性表中,而是存储在门店base表中。在编辑对应字段是后端统一接口都是接收的动态字段。后端服务查询到对应字段如果in_reference的值不为空则会通过反射把对应的值存储到门店base表中。

3.3.2 动态字段

动态字段的定义非常简单就是这个字段的value值存储在门店属性中,他的实现逻辑非常简单不再详细介绍。

3.3.3 引用字段

所谓的引用字段就是不可以通过web页面编辑,而是他的值通过它配置的引用字段的值通过计算得到的值。比如说我们有一个门店等级字段。他是通过门店面积计算门店等级的,大于150平为S级,大于120平小于150平是A级……,也就是说在编辑门店面积的时候这个字段也要跟着变化。在模块字段表中我们配置了一个reference_field_id字段这个字段记录着它引用的字段,同时会在save_script中记录着引用字段的计算逻辑脚本。当一个字段被编辑时,会查询这个字段是否被其他字段引用如果被引用那么查看,其对应的计算脚本根据脚本编辑这个引用字段的值。

可能你会问为什么引用字段一定要存储下来?从上面的描述似乎引用字段只是展示,那么在展示的时候计算一下不就行了?其实我们主要是基于引用字段也会被作为查询条件的考虑才把引用字段也存储起来。上文中提到了存储脚本计算,我们主要是引入了一个动态脚本语言来实现的,关于这个细节的实现不再赘述。

其实引用字段和内部引用字段以及动态字段字段有重合的部分,引用字段也可以属于内部引用字段也可以属于动态字段,但是内部引用字段和动态字段是严格区分不重合的

从业务上我们又把字段划分为以下类型:

3.3.4 文本字段

文本字段又细分为单行文本与多行文本,在模版字段中有两个字段是min和max,分别用于控制文本的最短和最长。

3.3.5 单选多选

单选和多选字段通过在模版字段配置中添加一个配置JSON字段init_config用于控制,字段所有的选项。JSON字段格式如下:

[{"label":"个人","value":"1"},{"label":"公司","value":"2"}
]

3.3.6 链接字段

链接字段又分为图片、视频、链接三种,在数据中存储的值都是用逗号分隔的文件链接。此时模版字段中的min和max控制可以上传的图片、视频以及链接的最大个数。

3.3.7 时间字段

时间字段分为日期、月份、时间三种,都以时间格式化后的字符串存储在数据库中。

3.3.8 数字字段

数字字段又分为正数、小数两种,直接把数字转化为字符串存储在数据库中。

3.3.9 组合字段

组合字段是由多个字字段组成,假设说一个班级是一个实体,那么班级里的学生就是一个组合字段,组合字段的子字段是学生姓名、年龄等,所以组合字段在页面的展示形式是一个表格。组合字段字段的定义同样存储在模版字段表中,而组合字段本身的定义存储在模版字段的init_config列中,其用json数组存储了它所有的子字段id。格式如下:

["FI-00020143","FI-00020142"]

组合字段的子字段不再分开存储,而是全部存储在组合字段的value中其格式如下:

[{"FI-00020143":1,"FI-00020142":"文本字段值"
}]

数组的形式存储着子字段的值,数组中每一项的key对应字段id,value对应字段的值。

3.3.10 条件字段

条件字段是当你选择某个条件时,就要填写在这个条件下的子字段的值。他的子字段也是存储在模版字段表中,定义存储在init_config中格式如下:

[{"fieldIds":["FI-00020128"],"labelName":"是","value":"1"},{"fieldIds":["FI-00020127"],"labelName":"否","value":"2"}
]

其中fieldIds就是对应选择条件下的子字段ID。
其子字段的值不再分开处理,全部存储在条件字段中,存储格式如下:

{"conditionValue":1,"subValues":{"FI-00020128":"子字段值"}
}

其中conditionValue对应的值就是条件值,subValues中每一个key对应子字段id,value对应子字段的值。

四、代码实现

上文详细介绍了各个字段的存储结构,接下来我们讲讲代码实现,从上文不难看出不同的业务字段要有不同的实现方式,那么我们选择设计模式中的策略模式来实现不同的字段逻辑。其实对于所有的字段无非就是三种操作:
1.字段新增/编辑的时候的校验。
2.字段的数据值转换成对应字段格式的存储值。
3.字段的存储值转换成展示值。

所以我们抽象出来的公共类如下:

public abstract class ModuleFieldStrategy {/*** 导入对象转化为存储值** @param moduleFieldDOMap* @param fieldDO* @param fieldColumn* @param subColumns* @param parentRequired* @return*/public abstract WarehouseAttributeDTO importConvert2PO(Map<String, ModuleFieldDO> moduleFieldDOMap, ModuleFieldDO fieldDO, ColumnDTO fieldColumn, List<ColumnDTO> subColumns, Boolean parentRequired);/*** 参数校验** @param fieldValue* @param parentRequired*/public abstract void fieldValueValidate(Map<String, ModuleFieldDO> moduleFieldDOMap, ModuleFieldDO fieldDO, ModuleFieldValueDTO fieldValue, Boolean parentRequired);/*** 转换为展示对象** @param fieldValue* @return*/public abstract ModuleFieldValueDTO parse2VoValue(WarehouseAttributeDTO fieldValue, ModuleFieldDO fieldDO, Map<String, ModuleFieldDO> allFieldMap);}

各种类型的字段通过实现上面的类,来实现自己的业务逻辑,对于组合字段和条件字段比较特殊,他会循环调用自己的子字段的策略来实现自己的业务逻辑,最后在业务操作时根据字段类型获取相应策略即可。

五、权限控制

目前的权限控制主要是对用户角色控制模块、字段的编辑和展示;在模块模版表以及字段表中都有一个字段is_show,具体的权限在权限系统配置对应模块的id,如果对应角色没有模块权限并且is_show 字段是true那么该字段就不会在前端显示。这一块的控制主要放在了权限系统和前端,并且控制实现逻辑也非常简单就不再细说。

六、查询优化

在本文最初已经提到过,营建服务需要提供统一的查询入口,由于门店字段非常多,不可能每接入一方我们就要开发一个接口,于是我们参考了大数据团队的技术方案,让调用放传入需要查询的字段id以及查询条件,营建服务返回对应字段id的值,传入参数:

public class WarehouseConditionDTO extends ClientDTO implements Serializable {private static final long serialVersionUID = -2304572913021892020L;/*** 返回结果字段列表*/private List<String> resultList;/*** 条件列表*/private List<Where> whereList;/*** 排序列表*/private List<Order> orderList;/*** 页码,第一页从1开始*/private Integer pageNum;/*** 分页大小,默认10,最大100*/private Integer pageSize;
}

参数示例:

{"appCode": "sms-site", "appSign": "nbynkFKYSPrtMrOs", "resultList": ["FI-10003", "FI-10004"], "whereList": [{"fieldId": "FI-10035", "symbol": "=", "values": ["S"]}], "orderList": [{"fieldId": "FI-10020", "sortType": "asc"}, {"fieldId": "FI-10012", "sortType": "asc"}], "pageNum": 1, "pageSize": 2
}

七、落地效果及复盘

7.1 落地效果展示

模块配置效果展示如下:

模块配置效果展示:

门店信息应用动态数据后展示效果如下:

7.2 技术复盘

在需求层面,当前营建服务已经完成了所有门店的灰度,收拢了门店信息的出入接口。将门店信息分为了7大模块,7大模块分别授权,不同的运营人员维护不同的模块,各模块可以自由添加自己想要的字段,在100%满足了业务诉求的同时,做到了各个业务模块精细化管理,降低了各个业务方维护同一份数据相互相应产生错误的概率。

在技术层面,由于动态数据模型的引入增加了代码复杂度,同时为了满足业务查询需求,营建服务加入了分布式缓存,这些在实现本次需求中确实增加了大概3pd左右的开发量,但是由于灵活的表单配置方式,在上线后关于各个业务方对于门店信息新增属性,修改属性的开发量直接降为了0,大大降低了未来的研发成本。

八、未来展望

虽然当前数据模型已经完全能够应对当前的业务需求但是还有一些细节需要打磨,比如对于单选多选字段可能选项不是配置出来的而是从第三方系统获取的,因此这里需要实现一个灵活接入第三方系统动态获取枚举的接口。同时对于门店数据多维度的可视化展示也需要完善,能够通过可视化的页面让运营同学更直观的看到门店的数据。

关注公众号,一起学习,共同成长

动态数据模型分析与应用相关推荐

  1. 景观格局动态变化分析方法(基于ArcGIS、Fragstats、ENVI、ERDAS、Patch Analysis for ArcGIS) (2011-03-15 08:07:03)...

    转载 http://blog.sina.com.cn/s/blog_54388b830100rfod.html 分类: GIS基础与应用, 景观格局 虽然以前对该类分析已经进行过研究,并已完成过相关项 ...

  2. 全球及中国皮肤晒黑喷雾行业销售模式及动态盈利分析报告2021年版

    全球及中国皮肤晒黑喷雾行业销售模式及动态盈利分析报告2021年版 [搜索鸿晟信合查看官网更多内容!]  2020年,全球皮肤晒黑喷雾市场规模达到了 百万美元,预计2027年可以达到 百万美元,年复合增 ...

  3. AMAP-TECH算法大赛开赛!基于车载视频图像的动态路况分析

    简介:车载视频图像包含了更多的信息量,给了我们另外一个解决问题的视角.通过视频或图片,可以观察到路面的真实状态,包括机动车数量.道路宽度和空旷度等等.基于车载视频图像可以获取更准确的路况状态,为用户出 ...

  4. 【MyBatis框架】订单商品数据模型-分析思路

    我们接下来要对即将用来实验的订单商品数据模型进行分析. 首先在MySql中创建mybatis数据库,在其中创建以下表: [sql] view plaincopy CREATE TABLE `items ...

  5. 动态污点分析隐式流--动静结合的解决方法

    隐式流 我们知道,对于动态污点分析来说,检测所有的信息流动是不可能的.因为污点只沿着实际执行的路径流动,特别地,动态污点分析会漏报一些implicit flows(控制流). 关于隐式流,参考之前的文 ...

  6. android全系统动态二进制分析--CopperDroid

    1. 简介 CopperDroid通过直接监测System call,不但可以判断操作系统的一些动作(比如进程创建.文件创建),还可以判断进程内部的动作(比如短信发送,这种行为和android的对象有 ...

  7. 全球及中国游戏耳机行业销售模式与动态盈利分析报告2022版

    全球及中国游戏耳机行业销售模式与动态盈利分析报告2022版 --------------------------------------- <修订日期>:2021年12月 <报告价格 ...

  8. ArcGis空间分析学习:土地利用动态变化分析

    说明:本实验主要参考GIS空间分析实验教程-田永中,适合初学者参阅 目录 一.实验理论 (1)实验目的 (2)实验内容 (3)实验原理 (4)实验方法 (5)实验流程 (6)实验材料 二.实验步骤 ( ...

  9. 基于Matlab的二阶电路的动态电路分析!

    基于Matlab的二阶电路的动态电路分析! 算法思路 1.一阶电路 1.只有电感:先得到电感所在端口的戴维宁等效电路: 接着代入公式计算,得到 i-t图像: 2.只有电容:先得到电容所在端口的戴维宁等 ...

  10. 动态恶意软件分析工具介绍

    网络安全观察者 在本教程中,我们将介绍动态恶意软件分析工具,用于了解恶意软件执行后的行为.本教程是我们恶意软件分析教程中的第2部分.如果您尚未阅读本系列的第1部分,请先阅读本系列教程1,然后再继续这一 ...

最新文章

  1. Drug Target Review | 虚拟现实(VR)用于新药设计
  2. JavaScript学习总结(7)——JavaScript基础知识汇总
  3. mysql编辑工具wf_轻松构建自定义WF设计器
  4. 【NLP】 聊聊NLP中的attention机制
  5. 第一个冲刺期的第九天
  6. [hackinglab][CTF][解密关][2020] hackinglab 解密关 writeup
  7. 【转载保存】java 23种设计模式 深入理解
  8. 云计算风起云涌,超融合恰逢其时!
  9. Fix My iPhone Mac版:修复iPhone白苹果、黑屏、卡住恢复错误等iOS 15 升级失败
  10. 信息学奥赛一本通C++语言——1052:计算邮资
  11. 信息学奥赛一本通C++语言——1022: 整型与布尔型的转换
  12. 算法分析与设计「三」二分算法
  13. 【渝粤教育】国家开放大学2018年秋季 0714-21T建筑识图与CAD 参考试题
  14. 从0开始学习 GitHub 系列之「04.向GitHub 提交代码」
  15. 数值积分方法的总结(从简单梯形积分到龙贝格积分、自适应积分、高斯积分等)
  16. 计算机应用if函数题目,if函数练习题.doc
  17. linux服务添加互信,Linux多节点互信配置
  18. Java通过mongo-java-driver-3.0+查询mongodb数据库
  19. Android 百分比布局、权重、隐藏TitleBar、引入自定义控件
  20. 互联网电商大厂库存系统设计案例讲解

热门文章

  1. 人工神经网络中的激活函数
  2. grid++ report报表模板设计手把手教学——简单模板、特殊属性设置、批量打印分组模板
  3. 程序员是一个很好的职业!
  4. 关于Field[]的解释!!!
  5. chapter02“良/恶性乳腺癌肿瘤预测”的问题
  6. 2021年大连12中高考成绩查询,2021年辽宁大连各高中中考分数线及录取时间结果查询安排...
  7. 查询异常:java.sql.SQLException: HOUR_OF_DAY: 0 -> 1
  8. 通达信打板首板指标公式源码准确率90%以上
  9. 2021AI中台白皮书 附下载
  10. 《学姐教我写代码(一)》十道题搞定C语言