Предварительное исследование редактора сетевой топологии на основе Jtopo

JavaScript
Предварительное исследование редактора сетевой топологии на основе Jtopo

написать впереди

В этом документе реализуется проектирование и редактирование топологии сети в режиме онлайн на основе Jtopo, который может создавать сложные сети и выполнять различные операции с сетями и устройствами, а также обеспечивать сериализацию и десериализацию топологии.

Для удобства демонстрации я развернул статическое DEMO на github,портал

Что касается доступа к проекту, из-за чтения удаленных данных json будут ограничения безопасности при открытии его напрямую с помощью браузера.Пожалуйста, поместите проект в tomcat, чтобы начать доступ, или напрямую откройте проект с помощью idea/webstorm и щелкните правой кнопкой мыши, чтобы открыть его, как показано на следующем рисунке:
img

Полный код выложен на гитхабе отдельно:GitHub.com/ProcessFan/Я…

Введение в функцию

Базовую операцию можно увидеть на изображении GIF ниже. Перетащите узел из панели значков слева в область редактирования. Щелкните значок узла и отпустите кнопку мыши, чтобы перетащить соединение. Нажмите на целевой узел, чтобы завершить операцию подключения. Вы можете выбрать различные способы подключения в левой колонке.
img

Этот пользовательский интерфейс редактора топологии представляет собой макет, созданный easyUI, а редактирование и подключение узлов основаны на вторичной разработке jTopo. jTopo предоставляет Stage, Scene, Node, Container и поддержку анимации, а API относительно прост в использовании. Недостатком является отсутствие документации, но мы можем ознакомиться с DEMO, предоставленным автором. После знакомства с его исходным кодом он очень удобен для вторичной разработки. Также легко реализовать некоторые анимации. Поскольку это живопись на основе холста, каждое изменение и операция фактически перерисовывают весь холст. Есть еще много областей, которые можно оптимизировать. На самом деле, я думаю, что D3.js также может достичь такого эффекта, но недавняя работа не была изучена на бэкэнде.
Так как извлекается только front-end часть проекта персональной разработки, эта часть может разрабатываться только по лицензии компании.Если интересно, можете зайти на мой гитхаб и клонировать для ознакомления. Основная часть кода находится в editor.js, который обеспечивает поддержку методов, таких как перетаскивание узлов, соединение узлов и макет. Это начальная версия. Код грубый, просто для справки.
Вот несколько важных моментов для объяснения:

Код инициализации редактора

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//创建JTOP舞台屏幕对象  
   var canvas = document.getElementById('drawCanvas');  
   canvas.width = $("#contextBody").width();  
   canvas.height = $("#contextBody").height();  
   //加载空白的编辑器  
   if(stageJson == "-1"){  
       this.stage = new JTopo.Stage(canvas);  
       this.stage.topoLevel = 1;  
       this.stage.parentLevel = 0;  
       this.modeIdIndex = 1;  
       this.scene=  new JTopo.Scene(this.stage);  
       this.scene.totalLevel = 1;  
   }else{  
       this.stage = JTopo.createStageFromJson(stageJson, canvas);  
       this.scene = this.stage.childs[0];  
   }

Мы можем вызвать JTopo.Stage(canvas) для инициализации области рисования, а затем мы можем использовать API для операций с узлами и анимацией. Иерархия JSON всего объекта рисования выглядит следующим образом:

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
{  
  "version": "0.4.8",  
  "deviceNum": "19",  
  "wheelZoom": 0.95,  
  "width": 864,  
  "height": 569,  
  "id": "ST172.19.105.52015100809430700001",  
  "topoLevel": "1",  
  "parentLevel": "0",  
  "nextLevel": "0",  
  "childs": [  
    {  
      "elementType": "scene",  
      "id": "S172.19.105.52015100809430700002",  
      "topoLevel": "1",  
      "parentLevel": "0",  
      "nextLevel": "0",  
      "translateX": 106.5,  
      "translateY": 20,  
      "scaleX": 1,  
      "scaleY": 1,  
      "totalLevel": "1",  
      "childs": [  
        {  
          "elementType": "node",  
          "id": "",  
          "topoLevel": 1,  
          "parentLevel": "0",  
          "nextLevel": "0",  
          "x": 211.5,  
          "y": 135,  
          "width": 32,  
          "height": 32,  
          "visible": true,  
          "rotate": 0,  
          "scaleX": 1,  
          "scaleY": 1,  
          "zIndex": 3,  
          "deviceId": "1404683827351.4666",  
          "dataType": "VR",  
          "nodeImage": "tpIcon_9.png",  
          "text": "CS路由器",  
          "textPosition": "Bottom_Center",  
          "templateId": undefined  
        }  
      ]  
    }  
  ]  
}

