ASP.NET Core 最小 API:極簡開發,高效構建(下)

在上篇文章 ASP.NET Core 最小 API:極簡開發,高效構建(上) 中我們添加了 API 代碼并且測試,本篇繼續補充相關內容。

一、使用 MapGroup API

示例應用代碼每次設置終結點時都會重復 todoitems URL 前綴。 API 通常具有帶常見 URL 前綴的終結點組,并且 MapGroup 方法可用于幫助組織此類組。 它減少了重復代碼,并允許通過對 RequireAuthorizationWithMetadata 等方法的單一調用來自定義整個終結點組。

將 Program.cs 的內容替換為以下代碼:

using Microsoft.EntityFrameworkCore;
using NSwag.AspNetCore;
using TodoApi;var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{config.DocumentName = "TodoAPI";config.Title = "TodoAPI v1";config.Version = "v1";
});var app = builder.Build();if (app.Environment.IsDevelopment())
{app.UseOpenApi();app.UseSwaggerUi(config =>{config.DocumentTitle = "TodoAPI";config.Path = "/swagger";config.DocumentPath = "/swagger/{documentName}/swagger.json";config.DocExpansion = "list";});
}var todoItems = app.MapGroup("/todoitems");todoItems.MapGet("/", async (TodoDb db) =>await db.Todos.ToListAsync());todoItems.MapGet("/complete", async (TodoDb db) =>await db.Todos.Where(t => t.IsComplete).ToListAsync());todoItems.MapGet("/{id}", async (int id, TodoDb db) =>await db.Todos.FindAsync(id)is Todo todo? Results.Ok(todo): Results.NotFound());todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{db.Todos.Add(todo);await db.SaveChangesAsync();return Results.Created($"/todoitems/{todo.Id}", todo);
});todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{var todo = await db.Todos.FindAsync(id);if (todo is null) return Results.NotFound();todo.Name = inputTodo.Name;todo.IsComplete = inputTodo.IsComplete;await db.SaveChangesAsync();return Results.NoContent();
});todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{if (await db.Todos.FindAsync(id) is Todo todo){db.Todos.Remove(todo);await db.SaveChangesAsync();return Results.NoContent();}return Results.NotFound();
});app.Run();

前面的代碼執行以下更改:

  • 添加 var todoItems = app.MapGroup("/todoitems"); 以使用 URL 前綴 /todoitems 設置組。
  • 將所有 app.Map<HttpVerb> 方法更改為 todoItems.Map<HttpVerb>
  • /todoitems 方法調用中移除 URL 前綴 Map<HttpVerb>

二、使用 TypedResults API

返回 TypedResults(而不是 Results)有幾個優點,包括可測試性和自動返回 OpenAPI 的響應類型元數據來描述終結點。有關詳細信息,請參閱 TypedResults 與 Results。

使用以下代碼更新 Program.cs:

