如何在TypeORM中通过标题自动生成并更新唯一Slug?
在TypeORM(NestJS + PostgreSQL)中自动生成并更新唯一Slug
实现方案
1. 安装slug生成工具
使用slugify将标题转换为标准化的无特殊字符slug:
npm install slugify
2. 核心实现逻辑
通过数据库约束保证slug唯一性,结合业务逻辑处理创建/更新时的slug生成,同时避免并发冲突。
修改Blog实体
给slug字段添加数据库唯一约束,作为最后一道防线:
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; @Entity() export class Blog { @PrimaryGeneratedColumn() id: number; @Column({ type: "text", nullable: false, unique: true }) slug: string; @Column({ type: "text", nullable: false }) title: string; @Column({ type: "text", nullable: false }) body: string; @CreateDateColumn() created_at: Date; @UpdateDateColumn() updated_at: Date; }
封装slug生成服务
创建独立服务统一处理唯一slug的生成逻辑:
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, Not } from 'typeorm'; import slugify from 'slugify'; import { Blog } from './blog.entity'; @Injectable() export class BlogSlugService { constructor( @InjectRepository(Blog) private readonly blogRepo: Repository<Blog>, ) {} async generateUniqueSlug(title: string, excludeId?: number): Promise<string> { let baseSlug = slugify(title, { lower: true, strict: true }); let targetSlug = baseSlug; let counter = 1; // 循环检查直至找到未被使用的slug while (true) { const exists = await this.blogRepo.findOne({ where: { slug: targetSlug }, ...(excludeId && { where: { id: Not(excludeId) } }), }); if (!exists) break; targetSlug = `${baseSlug}-${counter}`; counter++; } return targetSlug; } }
在业务服务中集成
在博客的创建和更新流程中自动处理slug:
import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Blog } from './blog.entity'; import { BlogSlugService } from './blog-slug.service'; @Injectable() export class BlogService { constructor( @InjectRepository(Blog) private readonly blogRepo: Repository<Blog>, private readonly slugService: BlogSlugService, ) {} async createBlog(dto: { title: string; body: string }) { const slug = await this.slugService.generateUniqueSlug(dto.title); const blog = this.blogRepo.create({ ...dto, slug }); return this.blogRepo.save(blog); } async updateBlog(id: number, dto: { title?: string; body?: string }) { const blog = await this.blogRepo.findOneBy({ id }); if (!blog) throw new NotFoundException('博客不存在'); // 仅当标题变更时重新生成slug if (dto.title && dto.title !== blog.title) { blog.slug = await this.slugService.generateUniqueSlug(dto.title, id); } Object.assign(blog, dto); return this.blogRepo.save(blog); } }
3. 可选:用TypeORM订阅者实现内聚逻辑
如果希望将slug处理逻辑和实体绑定,可以使用TypeORM订阅者:
import { EntitySubscriberInterface, EventSubscriber, InsertEvent, UpdateEvent } from 'typeorm'; import { Blog } from './blog.entity'; import slugify from 'slugify'; import { Not } from 'typeorm'; @EventSubscriber() export class BlogSubscriber implements EntitySubscriberInterface<Blog> { listenTo() { return Blog; } async beforeInsert(event: InsertEvent<Blog>) { const blog = event.entity; blog.slug = await this.getUniqueSlug(blog.title, event.manager); } async beforeUpdate(event: UpdateEvent<Blog>) { const blog = event.entity; const originalBlog = await event.manager.findOne(Blog, { where: { id: blog.id } }); if (blog.title !== originalBlog?.title) { blog.slug = await this.getUniqueSlug(blog.title, event.manager, blog.id); } } private async getUniqueSlug(title: string, manager: any, excludeId?: number) { let baseSlug = slugify(title, { lower: true, strict: true }); let targetSlug = baseSlug; let counter = 1; while (true) { const exists = await manager.findOne(Blog, { where: { slug: targetSlug }, ...(excludeId && { where: { id: Not(excludeId) } }), }); if (!exists) break; targetSlug = `${baseSlug}-${counter}`; counter++; } return targetSlug; } }
注册订阅者:在NestJS模块的providers数组中添加BlogSubscriber即可。
内容的提问来源于stack exchange,提问作者Md Ali
相关产品推荐
相关产品推荐

