abp中多种登陆用户的设计
项目地址:https://gitee.com/bxjg1987/abp
场景
在《学校管理系统》中,学生、家长、教师、教务都可能登陆,做一些属于他们自己的操作。这些用户需要的属性各不相同,比如学生有学号,而教师没有。
应用程序用户
在编码时,经常需要获取当前登陆用户的信息,这个当前登陆用户就是应用程序用户。asp.net提供了一整套方案来实现应用程序用户,包括身份验证、授权、asp.net identity等
应用程序用户与业务场景中的用户不同,应用程序用户只需要区别是谁,最简单情况只需要知道用户id,它不关心这个用户具体是教师还是学生或其它类型的用户。基于这个用户id还可以实现角色、授权等操作。 浅显点理解应用程序用户主要是识别用户id,方便实现角色和授权
而“学生”、“教师”则是《学校管理系统》这个场景中的具体业务概念。
abp中有个abp zero模块,它已实现应用程序用户管理、登陆、角色、授权等功能。
与abp用户一对一关联
当业务用户需要登陆时,一对一关联到应用程序用户。*
这样系统本身提供的登陆、注销、角色、授权等功能几乎保持不变,按需要可以实现多种业务用户类型。
模块化
我们的系统是按业务分模块开发的,参考:https://www.bilibili.com/video/BV1b5411L7Hf/
有些业务模块可能存在业务用户,比如【商城模块】中的“顾客”,在设计时需要考虑模块化和可扩展性。
由于是独立模块,所以我们不知道将来模块被什么系统引用,因此也不知道具体的AbpUser类型,因为那是模块使用方自己定义的,因此在开发业务模块时不应该出现具体的AbpUser的类型,需要关联时只能关联Id,必要时可以引用抽象的AbpUser及其管理类。
Core层
按ddd和abp的方式这层需要建立:聚合根、实体、值对象、领域服务、领域事件、仓储接口。
下面以《学校管理系统》场景说明
实体
按ddd方式定义学生实体(它应该是聚合根)。
定义学号、所属学院、所属专业等属性,重点是它有个UserId属性,关联到应用程序用户,注意不要使用导航属性关联到AbpUser,一来ddd建议一个聚合中的实体不要使用导航属性关联到另一个聚合中的实体,二来我们使用的是模块化开发方式,我们在开发自己的模块时并不会知道模块将来被谁使用,因此就不会知道具体的类型,因为在abp中,用户是由开发人员自定义的。
不要考虑模块使用方使用继承来扩展实体,建议使用abp提供的IExtensionObject接口或动态属性系统。
领域实体中可以触发事件,以便模块调用方扩展
领域服务、领域事件、仓储接口。
这个根据需要决定是否定义。
领域服务可以提供虚方法以便模块使用方法提供子类重写,也可以触发相应事件让模块使用方去订阅。
由于将来可能增加更多用户类型,领域服务可以考虑抽象封装在BXJG.Utils(依赖abp的通用功能模块)中
EFCore层
ef映射和种子数据的处理
Application层
新增业务用户时考虑同时建立并关联用户、删除时则一并删除。
可以提供虚方法,以便模块调用方继承并重写
在模块中,没有将新增、删除应用程序用户的逻辑预留给模块使用方,而是在模块内部直接做了,模块调用方可以订阅UserCreating、UserDeleting事件来插入自己的逻辑
由于将来可能增加更多用户类型,应用服务可以考虑抽象封装在BXJG.Utils.Application中
session与登陆
如《学生管理系统》有个学生后台,里面全都是学生可以操作的功能,做这些功能时通常需要获取当前登陆学生的id。abp的seession功能只能获取当前登陆用户id,这是abpUser的id,这个id并不是我们需要的,它与学生实体是一对一关联的。
我们可以通过abp的seesion获取当前abpUser的id,然后去对应用户表查询得到业务用户的id,但这样比较浪费性能。
我们的思路是在用户登陆时将关联的业务场景用户的id存储到claim中,然后提供一个类似abpsession的session来在需要是提供当前业务用户的id
我们可以为每种用户建立登陆页面,在登陆时存储业务用户id到claim中,也可以在统一的登陆页面加各判断,然后获取对应业务用户类型表中的业务用户id
session的设计也可以抽象出来,因为有多种用户类型
如何使用
abp官方文档教程中有[扩展session](https://aspnetboilerplate.com/Pages/Documents/Articles%5CHow-To%5Cadd-custom-session-field-aspnet-core)的说明,我们也是按这个思路做的。由于系统可能存在多种业务用户,因此需要简单封装下,下面看看商城模块中的顾客是如何实现session和登陆的
实体实现IBusinessUserEntity
1 public class CustomerEntity : FullAuditedAggregateRoot<long>, IBusinessUserEntity , IMustHaveTenant, IExtendableObject
2 //略....
定义session接口和实现
1 public interface ICustomerSession : IBusinessUserSession<long>{}
2
3 public class CustomerClaimSession : BusinessUserClaimSession<long>, ICustomerSession
4 {
5 public CustomerClaimSession(IPrincipalAccessor principalAccessor,
6 IMultiTenancyConfig multiTenancy,
7 ITenantResolver tenantResolver,
8 IAmbientScopeProvider<SessionOverride> sessionOverrideScopeProvider) : base(principalAccessor, multiTenancy, tenantResolver, sessionOverrideScopeProvider, CoreConsts.CustomerIdClaim)
9 {
10 }
11 }
在模块中实现ioc注入
1 public override void Initialize()
2 {
3 IocManager.IocContainer.Register(Component.For<ICustomerSession>()
4 .ImplementedBy<CustomerClaimSession>()
5 .LifestyleCustom<MsScopedLifestyleManager>()
6 .Named("sdf234sdf"));//CustomerClaimSession的父类已单例注册了,需要重命名下
7
8 }
定义登陆器接口和实现
public interface ICustomerLoginManager<TUser> : IBusinessUserLoginManager<TUser> { } /// <summary>
/// 提供与顾客登陆相关功能
/// </summary>
public class CustomerLoginManager<TTenant,
TRole,
TUser,
TUserManager> : BusinessUserLoginManager<CustomerEntity,
long,
TTenant,
TRole,
TUser,
TUserManager>, ICustomerLoginManager<TUser>
where TTenant : AbpTenant<TUser>
where TRole : AbpRole<TUser>, new()
where TUser : AbpUser<TUser>
where TUserManager : AbpUserManager<TRole, TUser>
{
public CustomerLoginManager(IRepository<CustomerEntity, long> repository,
TUserManager userManager) : base(repository,
userManager,
CoreConsts.CustomerRoleName,
CoreConsts.CustomerIdClaim)
{ }
}
主程序的XXX.Core的Module类中添加依赖注入
1 IocManager.Register<ICustomerLoginManager<User>, CustomerLoginManager<Tenant, Role, User, UserManager>>(Abp.Dependency.DependencyLifeStyle.Transient);
使用登陆器
最后在主程序的UserClaimsPrincipalFactory中
1 public class UserClaimsPrincipalFactory : AbpUserClaimsPrincipalFactory<User, Role>
2 {
3 private readonly ICustomerLoginManager<User> customerLoginManager;
4 public UserClaimsPrincipalFactory(
5 UserManager userManager,
6 RoleManager roleManager,
7 IOptions<IdentityOptions> optionsAccessor, ICustomerLoginManager<User> customerLoginManager)
8 : base(
9 userManager,
10 roleManager,
11 optionsAccessor)
12 {
13 this.customerLoginManager = customerLoginManager;
14 }
15 public override async Task<ClaimsPrincipal> CreateAsync(User user)
16 {
17 var claim = await base.CreateAsync(user);
18 var c = await customerLoginManager.GetBusinessUserClaim(user);
19 if(c!=null)
20 claim.Identities.First().AddClaim(c);
21 return claim;
22 }
23 }
流程说明
登陆时AbpLoginManager会调用UserClaimsPrincipalFactory来向当前登陆用户的Claims中插入Claim
UserClaimsPrincipalFactory会调用ICustomerLoginManager,先判断用户是否是顾客的角色,若是则根据用户Id找到顾客Id,然后将顾客Id存储到Claims中
后续我们在需要获取当前登陆的顾客的id时在我们的服务中依赖注入ICustomerSession就可以了,它会从当前登陆用户的Claims中去找到顾客id
abp中多种登陆用户的设计的更多相关文章
- JavaWeb-SpringSecurity在数据库中查询登陆用户
系列博文 项目已上传至guthub 传送门 JavaWeb-SpringSecurity初认识 传送门 JavaWeb-SpringSecurity在数据库中查询登陆用户 传送门 JavaWeb-Sp ...
- 在ABP VNext框架中处理和用户相关的多对多的关系
前面介绍了一些ABP VNext架构上的内容,随着内容的细化,我们会发现ABP VNext框架中的Entity Framework处理表之间的引用关系还是比较麻烦的,一不小心就容易出错了,本篇随笔介绍 ...
- C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息
在前面几篇文章中,逐步从原有微信的API封装的基础上过渡到微信应用平台管理系统里面,逐步介绍管理系统中的微信数据的界面设计,以及相关的处理操作过程的逻辑和代码,希望从更高一个层次,向大家介绍微信的应用 ...
- [2018-12-18]ABP中的AsyncCrudAppService介绍
前言 自从写完上次略长的<用ABP入门DDD>后,针对ABP框架的项目模板初始化,我写了个命令行工具Abp-CLI,其中子命令abplus init可以从github拉取项目模板以初始化项 ...
- Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析
Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析 生鲜电商搜索引擎的特点 众所周知,标准的搜索引擎主要分成三个大的部分,第一步是爬虫系统,第二步是数据分析,第三步才 ...
- 基于 Egg.js 框架的 Node.js 服务构建之用户管理设计
前言 近来公司需要构建一套 EMM(Enterprise Mobility Management)的管理平台,就这种面向企业的应用管理本身需要考虑的需求是十分复杂的,技术层面管理端和服务端构建是架构核 ...
- ABP源码分析四十七:ABP中的异常处理
ABP 中异常处理的思路是很清晰的.一共五种类型的异常类. AbpInitializationException用于封装ABP初始化过程中出现的异常,只要抛出AbpInitializationExce ...
- Apple、Google、Microsoft的用户体验设计原则
轻巧的Apple 注重设计过程: 在设计过程中引入用户交互的5个目标: 了解您的目标客户 分析用户的工作流 构造原型系统 观察用户测试 制定观察用户准则 做出设计决定 避免功能泛滥 80% 方案 优秀 ...
- ABP中使用OAuth2(Resource Owner Password Credentials Grant模式)
ABP目前的认证方式有两种,一种是基于Cookie的登录认证,一种是基于token的登录认证.使用Cookie的认证方式一般在PC端用得比较多,使用token的认证方式一般在移动端用得比较多.ABP自 ...
随机推荐
- DOM分类及HTML DOM
DOM简介 DOM是W3C(World Wide Web Consortium)标准. "W3C 文档对象模型(DOM,全称Document Object Model)"是一个使程 ...
- 手把手教你从Git上导入项目
Git上导入项目 进入Gitlab账户中的项目,点击Clone按钮,复制HTTPS路径.如果配置了SSH,则可以通过SSH导入项目. 在IDEA中,点击VCS-Checkout from Versio ...
- 【hdu 4859】海岸线(图论--网络流最小割)
题意:有一个区域,有'.'的陆地,'D'的深海域,'E'的浅海域.其中浅海域可以填充为陆地.这里的陆地区域不联通,并且整个地图都处在海洋之中.问填充一定浅海域之后所有岛屿的最长的海岸线之和. 解法:最 ...
- tomacat服务器上web资源访问流程、web应用打成war包发布、Context的reloadable属性、tomacat体系架构
一.web资源访问流程 二.web应用打成war包发布到服务器 好处:打成war包发布到服务器,那么服务器会自动把它拆解成文件夹 jar命令是java自带的一个命令,如果之前配置过Java编译环境就可 ...
- Milk Patterns POJ - 3261 后缀数组
Farmer John has noticed that the quality of milk given by his cows varies from day to day. On furthe ...
- 编译安装MySQL 5.5.33
环境要求: 主机名 IP地址 需要软件及版本 系统版本 mysql.mfyxw.com 192.168.80.135 Mysql5.5.33 5.5.33 1.设定主机名 hostnamectl se ...
- 爬虫入门一 基础知识 以及request
title: 爬虫入门一 基础知识 以及request date: 2020-03-05 14:43:00 categories: python tags: crawler 爬虫整体概述,基础知识. ...
- C++ part9
1.静态多态和动态多态 静态多态:函数重载,模板.编译期间完成. 动态多态:虚函数.运行期间实现. 2.模板的实现和优缺点 函数模板的代码并不能直接编译成二进制代码,而是要实例出一个模板实例.写了模板 ...
- sdut2878 环形依赖的DP(高斯消元,剪枝后的模板
这题的状态是循环依赖的有环.. 之前一道概率DP,类似有环..但是它是可以消掉的 比如dp[i]=0.3*dp[i+1]+0.2*dp[i+2]+0.5*dp[i]; 完全可以变成,0.5*dp[i] ...
- 云原生系列2 部署你的第一个k8s应用
云原生的概念和理论体系非常的完备,but talk is cheap , show me the code ! 但是作为一名程序员,能动手的咱绝对不多BB,虽然talk并不cheap , 能跟不同层次 ...