使用token登陆、前后端分离下的用户登陆

基于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 }}&nbsp;&nbsp;
            <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>&nbsp;&nbsp;
                    (<strong>{{ teacher.good_count }}</strong>)
                    &nbsp;&nbsp;&nbsp;&nbsp;
                    <a href="" @click.prevent="vote(teacher, false)">差评</a>&nbsp;&nbsp;
                    (<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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

像风一样的男人@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值