ASP.NET Core Clean Architecture
文章目录
- 项目地址
- 一、项目主体
- 1. CQRS
- 1.1 Repository数据库接口
- 1.2 GetEventDetail 完整的Query流程
- 1.3 创建CreateEventCommand并使用validation
- 2. EFcore层
- 2.1 BaseRepository
- 2.2 CategoryRepository
- 2.3 OrderRepository
- 3. Email/Excel导出
- 3.1 Email
- 1. IEmail接口层
- 2. Email的Model层
- 3. 具体Email的实现层
- 4. 配置settings
- 3.2 Excel导出
- 1. 导出excel接口层
- 2. Controller层
- 3. Query层
- 4. 实现IExcelService
- 4. 定义response/全局错误处理中间件
- 4.1 统一response
- 1. 定义统一的返回类
- 2. 使用
- 4.2 全局错误处理中间件
- 5. 用户权限相关
- 5.1 用户权限相关的接口层
- 5.2 登录/注册/jwt 实体类定义
- 5.2 用户实体
- 5.3 用户认证所有接口实现的地方
- 5.4 用户服务注册
- 6. 添加日志
- 7. 版本控制
- 8. 分页
- 9. 配置中间件和服务注册
- 二、测试
- 1. Unitest
- 2. Integration Tests
项目地址
-
教程作者:ASP.NET Core Clean Architecture 2022-12
-
教程地址:
https://www.bilibili.com/video/BV1YZ421M7UA?spm_id_from=333.788.player.switch&vd_source=d14620e2c9f01dee5d2a104075027ad1&p=16
- 代码仓库地址:
- 所用到的框架和插件:
一、项目主体
- 整个项目4层结构

- Application层

1. CQRS
1.1 Repository数据库接口
- Application层的Contracts里的Persistence,存放数据库的接口

IAsyncRepository:基类主要功能,规定 增删改查/单一查询/分页
namespace GloboTicket.TicketManagement.Application.Contracts.Persistence
{public interface IAsyncRepository<T> where T : class{Task<T?> GetByIdAsync(Guid id);Task<IReadOnlyList<T>> ListAllAsync();Task<T> AddAsync(T entity);Task UpdateAsync(T entity);Task DeleteAsync(T entity);Task<IReadOnlyList<T>> GetPagedReponseAsync(int page, int size);}
}
ICategoryRepository.cs:添加自己独特的GetCategoriesWithEvents 方法
namespace GloboTicket.TicketManagement.Application.Contracts.Persistence
{public interface ICategoryRepository : IAsyncRepository<Category>{Task<List<Category>> GetCategoriesWithEvents(bool includePassedEvents);}
}
IEventRepository.cs:添加Event自己的方法
namespace GloboTicket.TicketManagement.Application.Contracts.Persistence
{public interface IEventRepository : IAsyncRepository<Event>{Task<bool> IsEventNameAndDateUnique(string name, DateTime eventDate);}
}
IOrderRepository.cs: 没有自己的方法,直接继承使用
namespace GloboTicket.TicketManagement.Application.Contracts.Persistence
{public interface IOrderRepository: IAsyncRepository<Order>{}
}
1.2 GetEventDetail 完整的Query流程
-
项目层级

-
EventDetailVm.cs:用于返回给接口的数据

CategoryDto.cs:表示在GetEventDetail里需要用到的Dto

GetEventDetailQuery.cs:传入ID的值,以及返回EventDetailVm

GetEventDetailQueryHandler.cs:返回查询

