expo-popcore-app/.claude/skills/repo-core-di/references/controller.md

8.6 KiB

name description
repo-core-controller @repo/core HTTP 控制器装饰器使用指南。当需要创建 REST API 端点、处理 HTTP 请求参数、实现权限控制、或使用 SSE 流式响应时使用此技能。适用于:(1) 使用 @Controller 和 HTTP 方法装饰器(@Get, @Post, @Put, @Delete, @Patch) (2) 使用参数装饰器(@Body, @Query, @Param, @Header) (3) 实现权限和会话验证(@RequirePermissions, @RequireSession) (4) 创建 SSE 端点(@Sse) (5) 添加 API 文档(@ApiDescription)

@repo/core HTTP 控制器

核心概念

@repo/core 提供声明式 HTTP 控制器装饰器,支持路由定义、参数绑定、权限控制和 SSE 流式响应。

快速开始

创建控制器

import { Controller, Get, Post, Body, Param, Injectable } from '@repo/core'
import { z } from 'zod'

@Controller('/api/users')
class UserController {
  constructor(private userService: UserService) {}

  @Get('/list')
  async getUsers() {
    return this.userService.findAll()
  }

  @Get('/:id')
  async getUser(@Param('id') id: string) {
    return this.userService.findById(id)
  }

  @Post('/create', z.object({
    name: z.string(),
    email: z.string().email()
  }))
  async createUser(@Body() data: { name: string; email: string }) {
    return this.userService.create(data)
  }
}

HTTP 方法装饰器

@Get(path)

@Get('/users')
async getUsers() { }

@Get('/users/:id')
async getUser(@Param('id') id: string) { }

@Get('/search')
async search(@Query('q') query: string) { }

@Post(path, schema?, contentType?)

// 基本用法
@Post('/users')
async createUser(@Body() data: CreateUserDto) { }

// 带 Zod 验证
@Post('/users', z.object({
  name: z.string().min(1),
  email: z.string().email()
}))
async createUser(@Body() data: { name: string; email: string }) { }

// 指定 Content-Type
@Post('/upload', schema, 'multipart/form-data')
async upload(@Body() data: FormData) { }

@Put(path, schema?)

@Put('/users/:id', z.object({
  name: z.string().optional(),
  email: z.string().email().optional()
}))
async updateUser(
  @Param('id') id: string,
  @Body() data: Partial<User>
) { }

@Delete(path)

@Delete('/users/:id')
async deleteUser(@Param('id') id: string) { }

@Patch(path, schema?)

@Patch('/users/:id/status', z.object({
  status: z.enum(['active', 'inactive'])
}))
async updateStatus(
  @Param('id') id: string,
  @Body('status') status: string
) { }

@Sse(path) - Server-Sent Events

@Sse('/events')
async *streamEvents() {
  while (true) {
    yield { data: { timestamp: Date.now() } }
    await sleep(1000)
  }
}

@Sse('/notifications/:userId')
async *userNotifications(@Param('userId') userId: string) {
  const stream = this.notificationService.subscribe(userId)
  for await (const notification of stream) {
    yield { event: 'notification', data: notification }
  }
}

参数装饰器

@Body(key?, schema?)

// 获取整个请求体
@Post('/users')
async create(@Body() data: CreateUserDto) { }

// 获取特定字段
@Post('/users')
async create(@Body('name') name: string) { }

// 带验证
@Post('/users')
async create(@Body('email', z.string().email()) email: string) { }

@Query(key?, schema?)

// 获取查询参数
@Get('/search')
async search(@Query('q') query: string) { }

// 带默认值和验证
@Get('/list')
async list(
  @Query('page', z.coerce.number().default(1)) page: number,
  @Query('limit', z.coerce.number().default(10)) limit: number
) { }

@Param(key)

@Get('/users/:id')
async getUser(@Param('id') id: string) { }

@Get('/projects/:projectId/tasks/:taskId')
async getTask(
  @Param('projectId') projectId: string,
  @Param('taskId') taskId: string
) { }

@Header(key)

@Get('/protected')
async protected(@Header('authorization') auth: string) { }

@Post('/webhook')
async webhook(
  @Header('x-signature') signature: string,
  @Body() payload: any
) { }

@Session()

@Get('/profile')
async getProfile(@Session() session: UserSession) {
  return this.userService.findById(session.userId)
}

@Req() / @Res()

@Get('/download')
async download(@Req() req: Request, @Res() res: Response) {
  // 直接访问原始请求/响应对象
  res.setHeader('Content-Disposition', 'attachment')
  return fileStream
}

@Headers()

@Post('/webhook')
async webhook(@Headers() headers: Record<string, string>) {
  const signature = headers['x-signature']
  // ...
}

权限控制

@RequireSession()

@Controller('/api/account')
class AccountController {
  @Get('/profile')
  @RequireSession()
  async getProfile(@Session() session: UserSession) {
    return this.userService.findById(session.userId)
  }
}

@RequirePermissions(permissions, mode?)

// 需要单个权限
@Delete('/users/:id')
@RequirePermissions({ user: ['delete'] })
async deleteUser(@Param('id') id: string) { }

// 需要多个权限 (AND 模式 - 全部满足)
@Put('/projects/:id')
@RequirePermissions({ project: ['read', 'update'] }, 'AND')
async updateProject(@Param('id') id: string) { }

// OR 模式 - 满足任一即可
@Get('/reports')
@RequirePermissions({ report: ['read'], admin: ['access'] }, 'OR')
async getReports() { }

权限资源类型

// 可用的权限资源和操作
const permissions = {
  activity: ['create', 'read', 'update', 'delete'],
  project: ['create', 'read', 'list', 'update', 'delete', 'run'],
  template: ['create', 'read', 'list', 'update', 'delete', 'run'],
  user: ['read', 'update', 'delete'],
  admin: ['access', 'manage'],
  report: ['read', 'export']
}

API 文档

@ApiDescription(description, tags?)

@Controller('/api/users')
class UserController {
  @Get('/list')
  @ApiDescription('获取用户列表', ['Users', 'Admin'])
  async getUsers() { }

  @Post('/create')
  @ApiDescription('创建新用户', ['Users'])
  async createUser(@Body() data: CreateUserDto) { }
}

完整示例

import {
  Controller, Get, Post, Put, Delete, Sse,
  Body, Query, Param, Header, Session,
  RequireSession, RequirePermissions, ApiDescription,
  Injectable
} from '@repo/core'
import { z } from 'zod'

const CreateUserSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  role: z.enum(['user', 'admin']).default('user')
})

const UpdateUserSchema = CreateUserSchema.partial()

@Controller('/api/users')
class UserController {
  constructor(
    private userService: UserService,
    private notificationService: NotificationService
  ) {}

  @Get('/list')
  @RequireSession()
  @ApiDescription('获取用户列表', ['Users'])
  async list(
    @Query('page', z.coerce.number().default(1)) page: number,
    @Query('limit', z.coerce.number().default(20)) limit: number,
    @Query('search') search?: string
  ) {
    return this.userService.findAll({ page, limit, search })
  }

  @Get('/:id')
  @RequireSession()
  @ApiDescription('获取用户详情', ['Users'])
  async getById(@Param('id') id: string) {
    return this.userService.findById(id)
  }

  @Post('/create', CreateUserSchema)
  @RequirePermissions({ user: ['create'] })
  @ApiDescription('创建用户', ['Users', 'Admin'])
  async create(@Body() data: z.infer<typeof CreateUserSchema>) {
    return this.userService.create(data)
  }

  @Put('/:id', UpdateUserSchema)
  @RequirePermissions({ user: ['update'] })
  @ApiDescription('更新用户', ['Users', 'Admin'])
  async update(
    @Param('id') id: string,
    @Body() data: z.infer<typeof UpdateUserSchema>
  ) {
    return this.userService.update(id, data)
  }

  @Delete('/:id')
  @RequirePermissions({ user: ['delete'] })
  @ApiDescription('删除用户', ['Users', 'Admin'])
  async delete(@Param('id') id: string) {
    return this.userService.delete(id)
  }

  @Sse('/notifications')
  @RequireSession()
  @ApiDescription('用户通知流', ['Users', 'Realtime'])
  async *notifications(@Session() session: UserSession) {
    const stream = this.notificationService.subscribe(session.userId)
    for await (const notification of stream) {
      yield { event: 'notification', data: notification }
    }
  }
}

最佳实践

  1. 使用 Zod 验证 - 始终为 POST/PUT/PATCH 请求定义 schema
  2. 分层权限 - 使用 @RequireSession 和 @RequirePermissions 组合
  3. API 文档 - 为所有端点添加 @ApiDescription
  4. 参数验证 - 使用 @Query 和 @Body 的 schema 参数
  5. SSE 清理 - 确保 SSE 生成器正确处理客户端断开
  6. 控制器单一职责 - 每个控制器处理一个资源类型