实现登录

虽然 app.py 运行方式从之前的 app.run 改成了 socketio.run,但是不影响 Flask 传统的开发模式,如渲染模板、处理 cookie 和 session 等。socketio.run 在保留了 Flask 传统功能以外,还增加了 WebSocket 支持。因此,登录功能我们依然可以使用 HTTP 协议的方式。在 app.py 文件中添加以下代码。

class ResultCode:
    OK = 200
    ERROR_PARAMS = 400
    ERROR_SERVER = 500

def result(code=ResultCode.OK, data=None, message=""):
    return {"code": code, "data": data or {}, "message": message}

@app.route("/login", methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template("login.html")
    else:
        username = request.form.get('username')
        if not username:
            return result(ResultCode.ERROR_PARAMS, message="请输入用户名")
        elif UserManager.has_user(username):
            return result(ResultCode.ERROR_PARAMS, message="此用户名已存在")

        session['username'] = username
        return result()

上述代码中,我们实现了登录 URL 与视图。如果用 GET 请求,则返回登录模板;如果用 POST 请求,则执行登录操作。这里我们简化了登录过程,用户只要输入用户名即可。如果条件都符合,则把用户名存储在 session 中。为了让代码更加规范,我们添加了返回状态码的 ResultCode 类,以及返回 JSON 格式的 result 函数。并且我们用了 UserManager 类来管理当前登录的用户,UserManager 类的代码如下。

image 2025 01 22 18 18 30 118
image 2025 01 22 18 18 38 943

上述代码中,定义了两个类属性,分别为 __instance_users,其中 __instance 是用于保存单例对象的,_users 用来保存所有用户信息。通过重写 __new__ 方法使得 UserManager 仅能被创建一次,也就是单例对象。接着定义了一系列的类方法,分别为添加用户(add_user)、移除用户(remove_user)、获取用户(get_user)、判断用户是否存在(has_user)、获取当前用户(get_current_user)以及获取所有用户的用户名(all_username)。这些类方法可使后续处理即时消息变得更加简单。

此时在浏览器中访问 http://127.0.0.1:5000/login ,可以看到如图 10-1 所示的效果。

在后续的请求中,为了保证用户必须在登录后才能进行访问,我们实现一个 login_required 装饰器,代码如下。

from functools import wraps
from flask import session, redirect

def login_required(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if not session.get("username"):
            return redirect("/login")
        else:
            return func(*args, **kwargs)
    return wrapper
image 2025 01 22 18 20 39 147
Figure 1. 图10-1 在线即时聊天项目登录界面

上述装饰器代码中,我们判断 session 中是否包含 username,如果没有,则重定向到登录页面,否则就正常执行被装饰的函数。