【ASP.NET CORE】 14. RabbitMQ、洋葱架构

详解“洋葱架构” 领域驱动设计(Domain-driven design,DDD)是一种为复杂需求开发软件的方法,它将软件的实现与不断发展的核心业务概念模型紧密地结合在一起。 领域是一个知识的范畴。它指的是我们的软件所要模拟的业务知识。领域驱动设计的中心是领域模型,它对一个领域的流程和规则有着深刻的理解。洋葱架构实现了这一概念,并极大地改善了代码的品质,降低了复杂性,并且支持不断发展的企业系统。... 阅读详情

本系列专栏基于杨中科老师的《ASP.NET Core技术内幕与项目实战》,本人记录梳理的学习笔记,有部分的增补和省略。更全面系统的讲解,请看杨老师的视频课:【.NET教程,.Net Core视频教程,杨中科主讲】。​

一、集成事件

领域事件解决同一微服务/界限上下文内的通信,而集成事件负责跨微服务、跨服务器的业务协同,是实现分布式事务最终一致性的核心,必须依托消息中间件实现,主流选型:RabbitMQ(中小型项目)、Kafka(高吞吐场景)、Redis(轻量场景)。

1.RabbitMQ核心概念

  • 信道(Channel):TCP连接之上的虚拟通信通道,TCP创建销毁开销大,信道轻量化、用完即关,实现单TCP连接多信道复用,提升性能。
  • 队列(Queue):消息暂存容器,生产者投递消息至队列,消费者从队列拉取消息,支持持久化、独占、自动删除等配置。
  • 交换机(Exchange):消息路由核心,根据RoutingKey将消息转发至对应队列,常用类型:direct(精准路由)、fanout(广播)、topic(模糊匹配),DDD集成事件优先用direct。
  • 消息确认机制:生产者确认、消费者手动ACK,避免消息丢失,保证分布式数据一致性。

2.代码示例

消息生产者(发布集成事件)

using RabbitMQ.Client;
using System.Text;

// 模拟订单微服务:发布订单支付集成事件
var factory = new ConnectionFactory();
// 服务器配置,生产环境建议写入配置文件
factory.HostName = "127.0.0.1";
factory.UserName = "guest"; // 默认账号
factory.Password = "guest"; // 默认密码
factory.DispatchConsumersAsync = true;

// 交换机、路由键(与消费者保持一致)
string exchangeName = "OrderExchange";
string eventName = "OrderPaidEvent"; // RoutingKey,对应集成事件名称

// 复用TCP连接,避免频繁创建
using var conn = factory.CreateConnection();
while (true)
{
    // 模拟订单支付消息,生产环境用实体序列化(JSON)
    string msg = $"订单{Random.Shared.Next(1000, 9999)}已支付,支付时间:{DateTime.Now}";
    // 信道用完即释放
    using (var channel = conn.CreateModel())
    {
        // 声明持久化交换机,不存在则创建
        channel.ExchangeDeclare(exchange: exchangeName, type: "direct", durable: true, autoDelete: false);

        // 消息持久化配置,服务器重启不丢失
        var properties = channel.CreateBasicProperties();
        properties.DeliveryMode = 2; // 2=持久化,1=非持久化

        // 消息编码
        byte[] body = Encoding.UTF8.GetBytes(msg);
        // 发布消息
        channel.BasicPublish(exchange: exchangeName, routingKey: eventName, mandatory: true, basicProperties: properties, body: body);
    }
    Console.WriteLine($"【生产者】发布集成事件:{msg}");
    Thread.Sleep(1000);
}

消息消费者(订阅集成事件)

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;

// 模拟通知微服务:订阅订单支付集成事件
var factory = new ConnectionFactory();
factory.HostName = "127.0.0.1";
factory.UserName = "guest";
factory.Password = "guest";
factory.DispatchConsumersAsync = true;

string exchangeName = "OrderExchange";
string eventName = "OrderPaidEvent";
string queueName = "OrderPaid_NotificationQueue"; // 消费者专属队列

using var conn = factory.CreateConnection();
using var channel = conn.CreateModel();

