feat: 物联网相关代码

This commit is contained in:
ahhui
2023-08-08 17:40:28 +08:00
committed by 李强
parent 2722631276
commit b6b970f7a2
16 changed files with 238 additions and 13 deletions

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.urls import path from django.urls import path
from application.websocketConfig import MegCenter from application.websocketConfig import MegCenter
from device.routing import websocket_urlpatterns as device_ws_url
websocket_urlpatterns = [ websocket_urlpatterns = [
path('ws/<str:service_uid>/', MegCenter.as_asgi()), # consumers.DvadminWebSocket 是该路由的消费者 path('ws/<str:service_uid>/', MegCenter.as_asgi()), # consumers.DvadminWebSocket 是该路由的消费者
*device_ws_url,
] ]

View File

@@ -166,9 +166,9 @@ CORS_ORIGIN_ALLOW_ALL = True
# 允许cookie # 允许cookie
CORS_ALLOW_CREDENTIALS = True # 指明在跨域访问中后端是否支持对cookie的操作 CORS_ALLOW_CREDENTIALS = True # 指明在跨域访问中后端是否支持对cookie的操作
# ================================================= # # ===================================================== #
# ********************* channels配置 ******************* # # ********************* channels配置 ******************* #
# ================================================= # # ===================================================== #
ASGI_APPLICATION = 'application.asgi.application' ASGI_APPLICATION = 'application.asgi.application'
CHANNEL_LAYERS = { CHANNEL_LAYERS = {
"default": { "default": {
@@ -306,7 +306,7 @@ AUTHENTICATION_BACKENDS = ["dvadmin.utils.backends.CustomBackend"]
# ================================================= # # ================================================= #
SIMPLE_JWT = { SIMPLE_JWT = {
# token有效时长 # token有效时长
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=120), "ACCESS_TOKEN_LIFETIME": timedelta(minutes=1440),
# token刷新后的有效时间 # token刷新后的有效时间
"REFRESH_TOKEN_LIFETIME": timedelta(days=1), "REFRESH_TOKEN_LIFETIME": timedelta(days=1),
# 设置前缀 # 设置前缀

View File

@@ -30,6 +30,7 @@ from dvadmin.system.views.login import (
CaptchaView, CaptchaView,
ApiLogin, ApiLogin,
LogoutView, LogoutView,
LoginTokenView
) )
from dvadmin.system.views.system_config import InitSettingsViewSet from dvadmin.system.views.system_config import InitSettingsViewSet
from dvadmin.utils.swagger import CustomOpenAPISchemaGenerator from dvadmin.utils.swagger import CustomOpenAPISchemaGenerator
@@ -81,6 +82,10 @@ urlpatterns = (
path("api/init/dictionary/", InitDictionaryViewSet.as_view()), path("api/init/dictionary/", InitDictionaryViewSet.as_view()),
path("api/init/settings/", InitSettingsViewSet.as_view()), path("api/init/settings/", InitSettingsViewSet.as_view()),
path("apiLogin/", ApiLogin.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.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+ static(settings.STATIC_URL, document_root=settings.STATIC_URL) + static(settings.STATIC_URL, document_root=settings.STATIC_URL)

View File

@@ -122,6 +122,7 @@ class MessageCreateSerializer(CustomModelSerializer):
fields = "__all__" fields = "__all__"
read_only_fields = ["id"] read_only_fields = ["id"]
def websocket_push(user_id, message): def websocket_push(user_id, message):
username = "user_" + str(user_id) username = "user_" + str(user_id)
channel_layer = get_channel_layer() 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: if message is None:
message = {"contentType": "INFO", "content": None} message = {"contentType": "INFO", "content": None}
if target_role is None: if target_role is None:

View File

3
backend/device/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
backend/device/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class DeviceConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'device'

View File

73
backend/device/models.py Normal file
View 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",)

View 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
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

16
backend/device/urls.py Normal file
View 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

View 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

View 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

View 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

View 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