Refit 使用详解

Git官网:https://github.com/reactiveui/refit

Refit 是一个针对 .NET 应用程序的 REST API 客户端库,它通过接口定义 API 调用,从而简化与 RESTful 服务的交互。其核心理念是利用声明性编程的方式来创建 HttpClient 客户端,使得 API 调用更加简洁和易于维护。

目前Refit所支持的平台如下:

  • .NET 6 / 8
  • Blazor
  • Desktop .NET 4.6.1
  • UWP
  • Xamarin.Android
  • Xamarin.Mac
  • Xamarin.iOS
  • Uno Platform

简单使用

依赖库的安装

可以从Nuget中直接下载依赖库,在Nuget中搜索Refit可以看到有两个相关的依赖库可以下载,分别是RefitRefit.HttpClientFactory
在这里插入图片描述
Refit 是一个用于 .NET 的库,它通过接口定义 REST API 调用,允许开发者以声明性方式创建 HTTP 客户端。Refit 负责自动序列化和反序列化请求和响应,并简化与 RESTful 服务的交互。其核心功能如下:

  • 提供强类型的 API 调用
  • 自动处理 JSON 和其他格式的序列化
  • 支持异步调用和中间件

Refit.HttpClientFactory 是一个扩展库,它与 ASP.NET Core 的 HttpClientFactory 结合使用,提供了使用 Refit 创建 API 客户端的便利方式。其核心功能如下:

  • 使用 ASP.NET Core 的 HttpClientFactory 管管理 HttpClient 的生命周期,避免了潜在的性能问题(如 DNS 缓存问题)和过度创建 HttpClient 实例
  • 主要用于 ASP.NET Core 应用,依赖于 ASP.NET Core 的 HttpClientFactory,可以更好地与 ASP.NET Core 的依赖注入框架集成

可以根据自己的项目选择使用,如果是ASP.NET Core 应用中使用 Refit,那么直接安装Refit.HttpClientFactory就可以了,如果是一些其他项目,例如WPF啥的,那么安装Refit 然后自己管理HttpClient实例就可以了。

定义接口

要使用Refit,第一步先要根据要请求的RestAPI进行接口的定义。

  • 示例

    public interface IGitHubApi
    {[Get("/users/{user}")]Task<User> GetUser(string user);
    }public class User
    {public string Id { get; set; }public string Name { get; set; }
    }
    

进行Http请求

Refit提供了RestService服务,可以帮助我们生成指定接口的实现,其内部调用了HttpClient进行Http请求

  • 示例

    var gitHubApi = RestService.For<IGitHubApi>("https://api.github.com");
    var octocat = await gitHubApi.GetUser("octocat");
    

Asp.Net Core中的注册

如果是Asp.Net Core项目,Refit支持通过HttpClientFactory进行注册(安装Refit.HttpClientFactory),后面直接使用我们定义的接口进行依赖注入直接使用就可以了

  • 示例-Program.cs

    ......
    services.AddRefitClient<IGitHubApi>().ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.github.com"));
    ......
    

使用详解

一、路由特性

在进行接口定义时,每个方法都必须有一个HTTP特性,该特性提供请求方式和相对URL。

Refit提供了六个内置特性:GetPostPutDeletePatchHead,资源的相对URL在特性中指定。

  • 示例

    public interface IGitHubApi
    {[Get("/users/list")]Task<List<User>> GetUserList();//Get请求可以直接在请求地址中携带一些参数[Get("/users/list?sort=desc")]Task<List<User>> GetUserListWithSort();
    }
    

参数占位符

Refit的方法特性支持使用参数占位符,通过{}占位符可以将URL模板中的指定参数与参数列表中对应名称的参数进行关联

  • 示例

    public interface IGitHubApi
    {[Get("/users/{user}")]Task<User> GetUser(string user);
    }
    

如果参数列表中的参数名称与ULR模板中{}所指定的参数名称不一致,可以在参数列表中使用AliasAs(paramName)进行指定

  • 示例

    public interface IGitHubApi
    {[Get("/group/{id}/users")]Task<List<User>> GroupList([AliasAs("id")] int groupId);
    }
    

{}中还可以直接访问参数列表中的对象成员

  • 示例

    
    public interface IGitHubApi
    {[Get("/group/{request.groupId}/users/{request.userId}")]Task<List<User>> GroupList(UserGroupRequest request);
    }class UserGroupRequest{int groupId { get;set; }int userId { get;set; }
    }
    

需要注意的是,方法的参数列表中未匹配的参数一律会作为请求参数

  • 示例

    public interface IGitHubApi
    {[Get("/group/{id}/users")]Task<List<User>> GroupListA([AliasAs("id")] int groupId, [AliasAs("sort")] string sortOrder);[Get("/group/{id}/users")]Task<List<User>> GroupListB([AliasAs("id")] int groupId, string sortOrder);
    }GroupListA(4, "desc");   //请求路径为 /group/4/users?sort=desc 
    GroupListB(4, "desc");   //请求路径为 /group/4/users?sortOrder=desc 
    

路由转换

在声明方法时,如果希望方法的参数作为URL的一部分,那么可以使用{** paramName}进行匹配,使用**可以将匹配到的参数中所携带的斜杠 / 保持原样,不进行编码

  • 示例

    public interface IGitHubApi
    {[Get("/search/{**page}")]Task<List<Page>> Search(string page);
    }Search("admin/products"); // 请求的URL为  /search/admin/products
    

需要注意的是{** paramName}所匹配的参数,其类型必须为string

二、查询字符串

这里指出的查询字符串是指在请求的URL中通过?分隔开的查询参数

1、动态查询字符串

对象作为查询字符串

在Get请求中,如果使用一个引用类型作为方法参数,那么类型对象中所有公共且不为null的属性将为自动成为查询字符串。

  • 自定义类型中,可以使用[AliasAs]特性设置属性序列化和反序列化时键的名称。如果不设置,默认会使用属性名

  • 自定义类中可以使用[Query]特性设置指定属性的前缀和前缀与参数名之间的分隔符。不过通常更多是在接口的参数列表中使用,对类型的所有属性统一设置

  • 枚举类型则可以使用[EnumMember]特性设置序列化和序列化时的对应值

  • 示例

    public class MyQueryParams
    {[AliasAs("order")]public string SortOrder { get; set; }public int Limit { get; set; }public KindOptions Kind { get; set; }
    }public enum KindOptions
    {Foo,[EnumMember(Value = "bar")] //定义序列化和反序列化时对应的值Bar
    }public interface IGitHubApi
    {[Get("/group/{id}/users")]Task<List<User>> GroupList([AliasAs("id")] int groupId, MyQueryParams param);[Get("/group/{id}/users")]Task<List<User>> GroupListWithAttribute([AliasAs("id")] int groupId, [Query(".","search")] MyQueryParams param);
    }var myParams = new MyQueryParams();
    myParams.SortOrder = "desc";
    myParams.Limit = 10;
    myParams.Kind = KindOptions.Bar;GroupList(4, myParams)               //请求的相对URL为 /group/4/users?order=desc&objA.Limit=10&Kind=bar
    GroupListWithAttribute(4, myParams)  //请求的相对URL为/group/4/users?search.order=desc&search.Limit=10&search.Kind=bar
    

