走进 Leverage OJ / 第 9 课
NestJS 的入口、服务与请求校验
认识 Controller、Service、DTO、Guard 和 ValidationPipe,理解框架如何组织真实 API。
NestJS 把后端拆成边界清楚的部分。Controller 声明路由并读取请求,例如 POST /submissions 的方法与正文;它通常不负责查数据库或安排评测,而是把已整理的输入交给 Service。Service 执行业务步骤,像检查提交语言、保存记录并派发任务。这样一个 HTTP 入口可以保持简洁,规则也能在其他入口复用。
Controller 上常见 @Controller('submissions')、@Post()、@Body() 这样的 @ 写法。这里首次遇到装饰器:可以先把它理解为框架读取的附加标记,告诉 NestJS“这个类对应哪个路由”“这个参数来自请求正文”。它不是初学者需要自己实现的语法,也不表示 TypeScript 类型会在网络请求到来时自动验证数据。阅读时先观察标记描述什么,再看被标记的类、方法或参数做什么。
请求对象通常由 DTO(数据传输对象)承接。固定版本的 CreateSubmissionDto 把 problemId、code 和 language 写成字段,并为字段配置校验规则。注意语言字段在该版本是数字:@IsNumber() 只说明它必须是数字,不是字符串枚举,也不保证该语言能被评测。Service 随后通过 submissionLanguage 检查内部评测是否支持这个数字。Python 的 ID 是 9。
TypeScript 的 language: number 是静态信息,主要帮助开发时检查;客户端发来的内容到达服务器时仍是运行时数据。NestJS 的 ValidationPipe 会读取 DTO 上的规则,对真实请求执行校验。该公开版本在 main.ts 全局启用 transform: true 与 whitelist: true:转换输入并按 DTO 元数据过滤额外属性。字段规则与业务规则分工,不可互相取代。
Guard 则在进入处理流程前判断请求是否可以访问。提交创建路由使用 JwtAuthGuard,所以必须先通过登录身份检查;方法从 CurrentUser 取得用户,再把用户 ID 和 DTO 交给 Service。Guard 不负责验证代码长度,ValidationPipe 也不负责确认用户已登录。把“谁能进”“数据长什么样”“业务能不能做”分开,后端更容易读懂和维护。
小练习
若请求的 language 是数字 999,@IsNumber() 会拒绝吗?登录 Guard 通过是否代表该语言受支持?
**答案:**仅凭数字类型,@IsNumber() 不会拒绝 999;Guard 只处理访问控制。Service 的支持语言检查才决定能否提交给内部评测。
去真实仓库看看
阅读固定版本的 submission.controller.ts、create-submission.dto.ts、main.ts 全局校验管道,再对照 submission.service.ts 语言支持检查。
互动练习:找到 NestJS 控制器
在 NestJS 中,通常由哪个组件接收并处理匹配路由的 HTTP 请求?
Controller 接入 HTTP,Service 承担业务,DTO 和 ValidationPipe 在运行时检查请求,Guard 负责访问控制。