// 声明交换机、队列、绑定路由
channel.ExchangeDeclare(exchangeName, "direct", durable: true);
// 声明持久化队列
channel.QueueDeclare(queue: queueName, durable: true, exclusive: false, autoDelete: false, arguments: null);
// 队列绑定交换机,通过RoutingKey路由
channel.QueueBind(queue: queueName, exchange: exchangeName, routingKey: eventName);

// 异步消费者
var consumer = new AsyncEventingBasicConsumer(channel);
// 绑定消息处理事件
consumer.Received += Consumer_Received;
// 启动消费,关闭自动ACK,手动确认避免消息丢失
channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);

Console.WriteLine("【消费者】等待订单支付事件...");
Console.ReadLine();

// 消息处理逻辑
async Task Consumer_Received(object sender, BasicDeliverEventArgs args)
{
    try
    {
        var bytes = args.Body.ToArray();
        string msg = Encoding.UTF8.GetString(bytes);
        Console.WriteLine($"【消费者】收到集成事件:{msg}");

        // 执行业务:发送短信、推送通知、更新库存
        await Task.Delay(800);

        // 手动ACK,确认消息处理完成
        channel.BasicAck(args.DeliveryTag, multiple: false);
    }
    catch (Exception ex)
    {
        // 消费失败:重新放回队列重试,生产环境可加死信队列
        channel.BasicReject(args.DeliveryTag, requeue: true);
        Console.WriteLine($"【消费者】消息处理失败:{ex.Message},已重新入队");
    }
}

注意:原生RabbitMQ代码冗余、切换MQ成本高,推荐使用杨老师的Zack.EventBus开源库,优势在于屏蔽底层MQ差异,切换RabbitMQ/Kafka/Redis无需修改业务代码;自动实现消息序列化、ACK、异常处理、重试机制;贴合DDD集成事件规范,与MediatR领域事件无缝衔接;降低分布式事件开发门槛,减少重复代码。

二、洋葱架构

洋葱架构是DDD落地的核心架构模式,彻底解决传统三层架构的痛点,实现业务核心与技术实现解耦,是复杂业务系统、微服务的首选架构。

1.传统三层架构缺点

  • 面向数据库设计,而非业务驱动,违背DDD理念。
  • 简单CRUD也需要BLL层转发,代码冗余严重。
  • 单向依赖,内层无法调用外层,扩展性差。
  • 业务逻辑散落各层,难以维护和测试。

2.洋葱架构原则

  • 依赖方向一律向内,内层定义接口,外层实现细节。
  • 内层更抽象,表达业务核心(领域、规则),不依赖任何框架、数据库、UI。
  • 外层更具体,实现技术细节(EF、MQ、控制器、仓储)。
  • 内层通过依赖注入间接调用外层,实现解耦。
  • 业务逻辑集中在领域层,保证核心代码纯净、可移植。

3.洋葱架构分层

  • 领域核心层:实体、值对象、聚合根、领域事件、领域接口。
  • 领域服务层:封装核心业务规则,纯业务逻辑,无技术依赖。
  • 应用服务层:协调领域服务、外部系统,处理业务用例,不写核心规则。
  • 基础设施层:实现仓储、EF Core、MQ、日志、缓存等技术细节。
  • 接口层:Web API、控制器、前端交互,对外提供服务入口。

4.代码示例

领域层:定义仓储接口

领域层只定义接口,不涉及具体实现,完全脱离数据库、EF框架。

// 领域层:通用仓储接口(内层)
public interface IRepository<T> where T : class, IAggregateRoot
{
    Task<T?> GetByIdAsync(int id);
    Task AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(T entity);
}

// 领域层:用户专属仓储接口
public interface IUserRepository : IRepository<User>
{
    // 自定义业务查询
    Task<User?> GetByUserNameAsync(string userName);
}

领域服务层:核心业务逻辑

领域服务只依赖内层仓储接口,不接触任何技术细节,专注业务规则。

// 领域服务:用户领域核心业务(DDD核心)
public class UserDomainService
{
    private readonly IUserRepository _userRepository;

