前言
本章内容主要完成以下几个内容:
1、django切换sqllite至mysql
2、 用户注册功能
3、JWT用户认证功能
4、 获取用户信息
因为个人并不喜欢使用django 的 “视图集” ,所以后续的内容会使用“类视图”来实现API
1、django切换sqllite至mysql
下载依赖
pip install -i https://pypi.douban.com/simple/ pymysql
将settings中DATABASES的配置修改为mysql
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'testplatform',
'USER': 'root',
'PASSWORD': 'root',
'HOST': 'localhost',
'PORT': '3306',
}
}
需要在 TestPlatform/init.py中添加
import pymysql
pymysql.version_info = (1, 4, 0, "final", 0)
pymysql.install_as_MySQLdb()
2、用户注册功能
用户是一个独立的模块,所以这里我们继续采用之前的“老四样”:
1、新建APP
python manage.py startapp users
2、将APP注册在settings.py文件中
INSTALLED_APPS=[ + users.apps.UsersConfig]
3、实现业务逻辑
4、将url注册在urls.py文件中
2.1定义用户数据模型
第一步,第二步没有什么异议,只是我们需要在settings.py中增加一些内容
# 使用 新定义的user 不用django 的User
AUTH_USER_MODEL = 'users.User'
然后在models.py中定义User类
from django.contrib.auth.models import AbstractUser
from django.db import models
# Create your models here.
class User(AbstractUser):
'''
用户信息表
'''
name = models.CharField(max_length=32, null=False, blank=False, verbose_name="姓名")
mobile = models.CharField(max_length=11, unique=True, null=False, blank=False, verbose_name="电话")
email = models.EmailField(max_length=128, unique=True,null=False, blank=False, verbose_name="邮箱")
# username 不做唯一处理
username = models.CharField(max_length=30, unique=False)
USERNAME_FIELD = 'mobile'
class Meta:
# 联合约束 mobile ,email不能重复
unique_together = ["mobile", "email"]
verbose_name = '用户'
verbose_name_plural = '用户'
def __str__(self):
return self.name
AbstractUser类是django自带的用户类,我们继承这个类,添加一些我们需要的字段。AbstractUser默认username是唯一的,不符合我们的设计要求,所以 我将 USERNAME_FIELD 设置为’mobile’
执行命令行,自动生成模型相关的数据库表结构:
python manage.py makemigrations
python manage.py migrate
2.2 serializer 序列化
序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据
class UserRegSerializer(serializers.ModelSerializer):
name = serializers.CharField(label="用户名", help_text="用户名", required=True, allow_blank=False)
mobile = serializers.CharField(label="手机号", help_text="手机号", required=True, allow_blank=False,
validators=[UniqueValidator(queryset=User.objects.all(), message="手机号已经存在")])
email = serializers.EmailField(label="邮箱号", help_text="邮箱号", required=True, allow_blank=False,
validators=[UniqueValidator(queryset=User.objects.all(), message="邮箱号已经存在")])
password = serializers.CharField(
style={'input_type': 'password'}, help_text="密码", label="密码", write_only=True,
)
class Meta:
model = User
fields = ("name", "mobile", "email", 'password')
2.3 定义统一的返回格式
web开发中,通常我们会将系统返回的Reponse统一格式,方便前端获取数据。
我们再utilsapp/common中定义统一的返回数据
# 自定义状态码
class HttpCode(object):
# 正常登陆
ok = 200
# 参数错误
paramserror = 400
# 权限错误
unauth = 401
# 方法错误
methoderror = 405
# 服务器内部错误
servererror = 500
# 定义统一的 json 字符串返回格式
def result(code=HttpCode.ok, message="", data=None, kwargs=None):
json_dict = {"code": code, "message": message, "data": data}
# isinstance(object对象, 类型):判断是否数据xx类型
if kwargs and isinstance(kwargs, dict) and kwargs.keys():
json_dict.update(kwargs)
return Response(json_dict)
def ok():
return result(message="success")
def ok_data(data=None):
return result(data=data,message="success")
# 参数错误
def params_error(message="params error", data=None):
return result(code=HttpCode.paramserror, message=message, data=data)
# 权限错误
def unauth(message="", data=None):
return result(code=HttpCode.unauth, message=message, data=data)
# 方法错误
def method_error(message="methods error", data=None):
return result(code=HttpCode.methoderror, message=message, data=data)
# 服务器内部错误
def server_error(message="server error", data=None):
return result(code=HttpCode.servererror, message=message, data=data)
2.4 用户注册业务逻辑
字段的校验逻辑 在UserRegSerializ类中已经声明了,我们这里直接将接口的数据request.data 获取,进行校验,符合规则就保存
class UserRegisterView(CreateAPIView):
serializer_class = UserRegSerializer
permission_classes = [AllowAny]
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
return ok()
else:
return params_error(message=serializer.errors)
因为这里是直接保存,所以我们通过接口传过来的 password 字段也是明文保存到数据库中,这显然是不合理的。
在django 中我们使用 signals(信号)------允许解耦的应用在框架的其它地方发生操作时会被通知到,也就是说在特定事件发生时,可以发送一个信号去通知所有注册了这个信号的回调,在回调里进行想要的操作处理。
这里我们使用 post_save 方法,在调用save方法之后,对models进行操作,也就是对密码加密
User = get_user_model()
@receiver(post_save, sender=User)
def create_user(sender, instance=None, created=False, **kwargs):
if created:
password = instance.password
instance.username = instance.email
instance.set_password(password)
instance.save()
这里还有一步操作
instance.username = instance.email
我们希望将email 作为唯一的username进行存储,使用mobile 也可以
2.5 添加urls,并调试
我们按照之前文档的步骤,添加urls
urlpatterns = [
path('register', views.UserRegisterView.as_view(), name='register'),
]
然后调试
数据库中的数据
3、JWT用户认证功能
json web token 简称JWT ,建议不知道这个概念的小伙伴 搜索一下相关资料,这里不做过多讲解。
下载依赖
pip install -i https://pypi.douban.com/simple/ djangorestframework-jwt
3.1 定义token返回
因为我们集成了django 自带的 “ModelBackend” 类,我们如果不定义格式的话,会直接返回 token,为了保持统一格式,我们这里需要定义一个新的 ‘JWT_RESPONSE_PAYLOAD_HANDLER’,来保证格式的统一。
在 users/utils.py 中定义一个方法
def jwt_response_payload_handler(token, user=None, request=None):
"""
自定义jwt认证成功返回数据
"""
return {"code": 200, "message": "登录成功", "data": {"token": token}}
3.2 用户认证接口
我们再users/views.py中定义我们的登录接口,我们先通过 User.objects.get()方法来查询是否存在用户,其中 Q 的使用是 django.db.models 中的一个方法,等同于我们的mysql 语句
where email =xxx or mobile =xxx
然后通过check_password ,校验密码是否正确。
class CustomBackend(ModelBackend):
"""
自定义用户验证
"""
def authenticate(self, username=None, password=None, **kwargs):
try:
user = User.objects.get(Q(email=kwargs['mobile']) | Q(mobile=username))
if user.check_password(password):
return user
except Exception as e:
return None
3.3 注册相关信息
在settings.py文件中注册我们刚才完成的信息
REST_FRAMEWORK = {
# rest framework的认证机制
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
}
import datetime
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
'JWT_AUTH_HEADER_PREFIX': 'JWT',
# 自定义JWT返回数据
'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',
}
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend', # 用户自定义认证
'django.contrib.auth.backends.ModelBackend',
)
3.4 调试
将接口信息添加至urls.py ,启动服务器就可以进行调试了
4、获取用户信息
我们在view.py中新增了一个 UserInfoView 类,并让这个类继承了 RetrieveAPIView 这个视图类,表示 获取 单个数据
IsAuthenticated:必须登录用户;
permission_classes = (IsAuthenticated,)用来做用户认证的
authentication_classes = (JSONWebTokenAuthentication,SessionAuthentication)
class UserInfoView(RetrieveAPIView):
serializer_class = UserDetailSerializer
authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
def get(self, request, *args):
user_id = request.GET.get('id')
if request.user.id != int(user_id):
return unauth(message="无法查询他人信息")
else:
user = User.objects.filter(id=user_id)
user_info_str = serializers.serialize('json', user, fields=("name", "email", "mobile"))
user_info = json.loads(user_info_str)
return ok_data(data=user_info[0].get("fields"))
新增 UserInfosView 类 ,并让这个类继承了 ListAPIView 这个视图类,表示 获取多个数据。我们知道在获取多数据的时候,如果不做分页,会对 数据库造成很大的压力,所以我们这里同时实现了一个分页的功能
class UsersPagination(PageNumberPagination):
'''
商品列表自定义分页
'''
# 默认每页显示的个数
page_size = 10
# 可以动态改变每页显示的个数
page_size_query_param = 'page_size'
# 页码参数
page_query_param = 'page'
# 最多能显示多少页
max_page_size = 100
class UserInfosView(ListAPIView):
serializer_class = UserDetailSerializer
queryset = User.objects.all()
# 分页
pagination_class = UsersPagination
authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
def get(self, request, *args, **kwargs):
user_infos_str = serializers.serialize('json', self.queryset.all().order_by('-id'), fields=("name", "email", "mobile"))
user_infos = json.loads(user_infos_str)
# 实例化分页对象,获取数据库中的分页数据
paginator = UsersPagination()
page_user_list = paginator.paginate_queryset(user_infos, self.request, view=self)
json_list = []
for user in page_user_list:
user_info = user.get("fields")
json_list.append(user_info)
return ok_data(json_list)
接口调试:
单一数据接口
多数据接口:
5、总结
可能有人会觉得上面的代码很不“django ”,明明已经有很多封装好的东西没有拿来直接使用,而重新完成了一些逻辑 。
因为个人觉得直接使用django的 “视图集” 封装的太多,不看源码的话不知道这些类,方法 到底是做什么的,而且在这个项目中,我需要自己定制化一些东西,所以我选择 APIView 进行开发。
如果你有想实现的功能欢迎提交
本项目的代码已上传git
https://github.com/627886474/TestPlatform(本章内容分支 git checkout user)
如果你觉得项目对你有帮助,可以关注一下微信公众号,持续分享干货