使用 ASP.NET Core 8.0 创建最小 API

        构建最小 API,以创建具有最小依赖项的 HTTP API。 它们非常适合需要在 ASP.NET Core 中仅包括最少文件、功能和依赖项的微服务和应用。

        本教程介绍使用 ASP.NET Core 生成最小 API 的基础知识。 在 ASP.NET Core 中创建 API 的另一种方法是使用控制器。 有关在最小 API 和基于控制器的 API 之间进行选择的帮助,请参阅 API 概述。 有关基于包含更多功能的控制器创建 API 项目的教程,请参阅创建 Web API。

概述
本教程将创建以下 API: 

API描述请求正文响应正文
GET /todoitems获取所有待办事项None待办事项的数组
GET /todoitems/complete获取已完成的待办事项None待办事项的数组
GET /todoitems/{id}按 ID 获取项None待办事项
POST /todoitems添加新项待办事项待办事项
PUT /todoitems/{id}更新现有项待办事项None
DELETE /todoitems/{id}    删除项None

先决条件

带有 ASP.NET 和 Web 开发工作负载的 Visual Studio 2022 

创建 API 项目 

启动 Visual Studio 2022 并选择“创建新项目”。

在“创建新项目”对话框中:

    在“搜索模板”搜索框中输入 Empty。

    选择“ASP.NET Core 空”模板,然后选择“下一步”。

将项目命名为 TodoApi,然后选择“下一步”。

在“其他信息”对话框中:

    选择“.NET 8.0 (长期支持)”

    取消选中“不使用顶级语句”

    选择“创建”

检查代码
Program.cs 文件包含以下代码:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

前面的代码:

    创建具有预配置默认值的 WebApplicationBuilder 和 WebApplication。

    创建返回 Hello World! 的 HTTP GET 终结点 /:

运行应用

按 Ctrl+F5 以在不使用调试程序的情况下运行。

Visual Studio 会显示以下对话框:

如果信任 IIS Express SSL 证书,请选择“是”。

将显示以下对话框:

如果你同意信任开发证书,请选择“是”。

有关信任 Firefox 浏览器的信息,请参阅 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 证书错误。

Visual Studio 启动 Kestrel Web 服务器,然后打开浏览器窗口。

Hello World! 将显示在浏览器中。 Program.cs 文件包含一个最小但完整的应用。

关闭浏览器窗口。

添加 NuGet 包

必须添加 NuGet 包以支持本教程中使用的数据库和诊断。

    在“工具”菜单中,选择“NuGet 包管理器”>“管理解决方案的 NuGet 包”。

    选择“浏览”选项卡。

    在搜索框中输入“Microsoft.EntityFrameworkCore.InMemory”,然后选择 Microsoft.EntityFrameworkCore.InMemory。

    选中右窗格中的“项目”复选框,然后选择“安装” 。

    按照上述说明添加 Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore 包。

模型和数据库上下文类

在项目文件夹中,创建名为 Todo.cs 的文件,包含以下代码:

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

前面的代码为此应用创建模型。 模型是一个表示应用管理的数据的类。

    使用以下代码创建名为 TodoDb.cs 的文件:
    
using Microsoft.EntityFrameworkCore;

class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options)
        : base(options) { }

    public DbSet<Todo> Todos => Set<Todo>();
}

前面的代码定义了数据库上下文,它是为数据模型协调实体框架功能的主类。 此类从 Microsoft.EntityFrameworkCore.DbContext 类派生。

添加 API 代码

将 Program.cs 文件的内容替换为以下代码:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

app.MapPut("/todoitems/{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();
});

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();

以下突出显示的代码将数据库上下文添加到依赖关系注入 (DI) 容器,并且允许显示与数据库相关的异常:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

DI 容器提供对数据库上下文和其他服务的访问权限。

    本教程使用终结点资源管理器和 .http 文件来测试 API。

测试发布数据

Program.cs 中的以下代码创建 HTTP POST 终结点 /todoitems 以将数据添加到内存中数据库:

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

运行应用。 浏览器显示 404 错误,因为不再存在 / 终结点。

