欢迎来到我的博客!

django 获取用户上一次登录时间以及判断用户是否在线

Django Aaron 1 年,6 月 2544 0评论

最近在做邮件通知功能,需要判断用户是否在线,查看文档发现django的Auth.User有一个last_login字段,可以取得用户上一次登录的时间。但是这个方法有一个问题,它记录的是用户通过输入账号、密码登录的时间,如果用户从来没有logout来注销登录,或者浏览器已经记录登录的信息,每次都会自动登录的话,那么这个last_login字段就不会更新。因此通过这种方法获取的上一次登录时间不准确。

解决问题的大致思路是,通过用户发送request请求的时间,用户每法送一次请求,就更新一下其上一次活动的时间,如果在用户在一定时间内(比如30mins)没有发送新的request,则判断用户不在线。试想一下,如果每次都把这是时间记录在数据库,在很多用户在线的情况下,会对服务器造成很大的负担。因此我们把它们记录在缓存里,当用户发送两次请求的时间间隔超过设定时间,则把新的时间在数据库里更新一下。

项目地址:https://github.com/r26zhao/django-online-status

具体的实现过程如下:

开发环境

django 1.11, python3.4

新建一个app,叫做online_status

通过OneToOne的方式拓展用户模型, 用了记录用户的登录时间

app/models.py

import datetime
from django.utils import timezone
from django.db import models
from django.conf import settings
from django.core.cache import cache

# Create your models here.

class OnlineStatus(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    last_login = models.DateTimeField(default=timezone.now)

    class Meta:
        verbose_name = 'Online Status'
        verbose_name_plural = 'Online Status'
        ordering = ['-last_login']

    def __str__(self):
        return '%s last login at UTC %s' % (self.user.username, self.last_login.strftime('%Y/%m/%d %H:%M'))

    def get_last_active(self):
        pass

    def is_online(self):
        pass

ATUH_USER_MODEL 是settings里的变量,如果不设置的话,默认使用的事django auth的User模型

处理用户发送请求的时间,需要用到django的middlware中间件,关于middleware做一个简单介绍。 我们从浏览器发出一个请求 Request,得到一个响应后的内容 HttpResponse ,这个请求传递到 Django的过程如下:

django middleware

在项目的settings.py里有MIDDLEWARE这个变量,里面有很多中间件, 每一个请求都是先通过中间件中的 process_request 函数,这个函数返回 None 或者 HttpResponse 对象,如果返回前者,继续处理其它中间件,如果返回一个 HttpResponse,就处理中止,返回到网页上。 更有关于middleware的详细介绍,可以去自强学堂里查看。

app/middleware.py

import datetime
from django.utils import timezone
from django.core.cache import cache
from django.conf import settings
from .models import OnlineStatus
from django.utils.deprecation import MiddlewareMixin

class OnlineStatusMiddleware(MiddlewareMixin):

    def process_request(self, request):
        if request.user.is_authenticated():
            cache_key = '%s_last_login' % request.user.username
            now =timezone.now()
            # 用户是第一次登录、或者是缓存过期、或者是服务器重启导致缓存消失
            if not cache.get(cache_key):
                # print('#### cache not found #####')
                obj, created = OnlineStatus.objects.get_or_create(user=request.user)
                if not created:
                    # print("#### login before #####")
                    obj.last_login = now
                    obj.save()
                cache.set(cache_key, now, settings.USER_LAST_LOGIN_EXPIRE)
            else:
                # print("##### cache found ######")
                limit = now - datetime.timedelta(seconds=settings.USER_ONLINE_TIMEOUT)
                # 距离上一次发送request请求的时间 超过了TIMEOUT,更新上一次login的时间
                if cache.get(cache_key) < limit:
                    # print("#### renew login #####")
                    obj = OnlineStatus.objects.get(user=request.user)
                    obj.last_login = now
                    obj.save()
                cache.set(cache_key, now, settings.USER_LAST_LOGIN_EXPIRE)
        return None

settings.USER_LAST_LOGIN_EXPIRE 是我们需要在settings.py里是设置的缓存有效时间,单位是秒

settings.USER_ONLINE_TIMEOUT 是用户发送请求的时间间隔,超过这个时间,则用户是不在线状态,下一次发送请求时,会更新数据库。

如果用户是第一次登录,那么cache里是没有记录他上一次request的时间,对应的onlinestatus也没有实例。

如果用户在设置的时间内一直有发送新的request,那么用户就是一直在线的状态,数据库记录的就是用户登录的时间,无须更新,只需在cache里更新用户发送请求的时间。

project/settings.py

INSTALLED_APPS = [
    # 把app加入到这里
    'online_status',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    ·····
    ·····
    ·····
    # 在最后加上我们写的middleware
    'online_status.middleware.OnlineStatusMiddleware',
]

# User模型根据自己的情况更改
AUTH_USER_MODEL = 'blog.User'
# 5分钟
USER_ONLINE_TIMEOUT = 300 
# 7天
USER_LAST_LOGIN_EXPIRE = 60 * 60 * 24 * 7

在models里还有两个方法 get_last_active (返回用户上一次请求的时间) 和 is_online(返回用户是否在线)

app/models.py

class OnlineStatus(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    last_login = models.DateTimeField(default=timezone.now)

    class Meta:
        verbose_name = 'Online Status'
        verbose_name_plural = 'Online Status'
        ordering = ['-last_login']

    def __str__(self):
        return '%s last login at UTC %s' % (self.user.username, self.last_login.strftime('%Y/%m/%d %H:%M'))

    def get_last_active(self):
        cache_key = '%s_last_login' % self.user.username
        # 如果缓存过期,从数据库获取last_login,并存到缓存
        if not cache.get(cache_key):
            # print("####### -- cache not found")
            cache.set(cache_key, self.last_login, settings.USER_LAST_LOGIN_EXPIRE)
        return cache.get(cache_key)

    def is_online(self):
        now = timezone.now()
        if self.get_last_active() < now - datetime.timedelta(seconds=settings.USER_ONLINE_TIMEOUT):
            return False
        return True

使用缓存的好处是读取速度快,减少数据库读写次数,缺点是如果服务器重启的话,缓存就会消失。因此如果无法从缓存获取用户登录的时间的话,就从数据库里读取,然后保存到缓存里。

在settings.py里有设置缓存的变量,不设置的话,默认使用的backend是LocMem本地缓存,可以使用redis作为缓存来替代之。django博客开发:redis的安装以及用django-redis作缓存

评论 0人参与 | 0条评论