在开发中经常会遇到开发分销系统的需求,如何设计用户表,如何存储上下级关系,如何检索指定用户推广的下级,这些都是分销系统很核心的功能,今天就分享一种比较简单的用户表设计思路,也是我经常使用的方案。
1.用户表主要字段
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
`code` char(6) NOT NULL COMMENT '推广码',
`name` varchar(20) NOT NULL COMMENT '用户名',
`parent_id` int(10) unsigned DEFAULT NULL COMMENT '父级ID',
`parent_ids` varchar(255) DEFAULT NULL COMMENT '所有父级',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `user` VALUES (1, 'KJCKWX', '1001', 0, '0');
INSERT INTO `user` VALUES (2, 'PO21XD', '1002', 0, '0');
INSERT INTO `user` VALUES (3, 'QNJO6U', '1003', 0, '0');
INSERT INTO `user` VALUES (4, 'BM1YPD', '1004', 1, '1,0');
INSERT INTO `user` VALUES (5, 'BQSCMU', '1005', 1, '1,0');
INSERT INTO `user` VALUES (6, 'GWYKLC', '1006', 4, '4,1,0');
INSERT INTO `user` VALUES (7, 'CHVZXU', '1007', 6, '6,4,1,0');
INSERT INTO `user` VALUES (8, 'LULW0T', '1008', 7, '7,6,4,1,0');
INSERT INTO `user` VALUES (9, 'WEDCGK', '1009', 4, '4,1,0');
INSERT INTO `user` VALUES (10, 'HIFUCY', '1010', 6, '6,4,1,0');
INSERT INTO `user` VALUES (11, 'I2VKEJ', '1011', 6, '6,4,1,0');
INSERT INTO `user` VALUES (12, 'AVCVUE', '1012', 7, '7,6,4,1,0');
INSERT INTO `user` VALUES (13, '4GOU6S', '1013', 7, '7,6,4,1,0');
INSERT INTO `user` VALUES (14, 'R7QLWY', '1014', 8, '8,7,6,4,1,0');
INSERT INTO `user` VALUES (15, 'HAT2U5', '1015', 14, '14,8,7,6,4,1,0');
INSERT INTO `user` VALUES (16, 'WPGDZH', '1016', 14, '14,8,7,6,4,1,0');
INSERT INTO `user` VALUES (17, 'KIZESN', '1017', 12, '12,7,6,4,1,0');
INSERT INTO `user` VALUES (18, 'LH1CKJ', '1018', 13, '13,7,6,4,1,0');
INSERT INTO `user` VALUES (19, '1PPWTZ', '1019', 18, '18,13,7,6,4,1,0');
INSERT INTO `user` VALUES (20, 'LECH08', '1020', 5, '5,1,0');
根据 parent_id 可简单画出以下的用户关系链
2.推广码字段code
code 为用户推广码,每个用户唯一,系统正是根据 code 为每个用户生成推广链接或推广二维码,分享到社交媒体后,新用户注册时会带上code,在注册逻辑中即可锁定用户的上级。
推广连接格式一般如下:
http://xxx/register?code=xxx
3.用parent_id、parent_ids锁定父级
parent_id 为直接父级ID, 0表示无父级;
parent_ids 为所有父级ID用逗号分隔符连接, '0'表示无父级, 在后端代码中可以按照以下逻辑赋值:
parent_ids = parent_id + 逗号 + 直接父级的parent_ids
这样设计的好处是可以检索所有的父级和推广者(子孙用户), 不限于3级。
为什么用逗号分割,是根据 MySQL 函数 find_in_set( str, strlist ) 的参数 strlist 也是以逗号分割。
计算佣金时需要检索父级用户
此时根据 parent_id 可以查询直接父级,根据 parent_ids 可以查询所有父级。
下面的SQL语句为了方便调试使用了子查询,具体的后端代码肯定要进行要优化处理。
-- 查询1007的直接父级, 用户1007的ID为7
select * from user where id = (select parent_id from user where id=7);
-- 查询1007的所有父级
select * from user where find_in_set(id, (select parent_ids from user where id=7))
用户查看自己的推广成果
考虑到平台的公平,用户肯定需要查看自己的推广用户,而且根据分销机制的不同,用户也需要查看到不同级别的下级用户,具体的SQL语句如下:
-- 1007查看自己的一级推广用户(1)
select * from user where parent_id=7;
-- 1007查看自己的一级推广用户(2)
select * from user where find_in_set(7, parent_ids)=1;
-- 1007查看自己的二级推广用户
select * from user where find_in_set(7, parent_ids)=2;
-- 1007查看自己的三级推广用户
select * from user where find_in_set(7, parent_ids)=3;
-- 1007查看自己的四级推广用户
select * from user where find_in_set(7, parent_ids)=4;
4.ThinkPHP5 部分接口代码演示
//用户注册接口
public function register($name, $code){
//查询父级
$parent = db('user')->where('code', $code)->find();
//生成自己的推广码
$code = Random::alnum(6);
$code = strtoupper($code);
//注册用户
$user = [
'name' => $name,
'code' => $code,
'parent_id' => $parent['id'],
'parent_ids' => $parent['id'].','.$parent['parent_ids'],
];
db('user')->insert($user);
$this->success('注册成功');
}
//查询所有父级
public function get_parents($user_id){
$user = db('user')->where('id', $user_id)->find();
$user_list = db('user')->where('id', 'in', $user['parent_ids'])->select();
$this->success('success', ['list' => $user_list]);
}
//查询指定等级的推广者
public function get_subs($user_id, $level){
$where = sprintf('find_in_set(%s, parent_ids)=%s', $user_id, $level);
$user_list = db('user')->where($where)->select();
$this->success('success', ['list' => $user_list]);
}