在使用 Node.js/Nest.js 进行后端开发时,一个优秀的接口设计对于系统的可用性、可维护性和性能都至关重要。本文将结合具体示例,分享12个后端接口设计的关键实践。
参数校验是接口安全性的第一道防线。在 Nest.js 中,我们可以使用 class-validator 优雅地处理参数校验:
// user.dto.ts
import { IsEmail, IsNotEmpty, Length, Matches } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CreateUserDto {
@ApiProperty({ description: '用户名' })
@IsNotEmpty({ message: '用户名不能为空' })
@Length(4, 20)
username: string;
@ApiProperty({ description: '邮箱' })
@IsEmail({}, { message: '邮箱格式不正确' })
email: string;
@ApiProperty({ description: '手机号' })
@Matches(/^1[3-9]\d{9}$/, { message: '手机号格式不正确' })
phone: string;
}
// user.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { ApiOperation, ApiResponse } from '@nestjs/swagger';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
@ApiOperation({ summary: '创建用户' })
@ApiResponse({ status: 201, description: '用户创建成功' })
async createUser(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
}
设计接口时要遵循"开放封闭原则"。以通知服务为例:
// notification.service.ts
interface INotification {
send(message: string, target: string): Promise<void>;
}
@Injectable()
class EmailNotification implements INotification {
async send(message: string, email: string) {
// 发送邮件逻辑
}
}
@Injectable()
class SMSNotification implements INotification {
async send(message: string, phone: string) {
// 发送短信逻辑
}
}
@Injectable()
export class NotificationService {
private strategies = new Map<string, INotification>();
constructor(
private emailNotification: EmailNotification,
private smsNotification: SMSNotification,
) {
this.strategies.set('email', emailNotification);
this.strategies.set('sms', smsNotification);
}
async notify(type: string, message: string, target: string) {
const strategy = this.strategies.get(type);
if (!strategy) {
throw new Error('不支持的通知类型');
}
return strategy.send(message, target);
}
}
使用 Redis 实现幂等性控制:
// idempotency.middleware.ts
@Injectable()
export class IdempotencyMiddleware implements NestMiddleware {
constructor(private readonly redis: Redis) {}
async use(req: Request, res: Response, next: NextFunction) {
const token = req.headers['idempotency-key'];
if (!token) {
throw new BadRequestException('缺少幂等性token');
}
const key = `idempotency:${token}`;
const exists = await this.redis.set(key, '1', 'NX', 'EX', 300);
if (!exists) {
throw new ConflictException('重复请求');
}
next();
}
}
使用 Winston 进行日志管理:
// logger.service.ts
@Injectable()
export class LoggerService {
private logger: winston.Logger;
constructor() {
this.logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
}
logRequest(req: Request, res: Response) {
this.logger.info({
type: 'request',
method: req.method,
path: req.path,
params: req.params,
body: req.body,
response: res.statusCode
});
}
}
在 Node.js 中使用 worker_threads 实现线程池:
// thread-pool.service.ts
import { Worker } from 'worker_threads';
@Injectable()
export class ThreadPoolService {
private workers: Worker[] = [];
private taskQueue: any[] = [];
constructor() {
// 初始化工作线程池
for (let i = 0; i < 4; i++) {
const worker = new Worker('./worker.js');
this.workers.push(worker);
}
}
async executeTask(task: any) {
// 任务分配逻辑
}
}
调用第三方服务时需要考虑以下几个方面:
以下是一个结合了这些特性的最佳实践示例:
// external-api.service.ts
@Injectable()
export class ExternalApiService {
constructor(private circuitBreaker: CircuitBreakerService) {}
@Retry(3, 1000) // 重试装饰器:3次,间隔1秒
async callExternalAPI(data: any) {
return await this.circuitBreaker.execute(async () => {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('http://api.example.com', {
method: 'POST',
body: JSON.stringify(data),
signal: controller.signal
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
// 验证响应数据
if (!this.validateResponse(result)) {
throw new Error('Invalid response data');
}
return result;
} finally {
clearTimeout(timeoutId);
}
});
}
}
对于非核心流程(如发送通知、日志记录等),应采用异步处理机制:
下面是使用消息队列的最佳实践示例:
// notification.service.ts
@Injectable()
export class NotificationService {
constructor(
@InjectQueue('notifications') private notificationsQueue: Queue
) {}
async sendNotification(userId: string, message: string) {
// 核心业务处理完成后,异步发送通知
await this.notificationsQueue.add('send-notification', {
userId,
message
}, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 1000
},
removeOnComplete: true
});
}
}
// notification.processor.ts
@Processor('notifications')
export class NotificationProcessor {
@Process('send-notification')
async handleNotification(job: Job) {
const { userId, message } = job.data;
try {
// 发送通知的具体实现
await this.sendNotificationToUser(userId, message);
} catch (error) {
// 记录错误日志
this.logger.error(`Failed to send notification: ${error.message}`);
throw error; // 抛出错误以触发重试机制
}
}
}
对于复杂查询接口,可以使用 Promise.all 实现并行调用:
// user.service.ts
@Injectable()
export class UserService {
async getUserFullInfo(userId: string) {
const [userInfo, orderInfo, otherInfo] = await Promise.all([
this.getUserInfo(userId),
this.getOrderInfo(userId),
this.getOtherInfo(userId)
]);
return {
userInfo,
orderInfo,
otherInfo
};
}
// 也可以使用 Promise.allSettled 处理部分失败的情况
async getUserFullInfoWithFallback(userId: string) {
const results = await Promise.allSettled([
this.getUserInfo(userId),
this.getOrderInfo(userId),
this.getOtherInfo(userId)
]);
return results.map(result =>
result.status === 'fulfilled' ? result.value : null
);
}
}
在并发场景下,锁的粒度要恰当:
// lock.service.ts
import { Injectable } from '@nestjs/common';
import { RedisService } from '@nestjs/redis';
@Injectable()
export class LockService {
constructor(private readonly redisService: RedisService) {}
async acquireLock(key: string, timeout: number = 5000): Promise<boolean> {
const redis = this.redisService.getClient();
const lockValue = Date.now().toString();
const acquired = await redis.set(
`lock:${key}`,
lockValue,
'NX',
'PX',
timeout
);
return !!acquired;
}
async releaseLock(key: string): Promise<void> {
const redis = this.redisService.getClient();
await redis.del(`lock:${key}`);
}
}
长事务会占用系统资源,应该:
// transaction.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Connection, Repository } from 'typeorm';
@Injectable()
export class TransactionService {
constructor(
private connection: Connection,
@InjectRepository(User)
private userRepo: Repository<User>
) {}
async createUserWithProfile(userData: CreateUserDto) {
// 查询操作放在事务外
const existingUser = await this.userRepo.findOne({
where: { email: userData.email }
});
if (existingUser) {
throw new ConflictException('用户已存在');
}
// 开启事务
const queryRunner = this.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const user = await queryRunner.manager.save(User, userData);
await queryRunner.manager.save(UserProfile, {
userId: user.id,
...userData.profile
});
await queryRunner.commitTransaction();
return user;
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}
}
}
使用 Redis 实现分布式限流:
// rate-limit.guard.ts
@Injectable()
export class RateLimitGuard implements CanActivate {
constructor(private readonly redis: Redis) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const ip = request.ip;
const key = `ratelimit:${ip}`;
const [count] = await this.redis
.multi()
.incr(key)
.expire(key, 60)
.exec();
if (count > 100) {
throw new HttpException('请求过于频繁', HttpStatus.TOO_MANY_REQUESTS);
}
return true;
}
}
实现签名验证中间件:
// signature.middleware.ts
@Injectable()
export class SignatureMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const timestamp = req.headers['x-timestamp'];
const signature = req.headers['x-signature'];
const body = req.body;
// 验证时间戳是否过期
if (Date.now() - Number(timestamp) > 5 * 60 * 1000) {
throw new UnauthorizedException('请求已过期');
}
// 验证签名
const calculatedSignature = this.calculateSignature(timestamp, body);
if (signature !== calculatedSignature) {
throw new UnauthorizedException('签名验证失败');
}
next();
}
private calculateSignature(timestamp: string, body: any): string {
// 签名计算逻辑
return crypto
.createHash('md5')
.update(`${timestamp}${JSON.stringify(body)}${SECRET_KEY}`)
.digest('hex');
}
}
以上是基于 Node.js/Nest.js 的后端接口设计实践。这些原则和示例代码可以帮助我们构建更加健壮和可维护的后端服务。在实际应用中,需要根据具体业务场景和性能需求,灵活运用这些设计原则。
关键点回顾: