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,3list(@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)
}
}