wangrong
2021-12-17 1c2ac631d4afda8076da79c1d5e656085bdb41b1
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
const router = require("koa-router")();
const mysql = require("../utils/mysql_db");
const jpush = require("../utils/jpush");
 
router.prefix("/apicloud");
 
let map_timeout = {}; //心跳超时检测
 
const multer = require("koa-multer"); //加载koa-multer模块
//文件上传
//配置
var storage = multer.diskStorage({
  //文件保存路径
  destination: function(req, file, cb) {
    cb(null, "public/uploads/");
  },
  //修改文件名称
  filename: function(req, file, cb) {
    var fileFormat = file.originalname.split(".");
    cb(null, Date.now() + "." + fileFormat[fileFormat.length - 1]);
  }
});
//加载配置
var upload = multer({
  storage: storage
});
 
/**
 * 上传录音文件
 * 传入参数 file
 */
router.post("/upload", upload.single("file"), async (ctx, next) => {
  if (ctx.req.file) {
    //TODO 语音识别
    //AipSpeechClient.recognize(ctx.req.file.filename);
    ctx.body = {
      success: true,
      filename: ctx.req.file.filename //返回文件名
    };
  } else {
    ctx.body = {
      success: false
    };
  }
});
 
//下发即时任务/apicloud/jpush?alias=123456780000432&commond=record&extra={"freq":"98","length":10000}
router.get("/jpush", async function(ctx, next) {
  let params = ctx.query;
  let devices = JSON.parse(params.devices);
  if (params.command == "scan") {
    //扫台
    setDeviceStatus(devices, "2"); //更新设备状态 2:扫台
    // let extra = JSON.parse(params.extra)
    //生成并插入即时任务
    let sql = `INSERT INTO fm_t_realtime_task(device_list,command,freq_range,recording_length,create_time,detail)
                  VALUES('${JSON.stringify(devices)}',
                         '扫台',
                         ${JSON.stringify(params.freq_range)},
                         ${params.length},
                         NOW(),
                         ${
                           params.detail
                             ? "".concat("'", params.detail, "'")
                             : "null"
                         }
                        );`;
    let result = await mysql.queryNewId(sql);
    console.log(result);
    //下发扫台指令
    let extra = {};
    extra.identifier = {
      task_type: "即时任务",
      task_id: result[0].new_id
    };
    extra.title = "updateTask";
    extra.task_id = result[0].new_id;
    if (params.length == "0") {
      console.log(params.length);
      extra.isNeedRecording = false;
    } else {
      console.log(params.length);
      extra.isNeedRecording = true;
    }
 
    extra.isPeriodTask = false;
    extra.startTime = "04:00:00";
    extra.endTime = "03:27:00";
    extra.recordSeconds = params.length * 1000;
    let freq = JSON.parse(params.freq_range);
    extra.startFrequency = freq[0];
    extra.endFrequenty = freq[1];
 
    let sendDevices = [];
    for (let i = 0; i < devices.length; i++) {
      if (devices[i]) {
        sendDevices.push(devices[i]);
      }
    }
 
    // console.log(sendDevices);
 
    jpush.pushWithAlias(sendDevices.join(","), "updateTask", extra);
    params.logDes = `对编号为${params.devices}的设备下发扫台任务。`;
    ctx.body = {
      code: 20000,
      task_id: result[0].new_id
    };
  } else if (params.command == "record") {
    //录音
    setDeviceStatus(devices, "3"); //更新设备状态
    let extra = JSON.parse(params.extra);
    //生成并插入即时任务
    let sql = `INSERT INTO fm_t_realtime_task(device_list,command,create_time,detail,freq_range,recording_length)
                  VALUES('${JSON.stringify(devices)}',
                         '录音',
                         NOW(),
                         ${
                           params.detail
                             ? "".concat("'", params.detail, "'")
                             : "null"
                         },
                         ${extra.freq},
                         ${extra.length}
                        );`;
    let result = await mysql.queryNewId(sql);
    //按设备依次插入任务执行记录并下发录音指令
    // let task_id = result[0].new_id //即时任务id
    //   sql = `INSERT INTO fm_t_task_record(task_type,task_id,device_lgid,start_time) VALUES
    //   ('即时任务', '${task_id}', '${devices[i]}', NOW());`
    //   result = await mysql.queryNewId(sql)
    let task_record_id = result[0].new_id;
    //添加数据标识
    extra.identifier = {
      task_type: "即时任务",
      task_record_id: task_record_id,
      result_type: 0
    };
    extra.title = "updateTask";
    extra.task_id = result[0].new_id;
    extra.isNeedRecording = true;
    extra.isPeriodTask = false;
    extra.startTime = "04:00:00";
    extra.endTime = "03:27:00";
    extra.recordSeconds = params.extra.length * 1000;
 
    let sendDevices = [];
    for (let i = 0; i < devices.length; i++) {
      if (devices[i]) {
        // let task_id = result[0].new_id //即时任务id
        // sql = `INSERT INTO fm_t_task_record(task_type,task_id,device_lgid,start_time) VALUES
        // ('即时任务', '${task_id}', '${devices[i]}', NOW());`
        // result = await mysql.queryNewId(sql)
        sendDevices.push(devices[i]);
      }
    }
    // console.log(sendDevices);
 
    jpush.pushWithAlias(sendDevices.join(","), "updateTask", extra); //extra中的录音长度单位为毫秒
    params.logDes = `对编号为${params.devices}的设备下发录音任务。`;
 
    ctx.body = {
      code: 20000,
      task_id: task_record_id
    };
  } else if (params.command == "reboot") {
    //重启
    params.logDes = `对编号为${params.devices}的设备下发重启任务。`;
 
    let sendDevices = [];
    for (let i = 0; i < devices.length; i++) {
      if (devices[i]) {
        sendDevices.push(devices[i]);
      }
    }
 
    jpush.pushWithAlias(sendDevices.join(","), "reboot", params);
    ctx.body = {
      code: 20000
    };
  } else if (params.command == "periodicTasks") {
    setDeviceStatus(devices, "2"); //更新设备状态
    // let par = JSON.parse(params.extra)
    //生成并插入即时任务
    let sql = `INSERT INTO fm_t_realtime_task(device_list,command,freq_range,recording_length,create_time,detail)
                  VALUES('${JSON.stringify(devices)}',
                         '周期任务',
                         ${JSON.stringify(params.freq)},
                         ${params.length},
                         NOW(),
                         ${
                           params.detail
                             ? "".concat("'", params.detail, "'")
                             : "null"
                         }
                        );`;
 
    let result = await mysql.queryNewId(sql);
    //下发周期任务指令
    let extra = {};
    extra.identifier = {
      task_type: "周期任务",
      task_id: result[0].new_id
    };
    extra.title = "updateTask";
    extra.task_id = result[0].new_id;
    extra.isNeedRecording = true;
    extra.isPeriodTask = true;
    extra.startTime = "04:00:00";
    extra.endTime = "03:27:00";
    extra.recordSeconds = params.length * 1000;
    extra.period_seconds = params.periodLength * 1000 * 60;
    let freq = JSON.parse(params.freq);
    extra.startFrequency = freq[0];
    extra.endFrequenty = freq[1];
 
    let sendDevices = [];
    for (let i = 0; i < devices.length; i++) {
      if (devices[i]) {
        sendDevices.push(devices[i]);
      }
    }
    // console.log(sendDevices);
 
    jpush.pushWithAlias(sendDevices.join(","), "updateTask", extra);
    params.logDes = `对编号为${params.devices}的设备下发周期任务。`;
    ctx.body = {
      code: 20000,
      task_id: result[0].new_id
    };
  } else if (params.command == "installApp") {
    //重启
    params.logDes = `对编号为${params.devices}的设备下发更新设备。`;
 
    let sendDevices = [];
    for (let i = 0; i < devices.length; i++) {
      if (devices[i]) {
        sendDevices.push(devices[i]);
      }
    }
 
    jpush.pushWithAlias(sendDevices.join(","), "installApp", params);
    ctx.body = {
      code: 20000
    };
  } else if (params.command == "restartApp") {
    //重启
    params.logDes = `对编号为${params.devices}的设备下发重启App。`;
    // console.log(devices.join(','))
    let sendDevices = [];
    for (let i = 0; i < devices.length; i++) {
      if (devices[i]) {
        sendDevices.push(devices[i]);
      }
    }
 
    jpush.pushWithAlias(sendDevices.join(","), "restartApp", params);
    ctx.body = {
      code: 20000
    };
  }
});
 
