'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;