    // 依赖注入仓储接口,不依赖具体实现
    public UserDomainService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    /// <summary>
    /// 用户注册业务(包含完整业务规则)
    /// </summary>
    public async Task RegisterUserAsync(string userName, string password)
    {
        // 业务规则1:校验用户名是否重复
        var existUser = await _userRepository.GetByUserNameAsync(userName);
        if (existUser != null)
            throw new InvalidOperationException("用户名已存在,请勿重复注册");

        // 业务规则2:通过充血模型创建用户,自带参数校验
        var user = new User(userName);
        user.ChangePassword(password); // 充血模型内置密码长度校验

        // 持久化交由仓储实现,领域服务不关心数据库操作
        await _userRepository.AddAsync(user);
    }

    /// <summary>
    /// 用户积分增加业务
    /// </summary>
    public void AddUserCredit(User user, int credit)
    {
        if (credit <= 0)
            throw new InvalidOperationException("积分必须为正数");
        // 直接操作充血模型,保证状态合法性
        user.AddCredit(credit);
    }
}

基础设施层:仓储实现

基础设施层实现领域层定义的仓储接口,依托EF Core完成数据库操作。

// 基础设施层:EF仓储基类
public class EfRepository<T> : IRepository<T> where T : class, IAggregateRoot
{
    protected readonly AppDbContext DbContext;

    public EfRepository(AppDbContext dbContext)
    {
        DbContext = dbContext;
    }

    public async Task<T?> GetByIdAsync(int id)
    {
        return await DbContext.Set<T>().FindAsync(id);
    }

    public async Task AddAsync(T entity)
    {
        await DbContext.Set<T>().AddAsync(entity);
    }

    public Task UpdateAsync(T entity)
    {
        DbContext.Set<T>().Update(entity);
        return Task.CompletedTask;
    }

    public Task DeleteAsync(T entity)
    {
        DbContext.Set<T>().Remove(entity);
        return Task.CompletedTask;
    }
}

// 基础设施层:用户仓储实现
public class EfUserRepository : EfRepository<User>, IUserRepository
{
    public EfUserRepository(AppDbContext dbContext) : base(dbContext)
    {
    }

    public async Task<User?> GetByUserNameAsync(string userName)
    {
        return await DbContext.Users.FirstOrDefaultAsync(u => u.UserName == userName);
    }
}

应用服务层:业务用例协调

// 应用服务:对外提供业务接口,不写核心规则
public class UserAppService
{
    private readonly UserDomainService _userDomainService;
    private readonly IUnitOfWork _unitOfWork;

    public UserAppService(UserDomainService userDomainService, IUnitOfWork unitOfWork)
    {
        _userDomainService = userDomainService;
        _unitOfWork = unitOfWork;
    }

    /// <summary>
    /// 用户注册接口(供控制器调用)
    /// </summary>
    public async Task RegisterAsync(string userName, string password)
    {
        // 应用层仅协调,业务逻辑交由领域服务
        await _userDomainService.RegisterUserAsync(userName, password);
        // 工作单元统一提交事务
        await _unitOfWork.SaveChangesAsync();
    }
}

三、DDD完整版脚手架

补充洋葱架构必备的工作单元、规约模式、事务管控、全局异常。

1.工作单元模式

统一管理事务、批量提交数据库操作,保证数据一致性,避免多次SaveChanges导致的事务问题。

// 领域层:工作单元接口
public interface IUnitOfWork : IDisposable
{
    Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

// 基础设施层:工作单元实现
public class UnitOfWork : IUnitOfWork
{
    private readonly AppDbContext _dbContext;

    public UnitOfWork(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        // 统一提交事务,附带领域事件发布
        return await _dbContext.SaveChangesAsync(cancellationToken);
    }

    public void Dispose()
    {
        _dbContext.Dispose();
    }
}

2.规约模式

封装查询条件,将查询规则从应用层剥离至领域层,保证查询逻辑复用、业务规则统一。

// 领域层:规约基类
public abstract class Specification<T>
{
    public abstract Expression<Func<T, bool>> ToExpression();