//设备程序启动上报接口
router.get("/appLoaded", async function(ctx, next) {
  let params = ctx.query;
  let sql = `SELECT  log_id FROM fm_t_device  WHERE phy_id='${params.id}';`;
  let result = await mysql.query(sql);
 
  if (result.length == 1) {
    ctx.session.log_id = result[0].log_id;
    sql = `UPDATE fm_t_device set status=1 WHERE log_id='${ctx.session.log_id}'`;
    params.logDes = `设备${ctx.session.log_id}启动上报。`;
    mysql.query(sql);
    ctx.body = {
      device_id: result[0].log_id,
      code: 20000
    };
  } else {
    ctx.body = {
      success: false
    };
  }
});
 
//设备心跳接口
router.get("/heartBeat", async function(ctx, next) {
  let params = ctx.query;
 
  //插入数据库
  let sql = `INSERT INTO fm_t_heartbeat
      (device_lgid,battery,lon,lat,upload_time) VALUES
      (
      '${ctx.session.log_id}',
      '${params.battery}',
      ${params.lon},
      ${params.lat},
      NOW()
      )`;
  await mysql.query(sql);
 
  //获取心跳间隔
  sql = `SELECT heartbeat from fm_t_device WHERE log_id='${ctx.session.log_id}'`;
  let result = await mysql.query(sql);
  let interval = result[0].heartbeat;
  params.logDes = `设备${ctx.session.log_id}设置心跳。`;
  ctx.body = {
    code: 20000,
    interval: interval
  };
 
  //心跳超时检测
  if (typeof map_timeout[ctx.session.log_id] != "undefined")
    clearTimeout(map_timeout[ctx.session.log_id]);
  map_timeout[ctx.session.log_id] = setTimeout(function() {
    sql = `UPDATE fm_t_device set status=0 WHERE log_id='${ctx.session.log_id}'`;
    mysql.query(sql);
  }, interval * 60 * 1000 + 30000);
});
 
