This commit is contained in:
张成
2025-11-24 13:23:42 +08:00
commit 5d7444cd65
156 changed files with 50653 additions and 0 deletions

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