Его структура:
img

Обычно нам нужен только один объект сцены для управления всеми объектами.Конечно, если вы хотите добиться более сложного управления сгруппированными объектами, вы можете создать несколько объектов сцены для раздельного управления. В то же время мы можем вызвать метод JTopo.createStageFromJson(stageJson, canvas) для повторного рендеринга сохраненной топологии.

Перетаскивание узлов

Перетаскивание узлов реализовано с помощью родного перетаскивания H5.

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
/** 
 * 图元拖放功能实现 
 * @param modeDiv 
 * @param drawArea 
 */  
networkTopologyEditor.prototype.drag = function (modeDiv, drawArea, text) {  
    if (!text) text = "";  
    var self = this;  
    //拖拽开始,携带必要的参数  
    modeDiv.ondragstart = function (e) {  
        e = e || window.event;  
        var dragSrc = this;  
        var backImg = $(dragSrc).find("img").eq(0).attr("src");  
        backImg = backImg.substring(backImg.lastIndexOf('/') + 1);  
        var datatype = $(this).attr("datatype");  
        try {  
            //IE只允许KEY为text和URL  
            e.dataTransfer.setData('text', backImg + ";" + text + ";" + datatype);  
        } catch (ex) {  
            console.log(ex);  
        }  
    };  
    //阻止默认事件  
    drawArea.ondragover = function (e) {  
        e.preventDefault();  
        return false;  
    };  
    //创建节点  
    drawArea.ondrop = function (e) {  
        e = e || window.event;  
        var data = e.dataTransfer.getData("text");  
        var img, text,datatype;  
        if (data) {  
            var datas = data.split(";");  
            if (datas && datas.length == 3) {  
                img = datas[0];  
                text = datas[1];  
                datatype = datas[2];  
                var node = new JTopo.Node();  
                node.fontColor = self.config.nodeFontColor;  
                node.setBound((e.layerX ? e.layerX : e.offsetX) - self.scene.translateX - self.config.defaultWidth / 2, (e.layerY ? e.layerY : e.offsetY) - self.scene.translateY - self.config.defaultHeight / 2,self.config.defaultWidth,self.config.defaultHeight);  
                //设备图片  
                node.setImage(context + 'post/web-topology/icon/' + img);  
                //var cuurId = "device" + (++self.modeIdIndex);  
                var cuurId = "" + new Date().getTime() * Math.random();  
                node.deviceId = cuurId;  
                node.dataType = datatype;  
                node.nodeImage = img;  
                ++self.modeIdIndex;  
                node.text = text;  
                node.layout = self.layout;  
                //节点所属层次  
                node.topoLevel = parseInt($("#selectLevel").find("option:selected").val());  
                //节点所属父层次  
                node.parentLevel = $("#parentLevel").val();  
                //子网连接点的下一个层,默认为0  
                node.nextLevel = "0";  
                self.scene.add(node);  
  
                //加载属性面板  
                /* if(self.currDataType) 
                 self.clearOldPanels(self.currDataType) 
                 self.currDeviceId = cuurId; 
                 self.createNewPanels(datatype,self.templateId,self.currentModeId);*/  
                //self.currDataType = datatype;  
                self.currentNode = node;  
            }  
        }  
        if (e.preventDefault()) {  
            e.preventDefault();  
        }  
        if (e.stopPropagation()) {  
            e.stopPropagation();  
        }  
    }  
}

Передача подложки и необходимые параметры ondragStart обратный вызов Способ обратного вызова, для создания нового узла, а затем узлов JTOPO.NODE () Структура, соответствующие свойства, добавленные затем на сцену Scene.add (Node) в OnDrop. Почему добавить к выполнению операций на интерфейсе вы можете увидеть новый узел этого?

Причина в том, что у Stage есть свойствоframes, которое определяет частоту перерисовки холста 1000/кадров.

свойство фреймов

Установите количество кадров в секунду, воспроизводимых на текущей сцене

