ABP使用学习


创建项目(纯后端项目)

abp new 项目名称 -u none (不包含前端) -v 6.0.0(版本号)


连接数据库

  • 修改Acme.BookStore.DbMigrator中的appsettings.json中的连接字符串
  • Acme.BookStore.HttpApi.Host中的appsettings.json中的连接字符串


创建表并且迁移到数据库

  • 在Domain层中创建实体
  • 一个实体对应一个文件夹(以Book为例)
//需要继承 AuditedAggregateRoot<T>这个类(为审计字段) T为主键的类型,主键默认命名为Id
 
 public class Book:AuditedAggregateRoot<Guid>
 
  {
 
      public string BName { get; set; }
 
  
 
      public BookType BType { get; set; }
 
  
 
      public DateTime BPublishDate { get; set; }
 
  
 
      public float BPrice { get; set; }
 
  }
  • 在Domain.Shared层创建枚举之类的
  • 在EntityFrameworkCore层的BookStoreDbContext的上下文创建实体
public class BookStoreDbContext :
 
     AbpDbContext<BookStoreDbContext>, IIdentityDbContext,
 
  ITenantManagementDbContext
 
  {
 
       public DbSet<Book> Books { get; set; }//创建Book表
 
 }
  • 在迁移时加入数据库约束和数据
protected override void OnModelCreating(ModelBuilder builder)
 
 {
 
     base.OnModelCreating(builder);
 
  
 
     /* Include modules to your migration db context */
 
  
 
     builder.ConfigurePermissionManagement();
 
     builder.ConfigureSettingManagement();
 
     builder.ConfigureBackgroundJobs();
 
     builder.ConfigureAuditLogging();
 
     builder.ConfigureIdentity();
 
     builder.ConfigureOpenIddict();
 
     builder.ConfigureFeatureManagement();
 
     builder.ConfigureTenantManagement();
 
  
 
     /* Configure your own tables/entities inside here */
 
 	//跟以前的写法一样
 
     builder.Entity<Book>(b =>
 
     {
 
         b.ToTable(BookStoreConsts.DbTablePrefix + nameof(Book), 					BookStoreConsts.DbSchema);
 
         b.ConfigureByConvention(); //auto configure for the base class props
 
         //...
 
         b.Property(c => c.BName).IsRequired().HasMaxLength(50);
 
     });
 
 }
  • 迁移数据库,将程序包管理器的默认项目设置为EntityFrameworkCore
  • 正常使用数据库迁移指令即可


创建服务和生成动态的API(自动生成)

  • 在Application.Contracts层中创建一个类的文件夹
  • 需要定义一个实体的显示DTO和实体的添加修改DTO,服务接口
//需要继承 AuditedEntityDto<T>带审计字段DTO基类 T为主键的类型这个是用于显示的
 
 public class BookDTO:AuditedEntityDto<Guid>
 
  {
 
      public string BName { get; set; }
 
  
 
      public BookType BType { get; set; }
 
  
 
      public DateTime BPublishDate { get; set; }
 
  
 
      public float BPrice { get; set; }
 
  }
//以这个表为添加和修改的DTO并且可以直接搁这里面加入约束,swagger中会根据该约束进行判断
 
 public class CreateUpdateBookDto
 
  {
 
      [Required]
 
      [StringLength(128)]
 
      public string BName { get; set; }
 
  
 
      [Required]
 
      public BookType BType { get; set; } = BookType.Undefined;
 
  
 
      [Required]
 
      [DataType(DataType.Date)]
 
      public DateTime BPublishDate { get; set; } = DateTime.Now;
 
  
 
      [Required]
 
      public float BPrice { get; set; }
 
  }
  • 创建实体的服务接口