using Microsoft.EntityFrameworkCore;
using NSwag.AspNetCore;
using TodoApi;var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{config.DocumentName = "TodoAPI";config.Title = "TodoAPI v1";config.Version = "v1";
});var app = builder.Build();if (app.Environment.IsDevelopment())
{app.UseOpenApi();app.UseSwaggerUi(config =>{config.DocumentTitle = "TodoAPI";config.Path = "/swagger";config.DocumentPath = "/swagger/{documentName}/swagger.json";config.DocExpansion = "list";});
}var todoItems = app.MapGroup("/todoitems");todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);app.Run();static async Task<IResult> GetAllTodos(TodoDb db)
{return TypedResults.Ok(await db.Todos.ToArrayAsync());
}static async Task<IResult> GetCompleteTodos(TodoDb db)
{return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}static async Task<IResult> GetTodo(int id, TodoDb db)
{return await db.Todos.FindAsync(id)is Todo todo? TypedResults.Ok(todo): TypedResults.NotFound();
}static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{db.Todos.Add(todo);await db.SaveChangesAsync();return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{var todo = await db.Todos.FindAsync(id);if (todo is null) return TypedResults.NotFound();todo.Name = inputTodo.Name;todo.IsComplete = inputTodo.IsComplete;await db.SaveChangesAsync();return TypedResults.NoContent();
}static async Task<IResult> DeleteTodo(int id, TodoDb db)
{if (await db.Todos.FindAsync(id) is Todo todo){db.Todos.Remove(todo);await db.SaveChangesAsync();return TypedResults.NoContent();}return TypedResults.NotFound();
}

Map<HttpVerb> 代碼現在調用方法,而不是 lambda:

var todoItems = app.MapGroup("/todoitems");todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

這些方法返回實現 IResult 并由 TypedResults 定義的對象:

static async Task<IResult> GetAllTodos(TodoDb db)
{return TypedResults.Ok(await db.Todos.ToArrayAsync());
}static async Task<IResult> GetCompleteTodos(TodoDb db)
{return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}static async Task<IResult> GetTodo(int id, TodoDb db)
{return await db.Todos.FindAsync(id)is Todo todo? TypedResults.Ok(todo): TypedResults.NotFound();
}static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{db.Todos.Add(todo);await db.SaveChangesAsync();return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{var todo = await db.Todos.FindAsync(id);if (todo is null) return TypedResults.NotFound();todo.Name = inputTodo.Name;todo.IsComplete = inputTodo.IsComplete;await db.SaveChangesAsync();return TypedResults.NoContent();
}static async Task<IResult> DeleteTodo(int id, TodoDb db)
{if (await db.Todos.FindAsync(id) is Todo todo){db.Todos.Remove(todo);await db.SaveChangesAsync();return TypedResults.NoContent();}return TypedResults.NotFound();
}

三、防止過度發布

目前,示例應用公開了整個 Todo 對象。 在生產應用中,通常使用模型的一個子集來限制可以輸入和返回的數據。 這背后有多種原因,但安全性是主要原因。 模型的子集通常稱為數據傳輸對象 (DTO)、輸入模型或視圖模型。 本文使用的是 DTO。

DTO 可以用于:

  • 防止過度發布。
  • 隱藏客戶端不應查看的屬性。
  • 省略某些屬性以減少有效負載大小。
  • 平展包含嵌套對象的對象圖。 對客戶端而言,平展的對象圖可能更方便。

更新 Todo 類,使其包含機密字段:

public class Todo
{public int Id { get; set; }public string? Name { get; set; }public bool IsComplete { get; set; }public string? Secret { get; set; }
}

此應用需要隱藏機密字段,但管理應用可以選擇公開它。使用以下代碼創建名為 TodoItemDTO.cs 的文件:

public class TodoItemDTO
{public int Id { get; set; }public string? Name { get; set; }public bool IsComplete { get; set; }public TodoItemDTO() { }public TodoItemDTO(Todo todoItem) =>(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}

Program.cs 文件的內容替換為以下代碼以使用此 DTO 模型:

using Microsoft.EntityFrameworkCore;
using NSwag.AspNetCore;
using TodoApi;var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{config.DocumentName = "TodoAPI";config.Title = "TodoAPI v1";config.Version = "v1";
});var app = builder.Build();if (app.Environment.IsDevelopment())
{app.UseOpenApi();app.UseSwaggerUi(config =>{config.DocumentTitle = "TodoAPI";config.Path = "/swagger";config.DocumentPath = "/swagger/{documentName}/swagger.json";config.DocExpansion = "list";});
}app.MapGet("/todoitems", async (TodoDb db) =>await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>await db.Todos.FindAsync(id)is Todo todo? Results.Ok(new TodoItemDTO(todo)): Results.NotFound());app.MapPost("/todoitems", async (TodoItemDTO todoItemDTO, TodoDb db) =>
{var todoItem = new Todo{IsComplete = todoItemDTO.IsComplete,Name = todoItemDTO.Name};db.Todos.Add(todoItem);await db.SaveChangesAsync();return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});app.MapPut("/todoitems/{id}", async (int id, TodoItemDTO todoItemDTO, TodoDb db) =>
{var todo = await db.Todos.FindAsync(id);if (todo is null) return Results.NotFound();todo.Name = todoItemDTO.Name;todo.IsComplete = todoItemDTO.IsComplete;await db.SaveChangesAsync();return Results.NoContent();
});app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{if (await db.Todos.FindAsync(id) is Todo todo){db.Todos.Remove(todo);await db.SaveChangesAsync();return Results.NoContent();}return Results.NotFound();
});app.Run();

運行效果,

在這里插入圖片描述

在這里插入圖片描述

四、配置 JSON 序列化選項

1、全局配置 JSON 序列化選項

可通過調用 ConfigureHttpJsonOptions 來全局配置應用的選項。 以下示例包含公共字段,并設置 JSON 輸出的格式。

var builder = WebApplication.CreateBuilder(args);builder.Services.ConfigureHttpJsonOptions(options => {options.SerializerOptions.WriteIndented = true;options.SerializerOptions.IncludeFields = true;
});var app = builder.Build();app.MapPost("/", (Todo todo) => {if (todo is not null) {todo.Name = todo.NameField;}return todo;
});app.Run();class Todo {public string? Name { get; set; }public string? NameField;public bool IsComplete { get; set; }
}

運行效果:

在這里插入圖片描述

當請求報文為

{"name": "test","isComplete": true
}

則返回

{"name": null,"isComplete": true,"nameField": null
}

在這里插入圖片描述

當請求報文為

{"nameField": "Walk dog","isComplete": false
}

則返回

{"name": "Walk dog","isComplete": false,"nameField": "Walk dog"
}

2、為終結點配置 JSON 序列化選項

若要為終結點配置序列化選項,請調用 Results.Json 并向其傳遞 JsonSerializerOptions 對象,如以下示例所示:

using System.Text.Json;var app = WebApplication.Create();var options = new JsonSerializerOptions(JsonSerializerDefaults.Web){ WriteIndented = true };app.MapGet("/", () => Results.Json(new Todo { Name = "Walk dog", IsComplete = false }, options));app.Run();class Todo
{public string? Name { get; set; }public bool IsComplete { get; set; }
}

運行效果,

在這里插入圖片描述

或者,使用接受 JsonSerializerOptions 對象的 WriteAsJsonAsync 的重載。 以下示例使用此重載設置輸出 JSON 的格式:

using System.Text.Json;var app = WebApplication.Create();var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) {WriteIndented = true };app.MapGet("/", (HttpContext context) =>context.Response.WriteAsJsonAsync<Todo>(new Todo { Name = "Walk dog", IsComplete = false }, options));app.Run();class Todo
{public string? Name { get; set; }public bool IsComplete { get; set; }
}