/**
 * 设备周期任务获取接口
 * 需要返回下一次任务执行时间 next
 */
router.get("/PeriodicTask", async function(ctx, next) {
  let log_id = ctx.session.log_id; //设备逻辑号
  let time = -1; //下一次执行任务时间
 
  //获取周期任务id 和 period
  let sql = `SELECT id,period FROM fm_t_period_task WHERE id = (SELECT task_id FROM fm_t_device WHERE log_id = '${log_id}');`;
  let result = await mysql.query(sql);
 
  if (result.length > 0) {
    time = f_next(0, result[0].period * 60000); //计算任务时间
  }
  if (time != -1) {
    ctx.body = {
      success: true,
      next: time,
      identifier: {
        task_type: "周期任务",
        task_id: result[0].id
      } // 数据标识
    };
  } else {
    ctx.body = {
      success: false
    };
  }
});
 
//错误上报
router.get("/error", async function(ctx, next) {
  //TODO 数据库插入错误记录
 
  //返回是否需要重启应用程序
  ctx.body = {
    success: true,
    reboot: false
  };
});
 
/**上传扫台结果
 * 传入参数:result(扫台结果 字符串 &分割频率)  start(扫台开始时间 字符串) end(扫台结束时间) lon lat  ,
 */
router.get("/ReportScanResult", async function(ctx, next) {
  /**
   * 扫台记录保存流程:
   * 1 将扫台结果保存到任务记录表(fm_t_task_record)中
   * 2 如果是周期任务,则返回需要录音的频率列表;此外还需返回录音频率对应的标识,标识包括:
   *   i  task_record_id,即1中插入新的任务记录后得到的主键id
   *   ii result_type,即频率的判别类型(取值为字典表fm_d_result_type中的id值)
   */
  let log_id = ctx.session.log_id;
  let params = ctx.query;
  params.identifier = JSON.parse(params.identifier);
  let scan_result = params.result
    .split("&")
    .map(freq => Math.round(Number(freq) * 10) / 10);
 
  //保存任务执行记录及扫台结果(不需要区分周期任务和即时任务的扫台)
  let sql = `INSERT INTO fm_t_task_record(task_type,task_id,device_lgid,start_time,finish_time,scan_result)
               VALUES('${params.identifier.task_type}',
                      '${params.identifier.task_id}',
                      '${log_id}',
                      '${params.start}',
                      '${params.end}',
                      '${JSON.stringify(scan_result)}'
                     );`;
  let query_result = await mysql.queryNewId(sql);
  let task_record_id = query_result[0].new_id;
 
  //处理即时任务结果
  if (params.identifier.task_type == "即时任务") {
    setDeviceStatus([log_id], "1"); //完成扫台 更新状态为 空闲
  }
  //处理周期任务的扫台结果
  if (params.identifier.task_type == "周期任务") {
    //获取任务配置
    let sql = `SELECT freq_range,whitelist,recording_length FROM fm_t_period_task WHERE id=${params.identifier.task_id}`;
    let result = await mysql.query(sql);
    let freq_range = result[0].freq_range
      ? JSON.parse(result[0].freq_range)
      : null;
    let whitelist = result[0].whitelist
      ? JSON.parse(result[0].whitelist)
      : null;
    let recording_length = result[0].recording_length;
 
    //筛选出在任务扫描频率范围内的频率
    let freq_in_range = [];
    for (let i = 0; i < scan_result.length; i++) {
      const frequency = scan_result[i];
      if (f_inRanges(frequency, freq_range)) {
        freq_in_range.push(frequency);
      }
    }
 
    //根据白名单判断频率是否合法
 
    let freq_list = []; //需要录音的频率
    let identifier_list = []; //录音频率对应的数据标识
    if (whitelist && JSON.stringify(whitelist) != "{}") {
      //当任务白名单不为空时,利用白名单判断频率的合法性
 
      let whitelist_map = await getWhitelist(whitelist); //白名单详细数据
      //由于任务白名单保存的是白名单id,先获取白名单频率到id的映射
      let freq_map = {};
      for (let whitelist_id in whitelist) {
        freq_map[whitelist_map[whitelist_id].frequency] = whitelist_id;
      }
      //对扫台结果的频率进行逐个判别
      for (let i = 0; i < freq_in_range.length; i++) {
        const freq = freq_in_range[i];
        if (freq_map[freq] == undefined) {
          //当频率不在任务白名单中时
          //直接将频率分类为非法电台
          freq_list.push(freq);
          identifier_list.push({
            task_type: "周期任务",
            task_record_id: task_record_id,
            result_type: 2 //类型:频率非法
          });
        } else {
          //当频率在任务白名单中时
          const wid = freq_map[freq]; //频率所对应的白名单id
          let time_legal = true; //时间合法性
          let area_legal = true; //区域合法性
 
          //判断时间是否合法
          let task_time = new Date(params.start); //任务开始时间
          let i_day = task_time.getDay(); //星期
          i_day = i_day == 0 ? 7 : i_day;
          if (whitelist_map[wid].runtime) {
            //如果设置了运行时
            const runtime_day = whitelist_map[wid].runtime[i_day];
            if (runtime_day && runtime_day.length > 0) {
              //如果设置了当天的运行时
              let i_hour = task_time.getHours();
              time_legal = false;
              for (let index = 0; index < runtime_day.length; index++) {
                if (i_hour == runtime_day[index]) {
                  time_legal = true;
                  break;
                }
              }
            } else {
              //如果当天的运行时未设置,则判别为非法
              time_legal = false;
            }
          } else {
            //如果没有设置运行时,则直接判别频率不合法
            time_legal = false;
          }
 
          //TODO: 判断区域是否合法
 
          //合法性判别
          if (time_legal && area_legal) {
            //如果时间区域均合法且设置了录音
            if (whitelist[wid] * 1) {
              freq_list.push(freq);
              identifier_list.push({
                task_type: "周期任务",
                task_record_id: task_record_id,
                result_type: 1 //类型:合法电台
              });
            }
          } else {
            //优先判别时间合法性(即:如果时间非法则不再判断区域非法)
            if (!time_legal) {
              freq_list.push(freq);
              identifier_list.push({
                task_type: "周期任务",
                task_record_id: task_record_id,
                result_type: 3 //类型:时间非法
              });
            } else {
              freq_list.push(freq);
              identifier_list.push({
                task_type: "周期任务",
                task_record_id: task_record_id,
                result_type: 4 //类型:区域非法
              });
            }
          }
        }
      }
    } else {
      //当任务的白名单为空时,则将全部扫描结果判定为非法电台
      for (let i = 0; i < freq_in_range.length; i++) {
        const freq = freq_in_range[i];
        freq_list.push(freq);
        identifier_list.push({
          task_type: "周期任务",
          task_record_id: task_record_id,
          result_type: 2 //类型:频率非法
        });
      }
    }
    ctx.body = {
      success: true,
      gray_list: freq_list, //需要录音的频率数组
      identifier_list: identifier_list, //录音命令对应的数据标识
      recording_length: recording_length //秒
    };
  }
  ctx.body = {
    success: true,
    gray_list: []
  };
});
 
