Skip to content

BaseController 控制器基类使用文档

概述

BaseController 是一个通用的 RESTful API 控制器基类,提供了标准的 CRUD 操作接口。它与 BaseService 配合使用,能够快速构建符合规范的后台管理接口,包含用户身份注入、参数校验、统一响应格式等功能。

基础使用

创建自定义控制器

typescript
import { Controller } from '@nestjs/common'
import { BaseController } from '@/common/BaseController'
import { UserService } from './user.service'
import { SysUser } from './entities/sys-user.entity'

@Controller('api/user')
export class UserController extends BaseController<SysUser, UserService> {
  constructor(private readonly userService: UserService) {
    super(userService)
  }
}

自动生成的接口

继承 BaseController 后会自动获得以下标准接口:

方法路径功能
POST/api/user/save保存(新增/更新)
POST/api/user/add新增
PUT/api/user/update更新
DELETE/api/user/del/:ids删除
GET/api/user/list分页列表查询
GET/api/user/getOne/:id获取单条记录

API 参考

核心接口方法

save(@Body() body, @Req() req)

保存数据接口,根据是否包含 id 决定是新增还是更新。

请求方式: POST
路径: /api/{entity}/save

请求体:

json
{
  "id": "1", // 可选,有id则更新,无id则新增
  "username": "test",
  "email": "test@example.com"
  // 其他字段...
}

响应:

json
{
  "id": "1",
  "username": "test",
  "email": "test@example.com",
  "createTime": "2024-01-01 12:00:00",
  "createUser": "admin",
  "updateTime": "2024-01-01 12:00:00",
  "updateUser": "admin"
}

特点:

  • 自动注入 createUser/updateUser
  • 根据是否有 id 字段决定操作类型
  • 完整的数据验证流程

add(@Body() body, @Req() req)

新增数据接口。

请求方式: POST
路径: /api/{entity}/add

请求体:

json
{
  "username": "newuser",
  "email": "newuser@example.com"
  // 其他必需字段...
}

注意: 请求体中不应包含 id 字段,即使包含也会被忽略。

update(@Body() body, @Req() req)

更新数据接口。

请求方式: PUT
路径: /api/{entity}/update

请求体:

json
{
  "id": "1",
  "username": "updated_username"
  // 其他要更新的字段...
}

注意: 必须包含 id 字段,否则会报错。

del(@Param('ids') ids: string, @Req() req)

删除数据接口(软删除)。

请求方式: DELETE
路径: /api/{entity}/del/:ids

路径参数:

  • ids: string - 要删除的记录ID,多个ID用逗号分隔

示例:

DELETE /api/user/del/1,2,3

list(@Query() query: QueryListDto, @Req() req?)

分页列表查询接口。

请求方式: GET
路径: /api/{entity}/list

查询参数:

typescript
interface QueryListDto {
  pageNum?: number // 当前页码,默认1
  pageSize?: number // 每页条数,默认10
  [key: string]: any // 其他查询条件
}

示例:

GET /api/user/list?pageNum=1&pageSize=20&username=test&status=1

响应:

json
{
  "total": 100,
  "data": [
    {
      "id": "1",
      "username": "testuser",
      "email": "test@example.com"
      // ... 其他字段
    }
  ],
  "_flag": true
}

getOne(@Param('id') id: string)

获取单条记录详情接口。

请求方式: GET
路径: /api/{entity}/getOne/:id

路径参数:

  • id: string - 记录ID

示例:

GET /api/user/getOne/1

完整示例

用户管理控制器

