Casper Dai 3 лет назад
Родитель
Сommit
0b662c6b33

+ 1 - 0
package.json

@@ -42,6 +42,7 @@
     "chokidar": "^3.5.2",
     "commitizen": "^4.2.4",
     "conventional-changelog-cli": "^2.1.1",
+    "crypto-js": "^4.1.1",
     "cz-conventional-changelog": "^3.3.0",
     "eslint": "^8.17.0",
     "eslint-plugin-vue": "^8.0.3",

+ 9 - 3
src/router/index.js

@@ -338,13 +338,19 @@ export const asyncRoutes = [
     path: '/d',
     component: Layout,
     access: Access.MANAGE_TENANTS,
-    meta: { icon: 'bug' },
+    meta: { icon: 'bug', title: '调试' },
     children: [
       {
-        path: '',
+        path: 'logger',
         name: 'debug',
         component: () => import('@/views/realm/debug/index'),
-        meta: { title: '调试' }
+        meta: { title: '日志' }
+      },
+      {
+        path: 'simulator',
+        name: 'simulator',
+        component: () => import('@/views/realm/debug/simulator/index'),
+        meta: { title: '模拟器' }
       }
     ]
   },

+ 48 - 41
src/utils/mqtt.js

@@ -4,7 +4,7 @@ import SM4 from '@/utils/sm4'
 
 const willTopic = 'web/offline'
 