//录音结果上报
router.get("/ReportRecordResult", async function(ctx, next) {
  let params = ctx.query;
  const identifier = JSON.parse(params.identifier);
  let sql = `INSERT INTO fm_t_scan_record(task_record_id,frequency,lon,lat,signal_strength,radio_file_path,result,upload_time)
               VALUES(${identifier.task_record_id},
                      ${Math.round(params.frequency * 10) / 10},
                      ${params.lon},
                      ${params.lat},
                      ${params.rssi},
                      '/uploads/${params.file}',
                      '${identifier.result_type}',
                      '${params.start}'
                     );`;
  mysql.query(sql);
  //由于即时任务的录音,其task_record 和 scan_record是一对一的
  //所以对于即时录音任务,在录音完成后可更新任务记录表中的任务结束时间
  if (identifier.task_type == "即时任务") {
    setDeviceStatus([ctx.session.log_id], "1"); //完成录音 更新状态为 空闲
    sql = `UPDATE fm_t_task_record SET finish_time='${params.end}' WHERE id=${identifier.task_record_id};`;
    mysql.query(sql);
  }
  ctx.body = {
    success: true
  };
});
 
/**
 * 计算下一个周期任务的执行时间
 * @param {number} start 起算点(时间戳)
 * @param {number} period 周期
 * @param {number} end 结束点(时间戳)
 */