POST 终结点将用于向应用添加数据。

    选择“查看”>“其他 Windows”>“终结点资源管理器”。

    右键单击“POST”终结点,然后选择“生成请求”。

 

在名为 TodoApi.http 的项目文件夹中创建一个新文件,其内容类似于以下示例:

@TodoApi_HostAddress = https://localhost:7031

Post {{TodoApi_HostAddress}}/todoitems

###

    第一行创建了一个变量,该变量适用于所有终结点。

    下一行定义了 POST 请求。

    三重井号标签 (###) 行是请求分隔符:对于不同的请求,该标签之后的内容属于另一个请求。

POST 请求需要标头和正文。 若要定义请求的这些部分,请紧随 POST 请求行之后添加以下行:

Content-Type: application/json

{
  "name":"walk dog",
  "isComplete":true
}

前面的代码会添加 Content-Type 标头和 JSON 请求正文。 TodoApi.http 文件现在应如以下示例所示,但带有端口号:

@TodoApi_HostAddress = https://localhost:7057

Post {{TodoApi_HostAddress}}/todoitems
Content-Type: application/json

{
  "name":"walk dog",
  "isComplete":true
}

###

运行应用。

选择 POST 请求行上方的“发送请求”链接。

POST 请求将发送到应用,响应将显示在“响应”窗格中。 

检查 GET 终结点
示例应用通过调用 MapGet 实现多个 GET 终结点: 

API描述请求正文响应正文
GET /todoitems获取所有待办事项None待办事项的数组
GET /todoitems/complete获取所有已完成的待办事项None待办事项的数组
GET /todoitems/{id}按 ID 获取项None待办事项

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

测试 GET 终结点

通过从浏览器或使用终结点资源管理器调用 GET 终结点来测试应用。 以下步骤适用于终结点资源管理器。

    在“终结点资源管理器”中,右键单击第一个 GET 终结点,然后选择“生成请求”。

    将以下内容添加到 TodoApi.http 文件中:
    
Get {{TodoApi_HostAddress}}/todoitems

###

选择新的 GET 请求行上方的“发送请求”链接。

GET 请求将发送到应用,响应将显示在“响应”窗格中。

响应正文与以下 JSON 类似:

[
  {
    "id": 1,
    "name": "walk dog",
    "isComplete": true
  }
]

在“终结点资源管理器”中,右键单击“/todoitems/{id} GET”终结点,然后选择“生成请求”。 将以下内容添加到 TodoApi.http 文件中:

GET {{TodoApi_HostAddress}}/todoitems/{id}

###

将 {id} 替换为 1。

选择新的 GET 请求行上方的“发送请求”链接。

GET 请求将发送到应用,响应将显示在“响应”窗格中。

响应正文与以下 JSON 类似:

{
  "id": 1,
  "name": "walk dog",
  "isComplete": true
}

此应用使用内存中数据库。 如果已重新启动应用,GET 请求不会返回任何数据。 如果未返回任何数据,请将数据 POST 到应用,然后重新尝试 GET 请求。

返回值

ASP.NET Core 自动将对象序列化为 JSON,并将 JSON 写入响应消息的正文中。 此返回类型的响应代码为 200 OK(假设没有未处理的异常)。 未经处理的异常将转换为 5xx 错误。

返回类型可以表示大范围的 HTTP 状态代码。 例如,GET /todoitems/{id} 可以返回两个不同的状态值:

    如果没有任何项与请求的 ID 匹配,该方法将返回 404 状态 NotFound 错误代码。

    否则,此方法将返回具有 JSON 响应正文的 200。 返回 item 则产生 HTTP 200 响应。

检查 PUT 终结点

示例应用使用 MapPut 实现单个 PUT 终结点:

app.MapPut("/todoitems/{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();
});

        此方法类似于 MapPost 方法,但它使用 HTTP PUT。 成功响应返回 204 (无内容)。 根据 HTTP 规范,PUT 请求需要客户端发送整个更新的实体,而不仅仅是更改。 若要支持部分更新,请使用 HTTP PATCH。

测试 PUT 终结点

