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.mzph.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常用开…