function f_next(start, period, end) {
  let t = Math.ceil((Date.now() - start) / period) * period + start;
  if (end) {
    t = t <= end ? t : -1;
  }
  return t;
}
 
/**
 * 判断目标频率是否在频率区间内
 * @param {number} target 目标频率
 * @param {Array<Array<number>>} ranges 扫描频率范围
 */
function f_inRanges(target, ranges) {
  target = target * 1;
  for (let i = 0; i < ranges.length; i++) {
    const range = ranges[i];
    if (range.length == 1) {
      if (target == range[0]) return true;
    } else if (range.length == 2) {
      if (target >= range[0] && target <= range[1]) return true;
    } else {
      console.log("警告");
    }
  }
  return false;
}
 
/**
 * 获取任务白名单的详细信息
 * @param {JSON} whitelist_of_task 任务白名单
 */
async function getWhitelist(whitelist_of_task) {
  let ids = [];
  for (let id in whitelist_of_task) {
    ids.push(id);
  }
  let sql = `SELECT id,frequency,runtime,area from fm_t_whitelist where id IN (${ids.join(
    ","
  )});`;
  let result = await mysql.query(sql);
  whitelist_map = {};
  for (let i = 0; i < result.length; i++) {
    let element = result[i];
    if (element.runtime) {
      element.runtime = JSON.parse(element.runtime);
      //处理运行时为空对象的情况(正常情况下是不应该有这种情况的)
      if (JSON.stringify(element.runtime) == "{}") {
        element.runtime = null;
      }
    }
    whitelist_map[element.id] = element;
  }
  return whitelist_map;
}
 
/**
 * 设置设备的状态(主要用于下发即时任务和完成任务时改变设备的状态)
 * @param {Array<String>} device_list 设备逻辑号列表
 * @param {String} status 设备状态
 */
function setDeviceStatus(device_list, status) {
  let sql = `UPDATE fm_t_device SET status='${status}' WHERE log_id IN ('${device_list.join(
    "','"
  )}');`;
  mysql.query(sql);
}
 
module.exports = router;