diff --git a/api/asgi.py b/api/asgi.py index 1062d02..75649d6 100644 --- a/api/asgi.py +++ b/api/asgi.py @@ -9,8 +9,20 @@ https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ import os +from channels.routing import ProtocolTypeRouter, URLRouter +from channels.auth import AuthMiddlewareStack +from channels.security.websocket import AllowedHostsOriginValidator + from django.core.asgi import get_asgi_application +from chat.url import websocket_urlpatterns -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "api.settings") -application = get_asgi_application() +application = ProtocolTypeRouter( + { + "http": get_asgi_application(), + "websocket": AllowedHostsOriginValidator( + AuthMiddlewareStack(URLRouter(websocket_urlpatterns)) + ), + } +) diff --git a/api/settings.py b/api/settings.py index 4b7ec0f..e94a984 100644 --- a/api/settings.py +++ b/api/settings.py @@ -47,12 +47,14 @@ INSTALLED_APPS = [ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + "channels", "ninja", "corsheaders", "django_extensions", "account", "task", "submission", + "chat", ] MIDDLEWARE = [ @@ -85,7 +87,7 @@ TEMPLATES = [ ] WSGI_APPLICATION = "api.wsgi.application" - +ASGI_APPLICATION = "api.asgi.application" # Database # https://docs.djangoproject.com/en/5.1/ref/settings/#databases @@ -129,6 +131,16 @@ else: # 配置缓存 CACHES = PROD_CACHES + # WebSocket 的缓存 + CHANNEL_LAYERS = { + "default": { + "BACKEND": "channels_redis.core.RedisChannelLayer", + "CONFIG": { + "hosts": [(os.getenv("REDIS_HOST"), 6379)], + }, + }, + } + # Password validation # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators diff --git a/api/urls.py b/api/urls.py index cec5f6f..4217da6 100644 --- a/api/urls.py +++ b/api/urls.py @@ -34,4 +34,4 @@ apis = [ path("api/", api.urls), ] -urlpatterns = apis + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) +urlpatterns = apis + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/chat/__init__.py b/chat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chat/api.py b/chat/api.py new file mode 100644 index 0000000..e69de29 diff --git a/chat/apps.py b/chat/apps.py new file mode 100644 index 0000000..2fe899a --- /dev/null +++ b/chat/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ChatConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'chat' diff --git a/chat/consumers.py b/chat/consumers.py new file mode 100644 index 0000000..14a2a73 --- /dev/null +++ b/chat/consumers.py @@ -0,0 +1,17 @@ +import json + +from channels.generic.websocket import WebsocketConsumer + + +class ChatConsumer(WebsocketConsumer): + def connect(self): + self.accept() + + def disconnect(self, close_code): + pass + + def receive(self, text_data): + text_data_json = json.loads(text_data) + message = text_data_json["message"] + + self.send(text_data=json.dumps({"message": message})) diff --git a/chat/migrations/__init__.py b/chat/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chat/models.py b/chat/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/chat/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/chat/url.py b/chat/url.py new file mode 100644 index 0000000..0c77274 --- /dev/null +++ b/chat/url.py @@ -0,0 +1,6 @@ +from django.urls import path +from .consumers import ChatConsumer + +websocket_urlpatterns = [ + path("ws/chat/", ChatConsumer.as_asgi()), +] diff --git a/pyproject.toml b/pyproject.toml index af2ed64..0c5316d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,4 +16,6 @@ dependencies = [ "uvicorn>=0.34.0", "django-redis>=5.4.0", "redis>=5.0.1", + "channels>=4.2.2", + "channels-redis>=4.2.1", ] diff --git a/requirements.txt b/requirements.txt index 877207b..9e45547 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,7 @@ annotated-types==0.7.0 asgiref==3.9.1 +channels==4.2.2 +channels-redis==4.2.1 click==8.2.1 django==5.2.4 django-cors-headers==4.7.0 @@ -11,6 +13,7 @@ email-validator==2.2.0 gunicorn==23.0.0 h11==0.16.0 idna==3.10 +msgpack==1.1.1 packaging==25.0 psycopg==3.2.9 psycopg-binary==3.2.9 diff --git a/uv.lock b/uv.lock index 01c1e99..06d5cf3 100644 --- a/uv.lock +++ b/uv.lock @@ -20,6 +20,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/3c/0464dcada90d5da0e71018c04a140ad6349558afb30b3051b4264cc5b965/asgiref-3.9.1-py3-none-any.whl", hash = "sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c", size = 23790, upload-time = "2025-07-08T09:07:41.548Z" }, ] +[[package]] +name = "channels" +version = "4.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/d6/049f93c3c96a88265a52f85da91d2635279261bbd4a924b45caa43b8822e/channels-4.2.2.tar.gz", hash = "sha256:8d7208e48ab8fdb972aaeae8311ce920637d97656ffc7ae5eca4f93f84bcd9a0", size = 26647, upload-time = "2025-03-30T14:59:20.35Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/bf/4799809715225d19928147d59fda0d3a4129da055b59a9b3e35aa6223f52/channels-4.2.2-py3-none-any.whl", hash = "sha256:ff36a6e1576cacf40bcdc615fa7aece7a709fc4fdd2dc87f2971f4061ffdaa81", size = 31048, upload-time = "2025-03-30T14:59:18.969Z" }, +] + +[[package]] +name = "channels-redis" +version = "4.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "channels" }, + { name = "msgpack" }, + { name = "redis" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/6d/c379c9feea4522cbdb4eba9b3d23a6270ba8cbd94e847b21834d898109d6/channels_redis-4.2.1.tar.gz", hash = "sha256:8375e81493e684792efe6e6eca60ef3d7782ef76c6664057d2e5c31e80d636dd", size = 31152, upload-time = "2024-11-15T12:58:49.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/aa/981d08ae9627c3b9d8dd150f0fe644122a351abc1f47bcf53d2bfff80d91/channels_redis-4.2.1-py3-none-any.whl", hash = "sha256:2ca33105b3a04b5a327a9c47dd762b546f30b76a0cd3f3f593a23d91d346b6f4", size = 20487, upload-time = "2024-11-15T12:58:47.847Z" }, +] + [[package]] name = "click" version = "8.2.1" @@ -158,6 +186,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "msgpack" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555, upload-time = "2025-06-13T06:52:51.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677, upload-time = "2025-06-13T06:52:16.64Z" }, + { url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603, upload-time = "2025-06-13T06:52:17.843Z" }, + { url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504, upload-time = "2025-06-13T06:52:18.982Z" }, + { url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749, upload-time = "2025-06-13T06:52:20.211Z" }, + { url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458, upload-time = "2025-06-13T06:52:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976, upload-time = "2025-06-13T06:52:22.995Z" }, + { url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607, upload-time = "2025-06-13T06:52:24.152Z" }, + { url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172, upload-time = "2025-06-13T06:52:25.704Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347, upload-time = "2025-06-13T06:52:26.846Z" }, + { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341, upload-time = "2025-06-13T06:52:27.835Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -325,6 +371,8 @@ name = "webapi" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "channels" }, + { name = "channels-redis" }, { name = "django" }, { name = "django-cors-headers" }, { name = "django-extensions" }, @@ -340,6 +388,8 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "channels", specifier = ">=4.2.2" }, + { name = "channels-redis", specifier = ">=4.2.1" }, { name = "django", specifier = ">=5.1.6" }, { name = "django-cors-headers", specifier = ">=4.7.0" }, { name = "django-extensions", specifier = ">=3.2.3" },