По умолчанию: 24

Кадры могут быть равны 0, что означает отсутствие автоматического рисования, вызванного пользователем, вручную вызвавшим метод paint() объекта Stage.

Если меньше 0, значит: будут перерисовываться только клавиатура и мышь, например: stage.frames = -24.

Рамки изображения по умолчанию до 24 кадров, то есть каждые 1000/24 ​​мс перерисовывают экран. Фон обновить код следующим образом:

1
2
3
4
5
function() {  
             0 == stage.frames ? setTimeout(arguments.callee, 100) : stage.frames < 0 ? (stage.repaint(),  
                        setTimeout(arguments.callee, 1e3 / -stage.frames)) : (stage.repaint(),  
                        setTimeout(arguments.callee, 1e3 / stage.frames))  
        } ()

setTimeout вызовет следующую функцию перерисовки:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
this.paint = function() {  
      null  != this.canvas && (this.graphics.save(),  
          this.graphics.clearRect(0, 0, this.width, this.height),  
          this.childs.forEach(function(a) {  
                  1 == a.visible && a.repaint(stage.graphics)  
              }  
          ),  
      1 == this.eagleEye.visible && this.eagleEye.paint(this),  
          this.graphics.restore())  
  }  
  ,  
  this.repaint = function() {  
      0 != this.frames && (this.frames < 0 && 0 == this.needRepaint || (this.paint(),  
      this.frames < 0 && (this.needRepaint = !1)))  
  }

Пара рисования обходит все видимые объекты и по очереди вызывает метод перерисовки.

соединение узла

Используемый здесь метод соединения состоит в том, чтобы нажать левую кнопку мыши на узле, а затем отпустить мышь, чтобы создать соединение.Начальной точкой является выбранный узел, а конечная точка динамически обновляется при движении мыши. Поэтому, если вы отпустите кнопку мыши на одном узле, вы увидите линию соединения, которая перемещается вместе с мышью. Затем нажмите и отпустите левую кнопку на узле, чтобы завершить соединение между двумя узлами. Эффект следующий:
img

Часть кода реализована следующим образом:
img

jTopo поддерживает общие ответвления, полилинии, кривые и т. д., но длину углов полилиний теперь можно указать только во время создания. Для динамического создания второй точки требуется вторичное развитие. код показывает, как показано ниже:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(self.lineType == "line"){
    self.link = new JTopo.Link(self.tempNodeA, self.tempNodeZ);
    self.link.lineType = "line";
}else if(self.lineType == "foldLine"){
    self.link = new JTopo.FoldLink(self.tempNodeA, self.tempNodeZ);
    self.link.lineType = "foldLine";
    self.link.direction =  self.config.direction;
}else if(self.lineType == "flexLine"){
    self.link = new JTopo.FlexionalLink(self.tempNodeA, self.tempNodeZ);
    self.link.direction =  self.config.direction;
    self.link.lineType = "flexLine";
}else if(self.lineType == "curveLine"){
    self.link = new JTopo.CurveLink(self.tempNodeA, self.tempNodeZ);
    self.link.lineType = "curveLine";
}  xxxxx

О сохранении и загрузке топологий

Поскольку эта статья представляет собой только внешний интерфейс редактора топологии, внутренняя часть не имеет открытого исходного кода из-за коммерческих ограничений. А так как общий проект очень большой, его нелегко отделить от него по отдельности. На самом деле, если мы знаем логику загрузки и сохранения топологии, реализовать операцию сериализации структуры топологии очень просто. Следующее занимает место, чтобы сосредоточиться на обсуждении:

Загрузите существующую карту топологии