本示例使用内存内、数据库,每次启动应用时都必须对其进行初始化。 在进行 PUT 调用之前,数据库中必须有一个项。 调用 GET,以确保在调用 PUT 之前数据库中存在项。

更新具有 Id = 1 的 to-do 项,并将其名称设置为 "feed fish"。

    在“终结点资源管理器”中,右键单击 PUT 终结点,然后选择“生成请求”。
    
    将以下内容添加到 TodoApi.http 文件中:
    
    Put {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
    在 PUT 请求行中,将 {id} 替换为 1。
    
    紧随 PUT 请求行之后添加以下行:
    
    Content-Type: application/json
    
    {
    "name": "feed fish",
    "isComplete": false
    }
    
    前面的代码会添加 Content-Type 标头和 JSON 请求正文。
    
    选择新的 PUT 请求行上方的“发送请求”链接。
    
    PUT 请求将发送到应用,响应将显示在“响应”窗格中。 响应正文为空,状态代码为 204。

检查并测试 DELETE 终结点

示例应用使用 MapDelete 实现单个 DELETE 终结点:

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();
});

在“终结点资源管理器”中,右键单击 DELETE 终结点,然后选择“生成请求”。

将 DELETE 请求添加到 TodoApi.http。

将 DELETE 请求行中的 {id} 替换为 1。 DELETE 请求应如以下示例所示:

DELETE {{TodoApi_HostAddress}}/todoitems/1

###

选择 DELETE 请求的“发送请求”链接。

DELETE 请求将发送到应用,响应将显示在“响应”窗格中。 响应正文为空,状态代码为 204。

使用 MapGroup API

        示例应用代码每次设置终结点时都会重复 todoitems URL 前缀。 API 通常具有带常见 URL 前缀的终结点组,并且 MapGroup 方法可用于帮助组织此类组。 它减少了重复代码,并允许通过对 RequireAuthorization 和 WithMetadata 等方法的单一调用来自定义整个终结点组。

将 Program.cs 的内容替换为以下代码:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

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>。

    从 Map<HttpVerb> 方法调用中移除 URL 前缀 /todoitems。

测试终结点以验证它们的运行方式是否相同。

使用 TypedResults API

    返回 TypedResults(而不是 Results)有几个优点,包括可测试性和自动返回 OpenAPI 的响应类型元数据来描述终结点。 有关详细信息,请参阅 TypedResults 与 Results。

Map<HttpVerb> 方法可以调用路由处理程序方法,而不是使用 lambda。 若要查看示例,请使用以下代码更新 Program.cs:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

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();
}

单元测试可以调用这些方法并测试它们是否返回正确的类型。 例如,如果方法是 GetAllTodos:

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

单元测试代码可以验证是否从处理程序方法返回了 Ok<Todo[]>类型的对象。 例如:

public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
    // Arrange
    var db = CreateDbContext();

    // Act
    var result = await TodosApi.GetAllTodos(db);

    // Assert: Check for the correct returned type
    Assert.IsType<Ok<Todo[]>>(result);
}

防止过度发布
        目前,示例应用公开了整个 Todo 对象。 生产应用程序中的生产应用是模型的一个子集,通常用于限制可输入和返回的数据。 这背后有多种原因,但安全性是主要原因。 模型的子集通常称为数据传输对象 (DTO)、输入模型或视图模型。 本文使用的是 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;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

RouteGroupBuilder 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.Select(x => new TodoItemDTO(x)).ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db) {
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).Select(x => new TodoItemDTO(x)).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(new TodoItemDTO(todo))
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)
{
    var todoItem = new Todo
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.Name
    };

    db.Todos.Add(todoItem);
    await db.SaveChangesAsync();

    todoItemDTO = new TodoItemDTO(todoItem);

    return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);
}

static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = todoItemDTO.Name;
    todo.IsComplete = todoItemDTO.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();
}

确保可以发布和获取除机密字段以外的所有字段。

用完成的示例进行故障排除

如果遇到无法解决的问题,请将你的代码与完成的项目进行比较。 查看或下载已完成的项目(如何下载)

原文连接:教程:使用 ASP.NET Core 创建最小 API | Microsoft Learn

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用 pydub 的 AudioSegment 获取音频时长 - python 实现

