基于NestJS框架的详细后端服务实现方案


好的,用户之前已经询问过后端选择Java还是Node.js的问题,现在他们决定使用Node.js,特别是考虑到前端是TypeScript实现的小程序,可能希望保持技术栈的一致性。用户需要更详细的服务框架和公共能力的实现。

首先,我需要回顾之前的对话,确保理解用户的需求。用户之前得到过Java和Node.js的对比,现在选择了Node.js,所以需要深入Node.js的后端设计,特别是NestJS框架的应用。用户可能希望后端能够与前端TypeScript无缝集成,利用相同的语言特性,提高开发效率。

接下来,用户需要详细的服务框架和公共能力。公共能力可能包括日志记录、错误处理、配置管理、数据库集成、缓存、鉴权、接口文档、任务队列等。需要确保这些模块是可扩展、易于维护,并且符合最佳实践。

考虑到用户使用的是TypeScript,NestJS是一个很好的选择,因为它支持TypeScript,并且提供了模块化的结构,类似于Angular,这对前端开发者来说可能更熟悉。需要展示如何组织项目结构,每个模块的职责,以及如何集成各种公共能力。

用户可能还需要监控、性能优化和安全性方面的考虑,比如请求追踪、速率限制、数据验证等。此外,部署和持续集成/持续部署(CI/CD)的配置也是重要部分,尤其是Docker和Kubernetes的使用,这有助于项目的可扩展性和维护性。

需要提供具体的代码示例,比如使用NestJS的模块、控制器、服务、DTO和实体,以及如何实现全局的拦截器、过滤器、守卫等。同时,集成TypeORM进行数据库操作,使用Swagger生成API文档,使用BullMQ处理队列任务,都是关键点。

另外,用户可能关注如何保证代码质量和规范,所以需要包括ESLint和Prettier的配置,以及单元测试和集成测试的设置。日志系统需要支持多种传输方式,并结构化日志数据,方便后续分析。

最后,确保整个架构是可扩展的,能够处理高并发,并且有良好的错误处理机制,比如全局异常过滤器和自定义业务异常。安全方面,JWT鉴权和数据加密也是必须考虑的部分。

总结来说,需要构建一个结构清晰、模块化、具备完整公共能力的NestJS后端服务,与前端TypeScript小程序良好集成,同时注重性能、安全和可维护性。

以下是基于NestJS框架的详细后端服务实现方案,深度整合TypeScript工程化实践:


全量服务架构设计

├── src
│   ├── core                  # 核心框架
│   │   ├── decorators       # 自定义装饰器
│   │   ├── guards           # 鉴权守卫
│   │   ├── interceptors     # 全局拦截器
│   │   └── filters          # 异常过滤器
│   ├── modules              # 业务模块
│   │   └── user
│   │       ├── user.module.ts
│   │       ├── user.controller.ts
│   │       ├── user.service.ts
│   │       ├── user.entity.ts
│   │       └── dto          # 数据传输对象
│   ├── common               # 公共能力
│   │   ├── cache            # 缓存模块
│   │   ├── logger           # 日志系统
│   │   ├── queue            # 任务队列
│   │   └── utils            # 工具库
│   ├── config               # 配置中心
│   ├── middleware           # 全局中间件
│   └── main.ts              # 入口文件
├── test                     # 测试用例
├── docker-compose.yml       # 容器编排
├── package.json
└── nest-cli.json

核心公共能力实现

1. 增强型日志系统

// common/logger/log.service.ts
@Injectable()
export class LogService {
  private readonly logger: Logger;

  constructor(config: ConfigService) {
    const transports: Transport[] = [
      new transports.Console({ format: format.combine(format.timestamp(), format.json()) }),
      new transports.File({ filename: 'logs/combined.log' })
    ];
    
    if (config.get('ENV') === 'production') {
      transports.push(
        new transports.Http({
          host: 'log-collector.domain.com',
          path: '/ingest',
          ssl: true
        })
      );
    }

    this.logger = createLogger({
      level: config.get('LOG_LEVEL'),
      transports,
      defaultMeta: { service: 'backend-service' }
    });
  }

  @LogTrace()  // 自定义方法装饰器
  log(level: LogLevel, message: string, meta?: Record<string, any>) {
    this.logger.log(level, message, {
      ...meta,
      traceId: RequestContext.currentRequestId()
    });
  }
}