public interface IBookAppService : ICrudAppService< //Defines CRUD methods(创建CRUD方法,CRUD为增删改查)
 
         BookDTO, //Used to show books(数据显示的类型(直接用显示的DTO))
 
         Guid, //Primary key of the book entity(主键,删除和修改根据主键进行)
 
         PagedAndSortedResultRequestDto, //Used for paging/sorting(分页使用的表(DTO))
 
         CreateUpdateBookDto> //Used to create/update a book(添加修改使用的DTO)
 
 {
 
  
 
 }
  • 创建实体的服务
//需要继承 CrudAppServic接口<实体,显示的类型,主键,分页,添加修改>(创建一个定义了Crud的服务实现),定义的接口
 
 public class BookAppService : CrudAppService<Book, BookDTO, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>, IBookAppService
 
  {
 
      public BookAppService(IRepository<Book, Guid> repository) : 			base(repository)
 
      {
 
      }
 
  }


创建自定义服务和仓储

  • 在Application.Contracts层中创建一个类的文件夹
  • 需要定义一个实体的显示DTO和实体的添加修改DTO,并且包含实体的服务接口
//需要继承IApplicationService(实现这个接口的类型应该被当作一个应用服务来对待)
 
 public interface IAuthorService:IApplicationService
 
  {
 
      Task<AuthorDTO> GetAsync(Guid id);
 
  }
  • BookStore.Domain层的实体文件夹定义实体的仓储接口
//用于定义对数据库中的数据实体进行操作的接口
 
 public interface IAuthorRepository:IRepository<Author,Guid>
 
    {
 
        Task<Author> GetAsync(Guid id); 
 
    }
  • EntityFrameworkCore层的实体文件夹中定义实体的仓储实现
public class EFCoreAuthorRepository : 
 
 EfCoreRepository<BookStoreDbContext, Author, Guid>, //EfCoreRepository(或类似的名称)是 IRepository 接口的一个具体实现,它使用EF Core作为ORM(对象关系映射器)来与数据库交互。
 
 IAuthorRepository//继承的接口
 
 {
 
     //IDbContextProvider提供对 DbContext 的访问
 
     public EFCoreAuthorRepository(IDbContextProvider<BookStoreDbContext> dbContextProvider) : base(dbContextProvider)
 
     {
 
  
 
     }
 
     public async Task<Author> GetAsync(Guid id)
 
     {
 
         var dbSet=await GetDbSetAsync();
 
         return await dbSet.FirstOrDefaultAsync(c => c.Id == id);
 
     }
 
 }
  • 定义服务实现
public class AuthorService : BookStoreAppService, IAuthorService
 
  {
 
      private readonly IAuthorRepository repository;
 
      private readonly IMapper mapper;
 
      public AuthorService(IAuthorRepository repository, IMapper mapper)
 
      {
 
          this.repository = repository;
 
          this.mapper = mapper;
 
      }
 
   
 
      public async Task<AuthorDTO> GetAsync(Guid id)
 
      {
 
          var entity=repository.GetAsync(id);
 
          return mapper.Map<AuthorDTO>(entity);
 
      }
 
  }


结合使用

不用定义仓储

  • 定义服务的接口
//需要继承IApplicationService(实现这个接口的类型应该被当作一个应用服务来对待)
 
 public interface IAuthorService:IApplicationService
 
  {
 
      Task<AuthorDTO> GetAsync(Guid id);
 
  }
  • 定义服务的实现
  • 可以直接使用IRepository实现仓储的定义
public class AuthorService : BookStoreAppService, IAuthorService
 
 {
 
     private readonly IRepository<Admin,Guid> repository;
 
     private readonly IMapper mapper;
 
     public AuthorService(IRepository<Admin,Guid> repository, IMapper mapper)
 
     {
 
         this.repository = repository;
 
         this.mapper = mapper;
 
     }
 
   
 
     public async Task<AuthorDTO> GetAsync(Guid id)
 
     {
 
         var entity=repository.GetAsync(id);
 
         return mapper.Map<AuthorDTO>(entity);
 
     }
 
 }
  • 使用语法糖
