feat: 物联网相关代码
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.urls import path
|
||||
from application.websocketConfig import MegCenter
|
||||
from device.routing import websocket_urlpatterns as device_ws_url
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path('ws/<str:service_uid>/', MegCenter.as_asgi()), # consumers.DvadminWebSocket 是该路由的消费者
|
||||
*device_ws_url,
|
||||
]
|
||||
|
||||
|
||||
@@ -166,9 +166,9 @@ CORS_ORIGIN_ALLOW_ALL = True
|
||||
# 允许cookie
|
||||
CORS_ALLOW_CREDENTIALS = True # 指明在跨域访问中,后端是否支持对cookie的操作
|
||||
|
||||
# ================================================= #
|
||||
# ===================================================== #
|
||||
# ********************* channels配置 ******************* #
|
||||
# ================================================= #
|
||||
# ===================================================== #
|
||||
ASGI_APPLICATION = 'application.asgi.application'
|
||||
CHANNEL_LAYERS = {
|
||||
"default": {
|
||||
@@ -306,7 +306,7 @@ AUTHENTICATION_BACKENDS = ["dvadmin.utils.backends.CustomBackend"]
|
||||
# ================================================= #
|
||||
SIMPLE_JWT = {
|
||||
# token有效时长
|
||||
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=120),
|
||||
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=1440),
|
||||
# token刷新后的有效时间
|
||||
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
|
||||
# 设置前缀
|
||||
|
||||
@@ -30,6 +30,7 @@ from dvadmin.system.views.login import (
|
||||
CaptchaView,
|
||||
ApiLogin,
|
||||
LogoutView,
|
||||
LoginTokenView
|
||||
)
|
||||
from dvadmin.system.views.system_config import InitSettingsViewSet
|
||||
from dvadmin.utils.swagger import CustomOpenAPISchemaGenerator
|
||||
@@ -81,6 +82,10 @@ urlpatterns = (
|
||||
path("api/init/dictionary/", InitDictionaryViewSet.as_view()),
|
||||
path("api/init/settings/", InitSettingsViewSet.as_view()),
|
||||
path("apiLogin/", ApiLogin.as_view()),
|
||||
path("api/device/", include("device.urls")),
|
||||
|
||||
# 仅用于开发,上线需关闭
|
||||
path("api/token/", LoginTokenView.as_view()),
|
||||
]
|
||||
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
+ static(settings.STATIC_URL, document_root=settings.STATIC_URL)
|
||||
|
||||
@@ -122,6 +122,7 @@ class MessageCreateSerializer(CustomModelSerializer):
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
def websocket_push(user_id, message):
|
||||
username = "user_" + str(user_id)
|
||||
channel_layer = get_channel_layer()
|
||||
@@ -133,8 +134,9 @@ def websocket_push(user_id,message):
|
||||
}
|
||||
)
|
||||
|
||||
def create_message_push(title: str, content: str, target_type: int=0, target_user: list=[], target_dept=None, target_role=None,
|
||||
message: dict = {'contentType': 'INFO', 'content': '测试~'}, request= Request):
|
||||
|
||||
def create_message_push(title: str, content: str, target_type: int = 0, target_user: list = None, target_dept=None,
|
||||
target_role=None, message: dict = None, request=Request):
|
||||
if message is None:
|
||||
message = {"contentType": "INFO", "content": None}
|
||||
if target_role is None:
|
||||
|
||||
0
backend/device/__init__.py
Normal file
0
backend/device/__init__.py
Normal file
3
backend/device/admin.py
Normal file
3
backend/device/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
backend/device/apps.py
Normal file
6
backend/device/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DeviceConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'device'
|
||||
0
backend/device/migrations/__init__.py
Normal file
0
backend/device/migrations/__init__.py
Normal file
73
backend/device/models.py
Normal file
73
backend/device/models.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from django.db import models
|
||||
|
||||
from dvadmin.utils.models import CoreModel, table_prefix
|
||||
|
||||
|
||||
class Template(CoreModel):
|
||||
CLASSIFY_CHOICE = (
|
||||
(0, "网关"),
|
||||
(1, "终端设备"),
|
||||
)
|
||||
classify = models.SmallIntegerField(choices=CLASSIFY_CHOICE, verbose_name="模板类别")
|
||||
name = models.CharField(max_length=128, verbose_name="模板名称", unique=True)
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + 'device_template'
|
||||
verbose_name = "模板表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-id",)
|
||||
|
||||
|
||||
class TemplateDetail(CoreModel):
|
||||
template = models.ForeignKey(to="Template", null=False, on_delete=models.CASCADE, db_constraint=False,
|
||||
verbose_name="所属模板")
|
||||
key_title = models.CharField(max_length=64, verbose_name="键标题", unique=True)
|
||||
key_name = models.CharField(max_length=64, verbose_name="键名")
|
||||
key_type = models.CharField(max_length=32, verbose_name="键值类型")
|
||||
parent_key = models.ForeignKey(to="TemplateDetail", null=True, on_delete=models.CASCADE, db_constraint=False,
|
||||
verbose_name="父级键")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + 'device_template_detail'
|
||||
verbose_name = "模板详情表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-id",)
|
||||
unique_together = (('key_name', 'parent_key'),)
|
||||
|
||||
|
||||
class Gateway(CoreModel):
|
||||
name = models.CharField(max_length=128, verbose_name="设备名称", unique=True)
|
||||
specification = models.CharField(max_length=32, verbose_name="设备型号")
|
||||
mac_address = models.CharField(max_length=32, verbose_name="设备MAC地址")
|
||||
version = models.CharField(max_length=64, verbose_name="设备固件版本号")
|
||||
ip_address = models.CharField(max_length=32, verbose_name="设备IP地址")
|
||||
physics_address = models.CharField(max_length=255, default="暂无位置信息", verbose_name="设备实际地址")
|
||||
account = models.CharField(max_length=32, verbose_name="设备账号")
|
||||
password = models.CharField(max_length=32, verbose_name="设备密码")
|
||||
template = models.ForeignKey(to="Template", null=False, on_delete=models.CASCADE, db_constraint=False,
|
||||
verbose_name="所用模板")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + 'device_gateways'
|
||||
verbose_name = "网关表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-id",)
|
||||
|
||||
|
||||
class Terminal(CoreModel):
|
||||
name = models.CharField(max_length=128, verbose_name="设备名称", unique=True)
|
||||
specification = models.CharField(max_length=32, verbose_name="设备型号")
|
||||
identify = models.CharField(max_length=128, verbose_name="设备标识", unique=True)
|
||||
physics_address = models.CharField(max_length=255, default="暂无位置信息", verbose_name="设备实际地址")
|
||||
project = models.CharField(max_length=128, verbose_name="所属项目")
|
||||
remark = models.CharField(max_length=255, null=True, verbose_name="设备备注")
|
||||
gateway = models.ForeignKey(to=Gateway, null=True, on_delete=models.CASCADE, db_constraint=False,
|
||||
verbose_name="关联网关")
|
||||
template = models.ForeignKey(to="Template", null=False, on_delete=models.CASCADE, db_constraint=False,
|
||||
verbose_name="所用模板")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + 'device_terminal'
|
||||
verbose_name = "终端设备表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-id",)
|
||||
6
backend/device/routing.py
Normal file
6
backend/device/routing.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.urls import path
|
||||
from device.websocket import DeviceStatusWebSocket
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path('ws/gateway_status/<str:token>/', DeviceStatusWebSocket.as_asgi()),
|
||||
]
|
||||
3
backend/device/tests.py
Normal file
3
backend/device/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
16
backend/device/urls.py
Normal file
16
backend/device/urls.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.urls import path
|
||||
from rest_framework import routers
|
||||
|
||||
from device.views.gateway import GatewayViewSet
|
||||
from device.views.template import TemplateViewSet, TemplateDetailViewSet
|
||||
from device.views.terminal import TerminalViewSet
|
||||
|
||||
|
||||
device_url = routers.SimpleRouter()
|
||||
device_url.register(r'gateway', GatewayViewSet)
|
||||
device_url.register(r'template', TemplateViewSet)
|
||||
device_url.register(r'template_detail', TemplateDetailViewSet)
|
||||
device_url.register(r'terminal', TerminalViewSet)
|
||||
|
||||
urlpatterns = []
|
||||
urlpatterns += device_url.urls
|
||||
18
backend/device/views/gateway.py
Normal file
18
backend/device/views/gateway.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
from device.models import Gateway
|
||||
|
||||
|
||||
class GatewaySerializer(CustomModelSerializer):
|
||||
"""网关管理序列化器"""
|
||||
|
||||
class Meta:
|
||||
model = Gateway
|
||||
fields = '__all__'
|
||||
read_only_fields = ['id']
|
||||
|
||||
|
||||
class GatewayViewSet(CustomModelViewSet):
|
||||
"""网关管理视图集"""
|
||||
queryset = Gateway.objects.all()
|
||||
serializer_class = GatewaySerializer
|
||||
33
backend/device/views/template.py
Normal file
33
backend/device/views/template.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
from device.models import Template, TemplateDetail
|
||||
|
||||
|
||||
class TemplateSerializer(CustomModelSerializer):
|
||||
"""模板管理序列化器"""
|
||||
|
||||
class Meta:
|
||||
model = Template
|
||||
fields = '__all__'
|
||||
read_only_fields = ['id']
|
||||
|
||||
|
||||
class TemplateDetailSerializer(CustomModelSerializer):
|
||||
"""模板详情序列化器"""
|
||||
|
||||
class Meta:
|
||||
model = TemplateDetail
|
||||
fields = '__all__'
|
||||
read_only_fields = ['id']
|
||||
|
||||
|
||||
class TemplateViewSet(CustomModelViewSet):
|
||||
"""模板管理视图集"""
|
||||
queryset = Template.objects.all()
|
||||
serializer_class = TemplateSerializer
|
||||
|
||||
|
||||
class TemplateDetailViewSet(CustomModelViewSet):
|
||||
"""模板详情视图集"""
|
||||
queryset = TemplateDetail.objects.all()
|
||||
serializer_class = TemplateDetailSerializer
|
||||
18
backend/device/views/terminal.py
Normal file
18
backend/device/views/terminal.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
from device.models import Terminal
|
||||
|
||||
|
||||
class TerminalSerializer(CustomModelSerializer):
|
||||
"""终端设备管理序列化器"""
|
||||
|
||||
class Meta:
|
||||
model = Terminal
|
||||
fields = '__all__'
|
||||
read_only_fields = ['id']
|
||||
|
||||
|
||||
class TerminalViewSet(CustomModelViewSet):
|
||||
"""终端设备管理视图集"""
|
||||
queryset = Terminal.objects.all()
|
||||
serializer_class = TerminalSerializer
|
||||
41
backend/device/websocket.py
Normal file
41
backend/device/websocket.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import json
|
||||
from hashlib import md5
|
||||
from datetime import datetime
|
||||
|
||||
import jwt
|
||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
||||
|
||||
from application.websocketConfig import set_message
|
||||
|
||||
|
||||
class DeviceStatusWebSocket(AsyncJsonWebsocketConsumer):
|
||||
"""设备状态ws"""
|
||||
chat_group = 'device_status'
|
||||
|
||||
async def connect(self):
|
||||
try:
|
||||
print('设备ws创建连接', self.scope)
|
||||
self.token = self.scope['url_route']['kwargs']['token']
|
||||
decoded_result = jwt.decode(self.token, settings.SECRET_KEY, algorithms=['HS256'])
|
||||
if decoded_result:
|
||||
self.uid = decoded_result.get('user_id')
|
||||
self.hash = md5(str(self.token).encode('utf-8'))
|
||||
self.chat_name = f'{self.chat_group}:user_{self.uid}'
|
||||
await self.channel_layer.group_add(self.chat_name, self.channel_name)
|
||||
await self.accept()
|
||||
else:
|
||||
raise jwt.InvalidSignatureError()
|
||||
except jwt.InvalidSignatureError:
|
||||
await self.send_json(set_message('system', 'SYSTEM', {'message': 'Token无效'}), True)
|
||||
await self.disconnect(None)
|
||||
except jwt.ExpiredSignatureError:
|
||||
await self.send_json(set_message('system', 'SYSTEM', {'message': 'Token过期'}), True)
|
||||
await self.disconnect(None)
|
||||
|
||||
async def disconnect(self, code):
|
||||
print('设备ws连接关闭')
|
||||
await self.channel_layer.group_discard(self.chat_name, self.channel_name)
|
||||
try:
|
||||
await self.close(code)
|
||||
except:
|
||||
pass
|
||||
Reference in New Issue
Block a user