外观
FAQ / 常见问题
安装 & 配置
Prisma 密码含特殊字符连接失败
DATABASE_URL 中如果密码包含 #、@、% 等特殊字符,需要进行 URL 编码:
| 字符 | 编码 |
|---|---|
# | %23 |
@ | %40 |
% | %25 |
: | %3A |
bash
# 错误 ❌
DATABASE_URL="mysql://root:pass#123@localhost:3306/db"
# 正确 ✅
DATABASE_URL="mysql://root:pass%23123@localhost:3306/db"PowerShell 执行 pnpm 报错 "cannot be loaded because running scripts is disabled"
PowerShell 默认限制脚本执行策略,有两种解决方案:
powershell
# 方案 1:临时修改策略(当前用户)
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
# 方案 2:使用 cmd 代替
cmd /c "pnpm run dev"如何同时使用多个数据源?
每个数据源需要一个独立的 PrismaService 子类:
typescript
// 主库
@Injectable()
class MainPrismaService extends PrismaBaseService<PrismaClient> { ... }
// 从库
@Injectable()
class ReadPrismaService extends PrismaBaseService<PrismaClient> { ... }
@Module({
imports: [
PrismaModule.forRoot({ service: MainPrismaService, token: 'MAIN_DB' }),
PrismaModule.forRoot({ service: ReadPrismaService, token: 'READ_DB', global: false }),
],
})
export class AppModule {}每个 Service 独立持有扩展配置,互不干扰。详见 Prisma 多数据源。
多数据源时 @PrismaTransactional 报错找不到 PrismaClient?
默认 prismaField 为 'prisma',多数据源时属性名通常不同(如 primaryDb),必须显式指定:
typescript
// ❌ 默认找 this.prisma,找不到
@PrismaTransactional()
async create() { ... }
// ✅ 指定正确的属性名
@PrismaTransactional({ prismaField: 'primaryDb' })
async create() { ... }多数据源时能否在一个事务里操作两个库?
不能。@PrismaTransactional 只支持单个 PrismaClient 实例的交互式事务,不支持跨数据源的分布式事务。跨库操作需要:
- 方案 1:分开事务 — 主要操作用事务保证原子性,次要操作(如日志)允许降级
- 方案 2:Saga 补偿模式 — 每步操作记录状态,失败时执行逆向补偿
- 方案 3:最终一致性 — 通过消息队列异步同步
详见 多数据源事务。
事务内调用 $transaction() 报错?
这是框架的安全防护机制。在 @PrismaTransactional 事务内调用 $transaction() 会创建独立事务, 导致原子性丢失、死锁风险和连接池耗尽。正确做法:
typescript
// ❌ 事务内禁止
const [records, total] = await this.prisma.$transaction([
this.prisma.user.findMany({ where }),
this.prisma.user.count({ where })
])
// ✅ 改用 Promise.all
const [records, total] = await Promise.all([
this.prisma.user.findMany({ where }),
this.prisma.user.count({ where })
])事务超时了怎么办?
默认超时 30s(timeout: 30000),如果业务逻辑复杂可适当调大:
typescript
@PrismaTransactional({ timeout: 60000 }) // 60 秒
async complexOperation() { ... }但更推荐拆分大事务,保持事务尽可能小:
- 不要在事务中做 HTTP 调用或重计算
- 批量操作考虑分批处理
- 只把必须保证原子性的操作放在事务内
Zod 验证
前端传字符串 "1",后端收到的类型是什么?
框架的 Zod 预处理器(如 zInt、zNum、zBool)会自动将 query-string 中的字符串转为对应类型:
typescript
// 请求: GET /users?status=1&active=true&page=2
// 经过 zInt / zBool 预处理后:
// status → number 1
// active → boolean true
// page → number 2zVarChar、zStr 和字符串 helper 怎么选?
| 预设 | 必填 | 长度约束 | 典型场景 |
|---|---|---|---|
zStr | 选填 | 无 | 搜索关键词、普通可选文本 |
zStrReq | 必填 | 无 | 无长度限制的必填字段 |
zStrNull | 可空 | 无 | 前端空串表示“清空为 null” |
zVarChar(n) | 必填 | max=n | 对齐 @db.VarChar(n) |
zVarCharNull(n) | 可空 | max=n | 对齐 String? @db.VarChar(n) 且空串→null |
zVarCharOptional(n) | 选填 | max=n | 兼容旧 API,推荐新代码优先用语义更明确的 helper |
快速判断:
- 空串应该视为“没传”:用
zStr - 空串应该视为“清空为 null”:用
zStrNull或zVarCharNull - 字段要对齐 Prisma
@db.VarChar(n):优先zVarChar/zVarCharNull - 不想再让业务代码里出现
z.string().nullable().optional():直接换成新的 nullable helper
如何自定义 Zod 验证错误消息?
框架内置 formatIssue 会自动翻译为中文,但你也可以在 schema 中指定:
typescript
const username = zVarChar(50, 2)
// 自带: "最多 50 个字符" / "至少 2 个字符"
// 自定义消息
const email = zStr.pipe(
z.string().email({ message: '请输入有效的邮箱地址' })
)Prisma
PrismaBaseService 和 PrismaRepository 有什么区别?
| 类 | 职责 | 继承方式 |
|---|---|---|
PrismaBaseService<C> | 连接管理 — 生命周期、扩展注册、健康检查 | 每个数据源一个子类 |
PrismaRepository<T> | CRUD 操作 — 通用增删改查方法、分页 | 每个业务 Model 一个子类 |
typescript
// PrismaService:管连接
class PrismaService extends PrismaBaseService<PrismaClient> { ... }
// UserRepository:管业务
class UserRepository extends PrismaRepository<User> {
constructor(prisma: PrismaService) {
super(prisma, 'user')
}
}软删除扩展不生效?
检查以下几点:
- 模型必须有
deletedAt DateTime?字段 PrismaModule.forRoot的middlewares.softDelete.enabled必须为true- 扩展注册在
$connect()之前,确保setClient()在构造函数中调用 models设置为'auto'时,会从 DMMF 自动发现,确保prisma generate已执行
@PrismaTransactional 的 prismaField 有什么用?
当你的 Service 中 PrismaClient 的属性名不是 prisma 时,需要通过 prismaField 指定:
typescript
@Injectable()
class OrderService {
// 属性名不是 prisma,而是 db
constructor(private readonly db: PrismaClient) {}
@PrismaTransactional({ prismaField: 'db' })
async createOrder(dto: CreateOrder) {
const client = usePrismaClient(this.db)
// ...
}
}缓存
Redis 断连后会发生什么?
行为取决于 strict 模式配置:
| 模式 | Redis 断连时的行为 |
|---|---|
strict: false(默认) | 抛出 CacheNotReadyException,调用方可 try/catch 降级处理 |
strict: true | 直接抛出异常,请求返回 500 |
CacheService 内置自动重连机制(最多 10 次),健康检查间隔自动检测并恢复连接。
如何在 Service 中安全使用缓存?
typescript
@Injectable()
class UserService {
constructor(private readonly cache: CacheService) {}
async getUser(id: string) {
try {
const cached = await this.cache.get(`user:${id}`)
if (cached) return JSON.parse(cached)
} catch (e) {
// Redis 不可用时变降级:走数据库查询
}
return this.prisma.user.findUnique({ where: { id } })
}
}日志
createLogger() 和 LoggerModule 该用哪个?
| 方式 | 场景 | 优势 |
|---|---|---|
createLogger() + import { logger } | 工具函数、非 DI 上下文 | 简单直接 |
LoggerModule.forRoot() + DI 注入 | Service / Controller | 可测试、可替换 |
两者可以同时使用。LoggerModule 在 onModuleInit 时会自动调用 createLogger() 初始化全局 logger,所以使用 LoggerModule 后不需要再手动调用 createLogger()。
健康检查
如何在 Kubernetes 中配置探针?
yaml
# deployment.yaml
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10健康检查返回格式是什么?
json
{
"status": "ok",
"components": {
"prisma": { "status": "up", "duration": 3 },
"redis": { "status": "up", "duration": 1 }
},
"duration": 5
}status 为 "error" 时,对应组件的 status 为 "down" 并包含 message 字段。