重构《node+React实战:从0到1实现记账本》记录(三)用户模块:由注册验证码功能开发而搭建后端服务

218 阅读13分钟

前言

我是前端,该项目是用来学习全栈的练手项目,所以我的开发不同于公司开发,主要是学习为主。从页面开始开发,先写一个页面,然后写这个页面后面所需要的所有功能,包括后端服务,数据库,缓存等。这样我们看到一个页面的时候,不仅仅是一个页面,而是能想到这个页面的每一个功能从前端到后端功能的具体实现。

首先,我们的系统是面向多用户的,也就是一个纯正的 C 端项目,任何人可以通过网站,注册一个账号,登录使用该系统.

所以我们就从注册页面开始开发,先完成注册页面和一个验证码发送功能,顺便也把相关的后端项目、MySQL数据库、redis缓存等从零开始搭建。

引入图标

图标是每个前端必不可少的,我们可以直接引入阿里的图标库,写成公共组件,然后在页面中引入使用。

src/config.ts 中添加如下代码:

import { Icon } from 'antd'
....

export default Icon.createFromIconfont('//at.alicdn.com/t/c/font_4672973_oo70pxoae9j.js')

src/page/Index/index.tsx 中引入图标:

import Icon from '@/config'
...
<Button theme="primary" onClick={()=> { }}>按钮</Button>
<br />
<Icon type="icon-youxiang" />

出现下面效果就是引入成功了。

image.png

注册页面

首先在 src/page 新建 Login 文件夹,在文件夹添加两个文件 index.tsxindex.less。我们先把注册页面的静态页面写出来,首先给 index.tsx 添加代码如下:

export default function Login() {
  return <div className="auth">注册</div>;
}

为它添加一个路由配置,在 src/router/index.tsx 中添加如下代码:

import Login from '@/page/Login'
...

  {
    path: "/login",
    component: Login,
  }

重启服务,如下所示代表登录注册页面创建成功了:

image.png

接下来为 src/page/Login/index.tsx 添加静态页面代码:

// src/page/Login/index.tsx
import { List, Input, Button } from "zarm";
import Icon from "@/config";
import "./index.less";

export default function Login() {
  return (
    <div className="auth">
      <div className="head" />
      <div className="tab">
        <span>注册</span>
      </div>
      <div className="form">
        <List bordered={false}>
          <List.Item prefix={<Icon type="icon-youxiang" />}>
            <Input placeholder="请输入邮箱作为用户名" />
          </List.Item>

          <List.Item prefix={<Icon type="icon-yanzhengma" />}>
            <Input placeholder="请输入验证码" />
            <Button size="xs" className="get" theme="primary">
              获取验证码
            </Button>
          </List.Item>
          <List.Item prefix={<Icon type="icon-mima" />}>
            <Input placeholder="请输入密码" type="password" />
          </List.Item>
          <List.Item prefix={<Icon type="icon-mima" />}>
            <Input placeholder="请再次输入密码" type="password" />
          </List.Item>
        </List>
      </div>
      <div className="operation">
        <Button block theme="primary">
          注册
        </Button>
      </div>
    </div>
  );
}

增加样式文件 src/page/Login/index.less 如下:

.auth {
  min-height: 100vh;
  background-image: linear-gradient(
    217deg,
    #6fb9f8,
    #3daaf85e,
    #49d3fc1a,
    #3fd3ff00
  );

  .head {
    height: 200px;
    background: url("//s.yezgea02.com/1616032174786/cryptocurrency.png")
      no-repeat center;
    background-size: 120%;
    border-bottom-left-radius: 12px;
    border-bottom-right-radius: 12px;

    img {
      width: 34px;
      margin: 15px 0 0 15px;
    }
  }

  .tab {
    color: #597fe7;
    padding: 30px 24px 10px 24px;

    > span {
      margin-right: 10px;
      font-size: 14px;
      font-weight: bold;

      &.avtive {
        font-size: 20px;
        border-bottom: 2px solid #597fe7;
        padding-bottom: 6px;
      }
    }
  }

  .form {
    padding: 0 6px;

    .za-list {
      .za-list-item {
        background-color: transparent;

        &::after {
          border-top: none;
        }
      }
    }
  }

  .operation {
    padding: 10px 24px 0 24px;

    .agree {
      display: flex;
      align-items: center;
      margin-bottom: 10px;

      label {
        margin-left: 10px;
        font-size: 14px;
      }
    }
  }

  .get {
    width: 160px;
  }
}

