引入MassTransit消息通信,支持请求响应与点对点模式

本次提交集成MassTransit(基于RabbitMQ),实现微服务间请求-响应与点对点消息通信示例。新增MassTransit.Message项目,统一消息契约;主服务和库存服务均完成消息总线配置与相关接口/消费者实现。移除冗余测试代码,完善注释,提升系统异步解耦与扩展能力。
This commit is contained in:
YangQiang 2026-03-19 15:24:39 +08:00
parent 016987b101
commit 1ff9fd4cc2
18 changed files with 518 additions and 278 deletions

View File

@ -0,0 +1,3 @@
namespace MassTransit.Message;
public record CheckInventoryRequest(string ProductId, int Quantity);

View File

@ -0,0 +1,3 @@
namespace MassTransit.Message;
public record CheckInventoryResponse(bool IsAvailable, int Quantity);

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Remove="OrderInventoryNotificationRequest.cs" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,134 @@
using System;
namespace MassTransit.Message;
/// <summary>
/// ============================================================
/// Point-to-Point 通信模式示例 - 消息契约
/// ============================================================
///
/// Point-to-Point点对点通信模式特点
///
/// 1. 单向消息传递
/// - 发送方只发送消息,不等待响应
/// - 消息发送后立即返回,提高性能
/// - 适用于不需要立即知道处理结果的场景
///
/// 2. 消息只被一个消费者处理
/// - RabbitMQ 队列确保消息只被一个消费者消费
/// - 多个消费者会形成竞争关系,每个消息只被处理一次
/// - 适合负载均衡和水平扩展
///
/// 3. 高性能
/// - 无需等待响应,消息发送后立即返回
/// - 减少网络往返时间RTT
/// - 提高系统吞吐量
///
/// 4. 可靠性
/// - 消息持久化保证消息不丢失
/// - 死信队列DLQ处理失败的消息
/// - 重试机制处理临时性错误
///
/// 使用场景:
/// - 订单创建后异步通知库存服务扣减库存
/// - 用户注册后发送欢迎邮件(不等待邮件发送完成)
/// - 数据同步和事件通知
/// - 日志和监控数据收集
///
/// 与 Request-Response 模式的区别:
/// - Request-Response: 使用 IRequestClient.GetResponse<T>(),等待响应
/// - Point-to-Point: 使用 IBus.Send(),不等待响应
///
/// ============================================================
public record OrderInventoryNotificationRequest
{
/// <summary>
/// 消息唯一标识符
/// 用于消息去重和追踪
/// </summary>
public string MessageId { get; init; } = Guid.NewGuid().ToString("N");
/// <summary>
/// 关联ID用于关联相关消息
/// 例如订单ID、用户ID等业务标识
/// </summary>
public string CorrelationId { get; init; } = string.Empty;
/// <summary>
/// 通知类型
/// - OrderCreated: 订单创建
/// - OrderCancelled: 订单取消
/// </summary>
public string NotificationType { get; init; } = string.Empty;
/// <summary>
/// 产品信息列表
/// </summary>
public List<ProductInfo> Products { get; init; } = new();
/// <summary>
/// 消息创建时间
/// </summary>
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
/// <summary>
/// 无参构造函数(用于反序列化)
/// </summary>
public OrderInventoryNotificationRequest() { }
/// <summary>
/// 带参数构造函数
/// </summary>
public OrderInventoryNotificationRequest(
string correlationId,
string notificationType,
List<ProductInfo> products)
{
CorrelationId = correlationId;
NotificationType = notificationType;
Products = products;
}
}
/// <summary>
/// 产品信息
/// </summary>
public record ProductInfo
{
/// <summary>
/// 产品ID
/// </summary>
public string ProductId { get; init; } = string.Empty;
/// <summary>
/// 产品名称
/// </summary>
public string ProductName { get; init; } = string.Empty;
/// <summary>
/// 操作数量
/// 正数表示增加库存,负数表示减少库存
/// </summary>
public int Quantity { get; init; }
/// <summary>
/// 操作原因
/// </summary>
public string Reason { get; init; } = string.Empty;
/// <summary>
/// 无参构造函数(用于反序列化)
/// </summary>
public ProductInfo() { }
/// <summary>
/// 带参数构造函数
/// </summary>
public ProductInfo(string productId, string productName, int quantity, string reason)
{
ProductId = productId;
ProductName = productName;
Quantity = quantity;
Reason = reason;
}
}

