You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DRF项目中request_finished信号获取用户实例实现最后请求记录

为什么request_finished信号拿不到用户实例

你之前的判断是完全正确的:request_finished信号在设计上本身就不会绑定当前请求的用户对象,和user_logged_in的触发逻辑有本质区别:

  • user_logged_in是登录流程定向触发的信号,Django在触发时会主动把流程关联的request、user实例作为参数传给所有注册的回调,所以可以直接在函数入参里拿到用户。
  • request_finished是整个请求流程完成时触发的全局信号,Django触发这个信号时只会传递sender参数,从源码层面就没有设计传递当前请求上下文的逻辑,自然没法直接从入参里获取用户。
具体实现方案

结合你使用DRF的开发场景,可通过「兼容协程的本地上下文+中间件存用户+信号读上下文」的方案实现,不会和DRF的认证流程冲突,也不会跨请求串数据:

步骤1:搭建可隔离的请求上下文

直接用Django官方生态的asgiref.local.Local做上下文存储,兼容线程、协程模型,不会出现传统线程本地存储在ASGI部署下的串数据问题:

# core/context.py
from asgiref.local import Local

# 初始化本地上下文存储实例
_request_context = Local()

def set_current_auth_user(user):
    """把当前请求的认证用户存到上下文"""
    _request_context.auth_user = user

def get_current_auth_user():
    """从上下文取出当前请求的认证用户"""
    return getattr(_request_context, "auth_user", None)

def clear_current_auth_user():
    """用完之后清理上下文,避免残留"""
    if hasattr(_request_context, "auth_user"):
        del _request_context.auth_user

步骤2:写轻量中间件存用户信息

因为DRF的Token/JWT认证是在视图层完成的,Django内置的认证中间件执行完成时通常还没解析出真实用户,所以要在视图逻辑全部执行完之后,再把用户存到上下文中:

# core/middleware.py
from .context import set_current_auth_user

class UserRequestTrackMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # 先执行完整的请求流程:经过所有内层中间件、DRF认证、视图逻辑
        response = self.get_response(request)
        # 流程走完后拿到的request.user是经过DRF解析的真实登录用户
        current_user = getattr(request, "user", None)
        if current_user and current_user.is_authenticated:
            set_current_auth_user(current_user)
        return response

把这个中间件注册到settings.MIDDLEWARE的最开头,保证它是最外层的请求处理入口:

MIDDLEWARE = [
    "core.middleware.UserRequestTrackMiddleware",
    # 下面是Django和DRF默认的其他中间件...
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    ...
]

步骤3:在request_finished信号里读写上下文完成持久化

现在再改造你的信号回调,就可以拿到真实用户实例了:

from django.dispatch import receiver
from django.core.signals import request_finished
from django.utils import timezone
from .context import get_current_auth_user, clear_current_auth_user

@receiver(request_finished)
def last_user_request(sender, **kwargs):
    current_user = get_current_auth_user()
    # 没拿到登录用户就直接跳过,避免处理匿名请求
    if not current_user:
        return
    # 直接更新用户表的持久化字段
    current_user.last_request_at = timezone.now()
    # 可以按需存其他请求元数据:比如最后请求路径、IP、接口方法
    current_user.last_request_path = request.path
    current_user.save(update_fields=["last_request_at", "last_request_path"])
    # 用完清理上下文,避免残留
    clear_current_auth_user()
避坑提醒
  • 不要依赖Django默认的AuthenticationMiddleware阶段就拿到DRF解析的用户:DRF的认证逻辑是独立的,只有视图执行过认证流程后,request上的user才会被正确赋值,所以必须在中间件的get_response返回之后再读取用户,否则会一直拿到匿名用户。
  • 信号回调里不要做重操作:如果要写入的请求日志、统计数据量很大,不要直接在信号里同步写数据库,把写入逻辑丢到异步任务队列执行,避免拖慢接口返回速度。
  • 上下文必须主动清理:如果不做清理,遇到长连接、连接复用的场景,可能会出现下一个请求读到上一个请求残留用户的问题。

补充:其实这个场景完全可以不依赖request_finished信号,直接在刚才的中间件__call__方法里拿到response之后,执行数据库更新逻辑即可,时机和request_finished触发的时机几乎完全一致,代码更短,也没有信号传递的额外开销。


内容的提问来源于stack exchange,提问作者koko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.29 21:45:04