PS:这里的样式也包括登录页面的样式,后面就不会写了,就用这个样式。

刷新浏览器页面,展示如下所示:

image.png

此时我们已经完成注册页面需要的内容。

给页面加上相应的逻辑,首先是账号、验证码、密码、确认密码:

...
const [username, setUsername] = useState<string>(""); // 账号
const [verify, setVerify] = useState<string>(""); // 验证码
const [password, setPassword] = useState<string>(""); // 密码
const [confirmPassword, setConfirmPassword] = useState<string>(""); // 确认密码
...
<Input
  placeholder="请输入邮箱作为用户名"
  value={username}
  onChange={(e: { target: { value: SetStateAction<string> } }) =>
    setUsername(e.target.value)
  }
/>
...
<Input
  ref={verifyRef}
  placeholder="请输入验证码"
  value={verify}
  onChange={(e: { target: { value: SetStateAction<string> } }) =>
    setVerify(e.target.value)
  }
/>
...
<Input
  placeholder="请输入密码"
  type="password"
  value={password}
  onChange={(e: { target: { value: SetStateAction<string> } }) =>
    setPassword(e.target.value)
  }
/>
...
<Input
  placeholder="请再次输入密码"
  type="password"
  value={confirmPassword}
  onChange={(e: { target: { value: SetStateAction<string> } }) =>
    setConfirmPassword(e.target.value)
  }
/>

当输入框内容修改的时候,onChange 会被触发,接受的回调函数参数,便是变化的输入值,此时我们将其保存在声明的变量中。

PS: 这里我本来是不打算用 useState 的,因为useState 改变会导致页面组件重新渲染,而zarm官网Input 组件的官方这样写,由于页面表单字段少,先这样吧。实现优先,后期看看怎么优化。

接下来就是获取验证码功能。

获取验证码功能

验证码(CAPTCHA)是一种用于区分计算机和人类用户的技术,它的主要功能包括:

  1. 防止自动化攻击:验证码可以有效防止自动化的机器人程序进行垃圾注册、恶意登录、刷票等非法操作,这些行为通常是由自动化脚本执行的。
  2. 提高安全性:在进行敏感操作,如账户登录、密码重置、金融交易等时,验证码可以作为一道额外的安全验证,确保操作是由账户的真正拥有者执行的。
  3. 保护网站资源:通过限制非正常用户的访问,验证码有助于减轻网站服务器的负担,保护网站免受恶意流量的冲击。
  4. 供用户体验:尽管验证码可能会给用户带来一定的不便,但它们在维护网络环境的公平性和安全性方面发挥着重要作用,是现代互联网安全的一个基本组成部分。

而验证码的种类繁多,包括传统的文本验证码、图片验证码、滑块验证码、语义验证码等,它们各自有着不同的特点和适用场景。

今天我们写的是文本验证码,也就是验证码是一串数字。由后端生成验证码,然后发送到用户邮箱中,成为用户注册信息的一部分。大致的步骤如下:

  1. 用户用邮箱地址作为用户名输入,然后点击获取验证码。
  2. 前端验证用户名是否为空,是否为邮箱格式。
  3. 后端验证用户名是否为空,是否为邮箱格式。
  4. 在数据库中查找是否已存在该用户名。
  5. 后端生成验证码。
  6. 将验证码发送到用户邮箱中。
  7. 将验证码存储到redis中。
  8. 后端返回前端做出提示

详细泳道图:

image.png

前端验证

新建src/utils/verify.ts,写入验证邮箱的函数并导出:

// src/utils/verify.ts
export function validateEmail(email: string) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

引入验证函数并在“获取验证码”按钮上添加点击请求事件,验证用户名是否为邮箱格式:

// src/page/Login/index.tsx
....
import { List, Input, Button, Toast } from "zarm";
....
import { validateEmail } from "@/utils/verify";

....
const [confirmPassword, setConfirmPassword] = useState<string>(""); 