Теперь у нас есть топология, которая представляет собой топологию, отображаемую адресом текстового портала, и ее сериализованная структура выглядит следующим образом:

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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
{
  "errorInfo": "ok",
  "topologyJson": {
    "version": "0.4.8",
    "wheelZoom": "0.95",
    "deviceNum": "18",
    "width": "1098",
    "height": "671",
    "id": "ST172.19.105.52015100809430700001",
    "topoLevel": "1",
    "parentLevel": "0",
    "nextLevel": "0",
    "childs": [
      {
        "id": "S172.19.105.52015100809430700002",
        "elementType": "scene",
        "translateX": "106.5",
        "translateY": "20",
        "scaleX": "1",
        "scaleY": "1",
        "totalLevel": "1",
        "parentLevel": "0",
        "nextLevel": "0",
        "topoLevel": "1",
        "childs": [
          {
            "id": "L172.19.105.52015100811153300001",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "1136300297928.587",
            "deviceZ": "1406176935353.6848",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811153300002",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "1394105924967.951",
            "deviceZ": "1136300297928.587",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811153300004",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "564241624829.4331",
            "deviceZ": "318016674603.1266",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811153300005",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "1062340763210.1544",
            "deviceZ": "564241624829.4331",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811153300006",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "564241624829.4331",
            "deviceZ": "488323736331.2087",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811153300007",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "564241624829.4331",
            "deviceZ": "827722371280.0199",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811153300008",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "564241624829.4331",
            "deviceZ": "1045863334277.662",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811153300009",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "1262070648024.5728",
            "deviceZ": "564241624829.4331",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811153300010",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "1262070648024.5728",
            "deviceZ": "1136300297928.587",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811214600001",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "253209434749.73596",
            "deviceZ": "564241624829.4331",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811214600002",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "253209434749.73596",
            "deviceZ": "1136300297928.587",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811214600003",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "520008948580.8119",
            "deviceZ": "564241624829.4331",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811214600004",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "520008948580.8119",
            "deviceZ": "1136300297928.587",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811512000002",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "809054608657.865",
            "deviceZ": "1394105924967.951",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100811512000004",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "1250687739831.9912",
            "deviceZ": "1062340763210.1544",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "L172.19.105.52015100911051100001",
            "elementType": "link",
            "x": "0",
            "y": "0",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "undefined",
            "deviceA": "564241624829.4331",
            "deviceZ": "597645745716.1871",
            "lineType": "line",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "2"
          },
          {
            "id": "N172.19.105.52015100809464700002",
            "elementType": "node",
            "x": "198",
            "y": "315",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "attack-network",
            "textPosition": "Bottom_Center",
            "deviceId": "1136300297928.587",
            "dataType": "EC",
            "nodeImage": "tpIcon_5.png",
            "templateId": "NK172.19.105.52015100809464700001",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100809465000001",
            "elementType": "node",
            "x": "196",
            "y": "242",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "attackr-router",
            "textPosition": "Bottom_Center",
            "deviceId": "1394105924967.951",
            "dataType": "VR",
            "nodeImage": "tpIcon_9.png",
            "templateId": "RT172.19.105.52015100811241800001",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100809500700002",
            "elementType": "node",
            "x": "104",
            "y": "383.5",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "attack-client",
            "textPosition": "Bottom_Center",
            "deviceId": "1406176935353.6848",
            "dataType": "VM",
            "nodeImage": "tpIcon_2.png",
            "templateId": "VT172.19.105.52015100910413200001",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100810295700002",
            "elementType": "node",
            "x": "539",
            "y": "321.5",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "intert-networker",
            "textPosition": "Bottom_Center",
            "deviceId": "564241624829.4331",
            "dataType": "EC",
            "nodeImage": "tpIcon_5.png",
            "templateId": "NK172.19.105.52015100810295700001",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100810320100002",
            "elementType": "node",
            "x": "719",
            "y": "160.5",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "ws-manage",
            "textPosition": "Bottom_Center",
            "deviceId": "318016674603.1266",
            "dataType": "VM",
            "nodeImage": "mypc.png",
            "templateId": "VT172.19.105.52015100810320100001",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100810402700002",
            "elementType": "node",
            "x": "725",
            "y": "225.5",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "ws-browser",
            "textPosition": "Bottom_Center",
            "deviceId": "488323736331.2087",
            "dataType": "VM",
            "nodeImage": "tpIcon_2.png",
            "templateId": "VT172.19.105.52015100810402700001",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100810461200002",
            "elementType": "node",
            "x": "726",
            "y": "293.5",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "internal-mssql",
            "textPosition": "Bottom_Center",
            "deviceId": "827722371280.0199",
            "dataType": "VM",
            "nodeImage": "tpIcon_6.png",
            "templateId": "VT172.19.105.52015100810461200001",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100810485100002",
            "elementType": "node",
            "x": "726",
            "y": "361.5",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "internal-ossec",
            "textPosition": "Bottom_Center",
            "deviceId": "1045863334277.662",
            "dataType": "VM",
            "nodeImage": "tpIcon_6.png",
            "templateId": "VT172.19.105.52015100810485100001",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100810523500002",
            "elementType": "node",
            "x": "369",
            "y": "245.5",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "internet-www",
            "textPosition": "Bottom_Center",
            "deviceId": "1262070648024.5728",
            "dataType": "ECVR",
            "nodeImage": "vr-selfdefined.png",
            "templateId": "RT172.19.105.52015100810523500001",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100811153300003",
            "elementType": "node",
            "x": "536",
            "y": "237.5",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "intert-router",
            "textPosition": "Bottom_Center",
            "deviceId": "1062340763210.1544",
            "dataType": "VR",
            "nodeImage": "tpIcon_9.png",
            "templateId": "RT172.19.105.52015100811281200001",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100811163200002",
            "elementType": "node",
            "x": "367.5",
            "y": "320",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "internet-blog",
            "textPosition": "Bottom_Center",
            "deviceId": "253209434749.73596",
            "dataType": "ECVR",
            "nodeImage": "vr-selfdefined.png",
            "templateId": "RT172.19.105.52015100811163200001",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100811204100002",
            "elementType": "node",
            "x": "370.5",
            "y": "403",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "internet-vpn",
            "textPosition": "Bottom_Center",
            "deviceId": "520008948580.8119",
            "dataType": "ECVR",
            "nodeImage": "vr-selfdefined.png",
            "templateId": "RT172.19.105.52015100811204100001",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100811512000001",
            "elementType": "node",
            "x": "194.5",
            "y": "166",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "attack-firewall",
            "textPosition": "Bottom_Center",
            "deviceId": "809054608657.865",
            "dataType": "FW",
            "nodeImage": "tpIcon_4.png",
            "templateId": "undefined",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100811512000003",
            "elementType": "node",
            "x": "534.5",
            "y": "163",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "intert-firewall",
            "textPosition": "Bottom_Center",
            "deviceId": "1250687739831.9912",
            "dataType": "FW",
            "nodeImage": "tpIcon_4.png",
            "templateId": "undefined",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          },
          {
            "id": "N172.19.105.52015100911045400002",
            "elementType": "node",
            "x": "727.5",
            "y": "430",
            "width": "32",
            "height": "32",
            "rotate": "0",
            "scaleX": "1",
            "scaleY": "1",
            "text": "defend-client",
            "textPosition": "Bottom_Center",
            "deviceId": "597645745716.1871",
            "dataType": "VM",
            "nodeImage": "tpIcon_2.png",
            "templateId": "VT172.19.105.52015100911045400001",
            "topoLevel": "1",
            "parentLevel": "0",
            "nextLevel": "0",
            "zindex": "3"
          }
        ]
      }
    ]
  }
}