View File

@ -1,4 +1,5 @@
<Solution>
<Project Path="MassTransit.Message/MassTransit.Message.csproj" Id="1b835493-5383-420e-aae1-38d0a82a2c89" />
<Project Path="MicoService.Demo/MicoService.Demo.csproj" />
<Project Path="MicoService.Demo2/MicoService.Demo2.csproj" Id="efd7de63-eb5d-4687-8e58-967bc9d939e1" />
<Project Path="Microservice.Common.Tests/Microservice.Common.Tests.csproj" />

View File

@ -1,11 +1,10 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Nacos.V2;
using System.Threading.Tasks;
using MassTransit;
using MassTransit.Message;
using Microservice.Common;
using Microservice.Common.Models;
using Microservice.Common.Services;
using Microsoft.AspNetCore.Mvc;
using Nacos.V2;
namespace MicoService.Demo.Controllers
{
@ -18,18 +17,21 @@ namespace MicoService.Demo.Controllers
private readonly INacosNamingService _nacosNamingService;
private readonly IServiceClient _serviceClient;
private readonly IUserService _userService;
private readonly IRequestClient<CheckInventoryRequest> _checkInvRequestClient;
private readonly ILogger<TestController> _logger;
public TestController(IConfiguration configuration,
INacosNamingService nacosNamingService,
IServiceClient serviceClient,
IUserService userService,
IRequestClient<CheckInventoryRequest> checkInvRequestClient,
ILogger<TestController> logger)
{
this._configuration = configuration;
this._nacosNamingService = nacosNamingService;
this._serviceClient = serviceClient;
this._userService = userService;
this._checkInvRequestClient = checkInvRequestClient;
this._logger = logger;
}
@ -90,5 +92,44 @@ namespace MicoService.Demo.Controllers
}
/// <summary>
/// 演示 通过MassTransit 使用 【请求-响应模式】 调用其他微服务的接口,检查库存是否充足
/// </summary>
/// <param name="productId"></param>
/// <param name="quantity"></param>
/// <returns></returns>
[HttpGet("check-inventory")]
public async Task<bool> CheckInventoryAsync(string productId, int quantity)
{
try
{
var response = await _checkInvRequestClient.GetResponse<CheckInventoryResponse>(new CheckInventoryRequest(productId, quantity));
return response.Message.IsAvailable;
}
catch (RequestTimeoutException)
{
return false;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// 演示 通过MassTransit 使用 【点对点模式】 调用其他微服务的接口,扣减库存
/// </summary>
/// <param name="productId"></param>
/// <param name="quantity"></param>
/// <returns></returns>
[HttpGet("dec-inventory")]
public async Task DecInventoryAsync()
{
}
}
}

View File

@ -0,0 +1,16 @@
namespace MicoService.Demo.DTOs
{
public class OrderItemResponseDto
{
public int Id { get; set; }
public string ProductId { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal Subtotal { get; set; }
}
}

View File

@ -7,14 +7,28 @@
</PropertyGroup>
<ItemGroup>
<Compile Remove="Services\IOrderInventoryNotificationService.cs" />
<Compile Remove="Services\OrderInventoryNotificationService.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MassTransit" Version="8.5.8" />
<PackageReference Include="MassTransit.RabbitMQ" Version="8.5.8" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.2" />
<PackageReference Include="nacos-sdk-csharp.AspNetCore" Version="1.3.10" />
<PackageReference Include="nacos-sdk-csharp.Extensions.Configuration" Version="1.3.10" />
<PackageReference Include="Refit" Version="10.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MassTransit.Message\MassTransit.Message.csproj" />
<ProjectReference Include="..\Microservice.Common\Microservice.Common.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Services\" />
<Folder Include="新文件夹\" />
</ItemGroup>
</Project>

View File

@ -1,8 +1,10 @@
using Nacos.AspNetCore.V2;
using Nacos.V2.DependencyInjection;
using MassTransit;
using MassTransit.Message;
using Microservice.Common;
using Microservice.Common.Services;
using Nacos.AspNetCore.V2;
using Nacos.V2.DependencyInjection;
namespace MicoService.Demo
{
@ -20,10 +22,46 @@ namespace MicoService.Demo
// 配置 Nacos 注册发现
builder.Services.AddNacosAspNet(builder.Configuration);
builder.Services.AddNacosV2Naming(builder.Configuration);
builder.Services.AddNacosV2Naming(builder.Configuration);
#endregion
// MassTransit 配置
// ============================================================
// 配置 MassTransit 总线,使用 RabbitMQ 作为消息中间件
builder.Services.AddMassTransit(x =>
{
// 注册请求客户端,用于发送请求消息并等待响应
x.AddRequestClient<CheckInventoryRequest>();
// 配置 RabbitMQ 连接
x.UsingRabbitMq((context, cfg) =>
{
// 设置 RabbitMQ 主机连接信息
cfg.Host("rabbitmq://192.168.2.7:5672", h =>
{
h.Username("rabbit");
h.Password("qwe123!@#");
});
// 配置消息重试机制最多重试3次每次间隔1秒
cfg.UseMessageRetry(r => r.Interval(3, 1000));
// 自动配置消费者端点,将消费者注册为 RabbitMQ 队列
cfg.ConfigureEndpoints(context);
});
});
// Add services to the container.
builder.Services.AddControllers();
@ -35,6 +73,8 @@ namespace MicoService.Demo
// 注册用户服务
builder.Services.AddScoped<IUserService, UserService>();
// 注册业务逻辑服务
//builder.Services.AddScoped<IOrderInventoryNotificationService, OrderInventoryNotificationService>();
var app = builder.Build();
@ -42,6 +82,10 @@ namespace MicoService.Demo
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/openapi/v1.json", "API v1");
});
}
app.UseAuthorization();

