feat: 添加历史命令功能
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as CommandHistoryService from '../services/command-history.service';
|
||||
|
||||
/**
|
||||
* 处理添加新命令历史记录的请求
|
||||
*/
|
||||
export const addCommand = async (req: Request, res: Response): Promise<void> => {
|
||||
const { command } = req.body;
|
||||
|
||||
if (!command || typeof command !== 'string' || command.trim().length === 0) {
|
||||
res.status(400).json({ message: '命令不能为空' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const newId = await CommandHistoryService.addCommandHistory(command);
|
||||
res.status(201).json({ id: newId, message: '命令已添加到历史记录' });
|
||||
} catch (error: any) {
|
||||
console.error('添加命令历史记录控制器出错:', error);
|
||||
res.status(500).json({ message: error.message || '无法添加命令历史记录' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理获取所有命令历史记录的请求
|
||||
*/
|
||||
export const getAllCommands = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const history = await CommandHistoryService.getAllCommandHistory();
|
||||
// 注意:前端要求最新在下,最旧在上。Repository 返回的是升序(旧->新),符合要求。
|
||||
res.status(200).json(history);
|
||||
} catch (error: any) {
|
||||
console.error('获取命令历史记录控制器出错:', error);
|
||||
res.status(500).json({ message: error.message || '无法获取命令历史记录' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理根据 ID 删除命令历史记录的请求
|
||||
*/
|
||||
export const deleteCommand = async (req: Request, res: Response): Promise<void> => {
|
||||
const id = parseInt(req.params.id, 10);
|
||||
|
||||
if (isNaN(id)) {
|
||||
res.status(400).json({ message: '无效的 ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const success = await CommandHistoryService.deleteCommandHistoryById(id);
|
||||
if (success) {
|
||||
res.status(200).json({ message: '命令历史记录已删除' });
|
||||
} else {
|
||||
res.status(404).json({ message: '未找到要删除的命令历史记录' });
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('删除命令历史记录控制器出错:', error);
|
||||
res.status(500).json({ message: error.message || '无法删除命令历史记录' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理清空所有命令历史记录的请求
|
||||
*/
|
||||
export const clearAllCommands = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const count = await CommandHistoryService.clearAllCommandHistory();
|
||||
res.status(200).json({ count, message: `已清空 ${count} 条命令历史记录` });
|
||||
} catch (error: any) {
|
||||
console.error('清空命令历史记录控制器出错:', error);
|
||||
res.status(500).json({ message: error.message || '无法清空命令历史记录' });
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Router } from 'express';
|
||||
import * as CommandHistoryController from './command-history.controller';
|
||||
import { isAuthenticated } from '../auth/auth.middleware'; // 使用正确的认证中间件
|
||||
|
||||
const router = Router();
|
||||
|
||||
// 应用认证中间件到所有命令历史记录相关的路由
|
||||
router.use(isAuthenticated);
|
||||
|
||||
// 定义路由
|
||||
router.post('/', CommandHistoryController.addCommand); // POST /api/command-history
|
||||
router.get('/', CommandHistoryController.getAllCommands); // GET /api/command-history
|
||||
router.delete('/:id', CommandHistoryController.deleteCommand); // DELETE /api/command-history/:id
|
||||
router.delete('/', CommandHistoryController.clearAllCommands); // DELETE /api/command-history (用于清空)
|
||||
|
||||
export default router;
|
||||
@@ -16,6 +16,7 @@ import tagsRouter from './tags/tags.routes'; // 导入标签路由
|
||||
import settingsRoutes from './settings/settings.routes'; // 导入设置路由
|
||||
import notificationRoutes from './notifications/notification.routes'; // 导入通知路由
|
||||
import auditRoutes from './audit/audit.routes'; // 导入审计路由
|
||||
import commandHistoryRoutes from './command-history/command-history.routes'; // 导入命令历史记录路由
|
||||
import { initializeWebSocket } from './websocket';
|
||||
import { ipWhitelistMiddleware } from './auth/ipWhitelist.middleware'; // 导入 IP 白名单中间件
|
||||
|
||||
@@ -102,6 +103,7 @@ app.use('/api/v1/tags', tagsRouter); // 挂载标签相关的路由
|
||||
app.use('/api/v1/settings', settingsRoutes); // 挂载设置相关的路由
|
||||
app.use('/api/v1/notifications', notificationRoutes); // 挂载通知相关的路由
|
||||
app.use('/api/v1/audit-logs', auditRoutes); // 挂载审计日志相关的路由
|
||||
app.use('/api/v1/command-history', commandHistoryRoutes); // 挂载命令历史记录相关的路由
|
||||
|
||||
// 状态检查接口
|
||||
app.get('/api/v1/status', (req: Request, res: Response) => {
|
||||
|
||||
@@ -131,6 +131,14 @@ CREATE TABLE IF NOT EXISTS ip_blacklist (
|
||||
blocked_until INTEGER NULL -- 封禁截止时间戳 (秒),NULL 表示未封禁或永久封禁 (根据逻辑决定)
|
||||
);
|
||||
`;
|
||||
|
||||
const createCommandHistoryTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS command_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
command TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`;
|
||||
// --- 结束新增表结构定义 ---
|
||||
|
||||
|
||||
@@ -254,6 +262,15 @@ export const runMigrations = async (db: Database): Promise<void> => {
|
||||
});
|
||||
});
|
||||
|
||||
// 创建 command_history 表
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
db.run(createCommandHistoryTableSQL, (err: Error | null) => {
|
||||
if (err) return reject(new Error(`创建 command_history 表时出错: ${err.message}`));
|
||||
console.log('Command_History 表已检查/创建。');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// --- 结束新增表创建逻辑 ---
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import { getDb } from '../database';
|
||||
|
||||
// 定义命令历史记录的接口
|
||||
export interface CommandHistoryEntry {
|
||||
id: number;
|
||||
command: string;
|
||||
timestamp: number; // Unix 时间戳 (秒)
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入或更新一条命令历史记录。
|
||||
* 如果命令已存在,则更新其时间戳;否则,插入新记录。
|
||||
* @param command - 要添加或更新的命令字符串
|
||||
* @returns 返回插入或更新记录的 ID
|
||||
*/
|
||||
export const upsertCommand = (command: string): Promise<number> => {
|
||||
const db = getDb();
|
||||
// 使用 INSERT ... ON CONFLICT DO UPDATE 语法 (SQLite 3.24.0+)
|
||||
// 如果 command 列冲突 (假设我们为 command 列添加了 UNIQUE 约束,或者手动检查)
|
||||
// 这里我们先不加 UNIQUE 约束,而是先尝试 UPDATE,再尝试 INSERT
|
||||
const now = Math.floor(Date.now() / 1000); // 获取当前时间戳
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// 1. 尝试更新现有记录的时间戳
|
||||
const updateSql = `UPDATE command_history SET timestamp = ? WHERE command = ?`;
|
||||
db.run(updateSql, [now, command], function (updateErr) {
|
||||
if (updateErr) {
|
||||
console.error('更新命令历史记录时间戳时出错:', updateErr);
|
||||
return reject(new Error('无法更新命令历史记录'));
|
||||
}
|
||||
|
||||
if (this.changes > 0) {
|
||||
// 更新成功,需要获取被更新记录的 ID
|
||||
const selectSql = `SELECT id FROM command_history WHERE command = ? ORDER BY timestamp DESC LIMIT 1`;
|
||||
db.get(selectSql, [command], (selectErr, row: { id: number } | undefined) => {
|
||||
if (selectErr) {
|
||||
console.error('获取更新后记录 ID 时出错:', selectErr);
|
||||
return reject(new Error('无法获取更新后的记录 ID'));
|
||||
}
|
||||
if (row) {
|
||||
resolve(row.id);
|
||||
} else {
|
||||
// 理论上不应该发生,因为我们刚更新了它
|
||||
reject(new Error('更新成功但无法找到记录 ID'));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 2. 没有记录被更新,说明命令不存在,执行插入
|
||||
const insertSql = `INSERT INTO command_history (command, timestamp) VALUES (?, ?)`;
|
||||
db.run(insertSql, [command, now], function (insertErr) {
|
||||
if (insertErr) {
|
||||
console.error('插入新命令历史记录时出错:', insertErr);
|
||||
return reject(new Error('无法插入新命令历史记录'));
|
||||
}
|
||||
resolve(this.lastID); // 返回新插入的行 ID
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取所有命令历史记录,按时间戳升序排列(最旧的在前)
|
||||
* @returns 返回包含所有历史记录条目的数组
|
||||
*/
|
||||
export const getAllCommands = (): Promise<CommandHistoryEntry[]> => {
|
||||
const db = getDb();
|
||||
const sql = `SELECT id, command, timestamp FROM command_history ORDER BY timestamp ASC`;
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(sql, [], (err, rows: CommandHistoryEntry[]) => {
|
||||
if (err) {
|
||||
console.error('获取命令历史记录时出错:', err);
|
||||
return reject(new Error('无法获取命令历史记录'));
|
||||
}
|
||||
resolve(rows);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据 ID 删除指定的命令历史记录
|
||||
* @param id - 要删除的记录 ID
|
||||
* @returns 返回删除的行数 (通常是 1 或 0)
|
||||
*/
|
||||
export const deleteCommandById = (id: number): Promise<number> => {
|
||||
const db = getDb();
|
||||
const sql = `DELETE FROM command_history WHERE id = ?`;
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, [id], function (err) {
|
||||
if (err) {
|
||||
console.error('删除命令历史记录时出错:', err);
|
||||
return reject(new Error('无法删除命令历史记录'));
|
||||
}
|
||||
resolve(this.changes); // 返回受影响的行数
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 清空所有命令历史记录
|
||||
* @returns 返回删除的行数
|
||||
*/
|
||||
export const clearAllCommands = (): Promise<number> => {
|
||||
const db = getDb();
|
||||
const sql = `DELETE FROM command_history`;
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, [], function (err) {
|
||||
if (err) {
|
||||
console.error('清空命令历史记录时出错:', err);
|
||||
return reject(new Error('无法清空命令历史记录'));
|
||||
}
|
||||
resolve(this.changes); // 返回受影响的行数
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import * as CommandHistoryRepository from '../repositories/command-history.repository';
|
||||
import { CommandHistoryEntry } from '../repositories/command-history.repository';
|
||||
|
||||
/**
|
||||
* 添加一条命令历史记录
|
||||
* @param command - 要添加的命令
|
||||
* @returns 返回添加记录的 ID
|
||||
*/
|
||||
export const addCommandHistory = async (command: string): Promise<number> => {
|
||||
// 可以在这里添加额外的业务逻辑,例如校验命令格式、长度限制等
|
||||
if (!command || command.trim().length === 0) {
|
||||
throw new Error('命令不能为空');
|
||||
}
|
||||
// 可以在此添加去重逻辑,如果不想记录重复的命令
|
||||
// const existing = await CommandHistoryRepository.findCommand(command); // 如果需要更复杂的去重逻辑
|
||||
// if (existing) { ... }
|
||||
|
||||
// 调用 upsertCommand 来处理插入或更新时间戳
|
||||
return CommandHistoryRepository.upsertCommand(command.trim());
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取所有命令历史记录
|
||||
* @returns 返回所有历史记录条目数组,按时间戳升序
|
||||
*/
|
||||
export const getAllCommandHistory = async (): Promise<CommandHistoryEntry[]> => {
|
||||
return CommandHistoryRepository.getAllCommands();
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据 ID 删除一条命令历史记录
|
||||
* @param id - 要删除的记录 ID
|
||||
* @returns 返回是否成功删除 (删除行数 > 0)
|
||||
*/
|
||||
export const deleteCommandHistoryById = async (id: number): Promise<boolean> => {
|
||||
const changes = await CommandHistoryRepository.deleteCommandById(id);
|
||||
return changes > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* 清空所有命令历史记录
|
||||
* @returns 返回删除的记录条数
|
||||
*/
|
||||
export const clearAllCommandHistory = async (): Promise<number> => {
|
||||
return CommandHistoryRepository.clearAllCommands();
|
||||
};
|
||||
Reference in New Issue
Block a user