使用.NET Core创建服务监视器应用程序
目录
介绍
背景
数据库
.NET核心解决方案
ServiceMonitor.Core
ServiceMonitor.Common
约定
观察者
ServiceMonitor.WebApi
仪表板
管理
ServiceMonitor
- GitHub存储库
介绍
本文介绍如何创建服务监视器应用程序,但它是什么?简单来说:它是一个允许监视网络中的服务并保存监视结果到数据库的应用程序,本例中为SQL Server。
我知道有很多工具可以提供这个功能,还有更好的工具,可以用钱买,但本文的意图是展示如何使用.NET核心能力来构建开发人员可以扩展以满足自定义要求的应用程序。
基本思想是这样的:有一个以无限方式运行的进程来监视主机,数据库和API; 将监控结果保存在SQL Server数据库中,然后我们可以为最终用户构建一个精美的UI并显示每个服务的状态,我们可以有很多目标进行监控,但最好是允许用户订阅特定服务而不是全部; 例如,DBA需要观察数据库服务器而不是API,开发人员需要观察开发数据库和API等。
还要考虑在开发室中安装大型显示器并观察服务状态,并且最好的情况是使用图表。:)
一个特殊功能可能是让一个通知服务在一个或多个服务失败的情况下为所有管理员发送消息,在这种情况下,服务意味着目标,如主机,数据库,API。
在本文中,我们将使用以下服务进行监控:
名称 |
描述 |
主机 |
Ping现有主机 |
数据库 |
打开并关闭现有数据库的连接 |
RESTful API |
从现有API中获取一个操作 |
背景
正如我们之前所说,我们将创建一个应用程序来监视现有目标(主机,数据库,API),因此我们需要掌握有关这些概念的基本知识。
主机将使用ping操作进行监控,因此我们将添加与网络相关的包以执行此操作。
数据库将通过开放和关闭连接进行监视,不使用集成安全性,因为您需要使用凭据模拟服务监视器进程,因此在这种情况下,最好让特定用户与数据库连接,并且只有这样才能避免黑客攻击。
RESTful API将使用REST客户端进行监视,以定位返回简单JSON的操作。
数据库
在存储库内部,有一个名为\ Resources \ Database的目录,该目录包含相关的数据库文件,请确保按以下顺序运行以下文件:
文件名 |
描述 |
00 - Database.sql |
数据库定义 |
01 - Tables.sql |
表定义 |
02 - Constraints.sql |
约束(主键,外键和唯一性) |
03 - Rows.sql |
初始数据 |
我们可以在这里找到数据库脚本。
表说明 |
|
表 |
描述 |
EnvironmentCategory |
包含环境的所有类别:开发,qa和生产 |
ServiceCategory |
包含服务的所有类别:数据库,rest API,服务器,URL和Web服务 |
Service |
包含所有服务定义 |
ServiceWatcher |
包含C#端的所有组件以执行监视操作 |
ServiceEnvironment |
包含服务和环境的关系,例如我们可以定义一个以不同环境命名的FinanceService服务:开发,qa和生产 |
ServiceEnvironmentStatus |
包含每个环境的每个服务的状态 |
ServiceEnvironmentStatusLog |
包含每个服务环境状态的详细信息 |
Owner |
包含代表所有所有者的应用程序的用户列表 |
ServiceOwner |
包含服务和所有者之间的关系 |
User |
包含观看服务的所有用户 |
ServiceUser |
包含服务和用户之间的关系 |
请不要忘记我们正在使用在本地计算机上运行的解决方案,资源目录中有一个示例API来执行测试,但您需要更改连接字符串并根据您的上下文添加服务。
另外我不建议在ServiceEnvironment表中公开真实的连接字符串,请向您的DBA请求单个用户只能对目标数据库执行打开连接,以防数据库的安全性成为您的任务,创建特定的用户来执行仅打开与数据库的连接并防止泄露敏感信息。
.NET核心解决方案
现在我们需要为此解决方案定义项目,以获得有关项目范围的清晰概念:
项目名 |
类型 |
描述 |
ServiceMonitor.Core |
类库 |
包含与数据库存储相关的所有定义 |
ServiceMonitor.Common |
类库 |
包含ServiceMonitor项目的常见定义,例如观察者,序列化器和客户端(REST) |
ServiceMonitor.WebApi |
Web API |
包含Web API控制器,用于读取和写入有关监视的信息 |
ServiceMonitor |
控制台应用 |
包含监控所有服务的过程 |
ServiceMonitor.Core
该项目包含实体和数据库访问的所有定义,因此我们需要为项目添加以下包:
名称 |
版 |
描述 |
Microsoft.EntityFrameworkCore.SqlServer |
最新版本 |
通过EF Core提供对SQL Server的访问 |
该项目包含三个层次:业务逻辑,数据库访问和实体。
DashboardService 类代码:
using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Core.BusinessLayer.Contracts;
using ServiceMonitor.Core.BusinessLayer.Responses;
using ServiceMonitor.Core.DataLayer;
using ServiceMonitor.Core.DataLayer.DataContracts;
using ServiceMonitor.Core.EntityLayer;namespace ServiceMonitor.Core.BusinessLayer
{public class DashboardService : Service, IDashboardService{public DashboardService(ILogger<DashboardService> logger, ServiceMonitorDbContext dbContext): base(logger, dbContext){}public async Task<IListResponse<ServiceWatcherItemDto>> GetActiveServiceWatcherItemsAsync(){Logger?.LogDebug("'{0}' has been invoked", nameof(GetActiveServiceWatcherItemsAsync));var response = new ListResponse<ServiceWatcherItemDto>();try{response.Model = await DbContext.GetActiveServiceWatcherItems().ToListAsync();Logger?.LogInformation("The service watch items were loaded successfully");}catch (Exception ex){response.SetError(Logger, nameof(GetActiveServiceWatcherItemsAsync), ex);}return response;}public async Task<IListResponse<ServiceStatusDetailDto>> GetServiceStatusesAsync(string userName){Logger?.LogDebug("'{0}' has been invoked", nameof(GetServiceStatusesAsync));var response = new ListResponse<ServiceStatusDetailDto>();try{var user = await DbContext.GetUserAsync(userName);if (user == null){Logger?.LogInformation("There isn't data for user '{0}'", userName);return new ListResponse<ServiceStatusDetailDto>();}else{response.Model = await DbContext.GetServiceStatuses(user).ToListAsync();Logger?.LogInformation("The service status details for '{0}' user were loaded successfully", userName);}}catch (Exception ex){response.SetError(Logger, nameof(GetServiceStatusesAsync), ex);}return response;}public async Task<ISingleResponse<ServiceEnvironmentStatus>> GetServiceStatusAsync(ServiceEnvironmentStatus entity){Logger?.LogDebug("'{0}' has been invoked", nameof(GetServiceStatusAsync));var response = new SingleResponse<ServiceEnvironmentStatus>();try{response.Model = await DbContext.GetServiceEnvironmentStatusAsync(entity);}catch (Exception ex){response.SetError(Logger, nameof(GetServiceStatusAsync), ex);}return response;}}
}
ServiceMonitor.Common
约定
- IWatcher
- IWatchResponse
- ISerializer
IWatcher 接口代码:
using System.Threading.Tasks;namespace ServiceMonitor.Common.Contracts
{public interface IWatcher{string ActionName { get; }Task<WatchResponse> WatchAsync(WatcherParameter parameter);}
}
IWatchResponse 接口代码:
namespace ServiceMonitor.Common.Contracts
{public interface IWatchResponse{bool Success { get; set; }string Message { get; set; }string StackTrace { get; set; }}
}
ISerializer 接口代码:
namespace ServiceMonitor.Common.Contracts
{public interface ISerializer{string Serialize<T>(T obj);T Deserialze<T>(string source);}
}
观察者
这些是实现:
- DatabaseWatcher
- HttpRequestWatcher
- PingWatcher
DatabaseWatcher 类代码:
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;
using ServiceMonitor.Common.Contracts;namespace ServiceMonitor.Common
{public class DatabaseWatcher : IWatcher{public string ActionName=> "OpenDatabaseConnection";public async Task<WatchResponse> WatchAsync(WatcherParameter parameter){var response = new WatchResponse();using (var connection = new SqlConnection(parameter.Values["ConnectionString"])){try{await connection.OpenAsync();response.Success = true;}catch (Exception ex){response.Success = false;response.Message = ex.Message;response.StackTrace = ex.ToString();}}return response;}}
}
HttpWebRequestWatcher 类代码:
using System;
using System.Threading.Tasks;
using ServiceMonitor.Common.Contracts;namespace ServiceMonitor.Common
{public class HttpRequestWatcher : IWatcher{public string ActionName=> "HttpRequest";public async Task<WatchResponse> WatchAsync(WatcherParameter parameter){var response = new WatchResponse();try{var restClient = new RestClient();await restClient.GetAsync(parameter.Values["Url"]);response.Success = true;}catch (Exception ex){response.Success = false;response.Message = ex.Message;response.StackTrace = ex.ToString();}return response;}}
}
PingWatcher 类代码:
using System.Net.NetworkInformation;
using System.Threading.Tasks;
using ServiceMonitor.Common.Contracts;namespace ServiceMonitor.Common
{public class PingWatcher : IWatcher{public string ActionName=> "Ping";public async Task<WatchResponse> WatchAsync(WatcherParameter parameter){var ping = new Ping();var reply = await ping.SendPingAsync(parameter.Values["Address"]);return new WatchResponse{Success = reply.Status == IPStatus.Success ? true : false};}}
}
ServiceMonitor.WebApi
这个项目代表服务监视器的RESTful API,所以我们将有两个控制器:DashboardController和AdministrationController。仪表板具有与最终用户结果相关的所有操作,管理包含与保存信息(创建,编辑和删除)相关的所有操作。
仪表板
DashboardController 类代码:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Core.BusinessLayer.Contracts;
using ServiceMonitor.WebApi.Responses;namespace ServiceMonitor.WebApi.Controllers
{
#pragma warning disable CS1591[Route("api/v1/[controller]")][ApiController]public class DashboardController : ControllerBase{protected ILogger Logger;protected IDashboardService Service;public DashboardController(ILogger<DashboardController> logger, IDashboardService service){Logger = logger;Service = service;}
#pragma warning restore CS1591/// <summary>/// Gets service watcher items (registered services to watch with service monitor)/// </summary>/// <returns>A sequence of services to watch</returns>[HttpGet("ServiceWatcherItem")][ProducesResponseType(200)][ProducesResponseType(204)][ProducesResponseType(500)]public async Task<IActionResult> GetServiceWatcherItemsAsync(){Logger?.LogDebug("'{0}' has been invoked", nameof(GetServiceWatcherItemsAsync));var response = await Service.GetActiveServiceWatcherItemsAsync();return response.ToHttpResponse();}/// <summary>/// Gets the details for service watch/// </summary>/// <param name="id">Service ID</param>/// <returns></returns>[HttpGet("ServiceStatusDetail/{id}")][ProducesResponseType(200)][ProducesResponseType(204)][ProducesResponseType(500)]public async Task<IActionResult> GetServiceStatusDetailsAsync(string id){Logger?.LogDebug("'{0}' has been invoked", nameof(GetServiceStatusDetailsAsync));var response = await Service.GetServiceStatusesAsync(id);return response.ToHttpResponse();}}
}
管理
AdministrationController 类代码:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Core.BusinessLayer.Contracts;
using ServiceMonitor.WebApi.Requests;
using ServiceMonitor.WebApi.Responses;namespace ServiceMonitor.WebApi.Controllers
{
#pragma warning disable CS1591[Route("api/v1/[controller]")][ApiController]public class AdministrationController : ControllerBase{protected ILogger Logger;protected IAdministrationService Service;public AdministrationController(ILogger<AdministrationController> logger, IAdministrationService service){Logger = logger;Service = service;}
#pragma warning restore CS1591/// <summary>/// Saves a result from service watch action/// </summary>/// <param name="request">Service status result</param>/// <returns>Ok if save it was successfully, Not found if service not exists else server internal error</returns>[HttpPost("ServiceEnvironmentStatusLog")][ProducesResponseType(200)][ProducesResponseType(404)][ProducesResponseType(500)]public async Task<IActionResult> PostServiceStatusLogAsync([FromBody]ServiceEnvironmentStatusLogRequest request){Logger?.LogDebug("'{0}' has been invoked", nameof(PostServiceStatusLogAsync));var response = await Service.CreateServiceEnvironmentStatusLogAsync(request.ToEntity(), request.ServiceEnvironmentID);return response.ToHttpResponse();}}
}
ServiceMonitor
这个项目包含Service Monitor Client的所有对象,在这个项目中,我们添加了Newtonsoft.Json用于JSON序列化的包,在ServiceMonitor.Common中有一个名称为ISerializer的接口,因为我不想强制使用特定的序列化程序,你可以改变它在这个层。:)
ServiceMonitorSerializer 类代码:
using Newtonsoft.Json;
using ServiceMonitor.Common.Contracts;namespace ServiceMonitor
{public class ServiceMonitorSerializer : ISerializer{public string Serialize<T>(T obj)=> JsonConvert.SerializeObject(obj);public T Deserialze<T>(string source)=> JsonConvert.DeserializeObject<T>(source);}
}
接下来,我们将开始MonitorController类,在这个类中,我们将执行所有观察操作,并通过Service Monitor API 中的AdministrationController将所有结果保存在数据库中。
MonitorController 类代码:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Common;
using ServiceMonitor.Common.Contracts;
using ServiceMonitor.Models;namespace ServiceMonitor
{public class MonitorController{public MonitorController(AppSettings appSettings, ILogger logger, IWatcher watcher, RestClient restClient){AppSettings = appSettings;Logger = logger;Watcher = watcher;RestClient = restClient;}public AppSettings AppSettings { get; }public ILogger Logger { get; }public IWatcher Watcher { get; }public RestClient RestClient { get; }public async Task ProcessAsync(ServiceWatchItem item){while (true){try{Logger?.LogTrace("{0} - Watching '{1}' for '{2}' environment", DateTime.Now, item.ServiceName, item.Environment);var watchResponse = await Watcher.WatchAsync(new WatcherParameter(item.ToDictionary()));if (watchResponse.Success)Logger?.LogInformation(" Success watch for '{0}' in '{1}' environment", item.ServiceName, item.Environment);elseLogger?.LogError(" Failed watch for '{0}' in '{1}' environment", item.ServiceName, item.Environment);var watchLog = new ServiceStatusLog{ServiceID = item.ServiceID,ServiceEnvironmentID = item.ServiceEnvironmentID,Target = item.ServiceName,ActionName = Watcher.ActionName,Success = watchResponse.Success,Message = watchResponse.Message,StackTrace = watchResponse.StackTrace};try{await RestClient.PostJsonAsync(AppSettings.ServiceStatusLogUrl, watchLog);}catch (Exception ex){Logger?.LogError(" Error on saving watch response ({0}): '{1}'", item.ServiceName, ex.Message);}}catch (Exception ex){Logger?.LogError(" Error watching service: '{0}': '{1}'", item.ServiceName, ex.Message);}Thread.Sleep(item.Interval ?? AppSettings.DelayTime);}}}
}
在运行控制台应用程序之前,请确保以下方面:
- ServiceMonitor 数据库可用
- ServiceMonitor 数据库具有服务类别,服务,服务观察者和用户的信息
- ServiceMonitor API可用
我们可以检查url api/v1/Dashboard/ServiceWatcherItems的返回值:
{ "message":null,"didError":false,"errorMessage":null,"model":[ { "serviceID":1,"serviceEnvironmentID":1,"environment":"Development","serviceName":"Northwind Database","interval":15000,"url":null,"address":null,"connectionString":"server=(local);database=Northwind;user id=johnd;password=SqlServer2017$","typeName":"ServiceMonitor.Common.DatabaseWatcher, ServiceMonitor.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"},{ "serviceID":2,"serviceEnvironmentID":3,"environment":"Development","serviceName":"DNS","interval":3000,"url":null,"address":"192.168.1.1","connectionString":null,"typeName":"ServiceMonitor.Common.PingWatcher, ServiceMonitor.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"},{ "serviceID":3,"serviceEnvironmentID":4,"environment":"Development","serviceName":"Sample API","interval":5000,"url":"http://localhost:5612/api/values","address":null,"connectionString":null,"typeName":"ServiceMonitor.Common.HttpWebRequestWatcher, ServiceMonitor.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"}]
}
正如我们所看到的,API为DefaultUser返回所有服务,请记住关于一个用户可以订阅多个服务的概念,显然在此示例中,我们的默认用户被绑定到所有服务但我们可以在ServiceUser表中更改此链接。
Program 类代码:
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Common;
using ServiceMonitor.Common.Contracts;namespace ServiceMonitor
{class Program{private static ILogger logger;private static readonly AppSettings appSettings;static Program(){logger = LoggingHelper.GetLogger<Program>();var builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");var configuration = builder.Build();appSettings = new AppSettings{ServiceWatcherItemsUrl = configuration["serviceWatcherItemUrl"],ServiceStatusLogUrl = configuration["serviceStatusLogUrl"],DelayTime = Convert.ToInt32(configuration["delayTime"])};}static void Main(string[] args){StartAsync(args).GetAwaiter().GetResult();Console.ReadLine();}static async Task StartAsync(string[] args){logger.LogDebug("Starting application...");var initializer = new ServiceMonitorInitializer(appSettings);try{await initializer.LoadResponseAsync();}catch (Exception ex){logger.LogError("Error on retrieve watch items: {0}", ex);return;}try{initializer.DeserializeResponse();}catch (Exception ex){logger.LogError("Error on deserializing object: {0}", ex);return;}foreach (var item in initializer.Response.Model){var watcherType = Type.GetType(item.TypeName, true);var watcherInstance = Activator.CreateInstance(watcherType) as IWatcher;var task = Task.Factory.StartNew(async () =>{var controller = new MonitorController(appSettings, logger, watcherInstance, initializer.RestClient);await controller.ProcessAsync(item);});}}}
}
一旦我们检查了之前的方面,现在我们继续转向控制台应用程序,控制台输出是这样的:
dbug: ServiceMonitor.Program[0]Starting application
sr trce: ServiceMonitor.Program[0]06/20/2017 23:09:30 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:30 - Watching 'Northwind Database' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:30 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:35 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:37 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:39 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:42 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:43 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:45 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:47 - Watching 'Northwind Database' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:48 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:48 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:51 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:53 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:54 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]06/20/2017 23:09:57 - Watching 'DNS' for 'Development' environment
现在我们继续检查数据库中保存的数据,请检查ServiceEnvironmentStatus表,你会得到这样的结果:
ServiceEnvironmentStatusID ServiceEnvironmentID Success WatchCount LastWatch
-------------------------- -------------------- ------- ----------- -----------------------
1 4 1 212 2018-11-22 23:11:34.113
2 1 1 78 2018-11-22 23:11:33.370
3 3 1 366 2018-11-22 23:11:34.620(3 row(s) affected)
它是如何一起工作的?控制台应用程序从API中监视所有服务,然后在MonitorController中以无限循环的方式为每个监视项启动一个任务,每个任务都有一个延迟时间,该间隔在服务定义中设置,但是如果没有为间隔定义值,间隔取自AppSettings; 因此,在执行Watch操作之后,结果将通过API保存在数据库中,并且该过程会自行重复。如果要watch对其他类型执行操作,可以创建自己的Watcher类。
原文地址:https://www.codeproject.com/Articles/1165961/Creating-Service-Monitor-Application-with-NET-Core
使用.NET Core创建服务监视器应用程序相关推荐
- ASP .NET Core Web MVC系列教程:使用ASP .NET Core创建MVC Web应用程序
本系列教程翻译自微软官方教程,官方教程地址:Get started with ASP.NET Core MVC | Microsoft Docs 本系列教程介绍了构建MVC Web应用程序的基础知识. ...
- ASP .NET Core Web MVC系列教程一:创建一个Web应用程序
系列文章目录:ASP .NET Core Web MVC系列教程:使用ASP .NET Core创建MVC Web应用程序 从Visual Studio中选择" 创建新项目". 选 ...
- 简化管理面向服务的应用程序的创建
控件可以让开发人员专注于编写应用程序逻辑和委派基础性架构问题,如异步消息传送.会话和与远程资源的连接.但仍有很多复杂问题使这些应用程序难以管理. 在本文中,我们将使用内建的Weblogic Works ...
- 使用Hot Chocolate创建ASP.NET Core GraphQL服务
GraphQL介绍 GraphQL是一个用于API的查询语言,是一个使用基于类型系统来执行查询的服务端运行时.GraphQL对你的API中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它 ...
- 使用.NET Core创建Windows服务 - 使用.NET Core工作器方式
原文:Creating Windows Services In .NET Core – Part 3 – The ".NET Core Worker" Way 作者:Dotnet ...
- 使用.NET Core创建Windows服务(二) - 使用Topshelf方式
原文:Creating Windows Services In .NET Core – Part 2 – The "Topshelf" Way 作者:Dotnet Core Tut ...
- 使用.NET Core创建Windows服务(二) - 使用Topshelf方式
使用.NET Core创建Windows服务 使用微软推荐方式 使用Topshelf方式 在前一篇文章中,我给大家介绍了,如何基于微软推荐方式使用.NET Core创建Windows服务.我们发现使用 ...
- 2022年8月10日:使用 ASP.NET Core 为初学者构建 Web 应用程序--使用 ASP.NET Core 创建 Web UI(没看懂需要再看一遍)
ASP.NET Core 支持使用名为 Razor 的本地模板化引擎创建网页. 本模块介绍了如何使用 Razor 和 ASP.NET Core 创建网页. 简介 通过在首选终端中运行以下命令,确保已安 ...
- ASP.NET Core Web Razor Pages系列教程一:使用ASP.NET Core 创建一个Razor Pages网络应用程序
系列文章目录:系列教程:使用ASP.NET Core创建Razor Pages Web应用程序 - zhangpeterx的博客 系列教程代码的GitHub地址:ASP .Net Core Razor ...
最新文章
- jakarta_适用于Java EE / Jakarta EE开发人员的Micronaut
- linux下socket上限,[100分]高分求关于linux socket上限解决方案
- 北斗导航 | RAIM:改进的最优加权平均解(IOWAS)算法(接收机自主完好性监测)
- Kotlin实战指南十四:协程启动模式
- android 内部类传值,Android Studio中,从内部类
- 我的Python成长之路---第六天---Python基础(19)---2016年2月20日(晴)
- openssl pkeyutl执行SM2椭圆曲线数字签名
- 雷蛇在天猫618大爆发,雷军第二天就找其创始人取经
- php 如何将100w级的数据入库。
- linux 检验md5命令,linux命令行校验工具md5sum
- android gps 获取方位_Android GPS定位 获取经纬度
- Win10 安装 Rational Rose 教程(带超详细图文)
- FB是磁珠的符号,磁珠有很高的电阻率和磁导率,他等效于电阻和电感串联
- 企业IT规划方法建议
- 元宇宙系统全面学习线路
- javascript学习笔记-基础语法篇
- 自编一个从指定位置开始查找字符串的Python代码
- 隐形眼镜的评价分析报告
- 电动汽车(EV)电池粘合剂市场现状及未来发展趋势
- module_param传递参数
热门文章
- c# 打开的窗口显示在最前面_了解各种切换程序窗口的方法,提高工作效率
- python程序执行时间毫秒_如何使用python解析包含毫秒的时间字符串?
- github 码云 获取_开发记录篇使用VS2019管理github项目
- 如果你需要品质背景素材,看过来
- 电商美工必看|Banner设计没灵感?传说中大气的画册风格给你灵感
- 个性潮流的设计PSD分层模板
- 冬季海报素材温暖上线,PSD分层设计师最爱
- 导航类网站|设计没有感觉,苦于寻找各种工具 或者资料的小伙伴收藏好哦
- java opencv 读取视频_java使用OpenCV从视频文件中获取帧
- Python自己写模块提供调用