運行效果:

在這里插入圖片描述

五、最小 API 中的身份驗證和授權

1、有關身份驗證和授權的關鍵概念

身份驗證是確定用戶標識的過程。 授權是確定用戶是否有權訪問資源的過程。 在 ASP.NET Core 中,身份驗證和授權方案都有類似的實現語義。 身份驗證由身份驗證服務 IAuthenticationService 負責,而它供身份驗證中間件使用。 授權由授權服務 IAuthorizationService 負責,而它供授權中間件使用。

身份驗證服務會使用已注冊的身份驗證處理程序來完成與身份驗證相關的操作。 例如,與身份驗證相關的操作是對用戶進行身份驗證或注銷用戶。 身份驗證方案是用于唯一地標識身份驗證處理程序及其配置選項的名稱。 身份驗證處理程序負責實現身份驗證策略,并在給定特定身份驗證策略(如 OAuth 或 OIDC)的情況下生成用戶的聲明。 配置選項也是策略獨有的,并為處理程序提供會影響身份驗證行為的配置,例如重定向 URI。

在授權層中,有兩種策略可用于確定用戶對資源的訪問權限:

  • 基于角色的策略根據所分配的角色(例如 Administrator 或 User)確定用戶的訪問權限。
  • 基于聲明的策略根據中央頒發機構頒發的聲明來確定用戶的訪問權限。

在 ASP.NET Core 中,這兩種策略都被捕獲到授權要求中。 授權服務利用授權處理程序來確定特定用戶是否滿足應用于資源的授權要求。

2、在最小應用中啟用身份驗證

若要啟用身份驗證,請調用 AddAuthentication 以對應用的服務提供商注冊所需的身份驗證服務。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication();
var app = builder.Build();app.MapGet("/", () => "Hello World!");
app.Run();

通常情況下,會使用一個特定的身份驗證策略。 在以下示例中,應用被配置為支持基于 JWT 持有者的身份驗證。 此示例使用 Microsoft.AspNetCore.Authentication.JwtBearer NuGet 包中提供的 API。

