嵌套集合插入
// 注册
public function register(Request $request)
{
try{
$post = $request->param();
$this->validate($post,Register::class);
//验证短信验证码
if (!checkSms($post['phone'], 'register', $post['code'])) {
return resjson(400, '短信验证码输入错误');
}
}catch (ValidateException $e){
return resjson(400, $e->getError());
}
$servers = [
['127.0.0.1', 6379, 0.01],
];
$redLock = new \RedLock($servers);
//加锁
$lock = $redLock->lock('set_user_lft_and_rgt', 2000);
Db::startTrans();
try {
$userModel = new \app\model\User();
$userModel->salt = rand(1000, 9999);
$userModel->password = system_encrypt($post['password'], $userModel->salt);
$userModel->username = $post['phone'];
$userModel->phone = $post['phone'];
$userModel->nickname = '用户' . substr($post['phone'], 7);
$userModel->account = getUniqueNum(new \app\model\User(), 'account', 8);
$userModel->source = $request->header('apptype') || '';
//默认服务商节点ID为1
$userModel->store_id = 1;
//默认邀请人
//$userModel->pid = Store::where('id',$userModel->store_id)->value('user_id') ?? 0;
$userModel->pid = config('base.default_pid') ?? 0;
$userModel->depth = 3;
//获取推荐人
//$super = new \app\model\User();
if (!empty($post['account'])) {
$super = \app\model\User::where('account',$post['account'])->findOrEmpty();
if(!$super->isEmpty()){
$userModel->pid = $super->id;
$super->inviter_num = Db::raw('inviter_num+1');
//继承上级节点
$userModel->store_id = $super->store_id;
$userModel->depth = $super->depth + 1;
//判断是否是叶子节点
if($super['rgt'] == $super['lft'] + 1){
$rgt = $super['lft'];
}else{
//不是叶子节点,获取位置
$rgt = Db::name('user')->where('pid','=',$super->id)->order('rgt','desc')->value('rgt');
}
}/*else{
$rgt = Db::name('user')->where('pid','=',$userModel->pid)->order('rgt','desc')->lock(true)->value('rgt');
}*/
}/*else{
$rgt = Db::name('user')->where('pid','=',$userModel->pid)->order('rgt','desc')->lock(true)->value('rgt');
}*/
if(!isset($super) || $super->isEmpty()){
$super = \app\model\User::where('id',$userModel->pid)->findOrEmpty();
//判断是否是叶子节点
if($super['rgt'] == $super['lft'] + 1){
$rgt = $super['lft'];
}else{
//不是叶子节点,获取位置
$rgt = Db::name('user')->where('pid','=',$super->id)->order('rgt','desc')->value('rgt');
}
}
Db::name('user')->where('rgt','>',$rgt)->order('rgt','desc')->inc('rgt',2)->update();
Db::name('user')->where('lft','>',$rgt)->order('lft','desc')->inc('lft',2)->update();
$userModel->lft = $rgt + 1;
$userModel->rgt = $rgt + 2;
$userModel->origin_pid = $userModel->pid;
$userModel->store_id = $super->store_id;
$userModel->depth = $super['depth'] + 1;
if(!$userModel->save()){
throw new \Exception('注册失败',400);
}
// 触发UserRegister事件
$bonus = new Bonus();
$bonus->type = Bonus::REGISTER;
Event::trigger(new UserRegister($userModel,$super,$bonus));
$userModel->save();
if(isset($super->id)){
$super->save();
}
Db::commit();
//删除锁
$redLock->unlock($lock);
//重新登录
$user = new \app\model\User();
$user = $user->where('id',$userModel->id)
// ->field('id,username,account,phone,nickname,sex,email,headimg,status')
// ->withoutField('salt,password,deal_pwd')
->find();
if (!$user) {
return resjson(404, '注册失败');
}
$user->append(['store_text','is_deal_pwd']);
$user->token = setToken($user->phone);
$userArr = $user->toArray();
//更新登录信息
$user->last_login = date('Y-m-d H:i:s', time());
$user->last_ip = $this->request->ip();
$user->save();
Cache::set($user->token, $user, config('common.expires'));
unset($userArr['password']);
unset($userArr['salt']);
unset($userArr['deal_pwd']);
} catch (\Exception $exception) {
Db::rollback();
//删除锁
$redLock->unlock($lock);
if($exception->getCode() == 400){
return resjson(400, $exception->getMessage());
}
return resjson(500, '服务器错误');
}
return resjson(200, '注册成功',$userArr);
}
修改节点
//设置节点归属
public function setStore($id,$pid,$store_id = 0)
{
$user = \app\model\User::findOrEmpty($id);
if($pid){
$newParentUser = \app\model\User::findOrEmpty($pid);
if(!$newParentUser){
$this->error = '上级用户不存在';
return false;
}
}else{
//顶级栏目
if($store_id <= 0){
$this->error = '调整为顶级用户必须关联节点';
return false;
}
}
$servers = [
['127.0.0.1', 6379, 0.01],
];
$redLock = new \RedLock($servers);
//加锁
$lock = $redLock->lock('set_user_lft_and_rgt', 5000);
//将节点及其所有子节点的位置更改为负值,该值等于模块中的当前值。
//将所有位置“向上”移动,更多,即当前节点的pos_right。
//将所有位置“向下”移动更多,即新父节点的pos_right。
//更改当前节点及其所有子节点的位置,以使它现在恰好在新父节点的“之后”(或“向下”)。
// 启动事务
Db::startTrans();
try {
//1.将节点及其所有子节点的位置更改为负值,该值等于模块中的当前值。
\app\model\User::where('lft','>=',$user['lft'])->where('rgt','<=',$user['rgt'])->update([
'lft'=>Db::raw('0-lft'),
'rgt'=>Db::raw('0-rgt'),
]);
//2.将所有位置“向上”移动,(当前用户右边的用户左移)
$size = $user['rgt'] - $user['lft'] + 1;
\app\model\User::where('lft','>',$user['rgt'])->dec('lft',$size)->update();
\app\model\User::where('rgt','>',$user['rgt'])->dec('rgt',$size)->update();
if($pid){
//3.将所有位置“向下”移动,(新父级用户右边的用户右移)
$newSize = $newParentUser['rgt'] > $user['rgt'] ? $newParentUser['rgt'] - $size : $newParentUser['rgt'];
\app\model\User::where('lft','>=',$newSize)->inc('lft',$size)->update();
\app\model\User::where('rgt','>=',$newSize)->inc('rgt',$size)->update();
//4.更改当前节点及其所有子节点的位置
$addSize = $newParentUser['rgt'] > $user['rgt'] ? $newParentUser['rgt'] - $user['rgt'] - 1 : $newParentUser['rgt'] - $user['rgt'] - 1 + $size;
\app\model\User::where('lft','<=',0-$user['lft'])->where('rgt','>=',0-$user['rgt'])->update([
'lft'=>Db::raw('0-lft+'.$addSize),
'rgt'=>Db::raw('0-rgt+'.$addSize)
]);
//5.修改用户节点,上级
\app\model\User::where('id',$user['id'])->update([
'pid'=>$newParentUser['id'],
'old_pid'=>$user['pid'],
'store_id'=>$newParentUser['store_id'],
]);
//6.修改下级用户节点归属
$newUser = \app\model\User::where('id',$user['id'])->find();
\app\model\User::where('lft','>',$newUser['lft'])->where('rgt','<',$newUser['rgt'])->update(['store_id'=>$newParentUser['store_id']]);
//7.修改用户节点深度
$depth = $newParentUser['depth'] + 1 - $newUser['depth'];
if($depth != 0){
\app\model\User::where('lft','>=',$newUser['lft'])->where('rgt','<=',$newUser['rgt'])->inc('depth',$depth)->update();
}
}else{
//顶级用户
//4.更改当前节点及其所有子节点的位置
$topRight = \app\model\User::order('rgt','desc')->find();
$addSize = $topRight['rgt'] + 1 - $user['lft'];
\app\model\User::where('lft','<=',0-$user['lft'])->where('rgt','>=',0-$user['rgt'])->update([
'lft'=>Db::raw('0-lft+'.$addSize),
'rgt'=>Db::raw('0-rgt+'.$addSize)
]);
//5.修改用户节点,上级
\app\model\User::where('id',$user['id'])->update([
'pid'=>0,
'old_pid'=>$user['pid'],
'store_id'=>$store_id,
]);
//6.修改下级用户节点归属
$newUser = \app\model\User::where('id',$user['id'])->find();
\app\model\User::where('lft','>',$newUser['lft'])->where('rgt','<',$newUser['rgt'])->update(['store_id'=>$store_id]);
//7.修改用户节点深度
$depth = 1 - $newUser['depth'];
if($depth != 0){
\app\model\User::where('lft','>=',$newUser['lft'])->where('rgt','<=',$newUser['rgt'])->inc('depth',$depth)->update();
}
}
// 提交事务
Db::commit();
} catch (\Exception $e) {
// 回滚事务
Db::rollback();
$this->error = '操作失败';
return false;
}
//删除锁
$redLock->unlock($lock);
//记录日志
Db::name('store_log')->insert([
'user_id'=>$id,
'pid'=>$newUser->pid,
'store_id'=>$newUser->store_id,
'create_time'=>time(),
'update_time'=>time(),
'description'=>request()->param('description',''),
'action_id'=>app('loginUser')->user_id,
'before_id'=>$user->pid,
'before_store_id'=>$user->store_id,
]);
return true;
}
redis分布式锁实现代码
<?php
class RedLock
{
private $retryDelay;
private $retryCount;
private $clockDriftFactor = 0.01;
private $quorum;
private $servers = array();
private $instances = array();
function __construct(array $servers, $retryDelay = 200, $retryCount = 3)
{
$this->servers = $servers;
$this->retryDelay = $retryDelay;
$this->retryCount = $retryCount;
$this->quorum = min(count($servers), (count($servers) / 2 + 1));
}
public function lock($resource, $ttl)
{
$this->initInstances();
$token = uniqid();
$retry = $this->retryCount;
do {
$n = 0;
$startTime = microtime(true) * 1000;
foreach ($this->instances as $instance) {
if ($this->lockInstance($instance, $resource, $token, $ttl)) {
$n++;
}
}
# Add 2 milliseconds to the drift to account for Redis expires
# precision, which is 1 millisecond, plus 1 millisecond min drift
# for small TTLs.
$drift = ($ttl * $this->clockDriftFactor) + 2;
$validityTime = $ttl - (microtime(true) * 1000 - $startTime) - $drift;
if ($n >= $this->quorum && $validityTime > 0) {
return [
'validity' => $validityTime,
'resource' => $resource,
'token' => $token,
];
} else {
foreach ($this->instances as $instance) {
$this->unlockInstance($instance, $resource, $token);
}
}
// Wait a random delay before to retry
$delay = mt_rand(floor($this->retryDelay / 2), $this->retryDelay);
usleep($delay * 1000);
$retry--;
} while ($retry > 0);
return false;
}
public function unlock(array $lock)
{
$this->initInstances();
$resource = $lock['resource'];
$token = $lock['token'];
foreach ($this->instances as $instance) {
$this->unlockInstance($instance, $resource, $token);
}
}
private function initInstances()
{
if (empty($this->instances)) {
foreach ($this->servers as $server) {
list($host, $port, $timeout) = $server;
$redis = new \Redis();
$redis->connect($host, $port, $timeout);
$this->instances[] = $redis;
}
}
}
private function lockInstance($instance, $resource, $token, $ttl)
{
return $instance->set($resource, $token, ['NX', 'PX' => $ttl]);
}
private function unlockInstance($instance, $resource, $token)
{
$script = '
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
';
return $instance->eval($script, [$resource, $token], 1);
}
}