乌兰察布市智慧公路管理平台

乌兰察布市智慧公路管理平台


项目介绍 Link to 项目介绍

本系统涵盖多个子系统,每个子系统拥有独立的部门和角色数据。所有用户数据均在主系统中集中管理。 所有的子系统包含各自的业务模块,单独部署,方便维护和扩展并且支持第三方平台的接入。 其中资产管理系统、养护管理系统、信息发布系统均由原先的系统迁移过来,并做相应的修改,详情可以点击标题查看具体介绍。

  1. 门户主系统
  • 控制子系统的访问权限,同时可以接入第三方平台。 门户主系统
  1. 一张图系统
  • 所有子系统的图表显示,如路段状态、气象信息、车辆位置、视频播放等。 一张图系统
  1. 资产管理系统(原设备设施管理平台) 资产管理系统
  2. 养护管理系统 养护管理系统
  3. 养护管理系统H5 养护管理系统H5
  4. 信息发布系统(原交通态势系统) 信息发布系统

项目难点 Link to 项目难点

单点登录 Link to 单点登录

由于每个子系统部署在不同的服务器上,存在域名、端口号和协议不一致的情况。然而,所有用户数据均在主系统中集中管理。 为了解决这一问题,我们采用了通过地址栏加密传输token和projectId的方式来判断用户进入的是哪个子系统,类似于QQ中点击QQ邮箱可以实现免登的效果。

mqtt订阅 Link to mqtt订阅

系统需要获取路段上配置的雷达设备编号来订阅MQTT主题,实现实时展示车辆在路段上的行驶轨迹。地图上通过marker实时更新车辆位置, 并且在点击路段时打开弹窗展示路段详情,同时在弹窗中也展示车辆的实时位置。

海康视频流播放 Link to 海康视频流播放

系统默认使用video.js插件播放视频。如果用户安装了海康视频播放插件,则使用海康插件播放【video.js通过调用接口的形式实现,而海康插件则自带了这些功能仅需项目中引入SDK即可】 并支持以下功能: 海康视频流播放

  • 旋转
  • 聚焦
  • 截图
  • 回放
  • 自主码流切换
  • 录像
  • 喊话

百度地图 Link to 百度地图

项目中存在大量的图层交互事件,因此我们在百度地图API的基础上封装了一套更通用化的百度地图Vue组件库,以方便后续维护。该组件库主要支持以下功能:

  • 图层管理:添加和删除图层,包括点、线、多边形、文字、图片和自定义图层。
  • 交互事件:支持图层的交互事件,例如点击点位时将该点位替换图标以区分是否选中。
  • 图层动效:提供多种图层动效,如点位闪烁(预警效果)、区域边界绘制和线段动画。
  • 点聚合
  1. 支持指定层级聚合。
  2. 支持自定义聚合效果。
  3. 支持自定义聚合规则。
  4. 支持自定义聚合图标。
  5. 暴露鼠标事件以便进一步操作。

文件上传 Link to 文件上传

前面提到了利用海康视频插件来播放直播流,系统中就有个维护附件资源的功能, 其中海康插件内存占用大于500MB,导致上传附件时频繁崩溃,因此我们利用webwork实现大文件分片上传。

扫码登录 Link to 扫码登录

浙政钉、微信扫码登录【用户在后台扫码绑定微信或浙政钉授权码,绑定成功后,就可以通过扫码登录系统】

浙政钉扫码绑定

信息屏硬件对接 Link to 信息屏硬件对接

  1. 系统中配置好节目单【图片、文本、视频】
  2. 选择系统中维护的信息屏
  3. 调用硬件厂商提供的接口
  4. 实现信息屏节目的实时更新。 节目编辑

h5第三方授权免登 Link to h5第三方授权免登

养护h5是嵌套在第三方微信小程序里的,因此需实现第三方免登进入系统。 前提:第三方微信小程序登录账号的手机号在我们系统中已经注册 主要实现流程:

  1. 微信小程序登录,点击养护管理入口
  2. 小程序端将token和appid传给h5
  3. h5调用第三方系统接口,获取用户的手机号
  4. h5调用本系统接口,获取用户token和其他基础信息
  5. 成功免登跳转至系统首页

