1
This commit is contained in:
@@ -323,7 +323,7 @@ export default {
|
|||||||
sort_order: row.sort_order || 0
|
sort_order: row.sort_order || 0
|
||||||
}
|
}
|
||||||
this.$refs.editModal.editShow(editRow,(newRow)=>{
|
this.$refs.editModal.editShow(editRow,(newRow)=>{
|
||||||
debugger
|
|
||||||
this.handleSaveSuccess(newRow)
|
this.handleSaveSuccess(newRow)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -326,62 +326,76 @@ module.exports = {
|
|||||||
return ctx.fail('请提供设备SN码');
|
return ctx.fail('请提供设备SN码');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { task_status, task_commands, op } = await Framework.getModels();
|
const { task_commands } = await Framework.getModels();
|
||||||
|
const sequelize = task_commands.sequelize;
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
|
||||||
// 先查当前账号对应任务,再按 task_id 关联查指令
|
const conditions = ['ts.sn_code = :sn_code'];
|
||||||
const task_rows = await task_status.findAll({
|
const replacements = {
|
||||||
where: { sn_code },
|
sn_code,
|
||||||
attributes: ['id'],
|
|
||||||
order: [['id', 'DESC']],
|
|
||||||
limit: 2000
|
|
||||||
});
|
|
||||||
const task_ids = task_rows.map((row) => row.id).filter((id) => id != null);
|
|
||||||
if (!task_ids.length) {
|
|
||||||
return ctx.success({
|
|
||||||
list: [],
|
|
||||||
total: 0,
|
|
||||||
page,
|
|
||||||
page_size
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const where = {
|
|
||||||
task_id: { [op.in]: task_ids }
|
|
||||||
};
|
|
||||||
if (body.command_type) {
|
|
||||||
where.command_type = String(body.command_type).trim();
|
|
||||||
}
|
|
||||||
if (body.status) {
|
|
||||||
where.status = String(body.status).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
const { rows, count } = await task_commands.findAndCountAll({
|
|
||||||
where,
|
|
||||||
attributes: [
|
|
||||||
'id',
|
|
||||||
'task_id',
|
|
||||||
'command_type',
|
|
||||||
'command_name',
|
|
||||||
'command_params',
|
|
||||||
'status',
|
|
||||||
'priority',
|
|
||||||
'sequence',
|
|
||||||
'retry_count',
|
|
||||||
'max_retries',
|
|
||||||
'start_time',
|
|
||||||
'end_time',
|
|
||||||
'duration',
|
|
||||||
'result',
|
|
||||||
'error_message',
|
|
||||||
'progress'
|
|
||||||
],
|
|
||||||
order: [['id', 'DESC']],
|
|
||||||
limit: page_size,
|
limit: page_size,
|
||||||
offset
|
offset
|
||||||
});
|
};
|
||||||
|
const command_type = body.command_type != null ? String(body.command_type).trim() : '';
|
||||||
|
const status = body.status != null ? String(body.status).trim() : '';
|
||||||
|
if (command_type) {
|
||||||
|
conditions.push('tc.command_type = :command_type');
|
||||||
|
replacements.command_type = command_type;
|
||||||
|
}
|
||||||
|
if (status) {
|
||||||
|
conditions.push('tc.status = :status');
|
||||||
|
replacements.status = status;
|
||||||
|
}
|
||||||
|
// 仅保留真实下发到客户端的指令,过滤服务端汇总类记录
|
||||||
|
conditions.push("tc.command_type <> 'job_filter_summary'");
|
||||||
|
const where_sql = conditions.join(' AND ');
|
||||||
|
|
||||||
|
// 用 JOIN + 覆盖索引,避免大 IN 列表导致慢查询
|
||||||
|
const count_sql = `
|
||||||
|
SELECT COUNT(*) AS count
|
||||||
|
FROM task_commands tc
|
||||||
|
INNER JOIN task_status ts ON ts.id = tc.task_id
|
||||||
|
WHERE ${where_sql}
|
||||||
|
`;
|
||||||
|
const data_sql = `
|
||||||
|
SELECT
|
||||||
|
tc.id,
|
||||||
|
tc.task_id,
|
||||||
|
tc.command_type,
|
||||||
|
tc.command_name,
|
||||||
|
tc.command_params,
|
||||||
|
tc.status,
|
||||||
|
tc.priority,
|
||||||
|
tc.sequence,
|
||||||
|
tc.retry_count,
|
||||||
|
tc.max_retries,
|
||||||
|
tc.start_time,
|
||||||
|
tc.end_time,
|
||||||
|
tc.duration,
|
||||||
|
tc.result,
|
||||||
|
tc.error_message,
|
||||||
|
tc.progress
|
||||||
|
FROM task_commands tc
|
||||||
|
INNER JOIN task_status ts ON ts.id = tc.task_id
|
||||||
|
WHERE ${where_sql}
|
||||||
|
ORDER BY tc.id DESC
|
||||||
|
LIMIT :limit OFFSET :offset
|
||||||
|
`;
|
||||||
|
|
||||||
|
const [count_result, rows] = await Promise.all([
|
||||||
|
sequelize.query(count_sql, {
|
||||||
|
replacements,
|
||||||
|
type: Sequelize.QueryTypes.SELECT
|
||||||
|
}),
|
||||||
|
sequelize.query(data_sql, {
|
||||||
|
replacements,
|
||||||
|
type: Sequelize.QueryTypes.SELECT
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
const count = Number(count_result && count_result[0] ? count_result[0].count : 0) || 0;
|
||||||
|
|
||||||
const list = rows.map((r) => {
|
const list = rows.map((r) => {
|
||||||
const j = r.toJSON ? r.toJSON() : r;
|
const j = r && typeof r.toJSON === 'function' ? r.toJSON() : r;
|
||||||
return {
|
return {
|
||||||
...j,
|
...j,
|
||||||
// 前端直接展示,避免超长字段压垮 UI
|
// 前端直接展示,避免超长字段压垮 UI
|
||||||
@@ -400,6 +414,41 @@ module.exports = {
|
|||||||
console.error('[任务管理] 获取指令分页失败:', error);
|
console.error('[任务管理] 获取指令分页失败:', error);
|
||||||
return ctx.fail('获取指令分页失败: ' + (error.message || '未知错误'));
|
return ctx.fail('获取指令分页失败: ' + (error.message || '未知错误'));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按设备 SN 重试单条指令
|
||||||
|
*/
|
||||||
|
'POST /task/command/retry': async (ctx) => {
|
||||||
|
try {
|
||||||
|
const body = ctx.getBody() || {};
|
||||||
|
const query = typeof ctx.getQuery === 'function' ? (ctx.getQuery() || {}) : {};
|
||||||
|
const sn_code = body.sn_code || query.sn_code;
|
||||||
|
const command_id = Number(body.command_id || body.commandId || 0);
|
||||||
|
if (!sn_code) {
|
||||||
|
return ctx.fail('请提供设备SN码');
|
||||||
|
}
|
||||||
|
if (!command_id) {
|
||||||
|
return ctx.fail('请提供指令ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { task_commands, task_status } = await Framework.getModels();
|
||||||
|
const command_row = await task_commands.findByPk(command_id);
|
||||||
|
if (!command_row) {
|
||||||
|
return ctx.fail('指令不存在');
|
||||||
|
}
|
||||||
|
const task_row = await task_status.findByPk(command_row.task_id);
|
||||||
|
if (!task_row || String(task_row.sn_code || '') !== String(sn_code)) {
|
||||||
|
return ctx.fail('无权重试该指令');
|
||||||
|
}
|
||||||
|
|
||||||
|
const plaAccountService = require('../services/pla_account_service.js');
|
||||||
|
const result = await plaAccountService.retryCommand({ commandId: command_id });
|
||||||
|
return ctx.success(result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[任务管理] 重试指令失败:', error);
|
||||||
|
return ctx.fail('重试指令失败: ' + (error.message || '未知错误'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,12 @@ class DeliverHandler extends BaseHandler {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
deliveredCount: deliverCommands.length,
|
deliveredCount: deliverCommands.length,
|
||||||
|
filterSummary: {
|
||||||
|
rawCount: pendingJobs.length,
|
||||||
|
passedCount: filteredJobs.length,
|
||||||
|
deliverCount: jobsToDeliver.length,
|
||||||
|
filteredCount: Math.max(0, pendingJobs.length - filteredJobs.length)
|
||||||
|
},
|
||||||
...result
|
...result
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,14 +94,16 @@ class JobFilterEngine {
|
|||||||
*/
|
*/
|
||||||
async get_single_job_filter_fail_reason(job, config) {
|
async get_single_job_filter_fail_reason(job, config) {
|
||||||
const j = job;
|
const j = job;
|
||||||
|
const title_text = String(j.jobTitle || '').trim() || '未知职位';
|
||||||
const after_salary = this.filterBySalary([j], config);
|
const after_salary = this.filterBySalary([j], config);
|
||||||
if (after_salary.length === 0) {
|
if (after_salary.length === 0) {
|
||||||
return `薪资不在设定范围(${config.min_salary ?? 0}-${config.max_salary ?? 0}K)`;
|
const salary_text = String(j.salary || j.salaryDesc || '-');
|
||||||
|
return `职位「${title_text}」薪资不在设定范围(${config.min_salary ?? 0}-${config.max_salary ?? 0}K),当前薪资: ${salary_text}`;
|
||||||
}
|
}
|
||||||
const after_title = this.filterByTitleIncludeKeywords(after_salary, config);
|
const after_title = this.filterByTitleIncludeKeywords(after_salary, config);
|
||||||
if (after_title.length === 0) {
|
if (after_title.length === 0) {
|
||||||
const kws = (config.title_include_keywords || []).join('、') || '无';
|
const kws = (config.title_include_keywords || []).join('、') || '无';
|
||||||
return `职位标题须包含以下关键词之一:${kws}`;
|
return `职位「${title_text}」标题未命中必含关键词:${kws}`;
|
||||||
}
|
}
|
||||||
const after_kw = this.filterByKeywords(after_title, config);
|
const after_kw = this.filterByKeywords(after_title, config);
|
||||||
if (after_kw.length === 0) {
|
if (after_kw.length === 0) {
|
||||||
@@ -114,30 +116,30 @@ class JobFilterEngine {
|
|||||||
if (match_result && match_result.details && match_result.details.exclude && match_result.details.exclude.matched) {
|
if (match_result && match_result.details && match_result.details.exclude && match_result.details.exclude.matched) {
|
||||||
const hit_keywords = match_result.details.exclude.keywords || [];
|
const hit_keywords = match_result.details.exclude.keywords || [];
|
||||||
if (hit_keywords.length) {
|
if (hit_keywords.length) {
|
||||||
return `命中排除关键词: ${hit_keywords.join('、')}`;
|
return `职位「${title_text}」命中排除关键词: ${hit_keywords.join('、')}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (match_result && match_result.details && match_result.details.filter && !match_result.details.filter.matched) {
|
if (match_result && match_result.details && match_result.details.filter && !match_result.details.filter.matched) {
|
||||||
const needed = Array.isArray(config.filter_keywords) ? config.filter_keywords : [];
|
const needed = Array.isArray(config.filter_keywords) ? config.filter_keywords : [];
|
||||||
if (needed.length) {
|
if (needed.length) {
|
||||||
return `未命中包含关键词: ${needed.join('、')}`;
|
return `职位「${title_text}」未命中包含关键词: ${needed.join('、')}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return '命中排除关键词或未满足包含词规则';
|
return `职位「${title_text}」命中排除关键词或未满足包含词规则`;
|
||||||
}
|
}
|
||||||
if (config.filter_inactive_companies) {
|
if (config.filter_inactive_companies) {
|
||||||
const after_act = await this.filterByCompanyActivity(after_kw, config.company_active_days || 7);
|
const after_act = await this.filterByCompanyActivity(after_kw, config.company_active_days || 7);
|
||||||
if (after_act.length === 0) {
|
if (after_act.length === 0) {
|
||||||
return '公司活跃度不满足配置';
|
return `职位「${title_text}」公司活跃度不满足配置`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (config.deduplicate) {
|
if (config.deduplicate) {
|
||||||
const after_dedup = this.deduplicateJobs(after_kw);
|
const after_dedup = this.deduplicateJobs(after_kw);
|
||||||
if (after_dedup.length === 0) {
|
if (after_dedup.length === 0) {
|
||||||
return '与列表内职位重复(公司+岗位名)';
|
return `职位「${title_text}」与列表内职位重复(公司+岗位名)`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return '未通过职位过滤';
|
return `职位「${title_text}」未通过职位过滤`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -485,8 +485,9 @@ class PlaAccountService {
|
|||||||
finalParams.keyword = account.keyword;
|
finalParams.keyword = account.keyword;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get_job_list:command 只约定 pageCount + tabLabel(与前端一致),入库 keyword 由 jobManager 用 tabLabel 推导
|
// get_job_list:优先使用调用方显式传入的 tabLabel;仅在未传时回退到 resume_info.deliver_tab_label
|
||||||
if (commandTypeSnake === 'get_job_list') {
|
if (commandTypeSnake === 'get_job_list') {
|
||||||
|
const passedTabLabel = finalParams.tabLabel != null ? String(finalParams.tabLabel).trim() : '';
|
||||||
try {
|
try {
|
||||||
const resume_info = db.getModel('resume_info');
|
const resume_info = db.getModel('resume_info');
|
||||||
const resume = await resume_info.findOne({
|
const resume = await resume_info.findOne({
|
||||||
@@ -494,7 +495,7 @@ class PlaAccountService {
|
|||||||
order: [['last_modify_time', 'DESC']],
|
order: [['last_modify_time', 'DESC']],
|
||||||
attributes: ['deliver_tab_label']
|
attributes: ['deliver_tab_label']
|
||||||
});
|
});
|
||||||
if (resume && resume.deliver_tab_label) {
|
if (!passedTabLabel && resume && resume.deliver_tab_label) {
|
||||||
finalParams.tabLabel = String(resume.deliver_tab_label).trim();
|
finalParams.tabLabel = String(resume.deliver_tab_label).trim();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -639,6 +640,35 @@ class PlaAccountService {
|
|||||||
command_params: JSON.stringify(commandParams)
|
command_params: JSON.stringify(commandParams)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 防止 result 超长导致 "Data too long for column 'result'"
|
||||||
|
const serialize_retry_result_for_db = (value) => {
|
||||||
|
const max_length = 60000;
|
||||||
|
try {
|
||||||
|
let text = JSON.stringify(value || {});
|
||||||
|
if (text.length <= max_length) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
const summary = {
|
||||||
|
success: !!(value && value.success),
|
||||||
|
message: (value && value.message) ? String(value.message) : '重试结果过长,已摘要存储',
|
||||||
|
truncated: true,
|
||||||
|
raw_length: text.length,
|
||||||
|
preview: text.substring(0, 1000)
|
||||||
|
};
|
||||||
|
text = JSON.stringify(summary);
|
||||||
|
if (text.length > max_length) {
|
||||||
|
return text.substring(0, max_length - 12) + '...[已截断]';
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
} catch (error) {
|
||||||
|
return JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
message: '结果序列化失败',
|
||||||
|
error: error.message || 'unknown'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 执行指令并同步更新当前指令记录状态
|
// 执行指令并同步更新当前指令记录状态
|
||||||
const start_time = new Date();
|
const start_time = new Date();
|
||||||
try {
|
try {
|
||||||
@@ -650,7 +680,7 @@ class PlaAccountService {
|
|||||||
start_time: start_time,
|
start_time: start_time,
|
||||||
end_time: end_time,
|
end_time: end_time,
|
||||||
duration: end_time.getTime() - start_time.getTime(),
|
duration: end_time.getTime() - start_time.getTime(),
|
||||||
result: JSON.stringify(result || {})
|
result: serialize_retry_result_for_db(result)
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user