var builder = WebApplication.CreateBuilder(args);
// Requires Microsoft.AspNetCore.Authentication.JwtBearer
builder.Services.AddAuthentication().AddJwtBearer();
var app = builder.Build();app.MapGet("/", () => "Hello World!");
app.Run();

默認情況下,如果啟用了某些身份驗證和授權服務,WebApplication 會自動注冊身份驗證和授權中間件。 在以下示例中,無需調用 UseAuthenticationUseAuthorization 即可注冊中間件,因為在調用 WebApplicationAddAuthentication 后,AddAuthorization 會自動執行此操作。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
var app = builder.Build();app.MapGet("/", () => "Hello World!");
app.Run();

具體原理可以查閱最小 API 應用中的中間件。在某些情況(例如控制中間件順序)下,需要顯式注冊身份驗證和授權。 在以下示例中,身份驗證中間件是在 CORS 中間件運行后運行的。

var builder = WebApplication.CreateBuilder(args);builder.Services.AddCors();
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();var app = builder.Build();app.UseCors();
app.UseAuthentication();
app.UseAuthorization();app.MapGet("/", () => "Hello World!");
app.Run();

3、配置身份驗證策略

身份驗證策略通常支持通過選項加載的各種配置。 對于以下身份驗證策略,最小應用支持從配置加載選項:

  • 基于 JWT 持有者
  • 基于 OpenID 連接

ASP.NET Core 框架期望在Authentication:Schemes:{SchemeName}節的配置部分下找到這些選項。 在以下示例中,兩個不同的方案 BearerLocalAuthIssuer 是使用各自的選項定義的。 Authentication:DefaultScheme 選項可用于配置所使用的默認身份驗證策略。

{"Authentication": {"DefaultScheme":  "LocalAuthIssuer","Schemes": {"Bearer": {"ValidAudiences": ["https://localhost:7259","http://localhost:5259"],"ValidIssuer": "dotnet-user-jwts"},"LocalAuthIssuer": {"ValidAudiences": ["https://localhost:7259","http://localhost:5259"],"ValidIssuer": "local-auth"}}}
}

Program.cs 中,注冊了兩個基于 JWT 持有者的身份驗證策略,其中:

  • “Bearer”方案名稱。
  • “LocalAuthIssuer”方案名稱。

“Bearer”是支持基于 JWT 持有者的應用中的一個典型默認方案,但可以通過設置 DefaultScheme 屬性來替代默認方案,如前面的示例所示。

方案名稱用于唯一地標識身份驗證策略,并在從配置解析身份驗證選項時用作查找鍵,如以下示例所示:

var builder = WebApplication.CreateBuilder(args);builder.Services.AddAuthentication().AddJwtBearer().AddJwtBearer("LocalAuthIssuer");var app = builder.Build();app.MapGet("/", () => "Hello World!");
app.Run();

4、在最小應用中配置授權策略

身份驗證用于根據 API 識別和驗證用戶的標識。 授權用于驗證和證實對 API 中資源的訪問,并由通過 AddAuthorization 擴展方法注冊的 IAuthorizationService 提供便利。 在以下方案中,添加了 /hello 資源,該資源要求用戶提供一個 admin 角色聲明以及 greetings_api 范圍聲明。

配置資源上的授權要求的過程分為兩個步驟,需要:

  1. 全局配置策略中的授權要求。
  2. 將各個策略應用于資源。

在以下代碼中,將調用 AddAuthorizationBuilder,其作用是:

  • 將與授權相關的服務添加到 DI 容器。
  • 返回一個 AuthorizationBuilder,它可用于直接注冊身份驗證策略。

該代碼創建了一個名為 admin_greetings 的新授權策略,該策略封裝了兩個授權要求:

  • 一個通過 RequireRole 實現的基于角色的要求,面向具有 admin 角色的用戶。
  • 一個通過 RequireClaim 實現的基于聲明的要求,即用戶必須提供 greetings_api 范圍聲明。

admin_greetings 策略作為 /hello 終結點所需的策略提供。

using Microsoft.Identity.Web;var builder = WebApplication.CreateBuilder(args);builder.Services.AddAuthorizationBuilder().AddPolicy("admin_greetings", policy =>policy.RequireRole("admin").RequireClaim("scope", "greetings_api"));var app = builder.Build();app.MapGet("/hello", () => "Hello world!").RequireAuthorization("admin_greetings");app.Run();