const handleGetCaptcha = async() => {
  // 验证用户名是否为空
  if (!username) {
    Toast.show('请输入邮箱作为用户名')
    return;
  }
  // 验证用户名是否为邮箱格式
  if (!validateEmail(username)) {
    Toast.show('请输入正确的邮箱,请重新输入')
    return
  }
};
....
<Button
  size="xs"
  className="get"
  theme="primary"
  onClick={() => handleGetCaptcha()}
>
  获取验证码
</Button>

测试一下验证:

vaildateEmail.gif

后端验证

前端接口请求

src/utils/request.ts文件,写入接口函数,这里我们用get请求:

// src/utils/request.ts

/**下面写接口函数以便于统一管理**/

// 注册页面获取验证码接口
export async function getCaptcha(username: string) {
  return await axiosInstance.get("/user/captcha", {
    params: {
      username,
    },
  });
}

src/page/Login/index.tsx 中引入写入接口函数:

// src/page/Login/index.tsx
....
import { getCaptcha } from "@/utils";  // 引入get请求

....
const handleGetCaptcha = async() => {

  // 验证邮箱格式
  if (!username) {
    Toast.show('请输入邮箱')
    return;
  }

  if (!validateEmail(username)) {
    Toast.show('请输入正确的邮箱,请重新输入')
    return
  }

  const res = await getCaptcha(username);
  console.log(res);
};

在用户名中输入一个正确格式邮箱,点击获取验证码,在调试窗口中看到请求:

image.png

如图所示,看到了请求。但是没有任何响应,这是因为我们没有后端项目。接下来就来生成后端项目。

生成后端项目

生成后台项目,然后运行。

nest new account-nest-backend

配置 VSCode 调试

在该项目中,我们使用VSCode中的launch.json来配置调试。在项目根目录下,创建一个.vscode文件夹,并在其中创建launch.json文件,写入以下配置:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "name": "nest debug",
      "request": "launch",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["run-script", "start:dev"],
      "console": "integratedTerminal",
      "skipFiles": ["<node_internals>/**"],
      "program": "${workspaceFolder}/src/main.ts",
      "outFiles": ["${workspaceFolder}/dist/**/*.js"]
    }
  ]
}

配置完成后,保存后在vscode中按F5键,出现如下所示:

image.png

右边出了一个新的调试,左边和你直接运行npm run start:dev 一样,但是它会自动附加调试。

并且在vscode的右上角出现了调试的控制台:

image.png

这里我遇到一个问题,那就是在node版本中,v22 版本不支持调试,v21 版本可以。所以我用的是v21 版本。具体原因待查

src/app.controller.ts 中打一个断点,打开浏览器,输入localhost://3000,会发现,断点被触发了,左侧也查看相关的数据。

image.png

至此,我们就可以用vscode来调试nest后端项目了。

生成user模块

先在src/main.ts中写入 app.enableCors();用来去除跨域问题

// src/main.ts
....
const app = await NestFactory.create(AppModule);
app.enableCors();

然后在 nest-cli.json 里添加 generateOptions,设置 spec 为 false

// nest-cli.json
{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "generateOptions":{
    "spec": false
  },
  "sourceRoot": "src",
  "compilerOptions": {
    "deleteOutDir": true
  }
}

这样生成代码的时候不会生成测试代码

由于我们设计注册属于用户模块,我们在后端生成一个user的nest模块

nest g resource user

image.png

src/user/user.controller.ts 中写入获取验证码的接口,代码如下:

// src/user/user.controller.ts

import { Controller, Get, Query } from '@nestjs/common';
import { UserService } from './user.service';

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

  @Get('captcha')
  getCaptcha(@Query() params) {
    console.log(params, '获取验证码');
  }
}

再次点击获取验证码的接口,在调试窗口中看到请求:

image.png

看到请求成功,再看看后台项目是否有打印请求参数:

image.png

请求体校验

前端发给后端的数据,不仅仅是自己要检查一下,后端也需要检查。被检查的数据叫请求体(DTO文件传输对象),我们来做后端的请求体检查

首先生成一个验证码接口请求体的 dto。在src/user下创建目录dto,然后创建一个captcha.dto.ts文件,写入以下代码:

export class CaptchaDto {
  username: string;
}

src/user/user.controller.ts 中导入 CaptchaDto,并在 getCaptcha 方法中使用 @Query 装饰器来获取请求体:

