夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 任务管理日历

  • 无代码平台

  • 图书管理系统

    • 图书管理系统:需求分析和原型图
    • 图书管理系统:用户模块后端开发
      • 创建一个 user 模块:
      • 添加注册接口
      • 启用 ValidationPipe 校验
      • 模拟数据
      • 实现注册功能
      • 实现登录功能
      • 总结
    • 图书管理系统:图书模块后端开发
    • 图书管理系统:用户模块前端开发
    • 图书管理系统:图书搜索前端开发
    • 图书管理系统:图书增删改前端开发
    • 图书管理系统:项目总结
  • 《全栈项目》
  • 图书管理系统
神说要有光
2025-03-10
目录

图书管理系统:用户模块后端开发

我们做了需求分析,并画了原型图,这节开始写下后端代码。

创建个 nest 项目:

nest new book-management-system-backend
1

进入项目,把服务跑起来:

npm run start:dev
1

浏览器访问下:

服务跑起来了。

然后我们先实现下登录、注册。

# 创建一个 user 模块:

nest g resource user --no-spec
1

--no-spec 是不生成单测代码。

可以看到,src 下多了 user 模块的代码,并自动在 AppModule 里引入了:

# 添加注册接口

然后我们在 UserConstructor 添加注册接口:

import { Controller, Post, Body} from '@nestjs/common';
import { UserService } from './user.service';
import { RegisterUserDto } from './dto/register-user.dto';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post('register')
  register(@Body() registerUserDto: RegisterUserDto) {
    console.log(registerUserDto);
    return 'done';
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

路由是 /user/register 的 POST 接口。

创建 dto/register-user.dto.ts

export class RegisterUserDto {
    username: string;
    password: string;
}
1
2
3
4

然后在 postman 里调用下这个接口:

可以看到,服务端接收到了请求体的参数,并且返回了响应。

# 启用 ValidationPipe 校验

我们还要对参数做一些校验,校验请求体的参数需要用到 ValidationPipe

在 main.ts 里全局启用 ValidationPipe:

app.useGlobalPipes(new ValidationPipe());
1

然后安装用到的包:

npm install --save class-transformer class-validator
1

之后就可以在 dto 里添加 class-validator 的校验规则了:

import { IsNotEmpty, MinLength } from "class-validator";

export class RegisterUserDto {
    @IsNotEmpty({ message: '用户名不能为空' })
    username: string;

    @IsNotEmpty({ message: '密码不能为空' })
    @MinLength(6, { message: '密码最少 6 位'})
    password: string;
}
1
2
3
4
5
6
7
8
9
10

试一下:

校验生效了。

现在接收到的参数是普通对象:

在 ValidationPipe 指定 transform: true 之后,就会转为 dto 的实例了:

然后我们来实现下具体的注册逻辑。

# 模拟数据

我们还没有学习数据库,这里就用 json 文件来存储数据吧。

创建一个 db 模块:

nest g module db
nest g service db
1
2

这里没指定 --no-spec 也没生成单测文件是因为我在 nest-cli.json 里配了:

我们希望 DbModule 用的时候可以传入 json 文件的存储路径:

在 UserModule 里用的时候,path 是 users.json,在 BookModule 用的时候,path 是 books.json

这种需要传参的模块就是动态模块了。

而且不同模块里用传不同的参数,我们会用 register 作为方法名。

写下 db.module.ts

import { DynamicModule, Module } from '@nestjs/common';
import { DbService } from './db.service';

export interface DbModuleOptions {
  path: string
}

@Module({})
export class DbModule {
  static register(options: DbModuleOptions ): DynamicModule {
    return {
      module: DbModule,
      providers: [
        {
          provide: 'OPTIONS',
          useValue: options,
        },
        DbService,
      ],
      exports: [DbService]
    };
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

在 register 方法里接收 options 参数,返回 providers、exports 等模块配置。

把传入的 options 用 useValue 来声明为 provider,token 为 OPTIONS。

在 DbService 里实现下 read、write 方法:

import { Inject, Injectable } from '@nestjs/common';
import { DbModuleOptions } from './db.module';
import { access, readFile, writeFile } from 'fs/promises';

@Injectable()
export class DbService {

    @Inject('OPTIONS')
    private options: DbModuleOptions;

    async read() {
        const filePath  = this.options.path;

        try {
            await access(filePath)
        } catch(e) {
            return [];
        }

        const str = await readFile(filePath, {
            encoding: 'utf-8'
        });
        
        if(!str) {
            return []
        }

        return JSON.parse(str);
        
    }

    async write(obj: Record<string, any>) {
        await writeFile(this.options.path, JSON.stringify(obj || []), {
            encoding: 'utf-8'
        });
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

read 方法就是读取文件内容,然后 JSON.parse 一下转为对象。如果文件不存在就返回孔数组

write 方法是 JSON.stringify 之后写入文件。

DbModule 封装好了,接下来就可以继续写注册逻辑了:

在 UserController 里调用下 UserService 的 register 方法:

@Post('register')
async register(@Body() registerUserDto: RegisterUserDto) {
    return this.userService.register(registerUserDto);
}
1
2
3
4

# 实现注册功能

然后在 UserService 里实现这个方法:

import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { RegisterUserDto } from './dto/register-user.dto';
import { DbService } from 'src/db/db.service';
import { User } from './entities/user.entity';

@Injectable()
export class UserService {

    @Inject(DbService)
    dbService: DbService;

    async register(registerUserDto: RegisterUserDto) {
        const users: User[] = await this.dbService.read();
        
        const foundUser = users.find(item => item.username === registerUserDto.username);

        if(foundUser) {
            throw new BadRequestException('该用户已经注册');
        }

        const user = new User();
        user.username = registerUserDto.username;
        user.password = registerUserDto.password;
        users.push(user);

        await this.dbService.write(users);
        return user;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

注入 DbService 来读写数据。

首先读取出 users 的数据,如果找到当前 username,那就返回 400 的响应提示用户已注册。

否则创建一个新的用户,写入文件中。

user.entity.ts 也要改下:

export class User {
    username: string;
    password: string;
}
1
2
3
4

在 postman 里调用下试试:

注册成功,创建了 users.json 文件,并写入了数据。

再注册一个:

也没问题。

再次注册同样的 username 会返回 400

# 实现登录功能

注册完成了,然后再实现下登录:

@Post('login')
async login(@Body() loginUserDto: LoginUserDto) {
  return this.userService.login(loginUserDto);
}
1
2
3
4

添加 user/dto/login-user.dto.ts

import { IsNotEmpty, MinLength } from "class-validator";

export class LoginUserDto {
    @IsNotEmpty({ message: '用户名不能为空' })
    username: string;

    @IsNotEmpty({ message: '密码不能为空' })
    @MinLength(6, { message: '密码最少 6 位'})
    password: string;
}
1
2
3
4
5
6
7
8
9
10

和注册的校验规则一样。

async login(loginUserDto: LoginUserDto) {
    const users: User[] = await this.dbService.read();

    const foundUser = users.find(item => item.username === loginUserDto.username);

    if(!foundUser) {
        throw new BadRequestException('用户不存在');
    }

    if(foundUser.password !== loginUserDto.password) {
        throw new BadRequestException('密码不正确');
    }

    return foundUser;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

测试下:

当不满足校验规则时:

当用户不存在时:

当密码不正确时:

登录成功时:

这样,我们登录注册就都完成了。

案例代码上传了小册仓库 (opens new window)

# 总结

这节我们实现了用户模块的登录、注册功能。

通过读写文件实现了数据存储,封装了一个动态模块,用到时候传入 path,然后模块内的 service 里会读写这个文件的内容,通过 JSON.parse、JSON.stringify 和对象互转。

通过 ValidationPipe + class-validator 实现了 dto 的校验。

然后实现了注册和登录的业务逻辑。

这样,用户模块的功能就完成了。

编辑 (opens new window)
上次更新: 2025/5/7 18:16:53
图书管理系统:需求分析和原型图
图书管理系统:图书模块后端开发

← 图书管理系统:需求分析和原型图 图书管理系统:图书模块后端开发→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式