当前位置: 首页 > news >正文

Abp Vnext Pro Vben5

abp-vnext-pro: abp vnext vue

dotnet run --urls=“http://localhost:44356/”

官方工具

多租户

-------------------------

应用通用底层框架

Abp Vnext Pro_哔哩哔哩_bilibili

Domain.Shared

DDD中最核心的部分
Domain.Shared项目包含常量,枚举和其他对象,这些对象实际上是领域层的一部分,但是解决方案中所有的层/项目中都会使用到。

  • Domain.Shared不依赖解决方案中的其他项目,其他项目直接或间接依赖该项目。
  • NuGet安装:Volo.Abp.AuditLogging.Domain.Shared

Domain

Domain主要包含实体集合根领域服务值类型存储接口和解决方案的其他领域对象。

  • Domain项目依赖于Domain.Shared项目,因为项目中会用到它的一些常量,枚举和定义其他对象。
  • NuGet安装:Volo.Abp.Ddd.Domain

Application.Contracts

Application.Contracts项目主要包含IService应用服务接口和应用层的数据传输对象 (DTO)。它用于分离应用层的接口和实现。这种方式可以将接口项目做为约定包共享给客户端。

  • Application.Contracts项目依赖于Domain.Shared项目,因为它可能会在IService应用接口和DTO中使用常量、枚举和其他的共享对象。
  • NuGet安装:Volo.Abp.Ddd.Application.Contracts

Application

Application项目包含Application.Contracts项目的IService应用服务接口实现.

  • Application项目依赖于Application.Contracts项目,因为它需要实现IService接口与使用DTO
  • Application项目依赖于Domain项目,因为它需要使用领域对象(Entity实体,Repository存储接口等)执行应用程序逻辑。
  • NuGet安装:Volo.Abp.Ddd.Application

EntityFrameworkCore

EntityFrameworkCore项目集成EF Core项目,它定义了DbContext并实现Domain项目中定义的Repository存储层。

  • EntityFrameworkCore项目依赖于Domain,因为它需要引用Entity实体和Repository存储接口。
  • NuGet安装:Volo.Abp.EntityFrameworkCore.XXX

DbMigrator

配置连接字符串,给EntityFrameworkCore项目引用
如果需要通过程序包管理器生成数据库或代码,需要引用EntityFrameworkCoreApplication.Contracts项目

  • DbMigrator项目依赖于Application.Contracts项目
  • DbMigrator项目依赖于EntityFrameworkCore项目

HttpApi

远程服务层,即WebApi

  • HttpApi项目依赖于Application.Contracts项目

HttpApi.Client

远程服务代理层,客户端应用程序Blazor引用该项目,将直接通过依赖注入使用远程应用服务。

  • HttpApi.Client项目依赖于Application.Contracts项目

HttpApi.Host

WebApi项目的启动项,为swagger

  • HttpApi.Host项目依赖于Application项目
  • HttpApi.Host项目依赖于EntityFrameworkCore项目
  • HttpApi.Host项目依赖于HttpApi项目

前端

MVCBlazorVueAngular等等,可能会包含ViewModel

命名空间

除了Domain.Shared是使用自己的命名空间,其它项目的命名空间都是一样的

模块化

解决方案下的每一个项目都有一个Module类,继承AbpModule,注意设置项目的默认命名空间和目标框架,项目框架可能需要设置成.NET Standard 2.0,因为Domain.SharedApplication.ContractsHttpApi.Client的目标框架是.NET Standard 2.0
对应NuGetVolo.Abp.Core

ConfigureServices是将你的服务添加到依赖注入系统并配置其他模块的主要方法。例:

public class XXXModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){//...}
}

DependsOn

根据项目依赖,在各个项目的Module类中使用[DependsOn(typeof(XXXModule))],这玩意儿还是看Abp生成的项目吧

定义实体

定义在Domain项目中,以实体为名称的目录下

聚合根、实体、值对象的关系:
1.实体具有ID,生命周期,状态用值对象描述状态,实体通过ID进行区分是这个实体还是那个实体;
2.聚合根是实体,聚合根的ID全局唯一,聚合根下面的实体的ID在聚合根内唯一即可;
3.值对象的核心意思是值,与是否是复杂类型无关,例如Price、Count、OrderNo、CustomerAddress都是值对象;
4.值对象无生命周期,本质是一个值,通过两个值对象的值是否相同来区分是都是同一个值对象;
 

//聚合根
public class order{public string ID;//值对象,订单的ID,全局唯一public string OrderNo;//值对象public Address CustomerAddress;//值对象public IList<orderItem>Items;//实体集合
}
//实体
public class OrderItem
{public String  Production;//实体的主键,Order内唯一即可public String  ProductName;//值对象public float price;//值对象public int Count;//值对象
}
//值对象
public class Address
{public string Province;//值对象public string City;//值对象public string County;//值对象
}

聚合根(AggregateRoot)

继承聚合根<Guid>
聚合一般包含多个实体或者值对象,聚合根可以理解为根实体或者叫主实体。

按继承链

  • BasicAggregateRoot_distributedEvents分布式事件、_localEvents本地事件
  • AggregateRootExtraProperties扩展属性、ConcurrencyStamp并发同步标志
  • CreationAuditedAggregateRootCreationTime创建时间、CreatorId创建者Id
  • AuditedAggregateRootLastModificationTime最后修改时间、LastModifierId最后修改者Id
  • FullAuditedAggregateRootIsDeleted是否删除、DeleterId删除者Id、DeletionTime删除时间

实体(Entity)

继承Entity<TKey>

带复合主键实体

继承Entity,overrideGetKeys函数

public override object[] GetKeys()
{return new object[]{XId,XXId};
}

GUID主键

