PowerDotNet平台化软件架构设计与实现系列(17):PCRM个人用户管理平台

个人用户管理是业务系统中非常基础且重要的一个公共服务系统,我们写的绝大多数应用都和个人用户或会员有关,用户(会员)数据安全无小事,必须有一个完备的用户管理平台系统。

因为不同公司的主业务不同,个人用户管理的侧重点也会有不同,PowerDotNet这里介绍的个人用户管理平台,只是个人用户管理系统中很基础的通用功能的一部分。

当然,在我自己开发过的所有公共服务系统中,PCRM是中规中矩一般复杂甚至我个人认为是架构很简单的系统,真正混乱且困难的是订单、支付、财务、结算、库存、生产加工、配送等复合型系统。

曾经在某司接手过一个复杂繁琐另类但其实不中看更不中用的个人用户管理系统,功能极其凌乱,划分非常随意,实现也很恶劣,行业水平之低令人发指,总之就是稀烂到自己人都看不下去,咩哈哈。

因为个人在支付、财务、结算、账户和票券等系统有丰富的开发经验,对PCRM和这些系统的紧密联系耳濡目染,所以也认同某些模块或功能放到PCRM更合理,比如混合支付中的公司账户和积分功能。

在介绍支付平台的文章中,我们提到混合支付的概念,认为"不同的公司有不同的虚拟货币系统,混合支付是指公司的虚拟货币和外部的支付方式一起支付完成支付请求的一种玩法"。

混合支付设计和实现有很大的技术挑战,我已经不止一次开发维护过混合支付代码,尤其是涉及到虚拟货币(账户)、积分和票券等系统,恐怖程度堪称夜能止小儿啼哭,日可令恶犬夹尾,咩哈哈。

虚拟货币(账户)系统每家公司实现的方式可能都不一样,本来可以另开篇幅说明,但根据我的开发经验,这部分工作量也不小,为了快速开发需要,只要设计抽象合理,不拆分也能高效使用,所以就偷懒了^_^。

考虑到虚拟货币(账户)系统和个人用户的紧密联系,将这两个系统划分到一起也无可厚非,我就把用户虚拟货币(账户)系统抽象到PCRM中进行讲解,当然要独立出来也很容易,根据数据表结构前缀拆分即可。

有一种类似账户系统的票券系统,主要处理优惠券相关的业务逻辑,虽然处理方式可以参考虚拟货币系统部分处理逻辑,但又和订单、活动等系统"耦合"较为紧密,这个可以抽象出独立系统,等有空再写写这方面的内容。

根据个人经验,账户和票券系统,设计和实现的不好,往往会造成业务灾难,比如支付金额不准确,财务对账不匹配,同时账户和票券系统容易滋生腐烂代码,这也对社会主义精神文明建设有直接伤害。

支付平台、财务平台和个人用户管理平台在个人多年开发经验里算是投入较多钻研较深入的系统了。我亦无他,唯手熟尔,除了公共组件,业务逻辑同样非常考验程序设计实现水平。

支付、财务和CRM设计实现的不好或者考虑不周到,很容易出错,而根据墨菲定律,任何可能出错的事情最终都会出错。业务逻辑型公共服务的稳定性和可扩展性也非常重要,值得深度开发和积累。

PowerDotNet的设计和实现做了很多取舍,很多特定行业或定制化业务没有吸收进去,毕竟PowerDotNet的重要目标是要实现一套能够被绝大多数公司通用的公共服务。

环境准备

1、(必须).Net Framework4.5+

2、(必须)关系型数据库MySQL或SqlServer或PostgreSQL或MariaDB四选一

3、(必须)PowerDotNet数据库管理平台,主要使用DBKey功能

4、(必须)PowerDotNet配置中心Power.ConfigCenter

5、(必须)PowerDotNet注册中心Power.RegistryCenter

6、(必须)PowerDotNet缓存平台Power.Cache

7、(必须)PowerDotNet消息平台Power.Message

