import { DataSource, Repository, ObjectLiteral, DeepPartial, In } from 'typeorm'; import { z } from '@hono/zod-openapi'; export abstract class GenericCrudService { protected repository: Repository; private userTrackingOptions?: UserTrackingOptions; protected relationFields?: RelationFieldOptions; constructor( protected dataSource: DataSource, protected entity: new () => T, options?: { userTracking?: UserTrackingOptions; relationFields?: RelationFieldOptions; } ) { this.repository = this.dataSource.getRepository(entity); this.userTrackingOptions = options?.userTracking; this.relationFields = options?.relationFields; } /** * 获取分页列表 */ async getList( page: number = 1, pageSize: number = 10, keyword?: string, searchFields?: string[], where?: Partial, relations: string[] = [], order: { [P in keyof T]?: 'ASC' | 'DESC' } = {}, filters?: { [key: string]: any; } ): Promise<[T[], number]> { const skip = (page - 1) * pageSize; const query = this.repository.createQueryBuilder('entity'); // 添加关联关系(支持嵌套关联,如 ['contract.client']) if (relations.length > 0) { relations.forEach((relation, relationIndex) => { const parts = relation.split('.'); let currentAlias = 'entity'; parts.forEach((part, index) => { const newAlias = index === 0 ? part : `${currentAlias}_${relationIndex}`; query.leftJoinAndSelect(`${currentAlias}.${part}`, newAlias); currentAlias = newAlias; }); }); } // 关键词搜索 if (keyword && searchFields && searchFields.length > 0) { query.andWhere(searchFields.map(field => `entity.${field} LIKE :keyword`).join(' OR '), { keyword: `%${keyword}%` }); } // 条件查询 if (where) { Object.entries(where).forEach(([key, value]) => { if (value !== undefined && value !== null) { query.andWhere(`entity.${key} = :${key}`, { [key]: value }); } }); } // 扩展筛选条件 if (filters) { Object.entries(filters).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== '') { const fieldName = key.startsWith('_') ? key.substring(1) : key; // 支持不同类型的筛选 if (Array.isArray(value)) { // 数组类型:IN查询 if (value.length > 0) { query.andWhere(`entity.${fieldName} IN (:...${key})`, { [key]: value }); } } else if (typeof value === 'string' && value.includes('%')) { // 模糊匹配 query.andWhere(`entity.${fieldName} LIKE :${key}`, { [key]: value }); } else if (typeof value === 'object' && value !== null) { // 范围查询 if ('gte' in value) { query.andWhere(`entity.${fieldName} >= :${key}_gte`, { [`${key}_gte`]: value.gte }); } if ('gt' in value) { query.andWhere(`entity.${fieldName} > :${key}_gt`, { [`${key}_gt`]: value.gt }); } if ('lte' in value) { query.andWhere(`entity.${fieldName} <= :${key}_lte`, { [`${key}_lte`]: value.lte }); } if ('lt' in value) { query.andWhere(`entity.${fieldName} < :${key}_lt`, { [`${key}_lt`]: value.lt }); } if ('between' in value && Array.isArray(value.between) && value.between.length === 2) { query.andWhere(`entity.${fieldName} BETWEEN :${key}_start AND :${key}_end`, { [`${key}_start`]: value.between[0], [`${key}_end`]: value.between[1] }); } } else { // 精确匹配 query.andWhere(`entity.${fieldName} = :${key}`, { [key]: value }); } } }); } // 排序 Object.entries(order).forEach(([key, direction]) => { query.orderBy(`entity.${key}`, direction); }); return query.skip(skip).take(pageSize).getManyAndCount(); } /** * 根据ID获取单个实体 */ async getById(id: number, relations: string[] = []): Promise { return this.repository.findOne({ where: { id } as any, relations }); } /** * 设置用户跟踪字段 */ private setUserFields(data: any, userId?: string | number, isCreate: boolean = true): void { if (!this.userTrackingOptions || !userId) { return; } const { createdByField = 'createdBy', updatedByField = 'updatedBy', userIdField = 'userId' } = this.userTrackingOptions; // 设置创建人和更新人 if (isCreate && createdByField && data.hasOwnProperty(createdByField)) { data[createdByField] = userId; } if (updatedByField && data.hasOwnProperty(updatedByField)) { data[updatedByField] = userId; } // 设置关联的用户ID(如userId字段) if (isCreate && userIdField) { data[userIdField] = userId; } } /** * 创建实体 */ /** * 处理关联字段 */ private async handleRelationFields(data: any, entity: T, isUpdate: boolean = false): Promise { if (!this.relationFields) return; for (const [fieldName, config] of Object.entries(this.relationFields)) { if (data[fieldName] !== undefined) { const ids = data[fieldName]; const relationRepository = this.dataSource.getRepository(config.targetEntity); if (ids && Array.isArray(ids) && ids.length > 0) { const relatedEntities = await relationRepository.findBy({ id: In(ids) }); (entity as any)[config.relationName] = relatedEntities; } else { (entity as any)[config.relationName] = []; } // 清理原始数据中的关联字段 delete data[fieldName]; } } } /** * 创建实体 */ async create(data: DeepPartial, userId?: string | number): Promise { const entityData = { ...data }; this.setUserFields(entityData, userId, true); // 分离关联字段数据 const relationData: any = {}; if (this.relationFields) { for (const fieldName of Object.keys(this.relationFields)) { if (fieldName in entityData) { relationData[fieldName] = (entityData as any)[fieldName]; delete (entityData as any)[fieldName]; } } } const entity = this.repository.create(entityData as DeepPartial); // 处理关联字段 await this.handleRelationFields(relationData, entity); return this.repository.save(entity); } /** * 更新实体 */ async update(id: number, data: Partial, userId?: string | number): Promise { const updateData = { ...data }; this.setUserFields(updateData, userId, false); // 分离关联字段数据 const relationData: any = {}; if (this.relationFields) { for (const fieldName of Object.keys(this.relationFields)) { if (fieldName in updateData) { relationData[fieldName] = (updateData as any)[fieldName]; delete (updateData as any)[fieldName]; } } } // 先更新基础字段 await this.repository.update(id, updateData); // 获取完整实体并处理关联字段 const entity = await this.getById(id); if (!entity) return null; // 处理关联字段 await this.handleRelationFields(relationData, entity, true); return this.repository.save(entity); } /** * 删除实体 */ async delete(id: number): Promise { const result = await this.repository.delete(id); return result.affected === 1; } /** * 高级查询方法 */ createQueryBuilder(alias: string = 'entity') { return this.repository.createQueryBuilder(alias); } } export interface UserTrackingOptions { createdByField?: string; updatedByField?: string; userIdField?: string; } export interface RelationFieldOptions { [fieldName: string]: { relationName: string; targetEntity: new () => any; joinTableName?: string; }; } export type CrudOptions< T extends ObjectLiteral, CreateSchema extends z.ZodSchema = z.ZodSchema, UpdateSchema extends z.ZodSchema = z.ZodSchema, GetSchema extends z.ZodSchema = z.ZodSchema, ListSchema extends z.ZodSchema = z.ZodSchema > = { entity: new () => T; createSchema: CreateSchema; updateSchema: UpdateSchema; getSchema: GetSchema; listSchema: ListSchema; searchFields?: string[]; relations?: string[]; middleware?: any[]; userTracking?: UserTrackingOptions; relationFields?: RelationFieldOptions; };