// src/user/user.controller.ts
...
import { CaptchaDto } from './dto/captcha.dto';
...
@Get('captcha')
getCaptcha(@Query() params: CaptchaDto) {
  console.log(params, '获取验证码');
  return 'success'
}

然后加一下 ValidationPipe,来对请求体做校验。

npm i --save class-validator class-transformer

全局启用 ValidationPipe:

// src/main.ts
...
import { ValidationPipe } from '@nestjs/common';
...
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());

然后在 src/user/dto/captcha.dto.ts 中写入校验规则和提示语,来对请求体做校验:

// src/user/dto/captcha.dto.ts
import { IsEmail, IsNotEmpty } from 'class-validator';

export class CaptchaDto {
  @IsNotEmpty({ message: '用户名不能为空' })
  @IsEmail(
    {},
    {
      message: '用户名必须为邮箱格式,以方便接收验证码',
    },
  )
  username: string;
}

然后在用户名随便输入不是邮箱地址的字符,前端的验证注释。 再次点击获取验证码的接口,在调试窗口中看到请求:

image.png

可以看到,请求体在后端验证,并且返回了错误信息。输入一个正确的邮箱地址,再次点击获取验证码

image.png

可以看到,请求体在后端通过验证,并且返回了成功信息。

接下来就是要查看该用户(邮箱地址)是否注册过了。也就是在数据库用户表中查询该用户是否存在。

先部署数据库,我们这里用的是MySQL

数据库查询

创建数据库

打开Docker Desktop,下载一个mysql镜像(该操作需要翻墙)。

点击这里搜索:

image.png

输入mysql,下方出来后点击Pull

image.png

正在下载:

image.png

下载完成后点击这里,创建一个容器。

image.png

输入参数:

image.png

这里端口 3306 就是 client 连接 mysql 的端口。

(另一个 33060 端口是 mysql8 新加的管理 mysql server 的端口,这里用不到)

指定 volume,用本地目录作为数据卷挂载到容器的 /var/lib/mysql 目录,这个是 mysql 保存数据的目录。

(这里的 D:\MYSQL 是我本地的一个目录,任意目录都行。)

MYSQL_ROOT_PASSWORD,也就是 client 连接 mysql server 的密码。

然后点击 Run 出现下面的这个就表示创建成功了:

image.png

MySQL Workbench 连接并新建数据库

打开MySQL Workbench,点击界面中的+号,选择Standard TCP/IP over SSH,然后填写以下信息:

image.png

输入密码

image.png

然后点击 Test Connection,测试连接是否成功。

连接成功后就点击 ok 出现该页面就表示成功了

image.png

PS: 这里用MySQL Workbench 连接而不用DBeaver的原因是,DBeaver 连接不上,貌似是和MySQL Workbench 版本有关。

创建一个新的database(或者叫schema)输入一个名称,然后点击 Apply

image.png

database2.png

至此,我们可以在MySQL Workbench中查看、修改数据库了。

nest 连接数据库

nest中操作MySQL数据库,我们选择用mysql2typeorm来操作MySQL。

安装这两个对应相关的包

npm install --save @nestjs/typeorm typeorm mysql2

在 AppModule 中引入 TypeOrmModule 模块,并配置数据库连接信息:

// src/app.module.ts
....
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: "mysql",
      host: "localhost",
      port: 3306,
      username: "root",
      password: "guang",  // 这里的密码是你在创建容器的时候指定的密码
      database: "account", // 这里的数据库名称是你在创建数据库的时候指定的名称
      synchronize: true,
      logging: true,
      entities: [],
      poolSize: 10,
      connectorPackage: 'mysql2',
      extra: {
          authPlugin: 'sha256_password',
      }
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

生成数据库用户表

接下来我们要创建一个简单的数据库用户表,我们这里用的是typeorm

添加 src/user/entities 目录,新建 1 个实体 User。创建四个字段,id、username、createTime、updateTime(id、用户名、创建时间和更新时间)。

// src/user/entities/user.entity.ts
import {
  Entity, 
  PrimaryGeneratedColumn, 
  Column, 
  CreateDateColumn,
  UpdateDateColumn
} from "typeorm";

@Entity({
  name: 'users'
})
export class User {