- 返回API的结构类似于
{"eventId": "123e4567-e89b-12d3-a456-426614174000","name": "Rock Concert","price": 100,"artist": "The Rock Band","date": "2023-12-25T20:00:00","description": "An amazing rock concert to end the year!","imageUrl": "https://example.com/images/rock-concert.jpg","categoryId": "456e7890-f12g-34h5-i678-901234567890","category": {"id": "456e7890-f12g-34h5-i678-901234567890","name": "Music"}
}
1.3 创建CreateEventCommand并使用validation
- 设置验证类
CreateEventCommandValidator.cs
using FluentValidation;
using GloboTicket.TicketManagement.Application.Contracts.Persistence;
using System;
using System.Threading;
using System.Threading.Tasks;namespace GloboTicket.TicketManagement.Application.Features.Events.Commands.CreateEvent
{public class CreateEventCommandValidator : AbstractValidator<CreateEventCommand>{private readonly IEventRepository _eventRepository;public CreateEventCommandValidator(IEventRepository eventRepository){_eventRepository = eventRepository;RuleFor(p => p.Name).NotEmpty().WithMessage("{PropertyName} is required.").NotNull().MaximumLength(50).WithMessage("{PropertyName} must not exceed 50 characters.");RuleFor(p => p.Date).NotEmpty().WithMessage("{PropertyName} is required.").NotNull().GreaterThan(DateTime.Now);RuleFor(e => e).MustAsync(EventNameAndDateUnique).WithMessage("An event with the same name and date already exists.");RuleFor(p => p.Price).NotEmpty().WithMessage("{PropertyName} is required.").GreaterThan(0);}private async Task<bool> EventNameAndDateUnique(CreateEventCommand e, CancellationToken token){return !(await _eventRepository.IsEventNameAndDateUnique(e.Name, e.Date));}}
}
- Command类:
CreateEventCommand.cs
using MediatR;namespace GloboTicket.TicketManagement.Application.Features.Events.Commands.CreateEvent
{public class CreateEventCommand: IRequest<Guid>{public string Name { get; set; } = string.Empty;public int Price { get; set; }public string? Artist { get; set; }public DateTime Date { get; set; }public string? Description { get; set; }public string? ImageUrl { get; set; }public Guid CategoryId { get; set; }public override string ToString(){return $"Event name: {Name}; Price: {Price}; By: {Artist}; On: {Date.ToShortDateString()}; Description: {Description}";}}
}
CreateEventCommandHandler.cs:处理Command,并且使用validator

- 自定义验证逻辑:查询在
IEventRepository接口里

2. EFcore层
-
数据库接口层:Core层的Contracts里的Persistence

-
实现层:Infrastructure层的Persistence

2.1 BaseRepository
BaseRepository.cs:定义

2.2 CategoryRepository
CategoryRepository.cs:继承BaseRepository,以及实现接口

2.3 OrderRepository
OrderRepository.cs使用分页

3. Email/Excel导出
3.1 Email
1. IEmail接口层

- 接口
namespace GloboTicket.TicketManagement.Application.Contracts.Infrastructure
{public interface IEmailService{Task<bool> SendEmail(Email email);}
}
2. Email的Model层
- Model实体:定义Email发送的内容和设置

3. 具体Email的实现层
- 在Infrastructure层里的infrastructure里实现

4. 配置settings
appsettings.json

3.2 Excel导出
1. 导出excel接口层
- Core文件夹/Application类库/Contracts文件夹/infrastructure文件夹/
IEmailService.cs
namespace GloboTicket.TicketManagement.Application.Contracts.Infrastructure
{public interface ICsvExporter{byte[] ExportEventsToCsv(List<EventExportDto> eventExportDtos);}
}
2. Controller层
- API文件夹/GloboTicket.TicketManagement.Api类库/Controllers文件夹/
EventsController.cs

3. Query层

GetEventsExportQuery.cs:返回值EventExportFileVm类,无参数
using MediatR;namespace GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventsExport
{public class GetEventsExportQuery: IRequest<EventExportFileVm>{}
}
EventExportFileVm.cs:定义返回的文件类
public class EventExportFileVm
{public string EventExportFileName { get; set; } = string.Empty;public string ContentType { get; set; } = string.Empty;public byte[]? Data { get; set; }
}
EventExportDto.cs:
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventsExport
{public class EventExportDto{public Guid EventId { get; set; }public string Name { get; set; } = string.Empty;public DateTime Date { get; set; }}
}
- handler

