☰
基于TypeScript的网络安全检测系统服务端架构与规则引擎实现
2026/10/4 8:04:19 网站建设 项目流程

简介:这是一套面向信息安全课程设计/毕业设计的基于TypeScript实现的网络安全检测系统服务端源码,适合计算机、软件工程、网络安全等专业学生参考学习。源码按服务端工程化方式组织,涵盖数据访问(dao)、业务服务(service)、路由过滤器(filter/router)、工具类(util)以及多环境配置(config)等模块,能够帮助读者理解TypeScript在安全检测后端中的实际落地,也可作为课程设计、项目初期演示或毕业设计的二次开发基础。压缩包共63个文件,主要由29个TypeScript源文件、14个JSON配置文件、6个JS脚本以及少量CSS/SVG/HTML等界面资源组成,整体仅241KB。TS源码承担核心服务逻辑,JSON管理依赖与安全策略,CSS/SVG/HTML则用于登录、注册及内容展示页面。项目已通过测试可正常运行,目前已有587人学习下载,配套的README与工程配置齐全,适合快速上手并扩展成完整的网络安全检测平台。

1. 基于 TypeScript 的网络安全检测系统服务端:课程设计怎么做才不像玩具

把“网络安全检测系统”做成课程设计,最常见的翻车路径是:用 Python 写一堆正则匹配,跑通几个样例就交差。可一旦数据量上来、并发请求一多,脚本型服务端立刻暴露出两个问题——事件处理没有类型约束,字段名拼错一个就静默失败;检测逻辑全是过程式 if-else,想加一条规则得动主干代码。而用 TypeScript 写服务端,正好卡在“演示效果好看”和“工程结构能讲清楚”的中间点上。

这套基于 TypeScript 实现的网络安全检测系统服务端,核心不是“检测算法多高深”,而是把网络事件当成有结构的数据流来处理:采集层收数据,归一化成统一的事件对象,检测层用可插拔的规则引擎跑判定,最后告警层负责去重、落库、推送。对课程设计来说,它最大的价值是把信息安全里“检测”这个环节用工程化的方式讲明白,同时天然对接 Web 前端做可视化大屏。适合两类人:一是信息安全专业要做课设、参赛的学生,二是想从“会写 CRUD”跨到“懂安全业务逻辑”的 TypeScript 开发者。

2. 服务端架构与数据流:为什么 TS 比 Python 更适合做检测服务的骨架

2.1 系统整体模块划分:采集、归一化、检测、告警四层

一个能演示、能答辩的网络安全检测服务端,至少要包含三个模块:数据接入层、检测引擎层、告警与存储层。数据接入层解决“事件从哪来”的问题,常见做法是监听一个 HTTP 上报接口,让模拟攻击脚本或者 Agent 把日志推上来;检测引擎层是核心,它消费事件流,跑各种检测器;告警与存储层把命中的结果写进数据库、推给前端。

我一般会把数据接入层再拆成“上报接口”和“归一化适配器”两部分。上报接口只负责接收原始 JSON,不参与任何判断;归一化适配器把不同来源的日志(Nginx 访问日志、端口连接记录、登录失败记录)转成同一种内部事件结构。这一步非常关键——如果没有统一的事件模型,每加一个数据源就要改一次检测逻辑,代码很快就会变成一锅粥。

下面是事件模型的 TypeScript 定义,这是整个系统的地基:

// src/types/event.ts export type Protocol = 'tcp' | 'udp' | 'http' | 'dns' | 'unknown'; export interface SecurityEvent { id: string; // 事件唯一 ID,用 crypto.randomUUID() 生成 timestamp: number; // 统一用 epoch 毫秒,避免时区问题 srcIp: string; // 源 IP dstIp: string; // 目标 IP srcPort?: number; // 源端口,连接类事件才有 dstPort?: number; // 目标端口 protocol: Protocol; // 协议类型 action: 'allow' | 'deny' | 'fail' | 'success'; // 动作结果 detail: Record<string, unknown>; // 原始字段兜底,不丢信息 }

这个接口的核心设计点是:timestamp强制用 number 而不是 string。很多初学者会把时间存成 “2025-06-01 12:00:00” 这种格式,后续做滑动窗口计算时全要转一遍。detail字段是兜底用的,归一化过程中遇到无法映射的字段就先塞进去,保证不丢原始信息,调试时能追因。

2.2 检测引擎的可插拔设计:面向接口而不是面向实现

检测引擎层最忌讳把所有判断堆在一个大函数里。正确姿势是定义一个统一的检测器接口,每种攻击类型实现一个类,通过注册机制挂到引擎上。这样新增检测能力时只需要“新增一个文件、注册一行代码”,完全不动已有逻辑。

// src/detectors/detector.ts import type { SecurityEvent } from '../types/event'; export interface Alert { id: string; type: string; // 告警类型:PORT_SCAN / BRUTE_FORCE / MALICIOUS_REQUEST severity: 'low' | 'medium' | 'high' | 'critical'; srcIp: string; dstIp?: string; message: string; timestamp: number; meta: Record<string, unknown>; } export interface Detector { // 每个检测器拿到一批事件,返回命中的告警列表 detect(events: SecurityEvent[]): Alert[]; // 重置内部状态,用于测试和规则热加载 reset(): void; }

接口设计上有一个细节值得注意:detect接收的是事件数组而不是单个事件。原因很简单,像端口扫描、暴力破解这类行为,单看一条记录什么都看不出来,必须在一批事件里找统计特征。这个设计直接决定了后续所有检测器的写法。

配合这个接口,引擎层用一个注册表管理所有检测器,并对外开放注册方法:

// src/engine.ts import type { Detector, Alert } from './detectors/detector'; import type { SecurityEvent } from './types/event'; export class DetectionEngine { private detectors: Map<string, Detector> = new Map(); private alerts: Alert[] = []; register(name: string, detector: Detector): void { this.detectors.set(name, detector); } unregister(name: string): void { this.detectors.delete(name); } async process(event: SecurityEvent): Promise<Alert[]> { // 不阻塞事件消费,先收集,再统一跑检测 this.pendingEvents.push(event); // 每满 100 条或积压超过 500ms 就触发一轮检测 if (this.pendingEvents.length >= 100) { return this.flush(); } return []; } private flush(): Alert[] { const batch = this.pendingEvents.splice(0); const hits: Alert[] = []; for (const detector of this.detectors.values()) { const result = detector.detect(batch); hits.push(...result); } return hits; } }

这个引擎实现了批处理逻辑:事件不一条一条喂给检测器,而是攒到 100 条或者定时器触发时再批量跑。这么做有两个好处——第一,减少了检测器的调用次数,避免每个事件都遍历一遍全部规则;第二,滑动窗口类算法需要时间窗口内有足够多的样本,批处理天然保证这一点。

引擎内部维护pendingEvents队列,process方法推入队列后判断是否触发flush。这里有一个内存控制的点:如果长期凑不满 100 条(比如演示环境流量很小),必须有兜底定时器强制 flush,否则事件会越积越多。我一般会用setInterval每 500ms 主动检查一次队列。

2.3 TypeScript 选型理由:类型安全如何直接降低安全系统的事故率

选 TypeScript 而不是 Python,不是性能问题,而是工程问题。安全检测系统最怕的是“静默错误”:数据读进来了,字段对不上,程序不报错,只是检测结果一直不准。Python 字典取不到 key 返回 None,后续计算全变 NaN,排查几个小时。TypeScript 的编译期类型检查直接把这类问题挡在开发阶段。

另一个实际优势是前端复用。课程设计几乎都要配可视化界面,前后端共用 TypeScript 后,事件类型、告警类型的定义可以抽成共享的类型文件,前端拿到告警数据时字段提示、类型安全全都有保障。这会让答辩时的“系统设计”部分变得非常扎实——面试官问“为什么选 TypeScript”时,可以从类型安全、前后端类型共享、生态成熟度三个角度回答,比一句“我会这个”有说服力得多。