若無權限,則直接返回 401 Unauthorized 錯誤。

在這里插入圖片描述

六、使用 dotnet user-jwts 進行開發測試

本文使用配置了基于 JWT 持有者的身份驗證的應用。 基于 JWT 令牌持有者的身份驗證要求客戶端在請求頭中呈現令牌,以驗證其身份和聲明。 通常,這些令牌由中央頒發機構頒發,例如標識服務器。

在本地計算機上進行開發時,dotnet user-jwts 工具可用于創建持有者令牌。

dotnet user-jwts create

注意

在項目上調用時,該工具會自動將與生成的令牌匹配的身份驗證選項添加到 appsettings.json

可以通過多種自定義方式配置令牌。 例如,若要為上述代碼中的授權策略所需的 admin 角色和 greetings_api 范圍創建令牌,請執行以下操作:

dotnet user-jwts create --scope "greetings_api" --role "admin"

然后,可以在所選的測試工具中將生成的令牌作為標頭的一部分發送。 舉個使用 curl 的例子:

curl -i -H "Authorization: Bearer {token}" http://localhost:{port}/hello

在這里插入圖片描述

(base) sam@sam-PC:/data/home/sam/MyWorkSpace/DotNetCoreWorkSpace/TodoApi$ dotnet user-jwts create --scope "greetings_api" --role "admin"
New JWT saved with ID 'b0fafeb4'.
Name: sam
Roles: [admin]
Scopes: greetings_apiToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InNhbSIsInN1YiI6InNhbSIsImp0aSI6ImIwZmFmZWI0Iiwic2NvcGUiOiJncmVldGluZ3NfYXBpIiwicm9sZSI6ImFkbWluIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzQ0MiIsImh0dHBzOi8vbG9jYWxob3N0OjQ0MzgwIiwiaHR0cDovL2xvY2FsaG9zdDo1MDI2IiwiaHR0cHM6Ly9sb2NhbGhvc3Q6NzE1MyJdLCJuYmYiOjE3NDUwNTcwNDcsImV4cCI6MTc1MjkxOTQ0NywiaWF0IjoxNzQ1MDU3MDQ4LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.Rwgp9wO9PCLwJEiVgN-CGwlnLaAu0jhQXuzeo8Wh6Zg
curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InNhbSIsInN1YiI6InNhbSIsImp0aSI6ImIwZmFmZWI0Iiwic2NvcGUiOiJncmVldGluZ3NfYXBpIiwicm9sZSI6ImFkbWluIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzQ0MiIsImh0dHBzOi8vbG9jYWxob3N0OjQ0MzgwIiwiaHR0cDovL2xvY2FsaG9zdDo1MDI2IiwiaHR0cHM6Ly9sb2NhbGhvc3Q6NzE1MyJdLCJuYmYiOjE3NDUwNTcwNDcsImV4cCI6MTc1MjkxOTQ0NywiaWF0IjoxNzQ1MDU3MDQ4LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.Rwgp9wO9PCLwJEiVgN-CGwlnLaAu0jhQXuzeo8Wh6Zg" http://localhost:5026/hello

在這里插入圖片描述

可以看到,攜帶 token 請求可以正常訪問 /hello 接口。完整代碼如下:

using Microsoft.Identity.Web;var builder = WebApplication.CreateBuilder(args);builder.Services.AddAuthorization();
builder.Services.AddAuthentication("Bearer").AddJwtBearer();builder.Services.AddAuthorizationBuilder().AddPolicy("admin_greetings", policy =>policy.RequireRole("admin").RequireClaim("scope", "greetings_api"));var app = builder.Build();app.UseAuthorization();app.MapGet("/hello", () => "Hello world!").RequireAuthorization("admin_greetings");app.Run();

參考文檔

  • 教程:使用 ASP.NET Core 創建最小 API

  • 最小 API 中的身份驗證和授權

  • 使用 dotnet user-jwts 管理開發中的 JSON Web 令牌

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/77686.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/77686.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/77686.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring之我見 - Spring Boot Starter 自動裝配原理

