项目

本文档有多个版本。请选择最适合您的选项。

UI
Database

微服务教程 第 05 部分:构建订购服务

在上一部分中,我们创建了订购微服务。在本部分中,我们将手动实现订购微服务的功能。我们将不使用 ABP Suite 来生成代码,而是通过逐步创建必要的代码来更好地理解细节。

创建 Order 实体

我们将从创建 Order 实体开始,它将代表我们系统中的订单。我们将把这个实体添加到 CloudCrm.OrderingService 项目中。创建一个名为 Entities 的新文件夹,并在其中创建文件 Order.cs

using CloudCrm.OrderingService.Enums;
using Volo.Abp.Domain.Entities.Auditing;

namespace CloudCrm.OrderingService.Entities;

public class Order : CreationAuditedAggregateRoot<Guid>
{
    public Guid ProductId { get; set; }
    public string CustomerName { get; set; }
    public OrderState State { get; set; }
}

为了简化示例,我们允许用户在订单中只包含一个产品。Order 实体继承自 CreationAuditedAggregateRoot<> 类。这个由 ABP 框架提供的类包含常见的属性,如 IdCreationTimeCreatorId 等。

添加 OrderState 枚举

我们还需要定义 OrderState 枚举。在 CloudCrm.OrderingService.Contracts 项目中,创建一个名为 Enums 的文件夹,并在其中创建文件 OrderState.cs

namespace CloudCrm.OrderingService.Enums;

public enum OrderState : byte
{
    Placed = 0,
    Delivered = 1,
    Canceled = 2
}

最终的解决方案结构应如下图所示:

vs-ordering-entity

配置数据库映射

首先,我们需要将 Order 实体添加到 OrderingServiceDbContext 类中。打开 CloudCrm.OrderingService 项目 Data 文件夹中的 OrderingServiceDbContext 类,并在 DbSet 属性中添加以下代码:

using CloudCrm.OrderingService.Entities;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DistributedEvents;
using Volo.Abp.EntityFrameworkCore.Modeling;

namespace CloudCrm.OrderingService.Data;

[ConnectionStringName(DatabaseName)]
public class OrderingServiceDbContext :
    AbpDbContext<OrderingServiceDbContext>,
    IHasEventInbox,
    IHasEventOutbox
{
    public const string DbTablePrefix = "";
    public const string DbSchema = null;
    
    public const string DatabaseName = "OrderingService";
    
    public DbSet<IncomingEventRecord> IncomingEvents { get; set; }
    public DbSet<OutgoingEventRecord> OutgoingEvents { get; set; }
    public DbSet<Order> Orders { get; set; } // 新增:添加 DbSet 属性

    // 为简洁起见,省略代码
}

接下来,我们需要为 Order 实体配置数据库映射。我们将使用 EF Core Fluent API 进行此配置。在 OrderingServiceDbContext 类的 OnModelCreating 方法中添加以下代码:

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    builder.ConfigureEventInbox();
    builder.ConfigureEventOutbox();

    builder.Entity<Order>(b =>
    {
        // 配置表名
        b.ToTable("Orders");

        // 始终调用此方法以设置基实体属性
        b.ConfigureByConvention();

        // 实体的属性
        b.Property(q => q.CustomerName).IsRequired().HasMaxLength(120);
    });
}

在这段代码片段中,我们将 Order 实体配置为使用数据库中的 Orders 表。我们还指定 CustomerName 属性是必需的,并且最大长度为 120 个字符。

添加数据库迁移

现在,我们可以添加一个新的数据库迁移。你可以使用 Entity Framework Core 的 Add-Migration(或 dotnet ef migrations add)终端命令,但在本教程中,我们将使用 ABP Studio 的快捷 UI。

如果应用程序正在运行,请先停止它们,并确保解决方案已构建。你可以在 ABP Studio 的解决方案资源管理器中右键单击 services 文件夹下的 CloudCrm.OrderingService,然后选择 Dotnet CLI -> Graph Build 命令。

右键单击 CloudCrm.OrderingService 包,然后选择 EF Core CLI -> Add Migration 命令:

abp-studio-add-entity-framework-core-migration

Add Migration 命令会打开一个新对话框,获取迁移名称 Added_Order_Entity

abp-studio-entity-framework-core-add-migration-order

点击确定按钮后,一个新的数据库迁移类将被添加到 CloudCrm.OrderingService 项目的 Migrations 文件夹中:

visual-studio-new-migration-class

这些更改将在下次应用程序启动时应用到数据库。更多详情,请参考服务启动时的数据库迁移部分。

创建应用服务

