This commit is contained in:
zenghongyao 2025-04-23 15:11:27 +08:00
commit fff1ba1a7b
12 changed files with 337 additions and 247 deletions

View File

@ -24,7 +24,6 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts
{
_logger = logger;
_protocolInfoRepository = serviceProvider.GetRequiredService<IRepository<ProtocolInfo, Guid>>();
}

View File

@ -1,5 +1,8 @@
namespace JiShe.CollectBus.Host
{
/// <summary>
/// CollectBusHostConst
/// </summary>
public static class CollectBusHostConst
{
/// <summary>
@ -23,9 +26,15 @@
public const string HangfireDashboardEndPoint = "/hangfire";
/// <summary>
/// CAP 端点
/// 健康检查 端点
/// </summary>
public const string CapDashboardEndPoint = "/cap";
public const string HealthEndPoint = "/health";
/// <summary>
/// 健康检查 端点
/// </summary>
public const string HealthDashboardEndPoint = "/health-ui";
}
}

View File

@ -2,6 +2,7 @@
using Hangfire;
using Hangfire.Redis.StackExchange;
using JiShe.CollectBus.Host.Hangfire;
using JiShe.CollectBus.Host.HealthChecks;
using JiShe.CollectBus.Host.Swaggers;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.DataProtection;
@ -16,6 +17,9 @@ using Volo.Abp.Modularity;
using TouchSocket.Core;
using TouchSocket.Sockets;
using JiShe.CollectBus.Plugins;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using JiShe.CollectBus.Cassandra;
namespace JiShe.CollectBus.Host
@ -206,7 +210,6 @@ namespace JiShe.CollectBus.Host
private void ConfigureCustom(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
context.Services.AddHealthChecks();
}
/// <summary>
@ -241,5 +244,37 @@ namespace JiShe.CollectBus.Host
.SetUdpDataHandlingAdapter(() => new NormalUdpDataHandlingAdapter());
});
}
/// <summary>
/// 健康检查
/// </summary>
/// <param name="context"></param>
/// <param name="configuration"></param>
private void ConfigureHealthChecks(ServiceConfigurationContext context, IConfiguration configuration)
{
if (!configuration.GetValue<bool>("HealthChecks:IsEnable")) return;
var cassandraConfig = new CassandraConfig();
configuration.GetSection("Cassandra").Bind(cassandraConfig);
context.Services.AddHealthChecks()
.AddMongoDb(configuration.GetConnectionString("Default"), "MongoDB", HealthStatus.Unhealthy)
.AddRedis(configuration.GetValue<string>("Redis:Configuration") ?? string.Empty, "Redis",
HealthStatus.Unhealthy)
.AddKafka(new Confluent.Kafka.ProducerConfig
{
BootstrapServers = configuration.GetConnectionString("Kafka")
}, "Kafka", failureStatus: HealthStatus.Unhealthy)
.AddCheck<CassandraHealthCheck>("Cassandra")
.AddCheck<IoTdbHealthCheck>("IoTDB");
context.Services
.AddHealthChecksUI(options =>
{
options.AddHealthCheckEndpoint("JiSheCollectBus", "/health"); // 映射本地端点
})
.AddInMemoryStorage();
}
}
}

View File

@ -1,4 +1,5 @@
using Hangfire;
using HealthChecks.UI.Client;
using JiShe.CollectBus.Host.Extensions;
using JiShe.CollectBus.Host.HealthChecks;
using JiShe.CollectBus.Host.Swaggers;
@ -45,9 +46,9 @@ namespace JiShe.CollectBus.Host
ConfigureNetwork(context, configuration);
ConfigureJwtAuthentication(context, configuration);
ConfigureHangfire(context);
//ConfigureKafkaTopic(context, configuration);
ConfigureAuditLog(context);
ConfigureCustom(context, configuration);
ConfigureHealthChecks(context, configuration);
Configure<AbpClockOptions>(options => { options.Kind = DateTimeKind.Local; });
}
@ -89,11 +90,16 @@ namespace JiShe.CollectBus.Host
});
app.UseConfiguredEndpoints(endpoints =>
{
if (!configuration.GetValue<bool>("HealthChecks:IsEnable")) return;
endpoints.MapHealthChecks("/health", new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = HealthCheckResponse.Writer
});
endpoints.MapHealthChecksUI(options =>
{
options.UIPath = "/health-ui";
});
});
}
}

View File