3. 三个核心检测器的实现:从滑动窗口到规则引擎,代码可直接抄

3.1 端口扫描检测:滑动窗口统计 + 冷却时间机制

端口扫描检测的逻辑一句话讲就是:同一个源 IP 在短时间内访问了大量不常见端口。实现上用 Map 存储每个源 IP 的时间戳数组,每次来新事件就把窗口外的旧时间戳剔除,再看窗口内的数量是否超过阈值。

// src/detectors/port-scan.detector.ts import type { Detector, Alert } from './detector'; import type { SecurityEvent } from '../types/event'; export interface PortScanConfig { windowMs: number; // 时间窗口:毫秒 threshold: number; // 窗口内访问不同端口的数量阈值 cooldownMs: number; // 同源 IP 告警冷却时间 } export class PortScanDetector implements Detector { private history: Map<string, number[]> = new Map(); private lastAlertAt: Map<string, number> = new Map(); private readonly config: PortScanConfig; constructor(config: Partial<PortScanConfig> = {}) { this.config = { windowMs: 10_000, threshold: 20, cooldownMs: 60_000, ...config, }; } detect(events: SecurityEvent[]): Alert[] { const alerts: Alert[] = []; const now = Date.now(); for (const ev of events) { if (ev.protocol !== 'tcp' || ev.dstPort === undefined) continue; const key = ev.srcIp; const timestamps = this.history.get(key) ?? []; // 只保留窗口内的事件 timestamps.push(ev.timestamp); this.history.set(key, timestamps.filter(t => now - t <= this.config.windowMs)); // 统计不同目标端口数量,排除 80/443 等常见端口 const distinctPorts = new Set( this.history.get(key)!.map((_, i) => i), // 简化示意,实际应结合端口去重 ); void distinctPorts; const portCount = this.countDistinctPorts(events, ev.srcIp); const lastTime = this.lastAlertAt.get(key) ?? 0; if (portCount >= this.config.threshold && now - lastTime > this.config.cooldownMs) { alerts.push({ id: crypto.randomUUID(), type: 'PORT_SCAN', severity: 'medium', srcIp: ev.srcIp, message: `检测到疑似端口扫描行为:${ev.srcIp} 在 ${this.config.windowMs / 1000}s 内访问了 ${portCount} 个不同端口`, timestamp: now, meta: { windowMs: this.config.windowMs, portCount }, }); this.lastAlertAt.set(key, now); } } return alerts; } private countDistinctPorts(events: SecurityEvent[], srcIp: string): number { const ports = new Set<number>(); for (const ev of events) { if (ev.srcIp === srcIp && ev.dstPort !== undefined) { ports.add(ev.dstPort); } } return ports.size; } reset(): void { this.history.clear(); this.lastAlertAt.clear(); } }

三个核心参数值得细说。windowMs设 10 秒,是取常见端口扫描工具的平均扫描速率;threshold设 20,意味着 10 秒内访问 20 个不同端口才算可疑。这个值在演示环境里建议调低到 10,否则模拟攻击脚本要跑很久才能触发告警。cooldownMs必须设置,否则扫描行为持续时同源 IP 会每秒刷一条告警,把告警列表直接打爆。

3.2 暴力破解检测:失败次数统计 + 源 IP 维度聚合

暴力破解的核心特征是“大量失败,没有成功”。检测器维护每个源 IP 在时间窗口内的失败次数,超过阈值就判定为攻击。这里要额外处理一个边界:如果窗口内出现一次成功登录,可以认为暴力破解已得逞,此时应当发出 critical 级别的告警而不是继续数次数。

