| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- import { DataSource, Repository, ObjectLiteral, DeepPartial, In } from 'typeorm';
- import { z } from '@hono/zod-openapi';
- export abstract class GenericCrudService<T extends ObjectLiteral> {
- protected repository: Repository<T>;
- 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<T>,
- 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<string, string> = { 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<T | null> {
- 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<void> {
- 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<T>, userId?: string | number): Promise<T> {
- 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<T>);
-
- // 处理关联字段
- await this.handleRelationFields(relationData, entity);
-
- return this.repository.save(entity);
- }
- /**
- * 更新实体
- */
- async update(id: number, data: Partial<T>, userId?: string | number): Promise<T | null> {
- 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<boolean> {
- 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;
- };
|