第四十一章:WebSocket与BeatServer与JWT

1.需求

采用 django-channels 来实现 websocket 的消息实时通讯

2.介绍Django Channels

官方文档链接:Django-Channels

Channels 包装了 Django 的原生异步视图支持,允许 Django 项目不仅可以处理 HTTP,还可以处理需要长时间连接的协议 - WebSockets、MQTT、聊天机器人、业余无线电等。

它在实现这一点的同时保留了 Django 的同步和易用性,让您可以选择编写代码的方式 - 以 Django 视图的样式同步、完全异步或两者混合。除此之外,它还提供与 Django 的身份验证系统、会话系统等的集成,使您可以比以往更轻松地将仅 HTTP 项目扩展到其他协议。

Channels 还将这个事件驱动架构与通道层捆绑在一起,该系统允许您轻松地在进程之间进行通信,并将您的项目分成不同的进程。

如果您尚未安装 Channels,您可能需要先阅读安装 以进行安装。本介绍不是直接教程,但如果您愿意,您应该能够使用它来跟进并对现有的 Django 项目进行更改。

3.安装与注册

3.1 安装与配置

完成安装后,将 daphne、channels 添加到 INSTALLED_APPS 设置的开头:

pip install channels daphne

3.2 注册与启用频道层

settings.py 配置

配置 APP、入口路径、redis、通道层

# 注册 APP
INSTALLED_APPS = (
    'daphne',
    'channels',
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.sites",
    ...
)

# 新增配置:指定 ASGI 应用程序的入口
ASGI_APPLICATION = 'apps.ws.routing.application'

# redis 配置
REDIS_SETTING = {
    "redis_host": os.environ.get("REDIS_HOST", "123.57.3.49"),
    "redis_port": os.environ.get("REDIS_PORT", 6379),
    "redis_password": os.environ.get("REDIS_PASSWORD", 'hukai.2026'),
}

# 启用频道层
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [
                f"redis://:{REDIS_SETTING['redis_password']}@{REDIS_SETTING['redis_host']}:{REDIS_SETTING['redis_port']}/0"],
            "capacity": 1000,  # default 100
            "expiry": 60,  # default 60
        },
    },
}

asgi.py 配置

注意:在 asgi.py 文件中修改 ASGI 应用实例入口,使用 get_default_application 替换原来的 get_asgi_application,这时 http 接口会从 ASGI_APPLICATION = 'apps.ws.routing.application' 入口函数进入

import os
import django
# from django.core.asgi import get_asgi_application
from channels.routing import get_default_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
django.setup()
application = get_default_application()

# application = get_asgi_application()

3.3 创建 ws app

# 注意在 INSTALLED_APPS 注册 APP
python manage.py startapp ws

4.实例

# 相关包
PyJWT: 安装 djangorestframework_simplejwt 时会自动安装该依赖
channels-redis
django_redis

项目地址:https://gitee.com/ysging/ws-django-channels.git

案例介绍:采用 django-channels 来实现 websocket 的消息实时通讯,BeatServer 实现通道层(Channel)定时任务发送,djangorestframework-simplejwt 生成 token,用于 websocket 的连接认证。

实现逻辑:通过 Django Channels 的 ASGI 路由配置,用于处理多种协议类型的请求(WebSocket、Channel 消息、HTTP),通过 ProtocolTypeRouter 根据协议类型分发请求到不同的处理模块。

路由设计:
application = ProtocolTypeRouter(
    {
        "websocket": JWTAuthMiddlewareStack(
            URLRouter([
                re_path(r"^ws/$", AsyncWSConsumer.as_asgi()),
            ])
        ),
        # 通道层,可以不使用
        "channel": ChannelNameRouter({
            "periodic_sending": PeriodicConsumer.as_asgi(),
        }),
        'http': get_asgi_application()
    }
)

功能拆解:
## WebSocket 配置解析
"websocket": JWTAuthMiddlewareStack(
 	  URLRouter([
        re_path(r"^ws/$", AsyncWSConsumer.as_asgi()),
    ])
)
# JWTAuthMiddlewareStack:自定义中间件栈,用于处理 JWT 认证(通常封装认证逻辑)
# URLRouter:根据 URL 路径路由 WebSocket 连接到对应的 Consumer
# re_path(r"^ws/$", ...):匹配 WebSocket 连接路径(如 ws://127.0.0.1:8002/ws/)
# AsyncWSConsumer:处理 WebSocket 消息的异步消费者类(需实现 connect(), receive() 等方法)

## Channel 消息配置解析
"channel": ChannelNameRouter({
    "periodic_sending": PeriodicConsumer.as_asgi(),
}),
# ChannelNameRouter:根据通道名称(Channel Name)路由后台消息
# "periodic_sending":通道名称标识符(用于区分不同类型的后台任务)
# PeriodicConsumer:处理特定通道消息的消费者(通常用于定时任务或异步事件)