@ -0,0 +1,57 @@
using Cassandra;
using JiShe.CollectBus.Cassandra;
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace JiShe.CollectBus.Host.HealthChecks
{
/// <summary>
/// CassandraHealthCheck
/// </summary>
/// <seealso cref="Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck" />
public class CassandraHealthCheck : IHealthCheck
{
private readonly IConfiguration _configuration;
/// <summary>
/// Initializes a new instance of the <see cref="CassandraHealthCheck"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
public CassandraHealthCheck(IConfiguration configuration)
{
_configuration = configuration;
}
/// <summary>
/// Runs the health check, returning the status of the component being checked.
/// </summary>
/// <param name="context">A context object associated with the current execution.</param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> that can be used to cancel the health check.</param>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task`1" /> that completes when the health check has finished, yielding the status of the component being checked.
/// </returns>
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
var cassandraConfig = new CassandraConfig();
_configuration.GetSection("Cassandra").Bind(cassandraConfig);
try
{
var clusterBuilder = Cluster.Builder();
foreach (var node in cassandraConfig.Nodes)
{
clusterBuilder.AddContactPoint(node.Host)
.WithPort(node.Port);
}
clusterBuilder.WithCredentials(cassandraConfig.Username, cassandraConfig.Password);
var cluster = clusterBuilder.Build();
using var session = await cluster.ConnectAsync();
var result = await Task.FromResult(session.Execute("SELECT release_version FROM system.local"));
var version = result.First().GetValue<string>("release_version");
return HealthCheckResult.Healthy($"Cassandra is healthy. Version: {version}");
}
catch (Exception ex)
{
return new HealthCheckResult(context.Registration.FailureStatus, $"Cassandra is unhealthy: {ex.Message}", ex);
}
}
}
}

View File

@ -0,0 +1,51 @@
using System.Net.Sockets;
using JiShe.CollectBus.Cassandra;
using JiShe.CollectBus.IoTDB.Interface;
using JiShe.CollectBus.IoTDB.Options;
using JiShe.CollectBus.IoTDB.Provider;
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace JiShe.CollectBus.Host.HealthChecks
{
/// <summary>
/// IoTDBHealthCheck
/// </summary>
/// <seealso cref="Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck" />
public class IoTdbHealthCheck : IHealthCheck
{
private readonly IConfiguration _configuration;
/// <summary>
/// Initializes a new instance of the <see cref="IoTdbHealthCheck"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
public IoTdbHealthCheck(IConfiguration configuration)
{
_configuration = configuration;
}
/// <summary>
/// Runs the health check, returning the status of the component being checked.
/// </summary>
/// <param name="context">A context object associated with the current execution.</param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> that can be used to cancel the health check.</param>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task`1" /> that completes when the health check has finished, yielding the status of the component being checked.
/// </returns>
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
var ioTDbOptions = new IoTDbOptions();
_configuration.GetSection("IoTDBOptions").Bind(ioTDbOptions);
var pool = new SessionPoolAdapter(ioTDbOptions);
await pool.OpenAsync();
return HealthCheckResult.Healthy($"IoTDB is healthy.");
}
catch (Exception ex)
{
return new HealthCheckResult(context.Registration.FailureStatus, $"IoTDB不健康: {ex.Message}", ex);
}
}
}
}

View File

@ -20,9 +20,14 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.Kafka" Version="9.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="8.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.Redis" Version="9.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI" Version="9.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="9.0.0" />
<PackageReference Include="Hangfire.Redis.StackExchange" Version="1.9.4" />
<PackageReference Include="MassTransit" Version="8.4.0" />
<PackageReference Include="MassTransit.Kafka" Version="8.4.0" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="9.0.0" />
<PackageReference Include="Serilog" Version="4.1.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />

View File

