玩命加载中 . . .

项目之登录/注册模块


前言

上一篇博客:React-Ts 项目:在线编程笔试平台 有说到,这个项目是前后端分离项目,也就是说,其实该项目相当于两个项目,分别专注前端和后端,如果要上传到 github ,那么就需要创建两个仓库来分别放置。博主已经将源码 push ,读者可以跳转查阅:前端项目仓库后端项目仓库

前端项目采用的技术栈是 React 。UI 的设计是上下布局,即头部导航栏、下面主体内容,其中使用了 antd4 的 CardForm 组件,效果如图所示:登录注册

本来想实现的效果是翻转卡片,即点击“登录”或“注册”就能将卡片翻转,同时“登录”面带有标签,可以切换登录方式:邮箱登录和账号登录,但奈何一直找不到合适的解决方法,就使用了 antd 的 带标签页的卡片,如果读者知道如何解决这一问题,还望不吝赐教 ^_^

后端项目采用的技术栈是 NodeJs ,使用的数据库是 MYSQL 。项目中运用了 KoaTypeORM 框架来搭建 Node 服务和操作 MYSQL 数据库。

项目所使用的技术栈读者已经清楚,我们继续往下看吧~~

注册模块(支持邮箱注册)

code 前思考

首先我们需要理清注册的逻辑,联想我们在一个网站上注册时,操作流程是怎么样的。我们可以很快知道,通常有两种方式,一种是手机号注册,一种是邮箱注册。

  1. 手机号注册
    常见手机注册

  2. 邮箱注册
    常见邮箱注册

注册的方式有很多种,除了手机邮箱注册,还有用户名/xx号注册、第三方注册等,具体想选择哪种方式实现注册,读者可以自行选择,下面我们还需要来看看如何实现前后端的连接,以跑通整个项目。

情况说明:博主选择的方式是邮箱注册。

逻辑思考

  1. 用户在前端界面填写表单信息,点击“获取验证码”按钮后,会【发送请求】给后端
  2. 后端【接收前端传来的请求】后,会随机生成一个包含大小写字母和数字的 6 位数验证码,并将该验证码存储在数据库中,同时【响应数据】(这里指验证码)给前端。
  3. 前端【接收后端响应体】(这里指后端传过来的验证码)后,必须填入表单中才能提交(表单组件可以在 rule 里设定 required )。
  4. 点击“提交”后,这时前端发送的请求体中包含了表单中的所有数据,同样将其发送给后端。后端获取请求体中的所有信息,根据邮箱账号从数据库中查询是否已经存储了该用户,若查找不到则说明无人使用该邮箱注册过,可以允许注册,将邮箱和密码等相关信息一并存进数据库中;否则直接响应数据,告知前端该用户已经注册。前端根据后端响应的信息进行对应的操作,如注册成功,可以跳转到登录页面进行登录,否则弹出警告,告知用户注册信息填写有误。

说到这里,读者应该懂得了将前后端项目串联起来的一系列逻辑,概括起来便是前端调用后端接口【发送请求】,后端设定接口给前端调用【接收请求】,这样一来前后端便可以互连跑通服务了。

但问题来了,“你说了这么多,我还是不知道该怎么在前端调用后端接口,后端又如何接收到前端传来的请求”?别急,博主只是先抛砖引玉,先给大家捋顺跑通前后端服务的逻辑,心里好有个底。下面我们继续往下

  1. 前端发送请求的方式有多种,如 fetch、axios 等,这里使用的是 axios ,下面自定义封装的 axios 函数,仅供大家参考:
    // 文件结构:根目录下/src/api/index.ts
    import axios from 'axios';
    
    // 注意这个路径,http://localhost:8080 是后端服务的地址,/api 是请求的固定格式,后面调用该函数的时候还会再传入一个参数 /xxx ,因此后端要想接收前端请求,就必须配置接口路径成 前端服务的地址/api/xxx ,这样前后端项目才能跑通【不理解的话后面代码还会有注释】
    const REQUESTIP: string = "http://localhost:8080/api";
    
    export function generateHttpApi(method: 'get' | 'post') {
      return async (url: string, params?: any) => {
        const data = method === 'get' ? {
          params
        } : {
          data: params
        };
        // 调用函数时会传入 url (相当于上面说的xxx),在这里进行拼接,就成了前端调用后端接口的完整 url 了:后端服务地址/api/xxx
        url = REQUESTIP + url;
        try {
          const response = await axios({
            url,
            method,
            ...data,
          });
          return response.data;
        } catch (error) {
          return await Promise.reject(error);
        }
      }
    }
    
    export const get = generateHttpApi('get');
    export const post = generateHttpApi('post');
    调用 axios 封装函数的时候:
    // 文件结构:根目录/src/api/modules/interface.ts
    import { post } from '根目录/src/api/index';
    
    export async function testRegister(data: { email: string; cypher: string; captcha: string; identity: number}) {
      // 此时前端请求的整个路径为 http://localhost:8080/api/register,那么假设前端项目地址为 http://localhost:3000 ,后端就需要定义接口路径为 http://localhost:3000/api/register
      return post('/register', data);
    }
    
    
    // 文件结构:根目录/src/pages/login/index.tsx
    // 这里结构其实不是很好,因为注册和登录是写在一块的,没有分开来。读者清楚是在注册事件下发送 axios 请求的就好
    import { testRegister } from '根目录/src/api/modules/interface';
      // 点击“注册”按钮触发的函数:
    submitRegister = async (values: any) => {
      // 表单的值
      const { account, password, identity, email, cypher, captcha } = values;
      // 请求参数
      const data = { email, cypher, captcha, identity };
      // !发送请求并获取返回值
      const res = await testRegister(data);
      // 注册成功
      if (res.data.status === true) {
        message.success(res.msg);
        // 在当前页跳转至登录界面
        this.setState({ noTitleKey: 'login' });
      } else {
        message.error(res.msg);
      }
    };
  2. 然后是在后端定义接口。博主的后端项目使用了 Koa 框架,因此可以调用它的 API :

