今天分享如何在不修改业务代码的情况下, 完成后台管理的数据权限控制.
该方案要求业务代码是基于接口开发的, 数据库为 mongo、web 框架为 express 以及注入框架 typedi.
管理员权限模型(查询)
实现查询权限的方式是最简单的, 可以直接在原先的查询条件之上拼接限制条件, 由于管理员对于每个表有不同的权限控制, 因此管理员权限模型需要保存模型以查询限制条件, 模型代码如下:
// 管理员权限
class AdminPermission {
// 管理员id
public id: string;
// 权限 { 模型: 限制查询条件 }
public permissions: {
[model: string]: any
};
}
复制代码
数据库接口及业务代码
要在不修改业务代码的情况下就启用权限控制, 要求业务代码是基于接口开发, 因此需要先定义一个数据库接口, 大致代码如下:
// 数据库工厂
abstract class DbFactoryBase {
// 创建数据库仓储
db<T>(model: new () => T): IDbRepository<T>;
}
// 数据表仓储
interface IDbRepository<T> {
// 创建表查询对象
query(): IDbQuery<T>;
}
// 数据表查询
interface IDbQuery<T> {
// 查询结果
toArray(): Promise<T[]>;
// 设置查询条件
where(v: any): this;
}
复制代码
其次需要一段业务代码作为参考, 大致代码如下:
// 后台api(包含会话)
@Service()
class BgApiSession {
// 管理员id
protected adminID: string;
// 数据库工厂, api调用前会注入
@Inject()
public dbFactory: DbFactoryBase;
// 初始化会话(api注入后调用前会触发)
public async initSession(req: Request) {
// 从req上获取adminID并验证有效性, 代码略
}
}
// 业务模型
class Admin {
// 管理员id
public id: string;
// 用户名
public name: string;
// 密码
public password: string;
}
// api - 获取admin数据
@Service()
export class FindAdminsApi extends BgApiSession {
public async call() {
const rows = await this.dbFactroy.db(Admin).query().toArray();
return rows.map(r => {
return {
id: r.id,
name: r.name
});
}
}
复制代码
实现权限控制(查询)
有了数据库接口和管理员权限模型之后, 就可以实现DbFactoryBase
来实现查询权限控制, 原理为在toArray
的方法内用mongo实现的DbFactoryBase
去查询管理员的权限数据, 然后将管理员权限数据内对应的表的条件覆盖到原先的查询条件之上, 大致代码如下:
// 管理员权限数据库工厂
class AdminPermissionDbFactory extends DbFactoryBase {
public constructor(
// 基于mongo实现的DbFactoryBase
private m_MongoDbFactory: DbFactoryBase,
// 管理员id
private m_AdminID: string,
) {}
// 创建数据表库仓储
public db<T>(model: new () => T) {
return new AdminPermissionDbRepository<T>(this.m_MongoDbFactory, this.m_AdminID, model);
}
}
// 管理员权限数据表仓储
class AdminPermissionDbRepository<T> implements IDbRepository<T> {
public constructor(
// 基于mongo实现的DbFactoryBase
private m_MongoDbFactory: DbFactoryBase,
// 管理员id
private m_AdminID: string,
// 查询的模型
private m_Model: new () => T,
) {}
// 创建查询
public query() {
return new AdminPermissionDbQuery<T>(this.m_MongoDbFactory, this.m_AdminID, this.m_Model);
}
}
// 管理员数据表查询
class AdminPermissionDbQuery<T> implements IDbQuery<T> {
private m_Where: any;
public constructor(
// 基于mongo实现的DbFactoryBase
private m_MongoDbFactory: DbFactoryBase,
// 管理员ID
private m_AdminID: string,
// 查询的模型
private m_Model: new () => T,
) {}
// 获取数据行
public async toArray() {
// 权限数据存在则标识查询有限制
const adminPermissionRows = await this.m_MongoDbFactory.db(AdminPermission).query().where({
id: this.m_AdminID
}).toArray();
return this.m_MongoDbFactory.db(this.m_Model).query().where({
...this.m_Where,
...adminPermissionRows[0]?.permissions[this.m_Model.name]
}).toArray();
}
// 条件
public async where(v: any) {
this.m_Where = v;
return this;
}
}
复制代码
重构业务代码(查询)
有了基于权限实现的DbFactoryBase
之后, 需要重构BgApiSession
把原先注入的mongo实现的DbFactoryBase
实例替换成该实现, 大致代码如下:
class BgApiSession {
public async initSession(req: Request) {
// 替换成管理员权限数据库
this.dbFactory = new AdminPermissionDbFactory(this.dbFactory, this.adminID);
}
// 其他略
}
复制代码
管理员权限模型(增删改)
接下来需要记录管理员对于某个表的 CUD 权限,因此需要扩展原先的模型,,大致代码如下:
class AdminPermission {
public permissions: {
[model: string]: {
// 删除
delete: boolean,
// 新增
insert: boolean,
// 原先的查询限制条件
query: any,
// 更新
update: boolean,
}
};
}
复制代码
实现权限控制(增删改)
首先扩展DbFactoryBase
支持 CUD, 这里使用工作单元
模式, 大致代码如下:
abstract class DbFactoryBase {
// 创建数据表仓储, 此处增加uow参数(查询时不用传入)
public abstract db<T>(model: new () => T, uow?: IUnitOfWork): IDbRepository<T>;
// 创建工作单元
public abstract uow(): IUnitOfWork;
}
interface IDbRepository<T> {
// 删除, 该方法内会调用IUnitOfWorkRepository.registerDelete
delete<T>(row: T): Promise<void>;
// 插入, 该方法内会调用IUnitOfWorkRepository.registerInsert
insert<T>(row: T): Promise<void>;
// 更新, 该方法内会调用IUnitOfWorkRepository.registerUpadete
upadete<T>(row: T): Promise<void>;
// 其他, 略
}
// 工作单元
interface IUnitOfWork {
commit(): Promise<void>;
}
// 工作单元仓储
interface IUnitOfWorkRepository extends IUnitOfWork {
// 注册删除
registerDelete<T>(model: new () => T, row: T): void;
// 注册插入
registerInsert<T>(model: new () => T, row: T): void;
// 注册更新
registerUpadete<T>(model: new () => T, row: T): void;
}
复制代码
增删改的权限控制实现原理为registerInsert
registerRemove
registerUpdate
的实现中, 记录下模型的增删改以及调用mongo实现的IDbRepository
对应的方法, 然后在commit
的时候, 用mongo实现的IDbRepository
查询管理员的权限数据, 遍历模型增删改记录并检查是否拥有权限, 如果没有该权限则抛出错误, 大致代码如下:
class AdminPermissionUnitOfWork implements IUnitOfWorkRepository {
// mongo工作单元
private m_MongoUow: IUnitOfWorkRepository;
// 记录
private m_Records: {
// 行为, 增 or 删 or 改
action: stirng,
// 模型名
model: string,
}[] = [];
public constructor(
private m_MongoDbFactory: DbFactoryBase,
private m_AdminID: string,
) {
this.m_MongoUow = this.m_MongoDbFactory.uow() as IUnitOfWorkRepository;
}
public async commit() {
if (this.m_Records.length == 0)
return;
try {
const adminPermissionRows = await this.m_MongoDbFactory.db(AdminPermission).query().where({
id: this.m_AdminID
}).toArray();
// 数据存在且值为true时 表没有相关的操作权限
if (adminPermissionRows.length) {
for (const r of this.m_Records) {
if (adminPermissionRows[0].permissions[r.model]?.[r.action])
throw new Error(`${r.model}没有${r.action}权限`);
}
}
await this.m_MongoUow.commit();
} finally {
this.m_Records = [];
}
}
public registerDelete<T>(model: new () => T, row: T) {
this.m_MongoUow.registerDelete(model, row);
this.m_Records.push({
action: 'delete',
model: model.name
});
}
public registerInsert<T>(model: new () => T, row: T) {
this.m_MongoUow.registerInsert(model, row);
this.m_Records.push({
action: 'insert',
model: model.name
});
}
public registerUpdate<T>(model: new () => T, row: T) {
this.m_MongoUow.registerUpdate(model, row);
this.m_Records.push({
action: 'update',
model: model.name
});
}
// 其他, 略
}
复制代码
结尾
文章到此结束了, 如果有任何问题和疑问, 欢迎留言, 谢谢.
评论