GUID vs 自增

  1. GUID优点:
  • GUID全局唯一,适合分布式系统,方便拆分或合并表
  • 无需数据库往返即可在客户端生成GUID
  • GUID是无法猜测的,某些情况下它们可能更安全(例如,如果最终用户看到一个实体的Id,他们就找不到另一个实体的Id)
  1. GUID缺点:
  • GUID占16个字节,int占4个字节,long占8个字节
  • GUID本质上不是连续的,这会导致聚集索引出现性能问题

ABP提供IGuidGenerator默认生成顺序Guid值,解决了聚集索引的性能问题,建议用IGuidGenerator设置Id,如果你不设置Id,存储库默认会使用IGuidGenerator

使用存储库

存储库提供了一种标准方法来为实体执行常见的数据库操作

通用存储库

XXX指项目名称,TEntity指实体名称,TKey指主键类型,一般主键类型都是Guid

  1. EntityFrameworkCore项目中创建类XXXDbContext,继承AbpDbContext<XXXDbContext>
    在这个类中编写构造函数XXXDbContext(DbContextOptions<XXXDbContext> options)和overrideOnModelCreating函数,并在该函数中建立实体映射关系
    类上方添加[ConnectionStringName("Default")],这个Default是在DbMigrator项目的appsettings.json中配置
  2. EntityFrameworkCore项目中创建类XXXDbContextFactory实现IDesignTimeDbContextFactory<XXXDbContext>接口,这是一个工厂类,用于创建XXXDbContext,编写构造函数和BuildConfiguration,具体看创建项目时生成的代码
  3. EntityFrameworkCore项目中创建类EntityFrameworkCoreabpdemoDbSchemaMigrator,实现IXXXDbSchemaMigratorITransientDependency接口,用于数据库迁移
    IXXXDbSchemaMigratorDomain.Data中创建,里面只有Task MigrateAsync()一个函数
  4. Domain.Data中创建XXXDbMigrationService类,这是数据库迁移服务,具体看abp生成的代码
  5. 接下来就可以定义存储类,在Domain项目的TEntitys目录下创建ITEntityRepository存储库接口,继承IRepository<TEntity, TKey>接口,在EntityFrameworkCore项目的TEntitys目录下创建EfCoreTEntityRepository类,实现EfCoreRepository<XXXDbContext, TEntity, TKey>ITEntityRepository接口
  6. Application.Contracts项目的TEntitys目录下创建ITEntityAppService接口,继承IApplicationService,在Application项目的TEntitys目录下创建TEntityAppService服务类,实现ITEntityAppServiceXXXAppService接口,XXXAppServiceApplication项目下定义,在TEntityAppService中依赖注入ITEntityRepository就可以使用了
    也可以在TEntityService中使用IRepository<聚合根,TKey>来注入

增删改查

ABP自带增删改查,这些函数在IRepository自带,在IService中需要手动编写

  • InsertAsync
  • InsertManyAsync
  • UpdateAsync
  • UpdateManyAsync
  • DeleteAsync
  • DeleteManyASync

所有存储库方法都是异步的,尽可能使用异步模式,因为在.NET中,将异步与同步混合潜在死锁、超时和可伸缩性问题,不容易检测

autoSave

await this._TEntityRepository.InsertAsync(new TEntity(),autoSave:true);

在EF Core中。使用更改跟踪系统,对数据库的操作需要SaveChanges才会保存更改,如果需要立即执行更改,则把autoSave设置为true

CancellationToken

所有存储库默认带有一个CancellationToken参数,在需要的时候用来取消数据库操作,比如关闭浏览器后,无需继续执行冗长的数据库查询操作。大部分情况下,我们无需手动传入cancellationToken,因为ABP框架会自动从HTTP请求中捕捉并使用取消令牌

查询单个实体
  • GetAsync:根据Id表达式返回单个实体。如果未找到请求的实体,则抛出EntityNotFoundException
  • FindAsync:根据Id表达式返回单个实体。如果未找到请求的实体,则返回null

FindAsync适用于有自定义逻辑,否则使用GetAsync

查询实体列表
  • GetListAsync:返回满足给定条件的所有实体或实体列表

  • GetPagedListAsync:分页查询

  • GetAsyncFindAsync方法带有默认值为trueincludeDetails.

  • GetListAsyncGetPagedListAsync方法带有默认值为falseincludeDetails.

这意味着,默认情况下返回包含子对象的单个实体,而列表返回方法则默认不包括子对象信息.你可以明确通过 includeDetails 来更改此行为.

LINQ高级查询
private readonly ITEntityRepository _TEntityRepository;
private readonly IAsyncQueryableExecuter _asyncExecuter;public async Task<List<TEntity>> GetOrderedTEntityAsync(string name)
{//var queryable = await this._TEntityRepository.WithDetailsAsync(x=>x.Category);var queryable = await this._TEntityRepository.GetQueryableAsync();var query = from item in queryablewhere item.Name.Contains(name)orderby item.Nameselect item;return await this._asyncExecuter.ToListAsync(query);
}

  • 为什么不用return await query.ToListAsync()

ToListAsync 是由EF Core定义的扩展方法,位于Microsoft.EntityFrameworkCore包内,如果想保持应用层独立于ORM,ABP的IAsyncQueryableExecuter服务提供了必要的抽象

异步扩展方法

ABP框架为IRepository接口提供了所有标准异步LINQ扩展方法

  • AllAsync
  • AnyASync
  • AverageAsync
  • ContainsAsync
  • CountAsync
  • FirstAsync
  • FirstOrDefaultAsync
  • LastAsync
  • LastOrDefaultAsync
  • LongCountAsync
  • MaxAsync
  • MinAsync
  • SingleAsync
  • SingleOrDefaultAsync
  • SumAsync
  • ToArrayAsync
  • ToListAsync

以上方法只对IRepository有效

复合主键查询

复合主键不能使用IRepository<TEntity,TKey>接口,因为它是获取单个PK(Id)类型,可以使用IRepository<TEntity>接口

