第4章 Nest.js业务合并
在实际项目中,不同的业务操作需要明确的反馈信息。例如:
- 登录操作返回消息为「登录成功」,状态码为 1。
- 注册操作返回消息为「注册成功」,状态码为 2。
状态码 作为业务逻辑判断的操作标识,是一个数字或字符串标识符。不同数值代表不同的业务含义(如1表示登录成功、2表示注册成功),前端或调用方可根据具体数值执行相应的逻辑分支。业务描述 则面向用户或开发者,用自然语言清晰传达操作结果,提供直观的反馈信息,辅助理解状态码对应的具体业务场景。
在全局拦截器的使用中,message 和 code 这两个参数需要根据业务需求进行自定义。下面将介绍如何在 Nest.js 中对这两个参数,以及更多同类参数进行规范化管理。
ts
// src/interceptor/interceptor.interceptor.ts
return {
timestmap: new Date().toISOString(),
data: transformBigInt(data),
path: request.url,
message: 'success',//业务逻辑自定义
code: 200,//业务逻辑自定义
success: true,
};
要对全局拦截器的统一返回数据格式中的 message 和 code 进行自定义,应从专门的业务定义文件中引入自定义的业务状态码和描述信息,然后传递给 message 和 code 参数。
在自定义参数时,有一个重要原则:message 和 code 的自定义数据需要与业务逻辑层分离。这些数据应作为纯粹的配置信息使用,类似于 JSON 配置文件。业务层则建立在统一的业务规范基础上进行进一步封装。
首先创建一个响应格式模块(response module)和服务(response service),用于统一处理业务响应。通过 Nest CLI 命令生成的 response.service.ts 文件会自动关联到 response.module.ts 文件中。
ts
nest g mo response
nest g s response
在 src 目录下创建 business 文件夹,并在其中创建 index.ts 文件。该文件专门存放自定义的业务状态码和描述信息,我们只关心每个业务操作对应的状态码和消息内容。后续新增业务需求时,只需在此文件中添加相应的状态码和消息。
注意:业务字段命名通常采用大写字母和下划线组合的格式,即「功能_状态」的表达方式,如 LOGIN_SUCCESS。
ts
// src/business/index.ts
export const business = {
LOGIN_SUCCESS: {
code: 1,
message: '登录成功'
},
LOGIN_ERROR: {
code: 2,
message: '登录失败'
},
REGISTER_SUCCESS: {
code: 2,
message: '注册成功'
},
}
在 response 目录的 response.service.ts 文件中编写具体的响应格式逻辑,定义操作成功和操作失败时需要返回的数据结构。
ts
// src/response/response.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class ResponseService {
success(data: any = null, message: string = '操作成功', code: number = 200) {
return {
data,
message,
code
}
}
error(message: string = '操作失败', code: number = 500) {
return {
message,
code
}
}
}
如果我们想要使用自定义的业务状态码,要如何使用?假设要在user模块的业务层中使用。
ts
// src/user/user.service.ts(业务层)
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Injectable()
export class UserService {
create(createUserDto: CreateUserDto) {
return 'This action adds a new user';
}
findAll() {
return `This action returns all user`;
}
// 省略...
}
假设需要在 user 模块的业务层中使用自定义业务状态码,操作步骤如下:
(1)引入依赖文件:同时引入业务定义文件和响应格式文件。
(2)依赖注入:在 UserService 类中注入 ResponseService 类。
(3)使用响应格式:在 findAll() 方法中使用 ResponseService 类,按照 data、message 和 code 的顺序传入数据。
(4)引用业务定义:message 和 code 从 business 业务文件中读取,本次「登录成功操作」使用业务字段 LOGIN_SUCCESS。
ts
// src/user/user.service.ts(业务层)
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { ResponseService } from 'src/response/response.service';
import { business } from 'src/business';
@Injectable()
export class UserService {
constructor(private readonly responseService: ResponseService) { }
create(createUserDto: CreateUserDto) {
return 'This action adds a new user';
}
findAll() {
// 登录成功的消息内容和业务码
const message = business.LOGIN_SUCCESS.message;
const code = business.LOGIN_SUCCESS.code;
return this.responseService.success('This action returns all user', message, code);
}
}
此时启动项目,访问 localhost:3000/user 路由应返回登录成功的业务状态码。但可能出现以下错误:
RROR [ExceptionHandler] UnknownDependenciesException [Error]: Nest can't resolve dependencies of the UserService (?). Please make sure that the argument ResponseService at index [0] is available in the UserModule context.
错误信息表明 Nest 无法解析 UserService 的依赖,需要确保 ResponseService 在 UserModule 上下文中可用。这是因为 ResponseService 未被正确导出和导入。
解决上述问题需要如下3个步骤:
(1)导出服务:在 response.module.ts 文件中将 ResponseService 添加到 exports 数组中。
(2)导入模块:在 user.module.ts 文件中导入 ResponseModule 模块。
(3)完成注入:此时 UserModule 可以读取到 ResponseModule 导出的 ResponseService,UserService 才能正常使用 ResponseService。
ts
// src/response/response.module.ts
import { Module } from '@nestjs/common';
import { ResponseService } from './response.service';
@Module({
providers: [ResponseService],
exports: [ResponseService]
})
export class ResponseModule { }
在user.module.ts 文件中导入 ResponseModule 模块,完成导入模块。
ts
// src/user/user.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { ResponseModule } from 'src/response/response.module';
@Module({
imports: [ResponseModule],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
完成以上配置后,重新启动项目访问 http://localhost:3000/user,可见 data、message 和 code 三个业务字段的信息都成功输出。

图4-1 业务字段信息插入
接下来只需从全局拦截器中重新统一数据格式即可。
ts
// src/interceptor/interceptor.interceptor.ts
return {
timestmap: new Date().toISOString(),
data: transformBigInt(data.data) ?? null,
path: request.url,
message: data.message ?? 'success',//业务逻辑自定义
code: data.code ?? 200,//业务逻辑自定义
success: true,
};
统一业务字段格式如图4-2所示。

图4-2 统一业务字段格式
通过以上步骤,Nest.js 的业务状态码和业务状态描述已成功应用到对应的 user 模块接口中。但在实现过程中,我们发现每个使用 ResponseService 的模块都需要:
(1)在提供模块中导出服务。
(2)在使用模块中导入模块。
如果项目有十几个模块都需要使用,这种重复导入导出的操作会变得繁琐。为此,可以将 response 模块注册为全局模块,这样在整个项目中都可以直接使用,无需在每个使用模块的 module.ts 文件中重复导入。
将 response 模块注册为全局模块的方法为以下2步:
(1)从 @nestjs/common 中导入 Global 装饰器
(2)将 @Global() 装饰器应用到 ResponseModule 上
注册为全局模块后,ResponseService 可以在任何模块中直接使用,无需在各使用模块中导入 ResponseModule。
ts
// src/response/response.module.ts
import { Module, Global } from '@nestjs/common';
import { ResponseService } from './response.service';
@Global()
@Module({
providers: [ResponseService],
exports: [ResponseService]
})
export class ResponseModule { }
此时如果我们回到user.module.ts文件中,将刚才注册的ResponseModule删除,项目也不会报错。
对于全局模块的使用,若我想在xiaoyu模块中使用ResponseService,主要步骤如下,无需在XiaoyuModule中导入ResponseModule:
(1)在 xiaoyu.service.ts 文件中导入 ResponseService 服务类和 business 业务常量。
(2)在 XiaoyuService 的构造函数中注入 ResponseService,并使用其方法按照业务数据格式规范处理数据。
(3)在 xiaoyu.controller.ts 文件的控制器中注入 XiaoyuService,并在路由处理器中调用其业务方法返回处理结果。
注意:虽然 ResponseService 是全局模块无需导入,但 XiaoyuModule 仍需要在自身的 providers 中注册 XiaoyuService,在 controllers 中注册 XiaoyuController。
ts
// src/xiaoyu/xiaoyu.service.ts
import { Injectable } from '@nestjs/common';
import { ResponseService } from 'src/response/response.service';
import { business } from 'src/business';
@Injectable()
export class XiaoyuService {
constructor(private readonly responseService: ResponseService) {}
getHello(): any {
const message = business.LOGIN_SUCCESS.message;
const code = business.LOGIN_SUCCESS.code;
return this.responseService.success('This action returns all user', message, code);
}
}
ts
// src/xiaoyu/xiaoyu.controller.ts
import { Controller, Get } from '@nestjs/common';
import { XiaoyuService } from './xiaoyu.service';
@Controller('xiaoyu')
export class XiaoyuController {
// 依赖注入
constructor(private readonly xiaoyuService: XiaoyuService) {}
@Get()
getHello(): any {
return this.xiaoyuService.getHello();
}
}
xiaoyu模块业务合并如图4-3所示。

图4-3 xiaoyu模块业务合并
以上就是Nest.js业务层的所有内容,我们回顾一下,Nest.js 的业务处理分为 business 和 response 两个部分:
(1)business 文件夹:集中管理业务状态码和描述信息。
(2)response 模块:专门用于构建统一的响应格式。
在实际项目中,这两者的变化频率不同:响应格式通常保持稳定,而业务状态码会随着业务发展不断新增或调整。因此需要将两者分离管理。response 模块的功能与全局拦截器中统一返回客户端响应格式的功能是一致的,区别在于我们将自定义部分拆分出来,提高了灵活性和可维护性,更符合实际项目的需求变化。
这种架构设计的优势在于:业务状态定义与响应格式构建职责明确(单一职责);业务状态变化不影响响应格式,响应格式调整不影响业务逻辑;统一的响应格式可跨模块、跨项目使用;新增业务状态只需在 business 文件中添加,不影响现有结构。通过这种规范化管理,可以构建出清晰、可维护、可扩展的业务层架构,适应各种复杂的业务场景需求。
除了message和code这两个字段,我们还可以有权限与安全控制(例如IP白名单、频率限制、黑白名单)、数据持久化(创建、读取、更新、删除业务数据)、业务流程(多级审批、会签、或签逻辑)、第三方集成(支付宝、微信支付回调处理)、监控与统计(接口响应时间、成功率)等多方面基于实际业务需求去增添。