1
This commit is contained in:
537
api/controller_admin/statistics.js
Normal file
537
api/controller_admin/statistics.js
Normal file
@@ -0,0 +1,537 @@
|
||||
/**
|
||||
* 统计数据管理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 });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user