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
项目引用
如果需要通过程序包管理器生成数据库或代码,需要引用EntityFrameworkCore
和Application.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
项目
前端
MVC
、Blazor
、Vue
、Angular
等等,可能会包含ViewModel
命名空间
除了Domain.Shared
是使用自己的命名空间,其它项目的命名空间都是一样的
模块化
解决方案下的每一个项目都有一个Module
类,继承AbpModule
,注意设置项目的默认命名空间和目标框架,项目框架可能需要设置成.NET Standard 2.0
,因为Domain.Shared
、Application.Contracts
、HttpApi.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
本地事件AggregateRoot
:ExtraProperties
扩展属性、ConcurrencyStamp
并发同步标志CreationAuditedAggregateRoot
:CreationTime
创建时间、CreatorId
创建者IdAuditedAggregateRoot
:LastModificationTime
最后修改时间、LastModifierId
最后修改者IdFullAuditedAggregateRoot
:IsDeleted
是否删除、DeleterId
删除者Id、DeletionTime
删除时间
实体(Entity)
继承Entity<TKey>
带复合主键实体
继承Entity
,overrideGetKeys
函数
public override object[] GetKeys()
{return new object[]{XId,XXId};
}
GUID主键
GUID vs 自增
- GUID优点:
- GUID全局唯一,适合分布式系统,方便拆分或合并表
- 无需数据库往返即可在客户端生成GUID
- GUID是无法猜测的,某些情况下它们可能更安全(例如,如果最终用户看到一个实体的Id,他们就找不到另一个实体的Id)
- GUID缺点:
- GUID占16个字节,int占4个字节,long占8个字节
- GUID本质上不是连续的,这会导致聚集索引出现性能问题
ABP提供IGuidGenerator默认生成顺序Guid值,解决了聚集索引的性能问题,建议用IGuidGenerator设置Id,如果你不设置Id,存储库默认会使用IGuidGenerator
使用存储库
存储库提供了一种标准方法来为实体执行常见的数据库操作
通用存储库
XXX
指项目名称,TEntity
指实体名称,TKey
指主键类型,一般主键类型都是Guid
- 在
EntityFrameworkCore
项目中创建类XXXDbContext
,继承AbpDbContext<XXXDbContext>
在这个类中编写构造函数XXXDbContext(DbContextOptions<XXXDbContext> options)
和overrideOnModelCreating
函数,并在该函数中建立实体映射关系
类上方添加[ConnectionStringName("Default")]
,这个Default
是在DbMigrator
项目的appsettings.json
中配置 - 在
EntityFrameworkCore
项目中创建类XXXDbContextFactory
实现IDesignTimeDbContextFactory<XXXDbContext>
接口,这是一个工厂类,用于创建XXXDbContext
,编写构造函数和BuildConfiguration
,具体看创建项目时生成的代码 - 在
EntityFrameworkCore
项目中创建类EntityFrameworkCoreabpdemoDbSchemaMigrator
,实现IXXXDbSchemaMigrator
和ITransientDependency
接口,用于数据库迁移
IXXXDbSchemaMigrator
在Domain.Data
中创建,里面只有Task MigrateAsync()
一个函数 - 在
Domain.Data
中创建XXXDbMigrationService
类,这是数据库迁移服务,具体看abp生成的代码 - 接下来就可以定义存储类,在
Domain
项目的TEntitys
目录下创建ITEntityRepository
存储库接口,继承IRepository<TEntity, TKey>
接口,在EntityFrameworkCore
项目的TEntitys
目录下创建EfCoreTEntityRepository
类,实现EfCoreRepository<XXXDbContext, TEntity, TKey>
和ITEntityRepository
接口 - 在
Application.Contracts
项目的TEntitys
目录下创建ITEntityAppService
接口,继承IApplicationService
,在Application
项目的TEntitys
目录下创建TEntityAppService
服务类,实现ITEntityAppService
和XXXAppService
接口,XXXAppService
在Application
项目下定义,在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
:分页查询 -
GetAsync
和FindAsync
方法带有默认值为true
的includeDetails
. -
GetListAsync
和GetPagedListAsync
方法带有默认值为false
的includeDetails
.
这意味着,默认情况下返回包含子对象的单个实体,而列表返回方法则默认不包括子对象信息.你可以明确通过 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>
提供基本的存储库方法,但它们不支持LINQ
和IQueryable
功能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>
,CreateTEntityDto
、GetTEntityListDto
、UpdateTEntityDto
不需要继承EntityDto<TKey>
映射在Application
项目下的XXXApplicationAutoMapperProfile
类下
public class XXXApplicationAutoMapperProfile : Profile{public XXXApplicationAutoMapperProfile(){CreateMap<TEntity, TEntityDto>(); CreateMap<CreateTEntityDto, TEntity>();CreateMap<UpdateTEntityDto, TEntity>();}}
----------------
快速开始 | Abp VNext Pro
-
创建好实体之后,执行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地址(可选)
-
执行完成后,检查代码是否正确。然后执行dotnet ef迁移命令生成数据库即可。
-
如何获取项目id和模板id
-
?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>
CreateTEntityDto
、GetTEntityListDto
、UpdateTEntityDto
不需要继承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.auth
JavaScript 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>();
}
下载,假设我对Controller
或Action
使用[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
:如果方法名称以GetList
、GetAll
或Get
开头Put
:如果方法名称以Put
或Update
开头Delete
:如果方法名称以Delete
或Remove
开头Post
:如果方法名称以Create
、Add
、Insert
或Post
开头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
的规范是请求数据用Get
,Get
请求也是支持Body
传递数据的,但不是所有的前端请求框架都支持在Get
里加入Body
参数,比如axios
,需要使用Post
,Postman
是支持在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博客