8、(必须)PowerDotNet文件平台Power.File

9、(必须)PowerDotNet支付平台Power.Payment

10、(必须)PowerDotNet财务平台Power.Finance

11、(必须)PowerDotNet人员管理平台Power.HCRM

12、(必须)PowerDotNet基础数据平台Power.BaseData

一、用户管理

1、用户基础

对于用户的基本管理,包括基础资料、等级、状态、黑名单等。

针对用户的资料管理:

考虑到用户基础信息非常多,借鉴了经典的"分表"思想,将常用的不常更新的字段(user_info)和不常用且会变更的字段(user_extend)分两张数据表管理起来,这应该是比较常见的拆分方法,我待过的公司至少有两家是这样实现的。

用户黑名单的设计也是重要的功能:

下面再介绍下个人用户管理的一些常见功能。

修改密码(支持短信和邮件提醒):

找回密码:

用户头像:

其他如用户变更记录、登录等等基础功能,常见的人员关系场景管理后台都要有完善支持。

PCRM经常会有各种各样"我的XX"功能,"我的XX"功能和系统边界及系统解耦有直接关系。通常我们会根据需要划分出两个主要模块:用户单据和用户社交。

2、用户单据

有些用户业务数据,和用户关系看上去也比较紧密,但界面展示通常直接调用各个平台系统提供的接口就行,不需要直接在PCRM中进行数据维护。

典型的如我的订单、我的购物车、我的钱包、我的卡包、我的票券、我的财务、我的配送地址、我的开票公司信息、我的常旅客管理等功能模块可以直接划归到分解好的各个平台系统中去。

当然,有些用户电商购物单据划归到PCRM中也行得通,目前PowerDotNet实现的PCRM就将用户配送地址和开票公司信息直接划归到PCRM中管理,当然把它们分别放到配送平台和财务平台才是最合理的。

根据个人开发经验,我的XX数据经常因为划分和归属而产生各种各样的推诿扯皮,尤其是中大型的电商项目,互联互通非常复杂,公共服务性质的共用系统就经常面临模块和数据划分及归属管理的困境。

我的XX数据一个简单的经验之谈,稳定的变动极少的我的XX数据可以优先划归到PCRM中进行管理,比如我的配送地址或我的开票公司,这些就是改动较少的静态数据,放到PCRM中其实是很合适的。

当然稳定的变动极少的数据放到某系统中这个做法只是经验之谈,并不通用,具体划归到哪里,还是需要按照实际业务需要和成本控制来确定,比如我的银行卡号之类的静态数据放在支付平台管理更好。

3、用户社交

用户社交数据通常都和公司主业务强相关,我们可以将这些数据维护在PCRM中,也可以独立开发出子系统,很显然普通公司的用户社交和PCRM联系更紧密。

比如我的评论、我的随笔、我的投诉、我的相册、我的消息、我的关注、我的粉丝、我的好友、我的收藏、我的点赞、我的标签等可以直接划归到PCRM系统中。

用户单据和社交的设计和实现非常考验开发人员的水平,所谓夏虫不可语冰,井蛙不可语海,凡夫不可语道,没写过这方面代码的人很容易不知不觉中造成系统重大缺陷,这也是个人多年开发填坑经验之谈。

二、用户账户管理

根据个人开发过的虚拟货币系统,这里再提取抽象简化为个人用户账户系统,站在巨人的肩膀上,我也积累了一套完善的通用的用户账户处理单据流程。

用户账户和金钱有直接关系,这样就会产生这样那样的流水,以及对账红冲等常见功能。

基本的对账功能:

在产生流水的过程中,可以抽象出不同的流水单据进行处理。

用户账户的主要单据如下:

1、充值订单

充值订单支持对支付成功的订单记录进行财务入账和申请开票等操作。

2、充值流水

3、冻结流水

4、解冻流水

5、扣款(消费)流水

6、退款流水

每个处理单据,都有基本的对账功能。这里仅以退款示例下:

