171 lines
4.2 KiB
JavaScript
171 lines
4.2 KiB
JavaScript
/**
|
||
* 执行锁中间件 · L2 API 执行拦截层
|
||
*
|
||
* 当一个操作开始执行时:
|
||
* 1. 为该操作创建一个执行上下文(executionId)
|
||
* 2. 锁定相关资源(防止并发冲突)
|
||
* 3. 拒绝一切 cancel/abort/rollback 请求
|
||
* 4. 执行完成后自动释放锁
|
||
*
|
||
* 同一个开发者不能同时执行多个写入操作。
|
||
* 版权:国作登字-2026-A-00037559
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
var crypto = require('crypto');
|
||
|
||
// 内存中的执行锁表
|
||
var activeLocks = new Map();
|
||
|
||
function generateExecutionId() {
|
||
return 'exec-' + Date.now() + '-' + crypto.randomBytes(4).toString('hex');
|
||
}
|
||
|
||
/**
|
||
* 获取执行锁表引用(供看门狗使用)
|
||
*/
|
||
function getActiveLocks() {
|
||
return activeLocks;
|
||
}
|
||
|
||
/**
|
||
* 创建执行锁
|
||
* @param {string} devId - 开发者编号
|
||
* @param {string} operation - 操作描述
|
||
* @returns {Object} { success, executionId, lock } 或 { success: false, reply }
|
||
*/
|
||
function acquireLock(devId, operation) {
|
||
if (activeLocks.has(devId)) {
|
||
var existing = activeLocks.get(devId);
|
||
if (existing.state === 'running') {
|
||
return {
|
||
success: false,
|
||
reply: '⏳ 你有一个操作正在执行中(' + existing.operation +
|
||
'),请等待完成。\n\n系统不允许同时执行多个操作——这是为了保护数据一致性。'
|
||
};
|
||
}
|
||
}
|
||
|
||
var executionId = generateExecutionId();
|
||
var lock = {
|
||
executionId: executionId,
|
||
devId: devId,
|
||
operation: operation,
|
||
startTime: Date.now(),
|
||
state: 'running',
|
||
steps: [],
|
||
checkpoints: [],
|
||
warned: false,
|
||
completedSteps: 0,
|
||
totalSteps: 0,
|
||
result: null,
|
||
error: null
|
||
};
|
||
|
||
activeLocks.set(devId, lock);
|
||
|
||
return {
|
||
success: true,
|
||
executionId: executionId,
|
||
lock: lock
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 释放执行锁
|
||
*/
|
||
function releaseLock(devId) {
|
||
activeLocks.delete(devId);
|
||
}
|
||
|
||
/**
|
||
* 获取执行状态(通过 executionId)
|
||
*/
|
||
function getExecutionStatus(executionId) {
|
||
for (var entry of activeLocks) {
|
||
if (entry[1].executionId === executionId) {
|
||
return entry[1];
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 记录执行步骤(用于事后回放)
|
||
*/
|
||
function logStep(devId, step) {
|
||
var lock = activeLocks.get(devId);
|
||
if (lock) {
|
||
lock.steps.push({
|
||
message: step.message,
|
||
success: step.success,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
if (step.success) {
|
||
lock.completedSteps++;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存检查点(用于失败时回滚)
|
||
*/
|
||
function saveCheckpoint(devId, checkpoint) {
|
||
var lock = activeLocks.get(devId);
|
||
if (lock) {
|
||
lock.checkpoints.push({
|
||
id: checkpoint.id,
|
||
label: checkpoint.label,
|
||
data: checkpoint.data,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 取消拦截中间件
|
||
* 拦截所有试图取消执行中操作的请求
|
||
*/
|
||
function blockCancellation(req, res, next) {
|
||
var devId = req.user ? req.user.devId : null;
|
||
if (!devId) return next();
|
||
|
||
var isCancelAttempt =
|
||
req.path.includes('/cancel') ||
|
||
req.path.includes('/abort') ||
|
||
req.path.includes('/rollback');
|
||
|
||
var isDeleteOnLocked = req.method === 'DELETE' && activeLocks.has(devId);
|
||
|
||
if (isCancelAttempt || isDeleteOnLocked) {
|
||
var lock = activeLocks.get(devId);
|
||
if (lock && lock.state === 'running') {
|
||
return res.status(423).json({
|
||
error: true,
|
||
code: 'EXECUTION_LOCKED',
|
||
locked: true,
|
||
executionId: lock.executionId,
|
||
reply: '🔒 操作「' + lock.operation + '」正在执行中,不可取消。\n\n' +
|
||
'系统已接收你的指令并开始执行。这条执行链路涉及多个自动化流程的联动,' +
|
||
'中途中断会导致数据不一致和级联故障。\n\n' +
|
||
'⏳ 请等待执行完成。完成后你会看到完整的执行结果。\n\n' +
|
||
'如果你对结果不满意,可以说「前面的没想好,我们重新来」——' +
|
||
'系统会在已有结果的基础上重新迭代,而不是撤回已执行的操作。'
|
||
});
|
||
}
|
||
}
|
||
|
||
next();
|
||
}
|
||
|
||
module.exports = {
|
||
acquireLock: acquireLock,
|
||
releaseLock: releaseLock,
|
||
getExecutionStatus: getExecutionStatus,
|
||
getActiveLocks: getActiveLocks,
|
||
logStep: logStep,
|
||
saveCheckpoint: saveCheckpoint,
|
||
blockCancellation: blockCancellation
|
||
};
|