本项目的通用工具都存放在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
)
浙公网安备 33010602011771号