Нам просто нужно позвонить на страницу

editor.loadTopology("images/backimg.png",'${templateId}','${topologyId}',"");

Среди них templateIdtemplateId и topologyId — это идентификаторы первичных ключей, связанные с таблицей внутренних данных, которые здесь можно игнорировать. Код метода loadTopology выглядит следующим образом:

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
/**
 * 加载环境模板ID对应的拓扑图JSON数据结构
 * @param backImg 拓扑图的背景图片
 * @param templateId 环境模板ID
 * @param topologyId 拓扑 表记录ID
 */
propertyPanel.prototype.loadTopology = function (backImg,templateId,topologyId,topoLevel) {
    if(!topoLevel) topoLevel = "";
    var self = this;
    self.showLoadingWindow();
    if (!templateId) {
        templateId = editor.templateId;
    }
    $.ajax({
        url: './topology.html',
        async: false,
        type: "GET",
        dataType: "html",
        data: {
            "templateId":templateId,
            "topologyId":topologyId,
            "topoLevel":topoLevel
        },
        error: function () {
            self.closeLoadingWindow();
            jAlert("服务器异常,请稍后重试..");
        },
        success: function (response) {
            response = JSON.parse(response);
            var err = response.errorInfo;
            // 错误处理
            if (err && err != "ok") {
                if(err == "-1"){
                    editor.init(backImg, templateId, topologyId,"-1","");
                }else if (err == "logout") {
                    handleSessionTimeOut();
                    return;
                } else {
                    self.closeLoadingWindow();
                    jAlert(err);
                }
            } else {
                var topologyJson = response.topologyJson;
                editor.init(backImg, templateId, topologyId, topologyJson,"");
            }
        }
    });
    };

