环境搭建

在开始开发之前,我们需要配置好开发环境。本指南将帮助您安装所有必要的工具和依赖。

Node.js 环境

推荐使用 Node.js 18.x 或更高版本

# 检查 Node.js 版本 node --version # 安装 Node.js (使用 nvm) nvm install 18 nvm use 18

NestJS CLI

全局安装 NestJS 命令行工具

# 安装 NestJS CLI npm install -g @nestjs/cli # 验证安装 nest --version

数据库环境

我们使用 PostgreSQL 作为主数据库,Redis 作为缓存

PostgreSQL

  • • 版本 14.x 或更高
  • • 支持 JSON 数据类型
  • • 配置 UTF-8 编码

Redis

  • • 版本 6.x 或更高
  • • 会话存储
  • • 消息队列

项目初始化

创建新的 NestJS 项目并配置基本结构

1. 创建项目

# 创建新的 NestJS 项目 nest new im-system # 选择包管理器 (推荐使用 npm) ? Which package manager would you ❤️ to use? npm # 进入项目目录 cd im-system

这将创建一个包含基本结构的 NestJS 项目

2. 安装依赖

# 安装 Prisma 相关依赖 npm install prisma @prisma/client # 安装 WebSocket 支持 npm install @nestjs/websockets @nestjs/platform-socket.io socket.io # 安装认证相关依赖 npm install @nestjs/jwt @nestjs/passport passport passport-jwt # 安装其他工具库 npm install bcrypt class-validator class-transformer npm install @nestjs/config

安装项目所需的核心依赖包

3. 项目结构

src/ ├── auth/ # 认证模块 ├── users/ # 用户模块 ├── chat/ # 聊天模块 ├── prisma/ # Prisma 配置 ├── common/ # 公共组件 ├── app.module.ts # 根模块 └── main.ts # 应用入口

推荐的项目目录结构,按功能模块组织代码

数据库设计

使用 Prisma 设计数据库模型,建立实体关系

1. 初始化 Prisma

# 初始化 Prisma npx prisma init # 这将创建 prisma/schema.prisma 文件和 .env 文件

初始化 Prisma 并创建配置文件

2. 配置数据库连接

# .env 文件 DATABASE_URL="postgresql://username:password@localhost:5432/im_system?schema=public" # prisma/schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") }

配置 PostgreSQL 数据库连接

3. 定义数据模型

model User { id String @id @default(cuid()) email String @unique username String @unique password String avatar String? status UserStatus @default(ONLINE) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt sentMessages Message[] @relation("SentMessages") receivedMessages Message[] @relation("ReceivedMessages") groupMembers GroupMember[] @@map("users") } model Message { id String @id @default(cuid()) content String type MessageType @default(TEXT) room String senderId String receiverId String? fileUrl String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt sender User @relation("SentMessages", fields: [senderId], references: [id]) receiver User? @relation("ReceivedMessages", fields: [receiverId], references: [id]) @@map("messages") } enum UserStatus { ONLINE OFFLINE AWAY BUSY } enum MessageType { TEXT IMAGE VIDEO FILE }

定义用户和消息的数据模型

4. 生成 Prisma Client

# 生成 Prisma Client npx prisma generate # 创建数据库表 npx prisma db push # 查看数据库 npx prisma studio

生成类型安全的数据库客户端

认证模块

实现基于 JWT 的身份认证和授权系统

1. 创建认证模块

# 使用 NestJS CLI 创建模块 nest generate module auth nest generate service auth nest generate controller auth

生成认证模块的基本结构

2. JWT 策略配置

// auth/auth.module.ts import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; import { PassportModule } from '@nestjs/passport'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { JwtStrategy } from './jwt.strategy'; @Module({ imports: [ PassportModule, JwtModule.register({ secret: process.env.JWT_SECRET || 'your-secret-key', signOptions: { expiresIn: '1d' }, }), ], providers: [AuthService, JwtStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {}

配置 JWT 模块和 Passport 策略

3. JWT 策略实现

// auth/jwt.strategy.ts import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: process.env.JWT_SECRET || 'your-secret-key', }); } async validate(payload: any) { return { userId: payload.sub, username: payload.username }; } }

实现 JWT 验证策略

WebSocket 实现

使用 Socket.IO 实现实时双向通信

