diff --git a/packages/core/basic/src/utils/util.sp.ts b/packages/core/basic/src/utils/util.sp.ts index 1ff9cfc5..af7cae4e 100644 --- a/packages/core/basic/src/utils/util.sp.ts +++ b/packages/core/basic/src/utils/util.sp.ts @@ -1,8 +1,8 @@ //转换为import -import childProcess from 'child_process'; -import { safePromise } from './util.promise.js'; -import { ILogger, logger } from './util.log.js'; -import iconv from 'iconv-lite'; +import childProcess from "child_process"; +import { safePromise } from "./util.promise.js"; +import { ILogger, logger } from "./util.log.js"; +import iconv from "iconv-lite"; export type ExecOption = { cmd: string | string[]; env: any; @@ -11,12 +11,12 @@ export type ExecOption = { }; async function exec(opts: ExecOption): Promise { - let cmd = ''; + let cmd = ""; const log = opts.logger || logger; if (opts.cmd instanceof Array) { for (const item of opts.cmd) { if (cmd) { - cmd += ' && ' + item; + cmd += " && " + item; } else { cmd = item; } @@ -38,7 +38,7 @@ async function exec(opts: ExecOption): Promise { log.error(`exec error: ${error}`); reject(error); } else { - const res = stdout.toString('utf-8'); + const res = stdout.toString("utf-8"); log.info(`stdout: ${res}`); resolve(res); } @@ -57,11 +57,11 @@ export type SpawnOption = { }; function isWindows() { - return process.platform === 'win32'; + return process.platform === "win32"; } function convert(buffer: any) { if (isWindows()) { - const decoded = iconv.decode(buffer, 'GBK'); + const decoded = iconv.decode(buffer, "GBK"); // 检查是否有有效字符 return decoded && decoded.trim().length > 0 ? decoded : buffer.toString(); } else { @@ -74,12 +74,12 @@ function convert(buffer: any) { // } async function spawn(opts: SpawnOption): Promise { - let cmd = ''; + let cmd = ""; const log = opts.logger || logger; if (opts.cmd instanceof Array) { for (const item of opts.cmd) { if (cmd) { - cmd += ' && ' + item; + cmd += " && " + item; } else { cmd = item; } @@ -88,8 +88,8 @@ async function spawn(opts: SpawnOption): Promise { cmd = opts.cmd; } log.info(`执行命令: ${cmd}`); - let stdout = ''; - let stderr = ''; + let stdout = ""; + let stderr = ""; return safePromise((resolve, reject) => { const ls = childProcess.spawn(cmd, { shell: true, @@ -99,23 +99,23 @@ async function spawn(opts: SpawnOption): Promise { }, ...opts.options, }); - ls.stdout.on('data', data => { + ls.stdout.on("data", data => { data = convert(data); log.info(`stdout: ${data}`); stdout += data; }); - ls.stderr.on('data', data => { + ls.stderr.on("data", data => { data = convert(data); log.warn(`stderr: ${data}`); stderr += data; }); - ls.on('error', error => { + ls.on("error", error => { log.error(`child process error: ${error}`); reject(error); }); - ls.on('close', (code: number) => { + ls.on("close", (code: number) => { if (code !== 0) { log.error(`child process exited with code ${code}`); reject(new Error(stderr)); diff --git a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts index d8805cff..e76e4ded 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts @@ -175,6 +175,7 @@ export default { suiteSetting: "Suite Settings", orderManager: "Order Management", userSuites: "User Suites", + netTest: "Network Test", }, certificateRepo: { title: "Certificate Repository", diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts index f55c7a56..72631023 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts @@ -181,6 +181,7 @@ export default { suiteSetting: "套餐设置", orderManager: "订单管理", userSuites: "用户套餐", + netTest: "网络测试", }, certificateRepo: { title: "证书仓库", diff --git a/packages/ui/certd-client/src/router/source/modules/sys.ts b/packages/ui/certd-client/src/router/source/modules/sys.ts index b634f254..31fa225a 100644 --- a/packages/ui/certd-client/src/router/source/modules/sys.ts +++ b/packages/ui/certd-client/src/router/source/modules/sys.ts @@ -249,6 +249,17 @@ export const sysResources = [ }, ], }, + { + title: "certd.sysResources.netTest", + name: "NetTest", + path: "/sys/nettest", + component: "/sys/nettest/index.vue", + meta: { + icon: "ion:build-outline", + auth: true, + keepAlive: true, + }, + }, ], }, ]; diff --git a/packages/ui/certd-client/src/views/sys/nettest/DomainTestCard.vue b/packages/ui/certd-client/src/views/sys/nettest/DomainTestCard.vue new file mode 100644 index 00000000..c09e52e6 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/nettest/DomainTestCard.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/packages/ui/certd-client/src/views/sys/nettest/ServerInfoCard.vue b/packages/ui/certd-client/src/views/sys/nettest/ServerInfoCard.vue new file mode 100644 index 00000000..e3b79569 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/nettest/ServerInfoCard.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/packages/ui/certd-client/src/views/sys/nettest/TestCase.vue b/packages/ui/certd-client/src/views/sys/nettest/TestCase.vue new file mode 100644 index 00000000..2a7843a6 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/nettest/TestCase.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/packages/ui/certd-client/src/views/sys/nettest/api.ts b/packages/ui/certd-client/src/views/sys/nettest/api.ts new file mode 100644 index 00000000..f75d10fc --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/nettest/api.ts @@ -0,0 +1,33 @@ +import { request } from "/@/api/service"; + +export async function DomainResolve(domain: string) { + return await request({ + url: "/sys/nettest/domainResolve", + method: "post", + data: { domain }, + }); +} + +export async function PingTest(domain: string) { + return await request({ + url: "/sys/nettest/ping", + method: "post", + data: { domain }, + }); +} + +export async function TelnetTest(domain: string, port: number) { + return await request({ + url: "/sys/nettest/telnet", + method: "post", + data: { domain, port }, + }); +} + +// 获取服务器信息(包括本地IP、外网IP和DNS服务器) +export async function GetServerInfo() { + return await request({ + url: "/sys/nettest/serverInfo", + method: "post", + }); +} diff --git a/packages/ui/certd-client/src/views/sys/nettest/index.vue b/packages/ui/certd-client/src/views/sys/nettest/index.vue new file mode 100644 index 00000000..d8b3dce7 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/nettest/index.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/packages/ui/certd-server/src/controller/sys/nettest/nettest-controller.ts b/packages/ui/certd-server/src/controller/sys/nettest/nettest-controller.ts new file mode 100644 index 00000000..75c55f28 --- /dev/null +++ b/packages/ui/certd-server/src/controller/sys/nettest/nettest-controller.ts @@ -0,0 +1,47 @@ +import { BaseController } from '@certd/lib-server'; +import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core'; +import { NetTestService } from '../../../modules/sys/nettest/nettest-service.js'; + + +@Provide() +@Controller('/api/sys/nettest/') +export class SysNetTestController extends BaseController { + + @Inject() + netTestService: NetTestService; + + + @Post('/domainResolve', { summary: 'sys:settings:view' }) + public async domainResolve(@Body(ALL) body: { domain: string }) { + + const { domain } = body; + const result = await this.netTestService.domainResolve(domain); + return this.ok(result); + } + + // ping + @Post('/ping', { summary: 'sys:settings:view' }) + public async ping(@Body(ALL) body: { domain: string }) { + + const { domain } = body; + const result = await this.netTestService.ping(domain); + return this.ok(result); + } + + // telnet + @Post('/telnet', { summary: 'sys:settings:view' }) + public async telnet(@Body(ALL) body: { domain: string, port: number }) { + + const { domain, port } = body; + const result = await this.netTestService.telnet(domain, port); + return this.ok(result); + } + + // telnet + @Post('/serverInfo', { summary: 'sys:settings:view' }) + public async serverInfo() { + + const result = await this.netTestService.serverInfo(); + return this.ok(result); + } +} diff --git a/packages/ui/certd-server/src/modules/sys/nettest/nettest-service.ts b/packages/ui/certd-server/src/modules/sys/nettest/nettest-service.ts new file mode 100644 index 00000000..feb7ed70 --- /dev/null +++ b/packages/ui/certd-server/src/modules/sys/nettest/nettest-service.ts @@ -0,0 +1,208 @@ +import { Provide, Scope, ScopeEnum } from '@midwayjs/core'; +import { http, utils } from '@certd/basic'; + +// 使用@certd/basic包中已有的utils.sp.spawn函数替代自定义的asyncExec +// 该函数已经内置了Windows系统编码问题的解决方案 + +export type NetTestResult = { + success: boolean; //是否成功 + message: string; //结果 + testLog: string; //测试日志 + error?: string; //执行错误信息 +} + +@Provide('nettestService') +@Scope(ScopeEnum.Request, { allowDowngrade: true }) +export class NetTestService { + /** + * 执行Telnet测试 + * @param domain 域名 + * @param port 端口 + * @returns 测试结果 + */ + async telnet(domain: string, port: number): Promise { + try { + let command = ''; + + if (this.isWindows()) { + // Windows系统使用PowerShell执行测试,避免输入重定向问题 + // 使用PowerShell的Test-NetConnection命令进行端口测试 + command = `powershell -Command "& { $result = Test-NetConnection -ComputerName ${domain} -Port ${port} -InformationLevel Quiet; if ($result) { Write-Host '端口连接成功' } else { Write-Host '端口连接失败' } }"`; + } else { + // Linux系统使用nc命令进行端口测试 + command = `nc -zv -w 5 ${domain} ${port} 2>&1`; + } + + // 使用utils.sp.spawn执行命令,它会自动处理Windows编码问题 + const output = await utils.sp.spawn({ + cmd: command, + logger: undefined // 可以根据需要传入logger + }); + + // 判断测试是否成功 + const success = this.isWindows() + ? output.includes('端口连接成功') + : output.includes('succeeded') || output.includes('open'); + + // 处理结果 + return { + success, + message: success ? '端口连接测试成功' : '端口连接测试失败', + testLog: output, + }; + + } catch (error) { + return { + success: false, + message: 'Telnet测试执行失败', + testLog: error instanceof Error ? error.message : String(error), + error: error instanceof Error ? error.message : String(error) + }; + } + } + + /** + * 执行Ping测试 + * @param domain 域名 + * @returns 测试结果 + */ + async ping(domain: string): Promise { + try { + let command = ''; + + if (this.isWindows()) { + // Windows系统ping命令,发送4个包 + command = `ping -n 4 ${domain}`; + } else { + // Linux系统ping命令,发送4个包 + command = `ping -c 4 ${domain}`; + } + + // 使用utils.sp.spawn执行命令 + const output = await utils.sp.spawn({ + cmd: command, + logger: undefined + }); + + // 判断测试是否成功 + const success = this.isWindows() + ? output.includes('TTL=') + : output.includes('0% packet loss'); + + return { + success, + message: success ? 'Ping测试成功' : 'Ping测试失败', + testLog: output, + }; + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return { + success: false, + message: 'Ping测试执行失败', + testLog: errorMessage, + error: errorMessage + }; + } + } + + private isWindows() { + return process.platform === 'win32'; + } + + /** + * 执行域名解析测试 + * @param domain 域名 + * @returns 解析结果 + */ + async domainResolve(domain: string): Promise { + try { + let command = ''; + if (this.isWindows()) { + // Windows系统使用nslookup命令 + command = `nslookup ${domain}`; + } else { + // Linux系统优先使用dig命令,如果没有则回退到nslookup + command = `which dig > /dev/null && dig ${domain} || nslookup ${domain}`; + } + + // 使用utils.sp.spawn执行命令 + const output = await utils.sp.spawn({ + cmd: command, + logger: undefined + }); + + // 判断测试是否成功 + const success = output.includes('Address:') || output.includes('IN A') || + (this.isWindows() && output.includes('Name:')); + + return { + success, + message: success ? '域名解析测试成功' : '域名解析测试失败', + testLog: output, + }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return { + success: false, + message: '域名解析测试执行失败', + testLog: errorMessage, + error: errorMessage + }; + } + } + + + async getLocalIP(): Promise { + try { + const output = await utils.sp.spawn({ + cmd: 'bash -c "ip a | grep \'inet \' | grep -v \'127.0.0.1\' | awk \'{print $2}\' | cut -d/ -f1"', + logger: undefined + }); + return output.trim(); + } catch (error) { + return error instanceof Error ? error.message : String(error); + } + } + + async getPublicIP(): Promise { + try { + const res = await http.request({ + url:"https://ipinfo.io/ip", + method:"GET", + }) + return res + } catch (error) { + return error instanceof Error ? error.message : String(error); + } + } + + async getDNSservers(): Promise { + try { + const output = await utils.sp.spawn({ + cmd: 'cat /etc/resolv.conf | grep nameserver | awk \'{print $2}\'', + logger: undefined + }); + return output.trim().split('\n'); + } catch (error) { + return [error instanceof Error ? error.message : String(error)]; + } + } + /** + * 获取服务器信息(包括本地IP、外网IP和DNS服务器) + * @returns 服务器信息 + */ + async serverInfo(): Promise { + + const res = { + localIP: '', + publicIP: '', + dnsServers: [], + } + + res.localIP = await this.getLocalIP(); + res.publicIP = await this.getPublicIP(); + res.dnsServers = await this.getDNSservers(); + return res + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14fea808..66175e0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - '@certd/ui-server': - specifier: link:packages/ui/certd-server - version: link:packages/ui/certd-server axios: specifier: ^1.7.7 version: 1.9.0(debug@4.4.1) @@ -49,7 +46,7 @@ importers: packages/core/acme-client: dependencies: '@certd/basic': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../basic '@peculiar/x509': specifier: ^1.11.0 @@ -210,10 +207,10 @@ importers: packages/core/pipeline: dependencies: '@certd/basic': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../basic '@certd/plus-core': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../pro/plus-core dayjs: specifier: ^1.11.7 @@ -418,7 +415,7 @@ importers: packages/libs/lib-k8s: dependencies: '@certd/basic': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/basic '@kubernetes/client-node': specifier: 0.21.0 @@ -458,19 +455,19 @@ importers: packages/libs/lib-server: dependencies: '@certd/acme-client': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/pipeline '@certd/plugin-lib': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../plugins/plugin-lib '@certd/plus-core': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../pro/plus-core '@midwayjs/cache': specifier: 3.14.0 @@ -616,16 +613,16 @@ importers: packages/plugins/plugin-cert: dependencies: '@certd/acme-client': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/pipeline '@certd/plugin-lib': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../plugin-lib '@google-cloud/publicca': specifier: ^1.3.0 @@ -707,10 +704,10 @@ importers: specifier: ^3.787.0 version: 3.810.0(aws-crt@1.26.2) '@certd/basic': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/pipeline '@kubernetes/client-node': specifier: 0.21.0 @@ -798,19 +795,19 @@ importers: packages/pro/commercial-core: dependencies: '@certd/basic': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/basic '@certd/lib-server': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../libs/lib-server '@certd/pipeline': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/pipeline '@certd/plugin-plus': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../plugin-plus '@certd/plus-core': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../plus-core '@midwayjs/core': specifier: 3.20.11 @@ -895,19 +892,19 @@ importers: specifier: ^1.0.2 version: 1.0.3 '@certd/basic': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/basic '@certd/lib-k8s': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../libs/lib-k8s '@certd/pipeline': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/pipeline '@certd/plugin-cert': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../plugins/plugin-cert '@certd/plus-core': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../plus-core ali-oss: specifier: ^6.21.0 @@ -1010,7 +1007,7 @@ importers: packages/pro/plus-core: dependencies: '@certd/basic': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/basic dayjs: specifier: ^1.11.7 @@ -1306,10 +1303,10 @@ importers: version: 0.1.3(zod@3.24.4) devDependencies: '@certd/lib-iframe': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../libs/lib-iframe '@certd/pipeline': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/pipeline '@rollup/plugin-commonjs': specifier: ^25.0.7 @@ -1492,46 +1489,46 @@ importers: specifier: ^3.705.0 version: 3.810.0(aws-crt@1.26.2) '@certd/acme-client': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/basic '@certd/commercial-core': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../pro/commercial-core '@certd/cv4pve-api-javascript': specifier: ^8.4.2 version: 8.4.2 '@certd/jdcloud': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../libs/lib-jdcloud '@certd/lib-huawei': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../libs/lib-huawei '@certd/lib-k8s': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../libs/lib-k8s '@certd/lib-server': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../libs/lib-server '@certd/midway-flyway-js': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../libs/midway-flyway-js '@certd/pipeline': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../core/pipeline '@certd/plugin-cert': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../plugins/plugin-cert '@certd/plugin-lib': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../plugins/plugin-lib '@certd/plugin-plus': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../pro/plugin-plus '@certd/plus-core': - specifier: ^1.36.24 + specifier: ^1.37.1 version: link:../../pro/plus-core '@huaweicloud/huaweicloud-sdk-cdn': specifier: ^3.1.120