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) => { const parts = relation.split('.'); let currentAlias = 'entity'; parts.forEach((part, index) => { // 生成一致的别名:对于嵌套关联,使用下划线连接路径 const newAlias = index === 0 ? part : parts.slice(0, index + 1).join('_'); query.leftJoinAndSelect(`${currentAlias}.${part}`, newAlias); currentAlias = newAlias; }); }); } // 关键词搜索 - 支持关联字段搜索(格式:relation.field 或 relation.nestedRelation.field) if (keyword && searchFields && searchFields.length > 0) { const searchConditions: string[] = []; const searchParams: Record = { keyword: `%${keyword}%` }; searchFields.forEach((field) => { // 检查是否为关联字段(包含点号) if (field.includes('.')) { const parts = field.split('.'); const alias = parts.slice(0, -1).join('_'); // 使用下划线连接关系路径作为别名 const fieldName = parts[parts.length - 1]; searchConditions.push(`${alias}.${fieldName} LIKE :keyword`); } else { // 普通字段搜索 searchConditions.push(`entity.${field} LIKE :keyword`); } }); if (searchConditions.length > 0) { query.andWhere(`(${searchConditions.join(' OR ')})`, searchParams); } } // 条件查询 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; // 检查是否为关联字段(包含点号) let tableAlias = 'entity'; let actualFieldName = fieldName; if (fieldName.includes('.')) { const parts = fieldName.split('.'); tableAlias = parts.slice(0, -1).join('_'); // 使用下划线连接关系路径作为别名 actualFieldName = parts[parts.length - 1]; } // 支持不同类型的筛选 if (Array.isArray(value)) { // 数组类型:IN查询 if (value.length > 0) { query.andWhere(`${tableAlias}.${actualFieldName} IN (:...${key})`, { [key]: value }); } } else if (typeof value === 'string' && value.includes('%')) { // 模糊匹配 query.andWhere(`${tableAlias}.${actualFieldName} LIKE :${key}`, { [key]: value }); } else if (typeof value === 'object' && value !== null) { // 范围查询 if ('gte' in value) { query.andWhere(`${tableAlias}.${actualFieldName} >= :${key}_gte`, { [`${key}_gte`]: value.gte }); } if ('gt' in value) { query.andWhere(`${tableAlias}.${actualFieldName} > :${key}_gt`, { [`${key}_gt`]: value.gt }); } if ('lte' in value) { query.andWhere(`${tableAlias}.${actualFieldName} <= :${key}_lte`, { [`${key}_lte`]: value.lte }); } if ('lt' in value) { query.andWhere(`${tableAlias}.${actualFieldName} < :${key}_lt`, { [`${key}_lt`]: value.lt }); } if ('between' in value && Array.isArray(value.between) && value.between.length === 2) { query.andWhere(`${tableAlias}.${actualFieldName} BETWEEN :${key}_start AND :${key}_end`, { [`${key}_start`]: value.between[0], [`${key}_end`]: value.between[1] }); } } else { // 精确匹配 query.andWhere(`${tableAlias}.${actualFieldName} = :${key}`, { [key]: value }); } } }); } // 排序 Object.entries(order).forEach(([key, direction]) => { query.orderBy(`entity.${key}`, direction); }); const finalQuery = query.skip(skip).take(pageSize); // console.log(finalQuery.getSql()) return finalQuery.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[createdByField] = userId; } // 设置更新人 if (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; readOnly?: boolean; };