0043 通用工具

  本项目的通用工具都存放在GeneralTools目录下,主要包括以下内容:

01 更改JWT Token交接方式(Authentication.py)

  按JWT官方要求,JWT Token必须前端携带在Header提交。这样提交更安全,但前端每次提交数据请求的时候,都必须去获取Token,然后包装在Header里,特别实现网页跳转的时候,非常不便,因此需要写一个类去覆盖默认从Header中取Token的方法。直接把Token从session中获取。这样就不用前端去处理Token了,而是后端直接从session里拿就行了。

from rest_framework_jwt.authentication import JSONWebTokenAuthentication


class GetAuthentication(JSONWebTokenAuthentication):
    def get_jwt_value(self, request):
        
        # 从session中获取token
        # return request.session.get('token')
        # 从url中获取token
        return request.query_params.get('token')

02 获取和检查Access_Token(AuthToken)

from itsdangerous import TimedJSONWebSignatureSerializer as TJWSSerializer
from itsdangerous import BadData
from TongHeng2 import settings
from GeneralTools import Constents
import logging


logger = logging.getLogger('tongheng2')


def getToken(openid, mobile):
    """
    【功能说明】根据用户openid和mobile用于生成access_token
    """
    tjwserializer = TJWSSerializer(
        secret_key=settings.SECRET_KEY,  # 密钥
        salt=Constents.SALT,  # 盐值
        expires_in=Constents.VERIFY_ACCESS_TOKEN_EXPIRES  # 有效期
    )
    access_token = tjwserializer.dumps({'openid': openid, 'mobile': mobile})  # bytes
    access_token = access_token.decode()
    return access_token


def checkToken(token, request):
    """
    【功能说明】检查access_token是否正确
    """
    tjwserializer = TJWSSerializer(
        secret_key=settings.SECRET_KEY,  # 密钥
        salt=Constents.SALT,  # 盐值
        expires_in=Constents.VERIFY_ACCESS_TOKEN_EXPIRES  # 有效期
    )
    try:
        tjwdata = tjwserializer.loads(token)
        # 如果验证成功,则把手机号存入到session里面,以便在页面中可以随时根据mobile获取用户信息和权限。
        mobile = tjwdata['mobile']
        request.session['mobile'] = mobile
        return True
    except BadData as e:
        logger.error(e)
        return False

03 生成模型抽象类(BaseModel)

  本项目中所有模型都需要创建时间和更新时间两个字段,为了避免每个模型都去创建这两个字段,我们生成一个抽象类,所有模型都继承这个类,从而自动产生创建时间和更新时间两个字段。

from django.db import models


class BaseModel(models.Model):
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间', help_text='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间', help_text='更新时间')

    class Meta:
        # 说明这个类是一个抽象模型类,在迁移的时候不会生成表
        abstract = True

04 常量文件(Constants.py)

  本项目中所有的常量,全存储在这个文件里。

05 生成schemas概要(CustomSchema.py)

  项目中经常会用到临时字段用于前后端交互,这些字段,不需要存在数据库里,只是前后端交互的一个变量。这时候,没必要再去写序列化器。直接就用临时字段。

from rest_framework.schemas import AutoSchema


class CustomSchema(AutoSchema):
    """
    自定义AutoSchema,为view手动添加注释
    """

    def get_manual_fields(self, path, method):
        """
        location有下列可选选项可以选:
        path 包含在模板化URI中。例如,url值/products/{product_code}/可以与"path"字段一起使用。
        query 包含在URL查询参数中。例如?search=sale。通常用于GET请求。
        form 包含在请求正文中,作为JSON对象或HTML表单的单个项目。例如{"colour": "blue", ...}。通常的POST,PUT和PATCH请求。"form"单个链接上可以包含多个字段。
        header 包含在请求头中,可以自定义。
        {
            'get': [
                coreapi.Field(name="mobile", required=True, location="path", schema=coreschema.String(description='手机号')),
                coreapi.Field(name="name", required=True, location="query", schema=coreschema.String(description='用户名')),
                coreapi.Field(name="password", required=True, location="query", schema=coreschema.String(description='密码')),
            ],
            'post': [
                coreapi.Field(name="mobile", required=True, location="path", schema=coreschema.String(description='手机号')),
                coreapi.Field(name="subject", required=True, location="query", schema=coreschema.String(description='邮件主题')),
                coreapi.Field(name="message", required=True, location="query", schema=coreschema.String(description='邮件正文')),
                coreapi.Field(name="to_email", required=True, location="query", schema=coreschema.String(description='收件人')),
            ],
        }
        """

        # 可能是list,也可能是dict
        manual_fields = super(CustomSchema, self).get_manual_fields(path, method)

        if type(manual_fields) == list:
            return manual_fields
        else:
            # dict
            for k, v in self._manual_fields.items():
                if method.lower() == k.lower():
                    return v
            else:
                return []

06 微信认证装饰器(Decorate.py)

from rest_framework.response import Response
from rest_framework import status
from django.shortcuts import redirect
from .AuthToken import checkToken
from .WeChatOAuth import get_WeChatOAuth
from . import Constents


# 装饰器
def decorate(func):
    def wrapper(request, *args, **kwargs):
        # 从用户session中,获取access_token
        access_token = request.session.get('access_token', None)
        if access_token and checkToken(access_token, request):
            # 如果access_token存在,且正确,则直接执行下一步
            return func(request, *args, **kwargs)
        else:
            # 如果access_token不存在,或不正确
            userAgent = str(request.META['HTTP_USER_AGENT'])  # 获取访问浏览器的类型
            if userAgent.find('MicroMessenger') < 0:  # 如果不是微信浏览器,则直接跳转到登录页面
                return redirect('/Organizations/Login/')
            else:  # 如果是微信浏览器,返回微信授权回调地址,前端根据地址,调用login登录页面。
                url = get_WeChatOAuth(Constents.REDIRECT_URI).authorize_url
                return Response(data={'url': url}, status=status.HTTP_201_CREATED)

    return wrapper

