首先,参考了这个网页[Django+xadmin打造在线教育平台(一)](https://www.cnblogs.com/derek1184405959/p/8590360.html)https://www.cnblogs.com/derek1184405959/p/8590360.html,建立了数据模型,在users,course,study_log这三个django的子app中,每个models.py都写了相应的数据
from django.db import models
from django.contrib.auth.models import AbstractUser
import datetime
class userProfile(AbstractUser):
id = models.AutoField('ID',primary_key=True)
username = models.CharField('手机号',max_length=12,unique=True)
password = models.CharField('验证码',max_length =100)
last_send_code = models.IntegerField('最后一次发送验证码时间戳',default='0')
sumTimes = models.IntegerField('学习总时长(s)',default=0)
class Meta:
verbose_name = '用户个人信息'
verbose_name_plural = verbose_name
def __str__(self):
return self.username
可以看到,在users中,写了一个userProfile的类,id是主键,autofield意味着它可以自增,不需要手动添加,然后对于char类型的字段,都需要固定长度,整数型则不需要,这就是整个数据模型建立的过程。最好可以使用uml图建立数据模型,如[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
最开始,学长让我使用restful方式写接口,因为我们是前后端分离的合作方式,所以restful方式可能更好接受一些,但是我不太会,就用网上的一种方式,通过restframework继承APIView来写接口,最后也是可以实现的。这里,我们需要用到阿里云短信验证码接口调用,这里以这个网页的python代码为例来写的https://www.cnblogs.com/qinyuanyuan/p/11452761.html 和https://www.cnblogs.com/hszstudypy/p/12721582.html#autoid-0-0-0,用这个代码需要有自己的阿里云账号,以及短信模板,短信代号,还要往里面充值才能使用,0.05元/条。
众所周知,每个网页调用api都要鉴权,这样可以防止xss攻击,将难度提升到impossible级别,这里我找了多个网页:
1.前后端常见的几种鉴权方式https://blog.csdn.net/wang839305939/article/details/78713124
2.Django中的JWT(Json Web Token认证机制)https://blog.csdn.net/dakengbi/article/details/92717351
3.Django REST framework 的JWT Token获取用户信息https://www.jianshu.com/p/3aa4a9625717
4.Django中的JWT(Json Web Token认证机制)https://blog.csdn.net/python_nice/article/details/81474794
5.JWT认证在Django中的简单实现https://www.jianshu.com/p/9f707289478b
6.django使用jwt
7.常见的鉴权方式,你真的不想知道吗https://www.jianshu.com/p/4a00c0c3bf1d
8.Python JWT 实战 https://www.jianshu.com/p/c091a5473e35
从上面可以看出,我选择的是jwt方式,为什么选择这个,根据多种鉴权方式对比,jwt符合简便易用,而且安全高效的原则。但是我的jwt实现方式是手动实现,通过调用python的jwt插件来实现,不便于使用,更加不安全,因为这样通过get方式可以get到jwt加密的payload相关,从而反向得到加密信息,所以一定要使用restframwork的jwt,这是最好的方式。这是我的代码,jwtloginView是首次登陆时调用的API,其中post方式是较为安全的递送param的方式,jwt的加密过程就在其中
class JwtLoginView(APIView):
def post(self, request, *args, **kwargs):
"""
@api {post} /api/firstLogin/ 首次登陆或token失效后登陆
@apiGroup Login
@apiName 首次登陆或token失效后登陆
@apiVersion 0.6.0
@apiParam {String} username 用户名(手机号)
@apiParam {String} password 验证码(密码)
@apiParamExample {json} 参数示例
{
"username":"12345678901"
"password":"1234"
}
@apiError {String} message 错误信息
@apiErrorExample {json} error-example
{
'message': '用户名或验证码错误'
}
@apiSuccess {String} token token
@apiSuccess {String} username 用户名(手机号)
@apiSuccess {String} last_send_code 用户上次获取验证码时间戳
@apiSuccess {Int} sumTimes 用户总学习时长
@apiSuccess {Int} courseTimes 课程总时长
@apiSuccess {Int} study_id 学习卡id
@apiSuccess {Int} last_log 最后一次学习课程学习时长
@apiSuccess {String} last_study 最后一次学习时间戳
@apiSuccess {Int} course_id 最后一次学习课程id
@apiSuccess {String} course_name 最后一次学习课程标题
@apiSuccess {String} description 最后一次学习课程描述
@apiSuccess {Int} length 最后一次学习课程课程长度
@apiSuccess {String} path 课程url路径
@apiSuccess {String} image 课程图片路径
@apiSuccessExample {json} success-example
{
"token":"eyJ0eXAiOiJqd3RfIiwiYWxnIjoiSFMyNTYifQ.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFsZXgiLCJleHAiOjE1OTQzMDM0OTh9.y5K-c7rkMQWLjlJTTZvMKIBNsSZ0s1aHrfywxR9ytkE",
"username": "12345678901",
"last_send_code": 20200710,
'study_id':1,
'last_log':15,
'last_study':20200717,
'course_id': 1,
'course_name':'math',
'description':'math',
'length':15,
'path':'http://example.com/123',
'image':'/static/xxx.jpg'
}
"""
username = request.data.get("username")
password = request.data.get("password")
user_obj = userProfile.objects.filter(username=username, password=password).exists()
if not user_obj:
return JsonResponse({'message': '用户名或验证码错误'})
user = userProfile.objects.get(username=username)
import jwt
import datetime
salt = "fadsf$@%#%#%gsfdgsdgfd"
headers = {
"typ": "jwt_",
"alg": "HS256",
}
payload = {
"user_id": user.id,
"username": username,
"exp": datetime.datetime.utcnow() + datetime.timedelta(days=65)
}
token = jwt.encode(payload=payload, key=salt, headers=headers).decode("utf-8")
studyLogExists = study_log.objects.filter(user = user.id)
courseQuery =course.objects.all()
courseTimes = 0
user.sumTimes = 0
user.save()
for m in courseQuery:
courseTimes = courseTimes + m.length
if len(courseQuery) != len(studyLogExists):
courseRange = len(courseQuery)
for i in range(courseRange):
studyLog = study_log.objects.get_or_create(
last_log = 0,
last_study = str(datetime.datetime.now().strftime("%Y%m%d%H%M%S")),
course = i+1,
user = user
)
return JsonResponse({
'token': token,
'username': user.username,
'last_send_code': user.last_send_code,
'sumTimes':user.sumTimes,
'courseTimes':courseTimes,
'last_log':0,
'course_id': None,
'course_name':None,
'description':None,
'length':None,
'path':None,
'image':None,
})
else:
studyLogAll = study_log.objects.filter(user=user.id)
studyLog = studyLogAll.order_by('-last_study').first()
courseGet = course.objects.get(course_id=studyLog.course)
sumTimes = 0
for s in studyLogAll:
sumTimes += s.last_log
user.sumTimes = sumTimes
user.save()
return JsonResponse({
'token': token,
'username': user.username,
'last_send_code': user.last_send_code,
'sumTimes':user.sumTimes,
'courseTimes':courseTimes,
'study_id':studyLog.study_id,
'last_log':studyLog.last_log,
'last_study':studyLog.last_study,
'course_id': courseGet.course_id,
'course_name':courseGet.course_name,
'description':courseGet.description,
'length':courseGet.length,
'path':courseGet.path,
'image':courseGet.image
})
接下来是解密过程,在JwtAlwaysLogin中,是含解密和登陆过程的一个apiview的实现
class JwtAlwaysView(APIView):
def get(self, request, *args, **kwargs):
"""
@api {get} /api/alwaysLogin/ 使用token登陆(14天)
@apiGroup Login
@apiName 使用token登陆(14天)
@apiVersion 0.6.0
@apiParam {String} token token
@apiParamExample {json} 参数示例
{
"token":"eyJ0eXAiOiJqd3RfIiwiYWxnIjoiSFMyNTYifQ.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFsZXgiLCJleHAiOjE1OTQzMDM0OTh9.y5K-c7rkMQWLjlJTTZvMKIBNsSZ0s1aHrfywxR9ytkE"
}
@apiError {String} msg 错误信息
@apiErrorExample {json} error-example:
HTTP/1.1 411 Not Found
{
'msg': 'token失效',
'msg': 'token认证失败',
"msg": "非法token"
}
@apiSuccess {String} username 用户名(手机号)
@apiSuccess {String} last_send_code 用户上次获取验证码时间戳
@apiSuccess {Int} sumTimes 用户总学习时长
@apiSuccess {Int} courseTimes 课程总时长
@apiSuccess {Int} study_id 学习卡id
@apiSuccess {Int} last_log 最后一次学习课程学习时长
@apiSuccess {String} last_study 最后一次学习时间戳
@apiSuccess {Int} course_id 最后一次学习课程id
@apiSuccess {String} course_name 最后一次学习课程标题
@apiSuccess {String} description 最后一次学习课程描述
@apiSuccess {Int} length 最后一次学习课程课程长度
@apiSuccess {String} path 课程url路径
@apiSuccess {String} image 课程图片路径
@apiSuccessExample {json} success-example
{
"username": "12345678901",
"last_send_code": 20200710,
'sumTimes':124,
'courseTimes':333,
'study_id':1,
'last_log':15,
'last_study':20200720153900,
'course_id': 1,
'course_name':'math',
'description':'math',
'length':15,
'path':'http://example.com/123',
'image':'/static/xxx.jpg'
}
"""
# 获取token并验证
token = request.GET.get("token")
import jwt
from jwt import exceptions
result = None
msg = None
salt = "fadsf$@%#%#%gsfdgsdgfd"
try:
result = jwt.decode(token, salt, True)
user_id = result['user_id']
user = userProfile.objects.get(id = user_id )
except exceptions.ExpiredSignatureError:
msg = "token失效"
except exceptions.DecodeError:
msg = "token认证失败"
except exceptions.InvalidTokenError:
msg = "非法token"
if not result:
return JsonResponse({"msg": msg})
studyLogAll = study_log.objects.filter(user=user.id)
studyLog = studyLogAll.order_by('-last_study').first()
courseGet = course.objects.get(course_id=studyLog.course)
courseQuery =course.objects.all()
courseTimes = 0
sumTimes = 0
for m in courseQuery:
courseTimes = courseTimes + m.length
for s in studyLogAll:
sumTimes += s.last_log
user.sumTimes = sumTimes
user.save()
return JsonResponse({
'username': user.username,
'last_send_code': user.last_send_code,
'sumTimes':user.sumTimes,
'courseTimes':courseTimes,
'study_id':studyLog.study_id,
'last_log':studyLog.last_log,
'last_study':studyLog.last_study,
'course_id': courseGet.course_id,
'course_name':courseGet.course_name,
'description':courseGet.description,
'length':courseGet.length,
'path':courseGet.path,
'image':courseGet.image
})
这里接口封装,参考Django封装交互接口https://blog.csdn.net/weixin_41827390/article/details/81286154,以及json文档相关信息https://www.runoob.com/python/python-json.html,通过(2)中的代码块,在最前面import JsonResPonse 最后return JsonResponse()就是返回的json格式信息,可以方便前端的获取及使用,一定要这样return才可以。
至于接口文档,推荐使用apidoc,apiDoc - 超简单的文档生成器https://zhuanlan.zhihu.com/p/83487114,参考这个写出来的api文档易于读取,且书写方便,代码及图片示例在下面.
"""
@api {get} /api/alwaysLogin/ 使用token登陆(14天)
@apiGroup Login
@apiName 使用token登陆(14天)
@apiVersion 0.6.0
@apiParam {String} token token
@apiParamExample {json} 参数示例
{
"token":"eyJ0eXAiOiJqd3RfIiwiYWxnIjoiSFMyNTYifQ.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFsZXgiLCJleHAiOjE1OTQzMDM0OTh9.y5K-c7rkMQWLjlJTTZvMKIBNsSZ0s1aHrfywxR9ytkE"
}
@apiError {String} msg 错误信息
@apiErrorExample {json} error-example:
HTTP/1.1 411 Not Found
{
'msg': 'token失效',
'msg': 'token认证失败',
"msg": "非法token"
}
@apiSuccess {String} username 用户名(手机号)
@apiSuccess {String} last_send_code 用户上次获取验证码时间戳
@apiSuccess {Int} sumTimes 用户总学习时长
@apiSuccess {Int} courseTimes 课程总时长
@apiSuccess {Int} study_id 学习卡id
@apiSuccess {Int} last_log 最后一次学习课程学习时长
@apiSuccess {String} last_study 最后一次学习时间戳
@apiSuccess {Int} course_id 最后一次学习课程id
@apiSuccess {String} course_name 最后一次学习课程标题
@apiSuccess {String} description 最后一次学习课程描述
@apiSuccess {Int} length 最后一次学习课程课程长度
@apiSuccess {String} path 课程url路径
@apiSuccess {String} image 课程图片路径
@apiSuccessExample {json} success-example
{
"username": "12345678901",
"last_send_code": 20200710,
'sumTimes':124,
'courseTimes':333,
'study_id':1,
'last_log':15,
'last_study':20200720153900,
'course_id': 1,
'course_name':'math',
'description':'math',
'length':15,
'path':'http://example.com/123',
'image':'/static/xxx.jpg'
}
"""
这个,具体参考代码,django的urls中的实现,通过urls.py一眼就能看出怎么弄。
from django.urls import path
from . import views
from . import serializers
urlpatterns = [
path('api/users/', views.UsersAPIVIew.as_view(),name='全部用户操作'),
path('api/user/', views.UserAPIView.as_view(),name='单个用户操作'),
path('api/code/',views.CodeAPIView.as_view(),name='验证码'),
# # 认证令牌
# path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
# # 刷新令牌
# path('api/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
# path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
# # path('api/login/', serializers.MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/firstLogin/',views.JwtLoginView.as_view(),name='首次登陆或token失效后登陆'),
path('api/alwaysLogin/',views.JwtAlwaysView.as_view(),name='再次登录'),
path('api/wxLogin/',views.wxLoginView.as_view(),name='微信登录')
]