--- name: repo-core-controller description: "@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 流式响应。 ## 快速开始 ### 创建控制器 ```typescript 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) ```typescript @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?) ```typescript // 基本用法 @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?) ```typescript @Put('/users/:id', z.object({ name: z.string().optional(), email: z.string().email().optional() })) async updateUser( @Param('id') id: string, @Body() data: Partial ) { } ``` ### @Delete(path) ```typescript @Delete('/users/:id') async deleteUser(@Param('id') id: string) { } ``` ### @Patch(path, schema?) ```typescript @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 ```typescript @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?) ```typescript // 获取整个请求体 @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?) ```typescript // 获取查询参数 @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) ```typescript @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) ```typescript @Get('/protected') async protected(@Header('authorization') auth: string) { } @Post('/webhook') async webhook( @Header('x-signature') signature: string, @Body() payload: any ) { } ``` ### @Session() ```typescript @Get('/profile') async getProfile(@Session() session: UserSession) { return this.userService.findById(session.userId) } ``` ### @Req() / @Res() ```typescript @Get('/download') async download(@Req() req: Request, @Res() res: Response) { // 直接访问原始请求/响应对象 res.setHeader('Content-Disposition', 'attachment') return fileStream } ``` ### @Headers() ```typescript @Post('/webhook') async webhook(@Headers() headers: Record) { const signature = headers['x-signature'] // ... } ``` ## 权限控制 ### @RequireSession() ```typescript @Controller('/api/account') class AccountController { @Get('/profile') @RequireSession() async getProfile(@Session() session: UserSession) { return this.userService.findById(session.userId) } } ``` ### @RequirePermissions(permissions, mode?) ```typescript // 需要单个权限 @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() { } ``` ### 权限资源类型 ```typescript // 可用的权限资源和操作 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?) ```typescript @Controller('/api/users') class UserController { @Get('/list') @ApiDescription('获取用户列表', ['Users', 'Admin']) async getUsers() { } @Post('/create') @ApiDescription('创建新用户', ['Users']) async createUser(@Body() data: CreateUserDto) { } } ``` ## 完整示例 ```typescript 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) { return this.userService.create(data) } @Put('/:id', UpdateUserSchema) @RequirePermissions({ user: ['update'] }) @ApiDescription('更新用户', ['Users', 'Admin']) async update( @Param('id') id: string, @Body() data: z.infer ) { 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. **控制器单一职责** - 每个控制器处理一个资源类型