Skip to content

一些小案例

大文件上传

步骤:

  1. 后端定义一个上传接口和一个合并接口
  2. 前端进行分片上传,上传完成后调用合并接口
  3. 后端读取分片文件夹下的文件进行合并
  4. 合并后删除分片文件夹和文件

controller 中定义接口

typescript
// 创建处理文件上传的接口
@Post('upload')
// 使用FilesInterceptor拦截器处理上传的文件,允许一次最多上传20个文件,上传目录为'uploads'
@UseInterceptors(
    FilesInterceptor('files', 20, {
        dest: 'uploads',
    }),
)
/**
   * 上传文件函数
   *
   * @param files 上传的文件数组,由拦截器解析并注入
   * @param body 请求体,可能包含除文件外的其他上传数据
   */
uploadFiles(
    @UploadedFiles() files: Array<Express.Multer.File>,
    @Body() body,
    ) {
        // 打印请求体内容,用于调试和日志记录
        console.log('body', body);
        // 打印上传的文件信息,用于调试和日志记录
        console.log('files', files);
    }

需要安装 multer 的类型

bash
npm install -D @types/multer

main.ts 中开启跨域

ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors(); // 开启跨域
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

在 html 中试着请求 http://localhost:3000/upload 接口上传文件,并进行分片

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>大文件上传</title>
  <script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
<input id="fileInput" type="file" multiple/>
<script>
  const fileInput = document.querySelector('#fileInput');
  const chunkSize = 20 * 1024;
  fileInput.onchange =  async function () {
    const file = fileInput.files[0];

    const chunks = [];
    let startPos = 0;
    while(startPos < file.size) {
      chunks.push(file.slice(startPos, startPos + chunkSize));
      startPos += chunkSize;
    }

    chunks.map((chunk, index) => {
      const data = new FormData();
      data.set('name', file.name + '-' + index)
      data.append('files', chunk);
      axios.post('http://localhost:3000/upload', data);
    })
  }
</script>
</body>
</html>

上传后就会发现文件夹下多了很多分片文件

image-20250325112256889

接下来可以将同一张图片的分片放到同一个目录下,

ts
@Post('upload')
@UseInterceptors(
    FilesInterceptor('files', 20, {
        dest: 'uploads',
    }),
)
uploadFiles(
    @UploadedFiles() files: Array<Express.Multer.File>,
    @Body() body: { name: string },
    ) {
        // 从请求体的name中提取文件名前缀,用于后续的文件处理
        const arr = body.name.match(/(.+)-\d+$/);
        // 根据匹配结果提取文件名前缀,如果没有匹配到则使用原始name
        const fileName = arr ? arr[1] : body.name;
        // 拼接出分块目录路径
        const chunkDir = 'uploads/chunks_' + fileName;
        // 检查分块目录是否存在,如果不存在则创建
        if (!fs.existsSync(chunkDir)) {
            fs.mkdirSync(chunkDir);
        }
        // 将上传的文件复制到分块目录中,并使用原始name作为文件名
        fs.cpSync(files[0].path, chunkDir + '/' + body.name);
        // 删除上传的临时文件
        fs.rmSync(files[0].path);
    }

上传一个 34KB1.jpeg 图片,可以发现上传成功了

image-20250325134641548

如何觉得 chunks 名称会冲突,也可以在html中的name改为随机数

image-20250325135116439

image-20250325135218475

得到分片后,需要将分片合成为一个文件,增加一个 merge 接口,供上传完成后调用

ts
/**
   * 合并分块文件的函数
   * 该函数用于将上传的分块文件合并为一个完整的文件
   *
   * @param name 文件名,通过查询参数传递,用于定位分块文件所在的目录
   */