    public bool IsSatisfiedBy(T entity)
    {
        return ToExpression().Compile()(entity);
    }
}

// 领域层:用户活跃规约示例
public class ActiveUserSpecification : Specification<User>
{
    public override Expression<Func<User, bool>> ToExpression()
    {
        return u => u.Credit > 0 && !string.IsNullOrEmpty(u.PasswordHash);
    }
}

3.事务管控与分布式事务

  • 单体事务:依托工作单元+EF Core事务,保证单服务数据一致性。
  • 分布式事务:集成事件+最终一致性,通过RabbitMQ消息确认、重试机制实现。
  • 避免本地事务+消息队列混合使用导致的数据不一致。

4.全局异常处理

统一捕获业务异常、框架异常,返回标准化响应,提升接口健壮性。

// 全局异常中间件
public class GlobalExceptionMiddleware
{
    private readonly RequestDelegate _next;

    public GlobalExceptionMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (InvalidOperationException ex)
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
            await context.Response.WriteAsJsonAsync(new { Code = 400, Msg = ex.Message });
        }
        catch (Exception ex)
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            await context.Response.WriteAsJsonAsync(new { Code = 500, Msg = "服务器异常,请稍后重试" });
        }
    }
}

5.服务注册

Program.cs

// 注册DDD核心服务
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped<IUserRepository, EfUserRepository>();
builder.Services.AddScoped<UserDomainService>();
builder.Services.AddScoped<UserAppService>();

// 注册全局异常中间件
app.UseMiddleware<GlobalExceptionMiddleware>();

总结

  1. 集成事件:RabbitMQ实现跨微服务通信,手动ACK+持久化保证消息可靠,Zack.EventBus简化开发。
  2. 洋葱架构:依赖向内、业务与技术解耦,领域层纯净无框架依赖,领域服务封装核心规则。
  3. 脚手架核心:工作单元管控事务、规约模式封装查询、全局异常统一处理,落地DDD实践。
ASP.NET Core 10 Minimal APIs 快速构建轻量HTTP接口完整指南 HTTP接口是前后端协作与微服务通信的基础。传统Web API开发常伴随着Controller、模型绑定、分层结构等大量样板代码,而Minimal APIs提供了一条更轻量的技术路线,让开发者用最少的代码完成路由注册、参数绑定和结果返回。其核心原理是借助ASP.NET Core 底座,通过MapGet等声明式方法将请求直接映射到Lambda表达式,同时完整支持依赖注入、配置读取和中间件扩展,具备快速交付与灵活演进的技术价值。在内部工具、Webhook接收端、微服务端点以及AI服务转发层等场景中,Minima 阅读详情

相关推荐

DDD系列 - 第0讲 DDD中常提到的应用架构总结(六边形、洋葱、整洁、清晰)

最近在学习DDD应用架构设计时,接触到了不同的应用架构设计概念,如六边形架构、洋葱架构、整洁架构、清晰架构...,起初是一头雾水,在不断学习过程中也算对此有了些理解,故在本文中对这些架构进行了简单的介绍和总结。............

罗小爬的技术宝书 9424

【架构】洋葱架构

领域驱动设计(Domain-driven design,DDD)是一种为复杂需求开发软件的方法,它将软件的实现与不断发展的核心业务概念模型紧密地结合在一起。领域是一个知识的范畴。它指的是我们的软件所要模拟的业务知识。领域驱动设计的中心是领域模型,它对一个领域的流程和规则有着深刻的理解。洋葱架构实现了这一概念,并极大地改善了代码的品质,降低了复杂性,并且支持不断发展的企业系统。洋葱架构在开始时可能似乎有些困难,但是 在业界已经得到了普遍的认可。这是一种让软件易于演进的强有力架构。

DreamSun的博客 1375

戴尔PowerEdge服务器R450 RAID配置步骤

戴尔PowerEdge服务器R450 RAID配置步骤

且听风呤tmj的博客 6911

洋葱架构、三层架构及两者区别