4. 实现IExcelService
- Infrastructure文件夹/Infrastructure类库/FileExport文件夹/
CsvExporter.cs
using CsvHelper;
using GloboTicket.TicketManagement.Application.Contracts.Infrastructure;
using GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventsExport;namespace GloboTicket.TicketManagement.Infrastructure.FileExport
{public class CsvExporter : ICsvExporter{public byte[] ExportEventsToCsv(List<EventExportDto> eventExportDtos){using var memoryStream = new MemoryStream();using (var streamWriter = new StreamWriter(memoryStream)){using var csvWriter = new CsvWriter(streamWriter);csvWriter.WriteRecords(eventExportDtos);}return memoryStream.ToArray();}}
}
4. 定义response/全局错误处理中间件
4.1 统一response
- 除了使用.net直接返回状态码之外,还可以统一响应的格式
{"success": true, //是否成功"message": "操作成功", //操作结果"data": {}, //返回数据内容"errorCode": null //错误类型或错误码
}
1. 定义统一的返回类
ApiResponse.cs类:处理所有返回的格式
public class ApiResponse<T>
{public bool Success { get; set; }public string Message { get; set; }public T? Data { get; set; }public string? ErrorCode { get; set; }public List<string>? ValidationErrors { get; set; }public ApiResponse(bool success, string message, T? data = default, string? errorCode = null){Success = success;Message = message;Data = data;ErrorCode = errorCode;}public static ApiResponse<T> SuccessResponse(T data, string message = "操作成功"){return new ApiResponse<T>(true, message, data);}public static ApiResponse<T> ErrorResponse(string message, string errorCode, List<string>? validationErrors = null){return new ApiResponse<T>(false, message, default, errorCode) { ValidationErrors = validationErrors };}
}
2. 使用
- 在Handler里使用
public async Task<ApiResponse<CreateCategoryDto>> Handle(CreateCategoryCommand request, CancellationToken cancellationToken)
{// 1. 初始化响应var validator = new CreateCategoryCommandValidator();var validationResult = await validator.ValidateAsync(request);// 2. 验证失败,返回错误响应if (validationResult.Errors.Count > 0){var validationErrors = validationResult.Errors.Select(e => e.ErrorMessage).ToList();return ApiResponse<CreateCategoryDto>.ErrorResponse("请求验证失败", "VALIDATION_ERROR", validationErrors);}// 3. 验证成功,继续处理业务逻辑var category = new Category() { Name = request.Name };category = await _categoryRepository.AddAsync(category);var categoryDto = _mapper.Map<CreateCategoryDto>(category);// 4. 返回成功响应return ApiResponse<CreateCategoryDto>.SuccessResponse(categoryDto, "分类创建成功");
}
- 成功返回:
{"success": true,"message": "分类创建成功","data": {"id": 1,"name": "Sport"}
}
- 验证失败
{"success": false,"message": "请求验证失败","errorCode": "VALIDATION_ERROR","validationErrors": ["分类名称不能为空","分类名称长度不能超过50个字符"]
}
4.2 全局错误处理中间件
5. 用户权限相关
5.1 用户权限相关的接口层
- Core文件夹/Application类库/Contracts文件夹/Identity文件夹

5.2 登录/注册/jwt 实体类定义
- Core文件夹/Application类库/Contracts文件夹/Models文件夹/Authentication文件夹

5.2 用户实体
- Infrastructure文件夹/GloboTicket.TicketManagement.Identity类库/Models文件夹

ApplicationUser.cs:用户实体
//用户实体
namespace Demo.Domain.Entities
{public class User{public Guid Id { get; set; } = Guid.NewGuid(); public string FirstName { get; set; } = null!;public string LastName { get; set; } = null!;public string Email { get; set; } = null!;public string Password { get; set; } = null!;public string Role { get; set; } = null!;}
}
5.3 用户认证所有接口实现的地方
- 用户登录注册以及jwt所有接口实现的地方

