Skip to content

7.3 前端接入示例 ​

一个完整的、可直接改用的原生 JavaScript 接入示例,包含协议自适应、订阅、断线重连、重新订阅。

7.3.1 完整示例 ​

html
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>LPPLC 实时数据</title></head>
<body>
  <p>温度:<span id="车间1/1号机/温度">--</span> ℃</p>
  <p>湿度:<span id="车间1/1号机/湿度">--</span> %</p>

  <script>
    const CONFIG = {
      username: 'linpan',
      password: '你的密码',
      project: 'main',
      datas: ['车间1/1号机/温度', '车间1/1号机/湿度'],
    }

    let ws = null
    let retry = 0

    function connect() {
      // 按当前页面协议自动选择 ws / wss
      const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
      ws = new WebSocket(`${protocol}//${location.host}/ws`)

      ws.onopen = () => {
        retry = 0
        // 连上后立即发送订阅(认证信息在报文里,不在 URL 上)
        ws.send(JSON.stringify({ cmd: 'subscribe', ...CONFIG }))
      }

      ws.onmessage = (e) => {
        let list
        try { list = JSON.parse(e.data) } catch { return }
        if (!Array.isArray(list)) return
        // 推送是数组,元素为 {name, value}
        list.forEach(({ name, value }) => {
          const el = document.getElementById(name)
          if (el) el.textContent = value
        })
      }

      ws.onclose = () => scheduleReconnect()
      ws.onerror = () => { try { ws.close() } catch {} }
    }

    function scheduleReconnect() {
      // 指数退避,上限 30 秒,避免服务器重启时集体冲击
      const delay = Math.min(1000 * 2 ** retry, 30000)
      retry++
      setTimeout(connect, delay)
    }

    connect()
  </script>
</body>
</html>

7.3.2 关键点 ​

  • 协议自适应:HTTPS 页面必须用 wss://,HTTP 页面用 ws://,不能写死
  • 认证在报文里:连接 URL 是干净的 /ws,不带 ?token=;账号密码放在 subscribe 报文中
  • 推送是数组:onmessage 里先判断 Array.isArray,再遍历取 name/value
  • 重连后要重新订阅:onopen 里每次都发 subscribe,不要只在第一次发
  • 退避重连:固定间隔重连会在服务器重启时形成风暴,用指数退避

7.3.3 写数据(控制类界面) ​

WebSocket 只收不发业务写入。界面上要控制设备,用 HTTP 的 set_real:

javascript
async function setPoint(name, value) {
  const resp = await fetch(`${location.origin}/web`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      cmd: 'set_real',
      username: CONFIG.username,
      password: CONFIG.password,
      project: CONFIG.project,
      data: [{ name, value: String(value) }],
    }),
  })
  const r = await resp.json()
  if (r.cmdreq !== 'ok') throw new Error('写入失败: ' + r.cmdreq)
}

// 例:远程启动
// setPoint('车间1/1号机/启停', 1)

写入后短时间内 WebSocket 可能还会推来旧值,控制类控件要做写保护(见 7.2 第 7.2.5 节)。

7.3.4 生产环境提醒 ​

  • 账号密码会出现在前端代码里,不要用主账号,给页面单独开一个只读或最小权限的子账号
  • 页面走 HTTPS,WebSocket 自然走 WSS。TLS 只保证传输途中不被偷看,请求体里的账号密码本身仍是明文字段,所以任何能看到这份前端代码的人都能拿到它——这正是上一条要求单独开子账号的原因
  • 如需完善的重连、心跳、状态管理,可参考云组态运行画面自己的重连与心跳实现思路

LPPLC 工业物联网云平台 · 软硬件与云平台全栈自研