1、一次全额退款

2、多次部分退款,直至全额退款成功

3、部分退款

对于用户账户系统的提现功能,需要抽象出提现单,不过这个支付平台功能更紧密,这里不列在主要单据里。

三、账户提现

如果支付平台做得好,账户提现完全可以在支付平台里搞定,不需要在业务系统里单独开发功能。

PowerDotNet的支付平台完全可以应付常规的账户提现需求,之所以在这里还抽象出提现单进行介绍,主要就是:

在某厂我这么实现过,而且用户的反馈极其良好,不介绍有点可惜^_^

1、提现申请单

终端用户通过PCRM接口创建好提现申请单,接口支持同步和异步处理

上述两个接口基本满足了提现的常规需求。

2、提现流水

创建好的提现申请单,最终需要向支付平台发起真实退款请求才能完成用户的提现需求,提现成功后,提现申请单会对应产生相关提现流水。

3、提现补偿工具

有时候退款会发生这样或那样的问题,比如自己开发的支付系统在发布或维护,用户账户异常风控不通过,银行服务维护,支付宝、微信等账户注销或锁定,秘钥过期等等原因,这样用户提现需求就要进行补偿重试或者人工处理。

PowerDotNet结合个人开发和运营经验,开发了几个补偿小工具,能帮助开发和支持人员快速定位并解决问题。

上述补偿工具,严格来说是对支付平台接口的再次封装和调用。

再次说一遍,PowerDotNet的支付平台完全可以应付常规的账户提现需求,真没必要又包一层产生这么多单据,就是这么自信,咩哈哈。

四、财务记账

对于用户账户充值和提现,从财务角度看是一对典型的往来账,PCRM也将用户账户充值和提现设计成便于理解兼顾灵活性的入账和出账流程。

1、收款单

PCRM账户充值,用户通过支付平台完成账户充值后,支付平台回调通知PCRM扣款结果,PCRM完成充值业务逻辑后调用财务平台接口创建财务收款单,财务系统完成入账逻辑。

2、应收退款单

PCRM账户提现,用户通过支付平台完成账户提现后,支付平台回调通知PCRM退款结果,PCRM完成提现业务逻辑后调用财务平台接口创建财务应收退款单,财务系统完成出账逻辑。

注意,对于用户下订单使用账户金额消费的场景,虽然账户金额减少,但不用PCRM创建财务应收退款单,而应该由订单系统(商户)主动调用财务接口创建收款单。

3、申请开票

PCRM用户提交开票申请,调用财务平台接口创建应收发票,财务审核后完成开票。

在PCRM中进行开票申请:

在财务平台审核用户开票信息:

五、用户积分

严格来说,用户积分也是虚拟货币的一种,不过很多公司都会独立出来,毕竟积分相对虚拟账户而言,有一些特殊处理方式,比如积分有效期、积分比率、特定活动动态支付参数,单独抽象出来未尝不可。

PowerDotNet抽象出来的积分处理方式,借鉴了前厂的部分逻辑,并且去掉了具体业务有关的特殊逻辑,和用户虚拟账户已经有极大多数相同的处理单据。

在产生流水的过程中,可以抽象出不同的积分流水单据进行处理。

1、积分充值流水

2、积分冻结流水

3、积分解冻流水

4、积分扣款(消费)流水

5、积分退款流水

对账功能也基本和用户账户相同,不过因为绝大多数积分有效期的关系,有些功能,如退款和解冻的对账功能需要调整。

根据我的个人开发经验,积分有效期这个东西是个巨大的陷阱,因为很多业务逻辑和性能问题都由它引起,设计积分系统的时候,如果没有有效期,那真可以减少百分之八十的工作量。

六、混合支付

混合支付功能其实我是非常拒绝再写的,个人已对接过的混合支付特别费力不讨好,还容易造成现有系统出错,哪怕你给对方提需求并且定义了标准接口,奈何现状所限,挡不住出问题,效果聊胜于无。

