.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
英文地址,請注明出處:http://en.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…