django项目2

前边我们已经完成了基本配置信息,下面我们就进一步实现我们的具体业务逻辑。

首先实现注册以及登陆模块的义务逻辑

1.注册

pass

2.登陆

pass

3.qq登陆

qq登陆的业务逻辑

①用户在浏览器发送请求得到一个url,这个url是为了获取一个二维码。

请求方式:GET /oauth/qq/authorization/?next=xxx

next解释:用户登陆成功后需要转到的界面

我们后端需要做的就是把这样的数据进行拼接然后返回

这是QQ开发文档提供的步骤:

根据接口文档我们进行数据的拼接:

# QQ登录参数
QQ_CLIENT_ID = '101474184'
QQ_CLIENT_SECRET = 'c6ce949e04e12ecc909ae6a8b09b637c'
QQ_REDIRECT_URI = 'http://www.meiduo.site:8080/oauth_callback.html'
QQ_STATE = '/'

因为前端给我们传了next参数,它指定了登陆成功后跳转的界面,我们需要把这个参数传入赋值给state,因为成功授权后会原样带回。

视图函数调用辅助工具类:

class QQAuthURLView(APIView):
    def get(self, request):
        # 获取QQ登陆的url
        # 前端传过来的next
        next = request.query_params.get('next')
        oauth = OAuthQQ(state =  next) # 将next传入辅助工具类
        login_url = oauth.get_qq_login_url()
        return Response({'login_url':login_url})
# 辅助工具类
class OAuthQQ(object):

    def __init__(self, client_id = None, client_secret= None, redirect_uri = None, state = None):
        self.client_id = client_id or settings.QQ_CLIENT_ID,
        self.client_secret = client_secret or settings.QQ_CLIENT_SECRET
        self.redirect_uri = redirect_uri or settings.QQ_RESIRECT_URI
        self.state = state or settings.QQ_STATE # 用于保存登陆成功后的页面跳转路径

    def get_qq_login_url(self):
        """
        生成获取qq登陆二维码的url地址
        :return:
        """

        paramas = {
            'response_type': 'code',
            'client_id': self.client_id,
            'redirect_uri': self.redirect_uri,
            'state': self.state,
            'scope': 'get_user_info',
        }
        url = 'https://graph.qq.com/oauth2.0/authorize?'+ urlencode(paramas)
        return url

到此前端接受我们的url,生成一个二维码

②用户进行扫码,获取用户Code值

 如果用户成功登录并授权,则会跳转到指定的回调地址,并在redirect_uri地址后带上Code(过期时间10分钟)和原始的state值。

这样用户就得到了Code值,下面要做的就是将这个code值交给我们自己的应用,然后应用根据这个code 值向qq服务器发送请求获取Access Token

对应接口文档:

③同理,我们需要进行地址拼接,然后向qq服务器发送请求获取accsess token

先在辅助工具类内部增加方法:get_access_token()

def get_access_token(self, code):
        """
        使用code,获取access_token
        :param code: authorization code
        :return: access_token
        """
        # 准备url
        url = 'https://graph.qq.com/oauth2.0/token?'

        # 准备参数
        params = {
            'grant_type': 'authorization_code',
            'client_id': self.client_id,
            'client_secret': self.client_secret,
            'code': code,
            'redirect_uri': self.redirect_uri
        }

        # 将params字典转成查询字符串
        query_params = urlencode(params)

        # 拼接请求地址
        url += query_params

        try:
            # 美多商城向QQ服务器发送GET请求
            # (bytes)'access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14'
            response_data = urlopen(url).read()
            # (str)'access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14'
            response_str = response_data.decode()
            # 将response_str,转成字典
            response_dict = parse_qs(response_str)
            # 读取access_token
            access_token = response_dict.get('access_token')[0]
        except Exception as e:
            logger.error(e)
            # 在封装工具类的时候,需要捕获异常,并抛出异常,千万不要解决异常,谁用谁解决异常
            # BookInfo.objects.gSerializeet()   类似于这样的一种思想
            raise QQAPIException('获取access_token失败')

        return access_token
class QQAuthUserView(APIView):

    def get(self, request):
        """
        处理oauth_callback回调页面时
        提取code,access_token
        """

        # 提取code请求参数
        code = request.query_params.get('code')
        if code is None:
            return Response({'message':'缺少code'}, status=status.HTTP_400_BAD_REQUEST)

        # 创建OAuthQQ对象
        oauth = OAuthQQ()

        try:
            # 使用code向QQ服务器请求access_token
            access_token = oauth.get_access_token(code)  # 传入参数,调用方法获取access_token

要是没有得到,接口文档也给我们提供了返回信息的说明:

④有了access_token我们就可以用它获取open id

接口文档:

我们只需传入获取的access_token 就可以获取open_id

在辅助工具类内部增加方法:get_open_id

def get_open_id(self, access_token):
        """
        使用access_token获取openid
        :param access_token: 通过code 获取到的access_token
        :return: openid
        """
        # 准备请求地址
        url = 'https://graph.qq.com/oauth2.0/me?access_token=%s' % access_token

        response_str = ''
        try:
            # 美多商城向QQ服务器发送GET请求
            # (bytes)'callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );'
            response_data = urlopen(url).read()
            response_str = response_data.decode()
            # callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} )\n;
            response_dict = json.loads(response_str[10:-4])
            # 读取openid
            open_id = response_dict.get('openid')
        except Exception as e:
            # 如果有异常,QQ服务器返回 "code=xxx&msg=xxx"
            err_data = parse_qs(response_str)
            logger.error(e)
            raise QQAPIException('code=%s msg=%s' % (err_data.get('code'), err_data.get('msg')))

        return open_id