1、接口抽象

混合支付在支付平台里已经简单介绍过,本文就简单梳理下混合支付常用的冻结、解冻、扣款、退款四个接口:

现实中,用户账户和用户积分是分别处理的,无法保证事务性。完善的后台管理系统,是混合支付处理的关键保障。如果在特定定时任务都无法保证混合支付正常的情况下,通过后台人工介入也是很轻松的事情。

2、流水日志

对混合支付,需要有完善的日志,便于排查问题。

交易的每一笔流水都要记的一清二楚,必要时可对日志进行数据统计,本文省略数据统计。

3、支付风控

严格来说,任何支付功能,包括虚拟货币(如账户、积分等)的充值、消费和提现都应该有严格的风控,独立的风控系统是必不可少的,但是鉴于某些弱鸡公司孱弱的研发能力,风控聊胜于无。

小结:用户账户管理、账户提现、财务记账、用户积分、混合支付等模块设计实现很容易造成各种混乱,尤其是账户系统,我已经不止一次维护过这种容易造成用户投诉的类虚拟货币系统。

没有结果的爱情往往更加动人,缺少规范的系统常常麻烦缠身,个人经验中,只要系统形象磕碜,往往公司的技术管理水平极其低下,流程规范严重缺失,哪怕是规模庞大的上市公司。

七、信息安全

PCRM中有大量的根据用户Id或者用户名查询用户信息、账户、用户单据或用户社交数据的接口,这些涉及到用户数据的接口,一定要注意信息安全管理。

1、越权漏洞

越权访问(Broken Access Control,简称BAC)是应用程序中一种常见的漏洞,越权,顾名思义,就是获得了本不应该有的权限。

越权漏洞是指应用在检查授权时存在纰漏,使得攻击者在获得低权限用户账户后,利用一些方式绕过权限检查,访问或者操作其他用户或者更高权限。

越权漏洞往往是基于业务逻辑的漏洞,常见的比如在PCRM系统中有大量的CRUD接口,由于过分相信接口消费者而遗漏了权限的判定,这样的漏洞很难被WAF保护。

在实际的代码审计中,这种越权漏洞往往很难通过工具进行自动化监测,开发(甚至测试)理所应当的认为接口调用方便是第一位的,只要功能正常,授权管理是调用者的事情,这种想法危害极大。

根据越权漏洞经验统计,目前存在着两种显而易见的越权操作类型,即横向越权操作(水平越权)和纵向越权操作(垂直越权)。

(1)、水平越权

水平越权指相同权限下不同的用户可以互相访问。典型示例:用户A登录后,用户A操作却影响到B用户。

水平越权是相当常见的漏洞,多年前在某电商工作的时候,碰到过一个订单页面get方式传入UserId查看用户订单的漏洞,正常情况是登录用户只能查看当前自己的订单,但后端页面业务逻辑缺少这个校验。

(2)、垂直越权

垂直越权指使用权限低的用户可以访问到权限较高的用户。典型示例:低权限用户使用高权限用户的功能,比如普通用户可以使用管理员的功能。

2、越权防范

避免越权漏洞,尤其是水平越权,需要遵循一个简单的原则:用户只能看到属于自己的内容。

个人总结的常见防范越权漏洞措施包括:

(1)、服务治理平台做好接口认证、鉴权和授权,比如验签和(或)校验token

(2)、严格的功能权限控制,权限到页面或者按钮,调用功能前验证用户是否有权限

(3)、数据权限控制,执行关键操作前必须验证用户身份,验证用户是否具备操作数据的权限

(4)、双重验证,前后端同时对用户输入信息进行校验,永远不要相信来自用户的输入,对于可控参数进行严格的检查与过滤

(5)、对于系统中各种有规律的流水号,比如自增,设计时取舍务必小心,防止穷举漏洞攻击,对流水型资源ID加密,防止攻击者穷举ID