1. 创建 WebSocket 网关

nest generate gateway chat

生成 WebSocket 网关的基本结构

2. 实现聊天网关

// chat/chat.gateway.ts import { WebSocketGateway, WebSocketServer, SubscribeMessage, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect, MessageBody, ConnectedSocket, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; @WebSocketGateway({ cors: { origin: '*' } }) export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; afterInit(server: Server) { console.log('WebSocket server initialized'); } handleConnection(client: Socket) { console.log(`Client connected: ${client.id}`); } handleDisconnect(client: Socket) { console.log(`Client disconnected: ${client.id}`); } @SubscribeMessage('message') async handleMessage( @MessageBody() payload: { room: string; message: string }, @ConnectedSocket() client: Socket ) { this.server.to(payload.room).emit('message', { id: Date.now(), text: payload.message, sender: client.id, timestamp: new Date(), }); } @SubscribeMessage('joinRoom') handleJoinRoom( @MessageBody() payload: { room: string }, @ConnectedSocket() client: Socket ) { client.join(payload.room); client.emit('joined', { room: payload.room }); } }

实现基本的 WebSocket 事件处理

聊天模块

实现完整的聊天功能,包括消息存储和历史记录

1. 创建聊天服务

nest generate service chat

生成聊天服务的基本结构

2. 实现消息服务

// chat/chat.service.ts import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; @Injectable() export class ChatService { constructor(private prisma: PrismaService) {} async createMessage(data: { content: string; senderId: string; receiverId?: string; room: string; }) { return this.prisma.message.create({ data: { content: data.content, senderId: data.senderId, receiverId: data.receiverId, room: data.room, }, include: { sender: { select: { id: true, username: true, avatar: true, }, }, }, }); } async getMessages(room: string, limit: number = 50) { return this.prisma.message.findMany({ where: { room }, orderBy: { createdAt: 'desc' }, take: limit, include: { sender: { select: { id: true, username: true, avatar: true, }, }, }, }); } }

实现消息存储和查询功能

文件上传

实现文件上传功能,支持图片、视频等多种文件类型

1. 配置文件上传

npm install @nestjs/platform-express multer

安装文件上传所需的依赖

2. 实现文件上传控制器

// files/files.controller.ts import { Controller, Post, UseInterceptors, UploadedFile } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { extname } from 'path'; @Controller('files') export class FilesController { @Post('upload') @UseInterceptors(FileInterceptor('file', { storage: diskStorage({ destination: './uploads', filename: (req, file, callback) => { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9); const ext = extname(file.originalname); callback(null, `${uniqueSuffix}${ext}`); }, }), fileFilter: (req, file, callback) => { const allowedTypes = ['image/jpeg', 'image/png', 'video/mp4', 'application/pdf']; if (allowedTypes.includes(file.mimetype)) { callback(null, true); } else { callback(new Error('不支持的文件类型'), false); } }, limits: { fileSize: 10 * 1024 * 1024, // 10MB }, })) async uploadFile(@UploadedFile() file: Express.Multer.File) { return { originalname: file.originalname, filename: file.filename, size: file.size, mimetype: file.mimetype, url: `/uploads/${file.filename}`, }; } }

实现文件上传功能,包含类型和大小限制

测试策略

编写单元测试和集成测试,确保代码质量

1. 测试配置

# 安装测试依赖 npm install --save-dev @nestjs/testing supertest @types/supertest # 测试脚本 "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage"

配置测试环境和脚本

2. 单元测试示例

// auth/auth.service.spec.ts import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); describe('validateUser', () => { it('should return user data when credentials are valid', async () => { const result = await service.validateUser('test@example.com', 'password'); expect(result).toBeDefined(); }); }); });

编写服务层的单元测试

部署指南

将应用部署到生产环境

1. 生产环境构建

# 构建应用 npm run build # 启动生产服务器 npm run start:prod

构建和启动生产版本

2. Docker 部署

# Dockerfile FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build EXPOSE 3000 CMD ["npm", "run", "start:prod"]

使用 Docker 容器化部署

3. 环境变量配置

# .env.production NODE_ENV=production PORT=3000 DATABASE_URL="postgresql://..." JWT_SECRET="your-production-secret" REDIS_URL="redis://..."

配置生产环境变量