注意:如果是本地项目要解决跨域,Koa 可以使用 koa2-cors 直接解决;但如果是线上项目的话,还需要另外的配置,网上有多种处理跨域的解决方法,博主采用的方法是配置 nginx 反向代理,具体操作可以跳转到另一篇博客:前后端分离项目线上部署,里面有配置 nginx 的具体步骤,在这里不加叙述。

// 下面 code 仅供参考,大致是这么一个模式

import Koa from 'koa';
import Router from 'koa-router';
import bodyParser from 'koa-bodyparser';        // 处理 post 请求,有了这个插件才能获取 post 参数【通常前端的请求方式是 post 】
import cors from 'koa2-cors'; // 处理跨域问题
import register from './middleware/register';   // 导入注册的 .ts 文件,用于处理注册事件【通常不放在入口文件里,而是单独抽出来】

const app = new Koa();
const router = new Router(); 

app.use(cors(corsOptions));
// 处理 post 请求的参数。
// 注意这个插件的位置,必须要放在匹配接口之前!!!
app.use(bodyParser());
// 匹配接口,这里的路径和前端请求路径中端口号后面那部分是保持一致的
router.post('/api/register', register);
// 组装匹配好的路由,返回一个合并好的中间件
app.use(router.routes());

// 这里启动的后端服务是 8080 端口
app.listen(8080, () => {
  console.log('网站服务器启动成功,请访问 http://localhost:8080');
})

说一句:定义好了后端接口,可以查阅 postman 使用教程,在 postman 官网 下载 postman ,方便测试接口是否被成功调用,推荐使用!

登录模块(支持邮箱登录)

  1. 跑通前后端服务的逻辑同注册一致,也是【前端发送请求】–【后端接收请求】–【后端响应请求】 –【前端接收响应】

然而这样简单地实现登录功能,用户是可能通过控制台篡改后端的响应数据的,进而实现登录跳转,这里采用后端 session 身份验证进行防范。同时前端需实现登录拦截,防止用户未经登录便通过 url 跳过登录验证。

  1. 所谓 session 身份验证,打个比方就是“你第一次来的时候我发给你一张身份证,但只是一张写着身份证号码的纸片。以后你每次来办事,我根据这张身份证去后台查一下你的 id 是不是有效”。

对应到登录功能,就是每次用户登录的时候,后端获取到用户邮箱,根据这个邮箱查找数据库中存储的该用户的信息,若查找不到说明该用户未注册;若查找得到则说明该用户已注册成功,可以往下匹配密码,若密码错误,则不能登录,也就不用“发放身份证”了;若密码正确,则“可以发放带有身份证号码的身份证”–生成 session 并存进 cookie 中响应给前端。这样用户就算篡改后端响应数据,要登录的时候 session 还是会发生更改,不会与用户篡改后的数据相匹配;同时如果用户在未登录的状态下更改 url ,也会触发登录拦截,后端会验证登录状态。

这个 session 是一个长度为 20 左右的包含数字和大小写字母的 session 随机数,将这个随机数①存进该用户的信息中,以便在维护登录状态时可以根据用户账号获取;②生成一个 cookie ,将 session 存进该 cookie 中。

那么问题又来了,session 可以通过 JS 函数 return 一个值得到,但这个 cookie 该如何生成? session 又如何存进 cookie 中?

在 Koa 中,这两个问题可以一并解决:

// 文件结构:根目录/src/middleware/login.ts
export default async (ctx:Context) => {
  // generateMixed() 是封装的 JS 函数,用于生成随机数
  const session = generateMixed(20);
  // 设置 cookie 
  ctx.cookies.set(
    'session', session, { httpOnly: false, maxAge: 3600000 }
  )
}

然后在前端封装 axios 函数的地方加上配置 axios.defaults.withCredentials = true; 之后,就会在前端每次向后端请求数据时,请求头中自动带上 cookie ,后端便可以获取到这个 cookie 对用户身份进行验证:如果与数据库中该用户的 session 一致,则说明用户处于登录状态,否则用户未登录。可以设一个字段标识登录状态,返回给前端,以便前端判断用户是否登录成功。

  1. 前端在路由页面发送登录请求(参数为 cookie ),如果后端接收到了 cookie ,并在数据库中找到 session 为 cookie 的用户信息,说明用户处于登录状态,否则处于未登录状态。对于未登录的用户,重定向到登录页面即可。

文章作者: hcyety
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hcyety !
评论
  目录