Сначала отправить запрос на получение данных в формате json, а затем обработать структуру ответа.Логика реальной реализации загрузки топологии находится в методе editor.init. Здесь, если обнаруживается, что запрошенная топология не существует, будет создана пустая карта топологии.
Давайте посмотрим на логику реализации init:

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
/**
 * 编辑器初始化方法,根据请求返回结果加载空白的或者指定结构的拓扑编辑器
 * @param backImg     背景图片
 * @param templateId  环境模板ID
 * @param topologyId  拓扑记录ID
 * @param stageJson    拓扑JSON结构
 */
networkTopologyEditor.prototype.init = function (backImg,templateId,topologyId,stageJson,templateName) {
    if(!stageJson){
        jAlert("加载拓扑编辑器失败!");
        return;
    }
    this.templateId = templateId;
    this.topologyId = topologyId;
    //创建JTOP舞台屏幕对象
    var canvas = document.getElementById('drawCanvas');
    canvas.width = $("#contextBody").width();
    canvas.height = $("#contextBody").height();
    //加载空白的编辑器
    if(stageJson == "-1"){
        this.stage = new JTopo.Stage(canvas);
        this.stage.topoLevel = 1;
        this.stage.parentLevel = 0;
        this.modeIdIndex = 1;
        this.scene=  new JTopo.Scene(this.stage);
        this.scene.totalLevel = 1;
    }else{
        this.stage = JTopo.createStageFromJson(stageJson, canvas);
        this.scene = this.stage.childs[0];
    }
    $("#parentLevel").val(this.stage.parentLevel);
    //拓扑层次切换
    var options = "";
    for(var i = 1; i <= this.scene.totalLevel ;i++){
        options += '<option value="' + i +'" ';
        if( i == this.stage.topoLevel){
            options += 'selected="selected" ';
        }
        options += '>编辑第' + i + '层</option>';
    }
    $("#selectLevel").append(options);
    //滚轮缩放
    this.stage.frames = this.config.stageFrames;
    this.stage.wheelZoom = this.config.defaultScal;
    this.stage.eagleEye.visible = this.config.eagleEyeVsibleDefault;

    this.scene.mode = "edit";
    //背景由样式指定
    //this.scene.background = backImg;

    //用来连线的两个节点
    this.tempNodeA = new JTopo.Node('tempA');
    this.tempNodeA.setSize(1, 1);
    this.tempNodeZ = new JTopo.Node('tempZ');
    this.tempNodeZ.setSize(1, 1);
    this.beginNode = null;
    this.link = null;
    var self = this;

    //初始化菜单
    this.initMenus();
    //事件处理逻辑在此省略...
    //第一次进入拓扑编辑器,生成stage和scene对象
    if(stageJson == "-1"){
        this.saveToplogy(false);
    }
    //编辑器初始化完毕关闭loading窗口
    this.closeLoadingWindow();
}

Код здесь выглядит сложным, но на самом деле он делает две вещи:
1) Постройте базовую сцену и сцену
2) Вызовите JTopo.createStageFromJson(stageJson, canvas) для создания структуры всей топологии
3) Инициализируйте и отредактируйте его меню
4) Окончательно привязать логику обработки различных событий
5) Наконец, если при получении данных топологии возникает ошибка, создается пустая топология.

1
2
3
4
//第一次进入拓扑编辑器,生成stage和scene对象
   if(stageJson == "-1"){
       this.saveToplogy(false);
   }

На этом загрузка топологии и задача создания пустой топологии завершены.

сохранить топологию

Для сохранения топологии стоит сериализовать отредактированную топологию в json, а затем загрузить ее на сервер для сохранения базы данных. По самой простой идее нам нужно следовать Node, Scene, Stage. Итак, мы определяем следующие объекты сущности:

Node

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
package com.bjhit.cncert.common.beans.models;

import org.codehaus.jackson.map.annotate.JsonSerialize;

/**
 * Created by gongxufan on 2014/11/20.
 */