//需要继承 CrudAppServic接口<实体,显示的类型,主键,分页,添加修改>(创建一个定义了Crud的服务实现),定义的接口
 
 public class BookAppService : CrudAppService<Book, BookDTO, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>, IBookAppService
 
  {
 
      public BookAppService(IRepository<Book, Guid> repository) : 			base(repository)
 
      {
 
          	
 
      }
 
     //使用重写直接重写需要用的方法
 
     public override async Task<BookDTO> CreateAsync(CreateUpdateBookDto 	input)
 
 	{
 
 	    return base.CreateAsync(input);
 
 	}
 
  }


继承的区别

AuditedEntityDto和EntityDto的区别

  • EntityDto:这是基于实体的DTO,用于在不同的层之间传递实体的基本信息。它可能只包含实体的标识符(ID)和一些基本属性,不包含审计信息或完整的业务逻辑。(不包含审计字段)
  • AuditedEntityDto:这个DTO继承了CreationAuditedEntityDto,并在此基础上封装了额外的审计信息,如最后修改时间(LastModificationTime)和最后修改者用户ID(LastModifierUserId)。这些信息对于需要跟踪数据更改历史的场景非常有用。(包含审计字段)

CrudAppService

作用:自动创建一个实现Crud的服务实现,仓储接口同理

public class BookAppService : CrudAppService<Book, BookDTO, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>

ICrudAppService

作用:自动创建一个实现Crud的服务接口,可以不带实体,因为使用了范式,但是需要定义显示DTO,主键和分页的DTO还有添加修改的DTO

IRepository

作用:用于定义对数据库中的数据实体进行操作的接口

EfCoreRepository

  • EfCoreRepository(或类似的名称)是 IRepository 接口的一个具体实现,它使用EF Core作为ORM(对象关系映射器)来与数据库交互。
  • 这个实现类会包含EF Core的 DbContext 的引用,并通过它来执行数据库操作。
  • 它会将接口中的方法转换为具体的EF Core查询和命令。


如何将ABP改成自己需要的模式

注册服务和使用服务:通过ABP中的HOST的层封装好的HttpApiHostModule.cs文件中进行服务的注册