2. 统一响应拦截器

// core/interceptors/response.interceptor.ts
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    
    return next.handle().pipe(
      map(data => ({
        code: 0,
        data,
        requestId: request.requestId,
        timestamp: Date.now()
      })),
      tap(() => this.logAccess(request))
    );
  }

  private logAccess(request: Request) {
    const { method, originalUrl, ip, headers } = request;
    const userAgent = headers['user-agent'] || '';
    const responseTime = Date.now() - request.startTime;
    
    this.logService.info('API_ACCESS', {
      method,
      path: originalUrl,
      clientIp: ip,
      userAgent,
      responseTime
    });
  }
}

3. 分布式追踪实现

// core/middleware/tracing.middleware.ts
@Injectable()
export class TracingMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const requestId = req.headers['x-request-id'] || uuidv4();
    
    // 注入追踪上下文
    RequestContext.create({
      requestId,
      startTime: Date.now(),
      clientIp: req.ip
    }, next);

    // 设置响应头
    res.setHeader('X-Request-ID', requestId);
    res.setHeader('X-Server-Name', os.hostname());
  }
}

业务模块深度实现

用户模块示例

// modules/user/user.controller.ts
@Controller('users')
@UseGuards(JwtAuthGuard, RateLimitGuard)
@ApiTags('用户管理')
export class UserController {
  constructor(
    private readonly userService: UserService,
    private readonly cacheManager: Cache
  ) {}

  @Post()
  @ApiOperation({ summary: '创建用户' })
  async createUser(@Body() createUserDto: CreateUserDto) {
    const cachedUser = await this.cacheManager.get(`user:${createUserDto.mobile}`);
    if (cachedUser) throw new ConflictException('用户已存在');
    
    return this.userService.create(createUserDto);
  }

  @Get(':id')
  @CacheTTL(60)  // 自定义缓存装饰器
  async getUserProfile(@Param('id') userId: string) {
    return this.userService.findById(userId);
  }
}

// modules/user/user.service.ts
@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
    private readonly smsService: SmsService
  ) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    const existingUser = await this.userRepository.findOne({ 
      where: { mobile: createUserDto.mobile }
    });
    
    if (existingUser) {
      throw new BusinessException(ErrorCode.USER_ALREADY_EXISTS);
    }

    const user = this.userRepository.create({
      ...createUserDto,
      password: await bcrypt.hash(createUserDto.password, 10)
    });

    await this.smsService.sendVerification(user.mobile);
    return this.userRepository.save(user);
  }
}

基础设施集成

数据库配置(TypeORM)

// config/database.config.ts
export default TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (config: ConfigService) => ({
    type: 'mysql',
    host: config.get('DB_HOST'),
    port: config.get('DB_PORT'),
    username: config.get('DB_USER'),
    password: config.get('DB_PASSWORD'),
    database: config.get('DB_NAME'),
    entities: [__dirname + '/../**/*.entity{.ts,.js}'],
    synchronize: config.get('NODE_ENV') !== 'production',
    logging: config.get('DB_LOGGING'),
    extra: {
      connectionLimit: 10,
      waitForConnections: true
    }
  }),
  inject: [ConfigService]
});

队列系统完整实现

// common/queue/queue.module.ts
@Module({
  imports: [ConfigModule, BullModule.registerQueue({ name: 'email' })],
  providers: [
    {
      provide: 'QUEUE_CONNECTION',
      useFactory: (config: ConfigService) => new IORedis({
        host: config.get('REDIS_HOST'),
        port: config.get('REDIS_PORT'),
        maxRetriesPerRequest: null,
        enableReadyCheck: false
      }),
      inject: [ConfigService]
    },
    {
      provide: 'EMAIL_QUEUE',
      useFactory: (conn: IORedis) => new Queue('email', { 
        connection: conn,
        defaultJobOptions: {
          attempts: 3,
          backoff: { type: 'exponential', delay: 1000 }
        }
      }),
      inject: ['QUEUE_CONNECTION']
    }
  ],
  exports: ['EMAIL_QUEUE']
})
export class QueueModule {}

// 队列处理器(独立进程)
// src/consumers/email.consumer.ts
@Processor('email')
export class EmailConsumer {
  constructor(
    private readonly mailService: MailService,
    private readonly logger: Logger
  ) {}