typescript
import { Controller, Get, Post, Body, Query, Param, Req, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { BaseController } from '@/common/BaseController'
import { UserService } from './user.service'
import { SysUser } from './entities/sys-user.entity'
import { QueryListDto } from '@/common/dto'

@Controller('api/user')
@UseGuards(AuthGuard('jwt')) // 添加 JWT 认证
export class UserController extends BaseController<SysUser, UserService> {
  constructor(private readonly userService: UserService) {
    super(userService)
  }

  // 自定义搜索接口
  @Get('search')
  async search(
    @Query()
    query: QueryListDto & {
      keyword?: string
      status?: string
      createTime?: [string, string]
    },
    @Req() req,
  ) {
    // 添加默认分页参数
    query.pageNum = query.pageNum || 1
    query.pageSize = query.pageSize || 10

    // 调用自定义服务方法
    return this.userService.search(query)
  }

  // 用户统计接口
  @Get('stats')
  async getStats(@Req() req) {
    return this.userService.getUserStats()
  }

  // 批量操作接口
  @Post('batch-status')
  async batchUpdateStatus(
    @Body()
    body: {
      ids: string[]
      status: string
    },
    @Req() req,
  ) {
    const { ids, status } = body
    return this.userService.batchUpdateStatus(ids, status, req.user.name)
  }

  // 导出接口
  @Get('export')
  async exportUsers(@Query() query, @Req() req) {
    const result = await this.userService.list(query)
    // 处理导出逻辑
    return {
      data: result.data,
      filename: `users_${new Date().getTime()}.xlsx`,
      totalCount: result.total,
    }
  }

  // 重置密码接口
  @Post('reset-password/:id')
  async resetPassword(@Param('id') id: string, @Req() req) {
    return this.userService.resetPassword(id, req.user.name)
  }
}

对应的服务层实现

typescript
// user.service.ts
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { BaseService } from '@/common/BaseService'
import { SysUser } from './entities/sys-user.entity'
import { QueryListDto } from '@/common/dto'

@Injectable()
export class UserService extends BaseService<SysUser, UserService> {
  constructor(
    @InjectRepository(SysUser)
    private readonly userRepository: Repository<SysUser>,
  ) {
    super(SysUser, userRepository)
  }

  // 自定义搜索方法
  async search(query: any) {
    const queryOrm: any = {
      where: {},
      order: { createTime: 'DESC' },
    }

    // 处理关键字搜索
    if (query.keyword) {
      queryOrm.where = [{ username: this.sqlLike(query.keyword) }, { email: this.sqlLike(query.keyword) }]
    }

    // 处理状态筛选
    if (query.status) {
      queryOrm.where.status = query.status
    }

    // 处理时间范围
    if (query.createTime) {
      queryOrm.where.createTime = this.betweenTime(query.createTime)
    }

    return this.listBy(queryOrm, query)
  }

  // 用户统计
  async getUserStats() {
    const total = await this.repository.count()
    const active = await this.repository.count({ where: { status: '1' } })
    const inactive = total - active

    return {
      total,
      active,
      inactive,
      today: await this.getTodayCount(),
    }
  }

  // 批量更新状态
  async batchUpdateStatus(ids: string[], status: string, updateUser: string) {
    // 权限校验已在 BaseService.del 中处理
    return this.repository.update(ids, { status, updateUser })
  }

  // 重置密码
  async resetPassword(id: string, updateUser: string) {
    const newPassword = this.generateRandomPassword()
    const hashedPassword = await this.hashPassword(newPassword)

    await this.repository.update(id, {
      password: hashedPassword,
      updateUser,
    })

    return { newPassword }
  }

  private generateRandomPassword(): string {
    // 密码生成逻辑
    return Math.random().toString(36).slice(-8)
  }

  private async hashPassword(password: string): Promise<string> {
    // 密码加密逻辑
    return password // 实际应该使用 bcrypt 等加密
  }

  private async getTodayCount(): Promise<number> {
    const today = new Date().toISOString().split('T')[0]
    return this.repository.count({
      where: {
        createTime: this.betweenTime([today, today]),
      },
    })
  }
}

高级用法

文件上传处理

typescript
import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common'
import { FileInterceptor } from '@nestjs/platform-express'
import { BaseController } from '@/common/BaseController'
import { UserService } from './user.service'

@Controller('api/user')
export class UserController extends BaseController<SysUser, UserService> {
  constructor(private readonly userService: UserService) {
    super(userService)
  }

  // 头像上传
  @Post('upload-avatar/:id')
  @UseInterceptors(FileInterceptor('avatar'))
  async uploadAvatar(@Param('id') id: string, @UploadedFile() file: Express.Multer.File, @Req() req) {
    return this.userService.uploadAvatar(id, file, req.user.name)
  }

  // 批量导入
  @Post('import')
  @UseInterceptors(FileInterceptor('file'))
  async importUsers(@UploadedFile() file: Express.Multer.File, @Req() req) {
    return this.userService.importUsers(file, req.user.name)
  }
}

权限控制

typescript
import { Controller, UseGuards, SetMetadata } from '@nestjs/common'
import { RolesGuard } from '@/guards/roles.guard'
import { BaseController } from '@/common/BaseController'

@Controller('api/user')
@UseGuards(RolesGuard)
export class UserController extends BaseController<SysUser, UserService> {
  constructor(private readonly userService: UserService) {
    super(userService)
  }

  // 需要管理员权限的操作
  @Post('save')
  @SetMetadata('roles', ['admin'])
  async save(@Body() body, @Req() req) {
    return super.save(body, req)
  }

  // 需要特定权限的操作
  @Post('reset-password/:id')
  @SetMetadata('permissions', ['user:reset-password'])
  async resetPassword(@Param('id') id: string, @Req() req) {
    return super.resetPassword(id, req)
  }
}

数据验证和转换

typescript
import { Controller, ValidationPipe, UsePipes } from '@nestjs/common'
import { BaseController } from '@/common/BaseController'

@Controller('api/user')
@UsePipes(new ValidationPipe({ transform: true }))
export class UserController extends BaseController<SysUser, UserService> {
  constructor(private readonly userService: UserService) {
    super(userService)
  }

  // 使用 DTO 进行参数验证
  @Post('save')
  async save(@Body() createUserDto: CreateUserDto, @Req() req) {
    return super.save(createUserDto, req)
  }
}

// DTO 定义
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator'

export class CreateUserDto {
  @IsNotEmpty()
  @MinLength(3)
  username: string

  @IsEmail()
  email: string

  @IsNotEmpty()
  @MinLength(6)
  password: string
}

异常处理和日志记录

typescript
import { Controller, Logger, HttpException } from '@nestjs/common'
import { BaseController } from '@/common/BaseController'

@Controller('api/user')
export class UserController extends BaseController<SysUser, UserService> {
  private readonly logger = new Logger(UserController.name)

  constructor(private readonly userService: UserService) {
    super(userService)
  }

  @Post('save')
  async save(@Body() body, @Req() req) {
    try {
      this.logger.log(`用户 ${req.user.name} 正在保存数据`)
      const result = await super.save(body, req)
      this.logger.log(`保存成功,ID: ${result.id}`)
      return result
    } catch (error) {
      this.logger.error(`保存失败: ${error.message}`, error.stack)
      if (error instanceof HttpException) {
        throw error
      }
      throw new HttpException('操作失败', 500)
    }
  }
}

缓存集成

typescript
import { Controller, CacheInterceptor, UseInterceptors, CACHE_KEY_METADATA } from '@nestjs/common'
import { BaseController } from '@/common/BaseController'

@Controller('api/user')
export class UserController extends BaseController<SysUser, UserService> {
  constructor(private readonly userService: UserService) {
    super(userService)
  }

  // 为列表查询添加缓存
  @Get('list')
  @UseInterceptors(CacheInterceptor)
  @SetMetadata(CACHE_KEY_METADATA, 'user_list')
  async list(@Query() query: QueryListDto) {
    return super.list(query)
  }

  // 为详情查询添加缓存
  @Get('getOne/:id')
  @UseInterceptors(CacheInterceptor)
  async getOne(@Param('id') id: string) {
    return super.getOne(id)
  }

  // 清除缓存
  @Post('save')
  async save(@Body() body, @Req() req) {
    // 先执行保存操作
    const result = await super.save(body, req)
    // 然后清除相关缓存
    await this.clearCache()
    return result
  }

  private async clearCache() {
    // 缓存清理逻辑
  }
}

最佳实践

1. 统一响应格式

typescript
import { Controller, Get } from '@nestjs/common'
import { BaseController } from '@/common/BaseController'

@Controller('api/user')
export class UserController extends BaseController<SysUser, UserService> {
  constructor(private readonly userService: UserService) {
    super(userService)
  }

  // 统一包装响应结果
  @Get('list')
  async list(@Query() query) {
    const result = await super.list(query)
    return {
      code: 200,
      message: 'success',
      data: result,
    }
  }
}

2. 参数预处理

typescript
@Controller('api/user')
export class UserController extends BaseController<SysUser, UserService> {
  constructor(private readonly userService: UserService) {
    super(userService)
  }

  @Post('save')
  async save(@Body() body, @Req() req) {
    // 参数预处理
    body = this.preprocessSaveData(body, req)
    return super.save(body, req)
  }

  private preprocessSaveData(body: any, req: any): any {
    // 数据清洗和预处理
    if (body.phone) {
      body.phone = body.phone.replace(/\D/g, '') // 移除非数字字符
    }

    if (body.email) {
      body.email = body.email.toLowerCase() // 统一转小写
    }

    return body
  }
}

3. 分页参数标准化

typescript
@Controller('api/user')
export class UserController extends BaseController<SysUser, UserService> {
  constructor(private readonly userService: UserService) {
    super(userService)
  }

  @Get('list')
  async list(@Query() query) {
    // 标准化分页参数
    query.pageNum = Math.max(1, parseInt(query.pageNum) || 1)
    query.pageSize = Math.min(100, Math.max(1, parseInt(query.pageSize) || 10))

    // 添加默认排序
    query.orderBy = query.orderBy || 'createTime'
    query.orderDirection = query.orderDirection || 'DESC'

    return super.list(query)
  }
}

4. 安全性考虑

typescript
@Controller('api/user')
export class UserController extends BaseController<SysUser, UserService> {
  constructor(private readonly userService: UserService) {
    super(userService)
  }

  @Post('save')
  async save(@Body() body, @Req() req) {
    // 输入验证
    this.validateInput(body)

    // 权限检查
    await this.checkPermission(req.user, body)

    // 数据脱敏
    body = this.sanitizeData(body)

    return super.save(body, req)
  }

  private validateInput(body: any): void {
    // 防止注入攻击
    const dangerousKeys = ['__proto__', 'constructor', 'prototype']
    for (const key of dangerousKeys) {
      if (key in body) {
        throw new HttpException('非法参数', 400)
      }
    }
  }

  private async checkPermission(user: any, data: any): Promise<void> {
    // 实现具体的权限检查逻辑
  }

  private sanitizeData(body: any): any {
    // 移除敏感字段
    const sensitiveFields = ['password', 'salt']
    const sanitized = { ...body }
    sensitiveFields.forEach((field) => delete sanitized[field])
    return sanitized
  }
}

常见问题

Q: 如何自定义返回的数据格式?

A: 可以重写父类方法或使用拦截器:

typescript
// 方法1: 重写方法
@Get('list')
async list(@Query() query) {
  const result = await super.list(query)
  return {
    success: true,
    data: result,
    timestamp: new Date().toISOString()
  }
}

// 方法2: 使用拦截器
@UseInterceptors(ResponseInterceptor)
@Get('list')
async list(@Query() query) {
  return super.list(query)
}

Q: 如何处理跨域和安全性?

A: 在 main.ts 中配置:

typescript
// main.ts
import { NestFactory } from '@nestjs/core'
import { ValidationPipe } from '@nestjs/common'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)

  // CORS 配置
  app.enableCors({
    origin: ['http://localhost:3000'],
    credentials: true,
  })

  // 全局管道
  app.useGlobalPipes(new ValidationPipe())

  // 安全头
  app.set('trust proxy', 1)

  await app.listen(3000)
}

