Add files via upload

This commit is contained in:
weiaiweiai
2025-09-21 16:38:23 +08:00
committed by GitHub
commit a60afec5a8
11 changed files with 847 additions and 0 deletions

531
UI.html Normal file
View File

@@ -0,0 +1,531 @@
<div id="wp-co-nd-host"></div>
<script>
(function () {
/* 核心配置项 */
const CFG = {
api: 'https://127.0.0.1/WeatherForecast/lastws',//请求数据地址
poll: 3000,
offlineSec: 30,
debug: true
};
/* 元素ID定义 */
const IDS = {
container: 'wp-co-nd-host',
wrapper: 'wp-co-nd-wrapper',
grid: 'wp-co-nd-grid',
status: 'wp-co-nd-status',
error: 'wp-co-nd-error',
warning: 'wp-co-nd-warning'
};
/* 状态管理变量 */
let servers = [];
let isLoading = false;
/* ===================== 工具函数 ===================== */
function getSafe(obj, path, defaultValue = null, logMissing = true) {
const result = path.split('.').reduce((acc, part) => {
if (acc === null || acc === undefined) return defaultValue;
return acc[part] !== undefined ? acc[part] : defaultValue;
}, obj);
if (result === defaultValue && logMissing && CFG.debug) {
console.log(`[服务器监控] 缺失字段: ${path}`);
}
return result;
}
function formatPercent(used, total) {
if (total <= 0) return "0.0%";
const pct = Math.min(100, Math.max(0, (used / total) * 100));
return pct.toFixed(1) + "%";
}
function formatBytesToGB(bytes) {
if (bytes === null || isNaN(bytes)) return "未知";
const gb = bytes / (1024 * 1024 * 1024);
return gb >= 1 ? `${gb.toFixed(2)}GB` : `${(gb * 1024).toFixed(1)}MB`;
}
function formatSpeed(bytes) {
if (bytes === null || isNaN(bytes)) return "未知";
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)}MB/s`;
if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)}KB/s`;
return `${bytes}B/s`;
}
function formatTraffic(bytes) {
if (bytes === null || isNaN(bytes)) return "未知";
const gb = bytes / (1024 * 1024 * 1024);
return gb >= 1 ? `${gb.toFixed(2)}GB` : `${(gb * 1024).toFixed(1)}MB`;
}
function formatUptime(seconds) {
if (seconds === null || isNaN(seconds)) return "未知";
if (seconds < 60) return `${seconds}`;
if (seconds < 3600) return `${Math.floor(seconds / 60)}分钟`;
if (seconds < 86400) return `${(seconds / 3600).toFixed(1)}小时`;
return `${(seconds / 86400).toFixed(1)}`;
}
function formatCountryCode(code) {
if (!code) return "未知地区";
const regionMap = { 'hk': '中国香港', 'cn': '中国大陆' };
return regionMap[code] || code;
}
function formatCpuInfo(cpuArr) {
return cpuArr && cpuArr.length > 0 ? cpuArr.join(' | ') : "未知CPU信息";
}
function getStatusColor(cpu) {
if (cpu === null || isNaN(cpu)) return "#a0aec0";
if (cpu > 85) return "#e53e3e";
if (cpu > 70) return "#ed8936";
if (cpu > 40) return "#ecc94b";
return "#48bb78";
}
function isServerOnline(server) {
const lastActive = getSafe(server, 'last_active');
if (!lastActive) return false;
const lastActiveTime = new Date(lastActive).getTime();
return (Date.now() - lastActiveTime) < CFG.offlineSec * 1000;
}
/* ===================== 样式注入 ===================== */
function injectStyles() {
const oldStyle = document.getElementById('wp-co-nd-styles');
if (oldStyle) oldStyle.remove();
const style = document.createElement('style');
style.id = 'wp-co-nd-styles';
style.textContent = `
#${IDS.wrapper} {
width: 100% !important;
box-sizing: border-box !important;
padding: 15px !important;
font-family: "Microsoft YaHei", Arial, sans-serif !important;
max-width: 1400px !important;
margin: 0 auto !important;
}
#${IDS.grid} {
display: grid !important;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)) !important;
gap: 18px !important;
margin-top: 18px !important;
}
.wp-co-server-card {
border: 1px solid #e2e8f0 !important;
border-radius: 10px !important;
padding: 18px !important;
box-shadow: 0 3px 8px rgba(0,0,0,0.06) !important;
transition: box-shadow 0.2s ease !important;
}
.wp-co-server-card:hover {
box-shadow: 0 5px 15px rgba(0,0,0,0.08) !important;
}
.wp-co-server-card.offline {
opacity: 0.72 !important;
filter: grayscale(0.8) !important;
}
.wp-co-status-indicator {
display: inline-block !important;
width: 12px !important;
height: 12px !important;
border-radius: 50% !important;
margin-right: 8px !important;
vertical-align: middle !important;
}
.wp-co-server-header {
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
margin-bottom: 15px !important;
}
.wp-co-server-name {
font-weight: 600 !important;
font-size: 16px !important;
}
.wp-co-server-region {
font-size: 12px !important;
padding: 3px 8px !important;
border-radius: 12px !important;
border: 1px solid #e2e8f0 !important;
}
.wp-co-cpu-container {
margin-bottom: 15px !important;
}
.wp-co-cpu-label {
display: flex !important;
justify-content: space-between !important;
font-size: 13px !important;
margin-bottom: 4px !important;
}
.wp-co-cpu-bar-wrap {
height: 6px !important;
border-radius: 3px !important;
overflow: hidden !important;
border: 1px solid #e2e8f0 !important;
}
.wp-co-cpu-bar {
height: 100% !important;
transition: width 1s ease !important;
}
.wp-co-section-title {
font-size: 14px !important;
font-weight: 500 !important;
margin: 12px 0 6px 0 !important;
padding-left: 4px !important;
border-left: 2px solid #4299e1 !important;
}
.wp-co-info-grid {
display: grid !important;
grid-template-columns: 1fr 1fr !important;
gap: 8px !important;
font-size: 13px !important;
}
.wp-co-info-item {
display: flex !important;
flex-direction: column !important;
}
.wp-co-info-label {
font-size: 12px !important;
margin-bottom: 2px !important;
opacity: 0.8 !important;
}
.wp-co-footer-stats {
display: flex !important;
justify-content: space-between !important;
margin-top: 12px !important;
font-size: 13px !important;
flex-wrap: wrap !important;
}
#${IDS.status} {
font-size: 13px !important;
display: flex !important;
align-items: center !important;
gap: 4px !important;
}
#${IDS.error}, #${IDS.warning} {
margin-top: 15px !important;
padding: 10px 12px !important;
border-radius: 6px !important;
font-size: 13px !important;
display: none !important;
align-items: center !important;
gap: 6px !important;
}
#${IDS.error} {
border: 1px solid #ffe3e3 !important;
}
#${IDS.warning} {
border: 1px solid #ffeaa7 !important;
}
.wp-co-header {
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
}
.wp-co-page-title {
font-size: 18px !important;
font-weight: 600 !important;
display: flex !important;
align-items: center !important;
gap: 8px !important;
}
.wp-co-unknown-data {
font-style: italic !important;
opacity: 0.7 !important;
}
`;
document.head.appendChild(style);
}
/* ===================== 结构创建 ===================== */
function createBaseStructure() {
const container = document.getElementById(IDS.container);
if (!container) {
console.error("[服务器监控] 容器元素不存在");
return false;
}
container.innerHTML = `
<div id="${IDS.wrapper}">
<div class="wp-co-header">
<div class="wp-co-page-title">
<i class="fa fa-server"></i>
服务器状态监控
</div>
<div id="${IDS.status}">
<i class="fa fa-refresh fa-spin"></i>
加载中...
</div>
</div>
<div id="${IDS.grid}">
<div style="grid-column: 1 / -1; text-align: center; padding: 40px 0; font-size: 14px;">
<i class="fa fa-spinner fa-spin" style="font-size: 20px; margin-bottom: 8px;"></i>
<p>正在获取服务器数据...</p>
</div>
</div>
<div id="${IDS.error}">
<i class="fa fa-exclamation-circle"></i>
<span>获取数据失败,请稍后重试</span>
</div>
<div id="${IDS.warning}">
<i class="fa fa-exclamation-triangle"></i>
<span>部分数据加载异常,详情请查看控制台</span>
</div>
</div>
`;
return true;
}
/* ===================== 提示控制函数 ===================== */
function showError(show) {
const errorEl = document.getElementById(IDS.error);
if (errorEl) errorEl.style.display = show ? 'flex' : 'none';
}
function showWarning(show) {
const warningEl = document.getElementById(IDS.warning);
if (warningEl) warningEl.style.display = show ? 'flex' : 'none';
}
/* ===================== 数据渲染 ===================== */
function renderServers() {
const grid = document.getElementById(IDS.grid);
const statusEl = document.getElementById(IDS.status);
if (!grid || !statusEl) return;
const now = new Date();
statusEl.innerHTML = `
<i class="fa fa-clock-o"></i>
最后更新: ${now.toLocaleTimeString()}
`;
let hasMissingData = false;
if (servers.length === 0) {
showError(true);
showWarning(false);
grid.innerHTML = `
<div style="grid-column: 1 / -1; text-align: center; padding: 40px 0; font-size: 14px;">
<i class="fa fa-folder-open-o" style="font-size: 20px; margin-bottom: 8px;"></i>
<p>暂无服务器数据</p>
</div>
`;
} else {
showError(false);
if (CFG.debug) console.log(`[服务器监控] 渲染 ${servers.length} 台服务器数据`);
grid.innerHTML = servers.map((server, index) => {
const serverName = getSafe(server, 'name', `未命名服务器 #${index + 1}`);
if (CFG.debug) console.log(`[服务器监控] 处理服务器: ${serverName}`);
const online = isServerOnline(server);
const cpuUsed = getSafe(server, 'state.cpu', 0);
const cpuUsage = parseFloat(formatPercent(cpuUsed, 100));
const memUsed = getSafe(server, 'state.mem_used', 0);
const memTotal = getSafe(server, 'host.mem_total', 0);
const memUsage = formatPercent(memUsed, memTotal);
const diskUsed = getSafe(server, 'state.disk_used', 0);
const diskTotalVal = getSafe(server, 'host.disk_total', 0);
const diskUsage = formatPercent(diskUsed, diskTotalVal);
const statusColor = getStatusColor(cpuUsage);
const os = getSafe(server, 'host.platform', "未知系统");
const cpu = formatCpuInfo(getSafe(server, 'host.cpu', []));
const arch = getSafe(server, 'host.arch', "未知架构");
const region = formatCountryCode(getSafe(server, 'country_code'));
const uptime = formatUptime(getSafe(server, 'state.uptime'));
const netIn = formatSpeed(getSafe(server, 'state.net_in_speed'));
const netOut = formatSpeed(getSafe(server, 'state.net_out_speed'));
const trafficIn = formatTraffic(getSafe(server, 'state.net_in_transfer'));
const trafficOut = formatTraffic(getSafe(server, 'state.net_out_transfer'));
const tcpConn = getSafe(server, 'state.tcp_conn_count', "未知");
const processCount = getSafe(server, 'state.process_count', "未知");
const serverHasMissingData = [
os === "未知系统",
cpu === "未知CPU信息",
arch === "未知架构",
tcpConn === "未知",
uptime === "未知",
processCount === "未知"
].some(Boolean);
if (serverHasMissingData) {
hasMissingData = true;
if (CFG.debug) console.log(`[服务器监控] 服务器 "${serverName}" 存在缺失数据`);
}
return `
<div class="wp-co-server-card ${online ? '' : 'offline'}">
<div class="wp-co-server-header">
<div class="wp-co-server-name">
<span class="wp-co-status-indicator" style="background-color: ${statusColor};"></span>
${serverName}
</div>
<div class="wp-co-server-region">${region}</div>
</div>
<div class="wp-co-cpu-container">
<div class="wp-co-cpu-label">
<span>CPU 使用率</span>
<span style="font-weight: 500;">${cpuUsage.toFixed(1)}%</span>
</div>
<div class="wp-co-cpu-bar-wrap">
<div class="wp-co-cpu-bar" style="width: ${cpuUsage}%; background-color: ${statusColor};"></div>
</div>
</div>
<div class="wp-co-section-title">系统配置</div>
<div class="wp-co-info-grid">
<div class="wp-co-info-item">
<div class="wp-co-info-label">操作系统</div>
<div>${os === "未知系统" ? `<span class="wp-co-unknown-data">${os}</span>` : os}</div>
</div>
<div class="wp-co-info-item">
<div class="wp-co-info-label">CPU信息</div>
<div>${cpu === "未知CPU信息" ? `<span class="wp-co-unknown-data">${cpu}</span>` : cpu}</div>
</div>
<div class="wp-co-info-item">
<div class="wp-co-info-label">架构</div>
<div>${arch === "未知架构" ? `<span class="wp-co-unknown-data">${arch}</span>` : arch}</div>
</div>
</div>
<div class="wp-co-section-title">实时性能</div>
<div class="wp-co-info-grid">
<div class="wp-co-info-item">
<div class="wp-co-info-label">内存使用率</div>
<div>${memUsage}</div>
</div>
<div class="wp-co-info-item">
<div class="wp-co-info-label">磁盘使用率</div>
<div>${diskUsage}</div>
</div>
<div class="wp-co-info-item">
<div class="wp-co-info-label">网络上传</div>
<div>${netOut} (${trafficOut})</div>
</div>
<div class="wp-co-info-item">
<div class="wp-co-info-label">网络下载</div>
<div>${netIn} (${trafficIn})</div>
</div>
</div>
<div class="wp-co-footer-stats">
<div>TCP连接: ${tcpConn === "未知" ? `<span class="wp-co-unknown-data">${tcpConn}</span>` : tcpConn}</div>
<div>进程数: ${processCount === "未知" ? `<span class="wp-co-unknown-data">${processCount}</span>` : processCount} | 运行: ${uptime === "未知" ? `<span class="wp-co-unknown-data">${uptime}</span>` : uptime}</div>
</div>
</div>
`;
}).join('');
showWarning(hasMissingData);
}
}
/* ===================== 数据请求 ===================== */
async function fetchServers() {
if (isLoading) return;
isLoading = true;
const statusEl = document.getElementById(IDS.status);
let hadError = false;
if (statusEl) {
statusEl.innerHTML = `
<i class="fa fa-refresh fa-spin"></i>
加载中...
`;
}
if (CFG.debug) console.log(`[服务器监控] 开始请求数据: ${CFG.api}`);
showError(false);
showWarning(false);
try {
const response = await fetch(CFG.api, { cache: 'no-store' });
if (!response.ok) {
hadError = true;
console.error(`[服务器监控] 请求失败: HTTP状态码 ${response.status}`);
if (servers.length === 0) throw new Error(`HTTP状态错误: ${response.status}`);
} else {
if (CFG.debug) console.log(`[服务器监控] 请求成功: ${response.status}`);
const rawData = await response.text();
let parsedData = null;
try {
parsedData = JSON.parse(rawData);
if (CFG.debug) console.log(`[服务器监控] 数据解析成功`);
} catch (e) {
hadError = true;
console.error(`[服务器监控] 数据解析失败:`, e);
if (servers.length === 0) throw new Error("数据解析失败");
}
if (parsedData) {
let newServers = [];
if (parsedData?.state && parsedData?.host) {
newServers = [parsedData];
} else if (parsedData?.servers) {
newServers = parsedData.servers;
} else if (parsedData?.list || parsedData?.data) {
newServers = parsedData.list || parsedData.data;
}
if (newServers.length > 0) {
servers = newServers;
hadError = false;
if (CFG.debug) console.log(`[服务器监控] 获取到 ${newServers.length} 台服务器数据`);
} else {
hadError = true;
console.log(`[服务器监控] 未获取到有效服务器数据`);
}
}
}
} catch (error) {
console.error(`[服务器监控] 数据请求错误:`, error);
hadError = true;
if (servers.length === 0) servers = [];
} finally {
renderServers();
isLoading = false;
if (CFG.debug) console.log(`[服务器监控] 数据处理完成`);
}
}
/* ===================== 初始化 ===================== */
function init() {
injectStyles();
if (!createBaseStructure()) {
setTimeout(init, 1000);
return;
}
fetchServers();
setInterval(fetchServers, CFG.poll);
}
init();
})();
</script>