## HTTP 配置解析:就是原 asgi.py 中的 application 换了入口位置
'http': get_asgi_application()
# get_asgi_application():返回 Django 默认的 ASGI 应用,用于处理传统 HTTP 请求(兼容 Django 视图)

4.1 执行循序

# 以继承 AsyncJsonWebsocketConsumer 为例
from channels.generic.websocket import AsyncJsonWebsocketConsumer

# 开启链接时:
self.websocket_connect
self.connect
self.accept

# 接受消息时:
self.receive
self.receive_json

# 发送消息时:
self.send_json
self.send

# 关闭链接时:
self.websocket_disconnect
self.disconnect

class AsyncWSConsumer(AsyncJsonWebsocketConsumer):
  async def websocket_connect(self, message):
    pass
  async def connect(self, message):
    pass
  async def accept(self, message):
    # 接受传入套接字
    pass
  
	async def websocket_disconnect(self, code):
    # 遍历组,从通道层当中移除
    # 不必在手动实现
    pass
  async def disconnect(self, code):
    pass
  
  async def receive(self, code):
    pass
  async def receive_json(self, code):
    pass
 
	async def send_json(self, code):
    pass
  async def send(self, code):
    pass

4.2 必要重写方法

# connect 或 websocket_connect:开启链接时实现用户认证
# receive 或 receive_json:接收请求参数并进行数据处理,通过 send_json 发送数据

4.3 启动

# 启动 daphne 服务
daphne core.asgi:application -b 0.0.0.0 -p 8001

4.4 访问

请求 ws 接口

可以使用线上在线工具:https://wstool.js.org/
网址:ws://127.0.0.1:8001/ws/?token=xxx
请求:{"option": "subscribe", "group": "get_usrs_list"}

获取 token

curl \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "123456"}' \
  http://localhost:8000/api/token/

4.5 配置入口

# 1.注意在 setting.py 文件中新增启动入口配置:ASGI_APPLICATION = 'apps.ws.routing.application'
在 ws app 下新建 routine.py,作为入口, 与配置保持一致。

5.BeatServer

Beatserver,django 频道的周期性任务调度器,与 channels 可实现 websocket 的定时发送功能。
官网:https://github.com/rajasimon/beatserver

5.1 安装

pip install beatserver

5.2 配置

beatserversettings.pyINSTALLED_APPS中添加

INSTALLED_APPS = [
    'beatserver',
    'channels',
    '...'
]

5.3 beatconfig.py

定时任务配置文件,放在 core 目录下

from datetime import timedelta
from apps.ws.groups import beat_schedule

BEAT_SCHEDULE = {
    'periodic_sending': beat_schedule
}

# 可以与 websocker 的调度内容放在一起
beat_schedule = [
    {
        'type': 'broadcast',
        'message': {'group': 'online_state'},
        'schedule': timedelta(seconds=60)
    },
   {
        'type': 'broadcast',
        'message': {'group': 'online_state2'},
        'schedule': timedelta(seconds=60)
    },
]

5.4 routing.py

路由

# 可以与 websocker 的调度内容放在一起
application = ProtocolTypeRouter({
    "channel": ChannelNameRouter({
        "testing-print": PrintConsumer,
    }),
})

5.5 consumers.py

消费者

from channels.consumer import SyncConsumer

class PrintConsumer(SyncConsumer):
    def test_print(self, message):
        print(message)

5.6 启动

python manage.py beatserver

5.7 docker

docker-compose.yml 启动

version: "3"

services:
  beatserver:
    image: your-project/beatserver:latest
    container_name: my_beatserver
    command: /bin/sh -c "sleep 3 && python manage.py beatserver"
    restart: always
    environment:
      - DEBUG=0
      - DATABASE_HOST=postgres
      - DATABASE_PORT=5432
      - DATABASE_NAME=project
      - DATABASE_USER=postgres
      - DATABASE_PASSWORD=pswd123456
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - REDIS_PASSWORD=123456
    volumes:
      - ./project/src:/deploy/backend
    depends_on:
      - redis
  

6.JWT

6.1 介绍

官方文档链接:https://django-rest-framework-simplejwt.readthedocs.io/en/latest/index.html

6.2 安装

pip install djangorestframework-simplejwt

6.3 配置

settings.py

INSTALLED_APPS = [
    'rest_framework_simplejwt',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    )
}


# 一些 Simple JWT 的行为可以通过设置变量来定制
# 没要求可不配置
from datetime import timedelta

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=1),
    ...
}

urls.py

另外,在您的根urls.py文件(或任何其他 URL 配置)中,包含简单 JWTTokenObtainPairViewTokenRefreshView视图的路由:

from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    ...
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    ...
]

6.4 创建用户

python manage.py createsuperuser

6.5 用法

# 请求
curl \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "123456"}' \
  http://localhost:8000/api/token/

# 返回值
refresh:长期有效的刷新令牌
access:短期有效的访问令牌
{
    "refresh": "xxxxx",
    "access": "xxxxx"
}
posted @ 2025-03-29 18:31  亦双弓  阅读(29)  评论(0)    收藏  举报