最近很多H5或者小程序的广告表单提交需求需要做,索性使用Drf完整各个终端服务器数据汇总吧,完整记录下来供后续参考。
前提准备 服务器端 服务端使用uwsgi+nginx
发布我们的Django
应用,服务端已经部署完成,详细的操作见:**uwsgi配置发布web服务器 **
应用程序服务端 我们这里使用的Django
版本为最新的3.0.6
,Django REST Framework
版本为:3.11.0
。数据库我们采用mysql
。
创建项目及应用 我们本地使用windows
进行开发,pycharm
工具与服务器做了同步处理。
pycharm
同步设置
配置账户
映射配置
自动上传
使用命令行创建项目 1 2 django-admin startproject django_test_project django-admin startapp app
settings.py
设置1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # 所有主机访问 ALLOWED_HOSTS = ['*'] # 将新创建的app应用添加至已安装应用列表 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app.apps.AppConfig', ] # 使用mysql数据库 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dbname #你的数据库名称 'USER': 'dbuser你的数据库用户名 'PASSWORD': 'dbpassword密码 'HOST': '', #你的数据库主机,留空默认为localhost 'PORT': '3306', #你的数据库端口 } } # 语言和时区 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai'
模型类 我们使用django
内置的user
模型进行扩展,使用我们的api
发起某些请求需要传递token
。
后续如果牵涉到第三方登陆我们可以保存至第三方用户表,与内置用户表进行一对一关联。
用户提交的表单我们也要单独存放一个表(用户表单我们不确定都有什么字段,所以我们将用户表单提交过来的数据打包为json
字符串进行保存),暂时创建以上三张表,后续根据需要我们慢慢添加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from django.db import models from django.contrib.auth.models import User # Create your models here. # 用户token class UserToken(models.Model): user = models.OneToOneField(to=User, verbose_name='用户', on_delete=models.CASCADE) token = models.UUIDField(verbose_name='token') uptime = models.DateTimeField(auto_now=True, verbose_name='更新时间') # 第三方用户登录信息 class ThirdParty(models.Model): user = models.OneToOneField(to=User, verbose_name='用户', on_delete=models.CASCADE) wechat = models.CharField(max_length=50, verbose_name='微信唯一id', null=True) alipay = models.CharField(max_length=50, verbose_name='支付宝唯一id', null=True) thumbnail = models.ImageField(upload_to='thumbnail', verbose_name='图片', null=True) # 表单数据 class FormData(models.Model): id = models.AutoField(primary_key=True) user = models.ForeignKey(to=User, verbose_name='用户', on_delete=models.CASCADE) # 用来标识数据来源于该用户的哪个表单 slug = models.SlugField(max_length=100, verbose_name='来源') body = models.CharField(max_length=255, verbose_name='表单json数据') uptime = models.DateTimeField(auto_now=True, verbose_name='更新时间')
迁移数据 迁移之前尝试运行了下该程序,直接报错了:
1 django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.13 or newer is required, you have 0.9.2
嫌弃我们版本低?
项目的__init__.py
文件中修改
1 2 3 import pymysql pymysql.version_info = (1, 3, 13, "final", 0) pymysql.install_as_MySQLdb()
开始迁移
1 2 python manage.py makemigrations python manage.py migrati
顺手创建个管理员吧
1 python manage.py createsuperuser
创建序列化 app
应用中我们创建untils
文件夹,用来存放后续认证、频率、权限、序列化等文件。我们现在该文件夹中创建serializers.py
用来存放自定义序列化
1 2 3 4 5 6 7 8 9 10 11 12 # -*- coding: utf-8 -*- # @Time : 2020/5/12 13:33 # @Author : Tony Yu # @Author URI: https://www.diandian100.cn from rest_framework import serializers from app.models import FormData class FormDataSerializer(serializers.ModelSerializer): class Meta: model = FormData fields = '__all__'
我们先做一个表单数据表序列化,且并没有对任何字段做验证,因为使用的ModelSerializer
,所以默认会验证模型类中没有定义null=True
的字段。
使用token
后续路由验证 牵涉到表单提交数据,我们要做token
验证,此处我们直接使用jwt
生成token
进行后续验证使用。关于jwt
的详细使用请参见:xxxxxx
token
生成和验证此处我们直接使用app/untils/jwt_tokens.py
来存放上述代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 # -*- coding: utf-8 -*- # @Time : 2020/5/13 11:43 # @Author : Tony Yu # @Author URI: https://www.diandian100.cn import datetime import jwt from jwt import exceptions salt = 'iv%x1fo9l7_u9bf_u!9#g#m*)*=ej@bek5)(@u3kh*72+unjv=' def generate_token(playload, timeout = 120): ''' 生成token :param playload: 一般为用户id、用户名的字典 :param timeout: 多久过期,单位:分钟 :return: ''' # 构造header headers = { 'typ': 'jwt', 'alg': 'HS256' } # 构造payload playload = playload # token有效期,我这边设置为了分钟,即x分钟后过期 playload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=timeout) # 构造signature即token token = jwt.encode(payload=playload, key=salt, algorithm="HS256", headers=headers).decode('utf-8') return token def validate_token(token): '''校验token有效性''' # 定义返回相应字典 result = {'status': False, 'data': None, 'error': None} try: # 从token中获取payload【不校验合法性】 # unverified_payload = jwt.decode(token, None, False) # print(unverified_payload) # 从token中获取payload【校验合法性】;如果token正确,返回生成token时的playload字典 verified_payload = jwt.decode(token, salt, True) result['status'] = True result['data'] = verified_payload except exceptions.ExpiredSignatureError: result['error'] = 'token已失效' except jwt.DecodeError as err: result['error'] = 'token认证失败' except jwt.InvalidTokenError: result['error'] = '非法的token' return result
登录视图 要想提交数据,要携带有效token
,初次token
需要使用用户名和密码进行交换,获取token
我们就放在这个登录视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import datetime from django.contrib import auth from rest_framework.views import APIView from rest_framework.response import Response from app.untils.response import ResponseDict from app.untils import jwt_tokens # 登录 class Login(APIView): def post(self, request): # 实例化自定义响应器,响应给前端的字典 res = ResponseDict() # 数据接收字典 recev_data = {'username', 'password'} # 判断定义的待接收字典是否是用户提交的post数据的子集,即用户提交的数据是否包含了用户名和密码 if recev_data.issubset(set(request.data)): # 接收用户提交的用户名密码 user_info = dict() for item in recev_data: user_info[item] = request.data.get(item) # 验证该用户 user_obj = auth.authenticate(**user_info) # 判断用户是否存在 if user_obj: # 生成token,并将token有效期设为了10分钟 token = jwt_tokens.generate_token({"id":user_obj.id, "name":user_obj.username}, 10) # 封装返回数据 res.data = { "access_token": token } res.msg = "登录成功" res.expires = 600 else: res.code = 1001 res.msg = "用户名或密码错误" else: res.code = 1001 res.msg = "参数有误,请重新提交" return Response(res.dict)
我们测试一下:
尝试字段携带错误,请求一下:
尝试账户密码错误,请求一下:
最后使用正确的账户信息请求一下:
可以看到系统成功给我们返回了token
自定义认证组件 上面我们定义了token生成和验证token的方法,现在我们要自定义一个认证类。
在此之前我们在上面使用了一个自定义响应体类,没有放出来直接使用了,这里我们放出一下app\untils\response.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # -*- coding: utf-8 -*- # @Time : 2020/5/12 16:26 # @Author : Tony Yu # @Author URI: https://www.diandian100.cn class ResponseDict: '''定义一个自定义响应字典''' def __init__(self): self.code = 1000 self.data = None self.msg = '' # self.expires = 7200 def dict(self): return self.__dict__ if __name__ == '__main__': res = ResponseDict() res.msg = '测试上 ' print(res.dict)
接下来是我们的自定义认证类app\untils\authentications.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # -*- coding: utf-8 -*- # @Time : 2020/5/13 14:09 # @Author : Tony Yu # @Author URI: https://www.diandian100.cn from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from app.untils import jwt_tokens from app.untils.response import ResponseDict class JwtTokenAuthentication(BaseAuthentication): def authenticate_header(self, request): pass def authenticate(self, request): # 实例化我们的自定义响应体类 res = ResponseDict() # 获取用户请求头中携带的token authorization = request.META.get('HTTP_AUTHORIZATION', '') # 判断是否携带token if not authorization: res.msg = '未获取到Authorization请求头' res.code = 1001 raise AuthenticationFailed(res.dict) # 验证用户携带的token result = jwt_tokens.validate_token(authorization) # 判断token是否有效,无效输出错误提示 if not result['status']: res.msg = result['error'] res.code = 1001 raise AuthenticationFailed(res.dict) # token有效,返回用户字典和token return (result, authorization)
定义表单视图类 我们的表单暂时只定义列表页和post提交页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 from rest_framework.views import APIView from rest_framework.response import Response from app.models import FormData as FormDataModel from app.untils.serializers import FormDataSerializer from app.untils.response import ResponseDict from app.untils.authentications import JwtTokenAuthentication class FormDatas(APIView): # token验证类列表 authentication_classes = [JwtTokenAuthentication, ] def get(self, request): res = ResponseDict() # 获取所有formdatas对象 form_datas = FormDataModel.objects.all() # 使用序列化类序列化formsdatas字段,many代表获取的是多条信息 serialize_data = FormDataSerializer(form_datas, many=True) res.msg = "成功" res.data = serialize_data.data return Responseres.dict) def post(self, request): res = ResponseDict() #使用序列化模型接收用户提交的数据 serialize_data = FormDataSerializer(data=request.data) if serialize_data.is_valid(): # 保存表单数据 serialize_data.save() res.msg = "提交成功" res.data = serialize_data.data else: res.msg = "提交失败" res.data = serialize_data.errors return Response(res.dict)
测试:
token
已经过期了,我们需要重新获取token
,我们输入重新获取的token
再次尝试:
数据正确返回,接下来我们测试下post
数据
我们故意不填写body字段进行数据提交,出现以上提示,再次尝试一个正确的
这次数据成功提交了。
自定义节流 自定义限制频率类路径:app\untils\visit_thottle.py
1 2 3 4 5 6 7 8 9 10 11 # -*- coding: utf-8 -*- # @Time : 2020/5/13 17:07 # @Author : Tony Yu # @Author URI: https://www.diandian100.cn from rest_framework.throttling import SimpleRateThrottle class VisitThottle(SimpleRateThrottle): # 设定访问速度 rate = '5/m' def get_cache_key(self, request, view): return self.get_ident(request)
尝试刷新5次再次访问如下:
最后说下,我们api
应用肯定会有跨域问题,我这里安装了第三方跨域插件corsheaders
,使用pip安装好该插件在settings
里做以下设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'app.apps.AppConfig', # 解决跨域 'corsheaders' ] MIDDLEWARE = [ # 解决跨域 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] CORS_ORIGIN_ALLOW_ALL = True
至此,我们的api
已经基本完成了,其他视图无非重复以上的动作,这里我们没有做字段验证,使用了数据库模型默认验证,另外我们没有添加权限验证、分页等,因为我们的业务还是很简单的,需要用到的可以参考我之前的文章drf各个组件使用流程