洋葱架构它的名称来源于洋葱的层次结构,即软件代码的各层次之间的关系。在这种架构中,应用程序的各个组件通过一系列层次结构被逐层包裹在一起,形成一个类似于洋葱的结构。

qq_58159494的博客 2201

Ubuntu技巧-Ubuntu远程访问之SSH反向代理

💡大家好,我是可夫小子,《小白玩转ChatGPT》专栏作者,关注AIGC、互联网和自媒体。要把内网的一台的Ubuntu服务器让外面也能访问,总共有三种可行的方案,前面已经介绍了两种方式,今天就来介绍更健壮稳定的方式——ssh反向代理。大概的框架图如下,今天就从服务器购买到设置,手把手的教程,让你花费100块以内,就拥有一个强大的「云服务器」。

可夫小子的频道 1152

20240128 C#与SCPI协议实战:轻量级仪器控制库ScpiNet应用指南

本文详细介绍了轻量级C#库ScpiNet在仪器控制中的应用。相较于传统的NI-VISA,ScpiNet无需安装庞大运行时,通过纯C#实现直接与USB或以太网设备通信,支持跨厂商SCPI仪器。文章提供了从快速连接到实战封装、错误处理的完整指南,帮助开发者构建简洁高效的自动化测试系统。

h9j8k7l6m5n的博客 548

什么是洋葱架构?Onion Architecture

定义层次结构依赖关系特点优点

软件行业技术文化交流。 1943

Innovus Function ECO实战:5步搞定数字后端设计中的网表修正(含Calibre LVS验证技巧)

本文提供了一份完整的Innovus Function ECO实战指南,详细解析了数字后端设计中网表修正的五步流程。从理解Function ECO概念、准备关键文件,到在Innovus中执行逻辑比对、自动布线及GUI检查,并重点攻克了ECO后Calibre LVS验证的难点与调试技巧,帮助工程师高效、精准地完成流片前的设计微调。

day7的博客 950

ASP.NET Core2利用MassTransit集成RabbitMQ

在ASP.NET Core上利用MassTransit来集成使用RabbitMQ真的很简单,代码也很简洁。近期因为项目需要,我便在这基础上再次进行了封装,抽成了公共方法,使得使用RabbitMQ的调用变得更方便简洁。那么,就让咱们来瞧瞧其魅力所在吧。 MassTransit 先看看MassTransit是个什么宝贝(MassTransit官网的简介): MassTransit...

560

ORB-SLAM3复现的详细过程——配置安装及ROS和脚本运行---Ubuntu20.04

所提供的图像具有高动态范围,事先进行了光度标定,用20Hz的帧率采集,分辨率为1024像素*1024像素;需要下载数据集dataset-room1_512_16和rgbd_dataset_freiburg1_desk。在一键安装ROS后,只需要再安装Pangolin即可,其他所有的需要都已经存在。用微型飞行器采集的视觉惯性数据集,包含双目相机和IMU同步测量数据及位姿的真值。第二条命令中的${ROS_PACKAGE_PATH}是自己文件的路径。其采用的相机和IMU传感器,在硬件上进行了时间同步。

琉璃轻纱的博客 1万+

保姆级教程:用CameraX API快速搞定Android多摄像头切换与预览(附避坑指南)

本文提供了一份详细的CameraX API教程,帮助开发者快速实现Android多摄像头切换与预览功能。通过简化API设计和生命周期感知能力,CameraX大幅降低了开发复杂度,同时解决了多摄像头兼容性、预览优化等常见问题。文章包含完整代码示例和实战避坑指南,特别适合需要高效集成相机功能的Android开发者。

weixin_42699679的博客 166

ASP.NET Core 2.0利用MassTransit集成RabbitMQ

在ASP.NET Core上利用MassTransit来集成使用RabbitMQ真的很简单,代码也很简洁。近期因为项目需要,我便在这基础上再次进行了封装,抽成了公共方法,使得使用RabbitMQ的调用变得更方便简洁。那么,就让咱们来瞧瞧其魅力所在吧。   MassTransit 先看看MassTransit是个什么宝贝(MassTransit官网的简介): MassTransit是一个免费...

qq_38462278的博客 599