public override void ConfigureServices(ServiceConfigurationContext context)
 
 {
 
    var configuration = context.Services.GetConfiguration();
 
 	var hostingEnvironment = context.Services.GetHostingEnvironment();
 
 //注册服务
 
 YitIdHelper.SetIdGenerator(new IdGeneratorOptions());
 
 //注册上下文:AOP里面可以获取IOC对象,如果有现成框架比如Furion可以不写这一行
 
 //context等于以前的builder
 
 context.Services.AddHttpContextAccessor();
 
 context.Services.AddMemoryCache();
 
 //验证码
 
 ConfigureCaptcha(context, configuration);
 
 //允许使用Post
 
 ConfigureAntiForgery();
 
 //配置JWT(ABP自带的JWT授权,需要我们自己修改成自己想要的)
 
 ConfigureAuthentication(context);
 
 //自带
 
 ConfigureBundles();
 
 ConfigureUrls(configuration);
 
 ConfigureConventionalControllers();
 
 ConfigureLocalization();
 
 ConfigureVirtualFileSystem(context);
 
 ConfigureCors(context, configuration);
 
 //配置swagger(ABP自带,可以直接修改成我们想要的)
 
 ConfigureSwaggerServices(context, configuration);
 
 }
 
 //例如
 
     /// <summary>
 
     /// 验证码配置
 
     /// </summary>
 
     /// <param name="context"></param>
 
     /// <param name="configuration"></param>
 
     private void ConfigureCaptcha(ServiceConfigurationContext context, IConfiguration configuration)
 
     {
 
         // 默认使用内存存储(AddDistributedMemoryCache)
 
         context.Services.AddCaptcha(configuration);
 
  
 
         // 全部配置
 
         context.Services.AddCaptcha(configuration, option =>
 
         {
 
             option.CaptchaType = CaptchaType.WORD; // 验证码类型
 
             option.CodeLength = 6; // 验证码长度, 要放在CaptchaType设置后.  当类型为算术表达式时,长度代表操作的个数
 
             option.ExpirySeconds = 30; // 验证码过期时间
 
             option.IgnoreCase = true; // 比较时是否忽略大小写
 
             option.StoreageKeyPrefix = ""; // 存储键前缀
 
  
 
             option.ImageOption.Animation = true; // 是否启用动画
 
             option.ImageOption.FrameDelay = 30; // 每帧延迟,Animation=true时有效, 默认30
 
  
 
             option.ImageOption.Width = 150; // 验证码宽度
 
             option.ImageOption.Height = 50; // 验证码高度
 
             option.ImageOption.BackgroundColor = SkiaSharp.SKColors.White; // 验证码背景色
 
  
 
             option.ImageOption.BubbleCount = 2; // 气泡数量
 
             option.ImageOption.BubbleMinRadius = 5; // 气泡最小半径
 
             option.ImageOption.BubbleMaxRadius = 15; // 气泡最大半径
 
             option.ImageOption.BubbleThickness = 1; // 气泡边沿厚度
 
  
 
             option.ImageOption.InterferenceLineCount = 2; // 干扰线数量
 
  
 
             option.ImageOption.FontSize = 36; // 字体大小
 
             option.ImageOption.FontFamily = DefaultFontFamilys.Instance.Actionj; // 字体
 
  
 
             /* 
 
              * 中文使用kaiti,其他字符可根据喜好设置(可能部分转字符会出现绘制不出的情况)。
 
              * 当验证码类型为“ARITHMETIC”时,不要使用“Ransom”字体。(运算符和等号绘制不出来)
 
              */
 
  
 
             option.ImageOption.TextBold = true;// 粗体,该配置2.0.3新增
 
         });
 
     }
 
   /// <summary>
 
   /// 一起Program中的App
 
   /// </summary>
 
   /// <param name="context"></param>
 
   public override void OnApplicationInitialization(ApplicationInitializationContext context)
 
   {
 
       var app = context.GetApplicationBuilder();
 
       var env = context.GetEnvironment();
 
  
 
       if (env.IsDevelopment())
 
       {
 
           app.UseDeveloperExceptionPage();
 
       }
 
  
 
       app.UseAbpRequestLocalization();
 
  
 
       if (!env.IsDevelopment())
 
       {
 
           app.UseErrorPage();
 
       }
 
  
 
       app.UseCorrelationId();
 
       app.UseStaticFiles();
 
       app.UseRouting();
 
       app.UseCors();
 
       app.UseAuthentication();
 
       //app.UseAbpOpenIddictValidation();
 
  
 
       //if (MultiTenancyConsts.IsEnabled)
 
       //{
 
       //    app.UseMultiTenancy();
 
       //}
 
  
 
       app.UseUnitOfWork();
 
       app.UseAuthorization();
 
  
 
       app.UseSwagger();
 
       app.UseSwaggerUI(c => {
 
           c.SwaggerEndpoint("/swagger/v1/swagger.json", "基础接口");
 
           c.SwaggerEndpoint("/swagger/v2/swagger.json", "业务接口");
 
  
 
           // 模型的默认扩展深度,设置为 -1 完全隐藏模型
 
           c.DefaultModelsExpandDepth(0);
 
           // API文档仅展开标记
 
           c.DocExpansion(DocExpansion.List);
 
           // API前缀设置为空
 
           c.RoutePrefix = string.Empty;
 
           // API页面Title
 
           c.DocumentTitle = "😍接口文档 - 农业产品小组";
 
       });
 
  
 
       app.UseAuditing();
 
       app.UseAbpSerilogEnrichers();
 
       app.UseConfiguredEndpoints();
 
   }

因为定义了swagger的分类和授权所以需要我们的接口加上授权验证和接口分类

[ApiExplorerSettings(GroupName = "v1")]//方法分类
 
  [Authorize]//授权