30
WebApplication4.sln Normal file
View File

@@ -0,0 +1,30 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36414.22
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApplication4", "WebApplication4\WebApplication4.csproj", "{4414834D-B663-48A6-B4ED-97E81AD8D3FF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "解决方案项", "解决方案项", "{9FA3D6BD-1EC1-3BA5-80CB-CE02773A58D5}"
ProjectSection(SolutionItems) = preProject
UI.html = UI.html
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4414834D-B663-48A6-B4ED-97E81AD8D3FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4414834D-B663-48A6-B4ED-97E81AD8D3FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4414834D-B663-48A6-B4ED-97E81AD8D3FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4414834D-B663-48A6-B4ED-97E81AD8D3FF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E0942CCF-C02D-48D4-8861-7C9F9814D520}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using WebApplication4.Services;
namespace WebApplication4.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
private readonly WebSocketMessageStore _wsStore;
public WeatherForecastController(
ILogger<WeatherForecastController> logger,
WebSocketMessageStore wsStore)
{
_logger = logger;
_wsStore = wsStore;
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB> WebSocket <20><>Ϣ
[HttpGet("lastws")]
public IActionResult GetLastWebSocketMessage()
{
var (message, ts) = _wsStore.Get();
if (message == null)
{
return NotFound(new
{
success = false,
message = "<22><>δ<EFBFBD>յ<EFBFBD><D5B5>κ<EFBFBD> WebSocket <20><>Ϣ<EFBFBD><CFA2>"
});
}
return Ok(message);
}
}
}

