外观
Prisma 模块
Prisma 数据库服务基类与模块注册,内置软删除扩展、审计字段扩展和查询工具函数。
Prisma 7+
@prisma/client 7.0 移除了 $use() 中间件 API,同时 MySQL/MariaDB 连接需要 driver adapter (如 @prisma/adapter-mariadb)。框架已内置适配,透过 $extends() 自动注册软删除和审计功能。
PrismaBaseService
由于 PrismaClient 由 prisma generate 动态生成,框架提供泛型抽象基类,由用户继承实现:
typescript
// prisma/prisma.service.ts
import { Injectable } from '@nestjs/common'
import { PrismaClient } from '@/generated/prisma/client' // prisma generate 生成路径
import { PrismaMariaDb } from '@prisma/adapter-mariadb' // MySQL/MariaDB driver adapter
import { PrismaBaseService, PrismaModelProxy } from '@maxtan/nest-core'
function parseMysqlUrl(url: string) {
const m = url.match(/^mysql:\/\/([^:]+):(.+)@([^:/?#]+)(?::(\d+))?\/([^?]+)/i)!
return { user: m[1], password: m[2], host: m[3], port: Number(m[4]) || 3306, database: m[5] }
}
// 接口合并:让 PrismaService 拥有所有模型委托类型(user、post 等)
export interface PrismaService extends PrismaModelProxy<PrismaClient> {}
@Injectable()
export class PrismaService extends PrismaBaseService<PrismaClient> {
constructor() {
super()
const { host, port, user, password, database } = parseMysqlUrl(process.env.DATABASE_URL!)
const adapter = new PrismaMariaDb({ host, port, user, password, database })
this.setClient(new PrismaClient({ adapter }))
}
}PostgreSQL / SQLite
使用 PostgreSQL 或 SQLite 时,不需要 driver adapter,直接 new PrismaClient() 即可。
PrismaBaseService<C> 提供:
| 属性/方法 | 说明 |
|---|---|
client | 事务感知客户端:在 @PrismaTransactional 内自动返回 tx,外部返回原始 PrismaClient。this.prisma.client.xxx 和 this.prisma.xxx 等价 |
rawClient | 原始客户端(不受事务影响):用于连接管理、健康检查、DMMF 读取等基础设施操作 |
setClient(client) | 设置 PrismaClient 实例(子类构造函数中调用) |
onModuleInit() | 自动连接数据库 + 注册声明式扩展 |
onModuleDestroy() | 自动断开连接 |
isHealthy() | 健康检查(SELECT 1) |
模型直通访问
PrismaBaseService 内置 Proxy,支持 this.prisma.user 直接访问模型委托,等价于 this.prisma.client.user。 配合 PrismaModelProxy<PrismaClient> 接口合并可获得完整类型提示。
PrismaModule
基础用法
typescript
import { PrismaModule } from '@maxtan/nest-core'
import { PrismaService } from './prisma.service'
@Module({
imports: [
PrismaModule.forRoot({
service: PrismaService,
global: true // 默认 true
})
]
})
export class AppModule {}声明式扩展(推荐)
通过 middlewares 配置自动注册软删除和审计扩展,框架在 onModuleInit 阶段通过 $extends 自动注入:
typescript
PrismaModule.forRoot({
service: PrismaService,
middlewares: {
softDelete: {
enabled: true,
models: 'auto' // 自动从 DMMF 发现含 deletedAt 的模型
// models: ['User', 'Post'] // 或手动指定
// exclude: ['AuditLog'] // 自动发现时排除
},
audit: {
enabled: true,
getUserId: getCurrentUserId, // 参见《最佳实践》请求上下文章节
fallbackUserId: 'system', // 匿名/系统任务回退值
// fields: { createdBy: 'created_by', updatedBy: 'updated_by' } // 非标字段映射
// createManyMode: 'fast' // 大批量写入性能模式
}
}
})扩展注册顺序:softDelete → audit(框架自动管理)。
多数据源(多 Client)
每个数据源需要一个独立的 PrismaBaseService 子类,各自持有自己的 PrismaClient 实例和扩展配置,互不干扰。
1. 定义多个 PrismaService
每个数据源对应一个独立的 schema.prisma 和 PrismaClient(由各自的 prisma generate 生成):
typescript
// prisma/primary.service.ts — 主库(业务数据)
import { Injectable } from '@nestjs/common'
import { PrismaClient } from '@/generated/primary/client'
import { PrismaMariaDb } from '@prisma/adapter-mariadb' // MySQL/MariaDB 需要
import { PrismaBaseService, PrismaModelProxy } from '@maxtan/nest-core'
export interface PrimaryPrismaService extends PrismaModelProxy<PrismaClient> {}
@Injectable()
export class PrimaryPrismaService extends PrismaBaseService<PrismaClient> {
constructor() {
super()
const adapter = new PrismaMariaDb({ host: '...', user: '...', password: '...', database: 'primary' })
this.setClient(new PrismaClient({ adapter }))
}
}typescript
// prisma/secondary.service.ts — 从库(日志/分析)
import { Injectable } from '@nestjs/common'
import { PrismaClient } from '@/generated/secondary/client'
import { PrismaMariaDb } from '@prisma/adapter-mariadb'
import { PrismaBaseService, PrismaModelProxy } from '@maxtan/nest-core'
export interface SecondaryPrismaService extends PrismaModelProxy<PrismaClient> {}
@Injectable()
export class SecondaryPrismaService extends PrismaBaseService<PrismaClient> {
constructor() {
super()
const adapter = new PrismaMariaDb({ host: '...', user: '...', password: '...', database: 'secondary' })
this.setClient(new PrismaClient({ adapter }))
}
}Prisma 7 多 Schema 配置
Prisma 7 可通过 prisma.config.ts 中的 schema 数组或多个 generator + 不同 output 目录生成多个 Client。 每个 PrismaBaseService 子类导入各自 output 目录的 PrismaClient,互不冲突。MySQL/MariaDB 每个实例各自配置独立的 driver adapter。
2. 模块注册
typescript
@Module({
imports: [
// 主库:全局注册,开启软删除 + 审计
PrismaModule.forRoot({
service: PrimaryPrismaService,
token: 'PRIMARY_PRISMA',
middlewares: {
softDelete: { enabled: true, models: 'auto' },
audit: { enabled: true, getUserId: getCurrentUserId }
}
}),
// 从库:非全局,仅在需要的模块导入
PrismaModule.forRoot({
service: SecondaryPrismaService,
token: 'SECONDARY_PRISMA',
global: false,
middlewares: {
softDelete: { enabled: true, models: ['LogEntry'] }
// 从库不需要审计 → 不配 audit
}
})
]
})
export class AppModule {}| 配置项 | 主库 | 从库 | 说明 |
|---|---|---|---|
global | true(默认) | false | 从库按需导入,避免全局污染 |
token | 'PRIMARY_PRISMA' | 'SECONDARY_PRISMA' | 自定义注入令牌,区分不同数据源 |
softDelete | auto | 手动指定 | 各自独立的扩展配置 |
audit | 启用 | 不启用 | 从库仅读取/写日志,无需审计 |
3. Service 中注入
typescript
import { Injectable, Inject } from '@nestjs/common'
import { PrimaryPrismaService } from '@/prisma/primary.service'
import { SecondaryPrismaService } from '@/prisma/secondary.service'
@Injectable()
export class AnalyticsService {
constructor(
// 方式一:直接按类注入(推荐,有完整类型提示)
private readonly primaryDb: PrimaryPrismaService,
// 方式二:按 token 注入(适用于动态切换场景)
@Inject('SECONDARY_PRISMA')
private readonly secondaryDb: SecondaryPrismaService
) {}
/** 从主库读取用户,写入从库日志 */
async logUserAction(userId: string, action: string) {
// 主库读取
const user = await this.primaryDb.user.findUnique({
where: { id: userId }
})
// 从库写入日志
await this.secondaryDb.actionLog.create({
data: { userId, action, username: user?.username }
})
}
}注入方式选择
- 推荐按类注入(
private primaryDb: PrimaryPrismaService):类型安全,IDE 自动补全 - 按 token 注入(
@Inject('SECONDARY_PRISMA')):适用于需要运行时动态切换数据源的场景 - 全局注册(
global: true)的数据源可在任意模块直接注入;非全局的需要在使用模块中imports对应的PrismaModule或手动导入
4. 多数据源 + 事务
@PrismaTransactional 默认从 this.prisma 获取客户端。多数据源时需通过 prismaField 指定属性名:
typescript
@Injectable()
export class OrderService {
constructor(
private readonly primaryDb: PrimaryPrismaService,
private readonly secondaryDb: SecondaryPrismaService
) {}
/** 在主库事务中创建订单 */
@PrismaTransactional({ prismaField: 'primaryDb' })
async createOrder(dto: CreateOrder) {
// primaryDb 在事务内自动返回 tx
const order = await this.primaryDb.order.create({ data: dto })
await this.primaryDb.inventory.update({
where: { id: dto.productId },
data: { stock: { decrement: dto.quantity } }
})
return order
}
/** 在从库事务中批量写入日志 */
@PrismaTransactional({ prismaField: 'secondaryDb' })
async batchLog(logs: CreateLog[]) {
for (const log of logs) {
await this.secondaryDb.actionLog.create({ data: log })
}
}
}跨库事务限制
@PrismaTransactional 不支持跨数据源的分布式事务。每个事务只能操作单个 PrismaClient 对应的数据库。 如需跨库原子性,考虑使用补偿机制(Saga 模式)或最终一致性方案。
5. 多数据源 + Repository
typescript
@Injectable()
export class UserRepository extends PrismaRepository<User> {
constructor(private primaryDb: PrimaryPrismaService) {
super(primaryDb, 'user')
}
}
@Injectable()
export class LogRepository extends PrismaRepository<ActionLog> {
constructor(
@Inject('SECONDARY_PRISMA')
private secondaryDb: SecondaryPrismaService
) {
super(secondaryDb, 'actionLog')
}
}6. 多数据源最佳实践
| 规则 | 说明 |
|---|---|
| 一个数据源一个 Service 子类 | 各自持有独立的连接、扩展配置,生命周期隔离 |
| 主库全局注册,从库按需注册 | 减少依赖传播,从库 global: false |
事务指定 prismaField | 多数据源时必须明确指定属性名 |
| 不跨库事务 | 单次事务只操作一个数据库,跨库用补偿机制 |
| 各自独立的扩展策略 | 主库开审计,从库仅日志软删除等 |
| 健康检查覆盖所有数据源 | 每个 Service 的 isHealthy() 独立检测 |
软删除扩展
自动拦截查询,为指定模型:
- 查询时附加
deletedAt: null条件 delete→update { deletedAt: new Date() }deleteMany→updateMany { deletedAt: new Date() }
声明式注册(推荐)
通过 PrismaModule.forRoot 的 middlewares.softDelete 配置即可,参见上方示例。
手动注册
typescript
import { createSoftDeleteExtension } from '@maxtan/nest-core'
// 自动发现含 deletedAt 的模型
const base = new PrismaClient()
this.setClient(
base.$extends(createSoftDeleteExtension({ models: 'auto' }, base)) as any
)
// 手动指定模型
base.$extends(createSoftDeleteExtension({ models: ['User', 'Post', 'Comment'] }))
// 自动发现 + 排除 + 自定义字段名
base.$extends(createSoftDeleteExtension({
models: 'auto',
exclude: ['AuditLog'],
field: 'deletedAt' // 默认值,可自定义
}, base))自动发现工具
typescript
import { discoverSoftDeleteModels } from '@maxtan/nest-core'
const models = discoverSoftDeleteModels(prisma)
// => ['User', 'Post', 'Comment'] — 自动从 DMMF 扫描含 deletedAt 字段的模型在 schema.prisma 中添加 deletedAt 字段:
prisma
model User {
id Int @id @default(autoincrement())
name String
deletedAt DateTime? @map("deleted_at")
@@map("users")
}审计字段扩展
自动填充 createdBy / updatedBy 字段。AuthModule 内置了 ClsModule(nestjs-cls)和 RequestContextInterceptor,自动将 request.user 写入 CLS 上下文,通过 getCurrentUserId() 即可获取当前用户 ID(参见 最佳实践):
声明式注册(推荐)
通过 PrismaModule.forRoot 的 middlewares.audit 配置即可,参见上方示例。
手动注册
typescript
import { createAuditExtension } from '@maxtan/nest-core'
const base = new PrismaClient()
this.setClient(
base.$extends(createAuditExtension({
getUserId: getCurrentUserId,
fallbackUserId: 'system', // 匿名/系统任务回退
fields: { createdBy: 'createdBy', updatedBy: 'updatedBy' }, // 字段名映射
createManyMode: 'strict' // strict | fast
}, base)) as any
)手动注册时建议传入第二个参数
base,以便自动识别含审计字段的模型,避免误注入createdBy/updatedBy。
createManyMode 选项:
strict(默认):逐条注入审计字段,保证完整性fast:跳过注入,提升大批量写入性能
操作规则:
| 操作 | createdBy | updatedBy |
|---|---|---|
create | 自动填充 | 自动填充 |
update / updateMany | 不修改 | 自动填充 |
upsert.create | 自动填充 | 自动填充 |
upsert.update | 不修改 | 自动填充 |
createMany | 每条自动填充 | 每条自动填充 |
审计字段接口
typescript
import { PrismaAuditFields } from '@maxtan/nest-core'
interface PrismaAuditFields {
createdAt: Date
createdBy: string | null
updatedAt: Date
updatedBy: string | null
deletedAt: Date | null
}推荐的 schema.prisma 定义:
prisma
model User {
id Int @id @default(autoincrement())
name String
// 审计字段
createdAt DateTime @default(now()) @map("created_at")
createdBy String? @map("created_by")
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String? @map("updated_by")
deletedAt DateTime? @map("deleted_at")
@@map("users")
}查询工具函数
prismaContains — 模糊查询
typescript
import { prismaContains } from '@maxtan/nest-core'
prismaContains({ name: 'John', status: 1 }, ['name'])
// => { name: { contains: 'John' }, status: 1 }
// 不区分大小写(需数据库支持)
prismaContains({ name: 'John' }, ['name'], 'insensitive')
// => { name: { contains: 'John', mode: 'insensitive' } }prismaSoftDeleteWhere — 软删除条件
typescript
import { prismaSoftDeleteWhere } from '@maxtan/nest-core'
prismaSoftDeleteWhere({ status: 1 })
// => { status: 1, deletedAt: null }
// 手动指定 deletedAt 时不覆盖
prismaSoftDeleteWhere({ status: 1, deletedAt: { not: null } })
// => { status: 1, deletedAt: { not: null } }prismaPaginate — 分页参数
typescript
import { prismaPaginate } from '@maxtan/nest-core'
prismaPaginate(2, 20)
// => { skip: 20, take: 20 }
const users = await prisma.user.findMany({
where: { ... },
...prismaPaginate(query.current, query.size)
})prismaOrderBy — 排序参数
typescript
import { prismaOrderBy } from '@maxtan/nest-core'
prismaOrderBy('createdAt', 'desc')
// => { createdAt: 'desc' }buildPrismaQuery — 完整查询构建
整合:对象清理 + 模糊查询 + IN 查询 + 日期范围 + 软删除 + 分页 + 排序:
typescript
import { buildPrismaQuery } from '@maxtan/nest-core'
// 基础用法
const query = buildPrismaQuery({
where: { name: 'John', email: '', status: null },
containsFields: ['name'],
softDelete: true,
current: 1,
size: 20,
sortBy: 'createdAt',
sortOrder: 'desc'
})
// => {
// where: { name: { contains: 'John' }, deletedAt: null },
// skip: 0, take: 20,
// orderBy: { createdAt: 'desc' }
// }增强参数:
typescript
const query = buildPrismaQuery({
where: dto,
containsFields: ['name'], // 模糊查询
equalsFields: ['code'], // 精确匹配(不被 cleanObject 过滤)
inFields: ['status', 'type'], // 数组 → { in: [...] }
dateRangeFields: [ // 日期范围 → { gte, lte }
{ start: 'startAt', end: 'endAt', target: 'createdAt' }
],
ignoreFields: ['keyword'], // 排除字段(已在外部处理)
defaultWhere: { orgId: currentOrgId }, // 默认条件(低优先级)
softDelete: true,
current: 1, size: 20
})| 参数 | 说明 |
|---|---|
containsFields | 模糊查询字段(转 contains) |
equalsFields | 精确匹配字段(跳过清理过滤) |
inFields | 数组字段(转 { in: [...] }) |
dateRangeFields | 日期范围(Schema 的 start/end → 目标字段 gte/lte) |
ignoreFields | 忽略字段(如 keyword) |
defaultWhere | 默认合并条件(优先级低于 where) |
PrismaRepository — 泛型仓储基类
沉淀高频 CRUD 模板,自动集成软删除过滤和默认排序。继承即用:
typescript
import { Injectable } from '@nestjs/common'
import { PrismaRepository } from '@maxtan/nest-core'
import { PrismaService } from '../prisma/prisma.service'
import type { User } from '@prisma/client'
@Injectable()
export class UserRepository extends PrismaRepository<User> {
constructor(private prisma: PrismaService) {
super(prisma, 'user')
// softDelete: false (默认,由中间件处理)
// defaultOrderBy: { createdAt: 'desc' } (默认)
}
}事务安全
Repository 内部通过 usePrismaClient 动态获取模型委托,在 @PrismaTransactional 事务内自动参与事务,无需手动传递 tx。
内置方法:
| 方法 | 说明 |
|---|---|
findById(id) | 按 ID 查单条 |
findByIds(ids) | 按 ID 数组批量查询 |
findOne(where) | 按条件查单条 |
findList(where) | 条件列表(不分页) |
findPage({ where, current, size }) | 分页查询(返回 Page) |
findInBatches(handler, where?, options?) | 游标分批遍历,大表安全迭代 |
count(where) | 计数 |
exists(where) | 是否存在(使用 count() > 0,兼容复合主键) |
create(data) | 创建 |
createMany(data[], skip?) | 批量创建(支持跳过重复) |
updateById(id, data) | 按 ID 更新 |
updateMany(where, data) | 批量更新 |
upsert(where, create, update) | 存在则更新,否则创建 |
upsertMany(items, uniqueWhere, getUpdate?, options?) | 批量 Upsert(逐条 upsert,适合中小批量) |
bulkUpsert(items, uniqueWhere, getUpdate?, options?) | MySQL/MariaDB 原生批量 Upsert(高性能) |
removeById(id) | 按 ID 软删除/物理删除 |
removeByIds(ids) | 批量软删除/物理删除 |
removeMany(where) | 按条件批量删除 |
findByIds — 按 ID 数组批量查询
typescript
// 按 ID 数组批量查询,自动处理软删除过滤
const users = await userRepo.findByIds(['id1', 'id2', 'id3'])upsertMany — 批量 Upsert
消除业务层手写"查询 → diff → 分批写"模板。内部按 batchSize(默认 50)分批,批内并发,批间顺序执行,避免连接池打满。
性能说明
upsertMany 是便利封装,不是数据库原生 bulk upsert。当前实现会对数组中的每一项分别执行一次 upsert,只是做了分批并发控制。
因此它更适合:中小批量同步、管理后台导入、强调开发效率 的场景。
如果是大批量写入,优先考虑:
- 纯新增:
createMany(data, true) - 同一批记录更新同一份数据:
updateMany(where, data) - 大批量异构 upsert:改用数据库原生 SQL(如 MySQL
ON DUPLICATE KEY UPDATE)
typescript
// 单字段唯一键(最常用)
await accountRepo.upsertMany(data, 'externalId')
// 复合唯一键
await accountRepo.upsertMany(
data,
(item) => ({ userId: item.userId, platform: item.platform })
)
// 自定义 update 字段(如不覆盖 createdAt)
await accountRepo.upsertMany(
data,
'externalId',
(item) => ({ name: item.name, syncedAt: new Date() })
)需要原子性时,在调用方加 @PrismaTransactional(),所有批次自动参与同一事务。事务场景建议调小 batchSize(如 20),防止触发 Prisma 默认 5s 事务超时:
typescript
@PrismaTransactional()
async syncAccounts(data: AccountDto[]) {
await this.accountRepo.upsertMany(data, 'externalId', undefined, { batchSize: 20 })
}bulkUpsert — MySQL/MariaDB 原生批量 Upsert
当数据量较大时,优先使用 bulkUpsert。它使用原生 SQL:INSERT ... ON DUPLICATE KEY UPDATE,通常比 upsertMany 的逐条 upsert() 快得多。
typescript
// 单字段唯一键
await accountRepo.bulkUpsert(data, 'externalId')
// 复合唯一键
await accountRepo.bulkUpsert(data, ['userId', 'platform'])
// 自定义更新字段
await accountRepo.bulkUpsert(
data,
'externalId',
(item) => ({ name: item.name, updatedAt: new Date() }),
{ batchSize: 1000 }
)使用限制
- 仅适用于 MySQL / MariaDB
- 走的是原生 SQL,不会触发 Prisma middlewares / extensions
- 审计字段自动注入等 Prisma 层逻辑不会自动执行,需要你手动传值
- 若模型存在
createdAt/updatedAt字段且未显式传值,框架会自动补当前时间 getUpdate()返回的字段也会写入 INSERT 数据,因此要确保这些字段在“新建记录”时同样可接受
findInBatches — 大表分批遍历
使用游标翻页(cursor-based)而非 offset,每批定位复杂度 O(1),适合全表遍历、数据迁移、批量导出等场景。不会一次性将全表加载进内存。
与 offset 分页的区别
findPage 使用 skip: N * size,在大表的深页会做全表扫描(O(N))。findInBatches 使用游标定位,每批都是 O(1) 索引跳转,百万行性能恒定。
typescript
// 全表处理(每批默认 200 条)
const { count } = await userRepo.findInBatches(
async (batch) => { await sendNotifications(batch) }
)
console.log(`处理完成: ${count} 条`)
// 带条件过滤 + 自定义批次大小
await userRepo.findInBatches(
async (batch) => {
await Promise.all(batch.map(item => this.migrate(item)))
},
{ status: 0 },
{ batchSize: 500 }
)cursorField 必须是唯一索引字段(默认 'id'),且排序必须包含该字段(默认按 cursorField 升序)。
自定义配置:
typescript
super(prisma, 'log', {
softDelete: false, // 不启用 Repository 层软删除(推荐由中间件处理)
defaultOrderBy: { timestamp: 'desc' },
softDeleteField: 'removedAt' // 自定义软删除字段名(仅 softDelete: true 时生效)
})Prisma → Zod 辅助工具
从 Prisma DMMF 读取字段信息,生成 Zod schema 建议代码:
typescript
import { getModelFields, generateZodSuggestions, listPrismaModels } from '@maxtan/nest-core'
// 列出所有模型
listPrismaModels(prisma)
// => ['User', 'Post', 'Comment']
// 获取字段信息
const fields = getModelFields(prisma, 'User')
fields.forEach(f => console.log(f.name, f.zodSuggestion, f.dbType))
// 生成完整建议代码
console.log(generateZodSuggestions(prisma, 'User'))更多详情参见 分层最佳实践。
树形结构构建
将 parentId 扁平列表转换为嵌套树,适用于菜单、组织架构、分类等场景:
typescript
import { buildTree, flattenTree, findTreeNode, getTreePath } from '@maxtan/nest-core'
// 1. 基础用法 — 查询后构建树
const menus = await prisma.menu.findMany({
where: { status: 1 },
orderBy: { sort: 'asc' }
})
const tree = buildTree(menus)
// 2. 自定义字段名和根值
const deptTree = buildTree(departments, {
idField: 'deptId',
parentField: 'pid',
rootValue: '0' // parentId 为 '0' 视为根节点
})
// 3. 带排序(子节点也会递归排序)
const sorted = buildTree(categories, {
sortBy: (a, b) => a.sort - b.sort
})辅助函数:
| 函数 | 说明 |
|---|---|
buildTree(items, options?) | 扁平 → 树(O(n),Map 单遍构建) |
flattenTree(tree) | 树 → 扁平(附加 _depth 深度信息) |
findTreeNode(tree, predicate) | 深度优先查找节点 |
getTreePath(tree, predicate) | 获取祖先路径(面包屑导航) |
typescript
// 面包屑导航
const path = getTreePath(tree, n => n.id === currentId)
const breadcrumbs = path.map(n => n.name) // ['系统管理', '用户管理', '用户列表']
// 扁平导出(带缩进)
const flat = flattenTree(tree)
flat.forEach(item => console.log(' '.repeat(item._depth) + item.name))字段选择工具
基于 DMMF 元数据动态生成 Prisma select 对象,避免手动列举所有字段:
prismaExclude — 排除字段
typescript
import { prismaExclude } from '@maxtan/nest-core'
// 排除密码和软删除字段
const users = await prisma.user.findMany({
select: prismaExclude(prisma, 'User', ['password', 'deletedAt'])
})
// => select: { id: true, username: true, nickname: true, email: true, ... }prismaSelect — 精确选择
typescript
import { prismaSelect } from '@maxtan/nest-core'
// 只选择指定字段
const users = await prisma.user.findMany({
select: prismaSelect(prisma, 'User', ['id', 'username', 'nickname'])
})
// strict 模式:字段名错误时抛异常(开发环境推荐)
prismaSelect(prisma, 'User', ['id', 'typo'], { strict: true })
// => Error: Field "typo" not found in scalar fields of model "User"prismaExcludeSets — 预定义字段集
typescript
import { prismaExcludeSets, PrismaFieldSets } from '@maxtan/nest-core'
// 排除审计字段 + 敏感字段
const select = prismaExcludeSets(prisma, 'User', ['AUDIT', 'SENSITIVE'])
// 排除审计字段 + 额外自定义字段
const select2 = prismaExcludeSets(prisma, 'User', ['AUDIT'], ['internalNote'])预定义集合(PrismaFieldSets):
| 集合名 | 包含字段 |
|---|---|
AUDIT | createdAt, createdBy, updatedAt, updatedBy, deletedAt |
SOFT_DELETE | deletedAt |
SENSITIVE | password, salt, secret, token, refreshToken |
提示:
prismaExclude/prismaSelect依赖 DMMF 元数据,需传入原始 PrismaClient 实例(rawClient,非 tx 代理)。在 DAO 中建议初始化时缓存 select 对象。
完整 Service 示例(使用 DAO)
typescript
import { Injectable } from '@nestjs/common'
import { AppException, MSG } from '@maxtan/nest-core'
import { UserDao } from './user.dao'
@Injectable()
export class UserService {
constructor(private readonly dao: UserDao) {}
async list(dto: ListUser) {
return this.dao.findPage({
where: { status: dto.status },
current: dto.current,
size: dto.size
})
}
async detail(id: string) {
const user = await this.dao.findById(id)
if (!user) throw new AppException(MSG.NOT_FOUND_DATA)
return user
}
async remove(ids: string[]) {
return this.dao.removeByIds(ids)
}
}相关文档
- 事务管理 —
@PrismaTransactional声明式事务、多数据源事务 - 验证管道 (Zod) — Zod 预处理器与 Prisma 对齐
- 健康检查 — Prisma 健康检测
- 最佳实践 — Controller → Service → DAO 全链路
- FAQ — 软删除不生效、多数据源等常见问题