5.4 用户服务注册
- 所有Jwt和用户相关的服务注册
namespace GloboTicket.TicketManagement.Identity
{public static class IdentityServiceExtensions{public static void AddIdentityServices(this IServiceCollection services, IConfiguration configuration){services.Configure<JwtSettings>(configuration.GetSection("JwtSettings"));services.AddDbContext<GloboTicketIdentityDbContext>(options => options.UseSqlServer(configuration.GetConnectionString("GloboTicketIdentityConnectionString"),b => b.MigrationsAssembly(typeof(GloboTicketIdentityDbContext).Assembly.FullName)));services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<GloboTicketIdentityDbContext>().AddDefaultTokenProviders();services.AddTransient<IAuthenticationService, AuthenticationService>();services.AddAuthentication(options =>{options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;}).AddJwtBearer(o =>{o.RequireHttpsMetadata = false;o.SaveToken = false;o.TokenValidationParameters = new TokenValidationParameters{ValidateIssuerSigningKey = true,ValidateIssuer = true,ValidateAudience = true,ValidateLifetime = true,ClockSkew = TimeSpan.Zero,ValidIssuer = configuration["JwtSettings:Issuer"],ValidAudience = configuration["JwtSettings:Audience"],IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtSettings:Key"]))};o.Events = new JwtBearerEvents(){OnAuthenticationFailed = c =>{c.NoResult();c.Response.StatusCode = 500;c.Response.ContentType = "text/plain";return c.Response.WriteAsync(c.Exception.ToString());},OnChallenge = context =>{context.HandleResponse();context.Response.StatusCode = 401;context.Response.ContentType = "application/json";var result = JsonSerializer.Serialize("401 Not authorized");return context.Response.WriteAsync(result);},OnForbidden = context =>{context.Response.StatusCode = 403;context.Response.ContentType = "application/json";var result = JsonSerializer.Serialize("403 Not authorized");return context.Response.WriteAsync(result);}};});}}
}
6. 添加日志
7. 版本控制
8. 分页
9. 配置中间件和服务注册
- 模仿.ne5,将
Program.cs里注册分离
- 创建
StartupExtensions.cs用来将program.cs里的代码分离

- 在
program.cs里配置

二、测试
- 使用框架
Moq用来模拟数据
Shouldly 用来断言
xunit 测试框架
1. Unitest
- Automatically 代码片段测试,快速
- 测试的是Public API
- 独立运行 run in isolation
- 结果断言
2. Integration Tests
- end to end test between different layers
- more work to set up
- often linked with database
相关文章:
ASP.NET Core Clean Architecture
文章目录 项目地址一、项目主体1. CQRS1.1 Repository数据库接口1.2 GetEventDetail 完整的Query流程1.3 创建CreateEventCommand并使用validation 2. EFcore层2.1 BaseRepository2.2 CategoryRepository2.3 OrderRepository 3. Email/Excel导出3.1 Email1. IEmail接口层2. Ema…...
蓝桥杯备赛-精卫填海-DP
精卫终于快把东海填平了!只剩下了最后的一小片区域了。同时,西山上的木石也已经不多了。精卫能把东海填平吗? 事实上,东海未填平的区域还需要至少体积为 v 的木石才可以填平,而西山上的木石还剩下 n 块,每块…...
Windows10配置C++版本的Kafka,并进行发布和订阅测试
配置的环境为:Release x64下的环境 完整项目:https://gitee.com/jiajingong/kafka-publisher 1、首先下载相应的库文件(.lib,.dll) 参考链接: GitHub - eStreamSoftware/delphi-kafka GitHub - cloade…...
vue3 下载文件 responseType-blob 或者 a标签
在 Vue 3 中,你可以使用 axios 或 fetch 来下载文件,并将 responseType 设置为 blob 以处理二进制数据。以下是一个使用 axios 的示例: 使用 axios 下载文件 首先,确保你已经安装了 axios: npm install axios然后在你…...
【Gin-Web】Bluebell社区项目梳理6:限流策略-漏桶与令牌桶
本文目录 一、限流二、漏桶三、令牌桶算法四、Gin框架中实现令牌桶限流 一、限流 限流又称为流量控制,也就是流控,通常是指限制到达系统的并发请求数。 限流虽然会影响部分用户的使用体验,但是能一定程度上保证系统的稳定性,不至…...
51单片机-AT24CXX存储器工作原理
1、AT24CXX存储器工作原理 1.1、特点: 与400KHz,I2C总线兼容1.8到6.0伏工作电压范围低功耗CMOS技术写保护功能当WP为高电平时进入写保护状态页写缓冲器自定时擦写周期100万次编程/擦除周期可保存数据100年8脚DIP SOIC或TSSOP封装温度范围商业级和工业级…...
突破性能极限:DeepSeek开源FlashMLA解码内核技术解析
引言:大模型时代的推理加速革命 在生成式AI大行其道的今天,如何提升大语言模型的推理效率已成为行业焦点。DeepSeek团队最新开源的FlashMLA项目凭借其惊人的性能表现引发关注——在H800 GPU上实现580 TFLOPS计算性能,这正是大模型推理优化的…...
点击修改按钮图片显示有问题
问题可能出在表单数据的初始化上。在 ave-form.vue 中,我们需要处理一下从后端返回的图片数据,因为它们可能是 JSON 字符串格式。 vue:src/views/tools/fake-strategy/components/ave-form.vue// ... existing code ...Watch(value)watchValue(v: any) …...
[AI]从零开始的树莓派运行DeepSeek模型教程
一、前言 在前面的教程中,教了大家如何在windows中使用llama.cpp来运行DeepSeek模型。根据前面的教程中,我们也了解到了,我们只需要编译好llama.cpp就可以运行DeepSeek以及类似的LLM模型。那么本次教程就来教大家如何使用树莓派来运行大模型。…...
2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷(二)
2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷(二) 第一部分:网络平台搭建与设备安全防护任务书第二部分:网络安全事件响应、数字取证调查、应用程序安全任务书任务 1:应急响应&…...
Open WebUI本地部署教程
文章目录 1、系统环境配置2、源码下载2.1 github源码地址下载 3、环境启动3.1 后端环境3.2 前端环境 4、问题4.1 浏览器跨域问题4.2 all-MiniLM-L6-v2模型文件下载失败问题4.3 单独部署backend启动报错问题 1、系统环境配置 操作系统:windows/linux/macos Python版…...
Missing required prop: “maxlength“
背景: 封装一个使用功能相同使用频率较高的input公共组件作为子组件,大多数长度要求为200,且实时显示统计子数,部分input有输入提示。 代码实现如下: <template><el-input v-model"inputValue" t…...
dify本地部署
安装docker。 在官网安装docker。 如果遇到wsl报错,就使用 wsl --updata 进行更新。如果问题解决,进入docker应该是如下界面: 克隆 在自己创建的文件内使用 git clone gitgithub.com:langgenius/dify.git 或 git clone https://github.com…...
python学习一
学习网络安全为什么要学python? 1、在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工 具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者 编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定 的编程能力。 2、python是一门编程语言经常用它…...
git branch
文章目录 1.简介2.格式3.选项4.示例参考文献 1.简介 git branch 用于管理分支,包括查看、创建、删除、重命名和关联。 git branch 是 Git 版本控制系统中用于管理分支的命令。分支是 Git 的核心功能之一,允许开发者在同一个代码库中并行开发不同的功能…...
算法-图-数据结构(邻接矩阵)-BFS广度优先遍历
邻接矩阵广度优先遍历(BFS)是一种用于遍历或搜索图的算法,以下是具体介绍: 1. 基本概念 图是一种非线性的数据结构,由顶点和边组成,可分为无向图、有向图、加权图、无权图等。邻接矩阵是表示图的一种数…...
数学建模之数学模型—2:非线性规划
文章目录 非线性规划基本概念与结论凸集与凸函数极值条件无约束条件的极值判断条件有约束条件的极值判断条件 无约束非线性规划一维搜索算法步骤示例特点代码模板 最速下降法算法详细步骤 代码实现示例最优步长的求解 黄金分割法斐波那契法牛顿法阻尼牛顿法模式搜索法Powell方法…...
unity学习51:所有UI的父物体:canvas画布
目录 1 下载资源 1.1 在window / Asset store下下载一套免费的UI资源 1.2 下载,导入import 1.3 导入后在 project / Asset下面可以看到 2 画布canvas,UI的父物体 2.1 创建canvas 2.1.1 画布的下面是 event system是UI相关的事件系统 2.2 canvas…...
ctfshow做题笔记—栈溢出—pwn57~pwn60
目录 前言 一、pwn57(先了解一下简单的64位shellcode吧) 二、pwn58 三、pwn59(64位 无限制) 四、pwn60(入门难度shellcode) 前言 往前写了几道题,与shellcode有关,关于shellc…...
数据结构 1-2 线性表的链式存储-链表
1 原理 顺序表的缺点: 插入和删除移动大量元素数组的大小不好控制占用一大段连续的存储空间,造成很多碎片 链表规避了上述顺序表缺点 逻辑上相邻的两个元素在物理位置上不相邻 头结点 L:头指针 头指针:链表中第一个结点的存储…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