歡迎光臨小站&#xff1a;致橡樹 Spring Boot Starter 的核心設計理念是 約定優于配置&#xff0c;其核心實現基于 自動配置&#xff08;Auto-Configuration&#xff09; 和 條件化注冊&#xff08;Conditional Registration&#xff09;。以下是其生效原理&#xff1a; 約定…

精益數據分析(7/126):打破創業幻想,擁抱數據驅動

精益數據分析&#xff08;7/126&#xff09;&#xff1a;打破創業幻想&#xff0c;擁抱數據驅動 在創業的道路上&#xff0c;我們都懷揣著夢想&#xff0c;但往往容易陷入自我編織的幻想中。我希望通過和大家一起學習《精益數據分析》&#xff0c;能幫助我們更清醒地認識創業過…

牛客java練習題

[toc] 1.依賴注入 依賴注入是一種設計模式和編程思想,不依賴 具體的框架實現,可以通過多種方式和框架來實現可以通過Spring , Google Guice , PicoContainer 等都可以實現依賴注入,也可以通過手動編寫實現目的: 為了解耦合,將對象之間的依賴關系從代碼中解耦出來, 使系統更加…

大模型應用開發自學筆記

理論學習地址&#xff1a; https://zh.d2l.ai/chapter_linear-networks/index.html autodl學術加速&#xff1a; source /etc/network_turboconda常見操作: 刪除&#xff1a; conda remove --name myenv --all -y導出&#xff1a; conda env export > environment.yml…

鴻蒙ArkUI實戰之TextArea組件、RichEditor組件、RichText組件、Search組件的使用

本文接上篇繼續更新ArkUI中組件的使用&#xff0c;本文介紹的組件有TextArea組件、RichEditor組件、RichText組件、Search組件&#xff0c;這幾個組件的使用對應特定場景&#xff0c;使用時更加需要注意根據需求去使用 TextArea組件 官方文檔&#xff1a; TextArea-文本與輸…

除了`String`、`StringBuffer` 和 `StringBuilder`之外,還有什么處理字符串的方法?

一、標準庫中的字符串處理類 1. StringJoiner&#xff08;Java 8&#xff09; 用途&#xff1a;用于在拼接字符串時自動添加分隔符、前綴和后綴。示例&#xff1a;StringJoiner sj new StringJoiner(", ", "[", "]"); sj.add("A").…

Qt中讀寫結構體字節數據

在Qt中讀寫結構體字節數據通常涉及將結構體轉換為字節數組(QByteArray)或直接從內存中讀寫。以下是幾種常見方法&#xff1a; 方法1&#xff1a;使用QDataStream讀寫結構體 cpp #include <QFile> #include <QDataStream>// 定義結構體 #pragma pack(push, 1) //…

Windows 10 上安裝 Spring Boot CLI詳細步驟

在 Windows 10 上安裝 Spring Boot CLI 可以通過以下幾種方式完成。以下是詳細的步驟說明&#xff1a; 1. 手動安裝&#xff08;推薦&#xff09; 步驟 1&#xff1a;下載 Spring Boot CLI 訪問 Spring Boot CLI 官方發布頁面。下載最新版本的 .zip 文件&#xff08;例如 sp…

Unity3D仿星露谷物語開發37之澆水動畫

1、目標 當點擊水壺時&#xff0c;實現澆水的動畫。同時有一個水從水壺中流出來的特效。 假如某個grid被澆過了&#xff0c;則不能再澆水了。。 如果某個grid沒有被dug過&#xff0c;也不能被澆水。 2、優化Settings.cs腳本 增加如下內容&#xff1a; public static float…

【2】Kubernetes 架構總覽

Kubernetes 架構總覽 主節點與工作節點 主節點 Kubernetes 的主節點&#xff08;Master&#xff09;是組成集群控制平面的關鍵部分&#xff0c;負責整個集群的調度、狀態管理和決策。控制平面由多個核心組件構成&#xff0c;包括&#xff1a; kube-apiserver&#xff1a;集…

如何對docker鏡像存在的gosu安全漏洞進行修復——筑夢之路

這里以mysql的官方鏡像為例進行說明&#xff0c;主要流程為&#xff1a; 1. 分析鏡像存在的安全漏洞具體是什么 2. 根據分析結果有針對性地進行修復處理 3. 基于當前鏡像進行修復安全漏洞并復核驗證 # 鏡像地址mysql:8.0.42 安全漏洞現狀分析 dockerhub網站上獲取該鏡像的…

