Flask-爱家租房项目ihome-03-登录

登录/登出

后端逻辑编写

登录与登出都是对session这个资源的修改,因此url的名词资源就可以设置为sessions,编辑ihome/api_1_0/users.py的视图文件

# ihome/api_1_0/users.py
from flask import request, jsonify, current_app, session

@api.route('/sessions', methods=['GET', 'POST', 'DELETE'])
def handle_sessions():
    if request.method == 'POST':
        # 登录
        return login()
    elif request.method == 'DELETE':
        # 登出
        return logout()
    else:
        # 获取当前登录用户
        return get_login_user()

三个请求方式分别对应:登录POST,登出DELETE,获取当前登录用户GET,分别编写对应的三个函数

# ihome/api_1_0/users.py
def login():
    """登录"""
    # 接收数据
    post_dict = request.get_json()
    if not post_dict:
        return jsonify(errno=RET.PARAMERR, errmsg='参数不能为空')
    # 提取数据
    phone = post_dict.get('phone')
    password = post_dict.get('password')

    # 校验数据
    if not all([phone, password]):
        return jsonify(errno=RET.PARAMERR, errmsg='参数不完整')

    # 限制同一手机号一分钟内只能登陆5次
    his_login_key = f'his_login_{phone}'
    try:
        # incr增加记录的次数,若不存在该key则会创建并默认值为1
        count = redis_connect.incr(his_login_key)
        if int(count) > constants.USER_LOGIN_COUNT:
            return jsonify(errno=RET.REQERR, errmsg='一分钟内只能登录5次')
        redis_connect.expire(his_login_key, constants.USER_LOGIN_EXPIRES)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='获取登录记录异常')

    # 校验是否已经登录过
    if phone in session.values():
        return jsonify(errno=RET.OK)

    # 校验手机号是否注册过
    try:
        user = Users.query.filter_by(phone=phone).first()
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='获取用户信息异常')
    # 将用户名错误和密码错误放在一起返回,不要返回地太细节
    if not user or not user.check_password_hash(password):
        return jsonify(errno=RET.PWDERR, errmsg='用户名或密码不正确')

    # 记录登录session
    session['user_id'] = user.id
    session['name'] = user.name
    session['phone'] = phone

    return jsonify(errno=RET.OK)

def logout():
    """登出"""
    # 判断是否登录过
    if not session.get('user_id'):
        return jsonify(errno=RET.NODATA, errmsg='用户未登录')
    # 清除session
    session.pop('user_id')
    session.pop('name')
    session.pop('phone')

    return jsonify(errno=RET.OK)

def get_login_user():
    """获取登录用户"""
    # 判断是否登录过
    name = session.get('name')
    if name:
        return jsonify(errno=RET.OK, data={'name': name})
    return jsonify(errno=RET.SESSIONERR, errmsg='用户未登录')

前端逻辑编写-登录

在登录的js文件中添加登录按钮的逻辑,同样不使用form自带的表单提交功能,自定义表单的提交与返回操作

//login.js
$(document).ready(function() {
    ......
    $(".form-login").submit(function(e){
        //阻止默认的表单提交行为
        e.preventDefault();
        mobile = $("#mobile").val();
        passwd = $("#password").val();
        if (!mobile) {
            $("#mobile-err span").html("请填写正确的手机号!");
            $("#mobile-err").show();
            return;
        } 
        if (!passwd) {
            $("#password-err span").html("请填写密码!");
            $("#password-err").show();
            return;
        }
        //发送ajax请求
        //js对象数据转换为json格式
        var postData = JSON.stringify({phone:mobile, password:passwd});
        $.ajax({
            url: 'api/v1.0/sessions',
            type: 'post',
            data: postData,
            contentType: 'application/json',
            headers: {'X-CSRFToken': getCookie('csrf_token')},
            dataType: 'json',
            success: function (resp) {
                if(resp.errno == '0'){
                    //登录成功,跳转到首页
                    location.href='/index.html';
                }else{
                    //登录失败
                    alert(resp.errmsg);
                }
            }
        })
    });
})

登录成功后,进入主页页面,在主页的右上角显示登录的用户名,编辑主页js文件,页面一加载就调用接口查询登录的用户

//index.js
$(document).ready(function(){
    //发送ajax获取登录信息
    $.get("/api/v1.0/sessions", function (resp) {
        if (resp.errno == '0'){
            //已登录,显示登录用户名
            $(".user-info>.user-name").html(resp.data.name)
            $(".user-info").show()
        }else{
            //未登录,显示登录注册框
            $(".top-bar>.register-login").show();
        }
    }, "json")

前端逻辑编写-登出

点击主页的用户名,来到用户信息页面,最下面的'退出'按钮即为登出功能,设置该按钮的点击事件为logout()编辑该界面的js

//my.js
function logout() {
    $.ajax({
        url: 'api/v1.0/sessions',
        type: 'delete',
        dataType: 'json',
        headers: {
            'X-CSRFToken': getCookies('csrf_token')
        },
        success: function (resp) {
            if (resp.errno == '0'){
                location.href='/'
            }else{
                alert(resp.errmsg)
            }
        }
    })
}

登录验证装饰器

有些页面是需要用户登录才能访问的,比如用户信息页面,因此我们需要在这些页面的视图函数上添加登录的验证,这里使用装饰器的方式,在ihome/utils/commons.py文件中添加该登录装饰器

# ihome/utils/commons.py
from flask import session, jsonify, g
from .response_codes import RET
import functools

def login_required(view_func):
    """登录验证装饰器"""
    @functools.wraps(view_func)
    def wrapper(*args, **kwargs):
        # 判断登录状态
        user_id = session.get('user_id')
        if not user_id:
            # 没有登录
            return jsonify(errno=RET.SESSIONERR, errmsg='用户未登录')
        # 已登录,将user保存在g对象中
        try:
            user = Users.query.get(user_id)
            g.user = user
            print(g.user)
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg='获取用户异常')
        # 执行视图函数
        return view_func(*args, **kwargs)
    return wrapper

注:

  1. 在session中若未获取到登录用户,则返回错误信息,用户未登录,若已登录,则执行下面的被装饰的视图函数

  2. 在装饰器内部添加@functools.wraps(view_func)能使被装饰函数的文件字符串等属性不会改变

  3. 在可以讲获取到的一些信息存在g变量中,这样在视图函数中就不需要重复查找, 这里将当前登录的User对象存在g对象中

原文地址:https://www.cnblogs.com/gcxblogs/p/13527723.html