通过使用 pydub 的 AudioSegment 获取音频时长&#xff0c;音频常用格式如 m4a,wav等。 安装 python 库&#xff1a; pip install pydub 获取 m4a 格式的音频时长代码如下&#xff0c;代码如下&#xff1a; #-*-coding:utf-8-*- # date:2024-10 # Author: DataBall - XIAN #…

【云效】阿里云云效:一站式DevOps平台介绍与使用教程(图文)附PPT

【云效】阿里云云效:一站式DevOps平台介绍与使用教程(图文) 云效费用企业管理项目协作代码管理自动流水线测试管理扩展资料附:PPT版文件下载参考资料: https://devops.aliyun.com/ 云效 阿里云一站式DevOps(持续交付)平台,项目数字化协作能效工具。 官方介绍: 云效,一…

bindService 流程学习总结

Context.bindServiceContextImpl.bindServiceCommonActivityManagerService.bindIsolatedService ActiveServices.bindIsolatedServiceretrieveServiceLocked 获取服务信息&#xff1b;bringUpServiceLocked 拉起服务startProcessLocked创建进程 (进程不存在时)realStartServi…

【Android】MVP架构

MVP架构简介 MVP&#xff08;Model-View-Presenter&#xff09;是一种常见的软件架构模式&#xff0c;尤其在Android应用开发中被广泛使用。它将应用程序分为三层&#xff1a;Model、View 和 Presenter&#xff0c;以实现职责分离&#xff0c;提高代码的可维护性和可测试性。 …

ant design vue树选择器实现部分层级禁用(指定层级或依据字段判断)