// src/detectors/brute-force.detector.ts import type { Detector, Alert } from './detector'; import type { SecurityEvent } from '../types/event'; export interface BruteForceConfig { windowMs: number; // 统计窗口 failThreshold: number; // 失败次数阈值 successAlert: boolean; // 是否对窗口内出现成功的事件单独告警 } export class BruteForceDetector implements Detector { private failCount: Map<string, Array<{ time: number; detail: string }>> = new Map(); private readonly config: BruteForceConfig; constructor(config: Partial<BruteForceConfig> = {}) { this.config = { windowMs: 60_000, failThreshold: 10, successAlert: true, ...config, }; } detect(events: SecurityEvent[]): Alert[] { const alerts: Alert[] = []; const now = Date.now(); for (const ev of events) { // 只处理认证相关事件:登录失败/成功 if (!ev.detail?.authType) continue; const key = `${ev.srcIp}:${ev.dstIp}`; if (ev.action === 'fail') { const list = this.failCount.get(key) ?? []; list.push({ time: ev.timestamp, detail: String(ev.detail.username ?? '') }); this.failCount.set(key, list.filter(x => now - x.time <= this.config.windowMs)); if (list.length >= this.config.failThreshold) { alerts.push({ id: crypto.randomUUID(), type: 'BRUTE_FORCE', severity: 'high', srcIp: ev.srcIp, dstIp: ev.dstIp, message: `检测到暴力破解:${ev.srcIp} -> ${ev.dstIp} 在 ${this.config.windowMs / 1000}s 内失败 ${list.length} 次`, timestamp: now, meta: { usernameList: this.extractUsernames(list) }, }); } } else if (ev.action === 'success' && this.config.successAlert) { // 同源 IP 在此窗口内有过失败记录,突然成功,判定攻击成功 const prevFailures = this.failCount.get(key) ?? []; if (prevFailures.length > 0) { alerts.push({ id: crypto.randomUUID(), type: 'ACCOUNT_TAKEOVER', severity: 'critical', srcIp: ev.srcIp, dstIp: ev.dstIp, message: `暴力破解疑似成功:${ev.srcIp} 在失败 ${prevFailures.length} 次后登录成功`, timestamp: now, meta: { username: ev.detail?.username }, }); this.failCount.delete(key); // 告警后重置状态 } } } return alerts; } private extractUsernames(list: Array<{ time: number; detail: string }>): string[] { const unique = new Set(list.map(x => x.detail)); return [...unique].slice(0, 20); } }

这个检测器里最值得讲的是“失败后成功”的关联逻辑。单纯数失败次数会漏掉一个重要场景:攻击者已经撞库成功,只是你没察觉。所以检测器在action === 'success'时回查该源 IP 在窗口内有没有失败记录,有就发ACCOUNT_TAKEOVER告警,级别直接拉满到 critical。这个设计在答辩时讲出来,导师会认为你考虑了攻击链的闭环。

failThreshold建议在演示环境设 5,因为模拟脚本不会真的跑几十次请求;但真实环境 10 次/分钟已经算严格了,要考虑误报。

3.3 恶意请求检测:规则引擎 + 正则匹配,配置驱动而不是代码驱动

第三类检测器针对 Web 攻击,比如 SQL 注入、路径穿越、敏感文件探测。这类检测不适合写死在代码里,应该做成规则表,用 JSON 配置驱动。每条规则包含名称、匹配字段、正则表达式、严重级别。