其它存储库类型
  • IBasicRepository<TEntity,TPrimaryKey>IBasicRepository<TEntity>提供基本的存储库方法,但它们不支持LINQIQueryable功能
  • IReadOnlyRepository<TEntity,TKey>IReadOnlyRepository<TEntity>IReadOnlyBasicRepository<TEntity,TKey>IReadOnlyBasicRepository<TEntity>提供获取数据的方法,但不包括任何操作方法
自定义存储库
public interface ITEntityRepository:IRepository<TEntity,TKey>
{Task<List<TEntity>> GetListAsync(string name,bool includeDrafts = false);
}
  • 定义在Domain项目中
  • 从通用存储库派生
  • 如果不想包含通用存储库的方法,也可以派生自IRepository无泛型参数接口,这是一个空接口

EF Core 集成

EntityFramework Core项目的Module中配置要使用的DbContext

public override void PreConfigureServices(ServiceConfigurationContext context)
{abpdemoEfCoreEntityExtensionMappings.Configure();
}public override void ConfigureServices(ServiceConfigurationContext context)
{context.Services.AddAbpDbContext<XXXDbContext>(options =>{//添加默认存储库,包括所有实体都可以使用默认存储库//不使用includeAllEntities则只对聚合根使用默认存储库options.AddDefaultRepositories(includeAllEntities: true);});Configure<AbpDbContextOptions>(options =>{//配置数据库options.UseSqlServer();});
}
实体映射

有两种实体映射方法

  • 在实体类上使用数据注释属性
  • EntityFrameworkCore项目的XXXDbContext中overrideOnModelCreating函数配置映射,别忘了DbSet<TEntity>
protected override void OnModelCreating(ModelBuilder builder)
{//内置审计日志和数据过滤base.OnModelCreating(builder);builder.ConfigurePermissionManagement();builder.ConfigureSettingManagement();builder.ConfigureBackgroundJobs();builder.ConfigureAuditLogging();builder.ConfigureIdentity();builder.ConfigureIdentityServer();builder.ConfigureFeatureManagement();builder.ConfigureTenantManagement();/* Configure your own tables/entities inside here *///builder.Entity<YourEntity>(b =>//{//    b.ToTable(XXXConsts.DbTablePrefix + "YourEntities", XXXConsts.DbSchema);//    b.ConfigureByConvention(); //默认配置预定义的Entity或AggregateRoot,即约定,无需再额外配置继承的Entity或AggregateRoot,让代码整洁规范//    //...//});
}
DTO

DTO定义在Application.Contracts项目下的TEntitys目录下
TEntityDto继承EntityDto<TKey>CreateTEntityDtoGetTEntityListDtoUpdateTEntityDto不需要继承EntityDto<TKey>
映射在Application项目下的XXXApplicationAutoMapperProfile类下

    public class XXXApplicationAutoMapperProfile : Profile{public XXXApplicationAutoMapperProfile(){CreateMap<TEntity, TEntityDto>();  CreateMap<CreateTEntityDto, TEntity>();CreateMap<UpdateTEntityDto, TEntity>();}}

----------------

快速开始 | Abp VNext Pro

  1. 创建好实体之后,执行cli

  • 这个cli在你的项目的src路径下执行,它会默认找到这个位置,你也可以通过 -s 参数指定你的代码位置,代码位置为src的绝对路径

bash

lion.abp code  -p 项目id -t 模板id -s 参数指定的项目src地址(可选)
lion.abp new -t pro -c 公司名称 -p 项目名称 -o 输出路径 -v 版本(默认LastRelease)

lion.abp new -t pro -c Winson -p WeChatSenparc -o D:\test20250528

lion.abp new -t pro -c 公司名称 -p 项目名称 -o 输出路径 -v 版本(默认LastRelease)

lion.abp code  -p 项目id -t 模板id -s 参数指定的项目src地址(可选)

  1. 执行完成后,检查代码是否正确。然后执行dotnet ef迁移命令生成数据库即可。

  2. 如何获取项目id和模板id

  3. ?templateId=3a1a0dfc-1d83-300a-4948-d026b0a49b25&projectId=3a1a28a3-e6dc-e4e4-9a07-31331cabcbb0

lion.abp code  -p 项目id -t 模板id -s 参数指定的项目src地址(可选)

居然还在这里挖了个钓鱼的坑

15.Cli结合代码生成器配合使用_哔哩哔哩_bilibili

项目结构总结

XXX为项目名称,TEntity为实体名称,TKey为实体主键类型

Entity

定义在Domain项目中,新建目录,以实体名称的复数TEntitys为目录名称,在该目录下定义实体类TEntity

  • TEntity继承Entity<TKey>AggregateRoot<TKey>

DTO

定义在Application.Contracts项目中,新建目录,以实体名称的复数TEntitys为目录名称,在该目录下定义实体DTO类TEntityDto

  • TEntityDto继承EntityDto<TKey>
  • CreateTEntityDtoGetTEntityListDtoUpdateTEntityDto不需要继承EntityDto<TKey>
  • GetTEntityListDto继承PagedAndSortedResultRequestDto
  • 映射在Application项目下的XXXApplicationAutoMapperProfile类下

IRepository

定义在Domain项目中,新建目录,以实体名称的复数TEntitys为目录名称,在该目录下定义实体存储库接口IEntityRepository

  • IEntityRepository继承IRepository<TEntity,TKey>
  • 不继承IRepository<TEntity,TKey>也可以在Service中通过IRepository<TEntity,TKey>依赖注入来使用

Repository

定义在EntityFrameworkCore项目中,新建目录,以实体名称的复数TEntitys为目录名称,在该目录下定义实体存储库实现类EfCoreTEntityRepository

  • EfCoreTEntityRepository继承EfCoreRepository<XXXDbContext, TEntity, TKey>
  • EfCoreTEntityRepository实现IEntityRepository

IService

定义在Application.Contracts项目中,新建目录,以实体名称的复数TEntitys为目录名称,在该目录下定义实体应用服务接口ITEntityAppService

  • ITEntityAppService继承IApplicationService接口,并定义GetAsync(TKey id)GetListAsync(GetTEntityListDto input)CreateAsync(CreateTEntityDto input)UpdateAsync(TKey id, UpdateTEntityDto input)DeleteAsync(TKey id)
public interface ITEntityAppService : IApplicationService
{Task<TEntityDto> GetAsync(Guid id);Task<PagedResultDto<TEntityDto>> GetListAsync(GetTEntityListDto input);Task<TEntityDto> CreateAsync(CreateTEntityDto input);Task<TEntityDto> UpdateAsync(Guid id, UpdateTEntityDto input);Task DeleteAsync(Guid id);}
  • 如果不继承IApplicationService接口,可以继承ICrudAppService<TEntityDto,TKey,PagedAndSortedResultRequestDto,CreateUpdateTEntityDto>接口

Service

定义在Application项目中,新建目录,以实体名称的复数TEntitys为目录名称,在该目录下定义实体应用服务接口TEntityAppService

  • TEntityAppService继承XXXAppService
  • TEntityAppService实现ITEntityAppService
  • 通过依赖注入使用ITEntityRepository

认证授权

ABP的权限系统是为特定的用户或角色授予或禁止的策略,它与应用功能进行关联,并在用户尝试使用该功能时进行检查,通过当前用户已被授予权限,则可以使用该功能,否则,用户则无法使用该功能

定义权限

Application.Contracts项目下有一个Permissions目录,该目录下有一个XXXPermissionDefinitionProvider类,继承PermissionDefinitionProvider

public override void Define(IPermissionDefinitionContext context)
{//权限组var myGroup = context.AddGroup("权限组名称");//定义权限,例如:myGroup.AddPermission("权限组名称.Create");myGroup.AddPermission("权限组名称.Delete");//AddPermission("一级权限名称").AddChild("二级权限名称")可以添加子权限}

权限名称尽量使用常量,定义在Domain.Shared项目中

  • 一级权限名称可以使用实体名称
  • 二级权限名称可以使用增删改查操作名称

Permissions目录下还有一个XXXPermissions类,这是一个静态类,可以在这里定义一些常量,例如:

public static class XXXPermissions
{public const string GroupName = "XXX";public static class TEntitys1{public const string Default = GroupName + ".TEntitys1";public const string Create = Default + ".Create";public const string Update = Default + ".Update";public const string Delete = Default + ".Delete";}public static class TEntitys2{public const string Default = GroupName + ".TEntitys1";public const string Create = Default + ".Create";public const string Update = Default + ".Update";public const string Delete = Default + ".Delete";}
}

之后就可以使用XXXPermissions引用来代替权限名称字符串

public class XXXPermissionDefinitionProvider : PermissionDefinitionProvider
{public override void Define(IPermissionDefinitionContext context){var myGroup = context.AddGroup(XXXPermissions.GroupName);myGroup.AddPermission(XXXPermissions.TEntitys1.Default, L("实体1:查询"));myGroup.AddPermission(XXXPermissions.TEntitys1.Create, L("实体1:创建"));myGroup.AddPermission(XXXPermissions.TEntitys1.Update, L("实体1:修改"));myGroup.AddPermission(XXXPermissions.TEntitys1.Delete, L("实体1:删除"));}private static LocalizableString L(string name){return LocalizableString.Create<XXXResource>(name);}
}

权限本地化

官方文档:Localization | ABP.IO Documentation
本地化资源XXXResource,在Domain.Shared项目的Localization目录下,[LocalizationResourceName("本地化资源名称")]

public override void Define(IPermissionDefinitionContext context)
{var myGroup = context.AddGroup("权限组名称");myGroup.AddPermission("权限组名称.Create", L("本地化权限名称"));myGroup.AddPermission("权限组名称.Delete", L("本地化权限名称"));
}private static LocalizableString L(string name)
{//name就是本地化keyreturn LocalizableString.Create<XXXResource>(name);
}

在类中可以通过依赖注入来使用

public class MyService
{private readonly IStringLocalizer<XXXResource> _localizer;public MyService(IStringLocalizer<XXXResource> localizer){_localizer = localizer;}public void Foo(){var str = _localizer["本地化Key"];}
}

ABP提供了JavaScript服务,可以在客户端使用相同的本地化文本

//获取本地化资源
var testResource = abp.localization.getResource('本地化资源名称');
//获取本地化字符串
var str = testResource('本地化Key');

也可以这样获取本地化字符串

var str = abp.localization.localize('本地化Key', '本地化资源名称');

权限的本地化Key应该就是权限名称

权限检查

可以使用[Authorize]属性以声明的方式检查权限,也可以使用IAuthorizationService以编程方式检查权限

[Authorize("权限名称")],权限名称作为字符串参数,也就是策略名称,在需要指定策略名称的任何位置使用权限名称

public class TEntityController : Controller
{public async Task<List<TEntityDto>> GetListAsync(){}[Authorize("XXX.TEntity.Create")]public async Task CreateAsync(CreateTEntityDto input){}[Authorize("XXX.TEntity.Delete")]public async Task DeleteAsync(Guid id){}
}

如果使用XXXPermissions类声明权限,则可以通过XXXPermissions引用来代替权限名称字符串

public class TEntityController : Controller
{public async Task<List<TEntityDto>> GetListAsync(){}[Authorize(XXXPermissions.TEntitys1.Create)]public async Task CreateAsync(CreateTEntityDto input){}[Authorize(XXXPermissions.TEntitys1.Delete)]public async Task DeleteAsync(Guid id){}
}

[Authorize("权限名称")]声明式授权易于使用,建议尽可能使用。但是,当你想要有条件地检查权限或执行未授权案例的逻辑时,它是有限的,对于这种情况,可以注入并使用IAuthorizationService,例如

public class TEntityController : Controller
{private readonly IAuthorizationService _authorizationService;public TEntityController(IAuthorizationService authorizationService){this._authorizationService = authorizationService;}public async Task CreateAsync(CreateTEntityDto input){if(await this._authorizationService.IsGrantedAsync("权限名称")){//权限验证通过}}
}

IsGrantedAsync()方法检查给定的权限,如果当前用户(或用户的角色)已被授予权限,则返回true。如果你有自定义逻辑的权限要求,这将非常有用,但是,如果你只想检查权限,并对未经授权的情况抛出异常,CheckAsync()方法更实用
如果用户没有该操作权限,CheckAsync()方法会引发AbpAuthorizationException异常,该异常由ABP框架处理,并向客户端返回HTTP响应,IsGrantedAsync()CheckAsync()方法是ABP框架定义的有用的扩展方法

    public async Task CreateAsync(CreateTEntityDto input){await this._authorizationService.CheckAsync("权限名称"))//权限验证通过}

建议TEntityController继承AbpController类,而不是标准Controller类,因为它内部做了扩展,定义了一些有用的属性,比如AuthorizationService属性(IAuthorization类型),可以直接使用,无需手动注入

客户端权限

服务器上的权限检查是一种常见的方法,但是,你可能还需要检查客户端的权限
ABP公开了一个标准的HTTP API,其URL为/api/abp/application-configuration,返回包含本地化文本、设置、权限等的JSON数据,客户端可以使用该API来检查权限或在客户端执行本地化

不同的客户端类型可能会提供不同的服务来检查权限,例如,在MVC/Razor Pages中,可以使用abp.authJavaScript API检查权限

abp.auth.isGranted("权限名称");

这是一个全局函数,如果当前用户具有给定的权限,则返回true,否则,返回false

Blazor应用程序中,可以重用系统的[Authorize]属性和IAuthorizationService

基于策略的授权

定义权限需求
public class CreateTEntityRequirement : IAuthorizationRequirement
{}

CreateTEntityRequirement是一个空类,仅实现IAuthorizationRequirement接口,然后,为该需求定义一个授权处理程序CreateTEntityRequirementHandler

public class CreateTEntityRequirementHandler : AuthorizationHandler<CreateTEntityRequirement>
{protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,CreateTEntityRequirementHandler requirement){if(context.User.HasClaim(c => c.Type == "权限名称")){context.Succeed(requirement);}return Task.CompletedTask;}
}

定义权限需求和处理程序后,需要在Module类的ConfigureServices方法中注册它们

public override void ConfigureServices(ServiceConfigurationContext context)
{Configure<AuthorizationOptions>(options => {options.AddPolicy("权限名称");policy => policy.Requirements.Add(new CreateTEntityRequirementHandler());});context.Services.AddSingleton<IAuthorizationHandler,CreateTEntityRequirementHandler>();
}

下载,假设我对ControllerAction使用[Authorize("权限名称")]属性,或者使用IAuthorizationService检查策略,我的自定义授权处理程序就可以进行逻辑处理了

基于资源的授权

基于资源的授权是一种允许你基于对象(如实体)控制策略的功能,例如,你可以控制删除特定实体的访问权限,而不是对所有产品拥有共同的删除权限。这个内容可以看ASP.NET Core[Authorize]属性

控制器之外的授权

ASP.NET Core可以在Razor页面、Razor组件和Web层中的一些地方使用[Authorize]IAuthorizationService
ABP框架则更进一步,允许对服务类和方法使用[Authorize]属性,而不依赖Web层,即使在非Web应用程序中也是如此。因此,这种用法完全有效

public class TEntityAppService : ApplicationService, ITEntityService
{[Authorize("权限名称")]public Task CreateAsync(CreateTEntityDto input){}
}

Auto API

这个就是根据Service的方法命名自动创建Controller中的方法,并分配路由,手写的路由是不受影响的
这个配置在HttpApi.Host项目中,生成的路由默认以/api/app/TEntity开头

private void ConfigureConventionalControllers()
{Configure<AbpAspNetCoreMvcOptions>(options =>{options.ConventionalControllers.Create(typeof(UserCenterApplicationModule).Assembly);});
}

生成的路由

  • Get:如果方法名称以GetListGetAllGet开头
  • Put:如果方法名称以PutUpdate开头
  • Delete:如果方法名称以DeleteRemove开头
  • Post:如果方法名称以CreateAddInsertPost开头
  • Patch:如果方法名称以Patch开头
  • 其他情况,Post为默认方式

这些路由都是根据Service自动生成的,包括参数也是,如果你手写一个实体的Controller,在swagger中是可以看到手写的和生成的同时存在,参数也一致
比如

[ApiController]
[Route("/api/v1/[controller]")]
public class MenuController : AbpControllerBase
{private readonly IMenuAppService _menuAppService;public MenuController(IMenuAppService menuAppService){this._menuAppService = menuAppService;}[HttpGet("GetOne/{id}")]public async Task<ActionResult<MenuDto>> GetOne([FromRoute] string id){var menuDto = await this._menuAppService.GetAsync(Guid.Parse(id));return Ok(menuDto);}[HttpPost("GetList")]public async Task<ActionResult<PagedResultDto<MenuDto>>> GetList([FromBody] GetMenuListDto input){var list = await this._menuAppService.GetListAsync(input);return Ok(list);}[HttpPost("Create")]public async Task<ActionResult<MenuDto>> Create([FromBody] CreateMenuDto input){var menuDto = await this._menuAppService.CreateAsync(input);return Ok(menuDto);}[HttpPut("Update/{id}")]public async Task<ActionResult<MenuDto>> Update([FromRoute] string id, [FromBody] UpdateMenuDto input){var menuDto = await this._menuAppService.UpdateAsync(Guid.Parse(id), input);return Ok(menuDto);}[HttpDelete("Delete/{id}")]public async Task<IActionResult> Delete([FromRoute] string id){await this._menuAppService.DeleteAsync(Guid.Parse(id));return Ok();}}

这是结果,符合预期

如果需要修改路由的规则的话,举例

private void ConfigureConventionalControllers()
{Configure<AbpAspNetCoreMvcOptions>(options =>{options.ConventionalControllers.Create(typeof(abpdemoApplicationModule).Assembly, options =>{options.RootPath = "demo/v1";});});
}

这样生成的路由就是/app/demo/v1/TEntity开头,具体可以去参考官方文档

至于为什么要手写控制器,因为有时需要传递复杂对象,比如分页、筛选、排序字段,虽然RESTful的规范是请求数据用GetGet请求也是支持Body传递数据的,但不是所有的前端请求框架都支持在Get里加入Body参数,比如axios,需要使用PostPostman是支持在Get中添加body参数的,而且工作中我还真遇到过Get请求的url过长的问题,那是一个没有规范的项目

据说[FromQuery][FromUri]是可以传递复杂参数,但是我没成功过,前端参数的序列化也有些问题,也许可以自己写一个参数解析的Attribute来解决,我懒了

Abp vNext 基本使用 结束

---------------------------------

namespace Winson.WeChatAdmin.FileManagement;[DependsOn(typeof(FileManagementApplicationModule),typeof(FileManagementEntityFrameworkCoreModule),typeof(FileManagementHttpApiModule),typeof(AbpAutofacModule),typeof(AbpCachingStackExchangeRedisModule),typeof(AbpAspNetCoreSerilogModule),typeof(AbpSwashbuckleModule),typeof(AbpEntityFrameworkCoreMySQLModule)
)]
public class FileManagementHttpApiHostModule : AbpModule
{private const string DefaultCorsPolicyName = "Default";public override void ConfigureServices(ServiceConfigurationContext context){ConfigureVirtualFileSystem();ConfigureSwaggerServices(context);ConfigAntiForgery();ConfigureLocalization();ConfigureCache(context);ConfigureCors(context);ConfigDB();}public override void OnApplicationInitialization(ApplicationInitializationContext context){var app = context.GetApplicationBuilder();var env = context.GetEnvironment();app.UseHttpsRedirection();app.UseCorrelationId();app.UseStaticFiles();app.UseRouting();app.UseCors(DefaultCorsPolicyName);app.UseAuthentication();app.UseAbpRequestLocalization();app.UseAuthorization();app.UseSwagger();app.UseAbpSwaggerUI(options =>{options.SwaggerEndpoint("/swagger/v1/swagger.json", "FileManagement API");options.DocExpansion(DocExpansion.None);options.DefaultModelExpandDepth(-2);});app.UseAuditing();app.UseAbpSerilogEnrichers();app.UseConfiguredEndpoints();}/// <summary>///     配置虚拟文件系统/// </summary>private void ConfigureVirtualFileSystem(){Configure<AbpVirtualFileSystemOptions>(options => { options.FileSets.AddEmbedded<FileManagementHttpApiHostModule>(); });}/// <summary>///     配置SwaggerUI/// </summary>/// <param name="context"></param>private static void ConfigureSwaggerServices(ServiceConfigurationContext context){context.Services.AddSwaggerGen(options =>{options.SwaggerDoc("v1", new OpenApiInfo { Title = "FileManagement API", Version = "v1" });options.DocInclusionPredicate((docName, description) => true);#region 多语言options.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme{Type = SecuritySchemeType.ApiKey,In = ParameterLocation.Header,Name = "Accept-Language",Description = "多语言"});options.AddSecurityRequirement(new OpenApiSecurityRequirement{{new OpenApiSecurityScheme{Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "ApiKey" }},new string[] { }}});#endregion});}private void ConfigAntiForgery(){Configure<AbpAntiForgeryOptions>(options => { options.AutoValidate = false; });}private void ConfigDB(){Configure<AbpDbContextOptions>(options =>{/* The main point to change your DBMS.* See also OperationsMigrationsDbContextFactory for EF Core tooling. */options.UseMySQL();});}/// <summary>///     配置本地化/// </summary>private void ConfigureLocalization(){Configure<AbpLocalizationOptions>(options =>{options.Languages.Add(new LanguageInfo("ar", "ar", "العربية"));options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština"));options.Languages.Add(new LanguageInfo("en", "en", "English"));options.Languages.Add(new LanguageInfo("en-GB", "en-GB", "English (UK)"));options.Languages.Add(new LanguageInfo("fi", "fi", "Finnish"));options.Languages.Add(new LanguageInfo("fr", "fr", "Français"));options.Languages.Add(new LanguageInfo("hu", "hu", "Magyar"));options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português"));options.Languages.Add(new LanguageInfo("ro-RO", "ro-RO", "Română"));options.Languages.Add(new LanguageInfo("ru", "ru", "Русский"));options.Languages.Add(new LanguageInfo("sk", "sk", "Slovak"));options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe"));options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁體中文"));options.Languages.Add(new LanguageInfo("de-DE", "de-DE", "Deutsch"));options.Languages.Add(new LanguageInfo("es", "es", "Español"));});}/// <summary>///     Redis缓存/// </summary>/// <param name="context"></param>private void ConfigureCache(ServiceConfigurationContext context){Configure<AbpDistributedCacheOptions>(options =>{options.KeyPrefix = "FileManagement:";options.GlobalCacheEntryOptions = new DistributedCacheEntryOptions{// 全局缓存有效时间AbsoluteExpiration = DateTimeOffset.UtcNow.AddHours(2)};});var configuration = context.Services.GetConfiguration();var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]);context.Services.AddDataProtection().PersistKeysToStackExchangeRedis(redis, "YH.Wms.Operations-Keys");}/// <summary>///     配置跨域/// </summary>private void ConfigureCors(ServiceConfigurationContext context){var configuration = context.Services.GetConfiguration();context.Services.AddCors(options =>{options.AddPolicy(DefaultCorsPolicyName, builder =>{builder.WithOrigins(configuration["App:CorsOrigins"].Split(",", StringSplitOptions.RemoveEmptyEntries).Select(o => o.RemovePostFix("/")).ToArray()).WithAbpExposedHeaders().SetIsOriginAllowedToAllowWildcardSubdomains().AllowAnyHeader().AllowAnyMethod().AllowCredentials();});});}
}

 后端API

仓储

using Winson.WeChatAdmin.BasicManagement.UserRefreshTokens;
using Winson.WeChatAdmin.CodeManagement.DataTypes.Aggregates;
using Winson.WeChatAdmin.CodeManagement.EntityFrameworkCore;
using Winson.WeChatAdmin.CodeManagement.EntityModels.Aggregates;
using Winson.WeChatAdmin.CodeManagement.EnumTypes.Aggregates;
using Winson.WeChatAdmin.CodeManagement.Projects.Aggregates;
using Winson.WeChatAdmin.CodeManagement.Templates.Aggregates;
using Winson.WeChatAdmin.DataDictionaryManagement.DataDictionaries.Aggregates;
using Winson.WeChatAdmin.DynamicMenuManagement.EntityFrameworkCore;
using Winson.WeChatAdmin.DynamicMenuManagement.Menus;
using Winson.WeChatAdmin.FileManagement.EntityFrameworkCore;
using Winson.WeChatAdmin.FileManagement.Files;
using Winson.WeChatAdmin.LanguageManagement.EntityFrameworkCore;
using Winson.WeChatAdmin.LanguageManagement.Languages.Aggregates;
using Winson.WeChatAdmin.LanguageManagement.LanguageTexts.Aggregates;
using Winson.WeChatAdmin.NotificationManagement.Notifications.Aggregates;
using Winson.WeChatAdmin.TemplateManagement.EntityFrameworkCore;
using Winson.WeChatAdmin.TemplateManagement.TextTemplates;namespace Winson.WeChatAdmin.EntityFrameworkCore
{[ConnectionStringName("Default")]public class WeChatAdminDbContext : AbpDbContext<WeChatAdminDbContext>, IWeChatAdminDbContext,IBasicManagementDbContext,INotificationManagementDbContext,IDataDictionaryManagementDbContext,ILanguageManagementDbContext,ICodeManagementDbContext,ITemplateManagementDbContext,IFileManagementDbContext,IDynamicMenuManagementDbContext{public DbSet<IdentityUser> Users { get; set; }public DbSet<IdentityRole> Roles { get; set; }public DbSet<IdentityClaimType> ClaimTypes { get; set; }public DbSet<OrganizationUnit> OrganizationUnits { get; set; }public DbSet<IdentitySecurityLog> SecurityLogs { get; set; }public DbSet<IdentityLinkUser> LinkUsers { get; set; }public DbSet<IdentityUserDelegation> UserDelegations { get; set; }public DbSet<IdentitySession> Sessions { get; set; }public DbSet<FeatureGroupDefinitionRecord> FeatureGroups { get; set; }public DbSet<FeatureDefinitionRecord> Features { get; set; }public DbSet<FeatureValue> FeatureValues { get; set; }public DbSet<PermissionGroupDefinitionRecord> PermissionGroups { get; set; }public DbSet<PermissionDefinitionRecord> Permissions { get; set; }public DbSet<PermissionGrant> PermissionGrants { get; set; }public DbSet<Setting> Settings { get; set; }public DbSet<SettingDefinitionRecord> SettingDefinitionRecords { get; set; }public DbSet<Tenant> Tenants { get; set; }public DbSet<TenantConnectionString> TenantConnectionStrings { get; set; }public DbSet<BackgroundJobRecord> BackgroundJobs { get; set; }public DbSet<AuditLog> AuditLogs { get; set; }public DbSet<Notification> Notifications { get; set; }public DbSet<NotificationSubscription> NotificationSubscriptions { get; set; }public DbSet<DataDictionary> DataDictionaries { get; set; }public DbSet<Language> Languages { get; set; }public DbSet<LanguageText> LanguageTexts { get; set; }public DbSet<FileObject> FileObjects { get; set; }public DbSet<Template> Templates { get; set; }public DbSet<Project> Projects { get; set; }public DbSet<EntityModel> EntityModels { get; set; }public DbSet<DataType> DataTypes { get; set; }public DbSet<EnumType> EnumTypes { get; set; }public DbSet<TextTemplate> TextTemplates { get; set; }public DbSet<Menu> Menus { get; set; }public DbSet<UserRefreshToken> UserRefreshTokens { get; set; }public WeChatAdminDbContext(DbContextOptions<WeChatAdminDbContext> options): base(options){}protected override void OnModelCreating(ModelBuilder builder){// 如何设置表前缀// Abp框架表前缀 Abp得不建议修改表前缀// AbpCommonDbProperties.DbTablePrefix = "xxx";// 数据字典表前缀//DataDictionaryManagementDbProperties=“xxx”// 通知模块//NotificationManagementDbProperties = "xxx"base.OnModelCreating(builder);builder.ConfigureWeChatAdmin();// 基础模块builder.ConfigureBasicManagement();// 数据字典builder.ConfigureDataDictionaryManagement();// 消息通知builder.ConfigureNotificationManagement();// 多语言builder.ConfigureLanguageManagement();// 文件模块builder.ConfigureFileManagement();builder.ConfigureCodeManagement();builder.ConfigureTemplateManagement();builder.ConfigureDynamicMenuManagement();}}
}

同步git 仓库

 

前端 

{"name": "vben-admin-monorepo","version": "5.5.6","private": true,"keywords": ["monorepo","turbo","vben","vben admin","vben pro","vue","vue admin","vue vben admin","vue vben admin pro","vue3"],"homepage": "https://github.com/vbenjs/vue-vben-admin","bugs": "https://github.com/vbenjs/vue-vben-admin/issues","repository": "vbenjs/vue-vben-admin.git","license": "MIT","author": {"name": "vben","email": "ann.vben@gmail.com","url": "https://github.com/anncwb"},"type": "module","scripts": {"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build","build:analyze": "turbo build:analyze","build:antd": "pnpm run build --filter=@vben/web-antd","build:docker": "./scripts/deploy/build-local-docker-image.sh","build:docs": "pnpm run build --filter=@vben/docs","build:ele": "pnpm run build --filter=@vben/web-ele","build:naive": "pnpm run build --filter=@vben/web-naive","build:play": "pnpm run build --filter=@vben/playground","changeset": "pnpm exec changeset","check": "pnpm run check:circular && pnpm run check:dep && pnpm run check:type && pnpm check:cspell","check:circular": "vsh check-circular","check:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress","check:dep": "vsh check-dep","check:type": "turbo run typecheck","clean": "node ./scripts/clean.mjs","commit": "czg","dev": "turbo-run dev","dev:antd": "pnpm -F @vben/web-antd run dev","dev:docs": "pnpm -F @vben/docs run dev","dev:ele": "pnpm -F @vben/web-ele run dev","dev:naive": "pnpm -F @vben/web-naive run dev","dev:play": "pnpm -F @vben/playground run dev","format": "vsh lint --format","lint": "vsh lint","postinstall": "pnpm -r run stub --if-present","preinstall": "npx only-allow pnpm","preview": "turbo-run preview","publint": "vsh publint","reinstall": "pnpm clean --del-lock && pnpm install","test:unit": "vitest run --dom","test:e2e": "turbo run test:e2e","update:deps": "npx taze -r -w","version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile","catalog": "pnpx codemod pnpm/catalog"},"devDependencies": {"@changesets/changelog-github": "catalog:","@changesets/cli": "catalog:","@playwright/test": "catalog:","@types/node": "catalog:","@vben/commitlint-config": "workspace:*","@vben/eslint-config": "workspace:*","@vben/prettier-config": "workspace:*","@vben/stylelint-config": "workspace:*","@vben/tailwind-config": "workspace:*","@vben/tsconfig": "workspace:*","@vben/turbo-run": "workspace:*","@vben/vite-config": "workspace:*","@vben/vsh": "workspace:*","@vitejs/plugin-vue": "catalog:","@vitejs/plugin-vue-jsx": "catalog:","@vue/test-utils": "catalog:","autoprefixer": "catalog:","cross-env": "catalog:","cspell": "catalog:","happy-dom": "catalog:","is-ci": "catalog:","lefthook": "catalog:","playwright": "catalog:","rimraf": "catalog:","tailwindcss": "catalog:","turbo": "catalog:","typescript": "catalog:","unbuild": "catalog:","vite": "catalog:","vitest": "catalog:","vue": "catalog:","vue-tsc": "catalog:"},"engines": {"node": ">=20.10.0","pnpm": ">=9.12.0"},"packageManager": "pnpm@10.10.0","pnpm": {"peerDependencyRules": {"allowedVersions": {"eslint": "*"}},"overrides": {"@ast-grep/napi": "catalog:","@ctrl/tinycolor": "catalog:","clsx": "catalog:","esbuild": "0.25.3","pinia": "catalog:","vue": "catalog:"},"neverBuiltDependencies": ["canvas","node-gyp"]}
}

用这个

"dev:antd": "pnpm -F @vben/web-antd run dev",

ele这个作者没做完坑    "dev:ele": "pnpm -F @vben/web-ele run dev",

 刷新代理

 Abp vNext 基本使用 - .NET好耶 - 博客园

ABP.Next系列01-有啥?像啥?infrastructure -Fstyle-CSDN博客

ABP.Next系列02 搭个API框架要多久 项目下载 运行 -Fstyle_【abp vnext】下载并运行web api项目详细教程文档-CSDN博客

相关文章:

  • 【QueryServer】dbeaver使用phoenix连接Hbase(轻客户端方式)
  • unityPc端设置了全屏(Exclusive Fullscreen)但是仍然有白边解决办法
  • 网站每天几点更新,更新频率是否影响网站收录
  • Nidec Digitax HD M753 伺服控制器 尼得科
  • SpringBoot自定义实体类字段的校验注解
  • 响应式布局进阶:企业商城系统复杂交互页面的多端适配方案
  • [原创](现代Delphi 12指南):[macOS 64bit App开发]: 按钮大小设置的小技巧
  • 零衍课堂 | 环境初始化部署流程
  • 国内有哪些智能外呼机器人
  • 三强联合!Attention+LSTM,结合特征融合,起手二区!
  • git和gitee的常用语句命令
  • 【数据集】无缝1 km地表温度数据集(US)
  • 【Golang入门】第四章:控制结构——从条件分支到异常处理
  • 如何去除文章的AI痕迹2025新方法
  • linux——TCP问题
  • 正则表达式的修饰符
  • Error Swap_arc198c分析与解答
  • 如何做支付接口呢?
  • 论文阅读笔记——In-Context Edit
  • ETL怎么实现多流自定义合并?
  • 旅行社网站开发 论文/2021年热门关键词
  • 兰州企业网络优化方案/自己怎么给网站做优化排名
  • 网站自适应手机代码/在什么网站可以免费
  • 市住房和城乡建设局网站/永久免费的建站系统有哪些
  • java只能做网站开发吗/天津做网站的网络公司
  • 建设地方政府门户网站的措施/在百度怎么发布作品