@Get('merge')
merge(@Query('name') name: string) {
    // 拼接出分块文件所在的目录路径
    const chunkDir = 'uploads/chunks_' + name;

    // 读取分块目录下的所有文件名
    const files = fs.readdirSync(chunkDir);

    let startPos = 0; // 初始化写入起始位置
    files.map((file) => {
        // 拼接每个分块文件的完整路径
        const filePath = chunkDir + '/' + file;
        // 创建可读流以读取分块文件内容
        const stream = fs.createReadStream(filePath);
        // 将可读流的内容写入到最终合并的文件中,并指定写入的起始位置
        stream.pipe(
            fs.createWriteStream('uploads/' + name, {
                start: startPos,
            }),
        );

        // 更新写入起始位置,累加上当前分块文件的大小
        startPos += fs.statSync(filePath).size;
    });
}
html
<script>
  const fileInput = document.querySelector('#fileInput');
  const chunkSize = 20 * 1024;
  fileInput.onchange =  async function () {
    const file = fileInput.files[0];

    const chunks = [];
    let startPos = 0;
    while(startPos < file.size) {
      chunks.push(file.slice(startPos, startPos + chunkSize));
      startPos += chunkSize;
    }

    const tasks = []
    const randomStr = Math.random().toString().slice(2, 8);
    chunks.map((chunk, index) => {
      const data = new FormData();
      data.set('name', randomStr + '-' + file.name + '-' + index)
      data.append('files', chunk);
      tasks.push(axios.post('http://localhost:3000/upload', data));
    })
    await Promise.all(tasks);
    axios.get('http://localhost:3000/merge?name=' + randomStr + '-' + file.name);
  }
</script>

image-20250325141125610

上传完是合成成功的,然后需要将原来的分片进行删除

在 controller 中的 merge 接口中,监听一个 finish 事件,文件合并完成后进行删除操作

ts
let count = 0;
let startPos = 0; // 初始化写入起始位置
files.map((file) => {
    // 拼接每个分块文件的完整路径
    const filePath = chunkDir + '/' + file;
    // 创建可读流以读取分块文件内容
    const stream = fs.createReadStream(filePath);
    // 将可读流的内容写入到最终合并的文件中,并指定写入的起始位置
    stream
        .pipe(
        fs.createWriteStream('uploads/' + name, {
            start: startPos,
        }),
    )
        .on('finish', () => {
        count++;
        if (count === files.length) {
            fs.rm(
                chunkDir,
                {
                    recursive: true,
                },
                () => {},
            );
        }
    });

    // 更新写入起始位置,累加上当前分块文件的大小
    startPos += fs.statSync(filePath).size;
});

功能完成。

jwt 登录注册

创建一个项目

bash
nest new login-register -p npm

安装 typeorm 和 MySQL 所需依赖

bash
npm i typeorm mysql2 @nestjs/typeorm

生成 login 模块

bash
nest g resource login

配置数据库

ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LoginModule } from './login/login.module';
import { Login } from './login/entities/login.entity';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'password',
      database: 'nest_login',
      synchronize: true, // 根据同步建表,也就是当 database 里没有和 Entity 对应的表的时候,会自动生成建表 sql 语句并执行
      logging: true, // 打印生成的 sql 语句
      entities: [Login], // 指定有哪些和数据库的表对应的 Entity
      migrations: [], // 修改表结构之类的 sql
      subscribers: [], // 一些 Entity 生命周期的订阅者,比如 insert、update、remove 前后,可以加入一些逻辑
      poolSize: 10, // 指定数据库连接池中连接的最大数量
      connectorPackage: 'mysql2', // 驱动包
      extra: {
        // 额外发送给驱动包的一些选项
        authPlugin: 'sha256_password',
      },
    }),
    LoginModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

创建 dto

ts
// login.dto
export class LoginDto {
  username: string;
  password: string;
}

// register.dto
export class RegisterDto {
  username: string;
  password: string;
}

创建 entity

ts
import {
  Entity,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
  PrimaryGeneratedColumn,
} from 'typeorm';

@Entity({
  name: 'login',
})
export class Login {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({
    length: 50,
  })
  username: string;

