Merge remote-tracking branch 'origin/master'

# Conflicts:
#	springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java
#	springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/EmqxService.java
#	springboot/wumei-iot/src/main/java/com/ruoyi/iot/tdengine/config/TDengineConfig.java
#	springboot/wumei-iot/src/main/resources/mapper/tdengine/DatabaseMapper.xml
#	springboot/wumei-iot/src/main/resources/mapper/tdengine/TDDeviceLogMapper.xml
This commit is contained in:
wxy001
2022-05-24 01:18:18 +08:00
32 changed files with 2110 additions and 1251 deletions

View File

@@ -24,7 +24,7 @@
- EMQ管理 Mqtt客户端、监听器、消息主题、消息订阅、插件管理、规则引擎、资源 - EMQ管理 Mqtt客户端、监听器、消息主题、消息订阅、插件管理、规则引擎、资源
- 硬件 SDK 支持WIFI和MQTT连接、物模型响应、实时监测、定时上报监测数据、AES加密、NTP时间等 - 硬件 SDK 支持WIFI和MQTT连接、物模型响应、实时监测、定时上报监测数据、AES加密、NTP时间等
- 物模型管理: 属性(设备状态和监测数据),功能(执行特定任务),事件(设备主动上报给云端) - 物模型管理: 属性(设备状态和监测数据),功能(执行特定任务),事件(设备主动上报给云端)
- 其他开发中第三方登录设备分享、设备告警、场景联动进度50%智能音箱、多租户、APP界面自定义进度40%时序数据库、分布式集群部署、Granfa监控进度30%视频流处理、桌面端模拟器/监控、安卓端模拟器/监控进度20% - 其他开发中第三方登录设备分享、设备告警、场景联动进度50%; 智能音箱、多租户、APP界面自定义进度40%; 时序数据库、分布式集群部署、Granfa监控进度30%; 视频流处理、桌面端模拟器/监控、安卓端模拟器/监控进度20%
### 四、技术栈 ### 四、技术栈
@@ -74,7 +74,7 @@
* [权限管理系统ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue) * [权限管理系统ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
* [Mqtt消息服务器EMQX4.0](https://github.com/emqx/emqx) * [Mqtt消息服务器EMQX4.0](https://github.com/emqx/emqx)
* [ESP8266 Core For Arduino](https://github.com/esp8266/Arduino) * [ESP8266 Core For Arduino](https://github.com/esp8266/Arduino)
* [uCharts高性能跨平台图](https://www.ucharts.cn) * [uCharts高性能跨平台图](https://www.ucharts.cn)
##### Docker快速安装 ##### Docker快速安装
* Mysql中创建wumei-smart数据库[导入Sql脚本](https://gitee.com/kerwincui/wumei-smart/tree/master/springboot/sql) * Mysql中创建wumei-smart数据库[导入Sql脚本](https://gitee.com/kerwincui/wumei-smart/tree/master/springboot/sql)
* 修改命令中的Mysql配置并执行 * 修改命令中的Mysql配置并执行
@@ -104,8 +104,8 @@ kerwincui/wumei-smart:1.1
##### 项目贡献者 ##### 项目贡献者
|[小驿物联](https://gitee.com/iot-xiaoyi) |[Guanshubiao](https://gitee.com/guanshubiao)|[CrazyDull](https://gitee.com/crazyDull) |[Kami0314](https://github.com/kami0314)| |[小驿物联](https://gitee.com/iot-xiaoyi) |[Guanshubiao](https://gitee.com/guanshubiao)|[CrazyDull](https://gitee.com/crazyDull) |[Kami0314](https://github.com/kami0314)|[YBZX](https://github.com/YBZX)
|--|--|--|--| |--|--|--|--|--|
| [SXH](https://gitee.com/sixiaohu) | [Redamancy_zxp](https://gitee.com/redamancy-zxp) | [LEE](https://gitee.com/yueming188) | [LemonTree](https://gitee.com/fishhunterplus) | [Tang](https://gitee.com/mexiaotang) | [SXH](https://gitee.com/sixiaohu) | [Redamancy_zxp](https://gitee.com/redamancy-zxp) | [LEE](https://gitee.com/yueming188) | [LemonTree](https://gitee.com/fishhunterplus) | [Tang](https://gitee.com/mexiaotang)
|--|--|--|--|--| |--|--|--|--|--|

View File

@@ -9,7 +9,7 @@
#### 四、通过赞助、贡献代码、推广可以加入项目内部里面包含移动端源码和更多相关教程并且获得一年的免费更新。目前项目还在完善教程暂无赞助费暂定1500元。可以通过官网[http://wumei.live/](http://wumei.live/) 注册账号,联系作者加入项目内部。 #### 四、通过赞助、贡献代码、推广可以加入项目内部里面包含移动端源码和更多相关教程并且获得一年的免费更新。目前项目还在完善教程暂无赞助费暂定1500元。可以通过官网[http://wumei.live/](http://wumei.live/) 注册账号,联系作者加入项目内部。
##### 项目开发交流群【946029159】作者QQ 【164770707】 ##### 项目开发交流群【946029159】项目互助交流群【1073236354】作者QQ 【164770707】
<br /> <br />

View File

@@ -0,0 +1,45 @@
## 硬件端树莓派SDK说明
#### 一、运行环境
- Python 3.7.2 (其他python3的版本一般也可以)
- 开发板树莓派4b没有加入硬件相关代码安装好python3环境win下linux下都能运行
- 库 需要安装库
1. mqtt库
```
pip install paho-mqtt
```
2. ase加密库
```
# 前面两个卸载命令是为了防止一些安装环境问题
pip uninstall crypto
pip uninstall pycryptodome
pip install pycryptodome
```
3. 报错缺少xx库命令
```
pip install xx
```
#### 二、运行程序
```
python3 main_sdk.py
# 备注程序运行依赖aes.py文件保证该文件和main_sdk.py在同一目录
```
#### 三、开发参考资料:
- [Eclipse Paho MQTT Python Client 使用手册](https://www.cooooder.com/archives/20210303)
- 更多参考资料,下载目录中的文本文件查看

210
sdk/RaspberryPi/aes.py Normal file
View File

@@ -0,0 +1,210 @@
from Crypto.Cipher import AES
import base64
import binascii
# 数据类
class MData():
def __init__(self, data = b"",characterSet='utf-8'):
# data肯定为bytes
self.data = data
self.characterSet = characterSet
def saveData(self,FileName):
with open(FileName,'wb') as f:
f.write(self.data)
def fromString(self,data):
self.data = data.encode(self.characterSet)
return self.data
def fromBase64(self,data):
self.data = base64.b64decode(data.encode(self.characterSet))
return self.data
def fromHexStr(self,data):
self.data = binascii.a2b_hex(data)
return self.data
def toString(self):
return self.data.decode(self.characterSet)
def toBase64(self):
return base64.b64encode(self.data).decode() #decode()转成字符串形式,否则为字节型数据
def toHexStr(self):
return binascii.b2a_hex(self.data).decode()
def toBytes(self):
return self.data
def __str__(self):
try:
return self.toString()
except Exception:
return self.toBase64()
### 封装类
class AEScryptor():
def __init__(self,key,mode,iv = '',paddingMode= "NoPadding",characterSet ="utf-8"):
'''
构建一个AES对象
key: 秘钥,字节型数据
mode: 使用模式只提供两种AES.MODE_CBC, AES.MODE_ECB
iv iv偏移量字节型数据
paddingMode: 填充模式默认为NoPadding, 可选NoPaddingZeroPaddingPKCS5PaddingPKCS7Padding
characterSet: 字符集编码
'''
self.key = key
self.mode = mode
self.iv = iv
self.characterSet = characterSet
self.paddingMode = paddingMode
self.data = ""
def __ZeroPadding(self,data):
data += b'\x00'
while len(data) % 16 != 0:
data += b'\x00'
return data
def __StripZeroPadding(self,data):
data = data[:-1]
while len(data) % 16 != 0:
data = data.rstrip(b'\x00')
if data[-1] != b"\x00":
break
return data
def __PKCS5_7Padding(self,data):
needSize = 16-len(data) % 16
if needSize == 0:
needSize = 16
return data + needSize.to_bytes(1,'little')*needSize
def __StripPKCS5_7Padding(self,data):
paddingSize = data[-1]
return data.rstrip(paddingSize.to_bytes(1,'little'))
def __paddingData(self,data):
if self.paddingMode == "NoPadding":
if len(data) % 16 == 0:
return data
else:
return self.__ZeroPadding(data)
elif self.paddingMode == "ZeroPadding":
return self.__ZeroPadding(data)
elif self.paddingMode == "PKCS5Padding" or self.paddingMode == "PKCS7Padding":
return self.__PKCS5_7Padding(data)
else:
print("不支持Padding")
def __stripPaddingData(self,data):
if self.paddingMode == "NoPadding":
return self.__StripZeroPadding(data)
elif self.paddingMode == "ZeroPadding":
return self.__StripZeroPadding(data)
elif self.paddingMode == "PKCS5Padding" or self.paddingMode == "PKCS7Padding":
return self.__StripPKCS5_7Padding(data)
else:
print("不支持Padding")
def setCharacterSet(self,characterSet):
'''
设置字符集编码
characterSet: 字符集编码
'''
self.characterSet = characterSet
def setPaddingMode(self,mode):
'''
设置填充模式
mode: 可选NoPaddingZeroPaddingPKCS5PaddingPKCS7Padding
'''
self.paddingMode = mode
def decryptFromBase64(self,entext):
'''
从base64编码字符串编码进行AES解密
entext: 数据类型str
'''
mData = MData(characterSet=self.characterSet)
self.data = mData.fromBase64(entext)
return self.__decrypt()
def decryptFromHexStr(self,entext):
'''
从hexstr编码字符串编码进行AES解密
entext: 数据类型str
'''
mData = MData(characterSet=self.characterSet)
self.data = mData.fromHexStr(entext)
return self.__decrypt()
def decryptFromString(self,entext):
'''
从字符串进行AES解密
entext: 数据类型str
'''
mData = MData(characterSet=self.characterSet)
self.data = mData.fromString(entext)
return self.__decrypt()
def decryptFromBytes(self,entext):
'''
从二进制进行AES解密
entext: 数据类型bytes
'''
self.data = entext
return self.__decrypt()
def encryptFromString(self,data):
'''
对字符串进行AES加密
data: 待加密字符串数据类型为str
'''
self.data = data.encode(self.characterSet)
return self.__encrypt()
def __encrypt(self):
if self.mode == AES.MODE_CBC:
aes = AES.new(self.key,self.mode,self.iv)
elif self.mode == AES.MODE_ECB:
aes = AES.new(self.key,self.mode)
else:
print("不支持这种模式")
return
data = self.__paddingData(self.data)
enData = aes.encrypt(data)
return MData(enData)
def __decrypt(self):
if self.mode == AES.MODE_CBC:
aes = AES.new(self.key,self.mode,self.iv)
elif self.mode == AES.MODE_ECB:
aes = AES.new(self.key,self.mode)
else:
print("不支持这种模式")
return
data = aes.decrypt(self.data)
# mData = MData(self.__stripPaddingData(data),characterSet=self.characterSet)
# return mData()
return MData(self.__stripPaddingData(data),characterSet=self.characterSet)
if __name__ == '__main__':
key = b"1234567812345678"
iv = b"0000000000000000"
aes = AEScryptor(key,AES.MODE_CBC,iv,paddingMode= "ZeroPadding",characterSet='utf-8')
data = "好好学习"
rData = aes.encryptFromString(data)
print("密文:",rData.toBase64())
rData = aes.decryptFromBase64(rData.toBase64())
print("明文:",rData)
# ————————————————
# 版权声明本文为CSDN博主「Hello_wshuo」的原创文章遵循CC 4.0 BY-SA版权协议转载请附上原文出处链接及本声明。
# 原文链接https://blog.csdn.net/chouzhou9701/article/details/122019967

335
sdk/RaspberryPi/main_sdk.py Normal file
View File

@@ -0,0 +1,335 @@
# /***********************************************************
# * author: xiaoY [物美智能 wumei-smart]
# * create: 2022-05-10
# * emailqimint@outlook.com
# * source:https://github.com/kerwincui/wumei-smart
# * board:raspberry 4b
# ***********************************************************/
import time
import requests
import json
from aes import AEScryptor,AES
import paho.mqtt.client as mqtt
import random
import threading #导入线程模块,用作定时器
#################################################################
# 需要连接好外部网络
#################################################################
# 作为python的AES的iv,应该为16位字节型数据
wumei_iv = b"wumei-smart-open"
# NTP地址用于获取时间,可选的修改为自己部署项目的地址)
ntpServer = "http://120.24.218.158:8080/iot/tool/ntp?deviceSendTime="
# 设备信息配置
deviceNum = "DW43CI6RM8GMG23H"
userId = "1"
productId = "4"
firmwareVersion = "1.0"
# Mqtt配置
mqttHost = "120.24.218.158"
mqttPort = 1883
mqttUserName = "wumei-smart"
mqttPwd = "P261I5G3RY3MCIGG"
# 作为python的AES的key,应该为16位字节型数据
mqttSecret = b"K2IB784BM0O01GG6"
monitorCount =5 #发布监测数据的最大次数
# 使用esp8266单片机时服务器传来的间隔单位为毫秒本程序由于定时运行需要的是秒将转化为秒如需毫秒运行自行更改程序
monitorInterval =5 # 发布监测数据的间隔默认5秒
g_rc=-1 # 连接成功标志位
global t2 #全局变量,管理定时监测
# 订阅的主题
prefix = "/" + productId + "/" + deviceNum
sOtaTopic = prefix + "/ota/get"
sNtpTopic = prefix + "/ntp/get"
sPropertyTopic = prefix + "/property/get"
sFunctionTopic = prefix + "/function/get"
sPropertyOnline = prefix + "/property-online/get"
sFunctionOnline = prefix + "/function-online/get"
sMonitorTopic = prefix + "/monitor/get"
# 发布的主题
pInfoTopic = prefix + "/info/post"
pNtpTopic = prefix + "/ntp/post"
pPropertyTopic = prefix + "/property/post"
pFunctionTopic = prefix + "/function/post"
pMonitorTopic = prefix + "/monitor/post"
pEventTopic = prefix + "/event/post"
# 初始化
# 连接客户端ID = 设备编号 & 产品ID
clientId = deviceNum + "&" + productId
client=mqtt.Client(clientId)
#加密 (AES-CBC-128-pkcs5padding)
def encrypt(plain_data,wumei_key,wumei_iv):
aes=AEScryptor(wumei_key,AES.MODE_CBC,wumei_iv,paddingMode="PKCS5Padding",characterSet='utf-8')
rData=aes.encryptFromString(plain_data)
printMsg("密码(已加密)"+rData.toBase64())
return rData.toBase64()
#回调函数。当尝试与MQTT broker 建立连接时,触发该函数。
#client 是本次连接的客户端实例。
#userdata 是用户的信息,一般为空。但如果有需要,也可以通过 user_data_set 函数设置。
#flags 保存服务器响应标志的字典
#rc 是响应码。
# 0: 连接成功
# 1: 连接失败-不正确的协议版本
# 2: 连接失败-无效的客户端标识符
# 3: 连接失败-服务器不可用
# 4: 连接失败-错误的用户名或密码
# 5: 连接失败-未授权
# 6-255: 未定义.
#一般情况下我们只需要关注rc响应码是否为0就可以了。
def on_connect(client,userdata,flags,rc):
if rc==0:
printMsg("连接成功")
# 放在on_connect下可以保证重连重新订阅
# 订阅(OTA、NTP、属性、功能、实时监测)
client.subscribe(sOtaTopic, 1)
client.subscribe(sNtpTopic, 1)
client.subscribe(sPropertyTopic, 1)
client.subscribe(sFunctionTopic, 1)
client.subscribe(sPropertyOnline, 1)
client.subscribe(sFunctionOnline, 1)
client.subscribe(sMonitorTopic, 1)
printMsg("订阅主题:" + sOtaTopic)
printMsg("订阅主题:" + sNtpTopic)
printMsg("订阅主题:" + sPropertyTopic)
printMsg("订阅主题:" + sFunctionTopic)
printMsg("订阅主题:" + sPropertyOnline)
printMsg("订阅主题:" + sFunctionOnline)
printMsg("订阅主题:" + sMonitorTopic)
# 发布设备信息
publishInfo()
global g_rc
g_rc=0
else:
printMsg("连接失败rc="+str(rc))
printMsg("3秒后重连...")
time.sleep(3)
connectMqtt()
# 物模型-属性处理
def processProperty(payload):
data=json.loads(payload)
for item in data:
# 匹配云端定义的属性(不包含属性中的监测数据)
id = item["id"]
value=item["value"]
printMsg(str(id)+":"+str(value))
# 最后发布属性,服务端订阅存储(重要)
publishProperty(json.dumps(data))
# 物模型-功能处理
def processFunction(payload):
data=json.loads(payload)
for item in data:
# 匹配云端定义的功能
id = item["id"]
value=item["value"]
if(id=="switch"):
printMsg("开关 switch"+ str(value))
elif(id=="gear"):
printMsg("档位 gear"+ str(value))
elif(id=="light_color"):
printMsg("灯光颜色 light_color"+ str(value))
elif(id=="message"):
printMsg("屏显消息 message"+ str(value))
elif(id=="report_monitor"):
msg=randomPropertyData();
printMsg("订阅到上报监测数据指令,上报数据:")
printMsg(msg)
publishProperty(msg)
# 最后发布属性,服务端订阅存储(重要)
publishProperty(json.dumps(data))
# 回调函数在客户端订阅的主题上接收到消息时调用“message”变量是一个MQTT消息描述所有消息特征
def on_message(client,userdata,msg):
printMsg("接收数据:"+msg.topic+" "+str(msg.payload))
if(msg.topic==sOtaTopic):
printMsg("订阅到设备升级指令...")
elif(msg.topic==sNtpTopic):
printMsg("订阅到NTP时间...");
jsonData=json.loads(msg.payload)
deviceSendTime = jsonData["deviceSendTime"]
serverSendTime = jsonData["serverSendTime"]
serverRecvTime = jsonData["serverRecvTime"]
deviceRecvTime = round(time.time()*1000)
now = (serverSendTime + serverRecvTime + deviceRecvTime - deviceSendTime) / 2
printMsg("当前时间:"+str(round(now)))
elif(msg.topic==sPropertyTopic or msg.topic==sPropertyOnline):
printMsg("订阅到属性指令...")
processProperty(msg.payload)
elif(msg.topic==sFunctionTopic or msg.topic==sFunctionOnline):
printMsg("订阅到功能指令...")
processFunction(msg.payload)
elif(msg.topic==sMonitorTopic):
# python全局变量的使用
global t2
global monitorCount
global monitorInterval
printMsg("订阅到实时监测指令...")
jsonData=json.loads(msg.payload)
monitorCount = jsonData["count"]
monitorInterval = jsonData["interval"]/1000
t2.cancel()
t2=threading.Timer(monitorInterval,timing_publishMonitor)
t2.start()
# 1.发布设备信息
def publishInfo():
# rssi值 树莓派中暂时不处理wifi信号问题
# 信号强度信号极好4格[-55— 0]信号好3格[-70— -55]信号一般2格[-85— -70]信号差1格[-100— -85]
# status值 1-未激活2-禁用3-在线4-离线)
doc={"rssi":1,"firmwareVersion":firmwareVersion,"status":3,"userId":userId}
# client.publish('raspberry/topic',payload=i,qos=0,retain=False)
jsonData=json.dumps(doc)
printMsg("发布设备信息:"+pInfoTopic+" "+jsonData)
client.publish(pInfoTopic,jsonData)
# 2.发布时钟同步信,用于获取当前时间(可选)
def publishNtp():
data={"deviceSendTime":round(time.time()*1000)}
jsonData=json.dumps(data)
printMsg("发布NTP信息"+jsonData)
client.publish(pNtpTopic,jsonData)
# 3.发布属性
# msg 接收格式json
def publishProperty(msg):
printMsg("发布属性:" + msg)
client.publish(pPropertyTopic, msg)
# 4.发布功能
def publishFunction( msg):
printMsg("发布功能:" + msg)
client.publish(pFunctionTopic, msg)
# 5.发布事件
def publishEvent():
objTmeperature={"id":"height_temperature","value":40,"remark":"温度过高警告"}
objException={"id":"exception","value":"异常消息消息内容XXXXXXXX","remark":"设备发生错误"}
data=[objTmeperature,objException]
jsonData=json.dumps(data)
printMsg("发布事件:"+jsonData)
client.publish(pEventTopic,jsonData)
# 6.发布实时监测数据
def publishMonitor():
msg=randomPropertyData()
# 发布为实时监测数据,不会存储
printMsg("发布实时监测数据:"+msg)
client.publish(pMonitorTopic,msg)
# 随机生成监测值
def randomPropertyData():
# 匹配云端定义的监测数据,随机数代替监测结果
# random.randint(0,10) #生成数据包括0,10
# random.uniform(30,60)生成数据为浮点型
objTmeperature={"id":"temperature","value":str(round(random.uniform(10,30),2)),"remark":""}
objHumidity={"id":"humidity","value":str(round(random.uniform(30,60),2)),"remark":""}
objCo2={"id":"co2","value":str(random.randint(400,1000)),"remark":""}
objBrightness={"id":"brightness","value":str(random.randint(1000,10000)),"remark":""}
printMsg("随机生成监测数据值:")
data=[objTmeperature,objHumidity,objCo2,objBrightness]
print(json.dumps(data))
return json.dumps(data)
#连接mqtt
def connectMqtt():
printMsg("连接Mqtt服务器")
# 生成mqtt认证密码密码 = mqtt密码 & 用户ID & 过期时间)
password = generationPwd()
encryptPassword=encrypt(password,mqttSecret,wumei_iv)
# 生成mqtt认证密码密码 = mqtt密码 & 用户ID & 过期时间)
client.username_pw_set(mqttUserName,encryptPassword)
client.on_connect=on_connect
client.on_message=on_message
client.connect(mqttHost,mqttPort,10)
#打印提示信息
def printMsg(msg):
print("[{}] {}".format(time.strftime("%Y-%m-%d %H:%M:%S"),msg))
# 生成密码
def generationPwd():
try:
doc=json.loads(getTime())
except:
printMsg("Json解析失败")
exit()
deviceSendTime = doc["deviceSendTime"]
serverSendTime = doc["serverSendTime"]
serverRecvTime = doc["serverRecvTime"]
deviceRecvTime = round(time.time()*1000)
now = (serverSendTime + serverRecvTime + deviceRecvTime - deviceSendTime) / 2
expireTime = int(now + 1 * 60 * 60 * 1000)
password = mqttPwd + "&" + userId + "&" + str(expireTime)
printMsg("密码(未加密):" + password)
return password
# HTTP获取时间
def getTime():
try:
r=requests.get(ntpServer+str(round(time.time()*1000)))
if(r.status_code>0):
if(r.status_code==200 or r.status_code==301):
printMsg("获取时间成功data:"+r.text)
return r.text
else:
printMsg("获取时间失败error:"+r.status_code)
except:
printMsg("连接Http失败")
def timing_publishProperty():
printMsg("执行定时上报")
#发布事件
publishEvent()
#发布时钟同步
publishNtp()
# 发布属性(监测值)
msg=randomPropertyData()
publishProperty(msg)
t1=threading.Timer(60,timing_publishProperty)
t1.start()
def timing_publishMonitor():
global monitorCount
monitorCount=monitorCount-1
printMsg("执行监测")
publishMonitor()
if(monitorCount>0):
t2=threading.Timer(monitorInterval,timing_publishMonitor)
t2.start()
if __name__ == '__main__':
connectMqtt()
client.loop_start()
printMsg("等待连接MQTT")
while(g_rc!=0):
print("-",end=" ")
time.sleep(1)
t1=threading.Timer(60,timing_publishProperty)
t1.setDaemon(True) #当主线程被关闭后,子线程也关闭
t1.start()
t2=threading.Timer(monitorInterval,timing_publishMonitor)
t2.setDaemon(True) #当主线程被关闭后,子线程也关闭
t2.start()
while True:
time.sleep(10) #定时上报、检测上报都是线程执行,主线程可以做自己的任务

View File

@@ -0,0 +1,26 @@
Eclipse Paho MQTT Python Client 使用手册
https://www.cooooder.com/archives/20210303
Python 实现AES加密
https://zhuanlan.zhihu.com/p/261694311
python实现AES加密解密
https://blog.csdn.net/chouzhou9701/article/details/122019967
使用python time()方法
http://www.py.cn/jishu/jichu/20424.html
python 线程定时器Timer
https://zhuanlan.zhihu.com/p/91412537
浅谈Python的格式化输出
https://www.jb51.net/article/225609.htm
Python Request库入门
https://www.jianshu.com/p/d78982126318
Python JSON
https://www.runoob.com/python/python-json.html
Python random() 函数
https://www.runoob.com/python/func-number-random.html

View File

@@ -1,21 +1,34 @@
package com.ruoyi.framework.config; package com.ruoyi.framework.config;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays; import javax.servlet.*;
import java.util.HashSet;
import java.util.List;
import javax.sql.DataSource; import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.config.properties.DruidProperties;
import com.ruoyi.framework.datasource.DynamicDataSource;
import org.apache.ibatis.io.VFS; import org.apache.ibatis.io.VFS;
import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@@ -34,8 +47,8 @@ import com.ruoyi.common.utils.StringUtils;
* @author ruoyi * @author ruoyi
*/ */
@Configuration @Configuration
public class MyBatisConfig @MapperScan(basePackages = {"com.ruoyi.iot.mapper", "com.ruoyi.system.mapper", "com.ruoyi.quartz.mapper", "com.ruoyi.generator.mapper"}, sqlSessionTemplateRef = "mysqlSqlSessionTemplate")
{ public class MyBatisConfig {
@Autowired @Autowired
private Environment env; private Environment env;
@@ -119,10 +132,11 @@ public class MyBatisConfig
@Bean(name = "mysqlSessionFactory") @Bean(name = "mysqlSessionFactory")
@Primary @Primary
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception
{ {
String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage"); String typeAliasesPackage = "com.ruoyi.**.domain";//env.getProperty("mybatis.typeAliasesPackage");
String mapperLocations = env.getProperty("mybatis.mapperLocations"); String mapperLocations = "classpath:mapper/iot/*Mapper.xml,classpath:mapper/system/*Mapper.xml,classpath:mapper/quartz/*Mapper.xml";
// String typeAliasesPackage = "com.ruoyi.**.domain";
String configLocation = env.getProperty("mybatis.configLocation"); String configLocation = env.getProperty("mybatis.configLocation");
typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage); typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
VFS.addImplClass(SpringBootVFS.class); VFS.addImplClass(SpringBootVFS.class);
@@ -146,4 +160,5 @@ public class MyBatisConfig
public SqlSessionTemplate mysqlSqlSessionTemplate(@Qualifier("mysqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { public SqlSessionTemplate mysqlSqlSessionTemplate(@Qualifier("mysqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory); return new SqlSessionTemplate(sqlSessionFactory);
} }
} }

View File

@@ -138,13 +138,13 @@ public class ToolController extends BaseController {
// 设备端 // 设备端
String[] clientInfo = clientid.split("&"); String[] clientInfo = clientid.split("&");
if (clientInfo.length != 2) { if (clientInfo.length != 2) {
// 设备未加密认证 // 设备简单认证
String deviceNum = clientInfo[0]; String deviceNum = clientInfo[0];
Device device = deviceService.selectShortDeviceBySerialNumber(deviceNum); Device device = deviceService.selectShortDeviceBySerialNumber(deviceNum);
if (device !=null && mqttConfig.getusername().equals(username) && mqttConfig.getpassword().equals(password)) { if (device !=null && mqttConfig.getusername().equals(username) && mqttConfig.getpassword().equals(password)) {
System.out.println("-----------认证成功,clientId:" + clientid + "---------------"); System.out.println("-----------认证成功,clientId:" + clientid + "---------------");
ProductAuthorize authorize = new ProductAuthorize(null, device.getProductId(), device.getDeviceId(), device.getSerialNumber(), 1L, "admin"); // ProductAuthorize authorize = new ProductAuthorize(null, device.getProductId(), device.getDeviceId(), device.getSerialNumber(), 1L, "admin");
authorizeService.boundProductAuthorize(authorize); // authorizeService.boundProductAuthorize(authorize);
return ResponseEntity.ok().body("ok"); return ResponseEntity.ok().body("ok");
} }
return returnUnauthorized(clientid, username, password, "认证信息有误"); return returnUnauthorized(clientid, username, password, "认证信息有误");
@@ -178,16 +178,16 @@ public class ToolController extends BaseController {
// 设备状态验证 1-未激活2-禁用3-在线4-离线) // 设备状态验证 1-未激活2-禁用3-在线4-离线)
if (model.getDeviceId() != null && model.getDeviceId() != 0 && model.getStatus() != 2) { if (model.getDeviceId() != null && model.getDeviceId() != 0 && model.getStatus() != 2) {
System.out.println("-----------认证成功,clientId:" + clientid + "---------------"); System.out.println("-----------认证成功,clientId:" + clientid + "---------------");
ProductAuthorize authorize = new ProductAuthorize(null, model.getProductId(), model.getDeviceId(), model.getSerialNumber(), 1L, "admin"); // ProductAuthorize authorize = new ProductAuthorize(null, model.getProductId(), model.getDeviceId(), model.getSerialNumber(), 1L, "admin");
authorizeService.boundProductAuthorize(authorize); // authorizeService.boundProductAuthorize(authorize);
return ResponseEntity.ok().body("ok"); return ResponseEntity.ok().body("ok");
} else { } else {
// 自动添加设备 // 自动添加设备
int result = deviceService.insertDeviceAuto(deviceNum, userId, productId); int result = deviceService.insertDeviceAuto(deviceNum, userId, productId);
if (result == 1) { if (result == 1) {
System.out.println("-----------认证成功,clientId:" + clientid + "---------------"); System.out.println("-----------认证成功,clientId:" + clientid + "---------------");
ProductAuthorize authorize = new ProductAuthorize(null, model.getProductId(), model.getDeviceId(), model.getSerialNumber(), 1L, "admin"); // ProductAuthorize authorize = new ProductAuthorize(null, model.getProductId(), model.getDeviceId(), model.getSerialNumber(), 1L, "admin");
authorizeService.boundProductAuthorize(authorize); // authorizeService.boundProductAuthorize(authorize);
return ResponseEntity.ok().body("ok"); return ResponseEntity.ok().body("ok");
} }
} }

View File

@@ -0,0 +1,76 @@
package com.ruoyi.iot.init;
//import com.ruoyi.mysql.config.TDengineConfig;
//import com.ruoyi.iot.tdengine.config.TDengineConfig;
import com.alibaba.druid.pool.DruidDataSource;
import com.ruoyi.iot.tdengine.config.TDengineConfig;
import com.ruoyi.iot.domain.DeviceLog;
import com.ruoyi.iot.mapper.DeviceMapper;
import com.ruoyi.iot.tdengine.mapper.TDDeviceLogMapper;
import org.mybatis.spring.SqlSessionTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.Date;
/**
* 类名: ApplicationStarted
* 描述: TODO
* 时间: 2022/5/18,0018 1:41
* 开发人: wxy
*/
@Component
public class ApplicationStarted implements ApplicationRunner {
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationStarted.class);
@Autowired
private TDengineConfig dengineConfig;
@Autowired
private TDDeviceLogMapper deviceLogMapper;
@Autowired
private DeviceMapper deviceMapper;
@Override
public void run(ApplicationArguments args) {
deviceMapper.selectDeviceByDeviceId(0L);
System.out.println("初始化MySql链接成功");
initTDengine();
System.out.println("初始化TDengine链接成功");
}
/**
* @Method
* @Description 开始初始化加载系统参数,创建数据库和超级表
* @Param null
* @return
* @date 2022/5/22,0022 14:27
* @author wxy
*
*/
public void initTDengine() {
try {
String dbName = dengineConfig.getDbName();
int db = deviceLogMapper.createDB(dbName);
deviceLogMapper.createSTable(dbName);
System.out.println(db);
} catch (Exception e) {
e.printStackTrace();
System.out.println("ERROR");
}
}
}

View File

@@ -0,0 +1,25 @@
package com.ruoyi.iot.log.service;
import com.ruoyi.iot.domain.DeviceLog;
import com.ruoyi.iot.model.MonitorModel;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
/**
* @package iot.iot.log
* 类名: LogService
* 描述: 设备日志记录接口
* 时间: 2022/5/19,0019 18:04
* 开发人: admin
*/
public interface ILogService {
int saveDeviceLog(DeviceLog deviceLog);
List<DeviceLog> selectDeviceLogList(DeviceLog deviceLog);
List<MonitorModel> selectMonitorList(DeviceLog deviceLog);
}

View File

@@ -0,0 +1,74 @@
package com.ruoyi.iot.log.service.factory;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.framework.config.MyBatisConfig;
import com.ruoyi.iot.log.service.impl.MySqlLogServiceImpl;
import com.ruoyi.iot.log.service.impl.TdengineLogServiceImpl;
import com.ruoyi.iot.model.MonitorModel;
import com.ruoyi.iot.tdengine.config.TDengineConfig;
import com.ruoyi.iot.domain.DeviceLog;
import com.ruoyi.iot.log.service.ILogService;
import com.ruoyi.iot.mapper.DeviceLogMapper;
import com.ruoyi.iot.tdengine.mapper.TDDeviceLogMapper;
import com.ruoyi.iot.util.SnowflakeIdWorker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 类名: DeviceLogServiceImpl
* 描述: TODO
* 时间: 2022/5/19,0019 18:09
* 开发人: wxy
*/
@Component
public class LogServiceFactory {
@Autowired
private ApplicationContext applicationContext;
@Bean
public ILogService getLogService() {
//先获取TDengine的配置检测TDengine是否已经配置
if (containBean(TDengineConfig.class)) {
TDengineConfig tDengineConfig = applicationContext.getBean(TDengineConfig.class);
TDDeviceLogMapper tDDeviceLogMapper = applicationContext.getBean(TDDeviceLogMapper.class);
ILogService logService = new TdengineLogServiceImpl(tDengineConfig, tDDeviceLogMapper);
return logService;
} else if (containBean(MyBatisConfig.class)) {
//没有配置TDengine那么使用MySQL的日志配置
DeviceLogMapper deviceLogMapper = applicationContext.getBean( DeviceLogMapper.class);
ILogService logService = new MySqlLogServiceImpl(deviceLogMapper);
return logService;
} else {
return null;
}
}
/**
* @Method containBean
* @Description 根据类判断是否有对应bean
* @Param 类
* @return
* @date 2022/5/22,0022 14:12
* @author wxy
*
*/
private boolean containBean(@Nullable Class<?> T) {
String[] beans = applicationContext.getBeanNamesForType(T);
if (beans == null || beans.length == 0) {
return false;
} else {
return true;
}
}
}

View File

@@ -0,0 +1,38 @@
package com.ruoyi.iot.log.service.impl;
import com.ruoyi.iot.domain.DeviceLog;
import com.ruoyi.iot.log.service.ILogService;
import com.ruoyi.iot.mapper.DeviceLogMapper;
import com.ruoyi.iot.model.MonitorModel;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 类名: MySqlLogServiceImpl
* 描述: MySQL存储日志实现类
* 时间: 2022/5/22,0022 13:37
* 开发人: admin
*/
public class MySqlLogServiceImpl implements ILogService {
private DeviceLogMapper deviceLogMapper;
public MySqlLogServiceImpl(DeviceLogMapper _deviceLogMapper){
this.deviceLogMapper=_deviceLogMapper;
}
@Override
public int saveDeviceLog(DeviceLog deviceLog) {
return deviceLogMapper.insertDeviceLog(deviceLog);
}
@Override
public List<DeviceLog> selectDeviceLogList(DeviceLog deviceLog) {
return deviceLogMapper.selectDeviceLogList(deviceLog);
}
@Override
public List<MonitorModel> selectMonitorList(DeviceLog deviceLog) {
return deviceLogMapper.selectMonitorList(deviceLog);
}
}

View File

@@ -0,0 +1,57 @@
package com.ruoyi.iot.log.service.impl;
import com.ruoyi.iot.domain.DeviceLog;
import com.ruoyi.iot.log.service.ILogService;
import com.ruoyi.iot.model.MonitorModel;
import com.ruoyi.iot.tdengine.config.TDengineConfig;
import com.ruoyi.iot.tdengine.mapper.TDDeviceLogMapper;
import com.ruoyi.iot.util.SnowflakeIdWorker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 类名: TdengineLogServiceImpl
* 描述: TDengine存储日志数据实现类
* 时间: 2022/5/22,0022 13:38
* 开发人: admin
*/
public class TdengineLogServiceImpl implements ILogService {
private ApplicationContext applicationContext;
private TDDeviceLogMapper tdDeviceLogMapper;
private TDengineConfig tDengineConfig;
private SnowflakeIdWorker snowflakeIdWorker;
private String dbName;
public TdengineLogServiceImpl(TDengineConfig _tDengineConfig,TDDeviceLogMapper _tdDeviceLogMapper) {
this.tdDeviceLogMapper = _tdDeviceLogMapper;
this.tDengineConfig = _tDengineConfig;
snowflakeIdWorker=new SnowflakeIdWorker(1);
this.dbName=_tDengineConfig.getDbName();
}
@Override
public int saveDeviceLog(DeviceLog deviceLog) {
long logId = snowflakeIdWorker.nextId();
deviceLog.setLogId(logId);
return tdDeviceLogMapper.save(dbName,deviceLog);
}
@Override
public List<DeviceLog> selectDeviceLogList(DeviceLog deviceLog) {
return tdDeviceLogMapper.selectDeviceLogList(dbName,deviceLog);
}
@Override
public List<MonitorModel> selectMonitorList(DeviceLog deviceLog) {
return tdDeviceLogMapper.selectMonitorList(dbName,deviceLog);
}
}

View File

@@ -79,7 +79,7 @@ public class EmqxClient {
token.waitForCompletion(); token.waitForCompletion();
}catch (Exception e){ }catch (Exception e){
logger.error("=====>>>>>mqtt连接失败 message={}",e.getMessage()); logger.error("=====>>>>>mqtt连接失败 message={}",e.getMessage());
throw new ServiceException("mqtt客户端连接错误"+e.getMessage()); // e.printStackTrace();
} }
} }
} }
@@ -98,7 +98,7 @@ public class EmqxClient {
logger.debug("====>>>mqtt客户端启动成功"); logger.debug("====>>>mqtt客户端启动成功");
}catch (MqttException e){ }catch (MqttException e){
logger.error("mqtt客户端连接错误 error={}",e.getMessage()); logger.error("mqtt客户端连接错误 error={}",e.getMessage());
throw new ServiceException("mqtt客户端连接错误"+e.getMessage()); // e.printStackTrace();
} }
} }
} }

View File

@@ -3,13 +3,16 @@ package com.ruoyi.iot.mqtt;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.ruoyi.iot.domain.Device; import com.ruoyi.iot.domain.Device;
import com.ruoyi.iot.domain.DeviceLog; import com.ruoyi.iot.domain.DeviceLog;
import com.ruoyi.iot.tdengine.service.ILogService; import com.ruoyi.iot.log.service.ILogService;
import com.ruoyi.iot.model.NtpModel; import com.ruoyi.iot.model.NtpModel;
import com.ruoyi.iot.model.ThingsModels.IdentityAndName; import com.ruoyi.iot.model.ThingsModels.IdentityAndName;
import com.ruoyi.iot.model.ThingsModels.ThingsModelValueItem;
import com.ruoyi.iot.model.ThingsModels.ThingsModelValueRemarkItem; import com.ruoyi.iot.model.ThingsModels.ThingsModelValueRemarkItem;
import com.ruoyi.iot.model.ThingsModels.ThingsModelValuesInput; import com.ruoyi.iot.model.ThingsModels.ThingsModelValuesInput;
import com.ruoyi.iot.service.IDeviceLogService;
import com.ruoyi.iot.service.IDeviceService; import com.ruoyi.iot.service.IDeviceService;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.slf4j.Logger; import org.slf4j.Logger;

View File

@@ -2,7 +2,7 @@ package com.ruoyi.iot.tdengine.config;
import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSource;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.iot.tdengine.dao.TDDeviceLogMapper; import com.ruoyi.iot.tdengine.mapper.TDDeviceLogMapper;
import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.SqlSessionTemplate;
@@ -31,7 +31,7 @@ import java.util.List;
* 开发人: wxy * 开发人: wxy
*/ */
@Configuration @Configuration
@MapperScan(basePackages = {"com.ruoyi.iot.tdengine.dao"}, sqlSessionTemplateRef = "tdengineSqlSessionTemplate") @MapperScan(basePackages = {"com.ruoyi.iot.tdengine.mapper"}, sqlSessionTemplateRef = "tdengineSqlSessionTemplate")
@ConditionalOnProperty(name = "spring.datasource.druid.tdengine-server.enabled", havingValue = "true") @ConditionalOnProperty(name = "spring.datasource.druid.tdengine-server.enabled", havingValue = "true")
public class TDengineConfig { public class TDengineConfig {
@@ -64,6 +64,16 @@ public class TDengineConfig {
return new SqlSessionTemplate(sqlSessionFactory); return new SqlSessionTemplate(sqlSessionFactory);
} }
// @Bean
// @ConditionalOnProperty(name = "spring.datasource.druid.tdengine-server.enabled", havingValue = "true")
// public TDDeviceLogMapper genTdengineLogMapper(@Qualifier("tDengineDataSource") TDDeviceLogMapper mapper) {
// if(this.deviceLogMapper==null){
// this.deviceLogMapper=mapper;
// }
// return this.deviceLogMapper;
// }
public TDDeviceLogMapper getTDengineLogMapper() { public TDDeviceLogMapper getTDengineLogMapper() {
return deviceLogMapper; return deviceLogMapper;
} }

View File

@@ -0,0 +1,25 @@
package com.ruoyi.iot.tdengine.mapper;
import org.springframework.stereotype.Repository;
/**
* @package com.ruoyi.mysql.mysql.tdengine
* 类名: DatabaseMapper
* 描述: TODO
* 时间: 2022/5/16,0016 1:27
* 开发人: wxy
*/
@Repository
public interface DatabaseMapper {
int createDB();
int dropDatabase();
int useDatabase();
int createTable();
}

View File

@@ -0,0 +1,42 @@
package com.ruoyi.iot.tdengine.mapper;
import com.ruoyi.iot.domain.DeviceLog;
import com.ruoyi.iot.model.MonitorModel;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.lang.management.MonitorInfo;
import java.util.Date;
import java.util.List;
/**
* @package com.ruoyi.mysql.mysql.tdengine
* 类名: DatabaseMapper
* 描述: TODO
* 时间: 2022/5/16,0016 1:27
* 开发人: wxy
*/
@Repository
public interface TDDeviceLogMapper {
int createDB( String database);
int createSTable(String database);
int createTable(String database,String deviceId);
int save(@Param("database") String database,@Param("device") DeviceLog deviceLog);
List<DeviceLog> selectSTable(String database,DeviceLog deviceLog);
int delete(String dbName, DeviceLog deviceLog);
// List<DeviceLog> selectLogListByPage(String dbName, Integer pageSize, Integer pageNum, String deviceNum, Date beginDate, Date endDate);
List<DeviceLog> selectLogList(String dbName, Long deviceId, String serialNumber, Long isMonitor, Long logType, Date beginDate, Date endDate);
List<MonitorModel> selectMonitorList(@Param("database") String database, @Param("device") DeviceLog deviceLog);
List<DeviceLog> selectDeviceLogList(@Param("database") String database,@Param("device") DeviceLog deviceLog);
}

View File

@@ -2,7 +2,7 @@
<!DOCTYPE mapper <!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.iot.tdengine.dao.DatabaseMapper"> <mapper namespace="com.ruoyi.iot.tdengine.mapper.DatabaseMapper">
<!-- 创建数据库--> <!-- 创建数据库-->
<update id="createDB" parameterType="java.lang.String"> <update id="createDB" parameterType="java.lang.String">

View File

@@ -2,7 +2,7 @@
<!DOCTYPE mapper <!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.iot.tdengine.dao.TDDeviceLogMapper"> <mapper namespace="com.ruoyi.iot.tdengine.mapper.TDDeviceLogMapper">
<resultMap id="BaseResultMap" type="com.ruoyi.iot.domain.DeviceLog"> <resultMap id="BaseResultMap" type="com.ruoyi.iot.domain.DeviceLog">
<id column="ts" property="ts"/> <id column="ts" property="ts"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -1,5 +1,45 @@
<template> <template>
<div class="app-container home"> <div class="app-container home">
<el-row :gutter="40" style="margin-bottom:10px;">
<el-col :xs="24" :sm="24" :md="24" :lg="14" :xl="14">
<el-card shadow="hover" style="margin:-10px;margin-bottom:20px;font-size:14px;min-height:170px;">
<div style="font-size:16px;font-weight:bold;margin-bottom:18px;">物美智能生活物联网平台</div>
<div style="display:table;margin-bottom:10px;">
<div style="width:70px;font-weight: bold;display:table-cell;">个人用户</div>
<div style="display:table-cell;line-height:22px;"><b style="color:#F56C6C">可用于个人学习和使用非商业用途</b>不提供移动端源码后期可以通过两种方式使用移动端1.免费托管设备到平台2.使用移动端开放的接口连接自己搭建的系统</div>
</div>
<div style="display:table;">
<div style="width:70px;font-weight: bold;display:table-cell;">企业用户</div>
<div style="display:table-cell;line-height:22px;"><b style="color:#F56C6C">可用于商业用途并提供移动端源码通过赞助方式获取企业授权</b>但是不能低价或批量转售源码不能随意分发源码目前赞助费为1500元项目不断完善后会对应增加费用</div>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="10" :xl="10">
<el-card shadow="hover" style="margin:-10px;margin-bottom:20px;font-size:14px;min-height:170px;">
<div style="float:left;width:170px;">
<el-image style="width:154px;" :src="require('@/assets/images/code.jpg')"></el-image>
</div>
<div style="float:left;">
<div style="font-size:16px;font-weight:bold;margin:16px 0;">微信扫一扫查看小程序端</div>
<div style="display:table;margin-bottom:10px;">
<div style="width:70px;font-weight: bold;display:table-cell;">官方网站</div>
<div style="display:table-cell;">
<span>www.wumei.live</span>
</div>
</div>
<div style="display:table;margin-bottom:15px;">
<div style="width:70px;font-weight: bold;display:table-cell;">系统源码</div>
<div style="display:table-cell;">
<el-link target="_blank" href="https://gitee.com/kerwincui/wumei-smart" type="danger">Gitee源码</el-link>
<el-link target="_blank" href="https://github.com/kerwincui/wumei-smart" style="margin-left:20px;">Github源码</el-link>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="40" style="margin-bottom:80px;"> <el-row :gutter="40" style="margin-bottom:80px;">
<el-col :xs="24" :sm="24" :md="24" :lg="14" :xl="14"> <el-col :xs="24" :sm="24" :md="24" :lg="14" :xl="14">
<el-card style="margin:-10px;" shadow="hover" body-style="background-color:#F8F8F8;"> <el-card style="margin:-10px;" shadow="hover" body-style="background-color:#F8F8F8;">
@@ -141,13 +181,6 @@
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<div style="margin:-21px;margin-top:100px;bottom:0;border:1px solid #ccc;padding:10px;margin-bottom:-21px;">
<div>
<el-link href="http://wumei.live" target="_blank" type="primary" style="margin-left:20px;">开源生活物联网平台 >></el-link>
<el-link href="https://github.com/kerwincui/wumei-smart" target="_blank" type="danger" style="margin-left:30px;">Github源码 >></el-link>
<el-link href="https://gitee.com/kerwincui/wumei-smart" target="_blank" type="success" style="margin-left:30px;">码云源码 >></el-link>
</div>
</div>
</div> </div>
</template> </template>

View File

@@ -1,7 +1,6 @@
<template> <template>
<div style="padding: 6px"> <div style="padding: 6px">
<el-card v-show="showSearch" style="margin-bottom: 6px"> <el-card v-show="showSearch" style="margin-bottom: 6px">
<div style="height:50px; color:#F56C6C;margin-left:20px;">该功能下个版本发布</div>
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px" style="margin-bottom:-20px;"> <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px" style="margin-bottom:-20px;">
<el-form-item label="客户端ID" prop="clientId"> <el-form-item label="客户端ID" prop="clientId">
<el-input v-model="queryParams.clientId" placeholder="请输入客户端ID" clearable size="small" @keyup.enter.native="handleQuery" /> <el-input v-model="queryParams.clientId" placeholder="请输入客户端ID" clearable size="small" @keyup.enter.native="handleQuery" />
@@ -14,6 +13,7 @@
<el-form-item> <el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
<el-tag type="danger" style="margin-left:15px;">该功能暂不可用,后面版本发布</el-tag>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card> </el-card>

View File

@@ -392,6 +392,7 @@ export default {
mqttSubscribe(list) { mqttSubscribe(list) {
// 订阅当前页面设备状态和实时监测 // 订阅当前页面设备状态和实时监测
let topics = []; let topics = [];
// 订阅数太多会导致emqx连接中断或者订阅缓慢
for (let i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
let topicStatus = "/" + list[i].productId + "/" + list[i].serialNumber + "/status/post"; let topicStatus = "/" + list[i].productId + "/" + list[i].serialNumber + "/status/post";
let topicMonitor = "/" + list[i].productId + "/" + list[i].serialNumber + "/monitor/post"; let topicMonitor = "/" + list[i].productId + "/" + list[i].serialNumber + "/monitor/post";
@@ -451,9 +452,9 @@ export default {
this.queryParams.params["endActiveTime"] = this.daterangeActiveTime[1]; this.queryParams.params["endActiveTime"] = this.daterangeActiveTime[1];
} }
// 判断是否是admin角色 // 判断是否是admin角色
if (this.$store.state.user.roles.indexOf("admin") === -1) { // if (this.$store.state.user.roles.indexOf("admin") === -1) {
this.queryParams.userId = this.$store.state.user.userId // this.queryParams.userId = this.$store.state.user.userId
} // }
listDeviceShort(this.queryParams).then(response => { listDeviceShort(this.queryParams).then(response => {
this.deviceList = response.rows; this.deviceList = response.rows;
this.total = response.total; this.total = response.total;
@@ -519,6 +520,7 @@ export default {
// 筛选监测数据 // 筛选监测数据
this.monitorThings = thingsModel.properties.filter(item => item.isMonitor == 1); this.monitorThings = thingsModel.properties.filter(item => item.isMonitor == 1);
// 监测数据集合初始化 // 监测数据集合初始化
this.dataList=[];
for (let i = 0; i < this.monitorThings.length; i++) { for (let i = 0; i < this.monitorThings.length; i++) {
this.dataList.push({ this.dataList.push({
id: this.monitorThings[i].id, id: this.monitorThings[i].id,

View File

@@ -30,8 +30,7 @@ export default {
} }
}, },
data() { data() {
return { return {};
};
}, },
created() { created() {
@@ -39,12 +38,13 @@ export default {
methods: { methods: {
/** 连接Mqtt */ /** 连接Mqtt */
connectMqtt(subscribeTopics) { connectMqtt(subscribeTopics) {
let randomClientId='web-' + Math.random().toString(16).substr(2);
let options = { let options = {
username: "wumei-smart", username: "wumei-smart",
password: getToken(), password: getToken(),
cleanSession: false, cleanSession: false,
keepAlive: 30, keepAlive: 30,
clientId: 'web-' + Math.random().toString(16).substr(2), clientId: randomClientId,
connectTimeout: 10000 connectTimeout: 10000
} }
// 配置Mqtt地址 // 配置Mqtt地址
@@ -53,8 +53,9 @@ export default {
console.log("mqtt地址", url); console.log("mqtt地址", url);
this.client = mqtt.connect(url, options); this.client = mqtt.connect(url, options);
this.client.on("connect", (e) => { this.client.on("connect", (e) => {
console.log("成功连接服务器:", e); console.log("客户端:"+randomClientId+"成功连接服务器:", e);
// 订阅主题 // 订阅主题
if (subscribeTopics != '' && subscribeTopics.length > 0) {
this.client.subscribe(subscribeTopics, { this.client.subscribe(subscribeTopics, {
qos: 1 qos: 1
}, (err) => { }, (err) => {
@@ -65,6 +66,7 @@ export default {
console.log('消息订阅失败!') console.log('消息订阅失败!')
} }
}); });
}
}); });
// 重新连接 // 重新连接
this.reconnectMqtt() this.reconnectMqtt()

View File

@@ -2,8 +2,8 @@
<div style="padding: 6px"> <div style="padding: 6px">
<el-card v-show="showSearch" style="margin-bottom: 6px"> <el-card v-show="showSearch" style="margin-bottom: 6px">
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px" style="margin-bottom: -20px"> <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px" style="margin-bottom: -20px">
<el-form-item label="客户端" prop="categoryName"> <el-form-item label="客户端" prop="clientid">
<el-input v-model="queryParams.categoryName" placeholder="请输入客户端ID" clearable size="small" @keyup.enter.native="handleQuery" /> <el-input v-model="queryParams.clientid" placeholder="请输入客户端ID" clearable size="small" @keyup.enter.native="handleQuery" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@@ -43,7 +43,7 @@
<el-table-column label="会话创建时间" align="center" prop="created_at" /> <el-table-column label="会话创建时间" align="center" prop="created_at" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="small" type="danger" v-if="scope.row.connected" style="padding: 5px" v-hasPermi="['monitor:client:edit']" @click="handleDelete(scope.row)"> <el-button size="small" type="danger" v-if="scope.row.connected" style="padding: 5px" v-hasPermi="['iot:product:remove']" @click="handleDelete(scope.row)">
<svg-icon icon-class="disconnect" /> 断开连接 <svg-icon icon-class="disconnect" /> 断开连接
</el-button> </el-button>
</template> </template>
@@ -53,16 +53,14 @@
<pagination v-show="total > 0" :total="total" :page.sync="queryParams._page" :limit.sync="queryParams._limit" @pagination="getList" /> <pagination v-show="total > 0" :total="total" :page.sync="queryParams._page" :limit.sync="queryParams._limit" @pagination="getList" />
<!-- MQTT客户端详细 --> <!-- MQTT客户端详细 -->
<el-dialog :title="title" :visible.sync="open" width="50%" append-to-body> <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
<el-tabs v-model="activeName" tab-position="top" style="padding: 10px"> <el-tabs v-model="activeName" tab-position="top" style="padding: 10px">
<el-tab-pane name="basic"> <el-tab-pane name="basic">
<span slot="label">基本信息</span> <span slot="label">基本信息</span>
<el-form ref="form" :model="form" label-width="120px" size="mini"> <el-descriptions class="margin-top" :column="2" border size="medium">
<el-descriptions class="margin-top" title="基本信息" :column="4" direction="vertical">
<el-descriptions-item label="节点">{{form.node }}</el-descriptions-item> <el-descriptions-item label="节点">{{form.node }}</el-descriptions-item>
<el-descriptions-item label="客户端ID">{{form.clientid}}</el-descriptions-item> <el-descriptions-item label="客户端ID">{{form.clientid}}</el-descriptions-item>
<el-descriptions-item label="Clean Session">{{form.clean_start}}</el-descriptions-item> <el-descriptions-item label="清除Session">{{form.clean_start}}</el-descriptions-item>
<el-descriptions-item label="会话过期间隔(秒)">{{form.expiry_interval}}</el-descriptions-item> <el-descriptions-item label="会话过期间隔(秒)">{{form.expiry_interval}}</el-descriptions-item>
<el-descriptions-item label="用户名">{{form.username}}</el-descriptions-item> <el-descriptions-item label="用户名">{{form.username}}</el-descriptions-item>
<el-descriptions-item label="协议类型">{{form.proto_ver}}</el-descriptions-item> <el-descriptions-item label="协议类型">{{form.proto_ver}}</el-descriptions-item>
@@ -93,14 +91,11 @@
<el-descriptions-item label="接收的PUBLISH报文数量">{{form.recv_msg}}</el-descriptions-item> <el-descriptions-item label="接收的PUBLISH报文数量">{{form.recv_msg}}</el-descriptions-item>
<el-descriptions-item label="接收的字节数量">{{form.recv_oct}}</el-descriptions-item> <el-descriptions-item label="接收的字节数量">{{form.recv_oct}}</el-descriptions-item>
<el-descriptions-item label="接收的MQTT报文数量">{{form.recv_pkt}}</el-descriptions-item> <el-descriptions-item label="接收的MQTT报文数量">{{form.recv_pkt}}</el-descriptions-item>
<el-descriptions-item label="发送的TCP报文数量">{{form.send_cnt}}</el-descriptions-item> <el-descriptions-item label="发送的TCP报文数量">{{form.send_cnt}}</el-descriptions-item>
<el-descriptions-item label="发送的PUBLISH报文数量">{{form.send_msg}}</el-descriptions-item> <el-descriptions-item label="发送的PUBLISH报文数量">{{form.send_msg}}</el-descriptions-item>
<el-descriptions-item label="发送的字节数量">{{form.send_oct}}</el-descriptions-item> <el-descriptions-item label="发送的字节数量">{{form.send_oct}}</el-descriptions-item>
<el-descriptions-item label="发送的MQTT报文数量">{{form.send_pkt}}</el-descriptions-item> <el-descriptions-item label="发送的MQTT报文数量">{{form.send_pkt}}</el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-form>
</el-tab-pane> </el-tab-pane>
<el-tab-pane name="subscribe"> <el-tab-pane name="subscribe">
@@ -190,6 +185,7 @@ export default {
queryParams: { queryParams: {
_limit: 10, _limit: 10,
_page: 1, _page: 1,
clientid:null,
}, },
// 表单参数 // 表单参数
form: {}, form: {},

View File

@@ -14,10 +14,10 @@
<el-table-column label="描述" align="left" prop="description" /> <el-table-column label="描述" align="left" prop="description" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="small" type="success" style="padding:5px;" @click="loadMqttPlugin(scope.row.name)" v-if="!scope.row.active" v-hasPermi="['monitor:online:edit']"> <el-button size="small" type="success" style="padding:5px;" @click="loadMqttPlugin(scope.row.name)" v-if="!scope.row.active" v-hasPermi="['iot:product:remove']">
<svg-icon icon-class="start" /> 启动 <svg-icon icon-class="start" /> 启动
</el-button> </el-button>
<el-button size="small" type="danger" style="padding:5px;" @click="unloadMqttPlugin(scope.row.name)" v-else v-hasPermi="['monitor:online:edit']"> <el-button size="small" type="danger" style="padding:5px;" @click="unloadMqttPlugin(scope.row.name)" v-else v-hasPermi="['iot:product:remove']">
<svg-icon icon-class="stop" /> 停止 <svg-icon icon-class="stop" /> 停止
</el-button> </el-button>
</template> </template>

View File

@@ -12,19 +12,16 @@
<el-table v-loading="loading" :data="resourceList"> <el-table v-loading="loading" :data="resourceList">
<el-table-column label="ID" align="center" header-align="center" prop="id"> <el-table-column label="ID" align="center" header-align="center" prop="id">
<template slot-scope="scope"> <template slot-scope="scope">
<el-link :underline="false" type="primary">{{ scope.row.id }}</el-link> <el-link :underline="false" type="primary" @click="handleQuery(scope.row)">{{ scope.row.id }}</el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="资源类型" align="center" prop="type" /> <el-table-column label="资源类型" align="center" prop="type" />
<el-table-column label="备注" align="center" prop="description" /> <el-table-column label="备注" align="center" prop="description" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" width="200">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="small" type="text" style="padding: 5px" v-hasPermi="['monitor:resource:query']" @click="handleQuery(scope.row)"> <el-button size="small" type="text" icon="el-icon-connection" style="padding: 5px" v-hasPermi="['monitor:resource:checkStatus']" @click="checkStatus(scope.row)">状态
查看
</el-button> </el-button>
<el-button size="small" type="text" icon="el-icon-delete" style="padding: 5px" v-hasPermi="['monitor:resource:delete']" @click="handleDelete(scope.row)">删除 <el-button size="small" type="text" icon="el-icon-delete" style="padding: 5px" v-hasPermi="['iot:product:remove']" @click="handleDelete(scope.row)">删除
</el-button>
<el-button size="small" type="text" icon="el-icon-delete" style="padding: 5px" v-hasPermi="['monitor:resource:checkStatus']" @click="checkStatus(scope.row)">状态
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
@@ -32,7 +29,7 @@
</el-card> </el-card>
<!-- 资源详细 --> <!-- 资源详细 -->
<el-dialog title="资源详细" :visible.sync="openView" width="50%" append-to-body> <el-dialog title="资源详细" :visible.sync="openView" width="800px" append-to-body>
<el-form ref="form" :model="form" label-width="180px" size="mini"> <el-form ref="form" :model="form" label-width="180px" size="mini">
<el-card style="padding-bottom: 10px"> <el-card style="padding-bottom: 10px">
<div slot="header" class="clearfix"> <div slot="header" class="clearfix">
@@ -96,22 +93,18 @@
</el-dialog> </el-dialog>
<!-- 测试重连 --> <!-- 测试重连 -->
<el-dialog title="检测状态" :visible.sync="openStatusView" width="40%" append-to-body> <el-dialog title="检测状态" :visible.sync="openStatusView" width="600px" append-to-body>
<el-form ref="statusForm" :model="statusForm" label-width="180px" size="mini"> <el-form ref="statusForm" :model="statusForm" label-width="180px" size="mini" v-if="statusForm.status[0]">
<el-row>
<el-col :span="12" v-if="statusForm.status[0]">
{{ statusForm.status[0].node }} {{ statusForm.status[0].node }}
<el-tag type="success" v-if="statusForm.status[0].is_alive == true" style="margin-left: 10px">可用</el-tag> <el-tag type="success" v-if="statusForm.status[0].is_alive == true" style="margin-left: 10px">可用</el-tag>
<el-tag type="danger" v-if="statusForm.status[0].is_alive == false" style="margin-left: 10px">不可用</el-tag> <el-tag type="danger" v-if="statusForm.status[0].is_alive == false" style="margin-left: 10px">不可用</el-tag>
<el-button size="small" type="primary" icon="el-icon-connection" style="padding: 5px; margin-left: 10px" v-hasPermi="['monitor:resource:connect']" @click="checkNode(statusForm.id)">重新连接 <el-button size="small" type="primary" icon="el-icon-connection" style="padding: 5px; margin-left: 10px" v-hasPermi="['monitor:resource:connect']" @click="checkNode(statusForm.id)">重新连接
</el-button> </el-button>
</el-col>
</el-row>
</el-form> </el-form>
</el-dialog> </el-dialog>
<!-- 添加资源 --> <!-- 添加资源 -->
<el-dialog title="资源管理" :visible.sync="openAddView" width="50%" append-to-body :before-close="cancel"> <el-dialog title="资源管理" :visible.sync="openAddView" width="800px" append-to-body :before-close="cancel">
<el-form ref="addResourceForm" :model="addResourceForm" label-width="180px" :rules="rule"> <el-form ref="addResourceForm" :model="addResourceForm" label-width="180px" :rules="rule">
<el-card style="padding-bottom: 10px"> <el-card style="padding-bottom: 10px">
<div slot="header" class="clearfix"> <div slot="header" class="clearfix">

View File

@@ -10,37 +10,34 @@
</el-col> </el-col>
</el-row> </el-row>
<el-table v-loading="loading" :data="rulesList"> <el-table v-loading="loading" :data="rulesList">
<el-table-column label="ID" align="center" header-align="center" prop="id"> <el-table-column label="ID" align="center" header-align="center" prop="id" width="150">
<template slot-scope="scope"> <template slot-scope="scope">
<el-link :underline="false" type="primary">{{scope.row.id }}</el-link> <el-link :underline="false" type="primary" @click="handleQuery(scope.row)">{{scope.row.id }}</el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="主题" align="center" prop="for" width="120"> <el-table-column label="主题" align="center" prop="for">
<template slot-scope="scope"> <template slot-scope="scope">
<p v-for="(topic, index) in scope.row.for" :key="index"> <p v-for="(topic, index) in scope.row.for" :key="index">
<el-tag type="success">{{ topic }}</el-tag> <el-tag type="warning">{{ topic }}</el-tag>
</p> </p>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="SQL" align="center" prop="rawsql" /> <el-table-column label="SQL" align="center" prop="rawsql" />
<el-table-column label="响应动作" align="center" prop="actions"> <el-table-column label="响应动作" align="center" prop="actions" width="250">
<template slot-scope="scope"> <template slot-scope="scope">
<p v-for="(action, index) in scope.row.actions" :key="index"> <p v-for="(action, index) in scope.row.actions" :key="index">
<el-tag type="success">{{ action.name }}</el-tag> <el-tag type="success">{{ action.name }}</el-tag>
</p> </p>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="已命中" align="center" prop="matched"> <el-table-column label="已命中" align="center" prop="matched" width="100">
<template slot-scope="scope"> <template slot-scope="scope">
{{ scope.row.metrics[0].matched }} {{ scope.row.metrics[0].matched }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="small" type="text" style="padding: 5px" v-hasPermi="['monitor:rules:query']" @click="handleQuery(scope.row)"> <el-button size="small" type="danger" icon="el-icon-delete" style="padding: 5px" v-hasPermi="['iot:product:remove']" @click="handleDelete(scope.row)">删除
查看
</el-button>
<el-button size="small" type="text" icon="el-icon-delete" style="padding: 5px" v-hasPermi="['monitor:rules:delete']" @click="handleDelete(scope.row)">删除
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
@@ -48,7 +45,7 @@
</el-card> </el-card>
<!-- 规则引擎详细 --> <!-- 规则引擎详细 -->
<el-dialog title="规则引擎详细" :visible.sync="openView" width="50%" append-to-body> <el-dialog title="规则引擎详细" :visible.sync="openView" width="800px" append-to-body>
<el-form ref="form" :model="form" label-width="120px" size="mini"> <el-form ref="form" :model="form" label-width="120px" size="mini">
<el-card style="padding-bottom: 10px"> <el-card style="padding-bottom: 10px">
<div slot="header" class="clearfix"> <div slot="header" class="clearfix">
@@ -107,20 +104,18 @@
</el-dialog> </el-dialog>
<!-- 添加规则引擎 --> <!-- 添加规则引擎 -->
<el-dialog title="资源管理" :visible.sync="openAddView" width="60%" append-to-body :before-close="cancel"> <el-dialog title="添加规则引擎" :visible.sync="openAddView" width="1000px" append-to-body :before-close="cancel">
<el-form ref="form" :model="form" label-width="180px"> <el-card style="margin-bottom:10px;">
<el-card style="padding-bottom: 10px">
<div slot="header" class="clearfix"> <div slot="header" class="clearfix">
<span> <span style="font-size:16px;font-weight:bold;">条件</span>
<h2>条件</h2> <span style="font-size:12px;margin-left:12px;">使用 SQL 定义规则条件与数据处理方式</span>
<h6>使用 SQL 定义规则条件与数据处理方式</h6>
</span>
</div> </div>
<el-row :gutter="50"> <el-form ref="form" :model="form" label-width="90px">
<el-col :span="12"> <el-row :gutter="30">
<el-col :span="13">
<el-form-item prop="sql_example"> <el-form-item prop="sql_example">
<span slot="label"> 规则 SQL </span> <span slot="label"> 规则 SQL </span>
<CodeMirrorEditor :value="form.sql_example" myMode="text/x-mysql" height="420" /> <CodeMirrorEditor :value="form.sql_example" myMode="text/x-mysql" height="400" style="border:1px solid #ddd;" />
</el-form-item> </el-form-item>
<el-form-item prop="sql_example"> <el-form-item prop="sql_example">
<span slot="label"> 备注 </span> <span slot="label"> 备注 </span>
@@ -129,57 +124,23 @@
<el-form-item prop="SQLtest"> <el-form-item prop="SQLtest">
<span slot="label"> <span slot="label">
SQL测试 SQL测试
<el-tooltip content="自定义模拟数据进行 SQL 命令测试,仅用于测试功能" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</span> </span>
<el-switch v-model="form.SQLtest" active-text="" inactive-text="" :active-value="true" :inactive-value="false" active-color="#13ce66"> <el-switch v-model="form.SQLtest" active-text="" inactive-text="" :active-value="true" :inactive-value="false" active-color="#13ce66">
</el-switch> </el-switch>
<span style="font-size:12px;margin-left:10px;">自定义模拟数据进行 SQL 命令测试仅用于测试功能</span>
</el-form-item> </el-form-item>
<div v-if="form.SQLtest">
<el-form-item prop="test_columns.username" v-if="form.test_columns">
<span slot="label"> username </span>
<el-input v-model="form.test_columns.username" />
</el-form-item>
<el-form-item prop="test_columns.topic" v-if="form.test_columns">
<span slot="label"> topic </span>
<el-input v-model="form.test_columns.topic" />
</el-form-item>
<el-form-item prop="test_columns.qos" v-if="form.test_columns">
<span slot="label"> qos </span>
<el-input v-model="form.test_columns.qos" />
</el-form-item>
<el-form-item prop="test_columns.clientid" v-if="form.test_columns">
<span slot="label"> clientid </span>
<el-input v-model="form.test_columns.clientid" />
</el-form-item>
<el-form-item prop="test_columns.payload" v-if="form.test_columns">
<span slot="label"> payload </span>
<CodeMirrorEditor :value="form.test_columns.payload" myMode="application/json" height="150" />
</el-form-item>
<el-form-item v-if="form.test_columns">
<el-button @click="testConnect" type="success" size="mini"> </el-button>
</el-form-item>
<el-form-item v-if="form.test_columns">
<span slot="label"> 测试结果 </span>
<textarea style="width: 497px; height: 70px" v-model="content">
</textarea>
</el-form-item>
</div>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="11">
<div class="sql-tips"> <div class="sql-tips">
<div class="title">编写 SQL 进行条件过滤与数据处理</div> <div>编写 SQL 进行条件过滤与数据处理</div>
<div class="el-scrollbar"> <div class="doc-wrapper">
<div class="doc-wrapper" style="margin-bottom: -17px; margin-right: -17px">
<div class="el-scrollbar__view">
<div>
<p> <p>
EMQ X EMQ X
在消息发布事件触发时将触发规则引擎满足触发条件的规则将执行各自的 在消息发布事件触发时将触发规则引擎满足触发条件的规则将执行各自的
SQL 语句筛选并处理消息和事件的上下文信息 SQL 语句筛选并处理消息和事件的上下文信息
</p> </p>
<p class="item"> <p>
规则引擎借助响应动作可将特定主题的消息处理结果存储到数据库发送到 规则引擎借助响应动作可将特定主题的消息处理结果存储到数据库发送到
HTTP Server转发到消息队列 Kafka HTTP Server转发到消息队列 Kafka
RabbitMQ重新发布到新的主题甚至是另一个 Broker RabbitMQ重新发布到新的主题甚至是另一个 Broker
@@ -198,7 +159,7 @@
<div class="code"> <div class="code">
<code>SELECT payload.x as x FROM "t/a"</code> <code>SELECT payload.x as x FROM "t/a"</code>
</div> </div>
<p class="item"> <p>
规则引擎使用 $events/ 开头的虚拟主题事件主题处理 规则引擎使用 $events/ 开头的虚拟主题事件主题处理
EMQ X EMQ X
内置事件内置事件提供更精细的消息控制和客户端动作处理能力可用在 内置事件内置事件提供更精细的消息控制和客户端动作处理能力可用在
@@ -216,19 +177,50 @@
<p>规则引擎和 SQL 语句的详细教程参见 EMQ X 文档</p> <p>规则引擎和 SQL 语句的详细教程参见 EMQ X 文档</p>
</div> </div>
</div> </div>
</div>
</div>
</div>
</el-col> </el-col>
</el-row> </el-row>
</el-card> <el-row v-if="form.SQLtest" style="background-color:#f8f8f8;margin:-20px;">
<el-col :span="13">
<el-form-item prop="test_columns.username" v-if="form.test_columns">
<span slot="label"> username </span>
<el-input v-model="form.test_columns.username" />
</el-form-item>
<el-form-item prop="test_columns.topic" v-if="form.test_columns">
<span slot="label"> topic</span>
<el-input v-model="form.test_columns.topic" />
</el-form-item>
<el-form-item prop="test_columns.payload" v-if="form.test_columns">
<span slot="label"> payload</span>
<CodeMirrorEditor :value="form.test_columns.payload" myMode="application/json" height="150" style="border:1px solid #ddd;" />
</el-form-item>
</el-col>
<el-col :span="11">
<el-form-item prop="test_columns.clientid" v-if="form.test_columns">
<span slot="label"> clientid </span>
<el-input v-model="form.test_columns.clientid" />
</el-form-item>
<el-form-item prop="test_columns.qos" v-if="form.test_columns">
<span slot="label"> qos </span>
<el-input v-model="form.test_columns.qos" />
</el-form-item>
<el-form-item v-if="form.test_columns">
<span slot="label"> 测试结果 </span>
<el-input type="textarea" v-model="content" :rows="4"></el-input>
</el-form-item>
<el-form-item v-if="form.test_columns">
<el-button @click="testConnect" type="success" size="mini" style="width:100px;"> </el-button>
</el-form-item>
</el-col>
</el-row>
</el-form> </el-form>
</el-card>
<el-card style="padding-bottom: 10px"> <el-card style="padding-bottom: 10px">
<div slot="header" class="clearfix"> <div slot="header" class="clearfix">
<span> <div slot="header" class="clearfix">
<h2>响应动作</h2> <span style="font-size:16px;font-weight:bold;">响应动作</span>
<h6>处理命中规则的消息</h6> <span style="font-size:12px;margin-left:12px;">处理命中规则的消息</span>
</span> </div>
</div> </div>
<el-table ref="singleTable" :data="actions" highlight-current-row> <el-table ref="singleTable" :data="actions" highlight-current-row>
<el-table-column property="name" label="类型"> </el-table-column> <el-table-column property="name" label="类型"> </el-table-column>
@@ -254,10 +246,10 @@
</el-dialog> </el-dialog>
<!-- 添加响应动作 --> <!-- 添加响应动作 -->
<el-dialog title="响应动作" :visible.sync="openAddActionView" width="40%" append-to-body :before-close="cancelAction"> <el-dialog title="响应动作" :visible.sync="openAddActionView" width="600px" append-to-body :before-close="cancelAction">
<el-form ref="actionForm" :model="actionForm" label-width="180px" v-if="actionForm.actions" :rules="ruleActions"> <el-form ref="actionForm" :model="actionForm" label-width="180px" v-if="actionForm.actions" :rules="ruleActions">
<el-row> <el-row>
<el-col :span="20"> <el-col>
<el-form-item prop="actions.title"> <el-form-item prop="actions.title">
<span slot="label"> <span slot="label">
动作 动作
@@ -486,8 +478,7 @@ export default {
//检查 (调试)表单参数 //检查 (调试)表单参数
inspectForm: {}, inspectForm: {},
//消息重新发布表单参数 //消息重新发布表单参数
republishForm: { republishForm: {},
},
//桥接数据到 MQTT Broker表单参数 //桥接数据到 MQTT Broker表单参数
data_to_mqtt_broker_Form: { data_to_mqtt_broker_Form: {
resources: [], resources: [],
@@ -762,54 +753,18 @@ export default {
.sql-tips { .sql-tips {
border: 4px dashed #d8d8d8; border: 4px dashed #d8d8d8;
color: #71737d; color: #71737d;
} font-size: 12px;
padding: 10px;
.sql-tips {
padding: 20px 0;
border-radius: 4px; border-radius: 4px;
font-size: 15px; height: 510px;
max-height: 480px;
}
.sql-tips .title {
padding: 0 20px 12px;
}
.el-scrollbar {
overflow: hidden; overflow: hidden;
position: relative; margin-right: -10px;
}
.sql-tips .doc-wrapper {
max-height: 400px;
padding: 0 20px;
}
.sql-tips .el-scrollbar__wrap {
overflow-x: hidden;
}
.el-scrollbar__wrap {
overflow: scroll;
height: 100%;
}
p {
display: block;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
} }
.code { .code {
line-height: 1.4; line-height: 1.4;
padding: 6px; padding: 6px;
border-radius: 4px; border-radius: 4px;
margin-bottom: 12px;
}
.code {
background-color: hsla(0, 0%, 87%, 0.8); background-color: hsla(0, 0%, 87%, 0.8);
} }
</style> </style>

View File

@@ -2,23 +2,13 @@
<div class="app-container"> <div class="app-container">
<el-form v-show="showSearch" ref="queryForm" :inline="true" :model="queryParams" label-width="85px"> <el-form v-show="showSearch" ref="queryForm" :inline="true" :model="queryParams" label-width="85px">
<el-form-item label="第三方平台" prop="platform"> <el-form-item label="第三方平台" prop="platform">
<el-select v-model="queryParams.platform" clearable placeholder="请选择第三方平台" size="small"> <el-select v-model="queryParams.platform" clearable placeholder="请选择平台" size="small">
<el-option <el-option v-for="dict in dict.type.iot_social_platform" :key="dict.value" :label="dict.label" :value="dict.value" />
v-for="dict in dict.type.iot_social_platform"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" clearable placeholder="请选择 0:启用 ,1:禁用" size="small"> <el-select v-model="queryParams.status" clearable placeholder="请选择状态" size="small">
<el-option <el-option v-for="dict in dict.type.iot_social_platform_status" :key="dict.value" :label="dict.label" :value="dict.value" />
v-for="dict in dict.type.iot_social_platform_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@@ -29,49 +19,19 @@
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['iot:platform:add']">新增
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['iot:platform:add']"
>新增
</el-button> </el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate" v-hasPermi="['iot:platform:edit']">修改
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['iot:platform:edit']"
>修改
</el-button> </el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete" v-hasPermi="['iot:platform:remove']">删除
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['iot:platform:remove']"
>删除
</el-button> </el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['iot:platform:export']">导出
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['iot:platform:export']"
>导出
</el-button> </el-button>
</el-col> </el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@@ -79,8 +39,7 @@
<el-table v-loading="loading" :data="platformList" @selection-change="handleSelectionChange"> <el-table v-loading="loading" :data="platformList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" label="平台主键" prop="socialPlatformId" width="75"/> <el-table-column align="center" label="平台名称" prop="platform">
<el-table-column align="center" label="平台名称" prop="platform" width="95">
<template slot-scope="scope"> <template slot-scope="scope">
<dict-tag :options="dict.type.iot_social_platform" :value="scope.row.platform" /> <dict-tag :options="dict.type.iot_social_platform" :value="scope.row.platform" />
</template> </template>
@@ -91,106 +50,62 @@
<dict-tag :options="dict.type.iot_social_platform_status" :value="scope.row.status" /> <dict-tag :options="dict.type.iot_social_platform_status" :value="scope.row.status" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="平台申请Id" align="center" prop="clientId"/> <el-table-column label="平台申请ID" align="center" prop="clientId" />
<el-table-column label="平台密钥" align="center" prop="secretKey" :show-overflow-tooltip="true"/>
<el-table-column label="跳转地址" align="center" prop="redirectUri" width="180" :show-overflow-tooltip="true" /> <el-table-column label="跳转地址" align="center" prop="redirectUri" width="180" :show-overflow-tooltip="true" />
<el-table-column align="center" label="绑定登录uri" prop="bindUri" :show-tooltip-when-overflow="true" :render-header="(h,column)=>renderHeaderMethods(h,column,columnTips.bindId)" />
<el-table-column align="center" label="绑定登录uri" prop="bindUri" :show-tooltip-when-overflow="true" <el-table-column align="center" label="跳转登录uri" prop="redirectLoginUri" :show-tooltip-when-overflow="true" :render-header="(h,column)=>renderHeaderMethods(h,column,columnTips.redirectLogin)" />
:render-header="(h,column)=>renderHeaderMethods(h,column,columnTips.bindId)"/> <el-table-column align="center" label="错误提示uri" prop="errorMsgUri" :show-tooltip-when-overflow="true" :render-header="(h,column)=>renderHeaderMethods(h,column,columnTips.errorId)" />
<el-table-column align="center" label="跳转登录uri" prop="redirectLoginUri" :show-tooltip-when-overflow="true"
:render-header="(h,column)=>renderHeaderMethods(h,column,columnTips.redirectLogin)"/>
<el-table-column align="center" label="错误提示uri" prop="errorMsgUri" :show-tooltip-when-overflow="true"
:render-header="(h,column)=>renderHeaderMethods(h,column,columnTips.errorId)"/>
<el-table-column align="center" label="备注" prop="remark" width="75" :show-tooltip-when-overflow="true"/>
<el-table-column align="center" label="创建者" prop="createBy"/>
<el-table-column align="center" label="创建时间" prop="createTime" width="100"> <el-table-column align="center" label="创建时间" prop="createTime" width="100">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span> <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="更新者" prop="updateBy"/>
<el-table-column align="center" label="更新时间" prop="updateTime" width="100">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['iot:platform:edit']">修改
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['iot:platform:edit']"
>修改
</el-button> </el-button>
<el-button <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['iot:platform:remove']">删除
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['iot:platform:remove']"
>删除
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改第三方登录平台控制对话框 --> <!-- 添加或修改第三方登录平台控制对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px"> <el-form ref="form" :model="form" :rules="rules" label-width="140px">
<el-form-item label="第三方平台" prop="platform"> <el-form-item label="第三方平台名称" prop="platform">
<el-select v-model="form.platform" placeholder="请选择第三方平台"> <el-select v-model="form.platform" placeholder="请选择第三方平台">
<el-option <el-option v-for="dict in dict.type.iot_social_platform" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
v-for="dict in dict.type.iot_social_platform"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label=" 0:启用 ,1:禁用" prop="status"> <el-form-item label="第三方平台状态" prop="status">
<el-select v-model="form.status" placeholder="请选择 0:启用 ,1:禁用"> <el-select v-model="form.status" placeholder="请选择状态">
<el-option <el-option v-for="dict in dict.type.iot_social_platform_status" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
v-for="dict in dict.type.iot_social_platform_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="第三方平台申请Id" prop="clientId"> <el-form-item label="第三方平台申请ID" prop="clientId">
<el-input v-model="form.clientId" placeholder="请输入第三方平台申请Id" /> <el-input v-model="form.clientId" placeholder="请输入第三方平台申请Id" />
</el-form-item> </el-form-item>
<el-form-item label="第三方平台密钥" prop="secretKey"> <el-form-item label="第三方平台密钥" prop="secretKey">
<el-input v-model="form.secretKey" placeholder="请输入第三方平台密钥" /> <el-input v-model="form.secretKey" placeholder="请输入第三方平台密钥" />
</el-form-item> </el-form-item>
<el-form-item label="用户认证跳转地址" prop="redirectUri"> <el-form-item label="用户认证跳转地址" prop="redirectUri">
<el-input v-model="form.redirectUri" placeholder="请输入用户认证后跳转地址" /> <el-input v-model="form.redirectUri" placeholder="请输入用户认证后跳转地址" />
</el-form-item> </el-form-item>
<el-form-item label="删除标记位(0代表存在2代表删除)" prop="delFlag"> <el-form-item label="绑定注册登录URI" prop="bindUri">
<el-input v-model="form.delFlag" placeholder="请输入删除标记位(0代表存在2代表删除)"/> <el-input v-model="form.bindUri" placeholder="请输入绑定注册登录uri,http://localhost/login?bindId=" />
</el-form-item>
<el-form-item label="跳转登录URI" prop="redirectLoginUri">
<el-input v-model="form.redirectLoginUri" placeholder="请输入跳转登录uri,http://localhost/login?loginId=" />
</el-form-item>
<el-form-item label="错误提示URI" prop="errorMsgUri">
<el-input v-model="form.errorMsgUri" placeholder="请输入错误提示uri,http://localhost/login?errorId=" />
</el-form-item> </el-form-item>
<el-form-item label="备注" prop="remark"> <el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入内容" type="textarea" /> <el-input v-model="form.remark" placeholder="请输入内容" type="textarea" />
</el-form-item> </el-form-item>
<el-form-item label="绑定注册登录uri,http://localhost/login?bindId=" prop="bindUri">
<el-input v-model="form.bindUri" placeholder="请输入绑定注册登录uri,http://localhost/login?bindId="/>
</el-form-item>
<el-form-item label="跳转登录uri,http://localhost/login?loginId=" prop="redirectLoginUri">
<el-input v-model="form.redirectLoginUri" placeholder="请输入跳转登录uri,http://localhost/login?loginId="/>
</el-form-item>
<el-form-item label="错误提示uri,http://localhost/login?errorId=" prop="errorMsgUri">
<el-input v-model="form.errorMsgUri" placeholder="请输入错误提示uri,http://localhost/login?errorId="/>
</el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button> <el-button type="primary" @click="submitForm"> </el-button>
@@ -201,7 +116,13 @@
</template> </template>
<script> <script>
import {addPlatform, delPlatform, getPlatform, listPlatform, updatePlatform} from "@/api/iot/platform"; import {
addPlatform,
delPlatform,
getPlatform,
listPlatform,
updatePlatform
} from "@/api/iot/platform";
export default { export default {
name: "Platform", name: "Platform",
@@ -242,30 +163,46 @@ export default {
form: {}, form: {},
// 表单校验 // 表单校验
rules: { rules: {
platform: [ platform: [{
{required: true, message: "第三方平台不能为空", trigger: "change"} required: true,
], message: "第三方平台不能为空",
status: [ trigger: "change"
{required: true, message: " 0:启用 ,1:禁用不能为空", trigger: "change"} }],
], status: [{
clientId: [ required: true,
{required: true, message: "第三方平台申请Id不能为空", trigger: "blur"} message: " 0:启用 ,1:禁用不能为空",
], trigger: "change"
secretKey: [ }],
{required: true, message: "第三方平台密钥不能为空", trigger: "blur"} clientId: [{
], required: true,
redirectUri: [ message: "第三方平台申请Id不能为空",
{required: true, message: "用户认证后跳转地址不能为空", trigger: "blur"} trigger: "blur"
], }],
bindUri: [ secretKey: [{
{required: true, message: "绑定注册登录uri,http://localhost/login?bindId=不能为空", trigger: "blur"} required: true,
], message: "第三方平台密钥不能为空",
redirectLoginUri: [ trigger: "blur"
{required: true, message: "跳转登录uri,http://localhost/login?loginId=不能为空", trigger: "blur"} }],
], redirectUri: [{
errorMsgUri: [ required: true,
{required: true, message: "错误提示uri,http://localhost/login?errorId=不能为空", trigger: "blur"} message: "用户认证后跳转地址不能为空",
] trigger: "blur"
}],
bindUri: [{
required: true,
message: "绑定注册登录uri,http://localhost/login?bindId=不能为空",
trigger: "blur"
}],
redirectLoginUri: [{
required: true,
message: "跳转登录uri,http://localhost/login?loginId=不能为空",
trigger: "blur"
}],
errorMsgUri: [{
required: true,
message: "错误提示uri,http://localhost/login?errorId=不能为空",
trigger: "blur"
}]
} }
}; };
}, },
@@ -273,7 +210,9 @@ export default {
this.getList(); this.getList();
}, },
methods: { methods: {
renderHeaderMethods(h, {column}, content) { renderHeaderMethods(h, {
column
}, content) {
return h( return h(
'div', [ 'div', [
h('span', column.label), h('span', column.label),
@@ -300,14 +239,12 @@ export default {
this.total = response.total; this.total = response.total;
this.loading = false; this.loading = false;
}); });
} },
,
// 取消按钮 // 取消按钮
cancel() { cancel() {
this.open = false; this.open = false;
this.reset(); this.reset();
} },
,
// 表单重置 // 表单重置
reset() { reset() {
this.form = { this.form = {
@@ -317,7 +254,6 @@ export default {
clientId: null, clientId: null,
secretKey: null, secretKey: null,
redirectUri: null, redirectUri: null,
delFlag: null,
createBy: null, createBy: null,
createTime: null, createTime: null,
updateTime: null, updateTime: null,
@@ -328,34 +264,29 @@ export default {
errorMsgUri: null errorMsgUri: null
}; };
this.resetForm("form"); this.resetForm("form");
} },
,
/** 搜索按钮操作 */ /** 搜索按钮操作 */
handleQuery() { handleQuery() {
this.queryParams.pageNum = 1; this.queryParams.pageNum = 1;
this.getList(); this.getList();
} },
,
/** 重置按钮操作 */ /** 重置按钮操作 */
resetQuery() { resetQuery() {
this.resetForm("queryForm"); this.resetForm("queryForm");
this.handleQuery(); this.handleQuery();
} },
,
// 多选框选中数据 // 多选框选中数据
handleSelectionChange(selection) { handleSelectionChange(selection) {
this.ids = selection.map(item => item.socialPlatformId) this.ids = selection.map(item => item.socialPlatformId)
this.single = selection.length !== 1 this.single = selection.length !== 1
this.multiple = !selection.length this.multiple = !selection.length
} },
,
/** 新增按钮操作 */ /** 新增按钮操作 */
handleAdd() { handleAdd() {
this.reset(); this.reset();
this.open = true; this.open = true;
this.title = "添加第三方登录平台控制"; this.title = "添加第三方登录平台控制";
} },
,
/** 修改按钮操作 */ /** 修改按钮操作 */
handleUpdate(row) { handleUpdate(row) {
this.reset(); this.reset();
@@ -365,8 +296,7 @@ export default {
this.open = true; this.open = true;
this.title = "修改第三方登录平台控制"; this.title = "修改第三方登录平台控制";
}); });
} },
,
/** 提交按钮 */ /** 提交按钮 */
submitForm() { submitForm() {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
@@ -386,8 +316,7 @@ export default {
} }
} }
}); });
} },
,
/** 删除按钮操作 */ /** 删除按钮操作 */
handleDelete(row) { handleDelete(row) {
const socialPlatformIds = row.socialPlatformId || this.ids; const socialPlatformIds = row.socialPlatformId || this.ids;
@@ -396,10 +325,8 @@ export default {
}).then(() => { }).then(() => {
this.getList(); this.getList();
this.$modal.msgSuccess("删除成功"); this.$modal.msgSuccess("删除成功");
}).catch(() => { }).catch(() => {});
}); },
}
,
/** 导出按钮操作 */ /** 导出按钮操作 */
handleExport() { handleExport() {
this.download('iot/platform/export', { this.download('iot/platform/export', {

View File

@@ -22,14 +22,12 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码" <el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码" @keyup.enter.native="handleLogin">
@keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="code" v-if="captchaOnOff"> <el-form-item prop="code" v-if="captchaOnOff">
<el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" <el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter.native="handleLogin">
@keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
</el-input> </el-input>
<div class="login-code"> <div class="login-code">
@@ -38,19 +36,17 @@
</el-form-item> </el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;color:#000;">记住密码</el-checkbox> <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;color:#000;">记住密码</el-checkbox>
<el-form-item style="width:100%;"> <el-form-item style="width:100%;">
<el-button v-if="!bindAccount" :loading="loading" size="medium" type="primary" style="width:100%;" <el-button v-if="!bindAccount" :loading="loading" size="medium" type="primary" style="width:100%;" @click.native.prevent="handleLogin">
@click.native.prevent="handleLogin">
<span v-if="!loading"> </span> <span v-if="!loading"> </span>
<span v-else> 中...</span> <span v-else> 中...</span>
</el-button> </el-button>
<el-button v-else :loading="loading" size="medium" type="primary" style="width:100%;" <el-button v-else :loading="loading" size="medium" type="primary" style="width:100%;" @click.native.prevent="handleLogin">
@click.native.prevent="handleLogin">
<span v-if="!loading">绑定</span> <span v-if="!loading">绑定</span>
<span v-else> 中...</span> <span v-else> 中...</span>
</el-button> </el-button>
<div style="margin-top:20px;" v-if="!bindAccount"> <div style="margin-top:30px;" v-if="!bindAccount">
<div v-if="!bindAccount" style=";text-align: center "> <div v-if="!bindAccount" style="float:left;">
<span style="color:#fff;margin-right:10px;">第三方登录</span>
<el-button type="success" circle title="微信登录" size="small" @click="authLogin"> <el-button type="success" circle title="微信登录" size="small" @click="authLogin">
<svg-icon icon-class="wechat" /> <svg-icon icon-class="wechat" />
</el-button> </el-button>
@@ -61,17 +57,13 @@
<el-button type="primary" circle title="支付宝登录" size="small" @click="authLogin"> <el-button type="primary" circle title="支付宝登录" size="small" @click="authLogin">
<svg-icon icon-class="zhifubao" /> <svg-icon icon-class="zhifubao" />
</el-button> </el-button>
</div> </div>
</div> <div style="float:right;" v-if="register">
<a style="margin-right:20px;color:#222" target="_blank" href="http://wumei.live">返回官网</a> <router-link style="color:#333;font-size:16px;" :to='{path:"/register",query: this.$route.query }'>立即注册>>
<div style="float: right;margin-top:10px;" v-if="register">
<router-link style="color:#fff;font-size:16px;" :to='{path:"/register",query: this.$route.query }'>立即注册
>>
</router-link> </router-link>
</div> </div>
</div>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-col> </el-col>
@@ -79,8 +71,7 @@
</div> </div>
<!-- 底部 --> <!-- 底部 -->
<div class="el-login-footer"> <div class="el-login-footer">
<span>Copyright © 2018-2021 <a target="_blank" <span>Copyright © 2018-2021 <a target="_blank" href="http://wumei.live">wumei smart</a> All Rights Reserved.</span>
href="http://wumei.live">wumei smart</a> All Rights Reserved.</span>
</div> </div>
</div> </div>
</template> </template>
@@ -155,7 +146,6 @@ export default {
this.redirectLogin(loginId); this.redirectLogin(loginId);
} }
}, },
methods: { methods: {
redirectLogin(loginId) { redirectLogin(loginId) {
@@ -163,8 +153,7 @@ export default {
this.$store.dispatch("RedirectLogin", loginId).then(() => { this.$store.dispatch("RedirectLogin", loginId).then(() => {
this.$router.push({ this.$router.push({
path: this.redirect || "/" path: this.redirect || "/"
}).catch(() => { }).catch(() => {});
});
}).catch(() => { }).catch(() => {
this.loading = false; this.loading = false;
if (this.captchaOnOff) { if (this.captchaOnOff) {
@@ -198,8 +187,7 @@ export default {
checkErrorMsg() { checkErrorMsg() {
let errorId = this.$route.query.errorId; let errorId = this.$route.query.errorId;
if (errorId !== undefined && errorId !== null) { if (errorId !== undefined && errorId !== null) {
getErrorMsg(errorId).then(res => { getErrorMsg(errorId).then(res => {}).catch(err => {
}).catch(err => {
this.$router.push({ this.$router.push({
query: {} query: {}
}) })
@@ -262,8 +250,7 @@ export default {
this.$store.dispatch("Login", this.loginForm).then(() => { this.$store.dispatch("Login", this.loginForm).then(() => {
this.$router.push({ this.$router.push({
path: this.redirect || "/" path: this.redirect || "/"
}).catch(() => { }).catch(() => {});
});
}).catch(() => { }).catch(() => {
this.loading = false; this.loading = false;
if (this.captchaOnOff) { if (this.captchaOnOff) {

View File

@@ -10,8 +10,7 @@
</div> </div>
</el-col> </el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24"> <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form ref="registerForm" :model="registerForm" :rules="registerRules" class="register-form" <el-form ref="registerForm" :model="registerForm" :rules="registerRules" class="register-form" style="z-index:1000">
style="z-index:1000">
<h3 class="title" v-if="!bindAccount">注册账号</h3> <h3 class="title" v-if="!bindAccount">注册账号</h3>
<h3 class="title" v-else>注册绑定物美智能账户</h3> <h3 class="title" v-else>注册绑定物美智能账户</h3>
<el-form-item prop="username"> <el-form-item prop="username">
@@ -25,20 +24,17 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input v-model="registerForm.password" type="password" auto-complete="off" placeholder="密码" <el-input v-model="registerForm.password" type="password" auto-complete="off" placeholder="密码" @keyup.enter.native="handleRegister">
@keyup.enter.native="handleRegister">
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="confirmPassword"> <el-form-item prop="confirmPassword">
<el-input v-model="registerForm.confirmPassword" type="password" auto-complete="off" placeholder="确认密码" <el-input v-model="registerForm.confirmPassword" type="password" auto-complete="off" placeholder="确认密码" @keyup.enter.native="handleRegister">
@keyup.enter.native="handleRegister">
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="code" v-if="captchaOnOff"> <el-form-item prop="code" v-if="captchaOnOff">
<el-input v-model="registerForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" <el-input v-model="registerForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter.native="handleRegister">
@keyup.enter.native="handleRegister">
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
</el-input> </el-input>
<div class="register-code"> <div class="register-code">
@@ -46,42 +42,29 @@
</div> </div>
</el-form-item> </el-form-item>
<el-form-item style="width:100%;"> <el-form-item style="width:100%;">
<el-button v-if="!bindAccount" :loading="loading" size="medium" type="primary" style="width:100%;" <el-button v-if="!bindAccount" :loading="loading" size="medium" type="primary" style="width:100%;" @click.native.prevent="handleRegister">
@click.native.prevent="handleRegister">
<span v-if="!loading"> </span> <span v-if="!loading"> </span>
<span v-else> 中...</span> <span v-else> 中...</span>
</el-button> </el-button>
<el-button v-else :loading="loading" size="medium" type="primary" style="width:100%;" <el-button v-else :loading="loading" size="medium" type="primary" style="width:100%;" @click.native.prevent="handleRegister">
@click.native.prevent="handleRegister">
<span v-if="!loading"> </span> <span v-if="!loading"> </span>
<span v-else> 中...</span> <span v-else> 中...</span>
</el-button> </el-button>
<div style="margin-top:20px;"> <div style="margin-top:30px;">
<div v-if="!bindAccount" style=" margin-bottom: 10px;text-align: center ;float:left;">
<div v-if="!bindAccount" style=" margin-bottom: 10px;text-align: center "> <span style="color:#fff;margin-right:10px;">第三方登录</span>
<!-- <span style="color:#fff;margin-right:10px; float: left">快速登录</span>-->
<el-button type="success" circle title="微信登录" size="small" @click="authLogin"> <el-button type="success" circle title="微信登录" size="small" @click="authLogin">
<svg-icon icon-class="wechat" /> <svg-icon icon-class="wechat" />
</el-button> </el-button>
<el-button type="danger" circle title="QQ登录" size="small" @click="qqLogin"> <el-button type="danger" circle title="QQ登录" size="small" @click="qqLogin">
<svg-icon icon-class="qq" /> <svg-icon icon-class="qq" />
</el-button> </el-button>
<el-button type="primary" circle title="支付宝登录" size="small" @click="authLogin"> <el-button type="primary" circle title="支付宝登录" size="small" @click="authLogin">
<svg-icon icon-class="zhifubao" /> <svg-icon icon-class="zhifubao" />
</el-button> </el-button>
</div> </div>
<router-link style="float:left;color:#fff;font-size:16px;" <router-link style="float:right;color:#333;font-size:16px;" :to='{path:"/login",query: this.$route.query }'>已有账户登录 >>
:to='{path:"/login",query: this.$route.query }'>使用已有账户登录 >>
</router-link> </router-link>
<div style="float: right">
<a style="margin-right:20px;" target="_blank" href="http://wumei.live">返回官网</a>
</div>
</div> </div>
</el-form-item> </el-form-item>
</el-form> </el-form>
@@ -91,15 +74,16 @@
</div> </div>
<!-- 底部 --> <!-- 底部 -->
<div class="el-register-footer"> <div class="el-register-footer">
<span>Copyright © 2018-2021 <a target="_blank" <span>Copyright © 2018-2021 <a target="_blank" href="http://wumei.live">wumei smart</a> All Rights Reserved.</span>
href="http://wumei.live">wumei smart</a> All Rights Reserved.</span>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { import {
getCodeImg, checkBindId, bindRegister getCodeImg,
checkBindId,
bindRegister
} from "@/api/login"; } from "@/api/login";
import { import {
register register
@@ -269,8 +253,7 @@ export default {
type: 'success' type: 'success'
}).then(() => { }).then(() => {
this.$router.push("/login"); this.$router.push("/login");
}).catch(() => { }).catch(() => {});
});
} }
} }
}; };