现在,我们将创建应用服务来管理 Order 实体。

定义应用服务契约

首先,我们需要在 CloudCrm.OrderingService.Contracts 项目下定义应用服务契约。返回你的 IDE,打开 CloudCrm.OrderingService 模块的 .NET 解决方案,并为 CloudCrm.OrderingService.Contracts 项目在 Services 文件夹下创建一个 IOrderAppService 接口:

using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;

namespace CloudCrm.OrderingService.Services;

public interface IOrderAppService : IApplicationService
{
    Task<List<OrderDto>> GetListAsync();
    Task CreateAsync(OrderCreationDto input);
}

定义数据传输对象

接下来,我们需要为 Order 实体定义数据传输对象。GetListAsyncCreateAsync 方法将使用这些 DTO 与客户端应用程序通信。

CloudCrm.OrderingService.Contracts 项目的 Services 文件夹下创建 OrderCreationDto 类:

using System;
using System.ComponentModel.DataAnnotations;

namespace CloudCrm.OrderingService.Services;

public class OrderCreationDto
{
    [Required]
    [StringLength(150)]
    public string CustomerName { get; set; }

    [Required]
    public Guid ProductId { get; set; }
}

CloudCrm.OrderingService.Contracts 项目的 Services 文件夹下创建 OrderDto 类:

using System;
using CloudCrm.OrderingService.Enums;

namespace CloudCrm.OrderingService.Services;

public class OrderDto
{
    public Guid Id { get; set; }
    public string CustomerName { get; set; }
    public Guid ProductId { get; set; }
    public OrderState State { get; set; }
}

最终的解决方案结构应如下图所示:

vs-ordering-contracts

实现应用服务

现在,我们将在 OrderAppService 类中实现 IOrderAppService 接口。在 CloudCrm.OrderingService 项目的 Services 文件夹下创建一个 OrderAppService 类:

using System;
using System.Collections.Generic;
using CloudCrm.OrderingService.Entities;
using CloudCrm.OrderingService.Enums;
using CloudCrm.OrderingService.Localization;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;

namespace CloudCrm.OrderingService.Services;

public class OrderAppService : ApplicationService, IOrderAppService
{
    private readonly IRepository<Order, Guid>  _orderRepository;

    public OrderAppService(IRepository<Order, Guid> orderRepository)
    {
        LocalizationResource = typeof(OrderingServiceResource);
        _orderRepository = orderRepository;
    }

    public async Task<List<OrderDto>> GetListAsync()
    {
        var orders = await _orderRepository.GetListAsync();
        return ObjectMapper.Map<List<Order>, List<OrderDto>>(orders);
    }

    public async Task CreateAsync(OrderCreationDto input)
    {
        var order = new Order
        {
            CustomerName = input.CustomerName,
            ProductId = input.ProductId,
            State = OrderState.Placed
        };

        await _orderRepository.InsertAsync(order);
    }
}

在这段代码片段中,我们将 IRepository<Order, Guid> 注入到 OrderAppService 类中。我们使用这个仓储与 Order 实体交互。GetListAsync 方法从数据库检索订单列表,并将它们映射到 OrderDto 类。CreateAsync 方法创建一个新的订单实体并将其插入数据库。

之后,我们需要配置 Mapperly 对象以将 Order 实体映射到 OrderDto 类。打开 CloudCrm.OrderingService 项目 ObjectMapping 文件夹中的 OrderingServiceApplicationMappers 类,并添加以下代码:

using Riok.Mapperly.Abstractions;
using Volo.Abp.Mapperly;

namespace CloudCrm.OrderingService.ObjectMapping;

[Mapper]
public partial class OrderingServiceApplicationMappers : MapperBase<Order, OrderDto>
{
    public override partial OrderDto Map(Order source);

    public override partial void Map(Order source, OrderDto destination);
}

测试应用服务

现在,我们可以使用 Swagger UI 测试 OrderAppService 类。打开解决方案运行器,右键单击 CloudCrm.OrderingService 项目,然后选择启动命令。应用程序启动后,你可以通过点击浏览命令来打开 Swagger UI:

ordering-service-swagger-ui

展开 api/ordering/order API,然后单击 Try it out 按钮。然后,通过填写请求正文并单击 Execute 按钮来创建几个订单:

ordering-service-order-swagger-ui

如果你检查数据库,你应该能看到 Orders 表中创建的实体:

sql-server-orders-database-table-records

由于我们使用的是自动 API 控制器,我们不需要为 OrderAppService 创建控制器。ABP 框架会自动为 OrderAppService 类创建一个 API 控制器。你可以在 CloudCrm.OrderingService 项目的 CloudCrmOrderingServiceModule 类的 ConfigureAutoControllers 方法中找到配置。

