本项目的通用工具都存放在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 )