View File

@ -5,7 +5,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5252",
"applicationUrl": "http://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@ -0,0 +1,51 @@
using MicoService.Demo.DTOs;
namespace MicoService.Demo.Services
{
/// <summary>
/// ============================================================
/// Point-to-Point 通信模式示例 - 发送服务
/// ============================================================
///
/// 服务特点:
///
/// 1. 使用 IBus.Send() 发送单向消息
/// - 不等待响应,消息发送后立即返回
/// - 提高性能,减少等待时间
///
/// 2. 与 Request-Response 的区别
/// - Request-Response: 使用 IRequestClient.GetResponse<T>(),等待响应
/// - Point-to-Point: 使用 IBus.Send(),不等待响应
///
/// 3. 使用场景
/// - 订单创建后异步通知库存服务
/// - 不需要立即知道处理结果的场景
/// - 对性能要求较高的场景
///
/// 4. 消息可靠性
/// - 消息持久化保证消息不丢失
/// - 死信队列处理失败的消息
/// - 重试机制处理临时性错误
///
/// ============================================================
public interface IOrderInventoryNotificationService
{
/// <summary>
/// 发送订单创建通知(不等待响应)
/// </summary>
/// <param name="orderId">订单ID</param>
/// <param name="userId">用户ID</param>
/// <param name="items">订单项列表</param>
/// <returns>异步任务</returns>
Task SendOrderCreatedNotificationAsync(string orderId, string userId, List<OrderItemResponseDto> items);
/// <summary>
/// 发送订单取消通知(不等待响应)
/// </summary>
/// <param name="orderId">订单ID</param>
/// <param name="userId">用户ID</param>
/// <param name="items">订单项列表</param>
/// <returns>异步任务</returns>
Task SendOrderCancelledNotificationAsync(string orderId, string userId, List<OrderItemResponseDto> items);
}
}

View File