View File

@@ -0,0 +1,34 @@
using WebApplication4.Services;
var builder = WebApplication.CreateBuilder(args);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
builder.Services.AddControllers();
// ȫ<><C8AB><EFBFBD>ſ<EFBFBD> CORS<52><53><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ã<EFBFBD><C3A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ս<EFBFBD><D5BD><EFBFBD>
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", policy =>
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
});
// WebSocket <20><><EFBFBD><EFBFBD>
builder.Services.AddSingleton<WebSocketMessageStore>();
builder.Services.AddHostedService<WebSocketClientBackgroundService>();
builder.Services.AddEndpointsApiExplorer();
// builder.Services.AddSwaggerGen(); // <20><><EFBFBD><EFBFBD><EFBFBD>ٴ<EFBFBD><D9B4><EFBFBD>
var app = builder.Build();
// <20><>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD> MapControllers ֮ǰ
app.UseCors("AllowAll");
// <20><><EFBFBD><EFBFBD><EFBFBD>м<EFBFBD><D0BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD>ɷ<EFBFBD><C9B7>ڴ˴<DAB4><CBB4><EFBFBD>
// app.UseHttpsRedirection();
app.MapControllers();
app.Run();

View File

@@ -0,0 +1,31 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:28450",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5020",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,109 @@
using System;
using System.IO;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace WebApplication4.Services
{
public class WebSocketClientBackgroundService : BackgroundService
{
private readonly ILogger<WebSocketClientBackgroundService> _logger;
private readonly WebSocketMessageStore _store;
// Ŀ<><C4BF> WebSocket <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ
private readonly Uri _uri = new("ws://127.0.0.1:8008/api/v1/ws/server");
// <20>ɵ<EFBFBD><C9B5><EFBFBD><EFBFBD><EFBFBD>
private readonly TimeSpan _reconnectDelay = TimeSpan.FromSeconds(5);
private const int ReceiveBufferSize = 8 * 1024;
public WebSocketClientBackgroundService(
ILogger<WebSocketClientBackgroundService> logger,
WebSocketMessageStore store)
{
_logger = logger;
_store = store;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("WebSocket <20><>̨<EFBFBD><CCA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, Ŀ<><C4BF>: {Url}", _uri);
while (!stoppingToken.IsCancellationRequested)
{
using var client = new ClientWebSocket();
try
{
client.Options.KeepAliveInterval = TimeSpan.FromSeconds(30);
_logger.LogInformation("<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> WebSocket...");
await client.ConnectAsync(_uri, stoppingToken);
_logger.LogInformation("WebSocket <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: {State}", client.State);
await ReceiveLoopAsync(client, stoppingToken);
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("ֹͣ<CDA3><D6B9><EFBFBD><EFBFBD><EFBFBD>յ<EFBFBD><D5B5><EFBFBD><EFBFBD><EFBFBD>ֹ WebSocket ѭ<><D1AD><EFBFBD><EFBFBD>");
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "WebSocket <20><><EFBFBD>ӻ<EFBFBD><D3BB><EFBFBD><EFBFBD>շ<EFBFBD><D5B7><EFBFBD><EFBFBD><EFBFBD><ECB3A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD> {Delay}s <20><><EFBFBD><EFBFBD><EFBFBD>ԡ<EFBFBD>",
_reconnectDelay.TotalSeconds);
}
if (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(_reconnectDelay, stoppingToken);
}
catch (OperationCanceledException) { }
}
}
_logger.LogInformation("WebSocket <20><>̨<EFBFBD><CCA8><EFBFBD><EFBFBD><EFBFBD>ѽ<EFBFBD><D1BD><EFBFBD><EFBFBD><EFBFBD>");
}
private async Task ReceiveLoopAsync(ClientWebSocket client, CancellationToken ct)
{
var buffer = new byte[ReceiveBufferSize];
while (!ct.IsCancellationRequested && client.State == WebSocketState.Open)
{
using var ms = new MemoryStream();
WebSocketReceiveResult? result;
do
{
var segment = new ArraySegment<byte>(buffer);
result = await client.ReceiveAsync(segment, ct);
if (result.MessageType == WebSocketMessageType.Close)
{
_logger.LogWarning("<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD>: {Desc}", result.CloseStatusDescription);
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", ct);
return;
}
ms.Write(buffer, 0, result.Count);
}
while (!result.EndOfMessage);
if (result.MessageType == WebSocketMessageType.Text)
{
var text = Encoding.UTF8.GetString(ms.ToArray());
_store.Set(text);
_logger.LogDebug("<22>յ<EFBFBD><D5B5>ı<EFBFBD><C4B1><EFBFBD>Ϣ<EFBFBD><CFA2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: {Len}", text.Length);
}
else if (result.MessageType == WebSocketMessageType.Binary)
{
_logger.LogDebug("<22><><EFBFBD>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ<EFBFBD><CFA2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: {Len}", ms.Length);
}
}
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Threading;
namespace WebApplication4.Services
{
public class WebSocketMessageStore
{
private string? _lastMessage;
private DateTime? _receivedAtUtc;
private readonly object _lock = new();
public void Set(string message)
{
if (message == null) return;
lock (_lock)
{
_lastMessage = message;
_receivedAtUtc = DateTime.UtcNow;
}
}
public (string? Message, DateTime? ReceivedAtUtc) Get()
{
lock (_lock)
{
return (_lastMessage, _receivedAtUtc);
}
}
}
}

View File

@@ -0,0 +1,13 @@
namespace WebApplication4
{
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}