字典作为查询字符串

在Get请求中,可以使用字典Dictionary作为参数,其内容会自动序列化为查询字符串,只是无法通过[AliasAs]去指定键名

在非Get请求中的查询字符串

在非Get请求中,如果希望将指定的参数对象作为查询字符串,可以使用[Query]特性来声明对参数进行扁平化处理。实际上不论是Get还是非Get请求,如果要将方法参数对象作为查询字符串,那么建议在参数列表中都使用[Query]特性进行声明。

  • 示例

    [Post("/statuses/update.json")]
    Task<Tweet> PostTweet([Query]TweetParams myParams);
    

2、集合参数作为查询字符串

如果方法参数是一个集合对象,并且希望其内容作为查询字符串来使用,可以在参数列表中使用[Query]特性配合CollectionFormat枚举对集合进行格式化设置,其有效的格式如下:

  • CollectionFormat.Multi:根据参数名称,将集合中的每一个元素作为单独的查询字符串参数

  • CollectionFormat.Csv:将集合中的所有元素使用逗号连接,作为一个查询字符串

  • CollectionFormat.Ssv:将集合中的所有元素使用空格连接,作为一个查询字符串

  • CollectionFormat.Tsv:将集合中的所有元素使用制表符(\t)连接,作为一个查询字符串

  • CollectionFormat.Pipes:将集合中的所有元素使用竖线(|)连接,作为一个查询字符串

  • 示例

    [Get("/users/list")]
    Task Search([Query(CollectionFormat.Multi)]int[] ages);Search(new [] {10, 20, 30})  //"/users/list?ages=10&ages=20&ages=30"[Get("/users/list")]
    Task Search([Query(CollectionFormat.Csv)]int[] ages);Search(new [] {10, 20, 30})  //"/users/list?ages=10%2C20%2C30"  
    

如果向进行全局设置,可以在创建接口对象或进行容器注册时进行设置

  • 示例

    var gitHubApi = RestService.For<IGitHubApi>("https://api.github.com",new RefitSettings {CollectionFormat = CollectionFormat.Multi});
    

3、查询字符串参数解码

解码查询字符串其实就是不对查询字符串进行编码,保留原有的字符串内容,refit支持在进行接口方法定义时,通过使用[QueryUriFormat(UriFormat.Unescaped)]特性声明不对此方法中的查询字符串内容进行编码

  • 示例

    [Get("/query")]
    [QueryUriFormat(UriFormat.Unescaped)]
    Task Query(string q);Query("Select+Id,Name+From+Account") // 请求的相对URL为 /query?q=Select+Id,Name+From+Account
    

4、URL参数的自定义格式化

Refit提供了IUrlParameterFormatter接口来帮助我们对URL参数进行自定义的格式化,这在我们需要对日期、数值等数据进行格式化时十分有效。

实现接口