锂电池SOH估计 | 基于LSTM模型的锂电池SOH估计(Python)

锂电池SOH估计 | 基于LSTM模型的锂电池SOH估计(Python)

前程算法屋的博客 1434

ASP.NET Core2基于RabbitMQ对Web前端实现推送功能

在我们很多的Web应用中会遇到需要从后端将指定的数据或消息实时推送到前端,通常的做法是前端写个脚本定时到后端获取,或者借助WebSocket技术实现前后端实时通讯。因定时刷新的方法弊端很多(已不再采用),所以基于WebSocket技术实现的通讯方案正越来越受大家喜爱,于是在ASP.NET中就有了鼎鼎大名的Signalr。但Signalr不是咱们这里的主角,这里将给大家介绍另一套基于Web...

798

C# 开发实战:深度解析 13.56MHz 射频卡读写应用

本文详细介绍了如何在 C# 环境下开发 13.56MHz 射频卡的读写应用。通过 ACR122U 作为硬件设备,结合 C# 提供的 COM 组件或 SDK,我们能够实现对射频卡的读取与写入功能。在实际开发中,开发者还需要根据具体应用场景选择合适的卡片类型与设备,并注意性能优化和异常处理。随着技术的发展,RFID 技术的应用场景会越来越广泛,开发者可以根据不同需求,灵活扩展读写功能,实现更加智能化和自动化的系统。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1602

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

asp.net core程序在k8s中基于rabbitmq队列消息数的HPA实践!

背景最近一段时间,陆陆续续的把手里头项目都迁移到了k8s中,期间遇到很多的坑,并且也学到了许多k8s的知识(从0-1),大家都知道k8s中的一大特性是自动扩容,对此结合自己的业务发现很是...

dotNET跨平台 589

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

【微服务架构必备技能】:ASP.NET Core健康检查端点的9种高级用法

掌握ASP.NET Core健康检查端点的9种高级用法,提升微服务稳定性与可观测性。涵盖自定义检查、UI监控、第三方依赖检测等场景,支持快速故障定位与自动化运维。构建高可用系统的关键实践,值得收藏。

simcode的专栏 943

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

.NET周刊【11月第1期 2025-11-02】

本文集精选了.NET技术领域的多篇优质文章,涵盖性能优化、框架集成、开源项目等多个方向。主要内容包括:TaskFactory并发优化实践、Blazor与现代Web开发比较、Microsoft Agent框架的多智能体协作应用、Serilog日志库实践、RabbitMQ与ASP.NET Core集成方案等。技术深度与实用性兼备,既包含底层原理分析如ILAccess.Fody实现私有访问,也介绍了LVGLSharp等创新性开源项目。这些文章为.NET开发者提供了前沿技术参考和实用开发指南,展现了.NET生态的多

InCerry的博客 561

.NET Core 事件总线,分布式事务解决方案:CAP

官网:https://cap.dotnetcore.xyz/user-guide/zh/getting-started/quick-start/在构建分布式应用的过程中也会遇到分布式事务的问题,那么 CAP 就是在这样的背景下诞生的。https://github.com/dotnetcoreGithub:https://github.com/dotnetcore/CAP开源协议:MITCAP 是一个在分布式系统中(SOA,MicroService)实现事件总线及最终一致性(分布式事务)的一个开源的 C# 库

Lifelong learning 4405

用ASP.NET Core2重写了一款网络考试培训的免费软件

在IT圈混迹了近十年,今已正当而立之年却仍一事无成,心中倍感惶恐惭愧。面对竟争如此激列的环境,该如何应对?却也知不能让自已闲着,得转起来,动起来。于是,便想着利用最新技术栈将自已原来的收费产品重写一次,且完全开放免费的提供给最终用户使用。这也算是让自已多年的行业积累能有一个更大的扩展,同时也能让有网络考试培训需求的用户能快速免费的使用上该软件。下面,就来简单的说说这款培训考试的免费软件吧...

337
上一篇: 【ASP.NET CORE】 13. DDD初步实现
下一篇: 【系统分析师_知识点整理】 1.计算机系统
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值