@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class Node
{
    private String id;
    private String elementType;
    private String x;
    private String y;
    private String width;
    private String height;
    private String alpha;
    private String rotate;
    private String scaleX;
    private String scaleY;
    private String strokeColor;
    private String fillColor;
    private String shadowColor;
    private String shadowOffsetX;
    private String shadowOffsetY;
    private String zIndex;
    private String text;
    private String font;
    private String fontColor;
    private String textPosition;
    private String textOffsetX;
    private String textOffsetY;
    private String borderRadius;
    private String deviceId;
    private String dataType;
    private String borderColor;
    private String offsetGap;
    private String childNodes;
    private String nodeImage;
    private String templateId;
    private String deviceA;
    private String deviceZ;
    private String lineType;
    private String direction;
    private String vmInstanceId;
    private String displayName;
    private String vmid;
    private String topoLevel;
    private String parentLevel;
    private String nextLevel;
    //getter/setter
}

Scene

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
package com.bjhit.cncert.common.beans.models;

import org.codehaus.jackson.map.annotate.JsonSerialize;
import java.util.List;

/**
 * Created by gongxufan on 2014/11/20.
 */
@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class Scene
{
    private String id;
    private String elementType;
    private String background;
    private String backgroundColor;
    private String mode;
    private String translateX;
    private String translateY;
    private String alpha;
    private String scaleX;
    private String scaleY;
    private String totalLevel;
    private String parentLevel;
    private String nextLevel;
    private String topoLevel;
    //getter/setter
}

Stage

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
package com.bjhit.cncert.common.beans.models;

import org.codehaus.jackson.map.annotate.JsonSerialize;
import java.util.List;
/**
 * 拓扑编辑器舞台对象
 * Created by gongxufan on 2014/11/20.
 */
@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class Stage
{
    private String version;
    private String frames;
    private String wheelZoom;
    private String deviceNum;
    private String width;
    private String height;
    private String id;
    private String totalLevel;
    private String topoLevel;
    private String parentLevel;
    private String nextLevel;
    private List<Scene> childs;
    //getter/setter
}

После определения этих трех структур у нас есть отношение сравнения между передней и задней частями. Тогда посмотрите, как сериализуется интерфейс?
Сначала посмотрите, как внешний интерфейс сериализует топологию:

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
/**
 * 保存序列化的JSON数据到服务器,为减少请求参数长度,进行了字符串的替换
 */
