# 通用 CRUD 规范

## 版本信息
| 版本 | 日期 | 描述 | 作者 |
|------|------|------|------|
| 1.0 | 2025-10-15 | 创建通用 CRUD 规范文档 | Winston |

## 概述

通用 CRUD（Create, Read, Update, Delete）系统是本项目的核心基础设施，提供类型安全、可扩展的数据操作服务。基于 TypeORM 和 Hono 框架构建，支持自动生成 OpenAPI 文档、关联查询、用户跟踪等高级功能。

## 设计原则

### 核心原则
- **类型安全**: 基于 TypeScript 的完整类型支持
- **可扩展性**: 支持自定义扩展和业务逻辑注入
- **一致性**: 统一的 API 设计和错误处理
- **性能优化**: 支持分页、缓存、关联查询优化
- **安全性**: 内置用户跟踪和权限控制

### 架构模式
- **服务层**: `GenericCrudService` 提供核心 CRUD 操作
- **路由层**: `createCrudRoutes` 自动生成 RESTful API 路由
- **实体层**: TypeORM 实体定义数据模型
- **验证层**: Zod schema 提供输入验证

## 核心组件

### 1. GenericCrudService

**位置**: `src/server/utils/generic-crud.service.ts`

**核心功能**:
- 分页列表查询（支持搜索、排序、复杂筛选）
- 单个实体查询（支持关联关系）
- 创建、更新、删除操作
- 用户跟踪（创建人、更新人）
- 关联字段处理

**主要方法**:
```typescript
// 分页查询
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]>

// 根据ID查询
async getById(id: number, relations: string[] = []): Promise<T | null>

// 创建实体
async create(data: DeepPartial<T>, userId?: string | number): Promise<T>

// 更新实体
async update(id: number, data: Partial<T>, userId?: string | number): Promise<T | null>

// 删除实体
async delete(id: number): Promise<boolean>
```

### 2. ConcreteCrudService

**位置**: `src/server/utils/concrete-crud.service.ts`

**用途**: 简化 `GenericCrudService` 的实例化，自动注入数据源。

**使用示例**:
```typescript
const userService = new ConcreteCrudService(User, {
  userTracking: { createdByField: 'createdBy', updatedByField: 'updatedBy' }
});
```

### 3. createCrudRoutes

**位置**: `src/server/utils/generic-crud.routes.ts`

**功能**: 自动生成完整的 CRUD 路由，包括：
- `GET /` - 分页列表
- `GET /{id}` - 单个实体详情
- `POST /` - 创建实体
- `PUT /{id}` - 更新实体
- `DELETE /{id}` - 删除实体

**支持特性**:
- 自动 OpenAPI 文档生成
- 输入验证（Zod schema）
- 错误处理统一
- 中间件支持
- 只读模式

## 使用指南

### 1. 创建实体类

```typescript
// user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  username: string;

  @Column({ nullable: true })
  email: string | null;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}
```

### 2. 创建 Zod Schema

```typescript
// user.schema.ts
import { z } from '@hono/zod-openapi';

export const UserCreateSchema = z.object({
  username: z.string().min(3).max(50),
  email: z.string().email().optional().nullable(),
});

export const UserUpdateSchema = UserCreateSchema.partial();

export const UserGetSchema = z.object({
  id: z.number(),
  username: z.string(),
  email: z.string().email().nullable(),
  createdAt: z.string().datetime(),
  updatedAt: z.string().datetime(),
});

export const UserListSchema = UserGetSchema;
```

### 3. 注册 CRUD 路由

```typescript
// api/users/index.ts
import { createCrudRoutes } from '../../../utils/generic-crud.routes';
import { User } from '../../../modules/users/user.entity';
import {
  UserCreateSchema,
  UserUpdateSchema,
  UserGetSchema,
  UserListSchema
} from '../../../modules/users/user.schema';

export const userRoutes = createCrudRoutes({
  entity: User,
  createSchema: UserCreateSchema,
  updateSchema: UserUpdateSchema,
  getSchema: UserGetSchema,
  listSchema: UserListSchema,
  searchFields: ['username', 'email'],
  relations: ['roles'],
  middleware: [authMiddleware],
  userTracking: {
    createdByField: 'createdBy',
    updatedByField: 'updatedBy'
  }
});
```

