在NestJS中结合MySQL实现自定义自动生成ID功能
在NestJS结合MySQL实现自定义格式自动生成ID
要生成如GM22JA001格式的ID(规则:固定公司标识+年份后2位+月份两位缩写+三位补零自增编号),可以通过数据库序列表+NestJS事务逻辑实现,确保并发场景下ID不重复,以下是具体实现方案:
1. 数据库表设计
创建专门的序列维护表,用于按「公司标识+年月」跟踪当前自增编号,避免全表扫描目标表的性能问题:
CREATE TABLE `id_generator` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `company_code` VARCHAR(2) NOT NULL COMMENT '固定公司标识,如GM', `year_month` VARCHAR(4) NOT NULL COMMENT '年份后2位+月份缩写,如22JA', `current_seq` INT NOT NULL DEFAULT 0 COMMENT '当前自增序列值', UNIQUE KEY `uk_company_yearmonth` (`company_code`, `year_month`) );
2. 定义TypeORM实体
创建IdGenerator实体映射上述表:
import { Entity, Column, PrimaryGeneratedColumn, Unique } from 'typeorm'; @Entity('id_generator') @Unique(['companyCode', 'yearMonth']) export class IdGenerator { @PrimaryGeneratedColumn() id: number; @Column({ name: 'company_code', length: 2 }) companyCode: string; @Column({ name: 'year_month', length: 4 }) yearMonth: string; @Column({ name: 'current_seq' }) currentSeq: number; }
3. 实现ID生成服务
创建NestJS服务,通过数据库事务保证序列更新的原子性,处理并发场景:
import { Injectable, Logger } from '@nestjs/common'; import { EntityManager, Repository } from 'typeorm'; import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm'; import { IdGenerator } from './entities/id-generator.entity'; @Injectable() export class CustomIdGeneratorService { private readonly logger = new Logger(CustomIdGeneratorService.name); // 自定义月份两位缩写,对应1-12月 private readonly MONTH_ABBR = ['JA', 'FE', 'MR', 'AP', 'MY', 'JN', 'JL', 'AU', 'SE', 'OC', 'NV', 'DE']; // 公司标识可移至.env配置 private readonly COMPANY_CODE = 'GM'; constructor( @InjectRepository(IdGenerator) private readonly idGeneratorRepo: Repository<IdGenerator>, @InjectEntityManager() private readonly entityManager: EntityManager, ) {} async generateCustomId(): Promise<string> { const now = new Date(); const yearSuffix = now.getFullYear().toString().slice(-2); const monthAbbr = this.MONTH_ABBR[now.getMonth()]; const yearMonthKey = `${yearSuffix}${monthAbbr}`; // 用事务确保序列更新原子性 return await this.entityManager.transaction(async (txEntityManager) => { // 查询当前公司+年月的序列记录 let generatorRecord = await txEntityManager.findOne(IdGenerator, { where: { companyCode: this.COMPANY_CODE, yearMonth: yearMonthKey, }, }); if (!generatorRecord) { // 首次生成当月ID,初始化序列为1 generatorRecord = txEntityManager.create(IdGenerator, { companyCode: this.COMPANY_CODE, yearMonth: yearMonthKey, currentSeq: 1, }); await txEntityManager.save(generatorRecord); } else { // 已有记录,序列自增 generatorRecord.currentSeq += 1; await txEntityManager.save(generatorRecord); } // 格式化三位序列为补零字符串 const seqStr = generatorRecord.currentSeq.toString().padStart(3, '0'); return `${this.COMPANY_CODE}${yearMonthKey}${seqStr}`; }); } }
4. 在业务逻辑中调用ID生成服务
比如在创建业务实体(如商品)时,调用生成服务获取自定义ID:
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Product } from './entities/product.entity'; import { CustomIdGeneratorService } from '../custom-id-generator/custom-id-generator.service'; @Injectable() export class ProductService { constructor( @InjectRepository(Product) private readonly productRepo: Repository<Product>, private readonly idGeneratorService: CustomIdGeneratorService, ) {} async createProduct(productData: Partial<Product>) { const customId = await this.idGeneratorService.generateCustomId(); const product = this.productRepo.create({ ...productData, customId, // 赋值自定义ID }); return await this.productRepo.save(product); } }
关键说明
- 并发安全:通过数据库事务包裹查询+更新操作,确保同一时刻只有一个请求能修改序列值,避免重复ID。
- 扩展性:公司标识可移至环境变量(如
process.env.COMPANY_CODE),支持多公司场景;月份缩写可根据需求调整。 - 性能优化:使用独立的序列表而非查询目标表最大ID,避免大表全表扫描,提升生成效率。
内容的提问来源于stack exchange,提问作者Jothi Raj
相关产品推荐
相关产品推荐

