Compare commits

..

3 Commits

Author SHA1 Message Date
0b6b9624a6 乱七八糟的 2025-08-12 00:03:16 +08:00
0f8e7fe4d6 Merge remote-tracking branch 'origin/main' 2025-08-09 19:58:10 +08:00
bccdde74ad feat:telnet发送配置命令 2025-07-18 21:10:40 +08:00
17 changed files with 3870 additions and 4745 deletions

View File

@ -82,7 +82,7 @@ async def test_endpoint():
@router.get("/scan_network", summary="扫描网络中的交换机") @router.get("/scan_network", summary="扫描网络中的交换机")
async def scan_network(subnet: str = "192.168.1.0/24"): async def scan_network(subnet: str = "192.168.1.0/24"):
try: try:
devices = scanner.scan_subnet(subnet) devices = await scanner.scan_subnet(subnet)
return { return {
"success": True, "success": True,
"devices": devices, "devices": devices,
@ -94,7 +94,7 @@ async def scan_network(subnet: str = "192.168.1.0/24"):
@router.get("/list_devices", summary="列出已发现的交换机") @router.get("/list_devices", summary="列出已发现的交换机")
async def list_devices(): async def list_devices():
return { return {
"devices": scanner.load_cached_devices() "devices": await scanner.load_cached_devices()
} }
class CommandRequest(BaseModel): class CommandRequest(BaseModel):
@ -125,7 +125,7 @@ async def parse_command(request: CommandRequest):
@router.post("/apply_config", response_model=dict) @router.post("/apply_config", response_model=dict)
async def apply_config(request: ConfigRequest): async def apply_config(request: ConfigRequest):
""" """
应用配置到交换机 应用配置到交换机弃用
""" """
try: try:
configurator = SwitchConfigurator( configurator = SwitchConfigurator(

View File

@ -110,6 +110,9 @@ class SwitchConfigurator:
output = "" output = ""
for cmd in commands: for cmd in commands:
if cmd.startswith("!"):
logger.debug(f"跳过特殊命令: {cmd}")
continue
logger.info(f"发送命令: {cmd}") logger.info(f"发送命令: {cmd}")
writer.write(f"{cmd}\n") writer.write(f"{cmd}\n")
await writer.drain() await writer.drain()

View File

@ -42,7 +42,7 @@ class AIService:
] ]
try: try:
response = self.client.chat.completions.create( response = await self.client.chat.completions.create(
model="deepseek-ai/DeepSeek-V3", model="deepseek-ai/DeepSeek-V3",
messages=messages, messages=messages,
temperature=0.3, temperature=0.3,

View File

@ -9,7 +9,7 @@ class NetworkScanner:
self.cache_path = Path(cache_path) self.cache_path = Path(cache_path)
self.nm = nmap.PortScanner() self.nm = nmap.PortScanner()
def scan_subnet(self, subnet: str = "192.168.1.0/24", ports: List[int] = [22, 23, 80]) -> List[Dict]: async def scan_subnet(self, subnet: str = "192.168.1.0/24", ports: List[int] = [22, 23, 80]) -> List[Dict]:
"""扫描指定子网的设备,获取设备信息和开放端口""" """扫描指定子网的设备,获取设备信息和开放端口"""
logger.info(f"Scanning subnet: {subnet}") logger.info(f"Scanning subnet: {subnet}")
@ -33,16 +33,16 @@ class NetworkScanner:
except Exception as e: except Exception as e:
logger.error(f"Error while scanning subnet: {e}") logger.error(f"Error while scanning subnet: {e}")
self._save_to_cache(devices) await self._save_to_cache(devices)
return devices return devices
def _save_to_cache(self, devices: List[Dict]): async def _save_to_cache(self, devices: List[Dict]):
"""保存扫描结果到本地文件""" """保存扫描结果到本地文件"""
with open(self.cache_path, "w") as f: with open(self.cache_path, "w") as f:
json.dump(devices, f, indent=2) json.dump(devices, f, indent=2)
logger.info(f"Saved {len(devices)} devices to cache") logger.info(f"Saved {len(devices)} devices to cache")
def load_cached_devices(self) -> List[Dict]: async def load_cached_devices(self) -> List[Dict]:
"""从缓存加载设备列表""" """从缓存加载设备列表"""
if not self.cache_path.exists(): if not self.cache_path.exists():
return [] return []

View File

@ -21,6 +21,7 @@
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.2.0", "@nestjs/swagger": "^11.2.0",
"axios": "^1.10.0", "axios": "^1.10.0",
"ip": "^2.0.1",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"ssh2": "^1.16.0" "ssh2": "^1.16.0"
@ -34,6 +35,7 @@
"@swc/cli": "^0.6.0", "@swc/cli": "^0.6.0",
"@swc/core": "^1.10.7", "@swc/core": "^1.10.7",
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/ip": "^1.1.3",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"@types/node": "^22.16.4", "@types/node": "^22.16.4",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,13 @@
import { Module } from '@nestjs/common'; import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { RootModule } from './root/root.module'; import { RootModule } from './root/root.module';
import { HttpMiddleware } from './common/middleware/http.middleware';
import { NetworkModule } from './modules/network/network.module';
@Module({ @Module({
imports: [RootModule], imports: [RootModule, NetworkModule],
}) })
export class AppModule {} export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): any {
consumer.apply(HttpMiddleware).forRoutes('api');
}
}

View File

@ -1,28 +0,0 @@
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
HttpStatus,
} from '@nestjs/common';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message =
exception instanceof HttpException ? exception.message : '服务器内部错误';
response.status(status).json({
success: false,
data: null,
message,
});
}
}

