当前位置: 首页 > news >正文

ASP.NET Core SignalR 配置与集成测试究极指南

这篇文章也可以在我的博客中查看

前言

哥们最近都在埋头苦干,沉默是金,有一段时间没更新博客了。然而今儿SignalR集成测试实属是给我整破防了。虽说SignalR是.NET官方维护的实时通信库,已经开发了有十几年,甚至已经编入至了core dll,然而更新迭代异常迅速,导致文档不全,出了事不知所措。这不最近在集成测试SignalR这点上就踩了大坑。

今天就给大伙分享一下如何配置SignalR,并重点讲解如何在 .NET 8 中使用xUnitMicrosoft.AspNetCore.Mvc.Testing.WebApplicationFactory对最新版(ASP.NET Core)SignalR进行集成测试,希望后来者可以少走弯路。

痛点

SignalR测试为何困难,原因有下:

  1. WebApplicationFactory,或者说其背后的TestServer,并不提供真的服务器环境,所有默认配置下的网络客户端(当然包括HttpClient)都无法连接至该模拟的服务器。
    • 然而SignalR客户端所有连接都是在默认网络环境下的,需要替换成TestServer环境下的客户端
  2. HttpClient并不提供WebSocket连接支持。
    • 然而SignalR实时通讯首选的是WebSocket,所以我们还要配个TestServer环境下的WebSocket客户端
  3. Hub受身份验证保护。
    • 替换成TestServer客户端的时候还需要考虑身份验证

汗流浃背了家人们

关于本文

本文按这三个问题为思路逐步进行,最合理的解决方案会在文末给出。

如果你觉得TL;DR、不想关注过程、或者认为看代码比看文章舒服,可以跳转到文章最后获取项目源码👇

本文只介绍SignalR配置与集成测试,阅读本文前建议做以下准备工作(本文可能不会介绍以下内容):

  1. SignalR的使用(只提及部分)
  2. 配置[Authorize]身份认证(只一笔带过)
  3. 配置.NET集成测试框架,如 xUnit
  4. 配置WebApplicationFactory

本文操作环境:

  1. .NET 8
  2. xUnit 测试框架

无身份验证SignalR

在引入复杂性之前,应先处理最核心的配置,因此先不配置身份验证。

基本配置

配置Hub

在 .NET 8 中,SignalR已经集成至ASP.NET Core中,因此不需要下载任何Nuget包就能够使用。

配置也十分简洁,首先需要创建一个HubHub相当于是SignalR中的控制器。
创建Hub非常简单,只需要继承Hub即可。以下例子展示了一个最基本的收发消息ChatHubSendMessage向所有连接广播一条消息:

using Microsoft.AspNetCore.SignalR;namespace SignalR.IntegrationTests;public class ChatHub : Hub
{public async Task SendMessage(string message){await Clients.All.SendAsync("ReceiveMessage", message);}
}
  1. SendMessage是客户端向服务端发送消息的入口
    • 该方法可以有返回值,返回值会传回调用者
  2. ReceiveMessage是服务端向客户端发送消息的入口
    • message是参数,参数不一定只有一个,也不一定为string
  3. A向B发送一条聊天信息其实需要经历两次交互
    1. A向服务器发送消息
    2. 服务器向B发送消息

配置Program.cs

Program.cs中注册SignalR组件,最简单的配置如下:

const string HubsPrefix = "/hubs"; // <-- Grouped by prefix /hubsvar builder = WebApplication.CreateBuilder(args);builder.Services.AddSignalR(); // <-- Add SignalRvar app = builder.Build();app.MapGroup(HubsPrefix).MapHub<ChatHub>("/chat"); // <-- Map your ChatHub to /hubs/chatapp.Run();

强类型Hub

上面的例子中,服务端消息方法ReceiveMessage是字符串,众所周知字符串意味着弱类型,无编译时提示,稍不留神可能就会写错。
.NET提供了一个做法强类型化这些方法。

首先定义一个接口:

public interface IChatClientProxy
{public Task ReceiveMessage(string message);
}

由于客户端还是需要以字符串订阅消息,因此函数应以客户端的角度进行命名:

  1. Receive而不是Send
  2. 虽然是异步方法,但不加Async后缀

然后将ChatHub修改如下:

public class ChatHub : Hub<IChatClientProxy>
{public async Task SendMessage(string message){await Clients.All.ReceiveMessage(message);}
}

SignalR会自动实现IChatClientProxy接口,当调用这个接口的方法时,对应名称的消息就会被发出。

在Hub外向客户端发送消息

更多时候我们会在Hub之外发送消息,就需要借助IHubContext获取Hub上下文。这个接口也支持强类型化。
以下实现了一个简单的服务,先做一系列检测和记录,再使用IHubContext实现实时发送消息:

using Microsoft.AspNetCore.SignalR;namespace SignalR.IntegrationTests;public class ChatService(IHubContext<ChatHub, IChatClientProxy> _hubContext)
{public async Task SendMessageToAllAsync(string message){// Chek for permissions...// Record to database...// ...await _hubContext.Clients.All.ReceiveMessage(message);}
}

为了保持程序中的一致性,通常情况下也会希望在Hub中引用自己的服务,而不是直接发送消息:

public class ChatHub(ChatService _chatService) : Hub<IChatClientProxy>
{public async Task SendMessage(string message){await _chatService.SendMessageToAllAsync(message);}
}

别忘了在Program.cs中为自己的服务注册依赖注入:

builder.Services.AddScoped<ChatService>();

在客户端中接收SignalR消息

呃,严格意义上你无法在服务端中接收SignalR消息,你需要一个客户端接收服务端发出的信息。

以下代码是客户端代码,它可能位于另一个项目,可以是另一种语言实现,甚至可以处于另一个平台(e.g. Android)
但是它也可以碰巧是同一个平台,又碰巧是C#实现,甚至碰巧在同一个项目 😉

总之如果要在C#中接收SignalR消息,你需要安装客户端Nuget包Microsoft.AspNetCore.SignalR.Client
下面的例子展示了如何向服务端的ChatHub收发消息:

using Microsoft.AspNetCore.SignalR.Client;
using System.Diagnostics;var connection = new HubConnectionBuilder().WithUrl("http://localhost/hubs/chat").Build();
// Add receive message handler.
connection.On<string>("ReceiveMessage", (message) => Debug.WriteLine(message));await connection.StartAsync();// Send message.
await connection.InvokeAsync("SendMessage", "Hello World");await connection.StopAsync();
  1. On方法用于接收消息。注意泛型参数一定要与服务端的类型兼容,否则可能收不到对应消息
  2. InvokeAsync方法用于发送消息。第一个参数是远程方法名,第二个起是远程方法对应的参数
    1. 该方法可以有泛型参数TResult,以接受对应类型的返回值
  3. HubConnectionBuilder还可以配置断线重连、身份验证等功能,具体请查阅官方文档

集成测试

准备工作

进行下一步之前,需要先:

  1. 新建一个 xUnit 项目
  2. 添加主项目为依赖项
  3. 在测试项目中安装并配置WebApplicationFactory
  4. 在测试项目中安装Microsoft.AspNetCore.SignalR.ClientNuget包

测试用例

根据含义,我们会尝试使用SignalR客户端发送一条消息,然后断言能够收到消息:

using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.SignalR.Client;public class WebAppFactory : WebApplicationFactory<Program> { }public class HubIntegrationTests(WebAppFactory _factory) : IClassFixture<WebAppFactory>
{private HubConnection SetupHubConnection(string path){var uri = new Uri(_factory.Server.BaseAddress, path);return new HubConnectionBuilder().WithUrl(uri).Build();}[Fact]public async Task MessageTest(){// --> Arrangevar connection = SetupHubConnection("/hubs/chat");string? received = null;connection.On<string>("ReceiveMessage", (m) => received = m);await connection.StartAsync();string message = "Hello World";// --> Actawait connection.InvokeAsync("SendMessage", message);// Wait for messages to be received. You may need to increase the delay if you're running in a slow environment.await Task.Delay(1);// --> AssertAssert.Equal(message, received);}
}
  • SetupHubConnection函数用作连接SignalR服务器。
  • 其中等待了1毫秒以确保有足够的时间接收消息
    • 如果你的测试环境是老爷机,可能需要增加等待时间

然而这个用例会失败,错误如下:

System.Net.Http.HttpRequestException : No connection could be made because the target machine actively refused it. (localhost:80)

原因是TestServer并不是真的服务器,它只模拟ASP.NET应用服务器的行为,而不会在宿主机环境中启动真的服务器。因此我们使用常规的方式进行连接是无法访问的。但没有关系……

非WebSocket传输模式的测试

TestServer提供了一个用于连接至测试服务器的HttpMessageHandler对象,也就是任何支持HttpMessageHandler进行Http数据交换的库都可以通过使用该对象访问TestServer
经常接触.NET测试的伙伴此时已经要素察觉了:HttpClientHttpMessageHandler就是原生支持的!

然后还有两个好消息:

  1. WebSocket模式下的SignalR发起的连接使用的就是HttpClient
    • 没错,只是非WebSocket,但总比连接失败要好!
  2. SignalR提供了一个配置项,可以替换内部HttpClient使用的HttpMessageHandler

所以解决方案很简单,只需要将上述SetupHubConnection函数修改成以下形式:

private HubConnection SetupHubConnection(string path)
{var server = _factory.Server;var uri = new Uri(server.BaseAddress, path);return new HubConnectionBuilder().WithUrl(uri, o =>{o.HttpMessageHandlerFactory = _ => server.CreateHandler();}).Build();
}

TestServer.CreateHandler()生成了一个HttpMessageHandler,将它赋值给HttpMessageHandlerFactory,可以改变其内部HttpClient的连接行为,使其得以与TestServer进行交互。

问题

虽然测试是能通过了,但是注意到测试时间长达4秒,这对于本地服务器来讲显然是不正常的:

========== Starting test run ==========
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.5.3.1+6b60a9e56a (64-bit .NET 8.0.4)
[xUnit.net 00:00:00.04]   Starting:    SignalR.IntegrationTests.Tests
[xUnit.net 00:00:04.37]   Finished:    SignalR.IntegrationTests.Tests
========== Test run finished: 1 Tests (1 Passed, 0 Failed, 0 Skipped) run in 4.4 sec ==========

原因是因为产生了等待。事实上,这个用例并没有建立WebSocket连接,而是在等待WebSocket连接超时后,转为了使用LongPolling模式连接。
如果我们强制限制SignalR客户端使用WebSocket连接:

private HubConnection SetupHubConnection(string path)
{var server = _factory.Server;var uri = new Uri(server.BaseAddress, path);return new HubConnectionBuilder().WithUrl(uri, o =>{o.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransportType.WebSockets; // WebSockets only.o.HttpMessageHandlerFactory = _ => server.CreateHandler();}).Build();
}

这个用例会在4秒后超时失败:

System.AggregateException : Unable to connect to the server with any of the available transports. (WebSockets failed: Unable to connect to the remote server) (ServerSentEvents failed: The transport is disabled by the client.) (LongPolling failed: The transport is disabled by the client.)

轮询并不是一般情况下的连接方式,而且我们也不希望每个连接都等待4秒,所以,有没有办法能够进行Socket连接?

WebSocket传输模式的测试

WebSocket连接失败的原因是WebSocketClient独立于HttpClient,虽然我们构建了SignalR内部HttpClientTestServer之间的连接,但是并没有改变WebSocketClient,它仍然是向真正的宿主机环境建立连接,所以必然会失败。

但是没有关系,这个问题早在几年前就被SignalR团队注意到,并提供了替换WebSocketClient的配置项:

private HubConnection SetupHubConnection(string path)
{var server = _factory.Server;var uri = new Uri(server.BaseAddress, path);return new HubConnectionBuilder().WithUrl(uri, o =>{o.Transports = HttpTransportType.WebSockets;o.HttpMessageHandlerFactory = _ => server.CreateHandler();// Support WebSocket transports.o.WebSocketFactory = async (context, cancellationToken) =>{var wsClient = server.CreateWebSocketClient();return await wsClient.ConnectAsync(context.Uri, cancellationToken);};o.SkipNegotiation = true;}).Build();
}

通过配置WebSocketFactory,可以将默认的WebSocketClient换成TestServer提供的客户端。从而能够对其进行WebSocket访问。
在WebSocket模式下,顺便设置了SkipNegotiation,可以减少协商时间,而不会影响结果。

这里其实可以省略HttpMessageHandlerFactory的配置,因为使用WebSocket时不会用到HttpClient。但如果使用LongPolling则很重要,因此还是保留以供选择。

修改了WebSoketClient配置后,重新运行测试用例,这次可以快速以WebSocket模式通过测试:

========== Starting test run ==========
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.5.3.1+6b60a9e56a (64-bit .NET 8.0.4)
[xUnit.net 00:00:00.03]   Starting:    SignalR.IntegrationTests.Tests
[xUnit.net 00:00:00.21]   Finished:    SignalR.IntegrationTests.Tests
========== Test run finished: 1 Tests (1 Passed, 0 Failed, 0 Skipped) run in 216 ms ==========

带身份验证SignalR

身份配置

SignalR的身份验证方式

SignalR可以使用CookieToken令牌两种方式进行身份认证。

Cookie是浏览器环境下的首选方式,可以自动传递凭证;而Token则是非浏览器客户端下最简便的做法。
由于Cookie开箱即用,不需要做额外配置,因此本文只重点介绍Token做法。

SignalR Token令牌传递方式

根据SignalR文档,在不同情况下有不同的传达方式:

  1. 在非浏览器环境中,以Authorization请求头的方式传递
  2. 在浏览器环境的WebSocket, Server Side Event模式下,无法使用自定义请求头,需要以查询字符串的方式传递
    • 该查询字符串需要在身份验证服务器自行读取接收

服务端配置接收access_token

所有无法自定义连接请求头的情况下,都约定使用一个写死的(😅微软你也干这事啊)查询字符串access_token作为身份认证的参数。

你写死不要紧,要紧的是我们使用SignalR是需要手动处理这个查询字符串的,否则这种情况下永远无法触发身份验证。

虽然官网有说明,但是总有像我一样的愣头青不喜欢看官方文档然后捣鼓了一整天才发现涅麻麻的要手动配置这个查询字符串。

所以为了减少愣头青,请你务必:
按照以下操作配置查询字符串!
按照以下操作配置查询字符串!
按照以下操作配置查询字符串!

接收查询字符串

需要在SignalR服务端中主动接收这个查询字符串。
使用不同的身份验证库,需要以不同的方式进行接收:

  1. 你使用了内置的JWT库或者Identity Server,可以参照官方文档进行配置
  2. 你使用了Identity内置的BearerToken,可以在Bearer Token中间件进行配置(见下文)
  3. 你使用了其它的身份验证库,基本也是相同的套路:需要在验证请求事件中手动将该查询字符串赋值为用户凭证
Identity 内置Bearer Token身份验证

我这里使用了 .NET 8 Identity的内置BearerToken,所以能够实现目标的最小配置Program.cs是这样的:

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using SignalR.IntegrationTests;const string HubsPrefix = "/hubs";var builder = WebApplication.CreateBuilder(args);builder.Services.AddAuthorization();
builder.Services.AddAuthentication(IdentityConstants.BearerScheme).AddCookie(IdentityConstants.ApplicationScheme).AddBearerToken(IdentityConstants.BearerScheme, o =>{o.Events = new(){OnMessageReceived = context =>{var accessToken = context.Request.Query["access_token"];var path = context.HttpContext.Request.Path;// If the request is for our hub...if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments(HubsPrefix)){// Read the token out of the query stringcontext.Token = accessToken;}return Task.CompletedTask;}};});
builder.Services.AddIdentityCore<IdentityUser>().AddApiEndpoints().AddEntityFrameworkStores<IdentityDbContext>();
builder.Services.AddDbContext<IdentityDbContext>(x => x.UseInMemoryDatabase("db"));builder.Services.AddSignalR();var app = builder.Build();app.MapIdentityApi<IdentityUser>();app.MapGroup(HubsPrefix).MapHub<ChatHub>("/chat");app.Run();

使用身份验证保护Hub

与Controller一样,通过使用AuthorizeAllowAnonymous特性控制对Hub的访问

[Authorize]
public class ChatHub(ChatService _chatService) : Hub<IChatClientProxy>
{// ......
}

为Hub连接提供身份验证

Cookie验证
  1. 浏览器环境中,正常使用Cookie登录,凭证会在请求时自动携带
  2. 非浏览器环境中,可以通过手动设置Cookie请求头实现Cookie验证
    • 但这种做法不如使用Token更加正规
Token令牌验证

Token可以在客户端发起连接前使用AccessTokenProvider提供。

var connection = new HubConnectionBuilder().WithUrl("http://localhost/hubs/chat", options =>{options.AccessTokenProvider = () => Task.FromResult(token);}).Build();

考虑到重连与Token过期问题,AccessTokenProvider接受的是一个工厂函数,你可以选择动态获取新Token,而不是写死一个值

集成测试

由于我们替换了默认的WebSocketClient,我们需要手动携带Token令牌,以支持WebSocket模式下的身份验证;非WebSocket的身份验证仍然使用AccessTokenProvider配置项,无需修改。因此修改SetupHubConnection方法:

  1. 配置AccessTokenProvider参数,使非WebSocket连接方式能够携带令牌
  2. token添加至WebSocketClient中,使WebSocket连接方式能够携带令牌。由于是非浏览器环境,有两种方案可以选择:
    1. 添加名为access_token的查询字符串
    2. 添加Authorization请求头

小孩子才做选择,我全都要。

private HubConnection SetupHubConnection(string path, string? token = null)
{var server = _factory.Server;var uri = new Uri(server.BaseAddress, path);return new HubConnectionBuilder().WithUrl(uri, o =>{o.Transports = HttpTransportType.WebSockets;o.HttpMessageHandlerFactory = _ => server.CreateHandler();o.WebSocketFactory = async (context, cancellationToken) =>{var wsClient = server.CreateWebSocketClient();if (token != null){// Authentication for socket transports. (Chooses one of these.)// Option1: Use request headers.wsClient.ConfigureRequest = request => request.Headers.Authorization = new($"Bearer {token}");// Option2: Add access token to query string.uri = new Uri(QueryHelpers.AddQueryString(context.Uri.ToString(), "access_token", token));// I like both ;)}else{uri = context.Uri;}return await wsClient.ConnectAsync(uri, cancellationToken);};o.SkipNegotiation = true;// Authentication for non-socket transports. (Can be omitted here.)o.AccessTokenProvider = () => Task.FromResult(token);}).Build();
}

最后在用例中指定token参数,即可成功通过测试。

Q: 我应该如何生成token令牌?

如何生成令牌取决于你身份验证的实现方式。

在使用WebApplicationFactory的集成测试中,你可以比较容易地使用真实的用户与正常的登录方式获取令牌;如果身份认证本身并不是集成测试的关键,你可以设法使用测试替身替换掉原有的身份验证程序。(但一般情况下这只会更麻烦)

如果你想了解如何以正常登录方式获取令牌,可以查看我的源码👇


至此,所有问题解决!现在我们可以用SignalR WebSocket模式对带身份认证的Hub进行集成测试了!

项目源码

  • Github

参考资料

  1. Authentication and authorization in ASP.NET Core SignalR
  2. [SignalR] Better integration with TestServer
  3. SignalR Hub auth?

相关文章:

ASP.NET Core SignalR 配置与集成测试究极指南

这篇文章也可以在我的博客中查看 前言 哥们最近都在埋头苦干&#xff0c;沉默是金&#xff0c;有一段时间没更新博客了。然而今儿SignalR集成测试实属是给我整破防了。虽说SignalR是.NET官方维护的实时通信库&#xff0c;已经开发了有十几年&#xff0c;甚至已经编入至了core…...

JENKINS 安装,学习运维从这里开始

Download and deployJenkins – an open source automation server which enables developers around the world to reliably build, test, and deploy their softwarehttps://www.jenkins.io/download/首先点击上面。下载Jenkins 为了学习&#xff0c;从windows开始&#x…...

大语言模型从Scaling Laws到MoE

1、摩尔定律和伸缩法则 摩尔定律&#xff08;Moores law&#xff09;是由英特尔&#xff08;Intel&#xff09;创始人之一戈登摩尔提出的。其内容为&#xff1a;集成电路上可容纳的晶体管数目&#xff0c;约每隔两年便会增加一倍&#xff1b;而经常被引用的“18个月”&#xf…...

四级英语翻译随堂笔记

降维表达&#xff1a;中译英&#xff0c;英译英 没有强调主语&#xff0c;没有说明主语&#xff1a;用被动 但如果实在不行&#xff0c;再增添主语 不会就不翻译&#xff0c;不要乱翻译 以xxx为背景&#xff1a;against the backdrop of the xxx eg:against the backdrop of…...

Nacos支持的配置格式及其在微服务架构中的应用

今天&#xff0c;我想和大家探讨一下Nacos这一重要的微服务组件&#xff0c;特别是它所支持的配置格式以及这些格式在微服务架构中的应用。 一、Nacos简介 Nacos是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它提供了服务发现、配置管理…...

2024年华为OD机试真题-小明找位置-(C++)-OD统一考试(C卷D卷)

题目描述: 小朋友出操,按学号从小到大排成一列;小明来迟了,请你给小明出个主意,让他尽快找到他应该排的位置。 算法复杂度要求不高于nLog(n);学号为整数类型,队列规模<=10000; 输入描述: 1、第一行:输入已排成队列的小朋友的学号(正整数),以”,”隔开; …...

机器人系统ros2内部接口介绍

内部 ROS 接口是公共 C API &#xff0c;供创建客户端库或添加新的底层中间件的开发人员使用&#xff0c;但不适合典型 ROS 用户使用。 ROS客户端库提供大多数 ROS 用户熟悉的面向用户的API&#xff0c;并且可能采用多种编程语言。 内部API架构概述 内部接口主要有两个&#x…...

跟随Facebook的足迹:社交媒体背后的探索之旅

在当今数字化时代&#xff0c;社交媒体已经成为了人们日常生活中不可或缺的一部分。而在这庞大的社交媒体网络中&#xff0c;Facebook作为其中的巨头&#xff0c;一直在引领着潮流。从创立之初的一个大学社交网络到如今的全球性平台&#xff0c;Facebook的发展历程承载了无数故…...

面试题分享之Java并发篇

注意&#xff1a;文章若有错误的地方&#xff0c;欢迎评论区里面指正 &#x1f36d; 系列文章目录 面试题分享之Java集合篇&#xff08;三&#xff09; 面试题分享之Java集合篇&#xff08;二&#xff09; 面试题分享之Java基础篇&#xff08;三&#xff09; 前言 今天给小…...

bpmn-js 多实例配置MultiInstanceLoopCharacteristics实现或签会签

使用bpmn-js流程图开发过程中会遇到会签和或签的问题,这个时候我们就需要使用多实例配置来实现BPMN 2.0的配置实现了,多实例任务,是从流程编辑概念之初也就是Activiti时期就存在的一个方式。所谓的多实例任务也就是字面意思,一个任务由多个人完成,常见于我们的审批流程的或…...

【gpedit.msc】组策略编辑器的安装,针对windows家庭版,没有此功能

创建一个记事本文件然后放入以下内容 echo offpushd "%~dp0"dir /b %systemroot%\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum >gp.txtdir /b %systemroot%\servicing\Packages\Microsoft-Windows-GroupPolicy-…...

带EXCEL附件邮件发送相关代码

1.查看生成的邮件 2.1 非面向对象的方式&#xff08;demo直接copy即可&#xff09; ​ REPORT Z12. DATA: IT_DOCUMENT_DATA TYPE SODOCCHGI1,IT_CONTENT_TEXT TYPE STANDARD TABLE OF SOLISTI1 WITH HEADER LINE,IT_PACKING_LIST TYPE TABLE OF SOPCKLSTI1 WITH HEADER LIN…...

【算法作业】均分卡牌,购买股票

问题描述 John 有两个孩子&#xff0c;在 John病逝后&#xff0c;留下了一组价值不一定相同的魔卡&#xff0c; 现在要求你设计一种策略&#xff0c;帮John的经管人将John的这些遗产分给他的两个孩子&#xff0c;使得他们获得的遗产差异最小&#xff08;每张魔卡不能分拆&#…...

python作业

题目 分析 步骤&#xff1a; 判断先画空格还是数字 当有n层时&#xff0c;第i层有多少个空格第i层的起始数字是几&#xff0c;结尾是几&#xff0c;即数字取值范围当有n层时&#xff0c;第i层有多少个数字 代码 模式A n int(input("请输入行数:")) for i in range(…...

【Linux的文件篇章 - 管道文件】

Linux学习笔记---013 Linux的管道文件1、进程间通信1.1、进程为什么要通信&#xff1f;1.2、进程如何通信&#xff1f;1.3、进程通信的方式&#xff1f; 2、匿名管道2.1、理解一种现象2.2、基本概念和管道原理 3、管道的使用3.1、代码样例3.2、如何使用管道通信呢&#xff1f;3…...

C# 局部静态函数,封闭方法中的最佳选择

C# 局部静态函数&#xff0c;封闭方法中的最佳选择 简介特性 应用场景辅助计算递归与尾递归优化筛选与过滤操作查找与映射操作 生命周期静态局部函数 vs 普通局部函数性能封装性可读性 简介 C# 局部静态函数&#xff08;Local Static Functions&#xff09;是一种函数作用域内…...

【MySQL】MySQL 8.4.0 长期支持版(LTS)安装

就在2024年 “5.1” 节前&#xff0c;MySQL官方发布了8.4.0长期支持版&#xff08;LTS - Long Term Support&#xff09;。根据官方提供的文档&#xff0c;在本地虚拟机进行安装测试。 安装、配置和启动过程记录如下&#xff1a; 第一步&#xff0c;上传到安装包&#xff08;my…...

nest中的ORM

在 Nest.js 中执行 SQL 查询通常涉及使用 TypeORM 或 Sequelize 这样的 ORM&#xff08;对象-关系映射&#xff09;库。这些库使得在 Nest.js 应用程序中连接和操作 SQL 数据库变得更加简单和直观。 以下是一个使用 TypeORM 在 Nest.js 中执行 SQL 查询的示例代码&#xff1a;…...

TCP(Transmission Control Protocol,传输控制协议)如何保证数据的完整性?

TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;通过一系列机制来保证数据传输的可靠性和无错性&#xff0c;这些机制主要包括&#xff1a; 校验和&#xff1a;TCP报文段包含一个校验和字段&#xff0c;用于检测数据在传输过程中是否出错。…...

Numpy库介绍

NumPy&#xff08;Numerical Python的缩写&#xff09;是Python中用于科学计算的一个强大的库。它提供了高性能的多维数组对象&#xff08;即ndarray&#xff09;、用于处理这些数组的工具以及用于数学函数操作的函数。让我为你介绍一下它的一些主要功能&#xff1a; 1. 多维数…...

临时有事无法及时签字盖章?试试用契约锁设置“代理人”

遇到“领导休假中、在开重要会议、外出考察或者主任医生手术中等”一段时间内不方便或者无法及时签字盖章的情况怎么办&#xff1f;业务推进不了只能干等&#xff1f; 契约锁电子签及印控平台支持印章、签名“临时授权”、“代理签署”&#xff0c;实现指定人、指定时间段、指定…...

数据库权限管理

1.查看系统级权限&#xff08;global level) Select * from mysql.user\G; 2.查看数据库中所有表的权限 Select * from mysql.db\G 3.远程连接数据库 第一步在有数据库服务上的主机上&#xff1a;授权 grant all on *.* to root192.168.40.83 identified by Zxy20234; 第…...

如何创建一个 Django 应用并连接到数据库

简介 Django 是一个用 Python 编写的免费开源的 Web 框架。这个工具支持可扩展性、可重用性和快速开发。 在本教程中&#xff0c;您将学习如何为一个博客网站建立与 MySQL 数据库的初始基础。这将涉及使用 django-admin 创建博客 Web 应用程序的骨架结构&#xff0c;创建 MyS…...

【算法刷题day44】Leetcode:518. 零钱兑换 II、377. 组合总和 Ⅳ

文章目录 Leetcode 518. 零钱兑换 II解题思路代码总结 Leetcode 377. 组合总和 Ⅳ解题思路代码总结 草稿图网站 java的Deque Leetcode 518. 零钱兑换 II 题目&#xff1a;518. 零钱兑换 II 解析&#xff1a;代码随想录解析 解题思路 先遍历物品&#xff0c;再遍历背包。 代码…...

『51单片机』AT24C02[IIC总线]

存储器的介绍 ⒈ROM的功能⇢ROM的数据在程序运行的时候是不容改变的&#xff0c;除非你再次烧写程序&#xff0c;他就会改变&#xff0c;就像我们的书本&#xff0c;印上去就改不了了&#xff0c;除非再次印刷&#xff0c;这个就是ROM的原理。 注→在后面发展的ROM是可以可写可…...

Jenkins与Rancher的配合使用

Jenkins和Rancher是两个常用的DevOps工具&#xff0c;可以很好地配合使用来实现持续集成和持续部署。 Jenkins是一个开源的自动化构建工具&#xff0c;可以实现自动化的代码构建、测试和部署等一系列操作。可以通过Jenkins来触发构建任务&#xff0c;例如从代码仓库中拉取最新的…...

GIS入门,常用的多边形平滑曲线算法介绍和JavaScript的多边形平滑曲线算法库chaikin-smooth的实现原理和使用

前言 本章介绍一下常用的多边形平滑曲线算法及其使用案例。 多边形平滑算法通常用于图形处理或计算机图形学中,以使线条或曲线在连接处平滑过渡,而不出现明显的棱角或断裂。多边形平滑算法有多种实现方法,其中一些常见的有下面几种: 贝塞尔曲线插值(Bezier Curve Interpo…...

气膜体育馆内部的采光效果如何?—轻空间

气膜体育馆内部的采光效果如何&#xff1f;这是许多人对这种创新建筑的一个关键关注点。 首先&#xff0c;气膜体育馆的采光性非常好。阳光透过屋顶时以漫射光的方式进入室内&#xff0c;这种透射方式使得室内的光线柔和而均匀。从内部观察&#xff0c;整个屋顶就像一个连续的明…...

矩阵的对称正定性判决(复习)

文章目录 本科学的数学知识忘的太快了 如何判断一个实矩阵是否是对称正定 在线性代数中&#xff0c;一个实对称矩阵是否为正定可以通过以下方法判断&#xff1a; 对称性&#xff1a; 首先&#xff0c;确认矩阵是否对称&#xff0c;即矩阵的转置是否等于其本身。 特征值检查&…...

网络安全之DHCP详解

DHCP&#xff1a;Dynamic Host Configration Protocol 动态主机配置协议 某一协议的数据是基于UDP封装的&#xff0c;当它想确保自己的可靠性时&#xff0c;这个协议要么选确认重传机制&#xff0c;要么选周期性传输。 DHCP是确认重传&#xff0c;【UDP|DHCP】,当DHCP分配完地…...

【Proteus】LED呼吸灯 直流电机调速

1.LED呼吸灯 #include <REGX51.H> sbit LEDP2^0; void delay(unsigned int t) {while(t--); } void main() {unsigned char time,i;while(1){for(time0;time<100;time){for(i0;i<20;i){LED0;delay(time);LED1;delay(100-time);}}for(time100;time>0;time--){fo…...

今天遇到一个GPT解决不了的问题

问题描述 你好&#xff0c;postman的一个post请求&#xff0c;编辑器里面放了一个很长的json数据&#xff0c;报Tokenization is skipped for long lines for performance reasons. This can be configured via editor.maxTokenizationLineLength.&#xff0c;但是同样的数据&a…...

优化SQL的方法

来自组内分享&#xff0c;包含了比较常使用到的八点&#xff1a; 避免使用select * union all代替union 小表驱动大表 批量操作 善用limit 高效的分页 用连接查询代替子查询 控制索引数量 一、避免使用select * 消耗数据库资源 消耗更多的数据库服务器内存、CPU等资源。 消…...

库存管理系统开源啦

软件介绍 ModernWMS是一个针对小型物流仓储供应链流程的开源库存管理系统。该系统的开发初衷是为了满足中小型企业在有限IT预算下对仓储管理的需求。通过总结多年ERP系统研发经验&#xff0c;项目团队开发了这套适用于中小型企业的系统&#xff0c;以帮助那些有特定需求的用户。…...

【java】接口

什么是接口 接口当中存在的是对方法的定义&#xff0c;而不是对方法的具体实现。 为什么不实现这个方法呢&#xff1f; 继承的本质是代码的复用。当一个父类会经常被继承&#xff0c;并且子类都要自己实现方法时&#xff0c;父类中的方法就会显得累赘&#xff0c;并且占用了…...

Java中的类型转换

一、类型转换 对类型转换来说分为向上类型转换和向下类型转换&#xff1a; 向上类型转换是自动完成的&#xff0c;一般是小类型向大类型转换。在引用类型中是子类型向父类型转换。向下类型转换是强制完成的&#xff0c;一般是大类型向小类型转换。在引用类型中是父类型向子类…...

定义范围对PFMEA分析的重要性——SunFMEA软件

在进行PFMEA分析时&#xff0c;定义范围是一个至关重要的步骤。这是因为&#xff0c;通过明确分析的范围&#xff0c;可以确保团队关注到最关键、最可能影响产品质量的过程&#xff0c;从而更有效地识别和解决潜在问题。今天SunFMEA软件和大家一起讨论定义范围对PFMEA操作的重要…...

json返回工具类|世界协调时间(UTC)

一、问题 世界协调时间&#xff08;UTC&#xff09;是一个标准的时间参考&#xff0c;通常被用于跨越不同时区的时间标准。要将 UTC 时间转换为中国时间&#xff08;中国标准时间&#xff09;&#xff0c;你需要将时间加上8个小时&#xff0c;因为中国位于 UTC8 时区。 初中知…...

MySQL·内置函数

目录 函数 日期函数 案例1&#xff1a;创建一张表&#xff0c;记录生日 案例2&#xff1a;创建一个留言表 案例3&#xff1a;请查询在2分钟内发布的帖子 字符串函数 案例1&#xff1a; 获取emp表的ename列的字符集 案例2&#xff1a;要求显示exam_result表中的信息&am…...

vue根据文字动态判断溢出...鼠标悬停显示el-tooltip展示

使用自定义el- tooltip 组件 定义 Tooltip是一种小型弹出框,它显示有关特定页面元素的信息,例如按钮、链接或图标。Tooltip通常以半透明的气泡形式呈现,并出现在页面元素的旁边或下方。 它可以改善用户体验,使用户更容易理解页面元素的功能和意图。用户可以通过将鼠标悬停…...

使用Tkinter实现数据预测工具的GUI界面展示

如果构建好预测模型后&#xff0c;想将预测模型通过一个交互式的页面显示&#xff0c;可以通过下边两种方式实现。 本文中代码有详细解析注释&#xff0c;便不再如往期一样分开讲解了&#xff0c;有需要的朋友可以直接拿去使用&#xff0c;代码可以直接运行&#xff0c;把预测…...

机器学习笔记-22

终章 至此吴恩达老师的机器学习课程已经完成啦&#xff0c;总结一下&#xff1a; 1.监督学习的算法&#xff1a;线性回归、逻辑回归、神经网络和向量机 2.无监督学习的算法&#xff1a;K-Means、PCA、异常检测 3.推荐系统、大规模数据处理、正则化、如何评估算法 4.上限分析、…...

车间为什么选择蒸发式冷风机?

蒸发式冷风机具有以下特点&#xff1a; 节能环保&#xff1a;蒸发式冷风机不使用压缩机和化学制冷剂&#xff0c;而是通过水的蒸发来降低温度&#xff0c;因此它是无压缩机、无冷媒、无污染的环保型产品。降温效果显著&#xff1a;在较潮湿地区&#xff0c;它一般能达到5-9℃的…...

5分钟速通大语言模型(LLM)的发展与基础知识

✍️ 作者&#xff1a;哈哥撩编程&#xff08;视频号同名&#xff09; 博客专家全国博客之星第四名超级个体COC上海社区主理人特约讲师谷歌亚马逊演讲嘉宾科技博主极星会首批签约作者 &#x1f3c6; 推荐专栏&#xff1a; &#x1f3c5; 程序员&#xff1a;职场关键角色通识宝…...

vue项目开发流程

vue项目开发流程 环境配置 asdf plugin add nodejs asdf install nodejs 16.20.2创建项目 npm create vitelatest my-vue-app -- --template vue npm install npm run dev修改调试端口 修改vite.config.js,修改如下所示&#xff0c;添加server的host和port。 import { de…...

【Django学习笔记(十)】Django的创建与运行

Django的创建与运行 前言正文1、安装Django2、创建项目2.1 基于终端创建项目2.2 基于Pycharm创建项目2.3 两种方式对比 3、默认项目文件介绍4、APP5、启动运行Django5.1 激活App5.2 编写URL和视图函数对应关系5.3 启动Django项目5.3.1 命令行启动5.3.2 Pycharm启动5.3.3 views.…...

即时通讯技术文集(第37期):IM代码入门实践(Part1) [共16篇]

为了更好地分类阅读 52im.net 总计1000多篇精编文章&#xff0c;我将在每周三推送新的一期技术文集&#xff0c;本次是第37 期。 [- 1 -] 一种Android端IM智能心跳算法的设计与实现探讨&#xff08;含样例代码&#xff09; [链接] http://www.52im.net/thread-783-1-1.html […...

UV胶具有哪些特点和优势

1. 快速固化&#xff1a;UV胶在紫外线照射下能够迅速固化&#xff0c;固化时间通常在几秒钟到几分钟之间&#xff0c;大大提高了生产效率。 2. 高粘接强度&#xff1a;UV胶固化后&#xff0c;具有较高的粘接强度&#xff0c;能够在各种材料上实现可靠的粘接&#xff0c;提供持…...

python面试之mysql引擎选择问题

MySQL数据库提供了多种存储引擎&#xff0c;每种存储引擎有其特定的优势和场景适用。以下是几种常见的MySQL存储引擎及其特点&#xff1a; InnoDB&#xff1a; 支持事务&#xff0c;有回滚和提交事务的功能。 支持行级锁定&#xff0c;提供更高的并发。 支持外键约束&#…...

MT3031 AK IOI

思路&#xff1a;把每个节点存到堆&#xff08;大根堆&#xff09;里。 如果节点放入后总时间没有超过m则放入堆中&#xff1b;如果总时间超过了&#xff0c;就看堆头元素是否比新元素大。如果大&#xff0c;则删除堆头&#xff08;反悔贪心&#xff09;。 注意别忘记开long l…...

嵌入式是大坑的说法,是否与学生的信息不对称有关?

在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 目前也算是在搞嵌入式&#…...

普通人也能创业!轻资产短视频带货项目,引领普通人实现创业梦想

在这个信息爆炸的时代&#xff0c;创业似乎成为了越来越多人的梦想。然而&#xff0c;传统的创业模式 keJ0277 往往伴随着高昂的资金投入和复杂的管理流程&#xff0c;让许多普通人望而却步。然而&#xff0c;现在有一种轻资产短视频带货项目正在悄然兴起&#xff0c;它以其低…...

DOTWeen插件使用技巧

今天我们一起来研究一下DOTween动画插件。 对于该插件官网上的介绍是&#xff1a;“DOtween是一种快速&#xff0c;高效&#xff0c;完全类型安全的面向对象的动画引擎。” 将它导入Unity中&#xff0c;可以很方便快速地帮我们完成许多动画效果。本篇文章主要将通过一些小案例…...

window下批量修改部分文件名

window批量修改部分文件名 在Windows环境下批量修改文件名&#xff0c;可以使用批处理脚本&#xff08;Batch Script&#xff09;来实现。以下是一个简单的批处理脚本示例&#xff0c;它会将当前目录下所有.txt文件的文件名中的old替换为new&#xff1a; echo off setlocal en…...

码农慎入 | 入坑软路由,退烧IDC,Homelab折腾记

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 卷圈 运营 / SandLiu 卷圈 监制 / 姝琦 封面 / 姝琦Midjourney 产品统筹 / bobo 场地支持 / 声湃轩北京录音间 俗话说&#xff0c;入门软路由&#xff0c;退坑IDC 这一期&#xff0c;我们将深入探讨一个许多科技爱好者…...

【深度学习】Pytorch模型转成Onnx

前言 工作时需要将模型转成onnx使用triton加载&#xff0c;记录将pytorch模型转成onnx的过程。 1.转化步骤 1-1.安装依赖库 pip install onnx pip install onnxruntime 1-2.导入模型 将训练的模型导入 from torch.utils.data import TensorDataset, DataLoader from transfo…...