只需要实现IUrlParameterFormatter接口,并实现Format()方法,在方法中进行自定义格式化即可

  • 示例

    //对日期进行格式化
    public class CustomDateUrlParameterFormatter : IUrlParameterFormatter
    {public string? Format(object? value, ICustomAttributeProvider attributeProvider, Type type){if (value is DateTime dt){return dt.ToString("yyyyMMdd");}return value?.ToString();}
    }
    //对字典进行格式化
    public class CustomDictionaryKeyFormatter : IUrlParameterFormatter
    {public string? Format(object? value, ICustomAttributeProvider attributeProvider, Type type){// Handle dictionary keysif (attributeProvider is PropertyInfo prop && prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>)){// Custom formatting logic for dictionary keysreturn value?.ToString().ToUpperInvariant();}return value?.ToString();}
    }
    

使用自定义格式化

在创建接口对象或进行容器注册时进行设置即可

  • 示例

    var gitHubApi = RestService.For<IGitHubApi>("https://localhost:7234",new RefitSettings
    {UrlParameterFormatter = new CustomDateUrlParameterFormatter()
    });
    

三、请求体(Body)

在进行Post请求时,是需要携带请求体的,此时可以在声明方法时,在参数列表中对希望作为请求体的参数使用[Body]特性。

  • 示例

    [Post("/users/new")]
    Task CreateUser([Body] User user);
    

Refit对请求体的处理主要有如下四种情况

  • 当参数为Stream类型,那么数据会通过 StreamContent 进行流式传输
  • 当参数为string类型,默认情况下会直接将参数作为请求体(也就是Content-Type=text/plain);如果使用了特性 [Body(BodySerializationMethod.Json)],字符串将被作为StringContent 发送,并以 JSON 格式提供(Content-Type = application/json),这通常适用于需要符合 JSON 格式的 API 接口
  • 使用了[Body(BodySerializationMethod.UrlEncoded)]特性的参数,其内容将被 URL 编码。这种情况通常用于表单提交(Content-Type=application/x-www-form-urlencoded
  • 当参数是其他类型(即不属于上面的三种情况)时,则会使用 RefitSettings 中指定的内容序列化器进行序列化,默认情况下是 JSON 格式(也就是[Body(BodySerializationMethod.Json)]

1、流缓存及Content-Length

默认情况下,Refit对请求体进行流处理时,是不进行缓存的(针对所有请求体,不仅仅是文件),这意味着可以从磁盘流式传输文件,而不会产生将整个文件加载到内存中的开销。这样做的缺点是请求上没有设置Content-Length报头。如果需要Refit在进行API请求中发送一个Content-Length头,可以使用[Body(buffered:true)]特性来开启缓存

  • 原因在于,要得到Content-Length就必须先把要进行传输的文件加载到内存中才能正确的计算出来

  • 示例

    这里只是以文件上传为例子,实际上refit对所有的请求体默认都是不进行缓存的

    public interface IMyApi  
    {  [Post("/upload")]  Task UploadFile([Body(buffered: true)] Stream fileStream);  
    }  // 使用时  
    var api = RestService.For<IMyApi>("https://example.com");  
    using (var fileStream = File.OpenRead("largefile.txt"))  
    {  await api.UploadFile(fileStream); // Content-Length 会被设置  
    }  
    

2、Json的序列化管理

关于Json的请求和响应,Refit使用IHttpContentSerializer接口对象其进行序列化和反序列化。

Refit提供了两种Json序列化的实现方式:

  • SystemTextJsonContentSerializer:这是默认的 JSON 序列化器,基于 .NET 内置的 System.Text.Json 库。这个实现专注于高性能和低内存占用,适合对性能有严格要求的应用(默认)
  • NewtonsoftJsonContentSerializer:基于流行的 Newtonsoft.Json 库,这个实现更灵活,可以处理更多复杂的序列化需求,支持配置选项和定制化。

使用Newtonsoft.Json进行序列化

由于默认使用的是SystemTextJsonContentSerializer,所以这里学习一下如何使用NewtonsoftJsonContentSerializer就好了。

如果使用NewtonsoftJsonContentSerializer进行序列化,需要在项目中安装Refit.Newtonsoft.Json依赖库

在这里插入图片描述
然后在创建Refit接口对象或进行容器注册时进行设置即可

  • 示例

    var gitHubApi = RestService.For<IGitHubApi>("https://localhost:7234", new RefitSettings()
    {ContentSerializer = new NewtonsoftJsonContentSerializer()
    });
    

使用Newtonsoft.Json,可以通过JsonConvert.DefaultSettings对其默认的序列化行为进行全局配置。

  • 示例

    JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
    {ContractResolver = new CamelCasePropertyNamesContractResolver(),Converters = { new StringEnumConverter() }
    };
    

也可以在创建Refit接口对象或进行容器注册针对指定的API接口进行配置

  • 示例

    var gitHubApi = RestService.For<IGitHubApi>("https://api.github.com",new RefitSettings {ContentSerializer = new NewtonsoftJsonContentSerializer(new JsonSerializerSettings { ContractResolver = new SnakeCasePropertyNamesContractResolver()})});var otherApi = RestService.For<IOtherApi>("https://api.example.com",new RefitSettings {ContentSerializer = new NewtonsoftJsonContentSerializer(new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()})});
    

属性的序列化别名设置

如果使用的是Newtonsoft.Json,那么在类型的属性上,可以通过[JsonProperty]对属性进行自定义序列化设置,例如[JsonProperty(PropertyName="b"),其效果与[AliasAs("b")]一样

  • 示例

    public class Foo
    {// Works like [AliasAs("b")] would in form posts (see below)[JsonProperty(PropertyName="b")]public string Bar { get; set; }
    }
    

如果使用的是默认的System.Text.Json,则可以使用[JsonPropertyName]进行属性序列化别名的设置

  • 示例

    public class User  
    {  [JsonPropertyName("full_name")]  public string FullName { get; set; }  public int Age { get; set; }  
    }
    

3、XML序列化管理

Refit默认使用Json进行序列化,因此如果不进行配置,所有的请求和响应都会被处理为Json格式。

Refit的XML序列化使用System.Xml.Serialization.XmlSerializer,要配置XML序列化,需要将ContentSerializer设置为XmlContentSerializer对象

  • 示例

    var gitHubApi = RestService.For<IXmlApi>("https://www.w3.org/XML",new RefitSettings {ContentSerializer = new XmlContentSerializer()});
    

System.Xml.Serialization.XmlSerializer提供了许多序列化配置项,可以在创建或注册Refit接口时候进行设置。

  • 示例

    var gitHubApi = RestService.For<IXmlApi>("https://www.w3.org/XML",new RefitSettings {ContentSerializer = new XmlContentSerializer(new XmlContentSerializerSettings{XmlReaderWriterSettings = new XmlReaderWriterSettings(){ReaderSettings = new XmlReaderSettings{IgnoreWhitespace = true}}})});
    

此外,System.Xml.Serialization.XmlSerializer还提供了一些可以对类型属性的序列化进行自定义设置的特性

  • 示例

    public class Foo
    {[XmlElement(Namespace = "https://www.w3.org/XML")]public string Bar { get; set; }
    }
    

4、表单提交

如果需要进行表单提交,那么需要使用[Body(BodySerializationMethod.UrlEncoded)]特性对方法参数进行声明,其会使用application/x-www-form-urlencoded 格式进行序列化。

字典参数

可以使用IDictionary类型作为参数进行表单提交。

  • 示例

    public interface IMeasurementProtocolApi
    {[Post("/collect")]Task Collect([Body(BodySerializationMethod.UrlEncoded)] Dictionary<string, object> data);
    }var data = new Dictionary<string, object> {{"v", 1},{"tid", "UA-1234-5"},{"cid", new Guid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c")},{"t", "event"},
    };// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=event
    await api.Collect(data);
    

object类型参数

可以使用object类型作为参数进行表单提交,其公开可读的属性会被序列化为表单字段。且类型中的属性同样支持使用[AliasAs("whatever")][JsonProperty(PropertyName = "whatever")][JsonPropertyName("whatever")]进行属性的别名设置,但是[AliasAs("whatever")]的优先级更高

  • 示例

    public interface IMeasurementProtocolApi
    {[Post("/collect")]Task Collect([Body(BodySerializationMethod.UrlEncoded)] Measurement measurement);
    }public class Measurement
    {public int v { get { return 1; } }[AliasAs("tid")]public string WebPropertyId { get; set; }[JsonProperty(PropertyName = "one")][AliasAs("cid")]public Guid ClientId { get; set; }[JsonProperty(PropertyName = "t")]public string Type { get; set; }public object IgnoreMe { private get; set; }
    }var measurement = new Measurement {WebPropertyId = "UA-1234-5",ClientId = new Guid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c"),Type = "event"
    };// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=event
    await api.Collect(measurement);
    

需要注意的是,[AliasAs]特性对查询字符串参数和表单数据POST 请求的序列化有效,但对响应数据的反序列化无效,因此如果响应内容中的字段名称与接纳类型的属性名不同时,还是需要使用[JsonProperty][JsonPropertyName]特性进行别名指定。

四、请求头设置

1、静态请求头

Refit提供了[Headers]特性,可以在声明接口方法时,设置一个或多个请求头信息。

  • 示例

    public interface IGitHubApi
    {[Headers("User-Agent: Awesome Octocat App")][Get("/users/{user}")]Task<User> GetUser(string user);
    }
    

如果希望对接口中的所有方法都设置同样的头信息,那么直接在接口上使用[Headers]特性就可以了

  • 示例

    [Headers("User-Agent: Awesome Octocat App")]
    public interface IGitHubApi
    {[Get("/users/{user}")]Task<User> GetUser(string user);[Post("/users/new")]Task CreateUser([Body] User user);
    }
    

2、动态请求头

如果请求头中的信息需要根据运行时动态变化,那么可以将[Headers]特性直接在参数列表中对参数使用即可。

  • 示例

    [Get("/users/{user}")]
    Task<User> GetUser(string user, [Header("Authorization")] string authorization);// Will add the header "Authorization: token OAUTH-TOKEN" to the request
    var user = await GetUser("octocat", "token OAUTH-TOKEN");
    

批量请求头

如果需要设置多个动态请求头,可以使用IDictionary<string, string>类型作为参数,并对其使用[HeaderCollection]特性。

  • 示例

    [Get("/users/{user}")]
    Task<User> GetUser(string user, [HeaderCollection] IDictionary<string, string> headers);var headers = new Dictionary<string, string> {{"Authorization","Bearer tokenGoesHere"}, {"X-Tenant-Id","123"}};
    var user = await GetUser("octocat", headers);
    

3、授权头信息

添加授权头信息是很常见的操作,因此Refit提供了[Authorize]特性专门用于声明授权令牌参数,并且可以设置授权策略,例如JWT可以使用[Authorize("Bearer")]

  • 示例

    [Get("/users/{user}")]
    Task<User> GetUser(string user, [Authorize("Bearer")] string token);// Will add the header "Authorization: Bearer OAUTH-TOKEN}" to the request
    var user = await GetUser("octocat", "OAUTH-TOKEN");
    

授权令牌简化方案

通常,在API请求中携带授权令牌是很常见的需求,如果对每一个方法都要传入授权令牌,并对参数使用[Authorize("Bearer")]就太麻烦了,针对此,Refit提供了更为简介的方案。

  • 第一步,在创建Refit接口或注册时,通过RefitSettings,配置AuthorizationHeaderValueGetter委托,每次需要授权头信时候都会从此委托中获取授权令牌信息

  • 第二步,在需要携带授权令牌的接口或方法上使用[Headers("Authorization: Bearer")]特性即可

  • 示例

    [Headers("Authorization: Bearer")]
    public interface IGitHubApi
    {[Get("/users/{user}")]Task<string> GetUser(string user);
    }
    
    var gitHubApi = RestService.For<IGitHubApi>("https://localhost:7234", new RefitSettings()
    {AuthorizationHeaderValueGetter = (request,token) => Task.FromResult("返回授权令牌")
    });
    

4、请求头的全局配置

如果有一些头部信息是必须在所有的请求都携带的,对每一个接口或方法都进行配置未免显得繁琐和冗余,此时可以借助DelegatingHandler中间件,在 HTTP 请求和响应的处理管道中插入自定义逻辑,通过继承DelegatingHandler并重写SendAsync()方法,在SendAsync()方法中就可以在每个请求中自动添加所需的头部,而不需要在每个 API 方法中手动添加。对接口或方法特有的请求头,再另外使用特性去配置即可。

  • DelegatingHandler 是 .NET 中用于处理 HTTP 请求和响应的一个抽象类,属于 System.Net.Http 命名空间。它可以在 HTTP 请求的处理管道中插入自定义逻辑,是实现 HTTP 客户端自定义行为的一个重要组件

实现DelegatingHandler

这里假设我们在项目中已经实现了ITenantProvider 接口来获取当前租户的信息,以及一个 IAuthTokenStore接口来获取授权令牌,可以进行如下DelegatingHandler实现。

  • 示例

    public class CustomHeaderHandler : DelegatingHandler  
    {  private readonly ITenantProvider _tenantProvider;  private readonly IAuthTokenStore _authTokenStore;  public CustomHeaderHandler(ITenantProvider tenantProvider, IAuthTokenStore authTokenStore)  {  _tenantProvider = tenantProvider;  _authTokenStore = authTokenStore;  }  protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)  {  // 获取租户 ID 和授权令牌  var tenantId = _tenantProvider.GetCurrentTenantId();  var authToken = await _authTokenStore.GetAuthTokenAsync();  // 添加头部  request.Headers.Add("X-Tenant-Id", tenantId);  request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);  // 继续处理请求  return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);  }  
    }  
    

定义API接口

  • 示例

    public interface IGitHubApi
    {[Get("/users/{user}")]Task<User> GetUser(string user);
    }public class User
    {public string Id { get; set; }public string Name { get; set; }
    }
    

注册中间件及API接口

  • 示例-Program.cs

    ......
    builder.Services.AddTransient<ITenantProvider, TenantProvider>();
    builder.Services.AddTransient<IAuthTokenStore, AuthTokenStore>();
    builder.Services.AddTransient<CustomHeaderHandler>();builder.Services.AddRefitClient<IGitHubApi>().ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com")).AddHttpMessageHandler<CustomHeaderHandler>();
    ......
    

如果没有使用依赖容器,那么直接在创建Refit接口时候传入对应的接口对象即可

  • 示例

    var api = RestService.For<IGitHubApi>(new HttpClient(new CustomHeaderHandler(tenantProvider, authTokenStore)){BaseAddress = new Uri("https://api.example.com")}
    );
    

5、请求头的覆盖

在Refit中,如果同一个请求上对同一个请求头设置了多次,那么会根据如下优先级对请求头进行覆盖(仅覆盖同名的请求头):

  • 在接口上使用[Headers]特性进行设置(最低优先级)

  • 在方法上使用[Headers]特性进行设置

  • 在方法的参数上使用[Headers][HeaderCollection]进行设置

  • 示例

    [Headers("X-Emoji: :rocket:")]
    public interface IGitHubApi
    {[Get("/users/list")]Task<List> GetUsers();[Get("/users/{user}")][Headers("X-Emoji: :smile_cat:")]Task<User> GetUser(string user);[Post("/users/new")][Headers("X-Emoji: :metal:")]Task CreateUser([Body] User user, [Header("X-Emoji")] string emoji);
    }// X-Emoji: :rocket:
    var users = await GetUsers();// X-Emoji: :smile_cat:
    var user = await GetUser("octocat");// X-Emoji: :trollface:
    await CreateUser(user, ":trollface:");
    

6、删除请求头

如果希望对某个接口或方法删除指定的请求头,可以通过如下两种方式:

  • 静态请求头的方式设置请求头,且不设置请求头的值
  • 动态请求头的方式设置请求头,且将值设置为null

需要注意删除和设置为空""的区别

  • 示例

    [Headers("X-Emoji: :rocket:")]
    public interface IGitHubApi
    {[Get("/users/list")][Headers("X-Emoji")] // 删除 X-Emoji 请求头Task<List> GetUsers();[Get("/users/{user}")][Headers("X-Emoji:")] // 设置 X-Emoji 请求头为 ""Task<User> GetUser(string user);[Post("/users/new")]Task CreateUser([Body] User user, [Header("X-Emoji")] string emoji);
    }// No X-Emoji header
    var users = await GetUsers();// X-Emoji:
    var user = await GetUser("octocat");// No X-Emoji header
    await CreateUser(user, null);// X-Emoji:
    await CreateUser(user, "");
    

五、中间件的数据传递

1、参数数据传输

如果有些运行过程中的数据需要传递给DelegatingHandler中间件,需要对方法参数使用[Property]特性进行声明。Refit会将使用[Property]特性进行声明的方法参数传入到HttpRequestMessage.PropertiesHttpRequestMessage.Options

  • NET 5 以后HttpRequestMessage.Properties已被标记为过时,Refit会将数据放入到HttpRequestMessage.Options

声明需要传递的参数

  • 示例

    public interface IGitHubApi
    {[Post("/users/new")]Task CreateUser([Body] User user, [Property("SomeKey")] string someValue);[Post("/users/new")]Task CreateUser([Body] User user, [Property] string someOtherKey);
    }
    

读取传递的数据

DelegatingHandler中间件中进行读取

  • 示例

    class RequestPropertyHandler : DelegatingHandler
    {public RequestPropertyHandler(HttpMessageHandler innerHandler = null) : base(innerHandler ?? new HttpClientHandler()) {}protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){// See if the request has a the propertyif(request.Properties.ContainsKey("SomeKey")){var someProperty = request.Properties["SomeKey"];//do stuff}if(request.Properties.ContainsKey("someOtherKey")){var someOtherProperty = request.Properties["someOtherKey"];//do stuff}return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);}
    }
    

注册中间件

这里多提一嘴,记得向依赖容器注册中间件

  • 示例-Program.cs

    ......
    builder.Services.AddTransient<RequestPropertyHandler>();builder.Services.AddRefitClient<IGitHubApi>().ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com")).AddHttpMessageHandler<RequestPropertyHandler>();
    ......
    

如果没有使用依赖容器,那么则记得要在创建Refit接口时使用中间件

  • 示例

    var api = RestService.For<IGitHubApi>(new HttpClient(new RequestPropertyHandler()){BaseAddress = new Uri("https://api.example.com")}
    );
    

2、接口及方法信息的获取

这里指的是为了使用Refit所定义的接口及其方法。

在实际开发时,有时候可能需要知道当前所调用的方法是来自于哪个接口,特别是在使用的接口继承了某个公共接口的情况时。例如下列情况:

  • 示例

    public interface IGetAPI<TEntity>
    {[Get("/{key}")]Task<TEntity> Get(long key);
    }public interface IUsersAPI : IGetAPI<User>
    {
    }public interface IOrdersAPI : IGetAPI<Order>
    {
    }
    

获取接口信息

Refit提供了HttpRequestMessageOptions.InterfaceType静态字符串专门用于从HttpRequestMessage.PropertiesHttpRequestMessage.Options中获取对应接口的Type类型

  • .Net5之后,从HttpRequestMessage.Properties改为HttpRequestMessage.Options

通过获取接口信息,可以在中间件中进行一些业务处理,例如更换访问的URL

  • 示例

    class RequestPropertyHandler : DelegatingHandler
    {public RequestPropertyHandler(HttpMessageHandler innerHandler = null) : base(innerHandler ?? new HttpClientHandler()) {}protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){//通过 HttpMessageRequestOptions.InterfaceType 获取到接口的Type类型Type interfaceType = (Type)request.Properties[HttpMessageRequestOptions.InterfaceType];var builder = new UriBuilder(request.RequestUri);// Alter the Path in some way based on the interface or an attribute on itbuilder.Path = $"/{interfaceType.Name}{builder.Path}";// Set the new Uri on the outgoing messagerequest.RequestUri = builder.Uri;return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);}
    }
    

获取方法信息

Refit提供了HttpRequestMessageOptions.RestMethodInfo静态字符串专门用于从HttpRequestMessage.PropertiesHttpRequestMessage.Options中获取对应方法的RestMethodInfo类型,所有方法所相关的信息都封装在RestMethodInfo中,特别是在需要使用反射时,可以访问到完整的MethodInfo对象。

  • 示例

    class RequestPropertyHandler : DelegatingHandler
    {public RequestPropertyHandler(HttpMessageHandler innerHandler = null) : base(innerHandler ?? new HttpClientHandler()) {}protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){// Get the method infoif (request.Options.TryGetValue(HttpRequestMessageOptions.RestMethodInfo,out RestMethodInfo restMethodInfo)){var builder = new UriBuilder(request.RequestUri);// Alter the Path in some way based on the method info or an attribute on itbuilder.Path = $"/{restMethodInfo.MethodInfo.Name}{builder.Path}";// Set the new Uri on the outgoing messagerequest.RequestUri = builder.Uri;}return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);}
    }
    

六、Multipart uploads

Multipart uploads指的是多部份上传,类似于表单中携带一个或多个文件进行上传的情况。

Refit支持使用[Multipart]特性声明方法,使用[Multipart]特性的方法将会以Content-Type=multipart/form-data类型提交。

多部份上传所支持的参数类型

此时该方法所支持的参数类型如下:

  • string:字符串类型,参数名将用作表单数据的名称,字符串值作为其值
  • byte[]:字节数组,通常用于已经将内容加载到内存中的情况
  • Stream:流,用于处理文件或数据流,常用于大文件
  • FileInfo:文件信息,表示文件的元数据,会自动根据文件信息获取文件进行上传

字段名称的优先级

在多部份上传的数据中,字段名称的优先级如下:

  • multipartItem.Name:如果在运行时动态的指定了名称并且不为 null,则优先使用此名称,可以在执行时为表单数据部分命名
  • [AliasAs] 特性:可用于装饰方法签名中的流参数,为其提供一个静态名称
  • MultipartItem参数名称:这是默认的字段名称,直接根据方法签名中定义的参数名称使用

边界设置

边界指的是在多部份上传时,对不同部分进行分割的标识符,可以通过[Multipart(boundaryText)]进行设置,如果没有设置则默认使用----MyGreatBoundary

  • 边界效果示意

    ------MyGreatBoundary  
    Content-Disposition: form-data; name="field1"  value1  
    ------MyGreatBoundary  
    Content-Disposition: form-data; name="file"; filename="example.txt"  
    Content-Type: text/plain  (文件内容)  
    ------MyGreatBoundary--  
    

边界字符串可以帮助服务器解析请求中的不同部分,服务器用边界来识别这些部分的开始和结束。

文件名和内容类型的指定

对于 byte[]StreamFileInfo 参数,必须使用包装类来指定文件名和内容类型。具体的包装类包括:

  • ByteArrayPart:用于字节数组

  • StreamPart:用于流

  • FileInfoPart:用于文件信息

  • 示例-Stream

    public interface IMyApi  
    {  [Multipart]  [Post("/users/textFile")]Task UploadFile([AliasAs("file")] StreamPart fileStream, string description);  
    }  // 调用示例  
    using (var stream = File.OpenRead("path/to/largeFile.txt"))  
    {  var part = new StreamPart(stream, "largeFile.txt", "text/plain");  await api.UploadFile(part, "Large file description");  
    }  
    
  • 示例-byte[]

    public interface IMyApi  
    {  [Multipart]  [Post("/users/textFile")]Task UploadFile([AliasAs("file")] ByteArrayPart file,string description);  
    }  // 调用示例  
    var byteArray = File.ReadAllBytes("path/to/file.txt");  
    var part = new ByteArrayPart(byteArray, "file.txt", "text/plain");  
    await api.UploadFile(part, "File description");  
    
  • 示例-FileInfo

    public interface IMyApi  
    {  [Multipart]  [Post("/users/textFile")]Task UploadFile([AliasAs("file")]FileInfoPart file,string description);  
    }  // 调用示例  
    var fileInfo = new FileInfo("path/to/file.txt");  
    var part = new FileInfoPart(fileInfo);  
    await api.UploadFile(part, "File description");  
    

七、响应类型

在Refit中,所有的网络请求必须是异步的,所有请求都要返回一个 Task(表示正在进行的异步操作)或 IObservable(用于响应式编程)。

返回Task

如果方法返回 Task 而不带类型参数,这意味着调用只关注请求是否成功,而不关心返回的具体内容。示例中 CreateUser 方法创建用户,但不返回任何数据,只确认请求是否成功。

  • 示例

    [Post("/users/new")]  
    Task CreateUser([Body] User user);  
    

返回Task<T>

如果返回类型是Task<T>,则表示可以接收响应的内容,通常是从服务器返回的 JSON 数据(自动进行反序列化),或是一些基础的基本类型,例如stringint等等,这种情况下也是不关注响应的元数据的。

  • 示例

    // 获取用户内容作为字符串  
    [Get("/users/{user}")]  
    Task<string> GetUser(string user); 
    

返回ApiResponse<T>

使用 ApiResponse<T> 作为返回类型能够获取请求和响应的元数据,例如 HTTP 状态码、响应头等,同时也能获得反序列化后的内容

  • 通过ApiResponse<T>response.StatusCode获取状态码

  • 通过ApiResponse<T>IsSuccessful 属性来判断请求是否成功,并进一步处理响应

  • 通过ApiResponse<T>Content属性可以获取到T对象

  • 通过ApiResponse<T>Headers属性可以获取到响应头信息

  • 通过ApiResponse<T>Headers.Server属性可以获取到服务器信息

  • 示例

    [Get("/users/{user}")]
    Task<ApiResponse<User>> GetUser(string user);var response = await gitHubApi.GetUser("octocat");
    var httpStatus = response.StatusCode;if(response.IsSuccessful)
    {//YAY! Do the thing...
    }var serverHeaderValue = response.Headers.Server != null ? response.Headers.Server.ToString() : string.Empty;var customHeaderValue = string.Join(',', response.Headers.GetValues("A-Custom-Header"));foreach(var header in response.Headers)
    {var headerName = header.Key;var headerValue = string.Join(',', header.Value);
    }var user = response.Content;
    

返回IObservable<HttpResponseMessage>

当发出网络请求并返回一个 IObservable<HttpResponseMessage> 时,将接收到一个包含 HTTP 响应的对象,这个对象可以被用来获取请求的详细信息,比如状态码、响应头和响应体等。

由于很少用到IObservable<HttpResponseMessage>类型,这里就不展开说明了。哪天用到了再补充IObservable接口的用法

八、接口的使用

1、泛型接口

Refit允许使用泛型接口,这一点跟常规泛型接口的使用是一样的

  • 示例

    public interface IReallyExcitingCrudApi<T, in TKey> where T : class
    {[Post("")]Task<T> Create([Body] T payload);[Get("")]Task<List<T>> ReadAll();[Get("/{key}")]Task<T> ReadOne(TKey key);[Put("/{key}")]Task Update(TKey key, [Body]T payload);[Delete("/{key}")]Task Delete(TKey key);
    }var api = RestService.For<IReallyExcitingCrudApi<User, string>>("http://api.example.com/users");
    

2、接口继承

Refit支持接口继承,从而避免重复声明一样的方法

  • 示例

    public interface IBaseService
    {[Get("/resources")]Task<Resource> GetResource(string id);
    }public interface IDerivedServiceA : IBaseService
    {[Delete("/resources")]Task DeleteResource(string id);
    }public interface IDerivedServiceB : IBaseService
    {[Post("/resources")]Task<string> AddResource([Body] Resource resource);
    }
    

需要注意的是,使用继承时,请求头的配置也会被继承。

  • 示例

    [Headers("User-Agent: AAA")]
    public interface IAmInterfaceA
    {[Get("/get?result=Ping")]Task<string> Ping();
    }[Headers("User-Agent: BBB")]
    public interface IAmInterfaceB : IAmInterfaceA
    {[Get("/get?result=Pang")][Headers("User-Agent: PANG")]Task<string> Pang();[Get("/get?result=Foo")]Task<string> Foo();
    }public interface IAmInterfaceC : IAmInterfaceA, IAmInterfaceB
    {[Get("/get?result=Foo")]Task<string> Foo();
    }
    

上述示例中:

  • IAmInterfaceB 的Ping()和Foo()方法将会使用 User-Agent: BBB 请求头
  • IAmInterfaceB 的Pang()方法将会使用 User-Agent: PANG 请求头
  • 如果IAmInterfaceB 接口上没有设定请求头,那么Foo()方法将会使用 User-Agent: AAA 请求头
  • IAmInterfaceC 的 Foo()方法会先查看IAmInterfaceA所定义的请求头,如果有则直接使用,如果没有则查看IAmInterfaceB的请求头,这个跟继承的顺序有关,按顺序往下,找到第一个就直接使用

3、接口的默认实现

从C# 8.0开始,接口中可以定义默认实现方法。Refit支持在接口中通过默认实现为接口提供额外的逻辑。

  • 示例

    public interface IApiClient
    {// implemented by Refit but not exposed publicly[Get("/get")]internal Task<string> GetInternal();// Publicly available with added logic applied to the result from the API callpublic async Task<string> Get()=> FormatResponse(await GetInternal());private static String FormatResponse(string response)=> $"The response is: {response}";
    }
    

九、使用HttpClientFactory

Refit对ASP.Net Core 2.1 以后出现的HttpClientFactory有着一流的支持。

在ASP.Net Core中使用Refit,直接安装Refit.HttpClientFactory依赖库即可。
在这里插入图片描述
注册Refit接口

ASP.Net Core 项目中,可以在Program.cs中,通过AddRefitClient<IWebApi>()注册Refit接口并进行基RUL的配置

  • 示例-Program.cs

    ......
    builder.Services.AddRefitClient<IGitHubApi>().ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com"));//.AddHttpMessageHandler<MyHandler>()            //添加中间件//.SetHandlerLifetime(TimeSpan.FromMinutes(2));  //设置中间件的声明周期
    ......
    

进行Refit配置

如果需要对Refit进行自定义设置,可以有如下两种方式:

  • 先进行RefitSettings对象的配置,然后再直接传入到AddRefitClient<IWebApi>(RefitSettings settings)方法中

  • 示例-Program.cs

    var settings = new RefitSettings();
    builder.Services.AddRefitClient<IWebApi>(settings).ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com"));// Add additional IHttpClientBuilder chained methods as required here:// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));
    
  • 使用AddRefitClient<IWebApi>(Func<IServiceProvider, RefitSettings?>? settingsAction)方法获取容器注入的服务提供对象后再进行配置

  • 示例-Program.cs

    ......
    //从容器中注入服务提供对象后在进行设置
    builder.Services.AddRefitClient<IWebApi>(provider => new RefitSettings() { /* configure settings */ }).ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com"));// .AddHttpMessageHandler<MyHandler>()// .SetHandlerLifetime(TimeSpan.FromMinutes(2));
    ......
    

需要注意的是,RefitSettings中的一些属性会被忽略,因为HttpClientHttpClientHandlers是由HttpClientFactory管理而不是Refit。

依赖注入获得API接口对象

注册完成后,就可以再需要使用的地方通过依赖注入获得Refit接口对象了。

  • 示例

    public class HomeController : Controller
    {public HomeController(IWebApi webApi){_webApi = webApi;}private readonly IWebApi _webApi;public async Task<IActionResult> Index(CancellationToken cancellationToken){var thing = await _webApi.GetSomethingWeNeed(cancellationToken);return View(thing);}
    }
    

十、异常处理

1、Refit对异常的处理

Refit对于异常处理的方式会根据方法返回的类型而有所不同,例如返回Task<T>Task<IApiResponse>Task<IApiResponse<T>>Task<ApiResponse<T>>等类型。

返回 Task<T>

如果接口方法返回 Task<T>,Refit将会抛出任何由ExceptionFactory产生的 ApiException异常或在尝试反序列化为 Task<T> 时产生的异常。

  • 示例

    try
    {var result = await awesomeApi.GetFooAsync("bar");
    }
    catch (ApiException exception)
    {// 异常处理逻辑  
    }
    

返回 Task<IApiResponse>Task<IApiResponse<T>> 或 Task<ApiResponse<T>>

当接口方法返回Task<IApiResponse>Task<IApiResponse<T>> 或 Task<ApiResponse<T>>时,Refit将捕获所有由ExceptionFactory产生的ApiException异常以及在尝试反序列化ApiResponse<T> 时发生的异常,并将捕获到的异常填充到 ApiResponse<T> 的 Error 属性中,而不会直接抛出。

  • 示例

    var response = await _myRefitClient.GetSomeStuff();  
    if (response.IsSuccessful)  
    {  // 执行业务逻辑  
    }  
    else  
    {  _logger.LogError(response.Error, response.Error.Content);  
    }  
    

需要注意的是,ApiResponse<T>IsSuccessful所检查的是状态是否在200~299内且没有任何其他异常(例如反序列化时产生的异常),如果只是想检查HTTP响应状态,可以使用IsSuccessStatusCode属性。

2、自定义异常工厂

Refit允许用户提供自定义的异常处理逻辑。

处理响应的自定义异常工厂

如果是想自定义处理响应时发生异常,可以通过设置RefitSettings 中的ExceptionFactory属性来实现,例如可以选择在处理响应时忽略所有异常。

  • 示例

    var nullTask = Task.FromResult<Exception>(null);  var gitHubApi = RestService.For<IGitHubApi>("https://api.github.com",  new RefitSettings {  ExceptionFactory = httpResponse => nullTask;  });  
    

处理反序列化的自定义异常工厂

如果希望自定义处理反序列化时产生的异常,可以通过设置RefitSettings 中的DeserializationExceptionFactory属性来实现

  • 示例

    var nullTask = Task.FromResult<Exception>(null);var gitHubApi = RestService.For<IGitHubApi>("https://api.github.com",new RefitSettings {DeserializationExceptionFactory = (httpResponse, exception) => nullTask;});
    

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu24.04配置DINO-Tracker

一、引言 记录 Ubuntu 配置的第一个代码过程 二、更改conda虚拟环境的默认安装路径 鉴于不久前由于磁盘空间不足引发的重装系统的惨痛经历&#xff0c;在新系统装好后当然要先更改虚拟环境的默认安装路径。 输入指令&#xff1a; conda info可能因为我原本就没有把 Anacod…

vulnhub靶场【哈利波特】三部曲之Aragog

前言 使用virtual box虚拟机 靶机&#xff1a;Aragog : 192.168.1.101 攻击&#xff1a;kali : 192.168.1.16 主机发现 使用arp-scan -l扫描&#xff0c;在同一虚拟网卡下 信息收集 使用nmap扫描 发现22端口SSH服务&#xff0c;openssh 80端口HTTP服务&#xff0c;Apach…

顶刊算法 | 鱼鹰算法OOA-BiTCN-BiGRU-Attention多输入单输出回归预测(Maltab)

顶刊算法 | 鱼鹰算法OOA-BiTCN-BiGRU-Attention多输入单输出回归预测&#xff08;Maltab&#xff09; 目录 顶刊算法 | 鱼鹰算法OOA-BiTCN-BiGRU-Attention多输入单输出回归预测&#xff08;Maltab&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实…

getchar()

getchar():从计算机终端&#xff08;一般是键盘&#xff09;输入一个字符 1、getchar返回的是字符的ASCII码值&#xff08;整数&#xff09;。 2、getchar在读取结束或者失败的时候&#xff0c;会返回EOF 输入密码并确认&#xff1a; scanf读取\n之前的内容即12345678 回车符…

动态规划-----路径问题

动态规划-----路径问题 下降最小路径和1&#xff1a;状态表示2&#xff1a;状态转移方程3 初始化4 填表顺序5 返回值6 代码实现 总结&#xff1a; 下降最小路径和 1&#xff1a;状态表示 假设&#xff1a;用dp[i][j]表示&#xff1a;到达[i,j]的最小路径 2&#xff1a;状态转…

实现PDF文档加密,访问需要密码

01. 背景 今天下午老板神秘兮兮的来问我&#xff0c;能不能做个文档加密功能&#xff0c;就是那种用户下载打开需要密码才能打开的那种效果。boss都发话了&#xff0c;那必须可以。 需求&#xff1a;将 pdf 文档经过加密处理&#xff0c;客户下载pdf文档&#xff0c;打开文档需…

HarmonyOS Next 模拟器安装与探索

HarmonyOS 5 也发布了有一段时间了&#xff0c;不知道大家实际使用的时候有没有发现一些惊喜。当然随着HarmonyOS 5的更新也带来了很多新特性&#xff0c;尤其是 HarmonyOS Next 模拟器。今天&#xff0c;我们就来探索一下这个模拟器&#xff0c;看看它能给我们的开发过程带来什…

深入探索进程间通信:System V IPC的机制与应用

目录 1、System V概述 2.共享内存&#xff08;shm&#xff09; 2.1 shmget — 创建共享内存 2.1.2 ftok&#xff08;为shmmat创建key值&#xff09; 2.1.3 为什么一块共享内存的标志信息需要用户来传递 2.2 shmat — 进程挂接共享内存 2.3 shmdt — 断开共享内存连接 2.4…

Rust : 生成日历管理markdown文件的小工具

需求&#xff1a; 拟生成以下markdown管理小工具&#xff0c;这也是我日常工作日程表。 可以输入任意时间段&#xff0c;运行后就可以生成以上的markdown文件。 一、toml [package] name "rust-workfile" version "0.1.0" edition "2021"[d…

mean,median,mode,var,std,min,max函数

剩余的函数都放在这篇里面吧 m e a n mean mean函数可以求平均值 a a a为向量时&#xff0c; m e a n ( a ) mean(a) mean(a)求向量中元素的平均值 a a a为矩阵时&#xff0c; m e a n ( a , 1 ) mean(a,1) mean(a,1)求矩阵中各列元素的平均值&#xff1b; m e a n ( a , 2 )…

Android studio 签名加固后的apk文件

Android studio打包时&#xff0c;可以选择签名类型v1和v2&#xff0c;但是在经过加固后&#xff0c;签名就不在了&#xff0c;或者只有v1签名&#xff0c;这样是不安全的。 操作流程&#xff1a; 1、Android studio 对项目进行打包&#xff0c;生成有签名的apk文件&#xff…

【计算机网络】实验2:总线型以太网的特性

实验 2&#xff1a;总线型以太网的特性 一、 实验目的 加深对MAC地址&#xff0c;IP地址&#xff0c;ARP协议的理解。 了解总线型以太网的特性&#xff08;广播&#xff0c;竞争总线&#xff0c;冲突&#xff09;。 二、 实验环境 • Cisco Packet Tracer 模拟器 三、 实…

PHP RabbitMQ连接超时问题

问题背景 Error: The connection timed out after 3 sec while awaiting incoming data 看到这个报错&#xff0c;我不以为意&#xff0c;认为是我设置的超时时间不够导致的&#xff0c;那就设置长一点 Error: The connection timed out after 300 sec while awaiting incom…

asp.net core过滤器应用

筛选器类型 授权筛选器 授权过滤器是过滤器管道的第一个被执行的过滤器&#xff0c;用于系统授权。一般不会编写自定义的授权过滤器&#xff0c;而是配置授权策略或编写自定义授权策略。简单举个例子。 using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCo…

Linux DNS解释器

作用 DNS&#xff08;Domain Name System&#xff09;是互联网上的一项服务&#xff0c;用于将域名和IP地址进行相互映射&#xff0c;使人 更方便的访问互联网 正向解析&#xff1a;域名->IP 反向解析&#xff1a;IP->域名 连接方式 DNS使用53端口监听网络 查看方法&a…

3.STM32通信接口之SPI通信---SPI实战(W25Q64存储模块介绍)《精讲》

上一节介绍了SPI的通信过程和方法&#xff0c;接下来就要进行STM32与外围模块通信了&#xff0c;这个模块是一块非易失型存储芯片&#xff0c;能够提供8MB的存储空间。接下来跟着Whappy脚步&#xff0c;进行探索新大陆吧&#xff01;【免费】W25Q64(中英文数据手册)资源-CSDN文…

嵌入式系统应用-LVGL的应用-平衡球游戏 part2

平衡球游戏 part2 4 mpu60504.1 mpu6050 介绍4.2 电路图4.3 驱动代码编写 5 游戏界面移植5.1 移植源文件5.2 添加头文件 6 参数移植6.1 4 mpu6050 4.1 mpu6050 介绍 MPU6050是一款由InvenSense公司生产的加速度计和陀螺仪传感器&#xff0c;广泛应用于消费电子、机器人等领域…

java将word docx pdf转换为图片(不需要额外下载压缩包,直接导入maven坐标)

(本代码实现的是将第1页转为图片&#xff0c;主要用于制作文件缩略图) pdf转图片容易 docx转图片麻烦&#xff0c;看其他博客可以直接导入maven坐标&#xff0c;但我知道那是需要付费且有时限的包 本着简单实用的心&#xff0c;我找到法子了 pdf转图片&#xff1a;有库直接转…

C#学写了一个程序记录日志的方法(Log类)

1.错误和警告信息单独生产文本进行记录&#xff1b; 2.日志到一定内存阈值可以打包压缩&#xff0c;单独存储起来&#xff0c;修改字段MaxLogFileSizeForCompress的值即可&#xff1b; 3.Log类调用举例&#xff1a;Log.Txt(JB.信息,“日志记录内容”,"通道1"); usi…

linux(centos) 环境部署,安装JDK,docker(mysql, redis,nginx,minio,nacos)

目录 1.安装JDK (非docker)1.1 将文件放在目录下&#xff1a; /usr/local/jdk1.2 解压至当前目录1.3 配置环境变量 2.安装docker2.1 验证centos内核2.2 安装软件工具包2.3 设置yum源2.4 查看仓库中所有docker版本&#xff0c;按需选择安装2.5 安装docker2.6 启动docker 并 开机…