  @Column({
    length: 50,
  })
  password: string;

  @CreateDateColumn({
    comment: '创建时间',
  })
  createdTime: Date;

  @UpdateDateColumn({
    comment: '更新时间',
  })
  updatedTime: Date;
}

添加 登录和注册 接口

ts
import { Controller, Post, Body } from '@nestjs/common';
import { LoginService } from './login.service';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';

@Controller('login')
export class LoginController {
  constructor(private readonly loginService: LoginService) {}

  @Post('register')
  register(@Body() registerDto: RegisterDto) {
    return this.loginService.register(registerDto);
  }

  @Post('login')
  login(@Body() loginDto: LoginDto) {
    return this.loginService.login(loginDto);
  }
}

service 中实现逻辑

ts
import { Body, HttpException, Injectable, Logger } from '@nestjs/common';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
import * as crypto from 'crypto';
import { InjectRepository } from '@nestjs/typeorm';
import { Login } from './entities/login.entity';
import { Repository } from 'typeorm';

function md5(str: string) {
  return crypto.createHash('md5').update(str).digest('hex');
}

@Injectable()
export class LoginService {
  @InjectRepository(Login)
  private readonly loginRepository: Repository<Login>;

  private logger = new Logger();

  async register(@Body() registerDto: RegisterDto) {
    const { username, password } = registerDto;
    const findUser = await this.loginRepository.findOne({
      where: {
        username,
      },
    });
    if (findUser) {
      throw new HttpException('用户已存在', 400);
    }
    const user = new Login();
    user.username = username;
    user.password = md5(password);

    try {
      await this.loginRepository.save(user);
      return '注册成功';
    } catch (e) {
      this.logger.error(e, LoginService);
      return '注册失败';
    }
  }

  async login(@Body() loginDto: LoginDto) {
    const { username, password } = loginDto;
    const findUser = await this.loginRepository.findOne({
      where: {
        username,
      },
    });
    if (!findUser) {
      throw new HttpException('用户不存在', 400);
    }
    if (findUser.password !== md5(password)) {
      throw new HttpException('密码错误', 400);
    }
    return findUser;
  }
}

注册效果

image-20250409105736235

登录效果

image-20250409110034064

接下来就添加注册时候的字段校验,登录后返回token

jwt

安装jwt包

bash
npm install @nestjs/jwt

app.module 中注册jwt模块

ts
@Module({
  imports: [
    LoginModule,
    JwtModule.register({
      global: true,
      secret: 'fan',
      signOptions: { expiresIn: '1d' },
    }),
  ],
}

登录成功后给header中设置token,也可以将token返回给前端

ts
@Inject(JwtService)
private jwtService: JwtService;

@Post('login')
async login(
    @Body() loginDto: LoginDto,
    @Res({ passthrough: true }) res: Response,
    ) {
        const user = await this.loginService.login(loginDto);
        if (user) {
            const token = await this.jwtService.signAsync({
                user: {
                    id: user.id,
                    username: user.username,
                },
            });
            res.setHeader('Authorization', `Bearer ${token}`);
            return '登录成功';
        } else {
            return '登录失败';
        }
    }

登录成功后,header 中会返回token

image-20250409112655315

可以新增几个测试接口,控制它们不登录不能进行请求

使用 Guard 来限制访问

bash
nest g guard login --no-spec --flat

编写守卫

ts
import {
  CanActivate,
  ExecutionContext,
  Inject,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';

@Injectable()
export class LoginGuard implements CanActivate {
  @Inject(JwtService)
  private jwtService: JwtService;

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request: Request = context.switchToHttp().getRequest();

    const authorization = request.header('Authorization') || '';

    const bearer = authorization.split(' ');

    if (!bearer || bearer.length < 2) {
      throw new UnauthorizedException('登录 token 错误');
    }

    const token = bearer[1];

    try {
      const info = this.jwtService.verify(token);
      (request as any).user = info.user;
      return true;
    } catch (e) {
      throw new UnauthorizedException('登录 token 失效,请重新登录');
    }
  }
}

使用守卫,在对应的接口上使用 UseGuards 来使用守卫

tsx
@Get('testToken')
@UseGuards(LoginGuard)
getTest(): string {
    return 'test';
}

测试结果

image-20250409113931910

携带了token才能请求成功

image-20250409114007738

参数校验

可以使用 ValidationPipe + class-validator 来做

bash
npm install class-validator class-transformer

然后给 login 和 register 接口添加 ValidationPipe

ts
@Post('login')
async login(
    @Body(ValidationPipe) loginDto: LoginDto,
    @Res({ passthrough: true }) res: Response,
    ) {...}

@Post('register')
register(@Body(ValidationPipe) registerDto: RegisterDto) {
    return this.loginService.register(registerDto);
}

也可以全局添加 ValidationPipe

ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(process.env.PORT ?? 3000);
}