// src/detectors/malicious-request.detector.ts import type { Detector, Alert } from './detector'; import type { SecurityEvent } from '../types/event'; export interface MatchRule { name: string; field: keyof SecurityEvent['detail'] | 'path' | 'query'; pattern: string; // 正则字符串,运行前 new RegExp 编译 severity: 'low' | 'medium' | 'high'; description: string; } export class MaliciousRequestDetector implements Detector { private rules: MatchRule[] = []; private compiled: Array<MatchRule & { regex: RegExp }> = []; constructor(rules: MatchRule[]) { this.setRules(rules); } // 支持运行时热更新规则 setRules(rules: MatchRule[]): void { this.rules = rules; this.compiled = rules.map(r => ({ ...r, regex: new RegExp(r.pattern, 'i') })); } detect(events: SecurityEvent[]): Alert[] { const alerts: Alert[] = []; for (const ev of events) { if (ev.protocol !== 'http') continue; for (const rule of this.compiled) { // 提取要匹配的字符串 let target = ''; if (rule.field === 'path') { target = String(ev.detail?.path ?? ''); } else if (rule.field === 'query') { target = String(ev.detail?.query ?? ''); } else { target = String(ev.detail?.[rule.field] ?? ''); } if (rule.regex.test(target)) { alerts.push({ id: crypto.randomUUID(), type: 'MALICIOUS_REQUEST', severity: rule.severity, srcIp: ev.srcIp, message: `规则命中 [${rule.name}]:${rule.description}`, timestamp: ev.timestamp, meta: { matched: target.slice(0, 200), rule: rule.name }, }); } } } return alerts; } reset(): void { // 规则检测器是无状态的,不需要重置 } }
// config/rules.json [ { "name": "SQL注入尝试", "field": "query", "pattern": "(union\\s+select|sleep\\(|benchmark\\()", "severity": "high", "description": "检测常见的SQL注入特征" }, { "name": "路径穿越", "field": "path", "pattern": "(\\.\\./|\\.\\.\\\\)", "severity": "high", "description": "检测目录穿越攻击" }, { "name": "敏感文件探测", "field": "path", "pattern": "(\\.env|\\.git/|wp-admin|phpmyadmin)", "severity": "medium", "description": "检测敏感路径扫描行为" } ]

正则规则驱动的核心价值是“运营友好”:改规则不需要改代码、不需要重启服务,安全管理员直接编辑 JSON 文件即可。setRules方法对外暴露,意味着系统可以做一个规则管理接口,把“改规则”做成前端页面上的一个表单,这就是加分项了。

必须提醒的是正则的性能陷阱:pattern里不要写灾难性回溯模式(比如(a+)+$),否则攻击者构造一个特殊字符串就能把 Node.js 事件循环卡死。课程设计不需要做得太复杂,但至少要懂得这个道理。

4. 从源码压缩包到跑通演示:环境搭建、配置参数与接口自测

4.1 初始化 TypeScript 服务端项目:tsconfig 里哪些开关必须打开

拿到服务端源码压缩包后,第一步不是读代码,而是先把项目跑起来。解压后先看根目录有没有package.json,有就直接npm install。如果源码包是精简过的(常见于课程设计),可能需要手动补齐依赖。下面这套初始化步骤,是 TypeScript Node.js 服务端项目的标配流程:

# 1. 初始化项目 npm init -y # 2. 安装运行时依赖 npm install express ws dotenv winston # 3. 安装开发期依赖 npm install -D typescript ts-node @types/node @types/express # 4. 生成 tsconfig.json npx tsc --init

tsconfig.json 里这几个开关必须确认,否则编译期类型检查形同虚设:

{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "outDir": "./dist", "rootDir": "./src", "resolveJsonModule": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }

strict: true是最重要的一行。很多课程设计源码为了省事把 strict 关掉,结果any满天飞,等于没用 TypeScript。答辩时老师问“你的类型安全体现在哪”,你能指着 tsconfig 说“全程 strict 模式”——这比任何口头解释都有力。resolveJsonModule是让代码里能直接import rules from '../config/rules.json',规则引擎的 JSON 配置就靠它。ts-node负责开发期直接跑 TS 文件,不必每次改代码都手动编译。

4.2 环境变量与配置参数:一个 .env 文件管住所有检测阈值

所有检测器的阈值、服务端口、数据库路径都应该通过环境变量下发,而不是散落在代码里。新建.env文件,格式如下:

# 服务端口 PORT=3000 # 检测引擎批处理参数 BATCH_SIZE=100 FLUSH_INTERVAL_MS=500 # 端口扫描检测器参数 SCAN_WINDOW_MS=10000 SCAN_THRESHOLD=20 SCAN_COOLDOWN_MS=60000 # 暴力破解检测器参数 BRUTE_WINDOW_MS=60000 BRUTE_FAIL_THRESHOLD=10 # 告警数据文件(SQLite 或 JSON 文件,课程设计用 JSON 足够) DATA_FILE=./data/alerts.json

