feat(round-16): AI意图情绪分析/展会名片双向互换/雷达爬虫清洗/离线多媒体缓存 (103套件1101测试全绿)
这个提交包含在:
@@ -0,0 +1,102 @@
|
||||
import { Provide } from '@midwayjs/core';
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
|
||||
export type BotClassification = 'SEARCH_CRAWLER' | 'SOCIAL_PREVIEW' | 'SCRIPTER';
|
||||
|
||||
export interface BotDetectResult {
|
||||
isBot: boolean;
|
||||
botType?: BotClassification;
|
||||
botName?: string;
|
||||
}
|
||||
|
||||
export interface TrafficFilterResult {
|
||||
isBot: boolean;
|
||||
category: 'HUMAN' | 'CRAWLER' | 'SUSPICIOUS_SPAM';
|
||||
shouldRecordLead: boolean;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
const SEARCH_BOTS = ['googlebot', 'bingbot', 'baiduspider', 'yandexbot', 'duckduckbot', 'sogou'];
|
||||
const SOCIAL_BOTS = ['whatsapp', 'twitterbot', 'facebookexternalhit', 'linkedinbot', 'telegrambot', 'slackbot'];
|
||||
const SCRIPT_BOTS = ['curl', 'wget', 'python-requests', 'postman', 'httpclient', 'scrapy', 'axios'];
|
||||
|
||||
/**
|
||||
* 基于 User-Agent 识别机器人与爬虫
|
||||
*/
|
||||
export function detectBotUserAgent(userAgent?: string): BotDetectResult {
|
||||
if (!userAgent || typeof userAgent !== 'string') {
|
||||
return { isBot: false };
|
||||
}
|
||||
|
||||
const ua = userAgent.toLowerCase();
|
||||
|
||||
for (const bot of SEARCH_BOTS) {
|
||||
if (ua.includes(bot)) {
|
||||
return { isBot: true, botType: 'SEARCH_CRAWLER', botName: bot };
|
||||
}
|
||||
}
|
||||
|
||||
for (const bot of SOCIAL_BOTS) {
|
||||
if (ua.includes(bot)) {
|
||||
return { isBot: true, botType: 'SOCIAL_PREVIEW', botName: bot };
|
||||
}
|
||||
}
|
||||
|
||||
for (const bot of SCRIPT_BOTS) {
|
||||
if (ua.includes(bot)) {
|
||||
return { isBot: true, botType: 'SCRIPTER', botName: bot };
|
||||
}
|
||||
}
|
||||
|
||||
return { isBot: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测 1 秒内的频次是否异常超高
|
||||
*/
|
||||
export function isRateLimitSuspicious(timestamps: number[], thresholdPerSec = 10): boolean {
|
||||
if (!timestamps || timestamps.length < thresholdPerSec) {
|
||||
return false;
|
||||
}
|
||||
const sorted = [...timestamps].sort((a, b) => a - b);
|
||||
const now = sorted[sorted.length - 1];
|
||||
const countInLastSecond = sorted.filter(t => now - t <= 1000).length;
|
||||
return countInLastSecond >= thresholdPerSec;
|
||||
}
|
||||
|
||||
/**
|
||||
* 访客雷达异常流量与爬虫清洗过滤服务
|
||||
*/
|
||||
@Provide()
|
||||
export class RadarBotFilterService extends BaseService {
|
||||
async filterIncomingTraffic(input: {
|
||||
ip: string;
|
||||
userAgent: string;
|
||||
recentTimestamps?: number[];
|
||||
}): Promise<TrafficFilterResult> {
|
||||
const botCheck = detectBotUserAgent(input.userAgent);
|
||||
if (botCheck.isBot) {
|
||||
return {
|
||||
isBot: true,
|
||||
category: 'CRAWLER',
|
||||
shouldRecordLead: false,
|
||||
reason: `检测到 ${botCheck.botType}: ${botCheck.botName}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (input.recentTimestamps && isRateLimitSuspicious(input.recentTimestamps)) {
|
||||
return {
|
||||
isBot: true,
|
||||
category: 'SUSPICIOUS_SPAM',
|
||||
shouldRecordLead: false,
|
||||
reason: '1秒内突发异常高频请求,疑似自动化刷量攻击',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isBot: false,
|
||||
category: 'HUMAN',
|
||||
shouldRecordLead: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户