@ -0,0 +1,114 @@
using MassTransit;
using MassTransit.Message;
using MicoService.Demo.DTOs;
namespace MicoService.Demo.Services
{
/// <summary>
/// Point-to-Point 通信模式示例 - 发送服务实现
/// </summary>
public class OrderInventoryNotificationService : IOrderInventoryNotificationService
{
private readonly ILogger<OrderInventoryNotificationService> _logger;
private readonly IBus _bus;
/// <summary>
/// 构造函数注入依赖
/// </summary>
public OrderInventoryNotificationService(ILogger<OrderInventoryNotificationService> logger, IBus bus)
{
_logger = logger;
_bus = bus;
}
/// <summary>
/// 发送订单创建通知(不等待响应)
/// </summary>
/// <param name="orderId">订单ID</param>
/// <param name="userId">用户ID</param>
/// <param name="items">订单项列表</param>
/// <returns>异步任务</returns>
public async Task SendOrderCreatedNotificationAsync(string orderId, string userId, List<OrderItemResponseDto> items)
{
_logger.LogInformation(
"[Point-to-Point Example] 发送订单 {OrderId} 的创建通知 (用户ID: {UserId})",
orderId, userId);
try
{
// 构建通知消息
var notification = new OrderInventoryNotificationRequest(
correlationId: orderId,
notificationType: "OrderCreated",
products: items.Select(item => new ProductInfo(
productId: item.ProductId,
productName: item.ProductName,
quantity: item.Quantity,
reason: "订单创建扣减库存"
)).ToList()
);
// 发送消息(不等待响应)
// IBus.Send() 是单向的,发送后立即返回
await _bus.Send(notification);
_logger.LogInformation(
"[Point-to-Point Example] 订单 {OrderId} 的创建通知已发送",
orderId);
}
catch (Exception ex)
{
_logger.LogError(ex,
"[Point-to-Point Example] 发送订单 {OrderId} 的创建通知失败",
orderId);
// 可以选择将失败的消息发送到死信队列或重试队列
throw;
}
}
/// <summary>
/// 发送订单取消通知(不等待响应)
/// </summary>
/// <param name="orderId">订单ID</param>
/// <param name="userId">用户ID</param>
/// <param name="items">订单项列表</param>
/// <returns>异步任务</returns>
public async Task SendOrderCancelledNotificationAsync(string orderId, string userId, List<OrderItemResponseDto> items)
{
_logger.LogInformation(
"[Point-to-Point Example] 发送订单 {OrderId} 的取消通知 (用户ID: {UserId})",
orderId, userId);
try
{
// 构建通知消息
var notification = new OrderInventoryNotificationRequest(
correlationId: orderId,
notificationType: "OrderCancelled",
products: items.Select(item => new ProductInfo(
productId: item.ProductId,
productName: item.ProductName,
quantity: item.Quantity,
reason: "订单取消恢复库存"
)).ToList()
);
// 发送消息(不等待响应)
await _bus.Send(notification);
_logger.LogInformation(
"[Point-to-Point Example] 订单 {OrderId} 的取消通知已发送",
orderId);
}
catch (Exception ex)
{
_logger.LogError(ex,
"[Point-to-Point Example] 发送订单 {OrderId} 的取消通知失败",
orderId);
throw;
}
}
}
}

View File