代码里通过dotenv读取,并在启动时把参数注入检测器构造函数:

import dotenv from 'dotenv'; dotenv.config(); const config = { scan: { windowMs: Number(process.env.SCAN_WINDOW_MS) || 10_000, threshold: Number(process.env.SCAN_THRESHOLD) || 20, cooldownMs: Number(process.env.SCAN_COOLDOWN_MS) || 60_000, }, brute: { windowMs: Number(process.env.BRUTE_WINDOW_MS) || 60_000, failThreshold: Number(process.env.BRUTE_FAIL_THRESHOLD) || 10, }, }; const engine = new DetectionEngine(); engine.register('port-scan', new PortScanDetector(config.scan)); engine.register('brute-force', new BruteForceDetector(config.brute));

参数全部环境变量化的回报在演示当天才能体现:评委说“阈值设这么高是不是检测不到”,你改一行.env重启服务就能重新演示,而不是翻代码改常量。

4.3 启动服务并用 curl 完成一次完整的服务端接口测试

服务端启动脚本配置在package.json的scripts字段里:

{ "scripts": { "dev": "ts-node src/index.ts", "build": "tsc", "start": "node dist/index.js" } }
npm run dev

服务起来后,先用 curl 验证健康检查接口:

curl http://localhost:3000/health # 期望返回 {"status":"ok"}

接下来构造一条正常的 HTTP 请求事件上报给系统,验证归一化流程:

curl -X POST http://localhost:3000/api/events \ -H "Content-Type: application/json" \ -d '{ "id": "evt-001", "timestamp": 1718000000000, "srcIp": "192.168.1.100", "dstIp": "10.0.0.1", "dstPort": 8080, "protocol": "http", "action": "allow", "detail": { "path": "/api/login", "query": "", "method": "POST", "authType": "login", "username": "admin" } }'

再用一个带 SQL 注入特征的请求测规则引擎:

curl -X POST http://localhost:3000/api/events \ -H "Content-Type: application/json" \ -d '{ "id": "evt-002", "timestamp": 1718000000001, "srcIp": "192.168.1.101", "dstIp": "10.0.0.1", "dstPort": 8080, "protocol": "http", "action": "allow", "detail": { "path": "/api/search", "query": "keyword=1 UNION SELECT username FROM users", "method": "GET" } }'

然后查告警列表:

curl http://localhost:3000/api/alerts

如果告警列表里出现了MALICIOUS_REQUEST类型、规则名是“SQL注入尝试”的记录,说明整条链路已经通了。这组 curl 命令本身就是绝佳的答辩素材——用一个手工构造的恶意请求演示“从上报到告警”的完整过程,比打开浏览器点按钮更有说服力。

5. 服务端检测系统避坑指南:四个高频翻车现场与解法

5.1 时间戳格式不统一,滑动窗口计算全部错乱

现象:端口扫描检测器明明访问了几十个端口,就是不触发告警;或者刚启动服务就疯狂告警。

原因:上报事件里timestamp有的是"2025-06-01 12:00:00"字符串,有的是Date.now()毫秒数,还有的是秒级 Unix 时间戳。检测器里做now - t <= windowMs时,字符串转 number 得到 NaN,比较结果恒为 false;秒级和毫秒级混用时,窗口缩小了 1000 倍。

解决:入口处统一做一次时间戳规范化,任何数据源进来先过这一关。