  @PrimaryGeneratedColumn()
  id: number;

  @Column({
    length: 50,
    comment: '用户名'
  })
  username: string;

  @CreateDateColumn()
  createTime: Date;

  @UpdateDateColumn()
  updateTime: Date;
}

然后在 src/user/user.module.ts 中引入 User 实体:

image.png

点击F5,出现如下所示,就表示连接成功了。

image.png

去MySQL Workbench中查看数据库,就可以看到我们创建的表并生成字段了。

image.png

接下来就是数据库查询用户、生成验证码、发送验证码,把验证码写入Redis等操作.

查询用户

src/user/user.controller.ts 中修改接口:

// src/user/user.controller.ts
...
import { User } from './entities/user.entity';
...
@Get('captcha')
async getCaptcha(@Query() params: CaptchaDto) {
  return await this.userService.getCaptcha(params.username);
}

src/user/user.service.ts 中引入typeormUser 实体并查询:

// src/user/user.service.ts
...
import { Injectable, BadRequestException } from '@nestjs/common';
import { InjectEntityManager } from '@nestjs/typeorm';
import { EntityManager } from 'typeorm';
import { User } from './entities/user.entity';
...

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

  async getCaptcha(username: string) {
    const findUser = await this.entityManager.findOne(User, {
      where: {
        username
      }
    })

    if (findUser) {
      throw new BadRequestException('该用户已注册了')
    }
  }
}

生成验证码

// src/user/user.service.ts
...
if (findUser) {
  throw new BadRequestException('该用户已注册了')
}

const captcha  = Math.random().toString().slice(2,8) // 随机生成6位数字验证码

发送验证码邮件

封装模块

封装一个email模块

nest g resource email

image.png

安装发送邮件用的包:

npm i --save nodemailer

在 EmailService 里实现 sendMail 方法

// src/email/email.service.ts
import { Injectable } from '@nestjs/common';
import { createTransport, Transporter } from 'nodemailer';

@Injectable()
export class EmailService {
  transporter: Transporter;

  constructor() {
    this.transporter = createTransport({
      host: 'smtp.qq.com',
      port: 587,
      secure: false,
      auth: {
        user: '你的邮箱地址',
        pass: '你的授权码',
      },
    });
  }

  async sendMail({ to, subject, html }) {
    await this.transporter.sendMail({
      from: {
        name: '记账小助手',
        address: '你的邮箱地址',
      },
      to,
      subject,
      html,
    });
  }
}

获取授权码

我在这里用的 qq 邮箱(主要是免费,练手用的),你也可以换成别的邮箱。或者你也可以买专门发邮件的服务,填写对应的 smtp 服务的域名和端口就好了。

现在我就用我的QQ邮箱为例。登录QQ邮箱,点击设置:

image.png

点击账号:

image.png

下划找到 POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务,然后点击开启服务:

image.png

点击管理服务:

image.png

点击生成授权码:

image.png

这里会让你验证一下:

image.png

验证完就复制下面的授权码:

image.png

发送验证码邮件

把QQ邮箱和授权码填写在src/email/email.service.ts中,并在src/email/email.module.ts把 EmailModule 声明为全局的,并且导出 EmailService:

// src/email/email.module.ts
import { Global, Module } from '@nestjs/common';
import { EmailService } from './email.service';
import { EmailController } from './email.controller';

@Global() // 声明为全局的
@Module({
  controllers: [EmailController],
  providers: [EmailService],
  exports: [EmailService], // 导出 EmailService
})
export class EmailModule {}

src/user/user.service.ts中引入 EmailService,并在 getCaptcha 方法中调用 sendMail 方法发送邮件:

// src/user/user.service.ts
import { Injectable, BadRequestException, Inject } from '@nestjs/common';
...
import { EmailService } from 'src/email/email.service';

...
@InjectEntityManager()
entityManager: EntityManager;

@Inject(EmailService)
emailService: EmailService;

....
const captcha = Math.random().toString().slice(2, 8);

try {
  await this.emailService.sendMail({
    to: username,
    subject: '注册验证码',
    html: `<p>你的注册验证码是 ${captcha}</p>`,
  });
} catch (error) {
  console.log(error);
  throw new BadRequestException('验证码获取失败');
}

