263 lines
9.0 KiB
JavaScript
263 lines
9.0 KiB
JavaScript
'use strict';
|
|
|
|
const express = require('express');
|
|
const authMiddleware = require('../middleware/auth');
|
|
const { listOnlineDesktopUsers } = require('../services/desktopPresenceService');
|
|
const { assertDesktopSession } = require('../services/desktopSessionService');
|
|
const { listSelectionFissionUserShops } = require('../services/planningSelectionFissionService');
|
|
const {
|
|
DesktopExecutionTaskError,
|
|
cancelDesktopExecutionTask,
|
|
claimDesktopExecutionTask,
|
|
completeDesktopExecutionTask,
|
|
createDesktopExecutionTask,
|
|
failDesktopExecutionTask,
|
|
getDesktopExecutionSummary,
|
|
listDesktopExecutionTasks,
|
|
retryDesktopExecutionTask,
|
|
updateDesktopExecutionProgress,
|
|
} = require('../services/desktopExecutionTaskService');
|
|
|
|
const router = express.Router();
|
|
router.use(authMiddleware);
|
|
|
|
function actorFromRequest(req) {
|
|
return {
|
|
userId: req.user?.sub ?? req.user?.id,
|
|
username: req.user?.username || '',
|
|
name: req.user?.name || req.user?.username || '',
|
|
role: req.user?.role || 'user',
|
|
};
|
|
}
|
|
|
|
function assertControlCenterRequest(req) {
|
|
if (req.user?.tokenType === 'desktop-session') {
|
|
throw new DesktopExecutionTaskError('该接口仅供总控页面使用', 403, 'DESKTOP_TASK_CONTROL_CENTER_REQUIRED');
|
|
}
|
|
if (req.user?.role !== 'admin') {
|
|
throw new DesktopExecutionTaskError('只有管理员可以管理桌面执行任务', 403, 'DESKTOP_TASK_ADMIN_REQUIRED');
|
|
}
|
|
}
|
|
|
|
async function validatePresaleAssignment(input = {}) {
|
|
if (String(input.taskType || input.type || '').trim().toLowerCase() !== 'presale') return input;
|
|
const assignedUserId = Number(input.assignedUserId || input.userId || 0);
|
|
const presence = await listOnlineDesktopUsers();
|
|
const operator = (presence.users || []).find((item) => (
|
|
Number(item.userId) === assignedUserId && item.isOnline
|
|
));
|
|
if (!operator) {
|
|
throw new DesktopExecutionTaskError(
|
|
'所选运营的桌面客户端当前不在线',
|
|
409,
|
|
'DESKTOP_TASK_OPERATOR_OFFLINE'
|
|
);
|
|
}
|
|
|
|
const operatorName = operator.name || operator.username;
|
|
const shopData = await listSelectionFissionUserShops(operatorName, { includeInactive: false });
|
|
const requestedShopIds = [
|
|
input.targetShopId,
|
|
...(Array.isArray(input.input?.targetShopIds) ? input.input.targetShopIds : []),
|
|
].map((value) => String(value || '').trim()).filter(Boolean);
|
|
const uniqueShopIds = [...new Set(requestedShopIds)];
|
|
if (!uniqueShopIds.length) {
|
|
throw new DesktopExecutionTaskError(
|
|
'请至少选择一个登录状态正常的目标店铺',
|
|
400,
|
|
'DESKTOP_TASK_SHOP_REQUIRED'
|
|
);
|
|
}
|
|
const selectableById = new Map(
|
|
(shopData.rows || []).filter((shop) => shop.selectable).map((shop) => [String(shop.shopId), shop])
|
|
);
|
|
const selectedShops = uniqueShopIds.map((shopId) => selectableById.get(shopId)).filter(Boolean);
|
|
if (selectedShops.length !== uniqueShopIds.length) {
|
|
throw new DesktopExecutionTaskError(
|
|
'目标店铺已离线或登录状态失效,请刷新后重新选择',
|
|
409,
|
|
'DESKTOP_TASK_SHOP_UNAVAILABLE'
|
|
);
|
|
}
|
|
|
|
return {
|
|
...input,
|
|
assignedUserId,
|
|
assignedUserName: operator.name || operator.username || '',
|
|
assignedUsername: operator.username || '',
|
|
targetShopId: selectedShops.length === 1 ? selectedShops[0].shopId : '',
|
|
targetShopName: selectedShops.length === 1 ? selectedShops[0].shopName : '',
|
|
input: {
|
|
...(input.input || {}),
|
|
targetShopIds: selectedShops.map((shop) => shop.shopId),
|
|
targetShopNames: selectedShops.map((shop) => shop.shopName),
|
|
},
|
|
};
|
|
}
|
|
|
|
async function assertDesktopRequest(req) {
|
|
if (req.user?.tokenType !== 'desktop-session') {
|
|
throw new DesktopExecutionTaskError('该接口仅供桌面客户端使用', 403, 'DESKTOP_TASK_CLIENT_REQUIRED');
|
|
}
|
|
const clientId = req.body?.clientId || req.query?.clientId || req.user?.clientId;
|
|
await assertDesktopSession(req.user, clientId);
|
|
return clientId;
|
|
}
|
|
|
|
function sendError(res, error, fallback) {
|
|
const status = error instanceof DesktopExecutionTaskError || Number(error.status)
|
|
? Number(error.status || 500)
|
|
: 500;
|
|
if (status >= 500) console.error(`[DesktopExecutionTask] ${fallback}:`, error);
|
|
return res.status(status).json({
|
|
success: false,
|
|
code: error.code || 'DESKTOP_TASK_SERVER_ERROR',
|
|
error: error.message || fallback,
|
|
});
|
|
}
|
|
|
|
router.get('/tasks', async (req, res) => {
|
|
try {
|
|
assertControlCenterRequest(req);
|
|
const data = await listDesktopExecutionTasks(req.query || {}, actorFromRequest(req));
|
|
res.setHeader('Cache-Control', 'no-store');
|
|
return res.json({ success: true, data });
|
|
} catch (error) {
|
|
return sendError(res, error, '读取桌面任务失败');
|
|
}
|
|
});
|
|
|
|
router.get('/summary', async (req, res) => {
|
|
try {
|
|
assertControlCenterRequest(req);
|
|
const data = await getDesktopExecutionSummary(req.query || {}, actorFromRequest(req));
|
|
res.setHeader('Cache-Control', 'no-store');
|
|
return res.json({ success: true, data });
|
|
} catch (error) {
|
|
return sendError(res, error, '读取桌面任务概览失败');
|
|
}
|
|
});
|
|
|
|
router.get('/operators', async (req, res) => {
|
|
try {
|
|
assertControlCenterRequest(req);
|
|
const presence = await listOnlineDesktopUsers();
|
|
const users = (presence.users || []).map((user) => ({
|
|
userId: Number(user.userId || 0),
|
|
username: user.username || '',
|
|
name: user.name || user.username || '',
|
|
departmentName: user.departmentName || '',
|
|
isOnline: Boolean(user.isOnline),
|
|
onlineDeviceCount: Number(user.activeDeviceCount || 0),
|
|
lastSeenAt: user.lastSeenAt || null,
|
|
}));
|
|
return res.json({ success: true, data: { users, offlineAfterSeconds: presence.offlineAfterSeconds } });
|
|
} catch (error) {
|
|
return sendError(res, error, '读取桌面运营列表失败');
|
|
}
|
|
});
|
|
|
|
router.get('/operators/:userId/shops', async (req, res) => {
|
|
try {
|
|
assertControlCenterRequest(req);
|
|
const presence = await listOnlineDesktopUsers();
|
|
const user = (presence.users || []).find((item) => Number(item.userId) === Number(req.params.userId));
|
|
if (!user) {
|
|
throw new DesktopExecutionTaskError('未找到该桌面运营记录', 404, 'DESKTOP_TASK_OPERATOR_NOT_FOUND');
|
|
}
|
|
const userName = user.name || user.username;
|
|
const data = await listSelectionFissionUserShops(userName, { includeInactive: false });
|
|
return res.json({ success: true, data });
|
|
} catch (error) {
|
|
return sendError(res, error, '读取运营店铺状态失败');
|
|
}
|
|
});
|
|
|
|
router.post('/tasks', async (req, res) => {
|
|
try {
|
|
assertControlCenterRequest(req);
|
|
const input = await validatePresaleAssignment(req.body || {});
|
|
const data = await createDesktopExecutionTask(input, actorFromRequest(req));
|
|
return res.status(201).json({ success: true, data });
|
|
} catch (error) {
|
|
return sendError(res, error, '下发桌面任务失败');
|
|
}
|
|
});
|
|
|
|
router.post('/tasks/claim', async (req, res) => {
|
|
try {
|
|
const clientId = await assertDesktopRequest(req);
|
|
const data = await claimDesktopExecutionTask(
|
|
{ ...(req.body || {}), clientId },
|
|
actorFromRequest(req)
|
|
);
|
|
return res.json({ success: true, data });
|
|
} catch (error) {
|
|
return sendError(res, error, '领取桌面任务失败');
|
|
}
|
|
});
|
|
|
|
router.post('/tasks/:taskId/progress', async (req, res) => {
|
|
try {
|
|
const clientId = await assertDesktopRequest(req);
|
|
const data = await updateDesktopExecutionProgress(
|
|
req.params.taskId,
|
|
{ ...(req.body || {}), clientId },
|
|
actorFromRequest(req)
|
|
);
|
|
return res.json({ success: true, data });
|
|
} catch (error) {
|
|
return sendError(res, error, '回传桌面任务进度失败');
|
|
}
|
|
});
|
|
|
|
router.post('/tasks/:taskId/complete', async (req, res) => {
|
|
try {
|
|
const clientId = await assertDesktopRequest(req);
|
|
const data = await completeDesktopExecutionTask(
|
|
req.params.taskId,
|
|
{ ...(req.body || {}), clientId },
|
|
actorFromRequest(req)
|
|
);
|
|
return res.json({ success: true, data });
|
|
} catch (error) {
|
|
return sendError(res, error, '完成桌面任务失败');
|
|
}
|
|
});
|
|
|
|
router.post('/tasks/:taskId/fail', async (req, res) => {
|
|
try {
|
|
const clientId = await assertDesktopRequest(req);
|
|
const data = await failDesktopExecutionTask(
|
|
req.params.taskId,
|
|
{ ...(req.body || {}), clientId },
|
|
actorFromRequest(req)
|
|
);
|
|
return res.json({ success: true, data });
|
|
} catch (error) {
|
|
return sendError(res, error, '回传桌面任务失败结果失败');
|
|
}
|
|
});
|
|
|
|
router.post('/tasks/:taskId/cancel', async (req, res) => {
|
|
try {
|
|
assertControlCenterRequest(req);
|
|
const data = await cancelDesktopExecutionTask(req.params.taskId, actorFromRequest(req));
|
|
return res.json({ success: true, data });
|
|
} catch (error) {
|
|
return sendError(res, error, '取消桌面任务失败');
|
|
}
|
|
});
|
|
|
|
router.post('/tasks/:taskId/retry', async (req, res) => {
|
|
try {
|
|
assertControlCenterRequest(req);
|
|
const data = await retryDesktopExecutionTask(req.params.taskId, actorFromRequest(req));
|
|
return res.json({ success: true, data });
|
|
} catch (error) {
|
|
return sendError(res, error, '重试桌面任务失败');
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|