nestjs-entity添加subscribe监听

前言

在开发过程中是不是碰到这样一个场景,数据库直接获取某个对象的时候,需要额外生成一个参数给客户端,例如:file文件给用户时实际上要额外返回一个签名url,这个是不能从数据库直接获取的,这时候就可以通过typeorm提供的 subscribe 订阅监听来解决了

更多可以参考 这里

案例

只需要新建一个类,继承自 EntitySubscriberInterface,且使用 EventSubscriber 类装饰器,只需要设置监听 entity实现回调即可,这里实现的是加载 entity 后的回调 afterLoad,也就是我们查询生成对象的之后会进行回调,使用非常方便

实际使用可以不仅仅在加载后添加内容,还可以在 insert、remove 前后等做操作,具体可以点进接口查看

js 复制代码
import { DataSource, EntitySubscriberInterface, EventSubscriber } from 'typeorm'

//监听
@EventSubscriber()
export class FileSubscriber implements EntitySubscriberInterface<File> {
    constructor(dataSource: DataSource, private minioServer: MinioService) {
        dataSource.subscribers.push(this)
    }

    listenTo() {
        return File
    }

    async afterLoad(entity: File) {
        //假设我们使用 minio 对文件进行签名期限url
        entity.url = await this.minioServer.getPresignedUrl(entity.filename)
    }
}

//数据库 file entity
@Entity()
export class File {
    @PrimaryGeneratedColumn('uuid')
    id: string

    @Column('bigint', { default: 0 })
    size: number

    @Column({ nullable: true, default: null })
    filename: string

    @Column({ default: null })
    mimetype: string

    @CreateDateColumn()
    timestamp: Date

    //返回时获取的url,实际数据库不存在
    url: string
}

优缺点

优点很明显,使用方便了很多,不用频繁对我们查询的结果赋值了,能节省很多代码,且能专对 entity 操作前后进行我们额外的操作,用起来屡试不爽,且这是 typeorm 内部编写,因此支持最好,因此可以立即为比较适合 typeorm 的操作方式

缺点同时也很明显,我们的 entity 需要增加额外的参数,该监听也会与我们的 entity 耦合到一起,entity 不再纯粹对应关系数据库了,不管我们需不需要额外的参数,获取时一定会额外获取增加的参数,查询结果不再是单纯对应我们编写的 dto 了(甚至可能让你有了不想编写返回值文档的想法😂)

ps:如果 typeorm 能够在查询时,可以根据我们添加的参数选择是否使用监听那就更好了

简易版监听(@AfterLoad)

上面就是一个稍微重点的例子了,只能使用 subscribe 了(要导入 service,因此没办法直接写到 entity 中)

像下面的,可能只需要一个小小的加工即可生成,那么直接而使用 @AfterLoad 然后编写内容即可,这样就可以生成我们想要的 url 了

js 复制代码
//数据库 file entity
@Entity()
export class File {
    @PrimaryGeneratedColumn('uuid')
    id: string

    @Column('bigint', { default: 0 })
    size: number

    @Column({ nullable: true, default: null })
    filename: string

    @Column({ default: null })
    mimetype: string

    @CreateDateColumn()
    timestamp: Date

    //返回时获取的url,实际数据库不存在
    url: string
    
    @AfterLoad()
    generateUrl() {
        this.url = 'askdfjaskdhfaksdhfakhl'
    }
}

subscribe 小技巧(请权衡利弊后使用)

通过小技巧,将 entity 中额外增加的参数分离到 dto,可避免 entity 中出现新的参数,订阅时,将参数声明成 dto 的类型即可

js 复制代码
//监听
@EventSubscriber()
export class FileSubscriber implements EntitySubscriberInterface<FileDto> {
    constructor(dataSource: DataSource, private minioServer: MinioService) {
        dataSource.subscribers.push(this)
    }

    listenTo() {
        return File
    }

    async afterLoad(entity: FileDto) {
        //假设我们使用 minio 对文件进行签名期限url
        entity.url = await this.minioServer.getPresignedUrl(entity.filename)
    }
}

//数据库 file entity,此时url就不存在了
@Entity()
export class File {
    @PrimaryGeneratedColumn('uuid')
    id: string

    @Column('bigint', { default: 0 })
    size: number

    @Column({ nullable: true, default: null })
    filename: string

    @Column({ default: null })
    mimetype: string

    @CreateDateColumn()
    timestamp: Date
}

原因

我们的 typescript 最终也会被编译成 javascript,且 typeorm 的仓库都是 js + d.ts 的声明方式出现,意味着里面都是 js 编写的,因此里面实际生成 entity 的时候是没有类型限制的,那时候新增加一个 url,并不会出现错误(这就是解释型语言的好处),因此我们可以将类型声明成我们的返回类型dto,那么就可以实现 entity 与返回结果分离了(实际查询 entity 过程中仍然会额外返回 url)

为什么这么做?

我们的 dto 实际上的参数,才是实际上对应这返回给客户端的参数,除了我们新增的 url,什么一对一,一对多这种,如果根本不会反向关联,在返回时 dto 中实际都不会存在该参数,因此我们这个小技巧更像是专门为返回给用户的 dto 为主而做的,并且 dto 更像是我们的 entity 派生而来(实际上不一定是继承关系,可能就是entity与dto基础参数组合的原型函数,他们存在继承关系)

因此这个参数我们再返回 entity 类型时使用,类型上并不可见(理解为派生类,因此实际存在),返回给用户确实时该派生类,符合面向对象的多态

扩展

有人可能觉得,虽然用着很方便,甚至提到了类似派生类的方式来解释,但仍然无法避免这个增加的参数,且每次查询都要额外生成,如果这个参数不常用,且很消耗性能,那样会造成不必要的消耗,这样怎么办呢

只想说,用最原始的办法,生成返回的 dto 类型时,dto 额外增加一个参数,返回数据时手动给该参数赋值,如果是数组对象,则需要访问并赋值

想直接传参就可以选择是否使用监听,那么告诉编写 typeorm 的团队更新一下,一劳永逸(还是老老实实手动赋值吧)

相关推荐
Eric_见嘉4 天前
NestJS 🧑‍🍳 厨子必修课(九):API 文档 Swagger
前端·后端·nestjs
XiaoYu200212 天前
第3章 Nest.js拦截器
前端·ai编程·nestjs
XiaoYu200213 天前
第2章 Nest.js入门
前端·ai编程·nestjs
实习生小黄14 天前
NestJS 调试方案
后端·nestjs
当时只道寻常17 天前
NestJS 如何配置环境变量
nestjs
濮水大叔1 个月前
VonaJS是如何做到文件级别精确HMR(热更新)的?
typescript·node.js·nestjs
ovensi1 个月前
告别笨重的 ELK,拥抱轻量级 PLG:NestJS 日志监控实战指南
nestjs
ovensi1 个月前
Docker+NestJS+ELK:从零搭建全链路日志监控系统
后端·nestjs
Gogo8161 个月前
nestjs 的项目启动
nestjs