Q: 如何实现接口版本控制?

A: 使用路由前缀:

typescript
// 方式1: 控制器级别
@Controller('api/v1/user')
export class UserController extends BaseController<SysUser, UserService> {
  // ...
}

// 方式2: 模块级别
@Module({
  controllers: [UserController],
  providers: [UserService],
})
export class UserV1Module {}

@Module({
  controllers: [UserV2Controller],
  providers: [UserService],
})
export class UserV2Module {}

// 在 app.module.ts 中注册不同版本的模块

Q: 如何添加接口文档?

A: 使用 Swagger:

typescript
// main.ts
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)

  const config = new DocumentBuilder()
    .setTitle('用户管理API')
    .setDescription('用户管理接口文档')
    .setVersion('1.0')
    .addBearerAuth()
    .build()

  const document = SwaggerModule.createDocument(app, config)
  SwaggerModule.setup('api-docs', app, document)

  await app.listen(3000)
}

然后在控制器中添加 Swagger 注解:

typescript
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'

@ApiTags('用户管理')
@Controller('api/user')
export class UserController extends BaseController<SysUser, UserService> {
  @ApiOperation({ summary: '获取用户列表' })
  @ApiResponse({ status: 200, description: '成功返回用户列表' })
  @Get('list')
  async list(@Query() query) {
    return super.list(query)
  }
}