Files
张成 c8b60c3349 1
2025-11-26 15:35:18 +08:00

538 lines
15 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 统计数据管理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.isBefore(dayStart, 'day') && !itemDate.isAfter(dayEnd, 'day');
}).length;
const jobCount = allJobs.filter(item => {
const itemDate = dayjs(item.create_time);
return !itemDate.isBefore(dayStart, 'day') && !itemDate.isAfter(dayEnd, 'day');
}).length;
const chatCount = allChats.filter(item => {
const itemDate = dayjs(item.sendTime);
return !itemDate.isBefore(dayStart, 'day') && !itemDate.isAfter(dayEnd, 'day');
}).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.isBefore(dayStart, 'day') && !itemDate.isAfter(dayEnd, 'day');
}).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.isBefore(dayStart, 'day') && !itemDate.isAfter(dayEnd, 'day');
}).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.isBefore(dayStart, 'day') && !itemDate.isAfter(dayEnd, 'day');
}).length;
dates.push(dateStr);
data.push(count);
currentDate = currentDate.add(1, 'day');
}
return ctx.success({ dates, data });
}
};