dto 设置校验规则

ts
// login.dto
import { IsNotEmpty } from 'class-validator';

export class LoginDto {
  @IsNotEmpty()
  username: string;

  @IsNotEmpty()
  password: string;
}

// register.dto
import { IsNotEmpty, IsString, Length, Matches } from 'class-validator';

export class RegisterDto {
  @IsString()
  @IsNotEmpty()
  @Length(6, 30)
  @Matches(/^[a-zA-Z0-9#$%_-]+$/, {
    message: '用户名只能是字母、数字或者 #、$、%、_、- 这些字符',
  })
  username: string;

  @IsString()
  @IsNotEmpty()
  @Length(6, 30)
  password: string;
}

测试效果

image-20250409114721472

关注功能

实现用户之间的关注、被关注和互相关注功能

创建一个项目

bash
nest new following -p npm

安装所需依赖

bash
npm install @nestjs/typeorm typeorm mysql2

生成用户模块

bash
nest g resource user --no-spec

创建用户实体

ts
import {
  Column,
  Entity,
  JoinTable,
  ManyToMany,
  PrimaryGeneratedColumn,
} from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @ManyToMany(() => User, (user) => user.following)
  @JoinTable()
  followers: User[];

  @ManyToMany(() => User, (user) => user.followers)
  following: User[];
}

配置数据库

ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user/entities/user.entity';

