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>} 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} 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;