diff --git a/src/Gateway/BackgroundServices/GatewayBackground.cs b/src/Gateway/BackgroundServices/GatewayBackground.cs index adc450cda70da5bf80813d26b36822ee4f8de914..4db3a71c35edc6b22ad205993b1a1c3c108d82d2 100644 --- a/src/Gateway/BackgroundServices/GatewayBackground.cs +++ b/src/Gateway/BackgroundServices/GatewayBackground.cs @@ -9,10 +9,50 @@ public class GatewayBackgroundService( { // 数据库迁移 freeSql.CodeFirst.SyncStructure(typeof(RouteEntity), typeof(ClusterEntity), typeof(CertificateEntity), - typeof(StaticFileProxyEntity)); + typeof(StaticFileProxyEntity), typeof(SystemLoggerEntity)); // 首次启动时更新配置 await gatewayService.RefreshConfig(); await certificateService.RefreshConfig(); + + while (stoppingToken.IsCancellationRequested == false) + { + // 获取当前时间距离23:59:59剩余多少秒,然后等待这么多秒 + var seconds = (DateTime.Now.Date.AddDays(1) - DateTime.Now).TotalSeconds; + + // Task.Delay不能是负数 + if (seconds < 0) + { + seconds = 1000; + } + + #region 等待第二天凌晨 + + // 如果服务手动被关闭,则先统计当天流量记录。 + try + { + await Task.Delay(TimeSpan.FromSeconds(seconds), stoppingToken); + + // 停止一秒防止时间计算错误 + await Task.Delay(1000, stoppingToken); + } + catch + { + } + + #endregion + + var systemLoggerEntity = new SystemLoggerEntity + { + RequestCount = GatewayMiddleware.CurrentRequestCount, + ErrorRequestCount = GatewayMiddleware.CurrentErrorCount, + CurrentTime = DateTime.Now.AddDays(-1) + }; + + // 清空请求计数器 + GatewayMiddleware.ClearRequestCount(); + + await freeSql.Insert(systemLoggerEntity).ExecuteAffrowsAsync(); + } } } \ No newline at end of file diff --git a/src/Gateway/Dto/NetWorkDto.cs b/src/Gateway/Dto/NetWorkDto.cs index 9d3b531f8f01b13ae10406afe8a6f92c77224140..a0fdc69acc691df3765a59abc7a28fe2b9a27bbd 100644 --- a/src/Gateway/Dto/NetWorkDto.cs +++ b/src/Gateway/Dto/NetWorkDto.cs @@ -28,4 +28,36 @@ public class NetWorkDto(long received, long sent) /// 当前时间 /// public string Time => DateTime.Now.ToString("HH:mm:ss"); + + /// + /// 当天请求数量 + /// + public int CurrentRequestCount { get; set; } = GatewayMiddleware.CurrentRequestCount; + + /// + /// 当天错误数量 + /// + public int CurrentErrorCount { get; set; } = GatewayMiddleware.CurrentErrorCount; + + private double _totalRequestCount; + + /// + /// 当天错误率 + /// + public double TotalRequestCount + { + get => _totalRequestCount + CurrentRequestCount; + set => _totalRequestCount = value; + } + + private double _totalErrorCount; + + /// + /// 总错误数量 + /// + public double TotalErrorCount + { + get => _totalErrorCount + CurrentErrorCount; + set => value = _totalErrorCount; + } } \ No newline at end of file diff --git a/src/Gateway/Entities/SystemLoggerEntity.cs b/src/Gateway/Entities/SystemLoggerEntity.cs new file mode 100644 index 0000000000000000000000000000000000000000..89ad2ec04f663bfe25d227007538ee620ec8f950 --- /dev/null +++ b/src/Gateway/Entities/SystemLoggerEntity.cs @@ -0,0 +1,21 @@ +namespace Gateway.Entities; + +public sealed class SystemLoggerEntity : Entity +{ + /// + /// 请求数量 + /// + public int RequestCount { get; set; } + + /// + /// 异常数量 + /// + public int ErrorRequestCount { get; set; } + + /// + /// 当前时间 + /// + public DateTime CurrentTime { get; set; } + + +} \ No newline at end of file diff --git a/src/Gateway/Middlewares/GatewayMiddleware.cs b/src/Gateway/Middlewares/GatewayMiddleware.cs index 966ff75890cc4840e16fb78cdc1d5af152842d71..46a39fe2495a1cfbdea3bec6b5b88c553668cc95 100644 --- a/src/Gateway/Middlewares/GatewayMiddleware.cs +++ b/src/Gateway/Middlewares/GatewayMiddleware.cs @@ -2,6 +2,26 @@ public class GatewayMiddleware : IMiddleware { + /// + /// 请求计数器 + /// + private static int _currentRequestCount; + + /// + /// 异常计数器 + /// + private static int _currentErrorCount; + + public static int CurrentRequestCount => _currentRequestCount; + + public static int CurrentErrorCount => _currentErrorCount; + + public static void ClearRequestCount() + { + Interlocked.Exchange(ref _currentRequestCount, 0); + Interlocked.Exchange(ref _currentErrorCount, 0); + } + public async Task InvokeAsync(HttpContext context, RequestDelegate next) { // TODO: 由于h3需要对应请求的端口,所以这里需要动态设置 @@ -14,6 +34,18 @@ public class GatewayMiddleware : IMiddleware context.Response.Headers["Gateway-Version"] = GatewayOptions.Version; + // Gateway默认的请求不记录 + var record = context.Request.Path.Value?.StartsWith("/api/gateway/") == false; + + // 使用原子操作,防止并发问题 + if (record) + Interlocked.Increment(ref _currentRequestCount); + await next(context); + + if (record && context.Response.StatusCode >= 400) + { + Interlocked.Increment(ref _currentErrorCount); + } } } \ No newline at end of file diff --git a/src/Gateway/Program.cs b/src/Gateway/Program.cs index c50e208b83790ae08aef69292170d1e60930e1c8..199f3c7382d6d2edadfa6bb4cd1de662e49c7222 100644 --- a/src/Gateway/Program.cs +++ b/src/Gateway/Program.cs @@ -28,8 +28,8 @@ builder.Configuration.GetSection(GatewayOptions.Name) .Get(); // 获取环境变量 -var https_password = Environment.GetEnvironmentVariable("HTTPS_PASSWORD") ?? "dd666666"; -var https_file = Environment.GetEnvironmentVariable("HTTPS_FILE") ?? "gateway.pfx"; +var httpsPassword = Environment.GetEnvironmentVariable("HTTPS_PASSWORD") ?? "dd666666"; +var httpsFile = Environment.GetEnvironmentVariable("HTTPS_FILE") ?? "gateway.pfx"; builder.WebHost.UseKestrel(options => { @@ -43,8 +43,8 @@ builder.WebHost.UseKestrel(options => !CertificateService.CertificateEntityDict.TryGetValue(name, out var certificate)) { // 创建一个默认的证书 - return new X509Certificate2(Path.Combine(AppContext.BaseDirectory, "certificates", https_file), - https_password); + return new X509Certificate2(Path.Combine(AppContext.BaseDirectory, "certificates", httpsFile), + httpsPassword); } var path = Path.Combine("/data/", certificate.Path); @@ -55,8 +55,8 @@ builder.WebHost.UseKestrel(options => Console.WriteLine($"证书文件不存在:{path}"); Console.ResetColor(); - return new X509Certificate2(Path.Combine(AppContext.BaseDirectory, "certificates", https_file), - https_password); + return new X509Certificate2(Path.Combine(AppContext.BaseDirectory, "certificates", httpsFile), + httpsPassword); }; }); }); @@ -69,6 +69,7 @@ builder.WebHost.ConfigureKestrel(kestrel => { portOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; portOptions.UseHttps(); + }); kestrel.ListenAnyIP(8080, portOptions => { portOptions.Protocols = HttpProtocols.Http1AndHttp2; }); diff --git a/src/Gateway/Services/SystemService.cs b/src/Gateway/Services/SystemService.cs index 830edf9eac7966cfa5316270cefb9c0cf191182c..ad2cea30c7f56ff6804b715975191e8eaaa133df 100644 --- a/src/Gateway/Services/SystemService.cs +++ b/src/Gateway/Services/SystemService.cs @@ -1,14 +1,20 @@ namespace Gateway.Services; -public class SystemService +/// +/// 系统服务 +/// +public static class SystemService { - public static async Task StreamAsync(HttpContext context) + public static async Task StreamAsync(HttpContext context, IFreeSql sql) { // 使用sse,返回响应头 context.Response.Headers.ContentType = "text/event-stream"; var i = 0; + var totalErrorCount = (double)await sql.Select().SumAsync(x => x.ErrorRequestCount); + var totalRequestCount = (double)await sql.Select().SumAsync(x => x.RequestCount); + while (!context.RequestAborted.IsCancellationRequested) { // 获取所有网络接口 @@ -27,7 +33,7 @@ public class SystemService initialBytesSent += interfaceStats.BytesSent; initialBytesReceived += interfaceStats.BytesReceived; } - + // 等待1秒钟 await Task.Delay(1000, context.RequestAborted); @@ -50,7 +56,11 @@ public class SystemService var totalBytesReceivedIn1Sec = bytesReceivedAfter1Sec - initialBytesReceived; var data = - $"data:{JsonSerializer.Serialize(new NetWorkDto(totalBytesReceivedIn1Sec, totalBytesSentIn1Sec))}\n\n"; + $"data:{JsonSerializer.Serialize(new NetWorkDto(totalBytesReceivedIn1Sec, totalBytesSentIn1Sec) + { + TotalErrorCount = totalErrorCount, + TotalRequestCount = totalRequestCount + })}\n\n"; // 将数据写入到响应流中 await context.Response.WriteAsync(data, context.RequestAborted); @@ -58,8 +68,8 @@ public class SystemService i++; - // 只维持5秒的连接 - if (i > 5) + // 只维持10秒的连接 + if (i > 10) { break; } @@ -71,7 +81,7 @@ public static class SystemExtension { public static void MapSystem(this IEndpointRouteBuilder app) { - app.MapGet("/api/gateway/system", async context => - await SystemService.StreamAsync(context)); + app.MapGet("/api/gateway/system", async (HttpContext context, IFreeSql sql) => + await SystemService.StreamAsync(context, sql)); } } \ No newline at end of file diff --git a/web/src/pages/home/index.tsx b/web/src/pages/home/index.tsx index 8f4a4a8cf61cac41c3b53f7074015e3a56082654..88de3ba913a6864c5a1c39ed094aeecf0846ab03 100644 --- a/web/src/pages/home/index.tsx +++ b/web/src/pages/home/index.tsx @@ -1,9 +1,14 @@ import { useEffect, useState } from "react"; -import { Col, Row } from '@douyinfe/semi-ui'; +import { Card, Col, Row } from '@douyinfe/semi-ui'; import { stream } from '../../service/NetWorkService'; import * as echarts from 'echarts'; export default function Home() { + const [totalRequestCount, setTotalRequestCount] = useState(0); + const [totalErrorCount, setTotalErrorCount] = useState(0); + const [currentRequestCount, setCurrentRequestCount] = useState(0); + const [currentErrorCount, setCurrentErrorCount] = useState(0); + useEffect(() => { var chartDom = document.getElementById('network')!; var myChart = echarts.init(chartDom); @@ -124,6 +129,12 @@ export default function Home() { option.xAxis.data.push(chunk.Time); option.series[0].data.push(chunk.Sent); option.series[1].data.push(chunk.Received); + + setCurrentErrorCount(chunk.CurrentErrorCount); + setCurrentRequestCount(chunk.CurrentRequestCount); + setTotalErrorCount(chunk.TotalErrorCount); + setTotalRequestCount(chunk.TotalRequestCount); + option && myChart.setOption(option); myChart.resize(); @@ -137,7 +148,7 @@ export default function Home() { fetchData(); // 设置定时器,每隔一段时间调用获取数据的函数 - const intervalId = setInterval(fetchData, 5900); // 每5秒获取一次数据 + const intervalId = setInterval(fetchData, 10900); // 每5秒获取一次数据 // 组件卸载时清除定时器 return () => { @@ -158,6 +169,28 @@ export default function Home() { }}> 数据面板 + + + + 总请求数:{totalRequestCount} + + + + + 总错误数:{totalErrorCount} + + + + + 当天请求数:{currentRequestCount} + + + + + 当天错误数:{currentErrorCount} + + +