创建用户界面

现在,我们将为订购模块创建用户界面。我们将使用 CloudCrm.Blazor.Server 项目来创建用户界面。在你喜欢的 IDE 中打开 CloudCrm.Blazor.Server .NET 解决方案。

创建订单页面

CloudCrm.Blazor.Server 项目的 Components/Pages 文件夹下创建 Orders.razor 文件。

@page "/orders"
@using CloudCrm.OrderingService.Services
@inject IOrderAppService OrderAppService

<h1>Orders</h1>

<div class="card">
    <div class="card-body">
        @if (OrderList == null)
        {
            <p><em>Loading orders...</em></p>
        }
        else if (!OrderList.Any())
        {
            <p><em>No orders found.</em></p>
        }
        else
        {
            <ul class="list-group">
                @foreach (var order in OrderList)
                {
                    <li class="list-group-item">
                        <strong>Customer:</strong> @order.CustomerName <br />
                        <strong>Product:</strong> @order.ProductId <br />
                        <strong>State:</strong> @order.State
                    </li>
                }
            </ul>
        }
    </div>
</div>

@code {
    private List<OrderDto> OrderList { get; set; }

    protected override async Task OnInitializedAsync()
    {
        try
        {
            var result = await OrderAppService.GetListAsync();
            OrderList = result;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error loading orders: {ex.Message}");
            OrderList = new List<OrderDto>();
        }
    }
}

生成 UI 代理

为了在代理生成期间选择应用程序,请确保事先启动 CloudCrm.OrderingService。你可以使用解决方案运行器启动应用程序。

现在,我们需要为 UI 项目生成静态 API 代理。右键单击 CloudCrm.Blazor.Server ,然后选择 ABP CLI -> Generate Proxy -> C# 命令:

abp-studio-generate-proxy-blazor-server-2

它将打开生成 C# 代理窗口。选择 CloudCrm.OrderingService 应用程序,它将自动填充 URL 字段。选择 ordering 模块,服务类型为 application,最后勾选 Without contracts 复选框,因为 CloudCrm.Blazor.Server 项目中已经引用了 CloudCrm.OrderingService.Contracts 包:

abp-studio-generate-proxy-window-ordering-module

添加菜单项

ABP 提供了一个模块化导航菜单系统,允许你以模块化的方式定义菜单项。

最后,我们需要在侧边栏中添加一个菜单项以导航到 Orders 页面。打开 CloudCrm.Blazor.Client 项目 Navigation 文件夹中的 CloudCrmMenus 文件,并用以下代码进行编辑:

namespace CloudCrm.Web.Navigation;

public class CloudCrmMenus
{
    private const string Prefix = "CloudCrm";

    public const string Home = Prefix + ".Home";

    public const string HostDashboard = Prefix + ".HostDashboard";

    public const string TenantDashboard = Prefix + ".TenantDashboard";

    public const string Products = Prefix + ".Products";

    public const string Orders = Prefix + ".Orders"; // 新增:添加菜单项
}

然后,打开位于 Navigation 文件夹中的 CloudCrm.Blazor 项目中的 CloudCrmMenuContributor 类,并在 ConfigureMainMenuAsync 方法中添加以下代码:

private static async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{
    // 为简洁起见,省略代码

    context.Menu.AddItem(
            new ApplicationMenuItem(
                CloudCrmMenus.Orders, // 唯一的菜单 ID
                "Orders", // 菜单显示文本
                "~/Orders", // URL
                "fa-solid fa-basket-shopping" // 图标 CSS 类
            )
        );
}

构建并运行应用程序

现在,我们可以运行应用程序来查看更改。如果应用程序正在运行,请先停止。然后打开解决方案运行器面板,右键单击 CloudCrm 根项目,然后选择启动命令:

abp-studio-run-build-start

应用程序启动后,你可以浏览并导航到 Orders 页面以查看订单列表:

web-orders-page

很好!我们已经成功实现了订购模块。但是,有一个问题:

  • 我们看到的是产品的 GUID ID,而不是其名称。这是因为订购微服务与目录微服务没有集成,无法访问产品微服务的数据库来执行 JOIN 查询。

我们将在下一部分通过实现订购目录微服务之间的集成服务来解决这个问题。

总结

在本部分中,我们手动实现了订购模块。我们创建了 Order 实体、OrderState 枚举、OrderAppService 应用服务以及 Orders 页面的用户界面。我们还向侧边栏添加了一个菜单项以导航到 Orders 页面。


在本文档中