### 4. 在 API 中注册路由

```typescript
// api/index.ts
import { userRoutes } from './users';

const app = new OpenAPIHono();
app.route('/users', userRoutes);
```

## 高级功能

### 关联字段处理

支持多对多、一对多等关联关系的自动处理：

```typescript
const roleRoutes = createCrudRoutes({
  entity: Role,
  // ... schemas
  relationFields: {
    permissions: {
      relationName: 'permissions',
      targetEntity: Permission
    }
  }
});
```

### 复杂筛选

支持多种筛选方式：

```typescript
// 精确匹配
filters: '{"status": 1}'

// 范围查询
filters: '{"createdAt": {"gte": "2024-01-01", "lte": "2024-12-31"}}'

// IN 查询
filters: '{"status": [1, 2, 3]}'

// 模糊匹配
filters: '{"name": "%keyword%"}'
```

### 关联字段搜索

支持嵌套关联字段的搜索：

```typescript
// 搜索用户关联的角色名称
searchFields: ['username', 'roles.name']

// 搜索嵌套关联
searchFields: ['contract.client.name']
```

## 最佳实践

### 1. 实体设计
- 所有实体必须继承 `ObjectLiteral`
- 使用 TypeORM 装饰器定义字段
- 包含 `createdAt` 和 `updatedAt` 时间戳
- 考虑软删除需求

### 2. Schema 设计
- 创建和更新使用不同的 schema
- 响应 schema 应包含完整字段
- 使用 `.optional()` 和 `.nullable()` 明确字段可选性

### 3. 性能优化
- 合理设置分页大小（默认10条）
- 为常用查询字段添加索引
- 使用关联查询时注意 N+1 问题
- 考虑缓存策略

### 4. 安全性
- 使用用户跟踪记录操作人
- 实现适当的权限控制
- 验证所有输入数据
- 避免敏感信息暴露

## 扩展和自定义

### 自定义业务逻辑

```typescript
class CustomUserService extends ConcreteCrudService<User> {
  async createWithValidation(data: UserCreateDto): Promise<User> {
    // 自定义验证逻辑
    await this.validateUniqueUsername(data.username);

    // 调用父类方法
    return super.create(data);
  }

  private async validateUniqueUsername(username: string): Promise<void> {
    const existing = await this.repository.findOne({ where: { username } });
    if (existing) {
      throw new Error('用户名已存在');
    }
  }
}
```

### 自定义路由

```typescript
const customRoutes = new OpenAPIHono();

// 使用通用 CRUD 路由
customRoutes.route('/', createCrudRoutes(crudOptions));

// 添加自定义路由
customRoutes.post('/bulk-create', async (c) => {
  // 自定义批量创建逻辑
});
```

## 常见问题解答

### Q: 如何处理复杂的业务逻辑？
A: 继承 `ConcreteCrudService` 并添加自定义方法，或创建独立的业务服务类。

### Q: 如何实现软删除？
A: 在实体中添加 `deletedAt` 字段，重写 `delete` 方法实现软删除逻辑。

### Q: 如何优化关联查询性能？
A: 使用 `relations` 参数指定需要的关联关系，避免不必要的关联加载。

### Q: 如何处理文件上传等非标准操作？
A: 创建独立的文件管理服务，或扩展 CRUD 服务添加文件处理逻辑。

## 相关文档

- [主架构文档](../architecture.md)
- [API 设计规范](./api-design-integration.md)
- [数据模型规范](./data-model-schema-changes.md)
- [测试策略](./testing-strategy.md)

---

**文档状态**: 正式版
**下次评审**: 2025-11-15