一直从事互联网电商开发三年多的时间了,回头想一想却对整个业务流程不是很了解,说出去非常惭愧。可是身处互联网电商的环境中,或多或少接触了其中的各个业务,其次周边还有不少从事电商的同事和朋友,这都是资源。因而,我决定和个人同事、盆友们、甚至还有大家去梳理整个流程并分享出来,谈不上结果要作的多么好,至少在每个咱们有能力去作好的地方,必定会细致入微。php
除此以外,同时为了知足咱们自身在工做中可能得不到的技术知足感,咱们在作整个系统设计的过程当中,会去使用咱们最想用的技术栈。技术栈这一点咱们借助docker去实现,因此最终的结果:一方面咱们掌握了业务的东西,另外一方面又获得了技术上的知足感,两者兼得。git
最后,出于时间的考虑,咱们提出了一个想法Do design No code。**【只设计不码码】**这句话的意思:最终咱们设计出来整个系统的数据模型,接口文档,甚至交互过程,以及环境部署等,可是最后咱们却不写代码。是吧?若是这样了写代码还有什么意义。固然,也不全是这样,出于时间的考虑固然也会用代码实现出来的,说不定最后正是对面的你去实现的。github
其次,这些内容确定有考虑不全面或者在上规模的业务中存在更复杂的地方,欢迎指出,咱们也但愿学习和分享您的经验。sql
今天,咱们开始第一部分用户体系的设计。本文分为以下四大模块:docker
当你第一次接触和用户相关的互联网产品时,或者曾今在我眼里。用户体系无非就是“登陆”和“注册”,“修改用户信息”这些,等。简单来作的话,无非咱们须要一张表去记录用户的身份信息:注册时(insert操做),往表里插入一个数据;登陆时(select&update操做),经过用户标识(手机号、邮箱等)判断用户的密码是否正确;修改用户信息(select&update操做),就是直接update这个uid的用户信息(头像、昵称等)。json
这样设计的确没什么问题,很简单不是么。可是随着业务的发展,一方面咱们须要提供统一的用户管理(高内聚),又要提升系统的可扩展性,因此我想呈现出来的是我理解的一个基本用户体系应该有的东西。后端
首先咱们对原有的用户表进行再一次的抽象(抽离用户注册、登陆依赖的字段、第三方登录) -> 帐户表,为何这么作?随着业务的发展,之前只维护一个产品,也许某一天又开发新的产品,这样咱们就能够统一的维护咱们公司全部产品的注册登陆逻辑,不一样的产品只维护该产品和用户相关的信息便可(具体依赖产品形态)。以下图所示:bash
上图中,还提到了第三方登录/员工表/后台权限管理,这些都是一些用户体系基本必备的结构。架构
第三方登陆:第三方也是登录方式的一种,咱们也把它抽象到帐户的一部分,如上图所示。其次,关于第三方登陆这里存在一个交互方式设计存在的问题,后面交互设计时会提到。框架
员工:由于上面咱们抽离了帐户表,因此内部的管理系统后台也能够统一的使用帐户表的登陆逻辑,这样全公司在帐号这个事情上达到了真正的高内聚。
提到了员工,咱们的内部各类系统后台确定涉及各类的权限管理,因此这里提到了简单的RBAC(基于角色的权限控制),具体的逻辑数据模型设计会提到。
随着业务产品形态的愈来愈复杂,在设计架构的时候,咱们须要分析其中的变与不变:
最终的结果,咱们把原有的用户拆成了帐户和用户,同时咱们也要在这里明确这两个概念的区别:
最终的架构图以下:
对应上面的架构,咱们很容易设计出咱们的数据模型(这里假设咱们目前只有一个对C端的应用):
帐户 -> 1.帐户表
用户 -> 2.用户表
员工 -> 3.员工表
复制代码
除了上面三张表外,还须要咱们的R(role)B(base)A(access)C(control)权限管理,RBAC基于角色的权限管理你们应该很熟悉,这里我就不详细说了,简单的RBAC首先须要:
4.系统菜单表(菜单即权限),系统的uri路径
5.权限表(菜单即权限),具体的权限就是访问系统的菜单
6.角色表,一个角色具备哪些权限
7.员工和角色的关联表,一个员工属于哪一个角色
复制代码
好了一个简单的RBAC涉及的表基本罗列出来了,可是在个人工做经历中你们实现的权限管理每每只针对某个系统,这样对于众多的系统后台来讲就是乱、重复造轮子、权限管理效率低。因此我在上面的架构设计中把权限做为了一个服务为全系统提供基础服务能力。而达到这个目的的结果我只须要再增长一张表:
8.后台管理系统表, 登记全部的后台管理系统(这样经过系统id和系统资源uri的id就能够全局构成惟一性,单纯的uri存在重复的可能性,用uri不用url的缘由是域名存在变更的可能性)
复制代码
最后咱们的用户体系应该基本就上面8张表。咦,貌似漏掉了第三方登录,咱们加上吧,很简单以下:
9. 第三方用户登录表,记录不一样第三方的用户标示
复制代码
最最后就是上面的9张表了,具体的表结构和sql以下:
帐户模型
-- 帐户模型
CREATE TABLE `account_user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '帐号id',
`email` varchar(30) NOT NULL DEFAULT '' COMMENT '邮箱',
`phone` varchar(15) NOT NULL DEFAULT '' COMMENT '手机号',
`username` varchar(30) NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(32) NOT NULL DEFAULT '' COMMENT '密码',
`create_at` int(11) NOT NULL DEFAULT '0' COMMENT '建立时间',
`create_ip_at` varchar(12) NOT NULL DEFAULT '' COMMENT '建立ip',
`last_login_at` int(11) NOT NULL DEFAULT '0' COMMENT '最后一次登录时间',
`last_login_ip_at` varchar(12) NOT NULL DEFAULT '' COMMENT '最后一次登录ip',
`login_times` int(11) NOT NULL DEFAULT '0' COMMENT '登陆次数',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted',
PRIMARY KEY (`id`),
KEY `idx_email` (`email`),
KEY `idx_phone` (`phone`),
KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='帐户';
复制代码
-- 第三方帐户
CREATE TABLE `account_platform` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
`uid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '帐号id',
`platform_id` varchar(60) NOT NULL DEFAULT '' COMMENT '平台id',
`platform_token` varchar(60) NOT NULL DEFAULT '' COMMENT '平台access_token',
`type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '平台类型 0:未知,1:facebook,2:google,3:wechat,4:qq,5:weibo,6:twitter',
`nickname` varchar(60) NOT NULL DEFAULT '' COMMENT '昵称',
`avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '头像',
`create_at` int(11) NOT NULL DEFAULT '0' COMMENT '建立时间',
`update_at` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_uid` (`uid`),
KEY `idx_platform_id` (`platform_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方用户信息';
复制代码
用户模型
-- 用户模型
CREATE TABLE `skr_member` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户id',
`uid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '帐号id',
`nickname` varchar(30) NOT NULL DEFAULT '' COMMENT '昵称',
`avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '头像(相对路径)',
`gender` enum('male','female','unknow') NOT NULL DEFAULT 'unknow' COMMENT '性别',
`role` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '角色 0:普通用户 1:vip',
`create_at` int(11) NOT NULL DEFAULT '0' COMMENT '建立时间',
`update_at` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='帐户信息';
复制代码
员工模型
-- 员工表
CREATE TABLE `staff_info` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '员工id',
`uid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '帐号id',
`email` varchar(30) NOT NULL DEFAULT '' COMMENT '员工邮箱',
`phone` varchar(15) NOT NULL DEFAULT '' COMMENT '员工手机号',
`name` varchar(30) NOT NULL DEFAULT '' COMMENT '员工姓名',
`nickname` varchar(30) NOT NULL DEFAULT '' COMMENT '员工昵称',
`avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '员工头像(相对路径)',
`gender` enum('male','female','unknow') NOT NULL DEFAULT 'unknow' COMMENT '员工性别',
`create_at` int(11) NOT NULL DEFAULT '0' COMMENT '建立时间',
`update_at` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_uid` (`uid`),
KEY `idx_email` (`email`),
KEY `idx_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工信息(这里列了大概的信息,多的能够垂直拆表)';
复制代码
系统权限管理模型
-- 权限管理: 系统map
CREATE TABLE `auth_ms` (
`id` smallint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
`ms_name` varchar(255) NOT NULL DEFAULT '0' COMMENT '系统名称',
`ms_desc` varchar(255) NOT NULL DEFAULT '0' COMMENT '系统描述',
`ms_domain` varchar(255) NOT NULL DEFAULT '0' COMMENT '系统域名',
`create_at` int(11) NOT NULL DEFAULT '0' COMMENT '建立时间',
`create_by` int(11) NOT NULL DEFAULT '0' COMMENT '建立人staff_id',
`update_at` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
`update_by` int(11) NOT NULL DEFAULT '0' COMMENT '修改人staff_id',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted',
PRIMARY KEY (`id`),
KEY `idx_domain` (`domain`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统map(登记目前存在的后台系统信息)';
-- 权限管理: 系统menu
CREATE TABLE `auth_ms_menu` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
`ms_id` smallint(11) unsigned NOT NULL DEFAULT '0' COMMENT '系统id',
`parent_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '父菜单id',
`menu_name` varchar(255) NOT NULL DEFAULT '0' COMMENT '菜单名称',
`menu_desc` varchar(255) NOT NULL DEFAULT '0' COMMENT '菜单描述',
`menu_uri` varchar(255) NOT NULL DEFAULT '0' COMMENT '菜单uri',
`create_at` int(11) NOT NULL DEFAULT '0' COMMENT '建立时间',
`is_show` enum('yes','no') NOT NULL DEFAULT 'no' COMMENT '是否展现菜单',
`create_by` int(11) NOT NULL DEFAULT '0' COMMENT '建立人staff_id',
`update_at` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
`update_by` int(11) NOT NULL DEFAULT '0' COMMENT '修改人staff_id',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted',
PRIMARY KEY (`id`),
KEY `idx_ms_id` (`ms_id`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统menu';
-- 权限管理: 系统权限
CREATE TABLE `auth_item` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
`ms_id` tinyint(11) unsigned NOT NULL DEFAULT '0' COMMENT '系统id',
`menu_id` varchar(255) NOT NULL DEFAULT '0' COMMENT '页面/接口uri',
`create_at` int(11) NOT NULL DEFAULT '0' COMMENT '建立时间',
`create_by` int(11) NOT NULL DEFAULT '0' COMMENT '建立人staff_id',
`update_at` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
`update_by` int(11) NOT NULL DEFAULT '0' COMMENT '修改人staff_id',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted',
PRIMARY KEY (`id`),
KEY `idx_ms_menu` (`ms_id`, `menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统权限';
-- 权限管理: 系统权限(权限的各个集合)
CREATE TABLE `auth_role` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
`name` varchar(255) NOT NULL DEFAULT '0' COMMENT '角色名称',
`desc` varchar(255) NOT NULL DEFAULT '0' COMMENT '角色描述',
`auth_item_set` text COMMENT '权限集合 多个值,号隔开',
`create_at` int(11) NOT NULL DEFAULT '0' COMMENT '建立时间',
`create_by` int(11) NOT NULL DEFAULT '0' COMMENT '建立人staff_id',
`update_at` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
`update_by` int(11) NOT NULL DEFAULT '0' COMMENT '修改人staff_id',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工角色';
-- 权限管理: 角色与员工关系
CREATE TABLE `auth_role_staff` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
`staff_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '员工id',
`role_set` text COMMENT '角色集合 多个值,号隔开',
`create_at` int(11) NOT NULL DEFAULT '0' COMMENT '建立时间',
`create_by` int(11) NOT NULL DEFAULT '0' COMMENT '建立人staff_id',
`update_at` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
`update_by` int(11) NOT NULL DEFAULT '0' COMMENT '修改人staff_id',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted',
PRIMARY KEY (`id`),
KEY `idx_staff_id` (`staff_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限角色与员工关系';
复制代码
友情提示:一大波图片即将到来,此处图片较多,不清楚的可点击大图查看
注册成功以后存在至少两种交互方式:
具体交互流程以下:
快捷登陆的流程基本和上面一致只是验证密码换成了验证验证码。
第三方登陆的交互其实存在这样的问题:
由于我发现有些PM为了提升用户使用的简单快捷性,每每第三方登录成功后会直接产生uid,而不进行帐号的绑定。这样以后在再进行帐号绑定就涉及帐号合并的问题,很麻烦(若是有钱包等)。若是咱们一开始就进行绑定操做,这样将来帐号的关系就清晰明了便于维护,第三方登录其实就至关于普通帐号的别名。 最后这个事情作不作的结果就是,帐户表account_user和第三方用户信息表account_platform是的一对多仍是一对一的关系。
这个还好说,通常来讲绑定的选择基本是正确的。最后具体的流程图以下:
交互界面图以下:
首先,咱们的后台管理系统须要个响亮的称号,想了一会之前公司用过apollo,因而我准备用mars但忽然冒出来个earth,地球万物之根,恰好咱们这又是个全业务的基础服务管理系统,哈哈就这样吧~ Earth System
Earth System的权限管理功能主要分为如下四部分:
具体交互以下:
1.注册接口
请求参数:
字段 | 类型 | 是否必传 | 描述 |
---|---|---|---|
username | string | 非必传 | 用户帐号 |
string | email/phone二者择一 | 用户邮箱 | |
phone | string | email/phone二者择一 | 用户手机号 |
code | int | 必传 | 验证码 |
交互方式一(跳转到登录页面)响应内容:
{
"code": "200",
"msg": "OK",
"result": []
}
复制代码
交互方式二(跳转到首页页面)响应内容:
{
"code": "200",
"msg": "OK",
"result": {
"s_token": "string, 用户会话标示",
"s_token_expire": "string, 用户会话标示过时时间,0不过时",
"username": "string, 用户名",
"nickname": "string, 用户昵称",
"avatar": "string, 用户头像",
"gender": "string, 用户性别,male:男,female:女,other:未知",
}
}
复制代码
2.登陆接口
请求参数:
字段 | 类型 | 是否必传 | 描述 |
---|---|---|---|
username | string | username/email/phone三者择一 | 用户帐号 |
string | username/email/phone三者择一 | 用户邮箱 | |
phone | string | username/email/phone三者择一 | 用户手机号 |
password | string | 必传 | 密码 |
响应内容:
{
"code": "200",
"result": {
"s_token": "string, 用户会话标示",
"s_token_expire": "string, 用户会话标示过时时间,0不过时",
"nickname": "string, 用户昵称",
"username": "string, 用户名",
"avatar": "string, 用户头像",
"gender": "string, 用户性别,male:男,female:女,other:未知",
}
}
复制代码
3.快捷登陆接口
请求参数:
字段 | 类型 | 是否必传 | 描述 |
---|---|---|---|
string | email/phone二者择一 | 用户邮箱 | |
phone | string | email/phone二者择一 | 用户手机号 |
code | int | 必传 | 验证码 |
响应内容:
{
"code": "200",
"result": {
"s_token": "string, 用户会话标示",
"s_token_expire": "string, 用户会话标示过时时间,0不过时",
"nickname": "string, 用户昵称",
"username": "string, 用户名",
"avatar": "string, 用户头像",
"gender": "string, 用户性别,male:男,female:女,other:未知",
}
}
复制代码
4.第三方登陆接口
请求参数:
字段 | 类型 | 是否必传 | 描述 |
---|---|---|---|
type | string | 必传 | 平台类型 1:facebook,2:google,3:wechat,4:qq,5:weibo,6:twitter |
platform_id | string | 必传 | 第三方平台用户ID |
platform_token | string | 必传 | 第三方平台令牌 |
响应内容:
{
"code": "200",
"result": {
"s_token": "string, 用户会话标示",
"s_token_expire": "string, 用户会话标示过时时间,0不过时",
"username": "string, 用户名",
"nickname": "string, 用户昵称",
"avatar": "string, 用户头像",
"gender": "string, 用户性别,male:男,female:女,other:未知",
}
}
复制代码
5.用户信息修改接口
请求参数:
字段 | 类型 | 是否必传 | 描述 |
---|---|---|---|
username | string | 非必传 | 用户帐号 |
nickname | string | 非必传 | 昵称 |
avatar | string | 非必传 | 头像url |
gender | string | 非必传 | 用户性别,male:男,female:女,other:未知 |
响应内容:
{
"code": "200",
"result": {
"username": "string, 用户名",
"nickname": "string, 用户昵称",
"avatar": "string, 用户头像",
"gender": "string, 用户性别,male:男,female:女,other:未知",
}
}
复制代码
6.用户登陆状态校验
请求参数:
字段 | 类型 | 是否必传 | 描述 |
---|---|---|---|
s_token | string | 必传 | 用户会话标示 |
响应内容:
{
"code": "200",
"result": {
"s_token_expire": "string, 用户会话标示过时时间,0不过时, -1登录失效",
}
}
复制代码
帐户服务:
请求参数:
字段 | 类型 | 是否必传 | 描述 |
---|---|---|---|
username | string | 非必传 | 用户帐号 |
string | email/phone二者择一 | 用户邮箱 | |
phone | string | email/phone二者择一 | 用户手机号 |
交互方式一(跳转到登录页面)响应内容:
{
"code": "200",
"msg": "OK",
"result": {
"uid": "string, 帐户ID"
}
}
复制代码
请求参数:
字段 | 类型 | 是否必传 | 描述 |
---|---|---|---|
username | string | 非必传 | 用户帐号 |
string | email/phone二者择一 | 用户邮箱 | |
phone | string | email/phone二者择一 | 用户手机号 |
password | string | 必传 | 密码 |
响应内容:
{
"code": "200",
"msg": "OK",
"result": {
"uid": "string, 帐户ID"
}
}
复制代码
请求参数:
字段 | 类型 | 是否必传 | 描述 |
---|---|---|---|
type | string | 必传 | 平台类型 1:facebook,2:google,3:wechat,4:qq,5:weibo,6:twitter |
platform_id | string | 必传 | 第三方平台用户ID |
platform_token | string | 必传 | 第三方平台令牌 |
响应内容:
{
"code": "200",
"result": {
"uid": "string, 帐户ID",
"nickname": "string, 用户昵称",
"avatar": "string, 用户头像",
}
}
复制代码
权限服务
请求参数:
字段 | 类型 | 是否必传 | 描述 |
---|---|---|---|
ms_id | string | 必传 | 系统ID |
响应内容:
{
"code": "200",
"msg": "OK",
"result": {
"ms_name": "string, 系统名称",
"ms_desc": "string, 系统描述",
"ms_domain": "string, 系统域名",
"list": [
{
"parent_id": "string, 父菜单ID",
"menu_id": "string, 菜单ID",
"menu_name": "string, 菜单ID",
"menu_desc": "string, 菜单描述",
"menu_uri": "string, 菜单uri",
"child" : [
{
"parent_id": "string, 父菜单ID",
"menu_id": "string, 菜单ID",
"menu_name": "string, 菜单ID",
"menu_desc": "string, 菜单描述",
"menu_uri": "string, 菜单uri",
"child" : []
}
]
}
]
}
}
复制代码
请求参数:
字段 | 类型 | 是否必传 | 描述 |
---|---|---|---|
menu_id | string | 必传 | 菜单ID |
响应内容:
{
"code": "200",
"msg": "OK",
"result": []
}
复制代码
若是有写的不对或者不完善的地方,但愿你们多多评论,互相学习互相进步~
项目地址: github.com/skr-shop/ma…
排名不分前后,字典序
昵称 | 简介 | 我的博客 |
---|---|---|
AStraw | 研究生创业者, 现于小米科技海外商城组从事商城后端研发工做 | -------- |
大愚Dayu | 国内大多人使用的PHP第三方支付聚合项目Payment做者,创过业,现于小米科技海外商城组从事商城后端研发工做 | 大愚Talk |
lwhcv | 曾就任于百度/融360, 现于小米科技海外商城组从事商城后端研发工做 | -------- |
TIGERB | PHP框架EasyPHP做者,拥有A/B/C轮电商创业公司工做经验,现于小米科技海外商城组从事商城后端研发工做 | TIGERB.cn |