简化数据操作代码
学习目标
- 学会抽象代码,减少重复工作
文件结构
本节内容仍然主要聚焦于CoreModule
src/core
├── base
│ ├── data.service.ts
│ ├── index.ts
│ ├── repository.ts
│ ├── subscriber.ts
│ └── tree.repository.ts
├── constants.ts
├── core.module.ts
├── decorators
│ ├── dto-validation.decorator.ts
│ └── index.ts
├── helpers.ts
├── index.ts
├── providers
│ ├── app.filter.ts
│ ├── app.interceptor.ts
│ ├── app.pipe.ts
│ └── index.ts
└── types.ts
应用编码
核心代码
BaseRepository
这是一个通用的基础存储类,继承自自带的Repository
类
queryName
属性是一个抽象属性,在子类中设置,用于在构建查询时提供默认模型的查询名称buildBaseQuery
方法用于构建基础查询getQueryName
方法用于获取queryName
// src/core/base/repository.ts
export abstract class BaseRepository<
Entity extends ObjectLiteral,
> extends Repository<Entity> {
protected abstract queryName: string;
buildBaseQuery(): SelectQueryBuilder<Entity>
getQueryName():string
}
TreeRepository
默认的TreeRepository
基类的方法如findRoots
等无法在QueryBuilder
中实现排序,自定义query
函数等,所以创建一个继承自默认基类的新的TreeRepository
来实现
在实现此类之前先添加如下类型
// src/core/types.ts
/**
* 排序类型,{字段名称: 排序方法}
* 如果多个值则传入数组即可
* 排序方法不设置,默认DESC
*/
type OrderQueryType =
| string
| { name: string; order: 'DESC' | 'ASC' }
| Array<{ name: string; order: 'DESC' | 'ASC' } | string>;
/**
* 查询参数
* orderBy: 排序类型
* getQuery: 查询回调,可以在这个函数中添加自定义查询
*/
type TreeQueryParam<E> = {
getQuery?: (query: SelectQueryBuilder<E>) => SelectQueryBuilder<E>;
orderBy?: OrderQueryType;
};
TreeRepository
包含BaseRepository
的queryName
等所有属性和方法
其余属性及方法列如下
如果
params
中不传orderBy
则使用this.orderBy
属性
findTree
: 为findTrees
添加添加参数findRts
: 为findRoots
列表查询添加条件参数findDts
: 为findDescendants
添加条件参数findDtsTree
: 为findDescendantsTree
添加条件参数countDts
: 为countDescendants
添加条件参数createDtsQueryBuilder
: 为createDescendantsQueryBuilder
添加条件参数findAts
,findAtsTree
,countAts
,createAtsQueryBuilder
与DTS
的方法类似,都是为对应的原方法添加条件查询参数toFlatTrees
: 打平并展开树getOrderByQuery
: 根据orderBy
属性生成排序的query
// src/core/base/base.repository.ts
export abstract class BaseTreeRepository<
E extends ObjectLiteral,
> extends TreeRepository<E> {
// 自定义排序规则,如果没有设置则(entity中有`order`字段则使用`order`字段,否则不排序)
protected orderBy?: string | { name: string; order: 'DESC' | 'ASC' };
buildBaseQuery(): SelectQueryBuilder<E>
getQueryName()
// 查询树
async findTree(params: TreeQueryParam<E> = {}): Promise<E[]>
// 查询顶层列表
findRts(params: TreeQueryParam<E> = {}): Promise<E[]>
// 查询后代列表
findDts(entity: E, params: TreeQueryParam<E> = {}): Promise<E[]>
// 查询后代树
findDtsTree(entity: E, params: TreeQueryParam<E> = {}): Promise<E>
// 查询后代数量
countDts(entity: E, params: TreeQueryParam<E> = {}): Promise<number>
// 创建后代查询器
createDtsQueryBuilder(
alias: string,
closureTableAlias: string,
entity: E,
params: TreeQueryParam<E> = {},
): SelectQueryBuilder<E>
// 查询祖先列表
findAts(entity: E, params: TreeQueryParam<E> = {}): Promise<E[]>
// 查询祖先树
findAtsTree(entity: E, params: TreeQueryParam<E> = {}): Promise<E>
// 查询祖先数量
countAts(entity: E, params: TreeQueryParam<E> = {}): Promise<number>
// 创建祖先查询器
createAtsQueryBuilder(
alias: string,
closureTableAlias: string,
entity: E,
params: TreeQueryParam<E> = {},
): SelectQueryBuilder<E>
// 打平并展开树
async toFlatTrees(trees: E[], level = 0): Promise<E[]>
// 生成排序的query
protected getOrderByQuery(
query: SelectQueryBuilder<E>,
alias: string,
orderBy?: OrderQueryType,
)
}
BaseSubscriber
添加一个SubcriberSetting
类型用于添加设置
export type SubcriberSetting = {
// 监听的模型是否为树模型
tree?: boolean;
};
在构造函数中根据传入的参数设置连接,并在连接中加入当前订阅者,以及构建默认的repository
等
实现如下
// src/core/base/subscriber.ts
@EventSubscriber()
export abstract class BaseSubscriber<E extends ObjectLiteral>
implements EntitySubscriberInterface<E>
{
...
constructor(connection: Connection, repository?: Type<SubscriberRepo<E>>) {
this.connection = connection;
this.connection.subscribers.push(this);
this.em = this.connection.manager;
this.setRepository(repository);
if (!this.setting) this.setting = {};
}
listenTo()
async afterLoad(entity: any) {
// 是否启用树形
if (this.setting.tree && !entity.level) entity.level = 0;
}
protected setRepository(repository?: Type<SubscriberRepo<E>>)
// 判断某个属性是否被更新
protected isUpdated(cloumn: keyof E, event: UpdateEvent<E>)
}
DataService
此类目的在于封装和简化一些常用的数据操作
更改PaginateDto
使它支持泛型参数传入
// src/core/types.ts
export interface PaginateDto<C extends IPaginationMeta = IPaginationMeta>
extends Omit<IPaginationOptions<C>, 'page' | 'limit'> {
page: number;
limit: number;
}
对于create
和update
方法因为子类需要变化的地方比较多,所以直接交给子类去实现,如果子类没有实现则直接抛出403
异常.repository
属性则在子类中必须被定义,可使用依赖直接注入
// src/core/base/data.service.ts
export abstract class BaseDataService<
E extends ObjectLiteral,
P extends Record<string, any> = {},
M extends IPaginationMeta = IPaginationMeta,
> {
// 服务默认存储类
protected abstract repository: BaseRepository<E> | BaseTreeRepository<E>;
// 获取数据列表
async list(params?: P, callback?: QueryHook<E>): Promise<E[]>
// 获取分页数据
async paginate(
options: PaginateDto<M>,
params?: P,
callback?: QueryHook<E>,
): Promise<Pagination<E, M>>
// 获取数据详情
async detail(id: string, callback?: QueryHook<E>): Promise<E>
// 创建数据,如果子类没有实现则抛出404
create(data: any): Promise<E>
// 更新数据,如果子类没有实现则抛出404
update(data: any): Promise<E>
// 删除数据
async delete(id: string)
// 获取查询单个项目的QueryBuilder
protected async getItemQuery(
query: SelectQueryBuilder<E>,
callback?: QueryHook<E>,
)
// 获取查询数据列表的 QueryBuilder
protected async getListQuery(
query: SelectQueryBuilder<E>,
params: P,
callback?: QueryHook<E>,
)
// 如果是树形模型,则此方法返回父项
protected async getParent(id?: string)
}
修改应用
subscribers
使CategorySubscriber
和PostSubscriber
分别继承BaseSubscriber
,以CategorySubscriber
为例,如下
CategoryEntity
是一个树形模型,所以需要在设置中添加tree
// src/modules/content/subscribers/category.subscriber.ts
@EventSubscriber()
export class CategorySubscriber extends BaseSubscriber<CategoryEntity> {
protected entity = CategoryEntity;
protected setting: SubcriberSetting = {
tree: true,
};
constructor(protected connection: Connection) {
super(connection, CategoryRepository);
}
}
Services
三个服务类都继承BaseDataService
,省略掉各自一些在父类中已经实现而无需修改的方法,以CategoryService
为例,如下
create
和update
方法需要自己封装
// src/modules/content/services/category.service.ts
export class CategoryService extends BaseDataService<CategoryEntity> {
protected entity = CategoryEntity;
constructor(
protected entityManager: EntityManager,
protected repository: CategoryRepository,
) {
super();
}
...
}