feat: 重构保持工作空间存活功能,更新参数和添加数据管理,新增测试用例

This commit is contained in:
2026-02-25 17:34:49 +08:00
parent dd691f7a59
commit d3286e2766
6 changed files with 307 additions and 173 deletions

View File

@@ -1,214 +1,77 @@
import { createSkill, tool } from '@kevisual/router';
import { tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts';
import { nanoid } from 'nanoid';
import dayjs from 'dayjs';
import { createKeepAlive } from '../../../src/keep.ts';
type AliveInfo = {
startTime: number;
updatedTime?: number;
KeepAlive: ReturnType<typeof createKeepAlive>;
id: string;// 6位唯一标识符
}
const keepAliveMap = new Map<string, AliveInfo>();
import { addKeepAliveData, KeepAliveData, removeKeepAliveData, createLiveData } from '../../../src/workspace/keep-file-live.ts';
// 保持工作空间存活技能
app.route({
path: 'cnb',
key: 'keep-workspace-alive',
description: '保持工作空间存活技能,参数wsUrl:工作空间访问URLcookie:访问工作空间所需的cookie',
description: '保持工作空间存活技能参数repo:代码仓库路径,例如 user/repopipelineId:流水线ID例如 cnb-708-1ji9sog7o-001',
middleware: ['admin-auth'],
metadata: {
tags: [],
...({
args: {
wsUrl: tool.schema.string().describe('工作空间的访问URL'),
cookie: tool.schema.string().describe('访问工作空间所需的cookie')
repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
pipelineId: tool.schema.string().describe('流水线ID例如 cnb-708-1ji9sog7o-001'),
}
})
}
}).define(async (ctx) => {
const wsUrl = ctx.query?.wsUrl as string;
const cookie = ctx.query?.cookie as string;
if (!wsUrl) {
ctx.throw(400, '缺少工作空间访问URL参数');
}
if (!cookie) {
ctx.throw(400, '缺少访问工作空间所需的cookie参数');
}
const repo = ctx.query?.repo as string;
const pipelineId = ctx.query?.pipelineId as string;
// 检测是否已在运行(通过 wsUrl 遍历检查)
const existing = Array.from(keepAliveMap.values()).find(info => (info as AliveInfo).id && (info as any).KeepAlive?.wsUrl === wsUrl);
if (existing) {
ctx.body = { message: `工作空间 ${wsUrl} 的保持存活任务已在运行中`, id: (existing as AliveInfo).id };
return;
if (!repo || !pipelineId) {
ctx.throw(400, '缺少参数 repo 或 pipelineId');
}
const validCookie = await cnb.user.checkCookieValid()
if (validCookie.code !== 200) {
ctx.throw(401, 'CNB_COOKIE 环境变量无效或已过期请重新登录获取新的cookie');
}
const res = await cnb.workspace.getWorkspaceCookie(repo, pipelineId);
let wsUrl = `wss://${pipelineId}.cnb.space:443?skipWebSocketFrames=false`;
let cookie = '';
if (res.code === 200) {
cookie = res.data.value;
console.log(`启动保持工作空间 ${wsUrl} 存活的任务`);
} else {
ctx.throw(500, `获取工作空间访问cookie失败: ${res.message}`);
}
console.log(`启动保持工作空间 ${wsUrl} 存活的任务`);
const keep = createKeepAlive({
wsUrl,
cookie,
onConnect: () => {
console.log(`工作空间 ${wsUrl} 保持存活任务已连接`);
},
onMessage: (data) => {
// 可选:处理收到的消息
// console.log(`工作空间 ${wsUrl} 收到消息: ${data}`);
// 通过 wsUrl 找到对应的 id 并更新时间
for (const info of keepAliveMap.values()) {
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
info.updatedTime = Date.now();
break;
}
}
},
debug: true,
onExit: (code) => {
console.log(`工作空间 ${wsUrl} 保持存活任务已退出,退出码: ${code}`);
// 通过 wsUrl 找到对应的 id 并删除
for (const [id, info] of keepAliveMap.entries()) {
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
keepAliveMap.delete(id);
break;
}
}
}
});
const id = nanoid(6).toLowerCase();
keepAliveMap.set(id, { startTime: Date.now(), updatedTime: Date.now(), KeepAlive: keep, id });
const config: KeepAliveData = createLiveData({ wsUrl, cookie, repo, pipelineId });
addKeepAliveData(config);
ctx.body = { content: `已启动保持工作空间 ${wsUrl} 存活的任务`, id };
}).addTo(app);
// 获取保持工作空间存活任务列表技能
app.route({
path: 'cnb',
key: 'list-keep-alive-tasks',
description: '获取保持工作空间存活任务列表技能',
middleware: ['admin-auth'],
metadata: {
tags: [],
}
}).define(async (ctx) => {
const list = Array.from(keepAliveMap.entries()).map(([id, info]) => {
const now = Date.now();
const duration = Math.floor((now - info.startTime) / 60000); // 分钟
return {
id,
wsUrl: (info as any).KeepAlive?.wsUrl,
startTime: info.startTime,
startTimeStr: dayjs(info.startTime).format('YYYY-MM-DD HH:mm'),
updatedTime: info.updatedTime,
updatedTimeStr: dayjs(info.updatedTime).format('YYYY-MM-DD HH:mm'),
duration,
}
});
ctx.body = { list };
ctx.body = { content: `已启动保持工作空间 ${wsUrl} 存活的任务`, data: config };
}).addTo(app);
// 停止保持工作空间存活技能
app.route({
path: 'cnb',
key: 'stop-keep-workspace-alive',
description: '停止保持工作空间存活技能, 参数wsUrl:工作空间访问URL或者id',
description: '停止保持工作空间存活技能, 参数repo:代码仓库路径,例如 user/repopipelineId:流水线ID例如 cnb-708-1ji9sog7o-001',
middleware: ['admin-auth'],
metadata: {
tags: [],
...({
args: {
wsUrl: tool.schema.string().optional().describe('工作空间的访问URL'),
id: tool.schema.string().optional().describe('保持存活任务的唯一标识符'),
repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
pipelineId: tool.schema.string().describe('流水线ID例如 cnb-708-1ji9sog7o-001'),
}
})
}
}).define(async (ctx) => {
const wsUrl = ctx.query?.wsUrl as string;
const id = ctx.query?.id as string;
if (!wsUrl && !id) {
ctx.throw(400, '缺少工作空间访问URL参数或唯一标识符');
}
const repo = ctx.query?.repo as string;
const pipelineId = ctx.query?.pipelineId as string;
let targetId: string | undefined;
let wsUrlFound: string | undefined;
if (id) {
const info = keepAliveMap.get(id);
if (info) {
targetId = id;
wsUrlFound = (info as any).KeepAlive?.wsUrl;
}
} else if (wsUrl) {
for (const [key, info] of keepAliveMap.entries()) {
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
targetId = key;
wsUrlFound = wsUrl;
break;
}
}
}
if (targetId) {
const keepAlive = keepAliveMap.get(targetId);
const endTime = Date.now();
const duration = endTime - keepAlive!.startTime;
keepAlive?.KeepAlive?.disconnect();
keepAliveMap.delete(targetId);
ctx.body = { content: `已停止保持工作空间 ${wsUrlFound} 存活的任务,持续时间: ${duration}ms`, id: targetId };
} else {
ctx.body = { content: `没有找到对应的工作空间保持存活任务` };
if (!repo || !pipelineId) {
ctx.throw(400, '缺少参数 repo 或 pipelineId');
}
removeKeepAliveData(repo, pipelineId);
ctx.body = { content: `已停止保持工作空间 ${repo}/${pipelineId} 存活的任务` };
}).addTo(app);
app.route({
path: 'cnb',
key: 'reset-keep-workspace-alive',
description: '对存活的工作空间startTime进行重置',
middleware: ['admin-auth'],
metadata: {
tags: [],
}
}).define(async (ctx) => {
const now = Date.now();
for (const info of keepAliveMap.values()) {
info.startTime = now;
}
ctx.body = { content: `已重置所有存活工作空间的开始时间` };
}).addTo(app);
app.route({
path: 'cnb',
key: 'clear-keep-workspace-alive',
description: '对存活的工作空间超过5小时的进行清理',
middleware: ['admin-auth'],
metadata: {
tags: [],
}
}).define(async (ctx) => {
const res = clearKeepAlive();
ctx.body = {
content: `已清理所有存活工作空间中超过5小时的任务` + (res.length ? `,清理项:${res.map(i => i.wsUrl).join(', ')}` : ''),
list: res
};
}).addTo(app);
const clearKeepAlive = () => {
const now = Date.now();
let clearedArr: { id: string; wsUrl: string }[] = [];
for (const [id, info] of keepAliveMap.entries()) {
if (now - info.startTime > FIVE_HOURS) {
console.log(`工作空间 ${(info as any).KeepAlive?.wsUrl} 超过5小时自动停止`);
info.KeepAlive?.disconnect?.();
keepAliveMap.delete(id);
clearedArr.push({ id, wsUrl: (info as any).KeepAlive?.wsUrl });
}
}
return clearedArr;
}
// 每5小时自动清理超时的keepAlive任务
const FIVE_HOURS = 5 * 60 * 60 * 1000;
setInterval(() => {
clearKeepAlive();
}, FIVE_HOURS);