function normalizeTimestamp(input: number | string): number { if (typeof input === 'number') { // 13 位是毫秒,10 位是秒,统一转成毫秒 return input < 1e12 ? input * 1000 : input; } const parsed = Date.parse(input); return Number.isNaN(parsed) ? Date.now() : parsed; }

这是所有检测器的前置处理,没有例外。

5.2 同步阻塞导致积压事件丢失,检测永远慢半拍

现象:模拟攻击脚本一次性发 5000 条事件,服务端响应越来越慢,最后事件队列里的数据全丢了。

原因:事件上报接口里直接同步执行检测逻辑,每条事件的处理时间可能只要 0.1ms,但 5000 条累加就是 500ms 的阻塞。期间新请求进不来,内存里的队列被不断堆积,最终触发内存溢出。

解决:上报接口只做“接收 + 入队”,检测逻辑在setImmediate或独立的事件循环里异步执行。代码上用第 2 章的批处理引擎模式,接口层绝对不跑detect。

5.3 TypeScript enum 在 JSON 序列化后变成裸字符串,类型守护失效

现象:规则引擎匹配protocol === Protocol.TCP永远为 false,但对比字符串'tcp'却能匹配。

原因:TypeScript 的enum编译后是双向映射对象,但 JSON.parse 出来的数据里字段值是纯字符串,不是 enum 实例。Protocol.TCP是数字(默认 enum 从 0 开始),字符串与数字比较永远不相等。

解决:优先用const对象加 union 类型替代 enum,这是现代 TypeScript 的推荐做法。

export const Protocol = { TCP: 'tcp', UDP: 'udp', HTTP: 'http', DNS: 'dns', } as const; export type Protocol = typeof Protocol[keyof typeof Protocol];

这样Protocol.TCP的值就是字符串'tcp',与 JSON 数据天然一致,且依然有类型提示。

5.4 阈值太低导致告警风暴,告警列表秒变日志文件

现象:演示时模拟客户端一启动,告警列表瞬间几百条,前端大屏全屏飘红。

原因:threshold设了 5,模拟脚本 5 秒内就产生 20 个扫描事件,同源 IP 每满 5 个就触发一次告警,且没有冷却。

解决:告警必须做聚合,10 秒内的同类告警只保留一条,并累计触发次数。实现上就是第 3 章的cooldownMs机制,外加一条经验法则——阈值宁可先设高,演示时再调低,不要一开始设低被误报淹没。这个问题的本质是告警降噪,真实环境里没有降噪机制的系统根本没法用,安全运营人员会直接关掉你的推送。

6. 进阶方向:规则热加载、实时推送与可视化对接

演示系统跑通后,有三个低成本高回报的进阶方向可以让项目从“课程设计”变成“作品集”。

第一是规则热加载。现在的规则引擎支持setRules,但服务端没有暴露修改接口。加一个POST /api/rules的管理接口,把新规则写入 JSON 文件后调用setRules,整个检测系统不用重启就能生效。这一项在答辩时展示“动态更新检测策略”,效果极好。

第二是 WebSocket 实时推送告警。前端大屏用轮询拉告警,一秒一刷,演示时总觉得卡顿。改用ws库在服务端做一个发布订阅,检测引擎每次产生新告警就推送到所有连接的客户端,前端可以直接用 TypeScript 收告警——前后端共享类型定义在这一步会有回报。

第三是接入可视化系统做流量画像。用 ECharts 的桑基图展示源 IP 到目标 IP 的访问关系、用折线图展示事件量趋势,把告警叠加到时间轴上。这正好凑齐“恶意流量可视化检测系统”的完整形态。

验证方法上,不要手动点接口,写一个模拟攻击脚本批量造数据:300 条正常登录 + 30 次失败密码 + 2 次成功,验证暴力破解和撞库成功两条路径;再生成 200 个随机目标端口连接验证端口扫描。跑完看三类指标:检测准确率、误报率、单批事件处理延迟。延迟在 100ms 以内就算合格。

最后说一个我的血泪教训:课程设计不要追求“检测算法创新”,评委想看到的是完整闭环——数据能进、检测能跑、告警能看、参数能调。把这几件事做扎实,比塞一堆看起来高深但没跑通的模型强得多。用 TypeScript 做这件事,最大的收益是它逼着你先把数据结构定义清楚,而这恰恰是安全系统最不该糊弄的部分。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询