@ -11,59 +11,59 @@
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link href="libs/bootstrap/css/bootstrap.min.css" rel="stylesheet"/>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="libs/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
<title>后端服务</title>
</head>
<body>
<div class="container projects">
<div class="projects-header page-header">
<h2>后端服务列表</h2>
@* <p>这些项目或者是对Bootstrap进行了有益的补充或者是基于Bootstrap开发的</p> *@
</div>
<div class="row">
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="thumbnail" style="height: 180px">
<div class="container projects">
<div class="projects-header page-header">
<h2>后端服务列表</h2>
@* <p>这些项目或者是对Bootstrap进行了有益的补充或者是基于Bootstrap开发的</p> *@
</div>
<div class="row">
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="thumbnail" style="height: 180px">
<a href="@CollectBusHostConst.SwaggerUiEndPoint" target="_blank">
<img class="lazy" src="/images/swagger.png" width="300" height="150"/>
</a>
<div class="caption">
<h3>
<img class="lazy" src="/images/swagger.png" width="300" height="150" />
</a>
<div class="caption">
<h3>
<a href="@CollectBusHostConst.SwaggerUiEndPoint" target="_blank">SwaggerUI</a>
</h3>
</h3>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="thumbnail" style="height: 180px">
<a href="@CollectBusHostConst.HangfireDashboardEndPoint" target="_blank">
<img class="lazy" src="/images/hangfire.png" width="300" height="150"/>
</a>
<div class="caption">
<h3>
<a href="@CollectBusHostConst.HangfireDashboardEndPoint" target="_blank">Hangfire面板</a>
</h3>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="thumbnail" style="height: 180px">
<a href="@CollectBusHostConst.HangfireDashboardEndPoint" target="_blank">
<img class="lazy" src="/images/hangfire.png" width="300" height="150" />
</a>
<div class="caption">
<h3>
<a href="@CollectBusHostConst.HangfireDashboardEndPoint" target="_blank">Hangfire面板</a>
</h3>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="thumbnail" style="height: 180px">
<a href="@CollectBusHostConst.CapDashboardEndPoint" target="_blank">
<img class="lazy" src="/images/cap.png" width="300" height="150"/>
</a>
<div class="caption">
<h3>
<a href="@CollectBusHostConst.CapDashboardEndPoint" target="_blank">CAP</a>
</h3>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="thumbnail" style="height: 180px">
<a href="@CollectBusHostConst.HealthDashboardEndPoint" target="_blank">
<img class="lazy" src="/images/hangfire.png" width="300" height="150" />
</a>
<div class="caption">
<h3>
<a href="@CollectBusHostConst.HealthEndPoint" target="_blank">健康检查API</a> |
<a href="@CollectBusHostConst.HealthDashboardEndPoint" target="_blank">UI</a>
</h3>
</div>
</div>
</div>
</div>
@* <div class="col-sm-6 col-md-4 col-lg-3">
@* <div class="col-sm-6 col-md-4 col-lg-3">
<div class="thumbnail" style="height: 180px">
<a href="@CollectBusHostConst.MoreEndPoint" target="_blank">
<img class="lazy" src="/images/more.png" width="300" height="150"/>
@ -75,99 +75,113 @@
</div>
</div>
</div> *@
</div>
</div>
</div>
</body>
</html>
<style>
*:before,
*:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.container {
width: 1170px;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
.projects-header {
width: 60%;
text-align: center;
font-weight: 200;
display: block;
margin: 60px auto 40px !important;
}
.page-header {
padding-bottom: 9px;
margin: 40px auto;
border-bottom: 1px solid #eee;
}
.projects-header h2 {
font-size: 42px;
letter-spacing: -1px;
}
h2 {
margin-top: 20px;
margin-bottom: 10px;
font-weight: 500;
line-height: 1.1;
color: inherit;
/* text-align: center; */
}
p {
margin: 0 0 10px;
}
.row {
margin-right: -15px;
margin-left: -15px;
}
.col-lg-3 {
width: 25%;
}
.projects .thumbnail {
display: block;
margin-right: auto;
margin-left: auto;
text-align: center;
margin-bottom: 30px;
border-radius: 0;
}
.thumbnail {
display: block;
padding: 4px;
line-height: 1.42857143;
background-color: #fff;
border: 1px solid #ddd;
.transition(border 0.2s ease-in-out);
}
a {
color: #337ab7;
text-decoration: none;
background-color: transparent;
}
.projects .thumbnail img {
max-width: 100%;
height: auto;
}
.thumbnail a > img,
.thumbnail > img {
margin-right: auto;
margin-left: auto;
}
img {
vertical-align: middle;
}
/* .projects .thumbnail .caption {
overflow-y: hidden;
color: #555;
} */
*:before,
*:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.caption {
padding: 9px;
overflow-y: hidden;
color: #555;
}
.container {
width: 1170px;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
.projects-header {
width: 60%;
text-align: center;
font-weight: 200;
display: block;
margin: 60px auto 40px !important;
}
.page-header {
padding-bottom: 9px;
margin: 40px auto;
border-bottom: 1px solid #eee;
}
.projects-header h2 {
font-size: 42px;
letter-spacing: -1px;
}
h2 {
margin-top: 20px;
margin-bottom: 10px;
font-weight: 500;
line-height: 1.1;
color: inherit;
/* text-align: center; */
}
p {
margin: 0 0 10px;
}
.row {
margin-right: -15px;
margin-left: -15px;
}
.col-lg-3 {
width: 25%;
}
.projects .thumbnail {
display: block;
margin-right: auto;
margin-left: auto;
text-align: center;
margin-bottom: 30px;
border-radius: 0;
}
.thumbnail {
display: block;
padding: 4px;
line-height: 1.42857143;
background-color: #fff;
border: 1px solid #ddd;
.transition(border 0.2s ease-in-out);
}
a {
color: #337ab7;
text-decoration: none;
background-color: transparent;
}
.projects .thumbnail img {
max-width: 100%;
height: auto;
}
.thumbnail a > img,
.thumbnail > img {
margin-right: auto;
margin-left: auto;
}
img {
vertical-align: middle;
}
/* .projects .thumbnail .caption {
overflow-y: hidden;
color: #555;
} */
.caption {
padding: 9px;
overflow-y: hidden;
color: #555;
}
</style>

View File

@ -4,6 +4,7 @@ namespace JiShe.CollectBus.Host.Pages
{
public class Monitor : PageModel
{
public void OnGet()
{

View File

@ -1,12 +1,16 @@
using JiShe.CollectBus.Host;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Serilog;
using Volo.Abp.Modularity.PlugIns;
namespace JiShe.CollectBus.Host;
/// <summary>
/// Program
/// </summary>
public class Program
{
/// <summary>
///
/// Main
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
@ -19,47 +23,13 @@ public class Program
loggerConfiguration.ReadFrom.Configuration(context.Configuration);
})
.UseAutofac();
var configuration = builder.Configuration;
await builder.AddApplicationAsync<CollectBusHostModule>(options =>
{
// 加载插件,固定模式,可热插拔
options.PlugInSources.AddFolder(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins"));
options.PlugInSources.AddFolder((configuration["PlugInFolder"].IsNullOrWhiteSpace()? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins"): configuration["PlugInFolder"]) ?? string.Empty);
});
var app = builder.Build();
await app.InitializeApplicationAsync();
await app.RunAsync();
//await CreateHostBuilder(args).Build().RunAsync();
}
private static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseContentRoot(Directory.GetCurrentDirectory())
.UseSerilog((context, loggerConfiguration) =>
{
loggerConfiguration.ReadFrom.Configuration(context.Configuration);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseAutofac();
private static IHostBuilder CreateConsoleHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) => { ConfigureServices(services, hostContext); })
.UseAutofac()
.UseSerilog((context, loggerConfiguration) =>
{
loggerConfiguration.ReadFrom.Configuration(context.Configuration);
});
private static async Task ConfigureServices(IServiceCollection services, HostBuilderContext hostContext)
{
await services.AddApplicationAsync<CollectBusHostModule>();
}
}

View File

@ -1,53 +0,0 @@
using TouchSocket.Core;
using Volo.Abp.Modularity.PlugIns;
namespace JiShe.CollectBus.Host
{
/// <summary>
/// Startup
/// </summary>
public class Startup
{
private readonly IConfiguration _configuration;
/// <summary>
/// Initializes a new instance of the <see cref="Startup"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
public Startup(IConfiguration configuration)
{
_configuration = configuration;
}
/// <summary>
/// Configures the services.
/// </summary>
/// <param name="services">The services.</param>
public void ConfigureServices(IServiceCollection services)
{
services.AddApplication<CollectBusHostModule>(options =>
{
// 加载插件,固定模式,可热插拔
options.PlugInSources.AddFolder(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins"));
});
}
/// <summary>
/// Configures the specified application.
/// </summary>
/// <param name="app">The application.</param>
/// <param name="lifetime">The lifetime.</param>
public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime)
{
app.Use(async (context, next) =>
{
// 在请求处理之前调用 InitializeApplicationAsync
await app.InitializeApplicationAsync();
// 继续请求管道中的下一个中间件
await next();
});
}
}
}

View File

@ -51,16 +51,11 @@
"Issuer": "JiShe.CollectBus",
"ExpirationTime": 2
},
"HealthCheck": {
"HealthChecks": {
"IsEnable": true,
"MySql": {
"IsEnable": true
},
"Pings": {
"IsEnable": true,
"Host": "https://www.baidu.com/",
"TimeOut": 5000
}
"HealthCheckDatabaseName": "HealthChecks",
"EvaluationTimeInSeconds": 10,
"MinimumSecondsBetweenFailureNotifications": 60
},
"SwaggerConfig": [
{
@ -84,7 +79,7 @@
"SaslPassword": "lixiao1980",
"KafkaReplicationFactor": 3,
"NumPartitions": 30,
"ServerTagName": "JiSheCollectBus99",
"ServerTagName": "JiSheCollectBus30",
"FirstCollectionTime": "2025-04-22 16:07:00"
},
"IoTDBOptions": {
@ -144,5 +139,6 @@
"SerialConsistencyLevel": "Serial",
"DefaultIdempotence": true
}
}
},
"PlugInFolder": ""
}