class QQAuthUserView(APIView):

    def get(self, request):
        """
        处理oauth_callback回调页面时
        提取code,access_token,openid
        """

        # 提取code请求参数
        # code = request.query_params.get('code')
        # if code is None:
        #    return Response({'message':'缺少code'}, status=status.HTTP_400_BAD_REQUEST)

        # 创建OAuthQQ对象
        oauth = OAuthQQ()

        try:
            # 使用code向QQ服务器请求access_token
            #access_token = oauth.get_access_token(code)

            # 使用access_token向QQ服务器请求openid
            open_id = oauth.get_open_id(access_token)
        except QQAPIException as e:
            logger.error(e)
            return Response({'message': 'QQ服务异常'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)

获取了open_id后就可以得到一个唯一识别qq用户的标识

然后根据这个open_id查数据库的表,看是否有这样的模型对象:

1.说明不是第一次注册,生成JWT token(状态保持),返回:token,user_name,user_id;

2.没有说明是第一次登陆,在辅助工具类增加函数generate_save_user_token生成open id 签名(加密)后的结果,返回open id

class QQAuthUserView(GenericAPIView):

    # 指定序列化器
    serializer_class = serializers.QQAuthUserSerializer

    def get(self, request):
        """
        处理oauth_callback回调页面时
        提取code,access_token,openid
        """

        # 提取code请求参数
        #code = request.query_params.get('code')
        #if code is None:
            #return Response({'message':'缺少code'}, status=status.HTTP_400_BAD_REQUEST)

        # 创建OAuthQQ对象
        #oauth = OAuthQQ()

        #try:
            # 使用code向QQ服务器请求access_token
            #access_token = oauth.get_access_token(code)

            # 使用access_token向QQ服务器请求openid
            #open_id = oauth.get_open_id(access_token)
        #except QQAPIException as e:
            #logger.error(e)
            #return Response({'message': 'QQ服务异常'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)

        # 使用openid查询该QQ用户是否在美多商城中绑定过用户
        try:
            # oauth_model : 代表一条记录,OAuthQQUser类型的对象
            oauth_model = OAuthQQUser.objects.get(openid=open_id)
        except OAuthQQUser.DoesNotExist:
            # 如果openid没绑定美多商城用户,创建用户并绑定到openid

            # 生成openid签名后的结果
            token_openid = oauth.generate_save_user_token(open_id)
            # 将openid签名之后的结果响应给用户
            return Response({'access_token': token_openid})
            # return Response({'openid':open_id})
        else:
            # 如果openid已绑定美多商城用户,直接生成JWT token,并返回
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

            # 获取跟openid绑定的user对象
            user = oauth_model.user

            payload = jwt_payload_handler(user)
            # JWT token
            token = jwt_encode_handler(payload)

            # 响应数据
            return Response({
                'token':token,
                'user_id':user.id,
                'username':user.username
            })

前端根据返回值判断,要是返回open id,说明是第一次登陆,则过渡到绑定界面;否则直接跳转到应用主页

要是没绑定,前端会发送POST请求将手机号与open id绑定,要是用户注册过手机号,验证密码后直接绑定,要是没注册过,则先写入数据库,然后进行绑定(此过程需要序列化器所以视图类继承GenericAPIView,但是get方法,post方法还得写)

在视图中添加post方法:

def post(self, request):
        """openid绑定用户"""
        # 创建序列化器
        serializer = self.get_serializer(data=request.data) # 前端数据:手机号,密码,验证码,open_id
        # 校验
        serializer.is_valid(raise_exception=True)
        # 调用create方法,实现绑定用户的保存
        user = serializer.save()

        # 生成状态保持信息
        # 生成JWT token,并响应
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)

        return Response({
            'token': token,
            'username': user.username,
            'user_id': user.id
        })

对应的序列化器:

这里注意,前端将open id 保存在了access_token字段里边 

class QQAuthUserSerializer(serializers.Serializer):
    """
    QQ登录创建用户序列化器
    """
    access_token = serializers.CharField(label='操作凭证')
    mobile = serializers.RegexField(label='手机号', regex=r'^1[3-9]\d{9}$')
    password = serializers.CharField(label='密码', max_length=20, min_length=8)
    sms_code = serializers.CharField(label='短信验证码')

    def validate(self, data):
        # 检验access_token
        # 这里注意,前端将open id 保存在了access_token字段里边        
        access_token = data['access_token']
        # 获取身份凭证
        openid = OAuthQQ.check_save_user_token(access_token)
        if not openid:
            raise serializers.ValidationError('无效的access_token')

        # 将openid放在校验字典中,后面会使用
        data['openid'] = openid

        # 检验短信验证码
        mobile = data['mobile']
        sms_code = data['sms_code']
        redis_conn = get_redis_connection('verify_codes')
        real_sms_code = redis_conn.get('sms_%s' % mobile)
        if real_sms_code.decode() != sms_code:
            raise serializers.ValidationError('短信验证码错误')

        # 如果用户存在,检查用户密码
        try:
            user = User.objects.get(mobile=mobile)
        except User.DoesNotExist:
            pass
        else:
            password = data['password']
            if not user.check_password(password):
                raise serializers.ValidationError('密码错误')

            # 将认证后的user放进校验字典中,后续会使用
            data['user'] = user
        return data

    def create(self, validated_data):
        # 获取校验的用户
        user = validated_data.get('user')

        if not user:
            # 用户不存在,新建用户
            user = User.objects.create_user(
                username=validated_data['mobile'],
                password=validated_data['password'],
                mobile=validated_data['mobile'],
            )

        # 将用户绑定openid
        OAuthQQUser.objects.create(
            openid=validated_data['openid'],
            user=user
        )
        # 返回用户数据
        return user

猜你喜欢

转载自blog.csdn.net/weixin_31449201/article/details/81162565
今日推荐