View File

@ -19,7 +19,6 @@ export class ResponseInterceptor<T>
map((data) => ({ map((data) => ({
success: true, success: true,
data, data,
message: '操作成功',
})), })),
); );
} }

View File

@ -0,0 +1,25 @@
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class HttpMiddleware implements NestMiddleware {
private readonly logger = new Logger('HTTP');
use(req: Request, res: Response, next: NextFunction): void {
const { method, originalUrl } = req;
this.logger.log(`HTTP request for ${method} ${originalUrl}`);
const startTime = Date.now();
res.on('finish', () => {
const statusCode = res.statusCode;
const contentLength = res.get('content-length') || 0;
const duration = Date.now() - startTime;
this.logger.log(
`${method} ${originalUrl} ${statusCode} ${contentLength}B - ${duration}ms`,
);
});
next();
}
}

View File

@ -1,5 +1,4 @@
export interface ApiResponse<T = any> { export interface ApiResponse<T = any> {
success: boolean; success: boolean;
data: T; data: T;
message: string;
} }

View File

@ -3,13 +3,11 @@ import { AppModule } from './app.module';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ResponseInterceptor } from './common/interceptors/response.interceptor'; import { ResponseInterceptor } from './common/interceptors/response.interceptor';
import { AllExceptionsFilter } from './common/filters/all-exception.filter';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api'); app.setGlobalPrefix('api');
app.useGlobalInterceptors(new ResponseInterceptor()); app.useGlobalInterceptors(new ResponseInterceptor());
app.useGlobalFilters(new AllExceptionsFilter());
const config = new DocumentBuilder() const config = new DocumentBuilder()
.setTitle('交换机API平台') .setTitle('交换机API平台')
.setDescription('自动化交换机配置和流量监控接口') .setDescription('自动化交换机配置和流量监控接口')

View File

@ -0,0 +1,18 @@
import { Controller, Get, Inject } from '@nestjs/common';
import { NetworkService } from './network.service';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
@ApiTags('Network')
@Controller('network')
export class NetworkController {
constructor(
@Inject(NetworkService)
private readonly networkService: NetworkService,
) {}
@Get('adapters')
@ApiOperation({ summary: '获取网络适配器网段' })
async getNetworkAdapters() {
return this.networkService.getNetworkAdapters();
}
}

View File

@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { NetworkService } from './network.service';
import { NetworkController } from './network.controller';
@Module({
controllers: [NetworkController],
providers: [NetworkService],
exports: [NetworkService],
})
export class NetworkModule {}

View File

@ -0,0 +1,44 @@
import { Injectable, Logger } from '@nestjs/common';
import os from 'os';
import ip from 'ip';
@Injectable()
export class NetworkService {
private readonly logger = new Logger(NetworkService.name);
/**
*
*/
async getNetworkAdapters(): Promise<any> {
try {
this.logger.log('Getting network adapters');
const interfaces = os.networkInterfaces();
const networks: any[] = [];
for (const [adapter, addrs] of Object.entries(interfaces)) {
if (!addrs) continue;
for (const addr of addrs) {
if (addr.family === 'IPv4' && !addr.internal) {
const ipAddress = addr.address;
const subnetMask = addr.netmask;
const cidr = ip.subnet(ipAddress, subnetMask);
const networkCidr = `${cidr.networkAddress}/${cidr.subnetMaskLength}`;
networks.push({
adapter,
network: networkCidr,
ip: ipAddress,
subnet_mask: subnetMask,
});
}
}
}
return { networks };
} catch (error) {
this.logger.error('获取网络适配器信息失败', error);
return { error: `获取网络适配器信息失败: ${error.message}` };
}
}
}

View File

@ -3,7 +3,7 @@ import { Controller, Get } from '@nestjs/common';
@Controller() @Controller()
export class RootController { export class RootController {
@Get() @Get()
getWelcome() { async getWelcome() {
return { return {
message: '欢迎使用交换机管理平台 API', message: '欢迎使用交换机管理平台 API',
}; };

View File

@ -1,5 +1,6 @@
import { Module } from '@nestjs/common'; import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { RootController } from './root.controller'; import { RootController } from './root.controller';
import { HttpMiddleware } from '../common/middleware/http.middleware';
@Module({ @Module({
controllers: [RootController], controllers: [RootController],