-const sm4 = new SM4({
+export const sm4 = new SM4({
   // encrypt/decypt main key; cannot be omitted
   // key: 'fELIMxLsdoSgRZnX',
   key: [102, 69, 76, 73, 77, 120, 76, 115, 100, 111, 83, 103, 82, 90, 110, 88],
@@ -20,24 +20,6 @@ const host = `${GATEWAY_WS}${process.env.VUE_APP_MQTT_PROXY}`
 const username = process.env.VUE_APP_MQTT_USER_NAME
 const password = process.env.VUE_APP_MQTT_PASSWORD
 
-const options = {
-  username,
-  password,
-  clientId: `mqttjs_${Math.random().toString(16).slice(2)}`,
-  keepalive: 30,
-  protocolId: 'MQTT',
-  protocolVersion: 4,
-  clean: true,
-  reconnectPeriod: 1000,
-  connectTimeout: 30 * 1000,
-  will: {
-    topic: willTopic,
-    payload: 'Connection Closed abnormally..!',
-    qos: 0,
-    retain: false
-  }
-}
-
 const whiteList = [
   {
     type: 'topic',
@@ -63,6 +45,18 @@ function isWhite (item, topic, message) {
   }
 }
 
+export function createClient (options) {
+  return mqtt.connect(host, {
+    protocolId: 'MQTT',
+    protocolVersion: 4,
+    clean: true,
+    keepalive: 30,
+    reconnectPeriod: 1000,
+    connectTimeout: 30 * 1000,
+    ...options
+  })
+}
+
 const cbs = []
 let mqttClient
 
@@ -72,7 +66,17 @@ function changeState (state) {
 
 function start () {
   console.log('Connecting mqtt client...')
-  const client = mqtt.connect(host, options)
+  const client = createClient({
+    username,
+    password,
+    clientId: `mqttjs_${Math.random().toString(16).slice(2)}`,
+    will: {
+      topic: willTopic,
+      payload: 'Connection Closed abnormally..!',
+      qos: 0,
+      retain: false
+    }
+  })
 
   client.on('error', err => {
     console.log('MQTT connection error: ', err)
@@ -110,30 +114,38 @@ function start () {
     mqttClient = null
   })
 
-  client.on('message', (topic, message) => {
+  client.on('message', (topic, payload) => {
     console.log('MQTT topic', topic)
     if (cbs.length) {
-      try {
-        const s = utf8ArrayBufferToString(message)
-        if (s) {
-          message = decodeMessage(topic, message, s)
-        } else {
-          console.log('null')
-          message = s
-        }
-
-        console.log(message)
-
-        cbs.forEach(cb => cb(topic, message))
-      } catch (e) {
-        console.warn(e)
-      }
+      const message = decodePayload(topic, payload)
+      cbs.forEach(cb => cb(topic, message))
     }
   })
 
   return client
 }
 
+export function decodePayload (topic, payload) {
+  try {
+    const s = utf8ArrayBufferToString(payload)
+    if (s) {
+      payload = decodeMessage(topic, payload, s)
+    } else {
+      console.log('null')
+      payload = s
+    }
+    console.log(payload)
+  } catch (e) {
+    console.warn(e)
+  }
+  return payload
+}
+
+const decoder = new TextDecoder('utf-8')
+export function utf8ArrayBufferToString (buffer) {
+  return decoder.decode(buffer)
+}
+
 function decodeMessage (topic, arrayBuffer, message) {
   if (whiteList.some(item => isWhite(item, topic, message))) {
     console.log('white list')
@@ -148,11 +160,6 @@ function decodeMessage (topic, arrayBuffer, message) {
   return message
 }
 
-const decoder = new TextDecoder('utf-8')
-function utf8ArrayBufferToString (buffer) {
-  return decoder.decode(buffer)
-}
-
 function inst () {
   if (!mqttClient) {
     mqttClient = {

+ 147 - 0
src/views/realm/debug/simulator/index.vue

@@ -0,0 +1,147 @@
+<template>
+  <wrapper
+    fill
+    margin
+    padding
+    background
+  >
+    <div class="l-flex__none l-flex--row c-sibling-item--v">
+      <span class="l-flex__none c-sibling-item">sn</span>
+      <el-input
+        v-model.trim="sn"
+        class="c-sibling-item"
+      />
+      <button
+        class="l-flex__none c-sibling-item far o-button"
+        :disabled="loading"
+        @click="createProxy"
+      >
+        <i
+          v-if="loading"
+          class="el-icon-loading"
+        />
+        <template v-else>
+          代理
+        </template>
+      </button>
+    </div>
+    <template v-if="valid">
+      <div class="c-sibling-item--v far">
+        <button
+          class="l-flex__none c-sibling-item far o-button"
+          @click="publish('/oss')"
+        >
+          OSS
+        </button>
+        <button
+          class="l-flex__none c-sibling-item far o-button"
+          @click="publish('/online')"
+        >
+          Online
+        </button>
+        <button
+          class="l-flex__none c-sibling-item far o-button"
+          @click="publish('/offline')"
+        >
+          Offline
+        </button>
+      </div>
+      <div class="l-flex__auto c-sibling-item--v far c-simulator u-overflow-y--auto">
+        <div
+          v-for="message in messages"
+          :key="message.id"
+        >
+          <div>{{ message.topic }}</div>
+          <div>{{ message.payload }}</div>
+        </div>
+      </div>
+    </template>
+  </wrapper>
+</template>
+
+<script>
+import {
+  subscribe,
+  unsubscribe
+} from '@/utils/mqtt'
+import { createProxy } from './simulate'
+
+export default {
+  name: 'Simulate',
+  data () {
+    return {
+      sn: '',
+      valid: false,
+      loading: false,
+      messages: []
+    }
+  },
+  beforeDestroy () {
+    this.unsubscribe()
+  },
+  methods: {
+    createProxy () {
+      this.valid = false
+      this.unsubscribe()
+      if (this.sn) {
+        this.loading = true
+        this.$proxyPromise = createProxy(this.sn)
+        this.$proxyPromise.then(
+          val => {
+            this.$proxyPromise = null
+            this.$proxy = val
+            this.valid = true
+            subscribe(`${this.$proxy.productId}/${this.$proxy.deviceId}/+/#`, this.onMessage)
+            this.$message({
+              type: 'success',
+              message: '代理成功'
+            })
+          },
+          () => {
+            this.$message({
+              type: 'error',
+              message: '代理失败'
+            })
+          }
+        ).finally(() => {
+          this.loading = false
+        })
+      }
+    },
+    publish (topic) {
+      if (!this.$proxy) {
+        this.$message({
+          type: 'warning',
+          message: '请先代理设备'
+        })
+      }
+      return this.$proxy.publish(topic)
+    },
+    unsubscribe () {
+      if (this.$proxy) {
+        this.$proxy.cancel()
+        unsubscribe(`${this.$proxy.productId}/${this.$proxy.deviceId}/+/#`, this.onMessage)
+        this.$proxy = null
+      }
+      this.$proxyPromise?.reject()
+      this.messages = []
+    },
+    onMessage (topic, payload) {
+      this.messages.unshift({
+        id: Date.now(),
+        topic,
+        payload
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.c-simulator {
+  color: $black;
+  font-size: 14px;
+  line-height: 24px;
+  word-break: break-all;
+}
+</style>

+ 122 - 0
src/views/realm/debug/simulator/simulate.js

@@ -0,0 +1,122 @@
+import sha256 from 'crypto-js/sha256'
+import {
+  subscribe,
+  unsubscribe,
+  publish,
+  sm4,
+  createClient
+} from '@/utils/mqtt'
+
+export function createProxy (sn) {
+  const snHash = sha256(sn).toString().toUpperCase()
+
+  const timer = setTimeout(() => {
+    publish(`smsb/${snHash}/init`, JSON.stringify({ messageId: Date.now().toString(), timestamp: Date.now().toString() }), true)
+  }, 500)
+
+  let fn
+
+  const promise = new Promise((resolve, reject) => {
+    fn = (topic, payload) => {
+      unsubscribe(`smsb/${snHash}/init/reply`, fn)
+      try {
+        createProxyClient(JSON.parse(payload), resolve, reject)
+      } catch (e) {
+        reject()
+      }
+    }
+
+    subscribe(`smsb/${snHash}/init/reply`, fn)
+  })
+
+  promise.reject = () => {
+    clearTimeout(timer)
+    unsubscribe(`smsb/${snHash}/init/reply`, fn)
+  }
+
+  return promise
+}
+
+function createProxyClient ({ productId, deviceId, username, password }, resolve, reject) {
+  if (!deviceId) {
+    reject()
+    return
+  }
+
+  console.log('Simulate Connecting mqtt client...')
+  let reconnect = 0
+  let timer = -1
+
+  let client = createClient({
+    username,
+    password,
+    clientId: deviceId
+  })
+
+  client.on('error', e => {
+    console.log('Simulate MQTT connection error: ', e)
+    client?.end(true)
+  })
+
+  client.on('connect', () => {
+    console.log('Simulate MQTT connected')
+    clearTimeout(timer)
+    timer = setTimeout(() => {
+      reconnect = 0
+    }, 5000)
+    client.subscribe([
+      `${productId}/${deviceId}/oss/reply`
+    ])
+    resolve({
+      productId,
+      deviceId,
+      cancel () {
+        if (!client) {
+          return
+        }
+        client.unsubscribe([
+          `${productId}/${deviceId}/oss/reply`
+        ])
+        client.end(true)
+      },
+      publish (topic, message, encode = true) {
+        if (!client) {
+          return
+        }
+        message = JSON.stringify({
+          messageId: Date.now().toString(),
+          timestamp: Date.now().toString(),
+          ...message
+        })
+        client.publish(`${productId}/${deviceId}${topic}`, message && encode ? sm4.encrypt(message) : message)
+      }
+    })
+  })
+
+  client.on('reconnect', () => {
+    console.log('Simulate MQTT reconnecting...')
+    reconnect += 1
+    if (reconnect > 3) {
+      client?.end(true)
+    }
+  })
+
+  client.on('close', () => {
+    console.log('Simulate MQTT closed')
+  })
+
+  client.on('disconnect', () => {
+    console.log('Simulate MQTT disconnect')
+  })
+
+  client.on('offline', () => {
+    console.log('Simulate MQTT offline')
+  })
+
+  client.on('end', () => {
+    console.log('Simulate MQTT end')
+    clearTimeout(timer)
+    client = null
+    reject()
+  })
+}

+ 5 - 0
yarn.lock

@@ -3680,6 +3680,11 @@ crypto-browserify@^3.11.0:
     randombytes "^2.0.0"
     randomfill "^1.0.3"
 
+crypto-js@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
+  integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==
+
 css-color-names@0.0.4, css-color-names@^0.0.4:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"