由于用了 async/await,无法监听到错误,所以用了try...catch...;

在生成验证码写一个console.log把验证码打印出来,然后在前端写一个你自己的邮箱,点击获取验证码,测试一下是否能发送邮件:

image.png

看到生成的是788857,然后去邮箱中查看:

image.png

邮箱中也是788857,说明发送成功。

接下来是把验证码写入Redis

验证码写入 Redis

封装模块和安装包

我们需要先封装个 redis 模块。

nest g module redis
nest g service redis

image.png

安装 redis 的包:

npm i --save redis

配置本地 Redis 服务

打开 Docker Desktop,下载一个 redis 镜像(注意下载需要翻墙)。

image.png

下载完成后,点击运行:

image.png

填写一些容器信息

image.png

端口映射就是把主机的 6379 端口映射到容器内的 6379 端口,这样就能直接通过本机端口访问容器内的服务了。

指定数据卷,用本机的任意一个目录挂载到容器内的 /data 目录,这样数据就会保存在本机。

跑起来之后是这样的:

image.png

nest 连接 Redis 并生成公共模块

添加连接 redis 的 provider 到 src/redis/redis.module.ts

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

@Global()
@Module({
  providers: [
    RedisService,
    {
      provide: 'REDIS_CLIENT',
      async useFactory() {
        const client = createClient({
            socket: {
                host: 'localhost',
                port: 6379
            },
            database: 1
        });
        await client.connect();
        return client;
      }
    }
  ],
  exports: [RedisService] // 导出 RedisService以便调用

})
export class RedisModule {}

这里用 @Global() 把它声明为全局模块,这样只需要在 AppModule 里引入,别的模块不用引入也可以注入 RedisService 了。

然后写下 RedisService

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

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

  async get(key: string) {
    return await this.redisClient.get(key);
  }

  async set(key: string, value: string | number, ttl?: number) {
    await this.redisClient.set(key, value);

    if (ttl) {
      await this.redisClient.expire(key, ttl);
    }
  }
}

注入 redisClient,实现 get、set 方法,set 方法支持指定过期时间。

在 Redis 中写入验证码

src/user/user.service.ts 中引入 RedisService,用户名加前缀register_captcha_key,验证码做值在 getCaptcha 方法中调用 set 方法写入:

// src/user/user.service.ts
...
import { EmailService } from'src/email/email.service';
import { RedisService } from'src/redis/redis.service';

...
@Inject(EmailService)
emailService: EmailService;

@Inject(RedisService)
redisService: RedisService;

....
await this.emailService.sendMail({
  to: username,
  subject: '注册验证码',
  html: `<p>你的注册验证码是 ${captcha}</p>`,
});

await this.redisService.set(`register_captcha_${username}`, captcha, 5 * 60);
return '验证码获取成功';

现在再测试一下,点击前端获取验证码:

image.png

后端看到验证码是810184,然后去邮箱里中查看:

image.png

同上,再去Redis中查看,打开Redis Insight

image.png

image.png

三者一致,说明写入成功。

接下来就是前端提示了

前端提示

后端返回前端的数据如下:

image.png

只需要在前端src/page/Login/index.tsx做一个简单的判断即可:

// src/page/Login/index.tsx
...
const res = await getCaptcha(username);

if(res.status === 200 || res.status === 201) {
  Toast.show(res.data)
} else {
  Toast.show(res.message)
}

image.png

至此,我们的发送验证码功能就完成了。

总结

这节我们引入了Icont图标、完成了注册页面的开发。

从获取验证码功能开发的过程中,我们实现了:

  • 前端验证用户名。
  • nestjs后端服务生成、调试配置、用户模块生成和引入ValidationPipe做请求体验证。
  • 用 Docker Desktop 生成MySQL数据服务、MySQL Workbench连接新建数据库。
  • 创建了 User 的 entity,通过typeorm的自动建表功能,在数据库创建了对应的用户表。
  • 后台引入 nodemailer 来发邮件,如果是线上可以其他的平台的邮件推送服务。
  • 用 Docker Desktop 生成 redis 服务并写入验证码

到了现在,我们已经把该项目所有需要的服务都搭建好了,接下来就是各个功能的开发了。

代码仓库:

前端代码

后端代码