微信小程序

代码示例 Link to 代码示例

mqtt通用操作 Link to mqtt通用操作

JAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
var MqttTopics = [];
var MqttClients = [];
var connectUrl = CONFIG_g.VUE_APP_MQTT;
let Mqttserver = null;
// 接收mqtt
function screenMqtt(_topic, hostname, callback) {
  connectUrl = CONFIG_sreen.VUE_APP_MQTT[hostname];
  Mqtt(_topic, callback, hostname);
}
function Mqtt(_topic, callback, hostName) {
  if (typeof _topic === "string") {
    let MqttServer = getMqttServer(_topic, hostName);
    MqttServer.client.on("message", (topic, message, packet) => {
      if (callback) {
        let payloadStr = new TextDecoder("utf-8").decode(message);
        let msg = JSON.parse(payloadStr);
        if(msg.data && typeof msg.data === 'string'){
          msg.data = JSON.parse(msg.data)
        }
        callback(topic, msg, packet);
      }
    });
  } else {
    if (Array.isArray(_topic)) {
      let topics = _topic;
      topics.forEach((topic) => {
        let MqttServer = getMqttServer(topic, hostName);
        // console.log(MqttServer,'MqttServer');
        MqttServer.client.subscribe(topic, (err) => {
          if (!err) {
            console.log("订阅成功");
          }
        });
        // MqttServer.subscribe(topic, (err) => {
        //   if (!err) {
        //     console.log("订阅成功");
        //   }
        // });
        MqttServer.client.on("message", (topic, message, packet) => {
          if (callback) {
            let payloadStr = new TextDecoder("utf-8").decode(message);
            if (!payloadStr) {
              return;
            }
            let msg = JSON.parse(payloadStr);
            if(msg.data && typeof msg.data === 'string'){
              msg.data = JSON.parse(msg.data)
            }
            callback(topic, msg, packet);
          }
        });
      });
    } else {
      console.log("_topic格式错误");
    }
  }
}
// 发送mqtt
function screenMqttPublish(_topic, hostname, message) {
  connectUrl = CONFIG_sreen.VUE_APP_MQTT[hostname];
  MqttPublish(_topic, message);
}

function MqttPublish(_topic, message) {
  if (typeof _topic === "string") {
    let MqttServer = getMqttServer(_topic);
    MqttServer.publish(_topic, message);
  } else {
    if (Array.isArray(_topic)) {
      let topics = _topic;
      topics.forEach((topic) => {
        let MqttServer = getMqttServer(topic);
        MqttServer.publish(topic, message);
      });
    } else {
      console.log("_topic格式错误");
    }
  }
}
// 取消订阅mqtt
function screenMqttUnsubscribe(_topic, hostname) {
  connectUrl = CONFIG_sreen.VUE_APP_MQTT[hostname];
  MqttUnsubscribe(_topic);
}
function MqttUnsubscribe(_topic) {
  return new Promise((resolve) => {
    if (typeof _topic === "string") {
      let MqttServer = getMqttServer(_topic);
      if (MqttServer.client && MqttServer.client.connected) {
        MqttServer.unsubscribe(_topic);
        removeMqttServer(_topic);
        resolve();
      }
    } else {
      if (Array.isArray(_topic)) {
        let topics = _topic;
        topics.forEach((topic) => {
          let MqttServer = getMqttServer(topic);
          if (MqttServer.client && MqttServer.client.connected) {
            MqttServer.unsubscribe(topic);
            removeMqttServer(topic);
            resolve();
          }
        });
      } else {
        console.log("_topic格式错误");
      }
    }
  });
}

