写点什么

[ts] 后台管理数据权限控制实现 (无业务修改)

作者:林逸民
  • 2022 年 5 月 12 日
  • 本文字数:3643 字

    阅读完需:约 12 分钟

今天分享如何在不修改业务代码的情况下, 完成后台管理的数据权限控制.

该方案要求业务代码是基于接口开发的, 数据库为 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 }); } // 其他, 略}
复制代码
结尾

文章到此结束了, 如果有任何问题和疑问, 欢迎留言, 谢谢.

发布于: 刚刚阅读数: 6
用户头像

林逸民

关注

痛苦丢给身体 舒适留给灵魂 2018.11.13 加入

编程爱好者

评论

发布
暂无评论
[ts]后台管理数据权限控制实现(无业务修改)_typescript_林逸民_InfoQ写作社区