(6)、敏感信息脱敏,在配置中心做好敏感信息脱敏开关,防止意外发生时可及时兜底处理

八、水平分表

个人碰到过的PCRM的最大挑战在于用户数据量过大,一些数据量巨大的表需要分库分表处理。

常见的分库分表类型包括垂直分库垂直分表水平分库水平分表 。具体到PCRM分库分表,通常可使用水平分表策略。

水平分表是指将单张表的数据分片拆分到多台服务器节点上,每个节点具有相同的库与表,表结构都一样,每个表的数据不一样,没有交集,所有表的并集是全量数据。

比如用户信息user_info和用户扩展user_extend两张表,单库不足以支撑的时候必须要水平分表(常见算法如用户id取模、用户id范围分区等),几乎均匀的分片保存到不同服务器PCRM数据库中。

水平分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、CPU、内存、连接数、其他硬件资源等的限制,但是也带来了业务逻辑上的很多挑战,比如:连接查询、事务、跨节点分页、排序等。

举例来说,在PCRM中,水平分表后,以前单库单表相对简单的登录功能就会变得很复杂。

常见的PCRM支持用户名、手机号、邮箱、证件号码、OpenId等进行登录,没有水平分表前,很可能一个SQL连接查询就能搞定,分库分表后就需要考虑分区、跨库连接甚至引入其他中间件的查询问题。

正如我们所知道那样,软件的很多问题都可以通过加一层来解决,如果加一层还不能解决,那就再加一层^_^。

解决水平分表造成的查询问题的常见方法是添加冗余关系索引表,通常分为两种:

1、冗余全量数据

PCRM支持手机号登录,那么我们就把用户信息user_info和用户扩展user_extend两张表根据手机号分库分表。

优点:只需要查询一次就能定位数据。

缺点:占用空间大,每增加一个维度查询就得增加一倍的空间, 数据存储与变更时需要维护多张表。

2、冗余关系索引

PCRM支持手机号登录,那么我们就维护一个手机号和用户id的关系映射表。

优点:占用空间比冗余全量数据小很多。

缺点:必须查询两次, 先从映射表中查到用户id, 再根据用户id定位数据,性能略有下降。

冗余关系索引可以存储在RDBMS中,也可以存储在Redis、MongoDB、Cassandra、ElasticSearch等NewSQL中,得益于NewSQL集群海量数据存储和快速查询能力,功能性能都能满足业务需要。

3、PowerDotNet分表

PowerDotNet自研的ORM工具集成了常用的分库分表功能,配合代码生成器和DBKey服务,很容易实现分表。我们以PCRM数据库为例,ORM实现的分库分表常见三种情形:

同库不同表:PCrmDB下UserInfo0、UserInfo1...UserInfoN表

不同库相同表:PCrmDB1下UserInfo0、UserInfo1...表, PCrmDB2下UserInfo0、UserInfo1...表

不同库不同表:PCrmDB1下UserInfo0、UserInfo1...表, PCrmDB2下UserInfo2、UserInfo3...表

目前PCRM的用户和用户扩展信息就是根据用户Id进行哈希取模实现不同库相同表的分表形式,对分片的用户和用户扩展等表,不建议复杂连接查询,单表查询也能满足业务同时也保证线上运行稳定可靠。

PowerDotNet自研的ORM虽然看上去远没有TDDL、ShardingSphere、MyCat等强大,却也基本满足中小公司的分库分表需求,配合DBKey服务和代码生成器走出了自己的特色,等我有空再详细介绍。

九、系统交互

前面在用户管理一节已经提过各种"我的XX"管理,PCRM也和很多内部业务系统保持互通关系,尤其是ToC业务离开PCRM基本活不下去,整理下个人开发和对接过的几种常见互联系统。

1、订单系统

经典的"我的订单"模块。直接需求就是在界面查询我的订单而不是别人的订单,记得业务逻辑不严谨导致不同用户查询串号这种经典漏洞也不是没有发生过,咩哈哈。