function getMqttServer(_topic,hostName) {
  if(hostName){
    Mqttserver = MqttClients.find(clients => clients.hostName === hostName)
    if(Mqttserver) {
      return Mqttserver
    }else{
      // 没有订阅过该topic
      console.log("未订阅=>" + _topic);
      Mqttserver = new MqttApi(connectUrl, _topic, hostName);
      MqttTopics.push(_topic);
      MqttClients.push(Mqttserver);
      return Mqttserver
    }
  }

  if (MqttTopics.indexOf(_topic) != -1) {
    // 已经订阅过该topic
    console.log("已订阅=>" + _topic);
    MqttTopics.forEach((topic, index) => {
      if (topic === _topic) {
        Mqttserver = MqttClients[index];
      }
    });
  } else {
    // 没有订阅过该topic
    console.log("未订阅=>" + _topic);
    Mqttserver = new MqttApi(connectUrl, _topic, hostName);
    MqttTopics.push(_topic);
    MqttClients.push(Mqttserver);
  }
  return Mqttserver;
}
function removeMqttServer(_topic) {
  MqttTopics.forEach((topic, index) => {
    if (topic === _topic) {
      MqttTopics.splice(index, 1);
      MqttClients.splice(index, 1);
      return;
    }
  });
  console.log(MqttTopics);
  console.log(MqttClients);
}

class MqttApi {
  client = null;
  hostName;
  constructor(connectUrl, topic, hostName) {
    console.log(connectUrl,'connectUrl');
    this.hostName = hostName
    const options = {
      keepalive: 60, // 默认60秒,设置0为禁用
      clean: true, // 设置为false以在脱机时接收QoS 1和2消息
      clientId: "mqttjs_" + Math.random().toString(16).substr(2, 8),
      protocolId: "MQTT",
      protocolVersion: 4, //MQTT 协议版本号,默认为 4(v3.1.1)可以修改为 3(v3.1)和 5(v5.0)
      reconnectPeriod: 4000, //设置多长时间进行重新连接 单位毫秒 两次重新连接之间的时间间隔。通过将设置为,禁用自动重新连接0
      connectTimeout: 30 * 1000, // 收到CONNACK之前等待的时间
    };
    this.client = mqtt.connect(connectUrl, options);
    this.client.on("connect", (connack) => {
      console.log("链接成功");
      if (topic) {
        this.client.subscribe(topic, (err) => {
          if (!err) {
            console.log("订阅成功");
          }
        });
      }
    });
    this.client.on("reconnect", function (error) {
      if (error) {
        // console.log(error)
      } else {
        console.log("正在重连.....");
      }
    });
    this.client.on("close", function (error) {
      if (error) {
        // console.log(error)
      } else {
        console.log("客户端已断开.....");
      }
    });
    this.client.on("offline", function (error) {
      if (error) {
        console.log(error);
      } else {
        console.log("offline.....");
      }
    });
    //当客户端无法连接或出现错误时触发回调
    this.client.on("error", (error) => {
      console.log("客户端出现错误....." + error);
    });
    this.client.on("packetsend", (packet) => {
      if (packet && packet.payload) {
        console.log("客户端已发出数据包....." + packet.payload);
      }
    });
  }
  // 断开链接
  disconnect() {
    try {
      if (this.client && this.client.connected) {
        console.log("断开连接");
        this.client.end();
      }
    } catch (error) {
      console.log(error);
    }
  }
  // 重新链接
  reconnect() {
    this.client.reconnect();
  }
  // 关闭
  close() {
    try {
      if (this.client && this.client.connected) {
        this.client.end();
        this.client = null;
      }
    } catch (error) {
      console.log(error);
    }
  }
  // 发布消息
  publish(topic, message) {
    // if (this.client && this.client.connected) {
    if (this.client) {
      this.client.publish(topic, JSON.stringify(message), (error) => {
        if (error) {
          console.log(error);
        } else {
          console.log("发布消息成功");
        }
      });
    }
  }
  // 订阅主题
  subscribe(topic) {
    if (this.client && this.client.connected) {
      console.log(this,'clicent');

      this.client.subscribe(topic, function (error, granted) {
        if (error) {
          console.log(error,'error');
        } else {
          console.log(`${granted[0].topic} 订阅成功`);
        }
      });
    }
  }
  // 取消订阅
  unsubscribe(topic) {
    if (this.client && this.client.connected) {
      this.client.unsubscribe(topic, (error) => {
        if (error) {
          console.log(error);
        } else {
          console.log("取消订阅成功");
        }
      });
    }
  }
}