工作中的"注册"流程以及代码备份

简要:

        我觉得我们项目中用的注册流程好麻烦啊,可能是我是第一次给APP端写接口,token设计,存储,处理以及传输 都有好多啊,下面我将分步介绍.......

       注意点: 两个token access_token 1 天 refresh_token 30 天 ,存储到redis, 采用string类型

      流程:

	- 注册用户时,判断手机号格式是否正确
	- 在没发送验证码之前, 判断手机号是否已经被注册过
	- 密码和确认密码判断是否一致
	- 新创建两个token  
		setex access_token 1天
		setex refresh_token 30天
	- 注册完成后, 将两个token返回给用户, 并且默认用户是登录的
    设计格式:
        - 生成的两个token组合方式:
		$accessToken =  md5('at' . $mobile . $userId .time());
              	$refreshToken = md5('rt' . $mobile . $userId . time());
        - 将两个token和user_id之间创建联系,然后存储到redis上,以后可以通过token获取对应的user_id
            	user:at:{$access_token} => {$user_id}
            	user:rt:{$refresh_token} => {$user_id}

说明: 当生成好两个token后, 将他们与user_id建立起来,然后我将两个token返回给APP端.

      注册:代码如下:

    /**
     * Action Register
     * 用户注册
     */
     public function actionRegister()
     {
        $params = Yii::$app->request->post();
        $mAccount = new Account();
        $util = Yii::$app->util;

        //规格: 由8-16位的数字,字母组成的密码,其中不能有空格,不能全是数字,不能全是字母
        $regular = "/((?=.*\d)(?=.*\D)|(?=.*[a-zA-Z])(?=.*[^a-zA-Z]))^.{8,16}$/";

        if (!$params) {
            return $util->formatResData(1101, '无参数提供', (object)[], false);
        }

        if (!isset($params['mobile']) || !$params['mobile']) {
            return $util->formatResData(1102, '手机号不能为空', (object)[], false);
        }

        if (!$util->regularMobile($params['mobile'])) {
            return $util->formatResData(1103, '手机号码格式不正确', (object)[], false);
        }

        if (!isset($params['vcode']) || !$params['vcode']) {
            return yii::$app->util->formatResData(1104, '验证码不能为空', (object)[], false);
        }

        if (!isset($params['password']) || !$params['password']) {
            return yii::$app->util->formatResData(1105, '密码不能为空', (object)[], false);
        }

        if (!isset($params['confirm_password']) || !$params['confirm_password']) {
            return yii::$app->util->formatResData(1106, '确认密码不能为空', (object)[], false);
        }

        $users = $mAccount->isExistMobile($params['mobile'], 'user_id, salt, status');
        $status = 0;

        if ($users) {
            $status = $users['status'];

            //如果该用户存在,且状态为可用|禁用,都提示已注册
            if (($status == Account::STATUS_ENABLE) || ($status == Account::STATUS_DISABLE)){
                return $util->formatResData(1107, '该手机号已被注册', (object)[], false);
            }
        }

        //TODO 判断验证码是否正确

        $mobie_white_list = array('手机号', '手机号1');

       if(in_array($params['mobile'], $mobie_white_list)) {
       } else if (($result = $this->checkSmsVcode('reg', $params['mobile'], $params['vcode'])) !== true) {
          return $result;
       }

        $mInviteCode = new Invitecode();

        //判断邀请码是否存在
        if (isset($params['code']) && $params['code']) {
            $isBindMobile = $mInviteCode->isBindMobile($params['code'], $params['mobile']);

            if (!$isBindMobile) {
                return $util->formatResData(1108, '该手机号与邀请码未绑定', (object)[], false);
            }

            $data['is_receive_invokecode'] = HmcUser::STATUS_INVOKECODE_RECEIVED;
        }

        $tmp = $this->judgePassword($params['password'], $params['confirm_password'], $regular);

        if ($tmp) {
            return $tmp;
        }

        //从redis中获取用户ID
        $data = $this->getUid();

        if (!isset($data['user_id']) || !$data['user_id']) {
            return $data;
        }

        $params['user_id'] = $data['user_id'];
        unset($params['confirm_password'], $params['vcode'], $params['code']);
        $mHmcUser = new HmcUser();

        if (!$status) {
            $result = $mAccount->add($params);

        } else {
            $params['user_id'] = $users['user_id'];
            $updateVal['salt'] = $util->random(6);
            $updateVal['password'] = md5(md5($params['password']).$updateVal['salt']);
            $updateVal['status'] = Account::STATUS_ENABLE;
            $result = $mAccount->renew($params['mobile'], $updateVal);
        }

        if ($result) {
            $data['user_id'] = $params['user_id'];
            $data['mobile'] = $params['mobile'];

            if (!$status) {
                $userDetail = $mHmcUser->add($data);
            }

            //创建token,并将token存储在redis
            $handleData = $this->handleSession($params["user_id"], $params["mobile"]);

            if (!isset($handleData['variable']) || !$handleData['variable']) {
                return $handleData['error'];
            }

            $session = $handleData['variable'];
            Yii::$app->api->account_import((string)$params["user_id"], (string)$params["user_id"], '');

            try {
                $smsData = ['mobile'=> $params['mobile'], 'business_type' => 'reg'];
                $result = $this->renewSmsUseage($smsData);
            } catch (\Exception $e) {
                //TODO 将短信使用情况出现错误的地方写到日志里面
            }

            return $util->formatResData(0, '注册成功', $session, false);
        } else {
            return $util->formatResData(1201, '注册失败', (object)[], false);
        }
    }

 刷新refresh_token步骤

    流程如图所示:

代码如下:

                            /**
			     * Action RefreshToken
			     * 刷新refresh_token
			     */
			    public function actionRefreshToken()
			    {
			        $params = Yii::$app->request->get();

			        if (!isset($params['refresh_token']) || !$params['refresh_token']) {
			            return yii::$app->util->formatResData(1101, '未传递refresh_token参数', (object)[], false);
			        }

			        Yii::info($params['refresh_token'], 'api\refreshtoken');
			        $cache = Yii::$app->cache->instance('base');
			        $rtCountKey = 'user:rtcount:' . $params['refresh_token'];
			        $unitMseconds = 500000;

			        $count = $cache->incr($rtCountKey);
			        $cache->expire($rtCountKey, 10);

			        if ($count > 1 && $count <= 10) {
			            // usleep($unitMseconds * $count);
			            usleep($unitMseconds);
			        }

			        try {
			            $tokenKey = "user:rt:" . $params['refresh_token'];
			            $tmp = $cache->get($tokenKey);

			            //判断refresh_token是否过期
			            if (!$tmp) {
			                $tokens = $cache->exists('user:oldrt:' . $params['refresh_token']);

			                if (!$tokens) {
			                    return yii::$app->util->formatResData(1102, 'refresh_token过期', (object)[], false);
			                } else {
			                    $tokensKey = 'user:oldrt:' . $params['refresh_token'];
			                    $session['user_id'] = $cache->hget($tokensKey, 'user_id');
			                    $session['access_token'] = $cache->hget($tokensKey, 'access_token');
			                    $session['refresh_token'] = $cache->hget($tokensKey, 'refresh_token');
			                    $session['sig'] = $cache->hget($tokensKey, 'sig');
			                }
			            } else {
			                //如果不过期,user:rt:refresh_token 获取对应的用户ID,然后先删除rt,再重新创建两个token
			                $userId = $tmp;

			                $cache->del($tokenKey);
			                $mAccount = new Account();
			                $users = $mAccount->getByUid($userId, 'mobile');

			                //重新创建两个token
			                $handleData = $this->handleSession($userId, $users['mobile']);

			                //创建旧的refresh_token,与新的refresh_token之间关联的数据,防止并发导致找不到已经删过的refresh_token, 10s
			                if (!isset($handleData['variable']) || !$handleData['variable']) {
			                    return $handleData['error'];
			                }

			                $session = $handleData['variable'];
			                $accessToken = $session['access_token'];
			                $refreshToken = $session['refresh_token'];
			                $tmpTokenKey = "user:oldrt";
			                $oldRefreshToken = $params['refresh_token'];
			                $sig = Yii::$app->sig->getSig($userId);
			                $cache->hmset('user:oldrt:' . $oldRefreshToken, 'access_token', $accessToken, 'refresh_token', $refreshToken, 'user_id', $userId, 'sig', $sig);
			                $cache->expire('user:oldrt:' . $oldRefreshToken, 60);
			            }

			            return yii::$app->util->formatResData(0, 'refresh_token刷新成功', $session, false);
			        } catch (\Exception $e) {
			            return yii::$app->util->formatResData(-1, '服务器连接失败,请稍后重试', (object)[], false);
			        }
			    }

里面涉及的方法:

1: 创建token方法 handleSession

                    /**
		     * 创建token,并将token存储在redis上
		     *
		     * @param string $userId 用户ID
		     * @param string $mobile 手机号
		     * @param int  $atTime  access_token保存时间
		     * @param int  $rtTime  refresh_token保存时间
		     *
		     * @return array
		     * 说明: 返回的值: access_token,refresh_token,user_id,sig
		     */
		    // protected function handleSession($userId, $mobile, $atTime = 300, $rtTime = 2592000)
		    // protected function handleSession($userId, $mobile, $atTime = 120, $rtTime = 2592000)
		    protected function handleSession($userId, $mobile, $atTime = 86400, $rtTime = 2592000)
		    {
		        $now = time();
		        $accessToken =  md5('at' . $mobile . $userId . $now);
		        $refreshToken = md5('rt' . $mobile . $userId . $now);
		        $token = ['access_token' => $accessToken, 'refresh_token' => $refreshToken];


		        try {
		            // FIXED: 设置过期时间设置的为经过多少秒过去而不是时间点,如果是时间点应该单独使用expireat
		            $cache = Yii::$app->cache->instance('base');
		            $cache->setex('user:at:' . $accessToken, $atTime, $userId);
		            $cache->setex('user:rt:' . $refreshToken, $rtTime, $userId);
		            $sig = Yii::$app->sig->getSig($userId);

		            $result['variable'] = [
		                'access_token' => $accessToken,
		                'refresh_token' => $refreshToken,
		                'user_id' => $userId,
		                'sig' => $sig
		            ];
		        } catch (\Exception $e) {
		            $result['error'] = yii::$app->util->formatResData(-100, '连接redis服务器失败,请稍后重试 ', (object)[], false);
		        }

		        return $result;
		    }

2:验证手机号格式方法: regularMobile()

    /**
     * 中国移动
     */
    const CHINA_MOBILE_EXPR = '/^134[0-8]\d{7}$|^(?:13[5-9]|147|15[0-27-9]|178|18[2-478])\d{8}$/';
    /**
     * 中国联通
     */
    const CHINA_UNION_EXPR = '/^(?:13[0-2]|145|15[56]|176|18[56])\d{8}$/';
    /**
     * 中国电信
     */
    const CHINA_TELCOM_EXPR = '/^(?:133|153|177|18[019])\d{8}$/';
    
    /**
     * 手机正则表达式验证
     *
     * @param string $mobile 手机
     * @return int false 表示验证失败 true表示成功
     */
    public function regularMobile($mobile)
    {
        return preg_match(self::CHINA_MOBILE_EXPR, $mobile)
            || preg_match(self::CHINA_UNION_EXPR, $mobile)
            || preg_match(self::CHINA_TELCOM_EXPR, $mobile)
            || preg_match(self::CHINA_OTHER_EXPR, $mobile);
    }
3: 判断密码和确认密码 judgePassword()

    /**
     * 判断密码和确认密码
     */
    public function judgePassword($password, $confirmPwd, $regular)
    {
        if (!preg_match($regular, $password)) {
            return yii::$app->util->formatResData(1401, '密码格式不正确', (object)[], false);
        }

        if (!preg_match($regular, $confirmPwd)) {
            return yii::$app->util->formatResData(1402, '确认密码格式不正确', (object)[], false);
        }

        if ($password !== $confirmPwd) {
            return yii::$app->util->formatResData(1403, '密码与确认密码不一致', (object)[], false);
        }
    }

* 为什么要创建两个token ?
        - 假如你只设置一个token表示app端用户的登录状态, 那么如果你的token被别人采用非正当手段截取了, 他是不是就可以"为所欲为"了, 而我们设计两个token, access_token设置1天, refresh_token设置30天,
        如果access_token过期了, refresh_token会刷新重新创建新的两个token, 并且把旧的refresh_token删掉. 由于access_token生存时间短暂, 那么假如被人盗取了, 没关系, 我们access_token过期后, 会用refresh_token刷新, 再重新创建新的两个token, 即便是refresh_token被盗取了, 也没关系, 因为又重新创建两个新的access_token和refresh_token, 你说这样是不是就比较完美了!
 

总结:

         我只是把部分的流程列了出来,以后要是再碰到类似的,我就可以直接使用了,对于我来说,现在该学会的就是能够拿别人的代码应用到我的应用中,即便是我没明白,但是功能实现是第一步,因为工作领导不管你怎么样,人家要的是结果,因为现实的社会很残酷,不管你是谁,你是女生还是男生。

  所以加油,我可以的,不能被别人看不起!



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值