- 教程地址: v.pincman.com/courses/64.html
- 影片地址: www.bilibili.com/video/BV1DZ4y1Y78...
- qq: 1849600177
- qq 群: 455820533
另,本人在找工作中,希望能有遠端工作匹配(無法去外地),有需要的老闆可以看一下我的個人介紹:pincman.com/about
學習目標
- 過載TreeRepository自帶方法來對樹形結構的資料進行扁平化處理
- 對Typeorm查詢出的資料列表進行分頁處理
- 透過請求中的query查詢對資料進行篩選處理,比如排序,過濾等
- 實現釋出文章和取消釋出的功能
- Typeorm 模型事件和Subscriber(訂閱者)的使用
- 使用
sanitize-html
對文章內容進行防注入攻擊處理
預裝依賴
- nestjs-typeorm-paginate實現分頁
- sanitize-html過濾
html
標籤,防注入攻擊 - deepmerge深度合併物件
~ pnpm add nestjs-typeorm-paginate sanitize-html deepmerge && pnpm add @types/sanitize-html -D
檔案結構
建立檔案
cd src/modules/content && \
mkdir subscribers && \
touch dtos/query-category.dto.ts \
dtos/query-post.dto.ts \
subscribers/post.subscriber.ts \
subscribers/index.ts \
services/sanitize.service.ts \
&& cd ../../../
與上一節一樣,這一節的新增和修改集中於ContentModule
src/modules/content
├── constants.ts
├── content.module.ts
├── controllers
│ ├── category.controller.ts
│ ├── comment.controller.ts
│ ├── index.ts
│ └── post.controller.ts
├── dtos
│ ├── create-category.dto.ts
│ ├── create-comment.dto.ts
│ ├── create-post.dto.ts
│ ├── index.ts
│ ├── query-category.dto.ts
│ ├── query-post.dto.ts
│ ├── update-category.dto.ts
│ └── update-post.dto.ts
├── entities
│ ├── category.entity.ts
│ ├── comment.entity.ts
│ ├── index.ts
│ └── post.entity.ts
├── repositories
│ ├── category.repository.ts
│ ├── comment.repository.ts
│ ├── index.ts
│ └── post.repository.ts
├── services
│ ├── category.service.ts
│ ├── comment.service.ts
│ ├── index.ts
│ ├── post.service.ts
│ └── sanitize.service.ts
└── subscribers
├── index.ts
└── post.subscriber.ts
應用編碼
這節多了一個新的概念,即subscriber
,具體請查閱typeorm
文件,當然你也可以在模型中使用事件處理函式,效果沒差別
模型
CategoryEntity
程式碼:src/modules/content/entities/category.entity.ts
- 新增
order
欄位用於排序 - 新增
level
屬性(虛擬欄位)用於在打平樹形資料的時候新增當前項的等級
PostEntity
程式碼: src/modules/content/entities/post.entity.ts
type
欄位的型別用enum
列舉來設定,首先需要定義一個PostBodyType
的enum
型別,可以新增一個constants.ts
檔案來統一定義這些enum
和常量
- 新增
publishedAt
欄位用於控制釋出時間和釋出狀態 - 新增
type
欄位用於設定釋出型別 - 新增
customOrder
欄位用於自定義排序
儲存類
CategoryRepository
程式碼: src/modules/content/repositories/category.repository.ts
因為CategoryRepository
繼承自TreeRepository
,所以我們在typeorm
原始碼中找到這個類,並對部分方法進行覆蓋,如此我們就可以對樹形分類進行排序,覆蓋的方法如下
當然後面會講到更加深入的再次封裝,此處暫時先這麼用
findRoots
為根分類列表查詢新增排序createDescendantsQueryBuilder
為子孫分類查詢器新增排序createAncestorsQueryBuilder
為祖先分類查詢器新增排序
DTO驗證
新增QueryCategoryDto
和QueryPostDto
用於查詢分類和文章時進行分頁以及過濾資料和設定排序型別等
在新增DTO
之前,現在新增幾個資料轉義函式,以便把請求中的字串改成需要的資料型別
// src/core/helpers.ts
// 用於請求驗證中的number資料轉義
export function tNumber(value?: string | number): string |number | undefined
// 用於請求驗證中的boolean資料轉義
export function tBoolean(value?: string | boolean): string |boolean | undefined
// 用於請求驗證中轉義null
export function tNull(value?: string | null): string | null | undefined
修改create-category.dto.ts
和create-comment.dto.ts
的parent
欄位的@Transform
裝飾器
export class CreateCategoryDto {
...
@Transform(({ value }) => tNull(value))
parent?: string;
}
新增一個通用的DTO
介面型別
// src/core/types.ts
// 分頁驗證DTO介面
export interface PaginateDto {
page: number;
limit: number;
}
QueryCategoryDto
程式碼: src/modules/content/dtos/query-category.dto.ts
page
屬性設定當前分頁limit
屬性設定每頁資料量
QueryPostDto
除了與QueryCateogryDto
一樣的分頁屬性外,其它屬性如下
orderBy
用於設定排序型別isPublished
根據釋出狀態過濾文章category
過濾出一下分類及其子孫分類下的文章
orderBy
欄位是一個enum
型別的欄位,它的可取值如下
CREATED
: 根據建立時間降序UPDATED
: 根據更新時間降序PUBLISHED
: 根據釋出時間降序COMMENTCOUNT
: 根據評論數量降序CUSTOM
: 根據自定義的order
欄位升序
服務類
SanitizeService
程式碼: src/modules/content/services/sanitize.service.ts
此服務類用於clean html
sanitize
方法用於對HTML資料進行防注入處理
CategoryService
程式碼:src/modules/content/services/category.service.ts
新增一個輔助函式,用於對打平後的樹形資料進行分頁
// src/core/helpers.ts
export function manualPaginate<T extends ObjectLiteral>(
{ page, limit }: PaginateDto,
data: T[],
): Pagination<T>
新增paginate(query: QueryCategoryDto)
方法用於處理分頁
async paginate(query: QueryCategoryDto) {
// 獲取樹形資料
const tree = await this.findTrees();
// 打平樹形資料
const list = await this.categoryRepository.toFlatTrees(tree);
// 呼叫手動分頁函式進行分頁
return manualPaginate(query, list);
}
PostService
程式碼:src/modules/content/services/post.service.ts
getListQuery
: 用於構建過濾與排序以及透過分類查詢文章資料等功能的query
構建器paginate
: 呼叫getListQuery
生成query
,並作為nestjs-typeorm-paginate
的paginate
的引數對資料進行分頁
async paginate(params: FindParams, options: IPaginationOptions) {
const query = await this.getListQuery(params);
return paginate<PostEntity>(query, options);
}
訂閱者
PostSubscriber
程式碼: src/modules/content/subscribers/post.subscriber.ts
beforeInsert
(插入資料前事件): 如果在新增文章的同時釋出文章,則設定當前時間為釋出時間beforeUpdate
(更新資料前事件): 更改釋出狀態會同時更新發布時間的值,如果文章更新為未釋出狀態,則把釋出時間設定為nullafterLoad
(載入資料後事件): 對HTML型別的文章內容進行去標籤處理防止注入攻擊
一個需要注意的點是需要在subcriber
類的建構函式中注入Connection
才能獲取連結
constructor(
connection: Connection,
protected sanitizeService: SanitizeService,
) {
connection.subscribers.push(this);
}
註冊訂閱者
把訂閱者註冊成服務後,由於在建構函式中注入了connection
這個連線物件,所以typeorm
會自動把它載入到這個預設連線的subscribers
配置中
// src/modules/content/subscribers/post.subscriber.ts
import * as SubscriberMaps from './subscribers';
const subscribers = Object.values(SubscriberMaps);
@Module({
....
providers: [...subscribers, ...dtos, ...services],
})
控制器
CategoryController
程式碼: src/modules/content/controllers/category.controller.ts
list
: 透過分頁來查詢扁平化的分類列表index
: 把url設定成@Get('tree')
@Get()
// 分頁查詢
async list(
@Query(
new ValidationPipe({
transform: true,
forbidUnknownValues: true,
validationError: { target: false },
}),
)
query: QueryCategoryDto,
) {
return this.categoryService.paginate(query);
}
// 查詢樹形分類
@Get('tree')
async index() {
return this.categoryService.findTrees();
}
PostController
程式碼: src/modules/content/controllers/post.controller.ts
修改index
方法用於分頁查詢
// 透過分頁查詢資料
async index(
@Query(
new ValidationPipe({
transform: true,
forbidUnknownValues: true,
validationError: { target: false },
}),
)
{ page, limit, ...params }: QueryPostDto,
) {
return this.postService.paginate(params, { page, limit });
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結