基于session实现用户跟踪的方式需要服务器保存session对象,在做水平扩展增加新的服务器节点时,需要复制和同步session对象,这显然是非常麻烦的。解决这个问题有两种方案,一种是架设缓存服务器(如Redis),让多个服务器节点共享缓存服务并将session对象直接置于缓存服务器中;另一方式放弃基于session的用户跟踪,使用基于token的用户跟踪。
修改投票项目,把学科页面和老师页面做为数据接口
#urls.py中路由如下
from django.contrib import admin
from django.urls import path
from polls.views import show_subjects, show_teachers, /
praise_or_criticize, login, register, get_captcha, show_index
urlpatterns = [
path('admin/', admin.site.urls),
path('', show_index),
path('api/subjects/', show_subjects),
path('api/teachers/', show_teachers),
path('praise/', praise_or_criticize),
path('criticize/', praise_or_criticize),
path('login/', login),
path('register/', register),
path('captcha/', get_captcha),
]
#searializers.py中
from rest_framework import serializers
from polls.models import Subject, Teacher
class SubjectSerializer(serializers.ModelSerializer):
class Meta:
model = Subject
fields = '__all__'
class TeacherSerializer(serializers.ModelSerializer):
class Meta:
model = Teacher
fields = '__all__'
class SubjectSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Subject
fields = ('no', 'name')
#views.py中
import datetime
import jwt
from django.conf import settings
from django.db import DatabaseError
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import redirect
from django.utils import timezone
from jwt import InvalidTokenError
from rest_framework.decorators import api_view
from rest_framework.response import Response
from polls.captcha import Captcha
from polls.models import Subject, Teacher, User
from polls.serializers import SubjectSerializer, TeacherSerializer, SubjectSimpleSerializer
from polls.utils import gen_code, gen_md5_digest
def show_index(requests: HttpRequest) -> HttpResponse:
return redirect('/static/html/subjects.html') #重定向首页
@api_view(('GET', ))
def show_subjects(request: HttpRequest) -> HttpResponse:
subjects = Subject.objects.all().order_by('no')
serializer = SubjectSerializer(subjects, many=True) #学科的api接口
return Response(serializer.data)
@api_view(('GET', ))
def show_teachers(request: HttpRequest) -> HttpResponse:
try:
sno = int(request.GET.get('sno'))
subject = Subject.objects.only('name').get(no=sno) #老师的api接口
teachers = Teacher.objects.filter(sno=subject).defer('sno').order_by('no')
subject_seri = SubjectSimpleSerializer(subject)
teacher_seri = TeacherSerializer(teachers, many=True)
return Response({'subject': subject_seri.data, 'teachers': teacher_seri.data})
except (TypeError, ValueError, Subject.DoesNotExist):
return Response(status=404)
============================================================================================
def praise_or_criticize(request: HttpRequest) -> HttpResponse: #Ajax实现投票功能
token = request.META.get('HTTP_TOKEN') #请求中拿token
if token: #如果拿到了
try:
jwt.decode(token, settings.SECRET_KEY) #验证token是否有效或者已经过期
tno = int(request.GET.get('tno'))
teacher = Teacher.objects.get(no=tno)
if request.path.startswith('/praise/'):
teacher.good_count += 1
count = teacher.good_count
else:
teacher.bad_count += 1
count = teacher.bad_count
teacher.save()
data = {'code': 20000, 'mesg': '投票成功', 'count': count}
except (ValueError, Teacher.DoesNotExist):
data = {'code': 20001, 'mesg': '投票失败'}
except InvalidTokenError:
data = {'code': 20002, 'mesg': '登录已过期,请重新登录'}
else:
data = {'code': 20002, 'mesg': '请先登录'}
return JsonResponse(data)
def get_captcha(request: HttpRequest) -> HttpResponse:
captcha_text = gen_code()
request.session['captcha'] = captcha_text
image_data = Captcha.instance().generate(captcha_text)
return HttpResponse(image_data, content_type='image/png')
========================================================================
#使用token实现登陆
@api_view(('POST', ))
def login(request: HttpRequest) -> HttpResponse:
username = request.data.get('username') #data方法拿传过来的数据
password = request.data.get('password')
if username and password: #如果数据不为空
password = gen_md5_digest(password)
user = User.objects.filter(username=username, password=password).first() #数据库查找资源,
if user: #如果查询到了
payload = {
'exp': timezone.now() + datetime.timedelta(days=1), #设置token过期时间为1天
'userid': user.no
}
token = jwt.encode(payload, settings.SECRET_KEY).decode() #加盐
return Response({'code': 10000, 'token': token, 'username': user.username})
else:
hint = '用户名或密码错误'
else:
hint = '请输入有效的用户名和密码'
return Response({'code': 10001, 'mesg': hint})
@api_view(('POST', ))
def register(request: HttpRequest) -> HttpResponse: #注册
agreement = request.data.get('agreement')
if agreement:
username = request.data.get('username') #请求体中拿数据
password = request.data.get('password')
tel = request.data.get('tel')
if username and password and tel:
try:
password = gen_md5_digest(password)
user = User(username=username, password=password, tel=tel)
user.save()
return Response({'code': 30000, 'mesg': '注册成功'})
except DatabaseError:
hint = '注册失败,请尝试更换用户名'
else:
hint = '请输入有效的注册信息'
else:
hint = '请勾选同意网站用户协议及隐私政策'
return Response({'code': 30001, 'mesg': hint})
static/html中
#login.html中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
<style>
#container {
width: 520px;
margin: 10px auto;
}
.input {
margin: 20px 0;
width: 460px;
height: 40px;
}
.input>label {
display: inline-block;
width: 140px;
text-align: right;
}
form+div {
margin-top: 20px;
}
form+div>a {
text-decoration: none;
color: darkcyan;
font-size: 1.2em;
}
.button {
width: 500px;
text-align: center;
margin-top: 20px;
}
.hint {
color: red;
font-size: 14px;
}
</style>
</head>
<body>
<div id="container">
<h1>用户登录</h1>
<hr>
<p class="hint">{{ hint }}</p>
<form action="" method="post">
<fieldset>
<legend>用户信息</legend>
<div class="input">
<label>用户名:</label>
<input type="text" v-model="username">
</div>
<div class="input">
<label>密码:</label>
<input type="password" v-model="password">
</div>
</fieldset>
<div class="button">
<input type="submit" value="登录" @click.prevent="login()">
<input type="reset" value="重置">
</div>
</form>
<div>
<a href="/">返回首页</a>
<a href="/static/html/register.html">注册新用户</a>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js"></script>
<script>
let app = new Vue({
el: '#container',
data: {
hint: '',
username: '',
password: '',
},
methods: {
login() {
fetch('/login/', {
method: 'post',
body: JSON.stringify({
'username': this.username,
'password': this.password
}),
headers: {'content-type': 'application/json'}
}).then(resp => resp.json()).then(json => {
if (json.code === 10000) {
localStorage.token = json.token
localStorage.username = json.username
location.href = '/'
} else {
this.username = ''
this.password = ''
this.hint = json.mesg
}
})
}
}
})
</script>
</body>
</html>
--------------------------------------------------------
#register.html中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
<style>
#container {
width: 520px;
margin: 20px auto;
}
.input {
margin: 20px 20px;
width: 450px;
height: 40px;
}
.input>label {
display: inline-block;
width: 150px;
text-align: right;
}
a {
text-decoration: none;
color: darkcyan;
}
form+div {
margin-top: 20px;
}
form+div>a {
font-size: 1.2em;
}
.button {
width: 520px;
margin-top: 10px;
text-align: center;
}
.button>div {
text-align: left;
margin: 10px 10px;
}
.hint {
color: red;
font-size: 14px;
}
</style>
</head>
<body>
<div id="container">
<h1>用户注册</h1>
<hr>
<p class="hint">{{ hint }}</p>
<form action="" method="post">
<fieldset>
<legend>用户信息</legend>
<div class="input">
<label>用户名:</label>
<input type="text" v-model="username">
</div>
<div class="input">
<label>密码:</label>
<input type="password" v-model="password">
</div>
<div class="input mobile">
<label>手机号:</label>
<input type="tel" v-model="tel">
<input type="button" id="sendBtn" value="发送验证码">
</div>
<div class="input">
<label>验证码:</label>
<input type="text" v-model="mobilecode">
</div>
</fieldset>
<div class="button">
<input type="submit" value="注册" @click.prevent="register()">
<input type="reset" value="重置">
<div>
<input type="checkbox" v-model="agreement">
我已经同意网站<a href="">用户协议及隐私政策</a>
</div>
</div>
</form>
<div>
<a href="/">返回首页</a>
<a href="/static/html/login.html">返回登录</a>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js"></script>
<script>
let app = new Vue({
el: '#container',
data: {
hint: '',
username: '',
password: '',
tel: '',
mobilecode: '',
agreement: ''
},
methods: {
register() {
fetch('/register/', {
method: 'post',
body: JSON.stringify({
username: this.username,
password: this.password,
tel: this.tel,
agreement: this.agreement
}),
headers: {'content-type': 'application/json'}
}).then(resp => resp.json()).then(json => {
if (json.code === 30000) {
location.href = '/static/html/login.html'
} else {
this.hint = json.mesg
this.username = ''
this.password = ''
this.tel = ''
}
})
}
}
})
</script>
</body>
</html>
----------------------------------------------------------
#subjects.html中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>学科信息</title>
<style>
#container {
width: 80%;
margin: 10px auto;
}
.user {
float: right;
margin-right: 10px;
}
.user>a {
margin-right: 10px;
}
#main>dl>dt {
font-size: 1.5em;
font-weight: bold;
}
#main>dl>dd {
font-size: 1.2em;
}
a {
text-decoration: none;
color: darkcyan;
}
</style>
</head>
<body>
<div id="container">
<div class="user">
<a v-if="!localStorage.token" href="/static/html/login.html">用户登录</a>
{{ username }}
<a v-if="!!localStorage.token" href="" @click.prevent="logout()">退出登录</a>
<a href="/static/html/register.html">快速注册</a>
</div>
<h1>扣丁学堂所有学科</h1>
<hr>
<div id="main" v-loading.fullscreen.lock="loading">
<dl v-for="subject in subjects">
<dt>
<a :href="'/static/html/teachers.html?sno=' + subject.no">{{ subject.name }}</a>
<img v-if="subject.is_hot" src="/static/images/hot-icon-small.png">
</dt>
<dd>{{ subject.intro }}</dd>
</dl>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js"></script>
<script>
let app = new Vue({
el: '#container',
data: {
subjects: [],
loading: true,
username: localStorage.username
},
created() {
fetch('/api/subjects/')
.then(resp => resp.json())
.then(json => {
this.loading = false
this.subjects = json
})
},
methods: {
logout() {
delete localStorage.token
delete localStorage.username
this.username = ''
}
}
})
</script>
</body>
</html>
------------------------------------------------------------
#teachers.html中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>老师信息</title>
<style>
#container {
width: 80%;
margin: 10px auto;
}
.teacher {
width: 100%;
margin: 0 auto;
padding: 10px 0;
border-bottom: 1px dashed gray;
overflow: auto;
}
.teacher>div {
float: left;
}
.photo {
height: 140px;
border-radius: 75px;
overflow: hidden;
margin-left: 20px;
}
.info {
width: 75%;
margin-left: 30px;
}
.info div {
clear: both;
margin: 5px 10px;
}
.info span {
margin-right: 25px;
}
a {
text-decoration: none;
color: darkcyan;
}
</style>
</head>
<body>
<div id="container" v-loading.fullscreen.lock="loading">
<h1>{{ subject.name }}学科的老师信息</h1>
<hr>
<h2 v-if="teachers.length == 0">暂无该学科老师信息</h2>
<div class="teacher" v-for="teacher in teachers">
<div class="photo">
<img :src="'/static/images/' + teacher.photo" height="140" alt="">
</div>
<div class="info">
<div>
<span><strong>姓名:{{ teacher.name }}</strong></span>
<span>性别:{{ teacher.sex | maleOrFemale }}</span>
<span>出生日期:{{ teacher.birth }}</span>
</div>
<div class="intro">{{ teacher.intro }}</div>
<div class="comment">
<a href="" @click.prevent="vote(teacher, true)">好评</a>
(<strong>{{ teacher.good_count }}</strong>)
<a href="" @click.prevent="vote(teacher, false)">差评</a>
(<strong>{{ teacher.bad_count }}</strong>)
</div>
</div>
</div>
<a href="/">返回首页</a>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js"></script>
<script>
let app = new Vue({
el: '#container',
data: {
subject: null,
teachers: [],
loading: true
},
created() {
fetch('/api/teachers/' + location.search)
.then(resp => resp.json())
.then(json => {
this.loading = false
this.subject = json.subject
this.teachers = json.teachers
})
},
filters: {
maleOrFemale(sex) {
return sex? '男': '女'
}
},
methods: {
vote(teacher, isPraise) {
let url = (isPraise? '/praise/?tno=' : '/criticize/?tno=') + teacher.no
fetch(url, {
headers: {
"token": localStorage.token
}
})
.then(resp => resp.json())
.then(json => {
if (json.code === 20000) {
if (isPraise) {
teacher.good_count = json.count
} else {
teacher.bad_count = json.count
}
} else {
alert(json.mesg)
if (json.code === 20002) {
location.href = '/static/html/login.html'
}
}
})
}
}
})
</script>
</body>
</html>