.NET9 使用 OData 协议项目实战

.NET 中 ODate 协议介绍

OData(Open Data Protocol) 是一个开放的 Web 协议,用于查询和更新数据。在 .NET 生态系统中,OData 被广泛支持和使用。

主要特性
1. 统一的数据访问方式
  • 提供标准化的查询语法
  • 支持 CRUD 操作
  • 支持元数据描述
2. 查询能力

标准查询选项支持:

  • 过滤数据($filter
  • 排序($orderby
  • 分页($top, $skip
  • 投影,选择字段($select
  • 扩展关联数据($expand

.NET 9 增强功能:

  • 性能改进:查询执行和序列化性能优化
  • 更好的路由集成:与 ASP.NET Core 路由系统更紧密集成
  • 端点(Endpoint)路由支持:完全支持现代端点路由范式
3. 格式支持
  • JSON 格式(默认,推荐)
  • XML 格式
  • Atom 格式
版本支持

.NET 支持多个 OData 版本:

  • OData v4(当前最新版本,推荐使用)
  • OData v3(较旧版本)
安全考虑
  • 支持授权和认证
  • 查询复杂度限制
  • 防止拒绝服务攻击的机制
最佳实践
  1. 正确设置查询限制(SetMaxTop
  2. 使用 EnableQuery 特性控制查询行为
  3. 实现适当的错误处理
  4. 考虑使用 DTO 避免暴露内部模型
  5. 启用适当的缓存策略
优势
  1. 标准化:遵循开放标准,便于与其他系统集成
  2. 灵活性:客户端可以构建复杂的查询
  3. 性能优化:支持服务端(SSEef core 服务端评估)分页和投影字段选择(CSEef core 客户端评估)
  4. 工具支持Visual Studio 等工具提供良好支持

OData 遵循的国际标准:

  • 核心标准
### OASIS 标准
- **OData Version 4.0**: 由 OASIS 组织发布的开放标准
- **OData JSON Format Version 4.0**: 定义 JSON 格式的数据交换规范
- **OData Common Schema Definition Language (CSDL) Version 4.0**: 实体数据模型定义语言### ISO/IEC 标准
- **ISO/IEC 20802-1:2016**: Information technology - Open Data Protocol (OData) - Part 1: Core
- **ISO/IEC 20802-2:2016**: Information technology - Open Data Protocol (OData) - Part 2: URL Conventions
- **ISO/IEC 20802-3:2016**: Information technology - Open Data Protocol (OData) - Part 3: Common Schema Definition Language (CSDL)
  • 相关 Web 标准
### HTTP 标准
- **RFC 7231**: HTTP/1.1 Semantics and Content
- **RFC 7230-7237**: HTTP 协议系列标准
### URI 标准
- **RFC 3986**: Uniform Resource Identifier (URI): Generic Syntax
  • 数据格式标准
- **ECMA-404**: The JSON Data Interchange Format
- **RFC 7493**: The I-JSON Message Format
  • 其他相关标准
- **Atom Publishing Protocol (AtomPub)**: RFC 5023
- **OData Extension for Data Aggregation**: OASIS 标准扩展

这些标准确保了 OData 协议在全球范围内的互操作性和标准化实施。


使用场景
  • 构建 RESTful API
  • 数据分析和报表系统
  • 移动应用后端服务
  • 微服务间的数据交互

·OData 协议为 .NET 开发者提供了一种强大而标准化的方式来构建数据服务 API


实现方式

.csproj 项目添加 nuget 相关包:

<Project Sdk="Microsoft.NET.Sdk.Web"><PropertyGroup><TargetFramework>net9.0</TargetFramework><Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings></PropertyGroup><ItemGroup><PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" /><PackageReference Include="Microsoft.AspNetCore.OData" Version="9.3.2" /><PackageReference Include="Microsoft.OData.ModelBuilder" Version="2.0.0" /><PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.7" /><PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" /></ItemGroup></Project>

说明:此处使用 EF Core 内存模式,模拟 DB 数据库,并添加 OData 相关包。

目录结构

完整目录结构如下:

├─Controllers
├─Datatabase
│  ├─Entities
│  ├─Mappers
│  └─Repositories
├─Models
│  └─Dtos
├─Properties
└─Services
架构层次说明

这种实现保持了清晰的分层架构:

Controller 层:ProductsController
  • 负责处理 HTTP 请求和响应
  • 负责请求 DTO 的数据验证
  • 使用 OData 特性进行查询支持
  • 依赖服务层进行业务处理
Database 层:
  • 数据库相关的核心数据访问层
  • 包含实体、映射和仓储模式的实现
  • Database/Entities
    • 存放数据实体类,包含数据结构定义,通常对应数据库表结构
    • 表示业务领域中的核心数据对象
  • Database/Mappers
    • 数据映射器,负责 Entity(实体对象)与领域 DTO 之间的转换
  • Database/Repositories
    • 仓储模式实现,提供数据访问的抽象接口
    • 封装数据访问逻辑,提供 CRUD 操作
Service 层:ProductService
  • 实现业务逻辑
  • 协调多个仓储操作
  • 处理业务规则和验证
  • 依赖仓储层进行数据访问
Models 层:领域模型
  • 包含数据结构定义
  • 领域 DTO 模型
  • 业务领域层中间转换模型

这种架构模式的优势:

  • 关注点分离:每层职责明确
  • 可测试性:各层可以独立进行单元测试
  • 可维护性:修改某一层不会(最小化)影响其他层
  • 可扩展性:可以轻松添加新的业务逻辑或数据源
  • 复用性:服务和仓储可以在多个控制器中复用
详细示例
  • 创建数据库表实体模型
namespace ODataDemo.Database.Entities;/// <summary>
/// 分类领域实体
/// </summary>
public class Category
{public required string Id { get; set; }public required string Name { get; set; }public required string Description { get; set; }public DateTime CreatedDate { get; set; }public bool IsActive { get; set; }// 导航属性public ICollection<Product> Products { get; set; } = [];// 领域方法public void Activate() => IsActive = true;public void Deactivate(){IsActive = false;foreach (var product in Products){product.UpdateStock(-product.StockQuantity); // 清空库存}}public bool CanDelete() => !Products.Any(p => p.IsInStock());
}/// <summary>
/// 产品领域实体
/// </summary>
public sealed class Product
{public required string Id { get; set; }public required string Name { get; set; }public required string Description { get; set; }public decimal Price { get; set; }public int StockQuantity { get; set; }public DateTime CreatedDate { get; set; }public DateTime UpdatedDate { get; set; }// 导航属性public string CategoryId { get; set; } = string.Empty;public Category? Category { get; set; }// 领域方法public void UpdateStock(int quantity){if (quantity < 0 && Math.Abs(quantity) > StockQuantity){throw new InvalidOperationException("库存不足");}StockQuantity += quantity;UpdatedDate = DateTime.UtcNow;}public bool IsInStock() => StockQuantity > 0;public void ApplyDiscount(decimal discountPercentage){if (discountPercentage < 0 || discountPercentage > 100){throw new ArgumentException("折扣百分比必须在0-100之间");}Price = Price * (1 - discountPercentage / 100);UpdatedDate = DateTime.UtcNow;}
}
  • 创建 DTO 对象
using System.Text.Json.Serialization;namespace ODataDemo.Models.Dtos;//#######################################
// 分类数据传输对象,用于 OData API
//#######################################public class CategoryRequstDto
{[JsonPropertyName("name")]public required string Name { get; set; }[JsonPropertyName("description")]public required string Description { get; set; }[JsonPropertyName("is_active")]public bool IsActive { get; set; }
}public sealed class CategoryResponeDto : CategoryRequstDto
{[JsonPropertyName("id")]public required string Id { get; set; }[JsonPropertyName("created_date")]public DateTime CreatedDate { get; set; }[JsonPropertyName("product_count")]public int ProductCount { get; set; }
}//############################################
// 产品数据传输对象,用于 OData API
//############################################public class ProductRequstDto
{[JsonPropertyName("name")]public required string Name { get; set; }[JsonPropertyName("description")]public required string Description { get; set; }[JsonPropertyName("price")]public decimal Price { get; set; }[JsonPropertyName("stock_quantity")]public int StockQuantity { get; set; }[JsonPropertyName("category_id")]public string CategoryId { get; set; } = string.Empty;[JsonPropertyName("category_name")]public string CategoryName { get; set; } = string.Empty;[JsonPropertyName("is_in_stock")]public bool IsInStock { get; set; }
}public sealed class ProductResponeDto : ProductRequstDto
{[JsonPropertyName("id")]public required string Id { get; set; }[JsonPropertyName("created_date")]public DateTime CreatedDate { get; set; }[JsonPropertyName("updated_date")]public DateTime UpdatedDate { get; set; }
}
  • Entity 映射 DTO 处理
using ODataDemo.Database.Entities;
using ODataDemo.Models.Dtos;namespace ODataDemo.Database.Mappers;public static class CategoryMapper
{public static Category From(this CategoryRequstDto dto){return new Category(){Id = Guid.CreateVersion7().ToString(),Name = dto.Name,Description = dto.Description,CreatedDate = DateTime.UtcNow,IsActive = dto.IsActive,};}public static CategoryResponeDto ToModel(this Category entity){return new CategoryResponeDto{Id = entity.Id,Name = entity.Name,Description = entity.Description,IsActive = entity.IsActive,CreatedDate = entity.CreatedDate,ProductCount = entity.Products.Count,};}
}public static class ProductMapper
{public static Product From(this ProductRequstDto dto){return new Product(){Id = Guid.CreateVersion7().ToString(),Name = dto.Name,Description = dto.Description,Price = dto.Price,StockQuantity = dto.StockQuantity,CreatedDate = DateTime.UtcNow,UpdatedDate = DateTime.UtcNow,CategoryId = dto.CategoryId};}public static ProductResponeDto ToModel(this Product entity){return new ProductResponeDto{Id = entity.Id,Name = entity.Name,Description = entity.Description,Price = entity.Price,StockQuantity = entity.StockQuantity,CreatedDate = entity.CreatedDate,UpdatedDate = entity.UpdatedDate,CategoryId = entity.CategoryId,CategoryName = entity.Category?.Name ?? string.Empty,IsInStock = entity.IsInStock()};}
}
  • 定义仓储规范

说明:在仓储层,只出现数据库表对应的实体对象 Entity

using ODataDemo.Database.Entities;namespace ODataDemo.Database.Repositories;public interface IDataRepository
{#region ProductIQueryable<Product> GetProducts();Task<Product?> GetProductByIdAsync(string id);Task<Product> AddProductAsync(Product product);Task<Product> UpdateProductAsync(Product product);Task DeleteProductAsync(string id);Task<bool> ExistsProductAsync(string id);#endregion#region CategoryIQueryable<Category> GetCategorys();Task<Category?> GetCategoryByIdAsync(string id);Task<Category> AddCategoryAsync(Category category);Task<Category> UpdateCategoryAsync(Category category);Task DeleteCategoryAsync(string id);Task<bool> ExistsCategoryAsync(string id);#endregion
}
  • 定义服务规范

说明:服务层只出现 DTO 对象,在实现内部处理 EntityDTO 的转换。

using ODataDemo.Models.Dtos;namespace ODataDemo.Services;public interface ICategoryService
{IQueryable<CategoryResponeDto> GetAllCategories();Task<CategoryResponeDto?> GetCategoryByIdAsync(string id);Task<CategoryResponeDto> CreateCategoryAsync(CategoryRequstDto category);Task<CategoryResponeDto> UpdateCategoryAsync(string id, CategoryRequstDto category);Task DeleteCategoryAsync(string id);Task ActivateCategoryAsync(string id);Task DeactivateCategoryAsync(string id);
}public interface IProductService
{IQueryable<ProductResponeDto> GetProducts();Task<ProductResponeDto?> GetProductByIdAsync(string id);Task<ProductResponeDto> CreateProductAsync(ProductRequstDto dto);Task<ProductResponeDto> UpdateProductAsync(string id, ProductRequstDto dto);Task DeleteProductAsync(string id);Task ApplyDiscountAsync(string productId, decimal discountPercentage);Task UpdateStockAsync(string productId, int quantity);
}
  • 配置 EDM 模型

说明:EDM 配置是关键,更多信息请查看相关资料,篇幅有限不再详述。
相关资料:

  • 使用 OData 简化 EDM
using Microsoft.OData.Edm;
using Microsoft.OData.ModelBuilder;
using ODataDemo.Models.Dtos;namespace ODataDemo.Database;public static class EdmModelConfig
{// 配置 EDM 模型public static IEdmModel GetEdmModel(){var builder = new ODataConventionModelBuilder();// 只注册 DTO 类型var productDto = builder.EntityType<ProductResponeDto>();productDto.HasKey(p => p.Id);productDto.Property(p => p.Name);productDto.Property(p => p.Description);productDto.Property(p => p.Price);productDto.Property(p => p.StockQuantity);productDto.Property(p => p.CategoryId);productDto.Property(p => p.CreatedDate);productDto.Property(p => p.IsInStock);productDto.Property(p => p.CreatedDate);productDto.Property(p => p.UpdatedDate);var categoryDto = builder.EntityType<CategoryResponeDto>();categoryDto.HasKey(c => c.Id);categoryDto.Property(c => c.Name);categoryDto.Property(c => c.Description);categoryDto.Property(p => p.IsActive);categoryDto.Property(p => p.CreatedDate);categoryDto.Property(p => p.ProductCount);// 使用 DTO 创建实体集builder.EntitySet<ProductResponeDto>("Products");builder.EntitySet<CategoryResponeDto>("Categories");return builder.GetEdmModel();}
}
  • 配置 AppDbContext

说明:在此处添加一些初始化的种子数据。

using Microsoft.EntityFrameworkCore;
using ODataDemo.Database.Entities;namespace ODataDemo.Database;public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{public DbSet<Product> Products { get; set; }public DbSet<Category> Categories { get; set; }protected override void OnModelCreating(ModelBuilder modelBuilder){string id1 = Guid.CreateVersion7().ToString();string id2 = Guid.CreateVersion7().ToString();string id3 = Guid.CreateVersion7().ToString();// Seed datamodelBuilder.Entity<Category>().HasData(new Category { Id = id1, Name = "Electronics", Description = "Electronic devices" },new Category { Id = id2, Name = "Books", Description = "Books and literature" },new Category { Id = id3.ToString(), Name = "Clothing", Description = "Apparel and accessories" });modelBuilder.Entity<Product>().HasData(new Product { Id = Guid.CreateVersion7().ToString(), Name = "Laptop", Price = 1200.00m, StockQuantity = 50, CategoryId = id1, Description = "High-performance laptop" },new Product { Id = Guid.CreateVersion7().ToString(), Name = "Mouse", Price = 25.00m, StockQuantity = 100, CategoryId = id1, Description = "Wireless mouse" },new Product { Id = Guid.CreateVersion7().ToString(), Name = "Keyboard", Price = 75.00m, StockQuantity = 75, CategoryId = id1, Description = "Mechanical keyboard" },new Product { Id = Guid.CreateVersion7().ToString(), Name = "C# Programming Guide", Price = 45.00m, StockQuantity = 30, CategoryId = id2, Description = "Comprehensive C# guide" },new Product { Id = Guid.CreateVersion7().ToString(), Name = "T-Shirt", Price = 20.00m, StockQuantity = 200, CategoryId = id3, Description = "Cotton t-shirt" });base.OnModelCreating(modelBuilder);}
}

说明:添加两个 SwaggerOData 相关的处理

  • 支持 OData 查询参数显示
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Reflection;namespace ODataDemo.Filters;public class SwaggerDefaultValues : IOperationFilter
{public void Apply(OpenApiOperation operation, OperationFilterContext context){var apiDescription = context.ApiDescription;// 检查是否标记为过时var isDeprecated = context.MethodInfo.GetCustomAttribute<ObsoleteAttribute>() != null;if (isDeprecated){operation.Deprecated = true;}// 添加默认响应if (operation.Parameters == null){operation.Parameters = [];}// 为每个参数添加默认值和描述foreach (var parameter in operation.Parameters){var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);parameter.Description ??= description.ModelMetadata?.Description;if (parameter.Schema.Default == null &&description.DefaultValue != null &&description.DefaultValue is not DBNull &&description.ModelMetadata?.ModelType != null){var json = System.Text.Json.JsonSerializer.Serialize(description.DefaultValue);parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json);}parameter.Required |= description.IsRequired;}// 为 OData 操作添加通用参数说明if (context.ApiDescription.HttpMethod == "GET"){AddODataQueryParameters(operation);}}private void AddODataQueryParameters(OpenApiOperation operation){var odataParameters = new List<(string name, string description)>{("$filter", "Filters the results based on a Boolean condition"),("$orderby", "Sorts the results"),("$top", "Returns only the first n results"),("$skip", "Skips the first n results"),("$select", "Selects which properties to include in the response"),("$expand", "Expands related entities inline"),("$count", "Includes a count of the total number of items")};foreach (var (name, description) in odataParameters){if (!operation.Parameters.Any(p => p.Name == name)){operation.Parameters.Add(new OpenApiParameter{Name = name,In = ParameterLocation.Query,Description = description,Schema = new OpenApiSchema{Type = "string"},Required = false});}}}
}
  • 处理 OData 特定类型的序列化问题
using Microsoft.AspNetCore.OData.Deltas;
using Microsoft.AspNetCore.OData.Results;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;namespace ODataDemo.Filters;public class ODataSchemaFilter : ISchemaFilter
{public void Apply(OpenApiSchema schema, SchemaFilterContext context){// 处理 OData 特定类型的序列化问题if (context.Type.IsGenericType &&(context.Type.GetGenericTypeDefinition() == typeof(SingleResult<>) ||context.Type.GetGenericTypeDefinition() == typeof(Delta<>))){// 对于 SingleResult<T> 和 Delta<T>,使用泛型参数类型var genericType = context.Type.GetGenericArguments()[0];// 可以根据需要自定义 schema}}
}

  • 控制器实现

注意:控制器继承 ODataController,没有 [ApiController][Route] 特性
//[Route(“api/[controller]”)]
//[ApiController]

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Deltas;
using Microsoft.AspNetCore.OData.Formatter;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Results;
using Microsoft.AspNetCore.OData.Routing.Controllers;
using ODataDemo.Models.Dtos;
using ODataDemo.Services;namespace ODataDemo.Controllers;public class ProductsController(IProductService productService) : ODataController
{// GET /odata/Products[EnableQuery]public IActionResult Get(){return Ok(productService.GetProducts());}// GET /odata/Products(1) - 使用标准命名和路由[EnableQuery]public SingleResult<ProductResponeDto> Get([FromODataUri] string key){var result = productService.GetProducts().Where(p => p.Id == key);return SingleResult.Create(result);}// POST /odata/Productspublic async Task<IActionResult> Post([FromBody] ProductRequstDto dto){if (!ModelState.IsValid){return BadRequest(ModelState);}var result = await productService.CreateProductAsync(dto);return Created(result);}// PUT /odata/Products(1)public async Task<IActionResult> Put([FromRoute] string key, [FromBody] ProductRequstDto dto){if (!ModelState.IsValid){return BadRequest(ModelState);}var result = await productService.UpdateProductAsync(key, dto);return Updated(result);}// PATCH /odata/Products(1)public async Task<IActionResult> Patch([FromRoute] string key, [FromBody] Delta<ProductRequstDto> delta){var product = await productService.GetProductByIdAsync(key);if (product == null){return NotFound();}delta.Patch(product);var result = await productService.UpdateProductAsync(key, product);return Updated(result);}// DELETE /odata/Products(1)public async Task<IActionResult> Delete([FromRoute] string key){await productService.DeleteProductAsync(key);return NoContent();}
}
  • Program.cs 代码示例:
using Microsoft.AspNetCore.OData;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using ODataDemo.Data.Repositories;
using ODataDemo.Database;
using ODataDemo.Database.Repositories;
using ODataDemo.Filters;
using ODataDemo.Services;var builder = WebApplication.CreateBuilder(args);// Add services to the container.// 添加 API 探索器(必需)
builder.Services.AddEndpointsApiExplorer();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
//builder.Services.AddOpenApi();// 添加 Swagger 生成器(必需)
builder.Services.AddSwaggerGen(options =>
{options.SwaggerDoc("v1", new OpenApiInfo{Version = "v1",Title = "OData API",Description = "An ASP.NET Core OData API"});// 支持 OData 查询参数显示options.OperationFilter<SwaggerDefaultValues>();// 处理 OData 类型序列化问题options.SchemaFilter<ODataSchemaFilter>();options.CustomSchemaIds(type => type.ToString());
});// 添加控制器和 OData 服务
builder.Services.AddControllers().AddOData(options =>{// 启用分页相关查询选项options.Select().Filter().OrderBy().Expand().Count().SetMaxTop(100); // 设置每页最大记录数// 使用 Convention-based 路由options.AddRouteComponents("odata", EdmModelConfig.GetEdmModel());// 关键配置:启用大小写不敏感options.RouteOptions.EnablePropertyNameCaseInsensitive = true;options.RouteOptions.EnableControllerNameCaseInsensitive = true;options.RouteOptions.EnableActionNameCaseInsensitive = true;});// 添加数据库上下文
builder.Services.AddDbContext<AppDbContext>(options =>options.UseInMemoryDatabase("ODataDemo"));// 注册db仓储服务
builder.Services.AddScoped<IDataRepository, DataRepository>();
// 注册业务服务
builder.Services.AddScoped<ICategoryService, CategoryService>();
builder.Services.AddScoped<IProductService, ProductService>();var app = builder.Build();// 初始化数据库
using (var scope = app.Services.CreateScope())
{var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();context.Database.EnsureCreated();
}// Configure the HTTP request pipeline.
// 启用路由
app.UseRouting();if (app.Environment.IsDevelopment())
{// 添加错误处理中间件app.Use(async (context, next) =>{try{await next();}catch (Exception ex){if (context.Request.Path.StartsWithSegments("/swagger")){Console.WriteLine("Swagger Error:");}Console.WriteLine($"Stack Trace: {ex.StackTrace}");await context.Response.WriteAsync(ex.Message);}});// 使用 swaggerapp.UseSwagger();app.UseSwaggerUI(options =>{options.SwaggerEndpoint("/swagger/v1/swagger.json", "OData API v1");options.RoutePrefix = "swagger";});
}app.UseAuthorization();
app.MapControllers();await app.RunAsync();

启动项目

启动项目,显示如下页面,其中包含两个接口,分别是产品和分类,另外还有一个源数据信息接口。

swagger-odata

使用 Apipost 工具,访问源数据接口:

  • http://localhost:5108/odata/

odata

其他接口类似,此处就不再详述。

最后附上完整示例截图,感兴趣的小伙伴欢迎点赞关注哟。

Odata-demo


总结

ASP.NET Core WebAPIOData 的集成提供了一种标准化、高性能的方式来构建数据驱动的 REST API,大大简化了复杂查询接口的开发工作。

可以增强扩展统一的数据响应格式,比如 ApiResponse,统一的异常数据格式等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/93302.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93302.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android 性能优化:提升应用启动速度(GC抑制)

前言 在移动应用开发领域&#xff0c;启动速度是用户体验的重要指标。对于Android应用而言&#xff0c;垃圾回收&#xff08;Garbage Collection, GC&#xff09;机制虽然是内存管理的核心&#xff0c;但在应用启动期间频繁触发GC会显著拖慢启动速度。本文将深入探讨如何通过GC…

做了一款小而美的本地校验器

需求说明 前阵子收到一则读者留言&#xff0c;指出&#xff1a;市面上AI核稿工具&#xff08;ProWritingAid&#xff0c;WPS AI Spell Check&#xff0c;Writer&#xff0c;QuillBot&#xff0c;Grammarly&#xff09;要么收费太高&#xff0c;要么让人担心文章泄露。 如下图所…

uniapp + uview-plus 微信小程序二维码生成和保存完整解决方案

uniapp + uview-plus 微信小程序二维码生成和保存完整解决方案 📋 项目背景 在开发微信小程序时,经常需要实现二维码的生成和保存功能。本文档提供了一个基于 uniapp + uview-plus 框架的完整解决方案,彻底解决了以下常见问题: ✅ Canvas API 兼容性问题 ✅ 微信小程序权…

Linux中应用程序的安装于管理

Linux中应用程序的安装于管理 一 . rpm安装 1.挂载 光驱里面存放了很多rpm的软件包 光驱在系统中使用时&#xff0c;需要挂载 mount /dev/cdrom /mnt/ cd /mnt[rootstw mnt]# ls CentOS_BuildTag GPL LiveOS RPM-GPG-KEY-CentOS-7 EFI images Packag…

mysql重置密码

要区分 MySQL 是通过 systemd 还是传统 service 管理&#xff0c;以及对应的密码重置方案&#xff0c;可按以下步骤操作&#xff1a; 一、如何区分管理方式&#xff08;systemd 还是传统 service&#xff09; 通过以下命令判断系统默认的服务管理方式&#xff1a;检查系统是否使…

C++ TAP(基于任务的异步编程模式)

&#x1f680; C TAP&#xff08;基于任务的异步编程模式&#xff09;1. 引言&#xff1a;走进异步编程新时代&#xff08;&#x1f680;&#xff09; 在当今高性能计算领域&#xff0c;同步编程模型的局限性日益凸显。传统的回调地狱和线程管理复杂性促使微软提出了基于任务的…

利用C++手撕栈与队列的基本功能(四)

栈和队列详细教程可以观看 https://www.bilibili.com/video/BV1nJ411V7bd?spm_id_from333.788.videopod.episodes&vd_sourcedaed5b8a51d3ab7eb209efa9d0ff9a34&p48栈和队列概念 栈和队列是限定插入和删除只能在表的端点进行的线性表在装电池、装弹夹、拿放盘子时都会出…

net8.0一键创建支持(Redis)

Necore项目生成器 - 在线创建Necore模板项目 | 一键下载 RedisController.cs using CSRedis; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using UnT.Template.Application.Responses; using UnT.Template.Domain;namespace UnT.Template.Controllers {…

Leetcode——42. 接雨水

还记得第一次见该题根本无从下手。其实&#xff0c;我们不妨把问题拆解&#xff0c;简单化。不要怕自己写的是暴力算法&#xff0c;有很多算法技巧其实就是在暴力算法的基础上优化得来。题目目的是求所有可接雨水数量&#xff0c;我们可以求出每一个位置可接雨水数量&#xff0…

Go 语言-->指针

Go 语言–>指针 它允许你操作内存中的实际数据&#xff0c;而不仅仅是数据的副本。指针存储的是另一个变量的内存地址&#xff0c;而不是变量的实际值。 1. 什么是指针 指针是存储变量内存地址的变量&#xff0c;它指向另一个变量。通过指针&#xff0c;你可以间接地访问和修…

软工八将:软件开发全流程核心角色体系解析

软工八将&#xff1a;软件开发全流程核心角色体系解析 作者注&#xff1a;本概念是由大学生董翔提出&#xff0c;具有一些影响意义。 在现代软件开发领域&#xff0c;团队角色的专业化分工是产品成功的核心保障。“软工八将”作为一套系统梳理软件开发全流程核心角色的术语&…

安全风险监测系统是什么?内容有哪些?

安全风险监测系统是基于物联网感知网络与智能分析技术的综合管理平台&#xff0c;通过实时采集、分析和评估各类安全风险指标&#xff0c;构建起覆盖识别、预警、处置全流程的主动防御体系。作为现代安全管理的中枢神经系统&#xff0c;该系统实现了从被动响应到主动预防的范式…

车载诊断架构 ---面向售后的DTC应该怎么样填写?

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…

墨者:SQL注入漏洞测试(宽字节)

墨者学院&#xff1a;SQL注入漏洞测试(宽字节)&#x1f680; 1. 宽字节注入原理✨ 1.1. 与普通注入对比⭐ 特性普通注入宽字节注入适用场景无转义处理使用addslashes()等转义函数核心原理直接闭合引号利用GBK等编码吞掉转义符\关键字符 " -- #%df %5c防御难度易防御需调…

(二)Eshop(RabbitMQ手动)

文章目录项目地址一、Rabbit MQ1.1 Pulibsher1. IRabbitMQPublisher接口2. RabbitMQPublisher接口实现3. 使用1.2 Consumer1. 消费接口2. 实现消费者接口项目地址 教程作者&#xff1a;教程地址&#xff1a; 代码仓库地址&#xff1a; 所用到的框架和插件&#xff1a; dbt a…

WPF高级学习(一)

文章目录一、理解进程和线程1. 进程&#xff1a;就像一个独立的“工厂”举例&#xff1a;2. 线程&#xff1a;就像工厂里的“工人”举例&#xff1a;总结&#xff1a;进程 vs 线程二、线程一、WPF 中的线程类型二、核心规则&#xff1a;线程亲和性&#xff08;Thread Affinity&…

JAVA知识点(四):SpringBoot与分布式、微服务架构

文章目录SpringBoot 使用 Validation 进行参数校验并统一返回校验异常引入相应的依赖Validation的基本校验注解添加参数校验在DTO的属性上添加校验在controller对应的DTO添加Valid或者Validated对于复杂String校验我们可以使用正则来校验&#xff0c;如下所示&#xff1a;自定义…

GPU 服务器ecc报错处理

1. 常见原因分析内存硬件问题&#xff1a;DIMM 内存模块损坏或接触不良&#xff08;最常见原因&#xff09;。内存插槽氧化、松动或物理损坏。内存与主板兼容性问题&#xff08;尤其是非原厂内存&#xff09;。环境因素&#xff1a;服务器内部温度过高&#xff0c;导致内存稳定…

STM32入门之通用定时器PWM

一、通用定时器简介STM32通用定时器由一个通过可编程预分频器驱动的16位自动重装载计数器组成&#xff0c;适用于多种应用场景&#xff0c;包括测量输入信号的脉冲长度&#xff08;利用输入捕获功能&#xff09;和生成输出波形&#xff08;使用输出比较及PWM功能&#xff09;。…

第十八节 MATLAB for循环

MATLAB中 for 循环是一个重复的控制结构&#xff0c;可以有效地写一个循环&#xff0c;只是执行的次数是特定的。MATLAB for 循环语法:MATLAB中的 for循环的语法如下&#xff1a;for index values<program statements>... endfor 循环的值有下述三种形式之一&#xff1a…