@Module({
  imports: [
    UserModule,
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'password',
      database: 'following',
      synchronize: true, // 根据同步建表,也就是当 database 里没有和 Entity 对应的表的时候,会自动生成建表 sql 语句并执行
      logging: true, // 打印生成的 sql 语句
      entities: [User], // 指定有哪些和数据库的表对应的 Entity
      migrations: [], // 修改表结构之类的 sql
      subscribers: [], // 一些 Entity 生命周期的订阅者,比如 insert、update、remove 前后,可以加入一些逻辑
      poolSize: 10, // 指定数据库连接池中连接的最大数量
      connectorPackage: 'mysql2', // 驱动包
      extra: {
        // 额外发送给驱动包的一些选项
        authPlugin: 'sha256_password',
      },
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

初始化一些数据

ts
import { User } from './entities/user.entity';

@Injectable()
export class UserService {
  @InjectEntityManager()
  private readonly entityManager: EntityManager;

  async init() {
    const user2 = new User();
    user2.name = 'user2';

    const user3 = new User();
    user3.name = 'user3';

    const user4 = new User();
    user4.name = 'user4';

    const user5 = new User();
    user5.name = 'user5';

    await this.entityManager.save(User, [user2, user3, user4, user5]);

    const user1 = new User();
    user1.name = 'user1';
    user1.followers = [user2, user3];
    user1.following = [user3, user4, user5];

    await this.entityManager.save(User, user1);
  }
}

image-20250410165713373

user1的id是5,他的关注者有user2和user3,被关注者有user3、user4和user5,共同关注就是user3

image-20250410172242705

接下来实现互相关注功能,安装 redis

bash
npm i redis

生成模块

bash
nest g module redis
nest g service redis

在 RedisModule 创建连接 redis 的 provider,导出 RedisService,并把这个模块标记为 @Global 模块

ts
import { Global, Logger, Module } from '@nestjs/common';
import { RedisService } from './redis.service';
import { createClient } from 'redis';

@Global()
@Module({
  providers: [
    RedisService,
    {
      provide: 'REDIS_CLIENT',
      async useFactory() {
        const logger = new Logger('RedisFactory');
        const client = createClient({
          socket: {
            host: 'localhost',
            port: 6379,
          },
        });

        try {
          await client.connect();
          logger.log('Redis client connected successfully');
          return client;
        } catch (error) {
          logger.error('Failed to connect to Redis', error);
          throw new Error('Redis connection failed');
        }
      },
    },
  ],
  exports: [RedisService],
})
export class RedisModule {}

封装一些方法来操作redis

ts
import { Inject, Injectable } from '@nestjs/common';
import { RedisClientType } from 'redis';

@Injectable()
export class RedisService {
  @Inject('REDIS_CLIENT')
  private redisClient: RedisClientType;

  // 添加数据
  async sAdd(key: string, ...members: string[]) {
    return this.redisClient.sAdd(key, members);
  }

  // 求两个数据的交集
  async sInterStore(newSetKey: string, set1: string, set2: string) {
    return this.redisClient.sInterStore(newSetKey, [set1, set2]);
  }

  // 判断一个元素是否在集合中
  async sIsMember(key: string, member: string) {
    return this.redisClient.sIsMember(key, member);
  }

  // 获取集合中的所有元素
  async sMember(key: string) {
    return this.redisClient.sMembers(key);
  }

  async exists(key: string) {
    const result = await this.redisClient.exists(key);
    return result > 0;
  }
}

user.controller 中实现个方法,用来根据id获取他关注、被关注和互相关注的人

ts
@Get('follow-relationship')
async followRelationShip(@Query('id') id: string) {
    if (!id) {
        throw new BadRequestException('userId 不能为空');
    }
    return this.userService.getFollowRelationship(+id);
}

实现改接口

ts
@Inject(RedisService)
private redisService: RedisService;

async findUserByIds(userIds: string[] | number[]) {
    let users: Array<User | null> = [];

    for (let i = 0; i < userIds.length; i++) {
        const user = await this.entityManager.findOne(User, {
            where: {
                id: +userIds[i],
            },
        });
        users.push(user);
    }

    return users;
}

async getFollowRelationship(userId: number) {
    // 检查 Redis 中是否存在用户的关注者和被关注者信息
    const exists = await this.redisService.exists('followers:' + userId);
    if (!exists) {
        // 如果不存在,则从数据库中查询用户及其关注者和被关注者信息
        const user = await this.entityManager.findOne(User, {
            where: {
                id: userId,
            },
            relations: ['followers', 'following'],
        });

        // 如果用户没有关注者或被关注者,则直接返回空数组
        if (!user?.followers.length || !user?.following.length) {
            return {
            followers: user?.followers,
            following: user?.following,
            followEachOther: [],
        };
    }

    // 将关注者 ID 存储到 Redis 中
    await this.redisService.sAdd(
        'followers:' + userId,
        ...user.followers.map((item) => item.id.toString()),
    );

    // 将被关注者 ID 存储到 Redis 中
    await this.redisService.sAdd(
        'following:' + userId,
        ...user.following.map((item) => item.id.toString()),
    );

    // 计算互相关注的用户 ID 并存储到 Redis 中
    await this.redisService.sInterStore(
        'follow-each-other:' + userId,
        'followers:' + userId,
        'following:' + userId,
    );

    // 获取互相关注的用户 ID
    const followEachOtherIds = await this.redisService.sMember(
        'follow-each-other:' + userId,
    );

    // 根据互相关注的用户 ID 查询用户信息
    const followEachOtherUsers = await this.findUserByIds(followEachOtherIds);

    // 返回用户的关注者、被关注者和互相关注的用户信息
    return {
        followers: user.followers,
        following: user.following,
        followEachOther: followEachOtherUsers,
    };
    } else {
        // 如果 Redis 中存在用户的关注者和被关注者信息,则从 Redis 中获取
        const followerIds = await this.redisService.sMember(
            'followers:' + userId,
        );

        // 根据关注者 ID 查询用户信息
        const followUsers = await this.findUserByIds(followerIds);

        const followingIds = await this.redisService.sMember(
            'following:' + userId,
        );

        // 根据被关注者 ID 查询用户信息
        const followingUsers = await this.findUserByIds(followingIds);

        const followEachOtherIds = await this.redisService.sMember(
            'follow-each-other:' + userId,
        );

        // 根据互相关注的用户 ID 查询用户信息
        const followEachOtherUsers = await this.findUserByIds(followEachOtherIds);

        // 返回用户的关注者、被关注者和互相关注的用户信息
        return {
            followers: followUsers,
            following: followingUsers,
            followEachOtherUsers: followEachOtherUsers,
        };
    }
}

得到的结果

image-20250410173159450

实现关注功能,传入关注人和被关注人的id

ts
@Get('follow')
async follow(@Query('id1') userId1: string, @Query('id2') userId2: string) {
    await this.userService.follow(+userId1, +userId2);
    return '关注成功';
}
ts
async follow(userId: number, userId2: number) {
    const user = await this.entityManager.findOne(User, {
        where: {
            id: userId,
        },
        relations: ['followers', 'following'],
    });

    const user2 = await this.entityManager.findOne(User, {
        where: {
            id: userId2,
        },
    });

    if (user2) {
        user?.followers.push(user2);
    }

    if (user) {
        await this.entityManager.save(User, user);
    }

    const exists = await this.redisService.exists('followers:' + userId);

    if (exists) {
        await this.redisService.sAdd('followers:' + userId, userId2.toString());
        await this.redisService.sInterStore(
            'follow-each-other:' + userId,
            'followers:' + userId,
            'following:' + userId,
        );
    }

    const exists2 = await this.redisService.exists('following:' + userId2);

    if (exists2) {
        await this.redisService.sAdd('following:' + userId2, userId.toString());
        await this.redisService.sInterStore(
            'follow-each-other:' + userId2,
            'followers:' + userId2,
            'following:' + userId2,
        );
    }
}

请求 follow 接口 http://localhost:3000/user/follow?id1=1&id2=5

重新请求查看互相关注接口 http://localhost:3000/user/follow-relationship?id=5,互相关注里多了user2

image-20250410175445500

聊天室

创建项目

bash
nest new chat-room-backend -p npm

安装typeorm和mysql2

bash
npm install @nestjs/typeorm typeorm mysql2

配置数据库

ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserModule } from './user/user.module';

// 应用程序需要导入的模块
const imports = [
  TypeOrmModule.forRoot({
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: 'root',
    password: 'password',
    database: 'chat_room',
    synchronize: true, // 根据同步建表,也就是当 database 里没有和 Entity 对应的表的时候,会自动生成建表 sql 语句并执行
    logging: true, // 打印生成的 sql 语句
    entities: [], // 指定有哪些和数据库的表对应的 Entity
    migrations: [], // 修改表结构之类的 sql
    subscribers: [], // 一些 Entity 生命周期的订阅者,比如 insert、update、remove 前后,可以加入一些逻辑
    poolSize: 10, // 指定数据库连接池中连接的最大数量
    connectorPackage: 'mysql2', // 驱动包
    extra: {
      // 额外发送给驱动包的一些选项
      authPlugin: 'sha256_password',
    },
  }),
];

@Module({
  imports: [...imports, UserModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

生成用户模块

bash
nest g resource user --no-spec

编写用户实体

ts

Released under the MIT License.