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自 ...
随机推荐
- inceptor es表插入成功,返回报错you should set transaction.type before any DCL statement
在finebi下用星环的连接驱动去写inceptor es表,发现插入能成功,但是返回一个报错: Caused by: java.sql.SQLException: Error to commit. ...
- A - 欧拉回路
欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条回路.现给定一个图,问是否存在欧拉回路? Input测试输入包含若干测试用例.每个测试用例的第1行给出两个正整数,分别是节点数N ...
- H - Oil Skimming (挖石油)
题意大概是,海上漂浮着一些符号为#的石油,你要去搜集他们,但是你的勺子呢能且只能挖到两个单元的石油.问你最多能挖多少勺.注意 不能挖到纯净的海水,不然石油会被纯净的海水稀释的. 二分匹配,计算出里边有 ...
- 【bzoj 2038】 [2009国家集训队]小Z的袜子(算法效率--莫队分块算法 模版题)
题意:小Z有N只袜子,有不同的颜色.他有M个提问,问从编号为[L,R]的袜子中随机选一双同色的袜子的概率,用最简分数表示. 解法:经典的莫队算法--无修改.不强制在线(可离线).状态转移可以一步完成. ...
- poj2926Requirements (曼哈顿距离)
Description An undergraduate student, realizing that he needs to do research to improve his chances ...
- hdu4339 Query
Problem Description You are given two strings s1[0..l1], s2[0..l2] and Q - number of queries. Your t ...
- 牛客的两道dfs
1.传送门:牛客13594-选择困难症 题意:给你k类物品,每类物品有a[i]个每个物品都有一个value,每类物品最多选一个,要求有多少种选法使得总value>m(没要求每类物品都必须选) 题 ...
- ThreadLocal使用全解
一.何为ThreadLocal 1.ThreadLocal的含义 ThreadLocal,即线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构.这个结构被附带在线程上,也就是说一 ...
- Spring Cloud实战 | 第十一篇:Spring Cloud Gateway 网关实现对RESTful接口权限控制和按钮权限控制
一. 前言 hi,大家好,这应该是农历年前的关于开源项目 的最后一篇文章了. 有来商城 是基于 Spring Cloud OAuth2 + Spring Cloud Gateway + JWT实现的统 ...
- OpenStack Train版-15.创建并挂载存储卷
1.创建并挂载存储卷 创建一个1GB的卷 source ~/demo-openrc openstack volume create --size 1 volume1 很短的时间后,卷状态应该从crea ...