1、依据字段判断是否禁用 const handData (array, level?) > {array.forEach((item) > {if (level 0) {//获取一级菜单item.title item.levelName;item.value item.code;if (item.type LAYER) {item.disabled true;} else if (item.type JOB) {item.disabled f…

分享几个办公类常用的AI工具

办公类 WPS AI讯飞智文iSlideProcessOn亿图脑图ChatPPT WPS AI 金山办公推出的协同办公 AI 应用&#xff0c;具有文本生成、多轮对话、润色改写等多种功能&#xff0c;可以辅助用户进行文档编辑、表格处理、演示文稿制作等办公操作。 https://ai.wps.cn/ 讯飞智文 科大讯飞推…

OceanBase 首席科学家阳振坤:大模型时代的数据库思考

2024年 OceanBase 年度大会 即将于10月23日&#xff0c;在北京举行。 欢迎到现场了解更多“SQL AI ” 的探讨与分享&#xff01; 近期&#xff0c;2024年金融业数据库技术大会在北京圆满举行&#xff0c;聚焦“大模型时代下数据库的创新发展”议题&#xff0c;汇聚了国内外众多…

85.【C语言】数据结构之顺序表的中间插入和删除及遍历查找

目录 3.操作顺序表 1.分析中间插入函数 函数的参数 代码示例 图片分析 main.c部分改为 在SeqList.h添加SLInsert函数的声明 运行结果 2.分析中间删除函数 函数的参数 代码示例 图片分析 main.c部分改为 在SeqList.h添加SLErase函数的声明 运行结果 承接84.【C语…

前端开发:Vue中数据绑定原理

Vue 中最大的一个特征就是数据的双向绑定&#xff0c;而这种双向绑定的形式&#xff0c;一方面表现在元数据与衍生数据之间的响应&#xff0c;另一方面表现在元数据与视图之间的响应&#xff0c;而这些响应的实现方式&#xff0c;依赖的是数据链&#xff0c;因此&#xff0c;要…

未来汽车驾驶还会有趣吗?车辆动力学系统简史

未来汽车驾驶还会有趣吗&#xff1f;车辆动力学系统简史 本篇文章来源&#xff1a;Schmidt, F., Knig, L. (2020). Will driving still be fun in the future? Vehicle dynamics systems through the ages. In: Pfeffer, P. (eds) 10th International Munich Chassis Symposiu…

量子纠错--shor‘s 码

定理1 (量子纠错的条件) C是一组量子编码&#xff0c;P是映射到C上的投影算子。假设是一个算子元素描述的量子操作&#xff0c;那么基于量子编码C&#xff0c;存在一个能对抗描述的噪声的纠错操作R的充要条件是 对某个复元素厄米矩阵成立。 将算子元素称为导致的错误。如果这样…

【大模型实战篇】大模型分词算法Unigram及代码示例

1. 算法原理介绍 与 BPE 分词&#xff08;参考《BPE原理及代码示例》&#xff09;和 WordPiece 分词&#xff08;参考《WordPiece原理及代码示例》&#xff09;不同&#xff0c;Unigram 分词方法【1】是从一个包含足够多字符串或词元的初始集合开始&#xff0c;迭代地删除其中的…

WPF+MVVM案例实战(四)- 自定义GroupBox边框样式实现

文章目录 1、项目准备2、功能实现1、EnviromentModel.cs 代码2、GroubBoxViewModel.cs 代码实现3、ViewModelLocator.cs 依赖注入4、GroubBoxWindow.xaml 样式布局5、数据绑定 3、效果展示4、资源获取 1、项目准备 打开项目 Wpf_Examples&#xff0c;新建 GroubBoxWindow.xaml…

龙蟠科技业绩压力显著:资产负债率持续攀升,产能利用率也不乐观

《港湾商业观察》施子夫 黄懿 去年十月至今两度递表后&#xff0c;10月17日&#xff0c;江苏龙蟠科技股份有限公司(以下简称&#xff0c;龙蟠科技&#xff1b;603906.SH&#xff0c;02465.HK)通过港交所主板上市聆讯。 很快&#xff0c;龙蟠科技发布公告称&#xff0c;公司全…

基于STM32的Android控制智能家政机器人

基于STM32的Android控制智能家政机器人 基于STM32的Android控制智能家政机器人一、项目背景与意义二、系统设计方案三、硬件电路设计四、软件设计与实现4.1 Android端软件设计4.2 机器人端软件设计 五、系统调试与测试六、结论与展望七、附录 基于STM32的Android控制智能家政机…

信息安全工程师(55)网络安全漏洞概述

一、定义 网络安全漏洞&#xff0c;又称为脆弱性&#xff0c;是网络安全信息系统中与安全策略相冲突的缺陷&#xff0c;这种缺陷也称为安全隐患。漏洞可能导致机密性受损、完整性破坏、可用性降低、抗抵赖性缺失、可控性下降、真实性不保等问题。 二、分类 网络安全漏洞可以根据…

HDU Sum

题目大意&#xff1a;给你一个数字 n &#xff0c;n 个数字能分成多少组分类情况。 思路&#xff1a;这题要用插空法&#xff0c;一共 n 个数字&#xff0c;所以一共有 n - 1 个空可以插入&#xff0c;所以这道题目的答案就是&#xff0c;由二项式定理易得这个式子的和为 。但是…

Web应用框架-Django应用基础

1. 认识Django Django是一个用Python编写的开源高级Web框架&#xff0c; 旨在快速开发可维护和可扩展的Web应用程序。 使用Django框架的开发步骤&#xff1a; 1.选择合适的版本 2.安装及配置 3.生成项目结构 4.内容开发 5.迭代、上线、维护 Django官网&#xff1a; Djang…

UE4_Niagara基础实例—10、位置事件

效果&#xff1a; 若要为烟花火箭创建尾迹效果&#xff0c;则可将 生成位置事件&#xff08;Generate Location Event&#xff09; 模块放置到火箭发射器的粒子更新&#xff08;Particle Update&#xff09;组中。然后&#xff0c;尾迹发射器可使用位置数据生成跟随火箭的粒子…

离散制造和流程制造分别是什么?它们有什么区别?

为何有的企业生产过程看似一气呵成&#xff0c;而有的则是由多个环节组合而成&#xff1f;其实这就涉及到了制造业的两种常见生产模式。 流程制造离散制造 那么&#xff0c;在生产管理方面&#xff0c;离散制造和流程制造分别有什么特点、区别呢&#xff1f; 今天&#xff0…