引入MassTransit消息通信,支持请求响应与点对点模式
本次提交集成MassTransit(基于RabbitMQ),实现微服务间请求-响应与点对点消息通信示例。新增MassTransit.Message项目,统一消息契约;主服务和库存服务均完成消息总线配置与相关接口/消费者实现。移除冗余测试代码,完善注释,提升系统异步解耦与扩展能力。
This commit is contained in:
parent
016987b101
commit
1ff9fd4cc2
|
|
@ -0,0 +1,3 @@
|
|||
namespace MassTransit.Message;
|
||||
|
||||
public record CheckInventoryRequest(string ProductId, int Quantity);
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
namespace MassTransit.Message;
|
||||
|
||||
public record CheckInventoryResponse(bool IsAvailable, int Quantity);
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5252",
|
||||
"applicationUrl": "http://localhost:5001",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5181",
|
||||
"applicationUrl": "http://localhost:5002",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
namespace Microservice.Common.Tests;
|
||||
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue