253 lines
5.4 KiB
TypeScript
253 lines
5.4 KiB
TypeScript
/**
|
|
* 通用 CRUD 服务基类
|
|
* Generic CRUD Service Base Class
|
|
*
|
|
* 提供标准的 CRUD 操作,支持分页、搜索、排序等功能
|
|
* Provides standard CRUD operations with pagination, search, and sorting
|
|
*/
|
|
|
|
import { PrismaClient } from '@prisma/client'
|
|
import type { BaseEntity, PaginatedResponse, CrudOptions } from '@nuxt4crud/shared'
|
|
|
|
/**
|
|
* CRUD 服务基类
|
|
* CRUD Service Base Class
|
|
*/
|
|
export abstract class BaseCrudService<
|
|
T extends BaseEntity,
|
|
CreateInput,
|
|
UpdateInput,
|
|
QueryParams = any,
|
|
> {
|
|
protected model: any
|
|
protected options: CrudOptions
|
|
|
|
constructor(
|
|
protected prisma: PrismaClient,
|
|
protected modelName: string,
|
|
options: Partial<CrudOptions> = {}
|
|
) {
|
|
this.model = (prisma as any)[modelName]
|
|
this.options = {
|
|
softDelete: false,
|
|
defaultOrderBy: 'createdAt',
|
|
defaultOrderDirection: 'desc',
|
|
searchableFields: [],
|
|
uniqueFields: [],
|
|
...options,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取列表(支持分页、搜索、排序)
|
|
* Get list with pagination, search, and sorting
|
|
*/
|
|
async findMany(
|
|
params: QueryParams & {
|
|
page?: number
|
|
limit?: number
|
|
search?: string
|
|
orderBy?: string
|
|
orderDirection?: 'asc' | 'desc'
|
|
}
|
|
): Promise<PaginatedResponse<T>> {
|
|
const {
|
|
page = 1,
|
|
limit = 10,
|
|
search = '',
|
|
orderBy = this.options.defaultOrderBy,
|
|
orderDirection = this.options.defaultOrderDirection,
|
|
...filters
|
|
} = params
|
|
|
|
const where = this.buildWhereClause(search, filters)
|
|
|
|
const skip = (page - 1) * limit
|
|
const take = limit
|
|
|
|
const [data, total] = await Promise.all([
|
|
this.model.findMany({
|
|
where,
|
|
skip,
|
|
take,
|
|
orderBy: { [orderBy]: orderDirection },
|
|
}),
|
|
this.model.count({ where }),
|
|
])
|
|
|
|
return {
|
|
data,
|
|
total,
|
|
page,
|
|
limit,
|
|
totalPages: Math.ceil(total / limit),
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 根据ID查找
|
|
* Find by ID
|
|
*/
|
|
async findById(id: number): Promise<T | null> {
|
|
return await this.model.findUnique({
|
|
where: { id },
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 根据多个ID查找记录
|
|
* Find records by multiple IDs
|
|
*
|
|
* @param ids 要查询的ID数组
|
|
* @returns 匹配的记录列表
|
|
*/
|
|
async findByIds(ids: number[]): Promise<T[]> {
|
|
const normalizedIds = Array.from(
|
|
new Set(
|
|
(ids || [])
|
|
.map(id => Number(id))
|
|
.filter(id => Number.isFinite(id) && id > 0)
|
|
)
|
|
)
|
|
|
|
if (normalizedIds.length === 0) {
|
|
return []
|
|
}
|
|
|
|
return await this.model.findMany({
|
|
where: { id: { in: normalizedIds } },
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 创建记录
|
|
* Create record
|
|
*/
|
|
async create(data: CreateInput): Promise<T> {
|
|
await this.validateCreate(data)
|
|
|
|
const transformedData = await this.transformCreateData(data)
|
|
|
|
return await this.model.create({
|
|
data: transformedData,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 更新记录
|
|
* Update record
|
|
*/
|
|
async update(id: number, data: UpdateInput): Promise<T> {
|
|
const existing = await this.findById(id)
|
|
if (!existing) {
|
|
const error = new Error('记录不存在')
|
|
;(error as any).statusCode = 404
|
|
throw error
|
|
}
|
|
|
|
await this.validateUpdate(id, data, existing)
|
|
|
|
const transformedData = await this.transformUpdateData(data, existing)
|
|
|
|
return await this.model.update({
|
|
where: { id },
|
|
data: transformedData,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 删除记录
|
|
* Delete record
|
|
*/
|
|
async delete(id: number): Promise<void> {
|
|
const existing = await this.findById(id)
|
|
if (!existing) {
|
|
const error = new Error('记录不存在')
|
|
;(error as any).statusCode = 404
|
|
throw error
|
|
}
|
|
|
|
await this.validateDelete(id, existing)
|
|
|
|
if (this.options.softDelete) {
|
|
await this.model.update({
|
|
where: { id },
|
|
data: { deletedAt: new Date() },
|
|
})
|
|
} else {
|
|
await this.model.delete({
|
|
where: { id },
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 构建查询条件
|
|
* Build where clause
|
|
*/
|
|
protected buildWhereClause(search: string, filters: any): any {
|
|
const where: any = {}
|
|
|
|
// 搜索功能
|
|
if (search && this.options.searchableFields?.length) {
|
|
where.OR = this.options.searchableFields.map(field => ({
|
|
[field]: { contains: search },
|
|
}))
|
|
}
|
|
|
|
// 过滤条件
|
|
Object.entries(filters).forEach(([key, value]) => {
|
|
if (value !== undefined && value !== null && key !== 'search') {
|
|
where[key] = value
|
|
}
|
|
})
|
|
|
|
// 软删除过滤
|
|
if (this.options.softDelete) {
|
|
where.deletedAt = null
|
|
}
|
|
|
|
return where
|
|
}
|
|
|
|
/**
|
|
* 创建前验证钩子
|
|
* Pre-create validation hook
|
|
*/
|
|
protected async validateCreate(data: CreateInput): Promise<void> {
|
|
// 子类可以重写此方法
|
|
}
|
|
|
|
/**
|
|
* 更新前验证钩子
|
|
* Pre-update validation hook
|
|
*/
|
|
protected async validateUpdate(id: number, data: UpdateInput, existing: T): Promise<void> {
|
|
// 子类可以重写此方法
|
|
}
|
|
|
|
/**
|
|
* 删除前验证钩子
|
|
* Pre-delete validation hook
|
|
*/
|
|
protected async validateDelete(id: number, existing: T): Promise<void> {
|
|
// 子类可以重写此方法
|
|
}
|
|
|
|
/**
|
|
* 创建数据转换钩子
|
|
* Create data transformation hook
|
|
*/
|
|
protected async transformCreateData(data: CreateInput): Promise<any> {
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* 更新数据转换钩子
|
|
* Update data transformation hook
|
|
*/
|
|
protected async transformUpdateData(data: UpdateInput, existing: T): Promise<any> {
|
|
return data
|
|
}
|
|
}
|