538 lines
15 KiB
JavaScript
538 lines
15 KiB
JavaScript
/**
|
||
* 统计数据管理API - 后台管理
|
||
* 提供首页统计数据的查询功能
|
||
*/
|
||
|
||
const Framework = require("../../framework/node-core-framework.js");
|
||
const dayjs = require('dayjs');
|
||
|
||
module.exports = {
|
||
/**
|
||
* @swagger
|
||
* /admin_api/statistics/overview:
|
||
* get:
|
||
* summary: 获取今日统计概览
|
||
* description: 获取指定设备今日的投递、找工作、聊天、执行中任务数量
|
||
* tags: [后台-统计管理]
|
||
* parameters:
|
||
* - in: query
|
||
* name: deviceSn
|
||
* required: true
|
||
* schema:
|
||
* type: string
|
||
* description: 设备SN码
|
||
* responses:
|
||
* 200:
|
||
* description: 获取成功
|
||
*/
|
||
'GET /statistics/overview': async (ctx) => {
|
||
const models = Framework.getModels();
|
||
const { apply_records, job_postings, chat_records, task_status, op } = models;
|
||
const { deviceSn } = ctx.getQuery();
|
||
|
||
if (!deviceSn) {
|
||
return ctx.fail('设备SN码不能为空');
|
||
}
|
||
|
||
const todayStart = dayjs().startOf('day').toDate();
|
||
const todayEnd = dayjs().endOf('day').toDate();
|
||
|
||
const [
|
||
applyCount,
|
||
jobSearchCount,
|
||
chatCount,
|
||
runningTaskCount
|
||
] = await Promise.all([
|
||
apply_records.count({
|
||
where: {
|
||
sn_code: deviceSn,
|
||
applyTime: {
|
||
[op.gte]: todayStart,
|
||
[op.lte]: todayEnd
|
||
}
|
||
}
|
||
}).catch(err => {
|
||
console.error('[统计] 查询投递数量失败:', err);
|
||
return 0;
|
||
}),
|
||
job_postings.count({
|
||
where: {
|
||
sn_code: deviceSn,
|
||
create_time: {
|
||
[op.gte]: todayStart,
|
||
[op.lte]: todayEnd
|
||
}
|
||
}
|
||
}).catch(err => {
|
||
console.error('[统计] 查询找工作数量失败:', err);
|
||
return 0;
|
||
}),
|
||
chat_records.count({
|
||
where: {
|
||
sn_code: deviceSn,
|
||
direction: 'sent',
|
||
sendTime: {
|
||
[op.gte]: todayStart,
|
||
[op.lte]: todayEnd
|
||
}
|
||
}
|
||
}).catch(err => {
|
||
console.error('[统计] 查询聊天数量失败:', err);
|
||
return 0;
|
||
}),
|
||
task_status.count({
|
||
where: {
|
||
sn_code: deviceSn,
|
||
status: 'running'
|
||
}
|
||
}).catch(err => {
|
||
console.error('[统计] 查询任务数量失败:', err);
|
||
return 0;
|
||
})
|
||
]);
|
||
|
||
return ctx.success({
|
||
applyCount,
|
||
jobSearchCount,
|
||
chatCount,
|
||
runningTaskCount
|
||
});
|
||
},
|
||
|
||
/**
|
||
* @swagger
|
||
* /admin_api/statistics/daily:
|
||
* post:
|
||
* summary: 获取按天统计的数据
|
||
* description: 获取指定设备指定时间范围内按天统计的数据,用于图表展示
|
||
* tags: [后台-统计管理]
|
||
* requestBody:
|
||
* required: true
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* required:
|
||
* - deviceSn
|
||
* properties:
|
||
* deviceSn:
|
||
* type: string
|
||
* description: 设备SN码
|
||
* days:
|
||
* type: integer
|
||
* description: 统计最近几天(例如7天)
|
||
* startDate:
|
||
* type: string
|
||
* description: 开始日期 (YYYY-MM-DD)
|
||
* endDate:
|
||
* type: string
|
||
* description: 结束日期 (YYYY-MM-DD)
|
||
* responses:
|
||
* 200:
|
||
* description: 获取成功
|
||
*/
|
||
'POST /statistics/daily': async (ctx) => {
|
||
const models = Framework.getModels();
|
||
const { apply_records, job_postings, chat_records, op } = models;
|
||
const { deviceSn, days, startDate, endDate } = ctx.getBody();
|
||
|
||
if (!deviceSn) {
|
||
return ctx.fail('设备SN码不能为空');
|
||
}
|
||
|
||
let start;
|
||
let end;
|
||
if (days) {
|
||
const maxDays = Math.min(days, 30);
|
||
end = dayjs().endOf('day').toDate();
|
||
start = dayjs().subtract(maxDays - 1, 'day').startOf('day').toDate();
|
||
} else if (startDate && endDate) {
|
||
start = dayjs(startDate).startOf('day').toDate();
|
||
end = dayjs(endDate).endOf('day').toDate();
|
||
const diffDays = dayjs(end).diff(dayjs(start), 'day') + 1;
|
||
if (diffDays > 30) {
|
||
end = dayjs(start).add(29, 'day').endOf('day').toDate();
|
||
}
|
||
} else {
|
||
end = dayjs().endOf('day').toDate();
|
||
start = dayjs().subtract(6, 'day').startOf('day').toDate();
|
||
}
|
||
|
||
const [allApplies, allJobs, allChats] = await Promise.all([
|
||
apply_records.findAll({
|
||
where: {
|
||
sn_code: deviceSn,
|
||
applyTime: {
|
||
[op.gte]: start,
|
||
[op.lte]: end
|
||
}
|
||
},
|
||
attributes: ['applyTime'],
|
||
raw: true
|
||
}).catch(err => {
|
||
console.error('[统计] 查询投递记录失败:', err);
|
||
return [];
|
||
}),
|
||
job_postings.findAll({
|
||
where: {
|
||
sn_code: deviceSn,
|
||
create_time: {
|
||
[op.gte]: start,
|
||
[op.lte]: end
|
||
}
|
||
},
|
||
attributes: ['create_time'],
|
||
raw: true
|
||
}).catch(err => {
|
||
console.error('[统计] 查询岗位记录失败:', err);
|
||
return [];
|
||
}),
|
||
chat_records.findAll({
|
||
where: {
|
||
sn_code: deviceSn,
|
||
direction: 'sent',
|
||
sendTime: {
|
||
[op.gte]: start,
|
||
[op.lte]: end
|
||
}
|
||
},
|
||
attributes: ['sendTime'],
|
||
raw: true
|
||
}).catch(err => {
|
||
console.error('[统计] 查询聊天记录失败:', err);
|
||
return [];
|
||
})
|
||
]);
|
||
|
||
const dates = [];
|
||
const applyData = [];
|
||
const jobSearchData = [];
|
||
const chatData = [];
|
||
|
||
let currentDate = dayjs(start);
|
||
while (currentDate.isBefore(end) || currentDate.isSame(end, 'day')) {
|
||
const dateStr = currentDate.format('YYYY-MM-DD');
|
||
const dayStart = currentDate.startOf('day');
|
||
const dayEnd = currentDate.endOf('day');
|
||
|
||
const applyCount = allApplies.filter(item => {
|
||
const itemDate = dayjs(item.applyTime);
|
||
return itemDate.isSameOrAfter(dayStart) && itemDate.isSameOrBefore(dayEnd);
|
||
}).length;
|
||
|
||
const jobCount = allJobs.filter(item => {
|
||
const itemDate = dayjs(item.create_time);
|
||
return itemDate.isSameOrAfter(dayStart) && itemDate.isSameOrBefore(dayEnd);
|
||
}).length;
|
||
|
||
const chatCount = allChats.filter(item => {
|
||
const itemDate = dayjs(item.sendTime);
|
||
return itemDate.isSameOrAfter(dayStart) && itemDate.isSameOrBefore(dayEnd);
|
||
}).length;
|
||
|
||
dates.push(dateStr);
|
||
applyData.push(applyCount);
|
||
jobSearchData.push(jobCount);
|
||
chatData.push(chatCount);
|
||
|
||
currentDate = currentDate.add(1, 'day');
|
||
}
|
||
|
||
return ctx.success({
|
||
dates,
|
||
applyData,
|
||
jobSearchData,
|
||
chatData
|
||
});
|
||
},
|
||
|
||
/**
|
||
* @swagger
|
||
* /admin_api/statistics/running-tasks:
|
||
* get:
|
||
* summary: 获取当前正在执行的任务
|
||
* description: 获取指定设备当前正在执行的任务及其命令列表
|
||
* tags: [后台-统计管理]
|
||
* parameters:
|
||
* - in: query
|
||
* name: deviceSn
|
||
* required: true
|
||
* schema:
|
||
* type: string
|
||
* description: 设备SN码
|
||
* responses:
|
||
* 200:
|
||
* description: 获取成功
|
||
*/
|
||
'GET /statistics/running-tasks': async (ctx) => {
|
||
const models = Framework.getModels();
|
||
const { task_status, task_commands } = models;
|
||
const { deviceSn } = ctx.getQuery();
|
||
|
||
if (!deviceSn) {
|
||
return ctx.fail('设备SN码不能为空');
|
||
}
|
||
|
||
// 查询正在执行的任务
|
||
const runningTasks = await task_status.findAll({
|
||
where: {
|
||
sn_code: deviceSn,
|
||
status: 'running'
|
||
},
|
||
order: [['startTime', 'DESC']],
|
||
limit: 10 // 限制最多返回10个任务
|
||
});
|
||
|
||
// 为每个任务查询其命令列表
|
||
const tasksWithCommands = await Promise.all(
|
||
runningTasks.map(async (task) => {
|
||
const commands = await task_commands.findAll({
|
||
where: { task_id: task.id },
|
||
order: [
|
||
['sequence', 'ASC'],
|
||
['create_time', 'ASC']
|
||
]
|
||
});
|
||
|
||
return {
|
||
taskId: task.id,
|
||
taskName: task.taskName || task.taskType,
|
||
taskType: task.taskType,
|
||
startTime: dayjs(task.startTime).format('YYYY-MM-DD HH:mm:ss'),
|
||
progress: task.progress || 0,
|
||
commands: commands.map(cmd => ({
|
||
commandId: cmd.id,
|
||
commandName: cmd.command_name,
|
||
status: cmd.status || 'pending'
|
||
}))
|
||
};
|
||
})
|
||
);
|
||
|
||
return ctx.success(tasksWithCommands);
|
||
},
|
||
|
||
/**
|
||
* @swagger
|
||
* /admin_api/statistics/apply:
|
||
* post:
|
||
* summary: 获取投递数量统计(按天)
|
||
* description: 获取指定设备按天的投递数量统计
|
||
* tags: [后台-统计管理]
|
||
* requestBody:
|
||
* required: true
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* required:
|
||
* - deviceSn
|
||
* properties:
|
||
* deviceSn:
|
||
* type: string
|
||
* days:
|
||
* type: integer
|
||
* responses:
|
||
* 200:
|
||
* description: 获取成功
|
||
*/
|
||
'POST /statistics/apply': async (ctx) => {
|
||
const models = Framework.getModels();
|
||
const { apply_records, op } = models;
|
||
const { deviceSn, days = 7 } = ctx.getBody();
|
||
|
||
if (!deviceSn) {
|
||
return ctx.fail('设备SN码不能为空');
|
||
}
|
||
|
||
const maxDays = Math.min(days, 30);
|
||
const end = dayjs().endOf('day').toDate();
|
||
const start = dayjs().subtract(maxDays - 1, 'day').startOf('day').toDate();
|
||
|
||
const allApplies = await apply_records.findAll({
|
||
where: {
|
||
sn_code: deviceSn,
|
||
applyTime: {
|
||
[op.gte]: start,
|
||
[op.lte]: end
|
||
}
|
||
},
|
||
attributes: ['applyTime'],
|
||
raw: true
|
||
}).catch(err => {
|
||
console.error('[统计] 查询投递记录失败:', err);
|
||
return [];
|
||
});
|
||
|
||
const dates = [];
|
||
const data = [];
|
||
|
||
let currentDate = dayjs(start);
|
||
while (currentDate.isBefore(end) || currentDate.isSame(end, 'day')) {
|
||
const dateStr = currentDate.format('YYYY-MM-DD');
|
||
const dayStart = currentDate.startOf('day');
|
||
const dayEnd = currentDate.endOf('day');
|
||
|
||
const count = allApplies.filter(item => {
|
||
const itemDate = dayjs(item.applyTime);
|
||
return itemDate.isSameOrAfter(dayStart) && itemDate.isSameOrBefore(dayEnd);
|
||
}).length;
|
||
|
||
dates.push(dateStr);
|
||
data.push(count);
|
||
currentDate = currentDate.add(1, 'day');
|
||
}
|
||
|
||
return ctx.success({ dates, data });
|
||
},
|
||
|
||
/**
|
||
* @swagger
|
||
* /admin_api/statistics/job-search:
|
||
* post:
|
||
* summary: 获取找工作数量统计(按天)
|
||
* description: 获取指定设备按天的找工作数量统计
|
||
* tags: [后台-统计管理]
|
||
* requestBody:
|
||
* required: true
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* required:
|
||
* - deviceSn
|
||
* properties:
|
||
* deviceSn:
|
||
* type: string
|
||
* days:
|
||
* type: integer
|
||
* responses:
|
||
* 200:
|
||
* description: 获取成功
|
||
*/
|
||
'POST /statistics/job-search': async (ctx) => {
|
||
const models = Framework.getModels();
|
||
const { job_postings, op } = models;
|
||
const { deviceSn, days = 7 } = ctx.getBody();
|
||
|
||
if (!deviceSn) {
|
||
return ctx.fail('设备SN码不能为空');
|
||
}
|
||
|
||
const maxDays = Math.min(days, 30);
|
||
const end = dayjs().endOf('day').toDate();
|
||
const start = dayjs().subtract(maxDays - 1, 'day').startOf('day').toDate();
|
||
|
||
const allJobs = await job_postings.findAll({
|
||
where: {
|
||
sn_code: deviceSn,
|
||
create_time: {
|
||
[op.gte]: start,
|
||
[op.lte]: end
|
||
}
|
||
},
|
||
attributes: ['create_time'],
|
||
raw: true
|
||
}).catch(err => {
|
||
console.error('[统计] 查询岗位记录失败:', err);
|
||
return [];
|
||
});
|
||
|
||
const dates = [];
|
||
const data = [];
|
||
|
||
let currentDate = dayjs(start);
|
||
while (currentDate.isBefore(end) || currentDate.isSame(end, 'day')) {
|
||
const dateStr = currentDate.format('YYYY-MM-DD');
|
||
const dayStart = currentDate.startOf('day');
|
||
const dayEnd = currentDate.endOf('day');
|
||
|
||
const count = allJobs.filter(item => {
|
||
const itemDate = dayjs(item.create_time);
|
||
return itemDate.isSameOrAfter(dayStart) && itemDate.isSameOrBefore(dayEnd);
|
||
}).length;
|
||
|
||
dates.push(dateStr);
|
||
data.push(count);
|
||
currentDate = currentDate.add(1, 'day');
|
||
}
|
||
|
||
return ctx.success({ dates, data });
|
||
},
|
||
|
||
/**
|
||
* @swagger
|
||
* /admin_api/statistics/chat:
|
||
* post:
|
||
* summary: 获取聊天/沟通数量统计(按天)
|
||
* description: 获取指定设备按天的聊天数量统计
|
||
* tags: [后台-统计管理]
|
||
* requestBody:
|
||
* required: true
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* required:
|
||
* - deviceSn
|
||
* properties:
|
||
* deviceSn:
|
||
* type: string
|
||
* days:
|
||
* type: integer
|
||
* responses:
|
||
* 200:
|
||
* description: 获取成功
|
||
*/
|
||
'POST /statistics/chat': async (ctx) => {
|
||
const models = Framework.getModels();
|
||
const { chat_records, op } = models;
|
||
const { deviceSn, days = 7 } = ctx.getBody();
|
||
|
||
if (!deviceSn) {
|
||
return ctx.fail('设备SN码不能为空');
|
||
}
|
||
|
||
const maxDays = Math.min(days, 30);
|
||
const end = dayjs().endOf('day').toDate();
|
||
const start = dayjs().subtract(maxDays - 1, 'day').startOf('day').toDate();
|
||
|
||
const allChats = await chat_records.findAll({
|
||
where: {
|
||
sn_code: deviceSn,
|
||
direction: 'sent',
|
||
sendTime: {
|
||
[op.gte]: start,
|
||
[op.lte]: end
|
||
}
|
||
},
|
||
attributes: ['sendTime'],
|
||
raw: true
|
||
}).catch(err => {
|
||
console.error('[统计] 查询聊天记录失败:', err);
|
||
return [];
|
||
});
|
||
|
||
const dates = [];
|
||
const data = [];
|
||
|
||
let currentDate = dayjs(start);
|
||
while (currentDate.isBefore(end) || currentDate.isSame(end, 'day')) {
|
||
const dateStr = currentDate.format('YYYY-MM-DD');
|
||
const dayStart = currentDate.startOf('day');
|
||
const dayEnd = currentDate.endOf('day');
|
||
|
||
const count = allChats.filter(item => {
|
||
const itemDate = dayjs(item.sendTime);
|
||
return itemDate.isSameOrAfter(dayStart) && itemDate.isSameOrBefore(dayEnd);
|
||
}).length;
|
||
|
||
dates.push(dateStr);
|
||
data.push(count);
|
||
currentDate = currentDate.add(1, 'day');
|
||
}
|
||
|
||
return ctx.success({ dates, data });
|
||
}
|
||
};
|