2、购物车系统

"我的购物车"和"我的订单"类似,查询不能串号,同时还需要维护一个不同终端如PC、APP等未登录用户购物车和实际登录用户购物车的关系,这种也非常考验开发者的设计和实现水平。

3、支付系统

支付调用用户的地方还是比较频繁的,如果用户账户和积分也设计集成在PCRM中,那么混合支付的地方就必然和PCRM交互,还有比如查询用户基本信息如手机号、邮件等发送消息,这种也很常见。

4、风控系统

风控到人,懂的都懂。

5、财务系统

"我的财务",很常见,基本需求比如查询统计下个人某个时间段内收入支出消费趋势曲线啥的。

6、票券系统

"我的票券",各种电商活动送出的优惠券、购物券等等,设计实现不同,和个人用户的关系也不同,有些票券系统设计基本很少直接调用PCRM接口,因为可能不需要直接绑定到个人用户。

7、消息系统

发送验证码,这种需求随处可见,比如改密、找回密码等。

8、其他系统

其他系统,诸如开放平台、活动等,和PCRM没有那么紧密,但是又需要直接或间接调用PCRM接口。

十、其他

写到这里才发现前面画风有点偏账户、积分和混合支付了,PCRM真正的核心功能还没有介绍完全,限于篇幅,本文截图没有介绍的PowerDotNet的PCRM已经实现的公共服务基本功能,包括:

1、验证码管理

包括常见的字符验证码、运算验证码和滑动验证码等。

2、登录注册管理

支持传统的用户名(手机、邮箱)密码登录,扫码登录,以及OAuth登录,还包括单点登录(Single Sign On,简称SSO)功能,LDAP(轻量级目录访问协议)也有简易实现。

登录注册在CRM中算是非常普通常见的功能,但是千万别小瞧登录注册,至少个人接触过的PCRM系统,绝大多数登录注册都只是充斥HelloWorld级代码的CRUD玩具型Demo而已。

虽然有很多可以借鉴的经验和实践,但架不住乱来的需求和变更,看上去简单的登录注册,改来改去也容易事件多发。正如黑格尔所说,人类从历史中得到的唯一教训就是人类无法从历史中学到任何教训。

3、基于token+redis的token管理

万能token管理,主要实现基于加密token+redis,早期实现的jwt token可按照配置启用,绝大多数场景下不需要再启用jwt。

4、第三方关联用户管理

第三方关联用户业务场景也比较常见,通常涉及到不同公司的用户或会员的数据打通,设计实现考虑不周到,容易有安全风险。

==========分隔线==========

验证码管理、登录注册管理、基于token+redis的token管理在HCRM中已经被广泛使用,可复用的公共模块没有再次说明的必要,没错,我就是觉得简单懒得介绍了^_^。

个人用户管理是和具体公司具体业务紧密关联的公共服务业务系统,比如综合性电商、垂直类电商、酒店、机票、银行、保险、社交网站等等,它们对应的个人用户管理侧重点就不一样,所以有很多相关业务功能都需要按照业务要求去开发。

根据我的个人开发经验,个人用户或会员管理在业务领域内能衍生出很多业务功能子系统,比如账户、积分、票券、活动、投诉、论坛、广告营销、信息安全、开放平台、推荐算法等都和PCRM有千世万缕的联系,想写好也要面对很多的挑战,对于一线开发人员,学业务做设计堆代码,一个都不能少,是非常需要花时间积累和投入的公共服务。

基础设施和公共服务建设对系统稳定至关重要,我们欣赏并尊重少说多做严谨务实工作和实践的人,最讨厌不切实际夸夸其谈自以为比别人高明同时还不虚心学习也没什么逻辑和执行能力的人。

有了支付、财务和CRM的系统开发经验,在各种形态的公司或组织里开发管钱和管人的系统基本上都不会有太大问题,这就是PowerDotNet和PowerDotNetCore专注开发通用型公共服务的价值所在。