@ -0,0 +1,35 @@
using MassTransit;
using MassTransit.Message;
namespace MicoService.Demo2.Consumers
{
public class CheckInventoryConsumer : IConsumer<CheckInventoryRequest>
{
//private readonly IInventoryService _inventoryService;
public CheckInventoryConsumer(/*IInventoryService inventoryService*/)
{
//_inventoryService = inventoryService;
}
public async Task Consume(ConsumeContext<CheckInventoryRequest> context)
{
// 此处查询数据库,获取库存信息
//var inventory = await _inventoryService.GetByProductIdAsync(context.Message.ProductId);
// 模拟库存数据
var inventory = new
{
ProductId = context.Message.ProductId,
Quantity = 100 // 假设库存数量为100
};
var response = new CheckInventoryResponse(
inventory != null && inventory.Quantity >= context.Message.Quantity,
inventory?.Quantity ?? 0
);
await context.RespondAsync(response);
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
@ -7,13 +7,17 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MassTransit" Version="8.5.8" />
<PackageReference Include="MassTransit.RabbitMQ" Version="8.5.8" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.2" />
<PackageReference Include="nacos-sdk-csharp.AspNetCore" Version="1.3.10" />
<PackageReference Include="nacos-sdk-csharp.Extensions.Configuration" Version="1.3.10" />
<PackageReference Include="Refit" Version="10.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MassTransit.Message\MassTransit.Message.csproj" />
<ProjectReference Include="..\Microservice.Common\Microservice.Common.csproj" />
</ItemGroup>

View File

@ -1,7 +1,9 @@

using MassTransit;
using MicoService.Demo2.Consumers;
using Microservice.Common;
using Nacos.AspNetCore.V2;
using Nacos.V2.DependencyInjection;
using Microservice.Common;
namespace MicoService.Demo2
{
@ -21,6 +23,33 @@ namespace MicoService.Demo2
builder.Services.AddNacosV2Naming(builder.Configuration);
#endregion
// MassTransit 配置
// ============================================================
// 配置 MassTransit 总线,使用 RabbitMQ 作为消息中间件
builder.Services.AddMassTransit(x =>
{
// 注册消费者
x.AddConsumer<CheckInventoryConsumer>();
// 配置 RabbitMQ 连接
x.UsingRabbitMq((context, cfg) =>
{
// 设置 RabbitMQ 主机连接信息
cfg.Host("rabbitmq://192.168.2.7:5672", h =>
{
h.Username("rabbit");
h.Password("qwe123!@#");
});
// 配置消息重试机制最多重试3次每次间隔1秒
cfg.UseMessageRetry(r => r.Interval(3, 1000));
// 自动配置消费者端点,将消费者注册为 RabbitMQ 队列
cfg.ConfigureEndpoints(context);
});
});
// Add services to the container.
builder.Services.AddControllers();
@ -36,6 +65,10 @@ namespace MicoService.Demo2
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/openapi/v1.json", "API v1");
});
}
app.UseAuthorization();

View File

@ -5,7 +5,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5181",
"applicationUrl": "http://localhost:5002",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@ -1,256 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Logging;
using Moq;
using Nacos.V2;
using Nacos.V2.Common;
using Nacos.V2.Naming.Dtos;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Xunit;
namespace Microservice.Common.Tests
{
public class ServiceClientTests
{
private readonly Mock<INacosNamingService> _nacosNamingServiceMock;
private readonly HttpClient _httpClient;
private readonly Mock<ILogger<ServiceClient>> _loggerMock;
private readonly ServiceClient _serviceClient;
public ServiceClientTests()
{
// 初始化模拟对象
_nacosNamingServiceMock = new Mock<INacosNamingService>();
_loggerMock = new Mock<ILogger<ServiceClient>>();
// 创建一个测试用的 HttpClient
var httpClientHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
};
_httpClient = new HttpClient(httpClientHandler);
// 创建 ServiceClient 实例
_serviceClient = new ServiceClient(_nacosNamingServiceMock.Object, _loggerMock.Object);
}
[Fact]
public async Task GetAsync_ShouldReturnSuccess_WhenServiceIsAvailable()
{
// Arrange
var serviceName = "TestService";
var endpoint = "/api/test";
var expectedResponse = new { Message = "Test Response" };
// 模拟 Nacos 服务发现返回一个健康的实例
var instance = new Instance
{
Ip = "127.0.0.1",
Port = 8080,
Healthy = true,
Enabled = true
};
_nacosNamingServiceMock.Setup(x => x.SelectOneHealthyInstance(serviceName)).ReturnsAsync(instance);
// 创建一个测试服务器
var testServer = new TestServer(new WebApplicationFactory<object>().WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// 配置测试服务
});
builder.Configure(app =>
{
app.MapGet("/api/test", () => Results.Ok(expectedResponse));
});
}));
// 使用测试服务器的 HttpClient
var testHttpClient = testServer.CreateClient();
var testServiceClient = new ServiceClient(_nacosNamingServiceMock.Object, testHttpClient, _loggerMock.Object);
// Act
var result = await testServiceClient.GetAsync<object>(serviceName, endpoint);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedResponse.Message, ((dynamic)result).Message);
}
[Fact]
public async Task GetAsync_ShouldThrowException_WhenServiceIsNotAvailable()
{
// Arrange
var serviceName = "NonExistentService";
var endpoint = "/api/test";
// 模拟 Nacos 服务发现返回 null
_nacosNamingServiceMock.Setup(x => x.SelectOneHealthyInstance(serviceName)).ReturnsAsync((Instance)null);
// Act & Assert
await Assert.ThrowsAsync<Exception>(() => _serviceClient.GetAsync<object>(serviceName, endpoint));
}
[Fact]
public async Task GetAsync_ShouldHandleDifferentEndpointFormats()
{
// Arrange
var serviceName = "TestService";
var expectedResponse = new { Message = "Test Response" };
// 模拟 Nacos 服务发现返回一个健康的实例
var instance = new Instance
{
Ip = "127.0.0.1",
Port = 8080,
Healthy = true
};
_nacosNamingServiceMock.Setup(x => x.SelectOneHealthyInstance(serviceName)).ReturnsAsync(instance);
// 创建测试服务器
var testServer = new TestServer(new WebApplicationFactory<object>().WithWebHostBuilder(builder =>
{
builder.Configure(app =>
{
app.Run(async context =>
{
// 验证请求路径是否以 '/' 开头
Assert.StartsWith("/", context.Request.Path);
await context.Response.WriteAsJsonAsync(new { StatusCode = 200, Message = "Success", Data = expectedResponse });
});
});
}));
// 测试不同格式的端点
var endpoints = new[] { "/api/test", "api/test", "test", "/test" };
foreach (var endpoint in endpoints)
{
// Act & Assert
var result = await _serviceClient.GetAsync<object>(serviceName, endpoint);
Assert.NotNull(result);
}
}
[Fact]
public async Task PostAsync_ShouldReturnSuccess_WhenServiceIsAvailable()
{
// Arrange
var serviceName = "TestService";
var endpoint = "/api/test";
var requestData = new { Name = "Test" };
var expectedResponse = new { Message = "Test Response" };
// 模拟 Nacos 服务发现返回一个健康的实例
var instance = new Instance
{
Ip = "127.0.0.1",
Port = 8080,
Healthy = true,
Enabled = true
};
_nacosNamingServiceMock.Setup(x => x.SelectOneHealthyInstance(serviceName)).ReturnsAsync(instance);
// 创建一个测试服务器
var testServer = new TestServer(new WebApplicationFactory<object>().WithWebHostBuilder(builder =>
{
builder.Configure(app =>
{
app.MapPost("/api/test", (object data) => Results.Ok(expectedResponse));
});
}));
// 使用测试服务器的 HttpClient
var testHttpClient = testServer.CreateClient();
var testServiceClient = new ServiceClient(_nacosNamingServiceMock.Object, testHttpClient, _loggerMock.Object);
// Act
var result = await testServiceClient.PostAsync<object>(serviceName, endpoint, requestData);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedResponse.Message, ((dynamic)result).Message);
}
[Fact]
public async Task PutAsync_ShouldReturnSuccess_WhenServiceIsAvailable()
{
// Arrange
var serviceName = "TestService";
var endpoint = "/api/test";
var requestData = new { Name = "Test" };
var expectedResponse = new { Message = "Test Response" };
// 模拟 Nacos 服务发现返回一个健康的实例
var instance = new Instance
{
Ip = "127.0.0.1",
Port = 8080,
Healthy = true,
Enabled = true
};
_nacosNamingServiceMock.Setup(x => x.SelectOneHealthyInstance(serviceName)).ReturnsAsync(instance);
// 创建一个测试服务器
var testServer = new TestServer(new WebApplicationFactory<object>().WithWebHostBuilder(builder =>
{
builder.Configure(app =>
{
app.MapPut("/api/test", (object data) => Results.Ok(expectedResponse));
});
}));
// 使用测试服务器的 HttpClient
var testHttpClient = testServer.CreateClient();
var testServiceClient = new ServiceClient(_nacosNamingServiceMock.Object, testHttpClient, _loggerMock.Object);
// Act
var result = await testServiceClient.PutAsync<object>(serviceName, endpoint, requestData);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedResponse.Message, ((dynamic)result).Message);
}
[Fact]
public async Task DeleteAsync_ShouldReturnSuccess_WhenServiceIsAvailable()
{
// Arrange
var serviceName = "TestService";
var endpoint = "/api/test";
var expectedResponse = new { Message = "Test Response" };
// 模拟 Nacos 服务发现返回一个健康的实例
var instance = new Instance
{
Ip = "127.0.0.1",
Port = 8080,
Healthy = true,
Enabled = true
};
_nacosNamingServiceMock.Setup(x => x.SelectOneHealthyInstance(serviceName)).ReturnsAsync(instance);
// 创建一个测试服务器
var testServer = new TestServer(new WebApplicationFactory<object>().WithWebHostBuilder(builder =>
{
builder.Configure(app =>
{
app.MapDelete("/api/test", () => Results.Ok(expectedResponse));
});
}));
// 使用测试服务器的 HttpClient
var testHttpClient = testServer.CreateClient();
var testServiceClient = new ServiceClient(_nacosNamingServiceMock.Object, testHttpClient, _loggerMock.Object);
// Act
var result = await testServiceClient.DeleteAsync<object>(serviceName, endpoint);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedResponse.Message, ((dynamic)result).Message);
}
}
}

View File

@ -1,10 +0,0 @@
namespace Microservice.Common.Tests;
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}