07 自定义异常(Exceptions.py)

from rest_framework.views import exception_handler as drf_exception_handler
import logging
from django.db import DatabaseError
from redis.exceptions import RedisError
from rest_framework.response import Response
from rest_framework import status

# 获取在配置文件中定义的logger,用来记录日志
logger = logging.getLogger('tongheng2')


def exception_handler(exc, context):
    """
    自定义异常处理
    :param exc: 异常
    :param context: 抛出异常的上下文
    :return: Response响应对象
    """
    # 调用drf框架原生的异常处理方法
    response = drf_exception_handler(exc, context)

    if response is None:
        view = context['view']
        if isinstance(exc, DatabaseError) or isinstance(exc, RedisError):
            # 数据库异常
            logger.error('[%s] %s' % (view, exc))
            response = Response({'message': '服务器内部错误'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)

    return response

08 更改Django文件存储方式为fastDFS(FastDFSStorage.py)

from django.conf import settings
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructible
from fdfs_client.client import Fdfs_client
import os


@deconstructible
class FastDFSStorage(Storage):
    def __init__(self, base_url=None, client_conf=None):
        """
        初始化
        :param base_url: 用于构造图片完整路径使用,图片服务器的域名
        :param client_conf: FastDFS客户端配置文件的路径
        """
        if base_url is None:
            base_url = settings.FDFS_URL
        self.base_url = base_url
        if client_conf is None:
            client_conf = settings.FDFS_CLIENT_CONF
        self.client_conf = client_conf

    def _open(self, name, mode='rb'):
        """
        用不到打开文件,所以省略
        """
        pass

    def _save(self, name, content):
        """
        在FastDFS中保存文件
        :param name: 传入的文件名
        :param content: 文件内容
        :return: 保存到数据库中的FastDFS的文件名
        """
        client = Fdfs_client(self.client_conf)
        # 告诉fastDFS服务器,返回文件的扩展名。
        ext_name = os.path.splitext(content.name)[1][1:]
        ret = client.upload_by_buffer(content.read(), ext_name)
        if ret.get("Status") != "Upload successed.":
            raise Exception("upload file failed")
        file_name = ret.get("Remote file_id")
        # 必须替换路径中的分隔符,否则,查询不到上传的文件。
        file_name = str(file_name).replace('\\', '/')
        return file_name

    def url(self, name):
        """
        返回文件的完整URL路径
        :param name: 数据库中保存的文件名
        :return: 完整的URL
        """
        if name.startswith('http'):
            return name
        else:
            return self.base_url + name

    def exists(self, name):
        """
        判断文件是否存在,FastDFS可以自行解决文件的重名问题
        所以此处返回False,告诉Django上传的都是新文件
        :param name:  文件名
        :return: False
        """
        return False

09 更改JWT返回值(JwtHandler.py)

import logging

# 获取在配置文件中定义的logger,用来记录日志
# 注:其中的tongheng2必须和配置文件中指定的配置路径一致。
logger = logging.getLogger('tongheng2')


def jwt_response_payload_handler(token, user=None, request=None):
    """
    【功能描述】直接使用DRF-JWT提供的视图方法时,其默认的返回值只有token,若需要前端接收到用户其它信息,
    需要重写jwt_response_payload_handler方法。
    """

    return {
        'id': user.id,
        'username': user.username,
        'photo_url': user.photo_url,
        'mobile': user.mobile,
        'openid': user.openid,
        'token': token
    }

10 翻页设置(Paginations.py)

from rest_framework.pagination import PageNumberPagination


class SetPageSize5(PageNumberPagination):
    page_size = 5
    page_size_query_param = 'page_size'


class SetPageSize10(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'


class SetPageSize15(PageNumberPagination):
    page_size = 15
    page_size_query_param = 'page_size'

11 Redis数据库连接(Redis.py)

VERSION = (4, 11, 0)
__version__ = '.'.join(map(str, VERSION))


def get_redis_connection(alias='default', write=True):
    """
    Helper used for obtaining a raw redis client.
    """

    from django.core.cache import caches

    cache = caches[alias]

    if not hasattr(cache, "client"):
        raise NotImplementedError("This backend does not support this feature")

    if not hasattr(cache.client, "get_client"):
        raise NotImplementedError("This backend does not support this feature")

    return cache.client.get_client(write)

12 正则验证函数(Verifications.py)

import re


def mobileVerify(mobile):
    if re.match(r'^1[3-9]\d{9}$', mobile):
        return True
    return False

13 获取Wechatpy对象(WechatOAuth.py)

from GeneralTools import Constents
from wechatpy.oauth import WeChatOAuth


def get_WeChatOAuth(redirect_uri, state='123', scope='snsapi_userinfo'):
    """
    获取WeChatOAuth对象
    :param redirect_uri: 授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理
    :param scope:应用授权作用域,snsapi_base,snsapi_userinfo
    :param state:重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
    :return: WeChatOAuth对象
    """

    return WeChatOAuth(
        app_id=Constents.WECHAT_APPID,
        secret=Constents.WECHAT_APPSECRET,
        redirect_uri=redirect_uri,
        scope=scope,
        state=state
    )

  

猜你喜欢

转载自www.cnblogs.com/dorian/p/12430851.html