  @Process('welcome')
  async handleWelcomeEmail(job: Job<{ userId: string }>) {
    try {
      const user = await this.userService.findById(job.data.userId);
      await this.mailService.sendWelcomeEmail(user);
      this.logger.log(`Welcome email sent to ${user.email}`);
    } catch (e) {
      this.logger.error('邮件发送失败', e.stack);
      throw new Error(e);
    }
  }

  @OnQueueFailed()
  handleFailedJob(job: Job, err: Error) {
    this.logger.error(`任务失败: ${job.id}`, {
      jobData: job.data,
      error: err.message
    });
  }
}

安全防护完整实现

// core/guards/jwt.guard.ts
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  constructor(private reflector: Reflector) {
    super();
  }

  handleRequest(err: any, user: any, info: any, context: Context) {
    if (err || !user) {
      throw err || new UnauthorizedException('身份验证失败');
    }
    
    // 接口权限检查
    const roles = this.reflector.get<string[]>('roles', context.getHandler());
    if (roles && !roles.some(role => user.roles.includes(role))) {
      throw new ForbiddenException('权限不足');
    }
    
    return user;
  }
}

// 配套策略配置
// config/auth.config.ts
export const JwtStrategyConfig = {
  secretOrKeyProvider: (request: Request, rawJwtToken: string) => {
    return process.env.JWT_SECRET;
  },
  jwtFromRequest: ExtractJwt.fromExtractors([
    (request: Request) => request?.cookies?.Authorization,
    ExtractJwt.fromAuthHeaderAsBearerToken()
  ]),
  ignoreExpiration: false
};

增强缓存模块

// common/cache/cache.decorator.ts
export const CacheResponse = (ttl: number = 60) => applyDecorators(
  UseInterceptors(CacheInterceptor),
  SetMetadata('cache-ttl', ttl),
  ApiHeader({ name: 'X-Cache-Status', description: '缓存状态' })
);

// 缓存配置
// config/cache.config.ts
export const CacheConfig: CacheModuleOptions = {
  ttl: 5, // 默认秒数
  max: 100, // 最大缓存条目
  store: redisStore({
    socket: {
      host: process.env.REDIS_HOST,
      port: +process.env.REDIS_PORT
    },
    ttl: 60 * 60 // 单位秒
  })
};

Docker 生产环境配置

# docker-compose.prod.yml
version: '3.8'

services:
  app:
    build:
      context: .
      target: production
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - REDIS_HOST=redis
      - DB_HOST=mysql
    depends_on:
      - redis
      - mysql

  redis:
    image: redis:alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
    volumes:
      - mysql_data:/var/lib/mysql
    ports:
      - "3306:3306"

volumes:
  redis_data:
  mysql_data:

事务管理示例

// modules/payment/payment.service.ts
@Injectable()
export class PaymentService {
  constructor(
    @InjectEntityManager()
    private readonly entityManager: EntityManager,
    private readonly ledgerService: LedgerService
  ) {}

  async createTransaction(paymentData: PaymentDto) {
    return this.entityManager.transaction(async transactionalManager => {
      // 1. 创建支付记录
      const payment = transactionalManager.create(Payment, paymentData);
      await transactionalManager.save(payment);
      
      // 2. 更新账户余额
      await transactionalManager.update(
        Account,
        { userId: payment.userId },
        { balance: () => `balance - ${payment.amount}` }
      );
      
      // 3. 写入资金流水
      await this.ledgerService.createFlow(transactionalManager, {
        userId: payment.userId,
        amount: payment.amount,
        type: 'PAYMENT'
      });
      
      return payment;
    });
  }
}

Swagger API 文档集成

// main.ts
function buildSwagger(app: INestApplication) {
  const config = new DocumentBuilder()
    .setTitle('电商平台API')
    .setDescription('## 全局响应格式\n```json\n{\n  "code": 0,\n  "data": {},\n  "message": "success"\n}\n```')
    .setVersion('1.2.0')
    .addBearerAuth({ type: 'http', scheme: 'bearer' }, 'JWT')
    .addCookieAuth('sessionId', { type: 'apiKey' })
    .addServer('https://api.example.com', '生产环境')
    .addApiKey({ type: 'apiKey', name: 'X-API-KEY' }, 'PartnerKey')
    .build();

  const document = SwaggerModule.createDocument(app, config, {
    extraModels: [PaginatedResponse],

嘿手大叔 2025年2月20日 21:47 收藏文档