This will delete the page "会员系统Java端接口交接文档"
. Please be certain.
目前在192.168.20.122:3000
上的项目是MemberCenter_DW
,一共有两个分支,其实大部分功能都是本人开发的,剩余一些模块是祖传代码,没作过多修改,本来也没文档留下来。
主要是用的代码,里面有很多模块,比较复杂,后面详细说明
主要内容和master相同,主要是多了开发动态传递查询参数来实现查询代码,同时编写了一个小模块,用于简化单表CRUD逻辑,仅需要编写小量代码即可,CRUD逻辑均已在抽象层已经做好实现,该模块在member-web-common
中,后面说明。
会员系统是用于管理公司的会员,同时与大数据端相连接,用于展示离线分析的结果。因为这个系统成因复杂,后端是由两种语言语言实现,dotnet和Java,我在到职时是Java程序员,而因为历史遗留问题,这个项目一开始是使用dotnet实现的,而后面也有计划迁移到Java中,所以我这边就使用Java开发新功能和迁移部分老代码。
目前一共有6个模块:
开始了,这个模块因为历史遗留问题,原来有个旧项目是「元数据管理」,这块是用于管理数据库表的信息的。而这个功能其实是使用kettle
的依赖包,但是呢,这个kettle的依赖包的功能也不满足,所以要去修改它依赖包的代码,而这个修改却又是直接导入了依赖包,然后又在本项目代码中重新创建一个依赖包中同名的类型,没有运用继承等方式,而是想要直接覆盖。
由于kettle的依赖在maven仓库中没有找到,依赖十分不好整理,所以我就直接把要用的kettle依赖在maven中从本地导入。而因为不好重构,上级也没有给到什么文档,本人就把整个历史项目代码搬过来使用。
需要注意的一点是,给模块在启动时会报错kettle的错误,我在com.liangjian.dataplatform.member.startup.KettleDatabaseRunner
中添加了spring启动时的处理,如果后期在kettle方面出了问题,可以尝试从这里开始排查。(至于为什么这个模块会这么乱,可能已经失传了)
业务模块的映射对象和dao层。需要注意的是,者整个项目中使用了非常多的数据源,而dao层使用的是JPA
,因此在spring配置中配置了多数据源的配置,这些不同的数据源所对应的映射对象在com.liangjian.dataplatform.member.entity.po
下,通过不同的包来区分。
另外,这些映射对象的id
的值是通过雪花算法来实现生成的,代码在com.liangjian.dataplatform.member.entity.genernator
中。
下面是表格说明不同的映射对象的说明,只会列出由本人实现的部分,非本人实现的部分我可能也不太了解。
目录 | 名称 | 说明 |
---|---|---|
po/elasticsearch | UserGrowthLog | 在ES中的,用户成长值日志信息 |
po/elasticsearch | UserPointLog | 在ES中的,用户积分日志信息 |
po/primary | FeatureInfo | 前端图表的配置信息 |
po/primary | FeatureLog | 前端图表的变更日志信息(人工输入) |
po/primary | JobInfo | 标签任务 |
po/primary | LayoutConfig | 前端动态页面的配置信息 |
po/primary | MetaCore | 元数据管理的元数据存储结构 |
po/primary | MetaRel | 元数据管理的元数据关系结构 |
po/primary | SqlEntity | SQL查询配置对象,前端用于动态自行配置的sql |
po/primary | TagRule | 标签规则 |
po/secondary | UserLevelDesc | 用户等级描述 |
po/secondary | UserLevelUnit | 用于等级实例 |
po/secondary | UserPointDesc | 用户解纷描述 |
po/secondary | WeixinUserInfo | 微信用户信息关联 |
po/tertiary | OrderProducts | 商品信息 |
po/tertiary | Orders | 订单信息 |
po/tertiary | ProductImages | 商品图片 |
po/tertiary | ProductSamll | 商品简略信息 |
vo | TransportInfo | 物流信息 |
vo | TransportTrace | 物流详细信息 |
主要的核心功能。
权限控制的配置在PermissionConfg
中,目前是通过配置文件application.yaml
中的runOnProduct
字段控制,当这个字段不存在或为false时,不启动权限控制。由于主要的用于权限控制的token信息、后台登录控制等位于dotnet端,所以要先通过接口去后去token信息再进行权限控制。由于本项目的接口都是restful
的接口,也就是对于单表会有GET
POST
PUT
DELETE
来对应 查、新增、更新和删除。
sequenceDiagram
Client ->>+ Server: 请求访问,请求的信息
Server ->> dotnetServer: 通过接口获取token字符串
dotnetServer -->> Server: 返回token字符串
Server ->> Server: 该用户是否有权限访问
Server -->>- Client: 响应或拒绝
这个功能比较复杂,其实是利用kettle依赖包给实现一个表元数据管理。
先画一下类型的结构设计:
graph BT
subgraph 通过反向工程获取
A2[DataNode: 抽象的数据节点]
B2[MetaProcedure: 元数据 反向工程 存储过程] --> A2
C2[MetaSchema: 元数据 反向工程 Schema] --> A2
D2[MetaTable: 元数据 反向工程 表] --> A2
E2[MetaView: 元数据 反向工程 视图] --> A2
end
subgraph 直接存储记录的
A[DataNode: 抽象的数据节点]
B[MetaCoreRoot: 元数据根] --> A
C[MetaCorePackage: 元数据目录] --> A
D[MetaDataSource: 元数据 数据源] --> A
E[PDM: 元数据 表] --> A
F[PDMColumn: 元数据 表字段] --> A
G[MetaTable: 元数据 反向工程 表] --> A
B1[MetaCorePackageTransfer] --- B
D1[MetaDataSourceTransfer] --- D
E1[PDMTransfer] --- E
F1[PDMColumnTransfer] --- F
H[DataNodeTransfer: 抽象转换器,将抽象的数据库结构转成内存结构] --- B1
H --- D1
H --- E1
H --- F1
I[DataNodeTransferService: spring组件,转换器] --- H
end
元数据的逻辑是:对于元数据而言,数据库存储的结构是MetaCore
,但是在业务层是有不同的类型的,因此需要将存储层的MetaCore
根据不同的标示转换成不同的数据结构,如PDM
PDMColumn
MetaCorePacakge
MetaCoreRoot
等。转换的逻辑使用了类型责任链的模式,如下:
graph TB
start --> sp1[从数据库中去po]
sp1 --> sp2[DataNodeTransferService.transfer]
sp2 --> sp3[遍历DataNodeTransferService]
sp3 --> cond[判断Transfer和MetaData的标示]
cond --> |true| sp4[key匹配时直接返回xxTransfer.transfer]
cond --> |all false| sp5[返回初始化无意义的Vo]
sp4 --> finish
sp5 --> finish
而在Service层获取元数据的逻辑:首先判断是否需要进行反向工程的,如果需要反向工程的,需要调用kettle的接口,若不需要,直接以上述方式转换MetaCore即可:
graph TB
start --> sp1[queryById获取子节点]
sp1 --> sp2[judgeAndGetChildren 先执行判断是否需要反向工程]
sp2 --> cond{是否需要反向工程}
cond --> |true| sp3[MetaReverseService 获取节点逻辑]
sp3 --> sp4[直接返回结果]
sp4 --> finish[按照其父类DataNode返回结果]
cond --> |false| sp5[从库中取出节点列表]
sp5 --> sp6[通过DataNodeTransfer转换Vo结构]
sp6 --> sp7[返回结果]
sp7 --> finish
标签规则功能主要分两块,一块是标签规则的管理部分,一块是标签规则调度执行部分,我复杂的是前者。标签规则是规定好标签规则的结构,然后执行时是通过这个标签规则生成查询sql。标签规则结构如下:
graph LR
A[TagRule:数据库po] --> B[TagRuleVo]
B --- C[ConditionNode:条件节点]
C --- D[LogicTree:逻辑树]
重点在于ConditionNode
和LogicTree
的结构,用于构建可以不断递归的结构:
BNF 范式:
CondtitionNode(条件节点) ::= LogicTree, Map<String,String> result
LogicTree(逻辑树) ::= BinOperation , LogicNode*
BinOperation(二元运算) ::= BinOperaTye, PDMColumn, String
LogicNode(逻辑节点) ::= SimpleLogicNode | BracketsLogicNode
SimpleLogicNode(单逻辑节点) ::= LogicType, BinOperation
BracketsLogicNode(多逻辑节点) ::= LogicType, BinOperation, LogicNode*
BinOperaType(二元运算符) ::= EQ | LE | GE | NE | CONTAINS | BELONG
LogicType(逻辑类型符) ::= AND | OR | NOT | ANDNEW | ORNEW
ConditionNode
生成SQL的逻辑是,每一个BinOperation
用来表示查询的单个条件,每一个LogicNode
是用来表示条件与条件之间的关系,最后组装起来就是一个完整的查询条件:
对于条件:a = 1 and b = 2 or (c = 3 and (d = 4 or e = 5))
+-----------+
|LogicTree: | [a = 1] *
|node: a = 1|
+----+------+ and [b = 2]
| +----------------+
| |SimpleLogicNode:|
| |type:and |
+-----+node:b = 2 |
| +----------------+
| or ([c = 3] (*))
| +------------------+
| |BracketsLogicNode:|
| |type:OrNew |
+-----+node: c = 3 |
+--------+---------+
| and ([d = 4] (*))
| +------------------+
| |BracketsLogicNode:|
+---+type:AndNew |
|node:d = 4 |
+---------+--------+
| and e = 5
| +----------------+
| |SimpleLogicNode:|
+--+type:or |
|node: e = 5 |
+----------------+
功能管理是在管理前端图表的,前端的图表是和动态前端页面设计在一起的。功能管理是在前端动态导入superset
的图表,当然superset的设计是要在superset里编辑,然后将编辑的export url导入到功能管理里管理。这里没有特别的说明,仅仅是规定好类型结构后CRUD。
前端动态页面功能,这个功能先说一下背景:上级希望通过前端页面的拖拽来实现动态编辑页面,而不需要写代码,因此这块存储的是一个前端模块react-grid-layout
所使用的布局信息。这里没有特别的说明,仅仅是规定好类型结构后CRUD。
这块是用于管理前端编写管理的sql,这些写的sql会用于在功能管理
和动态页面
中生成的图表而使用。在动态页面中,要拖拽编辑到一些图表例如「各地门店销售情况」,「昨天各网店销售情况」等,因为拖拽编辑时去写sql会很麻烦和不安全,所以在这里管理好,在拖拽时直接输入SQL配置的id即可。这里没有特别的说明,仅仅是规定好类型结构后CRUD。
这块就与业务相关度很高,是在「积分商城」中管理会员的积分和成长值的,而「积分商城」是在微信端中的应用的后台管理,后面会说明到。
前端中的「积分商城」-「会员积分」的列表是从ES中查询出的列表,而点击每一个会员右侧的操作按钮就是去操作UserPointEs
和UserGrowthEs
表的CRUD操作。
等级管理是微信端的「积分商城」的等级的管理后台,前端由两个tab管理,多个等级的设置和等级的描述。在保存的时候其实是两个表结构UserLevelDesc
UserLevelUnit
共同处理,其中没什么逻辑。
这块内容本身是在分支develop
中的功能,本意是通过一个结构话的结构来描述CRUD中的查询,有很多个条件的时候自动适配,而不需要特地写查询条件接口,这部分功能并没有实际使用,后期可能不会使用,所以感兴趣的时候可以研究看看。这块的价值在于抽象化查询条件,减少特殊化查询条件接口代码的编写。
这块实际的在member-web-common
中,但是spring的配置端在本模块,先简单介绍。
下面仅给出这个结构的设计:
BNF 范式
DynamicParams ::= DynamicFilter*, (分页信息) Integer, Integer, (排序信息) SortDirection, String
DynamicFilter ::= field, OperaType, value
OperaType ::= Equal | NotEqual | Less | LessEqual | Greater | GreateEqual | Contains | StartWith | EndWith
SortDirection ::= ASC | DESC
下面由表格来说明一下各个目录和代码。
目录 | 名称 | 说明 |
---|---|---|
algorithm | * | 代码已作废 |
config | CorsConfig | spring-boot的Cors配置 |
config | DataSourceConfig | spring多数据源Bean配置 |
config | DB*Config | 多数据源的指向配置 |
config | 数据源的分库分表配置,现已废用 | |
config | DuridConfig | durid数据源配置 |
config | DynamicParamConfig | 用于解析get参数成动态查询的配置的拦截器 |
config | ElasticsearchConfig | Elasticsearch配置 |
config | PermissionConfig | 权限控制的拦截器配置 |
config | SwaggerConfig | swagger文档配置 |
constant | DatabaseUrls | 常用数据的url |
constant | DataNodeConstant | 元数据DataNode的类型常量 |
constant | MetaCoreConstant | 元数据的标示常量 |
controller | BirdGridController | 前端BirdGrid脚手架的控制层,目前好像没用上 |
controller | FeatureInfoController | 功能管理控制层 |
controller | ImportSupersetController | 导入superset图表的控制层 |
controller | LayoutConfigController | 动态页面控制层 |
controller | MetaCoreController | 元数据管理控制层 |
controller | SqlApiController | 从Clickhouse 查询数据的控制层 |
controller | SqlEntityController | SQL配置管理控制层 |
controller | SqlExportController | 从Clickhouce 查询数据并导出xslx下载 |
controller | TagRuleController | 标签规则控制器 |
controller | UserGrowthLogController | 用户成长值控制层 |
controller | UserLevelSettingController | 用户等级管理控制层 |
controller | UserPointLogController | 用户积分控制层 |
controller | WeixinUserInfoController | 微信用户积分、成长值控制层 |
dao/elasticsearch | *Repostitory | 用户积分、成长值的ES的DAO层 |
dao/* | * | 这块结合member-services-models 来看,都是一一对应的DAO层 |
entity/pojo | BirdGirdFilter | BirdGird的过滤器类型 |
entity/pojo | DataBaseInfo | 元数据中的数据库信息类型 |
entity/pojo | KeyValuePair | 一个kv对类型 |
entity/pojo | PkAndNullable | 元数据中的内部定义数据的信息类型 |
entity/token | DynamicParamGetter | 动态参数的threadlocal |
entity/vo/datanodes | * | 元数据逻辑所需要用的类型结构 |
entity/vo/detailvo | * | 元数据逻辑里Vo的逻辑的类型结构 |
entity/vo/request | * | 一些restful入参参数类型结构 |
entity/vo/response | BirdGirdTableResponse* | 前端Birdgird的响应结构 |
entity/vo/tagrules | * | 标签规则逻辑所需要的类型结构 |
entity/vo | LayoutConfigVo | 动态页面的VO结构 |
entity/vo | LayoutInfo | 动态页面的VO的内部结构 |
entity/vo | MetaRelVo | 元数据的关系VO |
entity/vo | TagRuleVo | 标签规则的VO结构 |
interceptor | DynamicParamAnalyze | 动态查询参数分析器的拦截器 |
interceptor | PermInterceptor | 权限控制的拦截器 |
service | DataNodeTransferService | 节点转换逻辑层 |
service | IBirdGirdService | 前端BirdGird的逻辑层 |
service | IFeatureInfoService | 功能管理的逻辑层 |
service | IFeatureLogService | 功能管理的日志的逻辑层 |
service | ILayoutConfigService | 动态页面的逻辑层 |
service | IMetaCoreCUDService | 元数据的增删改的逻辑层 |
service | IMetaCoreQueryService | 元数据的查询的逻辑层 |
service | IPointGrowthRuleService | 会员积分成长值逻辑层 |
service | ISqlEntityService | SQL配置管理的逻辑层 |
service | ISqlExportService | clickhouse查询导出逻辑层 |
service | ITagRuleService | 标签规则逻辑层 |
service | ITagRuleSqlService | 标签规则转换sql逻辑层 |
service | IUserGrowthLogService | 会员成长值逻辑层 |
service | IUserLevelService | 积分商城等级管理逻辑层 |
service | IUserPointLogService | 会员积分逻辑层 |
service | IWeixinUserInfoQueryService | 微信会员信息逻辑层 |
这个模块是为了让一些通用的模块写在这里,上面有说过,member-common
这个模块因为历史遗留原因变得难以整理,所以把一些web通用型模块写在这里。
而目前这个模块并没有什么代码在,只有一个动态查询参数的模块,结构上面已经给出DynamicParam动态查询。
下面就简单说说如何做到动态的查询:因为我们的DAO层用的JPA
,而动态查询的这部分已经算是复杂的查询了,所以这里是把DynamicParam
转换成Specification<T>
,逻辑其实是简单的,就在DynamicParam
中有实现:
graph TB
A[DynamicParam] --> B[DynamicFilter]
B --> C[filter.toJpaSpecifition]
C --> |Specifition列表| B
B --> |组合Specifition| A
A --> |page,size,sortDirection| D[完整的Specifition]
D --> E[Dao层]
这个模块其实是整个Web项目的入口,本模块的pom的会指定声明依赖了那些模块。这个模块仅如此。
这个模块其实是微信端的「积分商城」的后台管理。功能相对会表简单,有会员的绑定登录,下单积分商品,查看积分、成长值,查看物流等信息。
绑定登录的逻辑是,用户从公众号中点击进入积分商城,然后后台拿到微信的code,后端根据code去获取用户的openid
和unionid
,然后在数据库表中查询,若查询到有此用户就返回登录的用户信息,否则,跳转到绑定。而绑定的逻辑是,用户输入了手机号后,后端将该手机号和随机生成的code放入redis
中,过期1分钟,然后后端将该随机code发送短信,后端再作判断:
graph TB
start --> sp1[点击微信公众号按钮]
sp1 --> sp2[微信端带微信code跳转]
sp2 --> sp3[后端根据code获取用户的openid]
sp3 --> cond1{数据库中是否有该用户}
cond1 --> |有| finish[返回登录用户信息]
cond1 --> |无| sp4[跳转到绑定页面]
sp4 --> sp5[用户输入手机号]
sp5 --> sp6[后端发送随机code短信]
sp6 --> sp7[校验,绑定]
sp7 --> finish2[后端重新登录,返回登录用户信息]
这块的控制大致上也是和会员系统的权限控制是一样的,都是利用jwt来控制。后端拿到客户端传上来的jwt,1、进行key校验,2、进行过期校验。不通过校验的直接视为无法访问,需重新登录(重新进入),而每次通过校验后都会签发新的jwt来保持最新。
物流的查询逻辑其实很简单,查询物流的接口在别的项目中已经有现成的,这里仅需要去调用该接口然后返回给前端即可。
这里的逻辑是这样的,当用户没有登录时,「积分商城」还是能直接显示商品的,只不过都是原始信息。而当用户登录后,用户是有成长值的,每个商品都有对应各个会员等级的优惠。而后台中的会员等级管理就是去管理每个等级对应的成长值。所以在用户登录后将查询出来的商品信息搭配会等级的优惠顶级进行过滤即可。
graph TB
start --> sp1[查询商品连同等级优惠一起]
sp1 --> cond{是否已登陆}
cond --> |是| sp2[对商品的等级优惠进行过滤]
sp2 --> finish[返回分页商品信息]
cond --> |否| sp3[对商品信息的等级优惠给覆盖为空列表]
sp3 --> finish
可以看到上面的逻辑是无论是否已登陆都会把等级优惠信息一同全查出来,这个效率其实肯定不好,但是因为是使用了JPA
,这块的查询代码上变得简单和无脑,所以缩短开发耗时,直接给查出来后再处理。
目录 | 名称 | 说明 |
---|---|---|
api | MySnsApi | 微信通过code获取openid,依赖这块没做好,自己继承实现了个 |
config | WeixinCachingConfig | 微信使用的包需要用的缓存配置 |
config | WeixinInterceptorConfig | 权限访问控制的配置 |
controller | WeixinController | 积分商城主要功能的控制层 |
controller | WeixinIndexController | 微信端跳转控制层 |
controller | WeixinRegisterController | 积分商城登录绑定控制层 |
dao/secondary | MemIngProductLevelRepository | 积分商品等级优惠信息DAO层 |
dao/secondary | MemIngProductPicRepository | 积分商品图片信息DAO层 |
dao/secondary | MemIngProductRepository | 积分商品信息DAO层 |
dao/tertiary | OrderProductsRepository | 正式库中的订单商品的DAO层 |
dao/tertiary | OrderRepository | 正式库中的订单的DAO层 |
entity/token | WeixinJwtTokenGetter | 权限控制的Jwt中Token的threadlocal |
entity/vo/request | PhoneSMSInfo | 手机号等信息的提交参数 |
entity/vo/request | WeixinMessageInfo | 向微信用户发送消息的提交参数 |
entity/vo/response | LevelAndGrowth | 用户等级和成长值信息的响应数据 |
entity/vo | WeixinUserInfo | 微信用户信息Vo |
interceptor | WeixinInterceptor | 微信用户权限控制拦截器 |
service | IMemIngProductService | 积分商品逻辑层 |
service | IOrderProductService | 订单查询逻辑层 |
service | IPhoneSMSService | 发送短信逻辑层 |
service | IQueryOrderService | 查询订单逻辑层 |
service | ITransportQueryService | 查询物流信息逻辑层 |
service | IWerxinUserInfoService | 微信用户逻辑层 |
key | 值类型 | 说明 |
---|---|---|
runOnProduct | boolean | 若开启则开启权限控制 |
superseturl | url字符串 | superset的服务端 |
supersetReplace | 字符串,内部以逗号分隔 | 用于替换由superset端分享过来的url |
supersetReplaceAs | 字符串 | 替换superset分享的url的目标值 |
getTokenUrl | url字符串 | 获取权限token的dotnet服务 |
jwt-secret | 字符串 | jwt加密key |
clickhouseUrl | jdbc url 字符串 | clickhouse的服务端 |
clickhouseUser | 字符串 | clickhouse用户名 |
clickhousePassword | 字符串 | clickhouse密码 |
jwt.expire-time | 数字 | jwt过期毫秒数 |
这个项目仍然涉及到dotnet端的代码,前端和大数据端,有什么问题需要与各个部分沟通。
This will delete the page "会员系统Java端接口交接文档"
. Please be certain.