【Tauri2】026——Tauri+Webassembly

前言 不多廢話 直言的說&#xff0c;筆者看到這篇文章大佬的文章 【04】Tauri 入門篇 - 集成 WebAssembly - 知乎https://zhuanlan.zhihu.com/p/533025312嘗試集成一下WebAssembly&#xff0c;直接開始 正文 準備工作 新建一個項目 安裝 vite的rsw插件和rsw pnpm instal…

OpenHarmony Camera開發指導(五):相機預覽功能(ArkTS)

預覽是在相機啟動后實時顯示場景畫面&#xff0c;通常在拍照和錄像前執行。 開發步驟 創建預覽Surface 如果想在屏幕上顯示預覽畫面&#xff0c;一般由XComponent組件為預覽流提供Surface&#xff08;通過XComponent的getXcomponentSurfaceId方法獲取surfaceid&#xff09;&…

puzzle(0531)腦力航跡

目錄 腦力航跡 規則 解法 簡單模式 中等模式 困難模式 專家模式 腦力航跡 規則 2條航跡會產生一個相對航跡&#xff1a; 根據相對航跡和其中一個航跡推導另外一個航跡。 解法 沒有任何需要推理的地方&#xff0c;就是純粹的2個矢量相加。 簡單模式 中等模式 困難模…

在win上安裝Ubuntu安裝Anaconda(linx環境)

一&#xff0c;安裝Ubuntu 1. 在 Microsoft 商城去下載Ubuntu(LTS:是長期維護的版本) 2.安裝完之后啟動程序&#xff0c;再重新打開一個黑窗口&#xff1a; wsl --list --verbose 3.關閉Ubuntu wsl --shutdown Ubuntu-22.04 WSL2 Ubuntu-20.04文件太占c盤空間&#xff0c;…

NEAT 算法解決 Lunar Lander 問題:從理論到實踐

NEAT 算法解決 Lunar Lander 問題:從理論到實踐 0. 前言1. 定義環境2. 配置 NEAT3. 解決 Lunar lander 問題小結系列鏈接0. 前言 在使用 NEAT 解決強化學習問題一節所用的方法只適用于較簡單的強化學習 (reinforcement learning, RL) 環境。在更復雜的環境中使用同樣的進化解…

【KWDB 創作者計劃】_上位機知識篇---ESP32-S3Arduino

文章目錄 前言1. ESP32-S3核心特性2. 開發環境搭建(1) 安裝Arduino IDE(2) 添加ESP32-S3支持(3) 選擇開發板(4) 關鍵配置3. 基礎代碼示例(1) 串口通信(USB/硬件串口)(2) Wi-Fi連接(3) 藍牙LE廣播4. 高級功能開發(1) USB OTG功能(2) AI加速(MicroTensorFlow)(3) 雙核任務處理…

JavaScript學習教程,從入門到精通,DOM節點操作語法知識點及案例詳解(21)

DOM節點操作語法知識點及案例詳解 一、語法知識點 1. 獲取節點 // 通過ID獲取 const element document.getElementById(idName);// 通過類名獲取&#xff08;返回HTMLCollection&#xff09; const elements document.getElementsByClassName(className);// 通過標簽名獲取…

PCA 降維實戰:從原理到電信客戶流失數據應用

一、簡介 在機器學習領域&#xff0c;數據的特征維度往往較高&#xff0c;這不僅會增加計算的復雜度&#xff0c;還可能導致過擬合等問題。主成分分析&#xff08;Principal Component Analysis&#xff0c;簡稱 PCA&#xff09;作為一種經典的降維技術&#xff0c;能夠在保留數…

信創時代編程開發語言選擇指南:國產替代背景下的技術路徑與實踐建議

&#x1f9d1; 博主簡介&#xff1a;CSDN博客專家、CSDN平臺優質創作者&#xff0c;高級開發工程師&#xff0c;數學專業&#xff0c;10年以上C/C, C#, Java等多種編程語言開發經驗&#xff0c;擁有高級工程師證書&#xff1b;擅長C/C、C#等開發語言&#xff0c;熟悉Java常用開…