propertyPanel.prototype.saveToplogy = function (showAlert) {
        editor.mainMenu.hide();
        var self = this;
        this.showLoadingWindow();
        //先删除标尺线
        editor.utils.clearRuleLines();
        //保存container状态
        var containers = editor.utils.getContainers();
        for(var c=0 ; c < containers.length ; c++){
              var temp = [];
              var nodes = containers[c].childs;
              for(var n =0 ; n < nodes.length ; n++){
                  if(nodes[n] instanceof JTopo.Node){
                      temp.push(nodes[n].deviceId);
                  }
              }
            containers[c].childNodes = temp.join(",");
        }
        //设置拓扑当前层次和最大层次数
        var selectLevel = $("#selectLevel");
        var levels = selectLevel.find("option:selected");
        editor.stage.topoLevel = parseInt(levels.eq(0).val());
        if(editor.stage.topoLevel == -1) editor.stage.topoLevel = 1;
        editor.stage.parentLevel = $("#parentLevel").val();
        editor.scene.totalLevel = selectLevel.find("option").size() - 1;
        topologyJSON = editor.stage.toJson();
        if(topologyJSON){
            for(var key in this.jsonCode){//字符压缩
                topologyJSON = topologyJSON.replace(new RegExp('"'+ key +'"',"gm"),this.jsonCode[key]);
            }
            topologyJSON = topologyJSON.replace(new RegExp('",',"gm"),';');
            topologyJSON = topologyJSON.replace(new RegExp('"',"gm"),'@');
            topologyJSON = topologyJSON.replace(new RegExp('undefined',"gm"),'#');
        }
        $.ajax({
            url: context + "topologyManage/saveTopologyJSON",
            async: true,
            type: "POST",
            dataType: "json",
            data: {
                "topologyJSON": topologyJSON,
                "templateId": editor.templateId,
                "topologyId":editor.topologyId
            },
            error: function () {
                self.closeLoadingWindow();
                jAlert("服务器异常,请稍后重试..");
            },
            success: function (response) {
                var err = response.errorInfo;
                // 错误处理
                if (err && err != "ok") {
                    if (err == "logout") {
                        handleSessionTimeOut();
                        return;
                    } else {
                        self.closeLoadingWindow();
                        jAlert(err);
                    }
                } else {
      s/**
 * 保存序列化的JSON数据到服务器,为减少请求参数长度,进行了字符串的替换
 */
propertyPanel.prototype.saveToplogy = function (showAlert) {
        editor.mainMenu.hide();
        var self = this;
        this.showLoadingWindow();
        //先删除标尺线
        editor.utils.clearRuleLines();
        //保存container状态
        var containers = editor.utils.getContainers();
        for(var c=0 ; c < containers.length ; c++){
              var temp = [];
              var nodes = containers[c].childs;
              for(var n =0 ; n < nodes.length ; n++){
                  if(nodes[n] instanceof JTopo.Node){
                      temp.push(nodes[n].deviceId);
                  }
              }
            containers[c].childNodes = temp.join(",");
        }
        //设置拓扑当前层次和最大层次数
        var selectLevel = $("#selectLevel");
        var levels = selectLevel.find("option:selected");
        editor.stage.topoLevel = parseInt(levels.eq(0).val());
        if(editor.stage.topoLevel == -1) editor.stage.topoLevel = 1;
        editor.stage.parentLevel = $("#parentLevel").val();
        editor.scene.totalLevel = selectLevel.find("option").size() - 1;
        topologyJSON = editor.stage.toJson();
        if(topologyJSON){
            for(var key in this.jsonCode){//字符压缩
                topologyJSON = topologyJSON.replace(new RegExp('"'+ key +'"',"gm"),this.jsonCode[key]);
            }
            topologyJSON = topologyJSON.replace(new RegExp('",',"gm"),';');
            topologyJSON = topologyJSON.replace(new RegExp('"',"gm"),'@');
            topologyJSON = topologyJSON.replace(new RegExp('undefined',"gm"),'#');
        }
        $.ajax({
            url: context + "topologyManage/saveTopologyJSON",
            async: true,
            type: "POST",
            dataType: "json",
            data: {
                "topologyJSON": topologyJSON,
                "templateId": editor.templateId,
                "topologyId":editor.topologyId
            },
            error: function () {
                self.closeLoadingWindow();
                jAlert("服务器异常,请稍后重试..");
            },
            success: function (response) {
                var err = response.errorInfo;
                // 错误处理
                if (err && err != "ok") {
                    if (err == "logout") {
                        handleSessionTimeOut();
                        return;
                    } else {
                        self.closeLoadingWindow();
                        jAlert(err);
                    }
                } else {
                    self.replaceStage(editor.templateId,editor.topologyId,showAlert,editor.stage.topoLevel);
                    self.closeLoadingWindow();
                }
            }
        });
    };              self.replaceStage(editor.templateId,editor.topologyId,showAlert,editor.stage.topoLevel);
                    self.closeLoadingWindow();
                }
            }
        });
    };
  1. Чтобы удалить линии линейки (горизонтальные и вертикальные линии, отображаемые в области редактирования), этот объект нужно удалить из нашей структуры, а также вызвать метод clearRuleLines.
  2. Объекты узлов в области контейнеров необходимо извлечь, поскольку эти узлы уже включены при вызове сериализации, поэтому вам нужно только связать их идентификаторы узлов с контейнерами при их сохранении.
  3. Если поддерживается иерархическое редактирование, также сохраните количество уровней в текущей топологии.
  4. Вызовите editor.stage.toJson(), чтобы получить структуру всей топологии.
  5. замена json, просто для уменьшения передаваемых данных
  6. Наконец, отправьте проанализированный json в фоновую обработку.

Остальные подробности здесь повторяться не будут.Этот проект требует от читателей базовых знаний, таких как H5, easyUI, jTopo, canvas и JSON. Что касается jTopo, вы можете быстро начать работу, просто взглянув на DEMO и несколько API, предоставленных его автором. Лучший способ научиться — разбить различные трассировки отладки в точках останова и посмотреть, как все это работает. Демонстрируемое здесь редактирование топологии также является очень простым и неполным примером.На самом деле, есть еще много вещей, которые можно настроить, например соединения и методы подключения, которые можно настроить дополнительно.

end

Использованная литература:
www.jtopo.com/

Woohoo J easy UI.com/document ATI…

www.w3school.com.cn/html5/