登录功能的实现(包括前后端)

前言

登录功能对于前端刚入门不久的我来说较为困难,花了很久查阅了许多资料。代码部分涉及的技术栈和细节也很多。

过了一段时间来看,发现对自己写的代码竟然感觉十分陌生,故想写篇博客,对整个过程进行梳理。方便自己重看代码,同时也希望能帮到在实现这一功能受阻的程序猿们o( ̄▽ ̄)ブ

概述

登录功能的实现大致可分成6步:

  1. 前端验证用户输入是否符合规范
  2. 前端调用后端登录接口,向后端发起登录请求
  3. 后端收到请求,查验数据库种是否有相应账号以及密码是否正确
  4. 验证通过后,将成功信息连同token一起发送给前端
  5. 前端将token储存起来,每次用户进入需要登录才能访问的页面时或者每次进入网站时向后端发送token
  6. 若过期,则清楚用户信息,回到未登录状态

技术栈

  • 前端:JavaScript,Vue

    • axios (发送请求的第三方库)
    • element-plus (基于 Vue 3 的组件库,简化UI代码)
  • 后端:Node.js

    • express (简化请求响应代码)
    • cors (解决跨域问题)
    • bcryptjs(密码加密解密)
    • jsonwebtoken(实现token的生成与验证)

效果图

登陆前
在这里插入图片描述
登录界面
在这里插入图片描述

登录成功
在这里插入图片描述

代码

表单验证及发送请求

<template>
  <el-form
    ref="ruleFormRef"
    :model="ruleForm"
    :rules="rules"
    label-width="35rem"
  >
    <el-form-item prop="phone_number" class="phone_number">
      <el-input
        type="text"
        placeholder="你的手机号/邮箱"
        v-model="ruleForm.phone_number"
      />
    </el-form-item>

    <el-form-item prop="password" class="password">
      <el-input
        type="password"
        placeholder="密码"
        v-model="ruleForm.password"
        autocomplete="off"
      />
    </el-form-item>

    <el-form-item class="login_in_button">
      <el-button type="primary" @click="submitForm(ruleFormRef)"
        >登录</el-button
      >
      <el-button>注册</el-button>
    </el-form-item>
  </el-form>
</template>

<script lang="ts" setup>
import { reactive, ref } from "vue";
import type { FormInstance } from "element-plus";
import { ElMessage } from "element-plus";
import { LoginInPost } from "../../api/LoginIn";
import { useRouter } from "vue-router";
import axios from "axios";
import { useStore } from "../../../store.js";
import { storeToRefs } from "pinia";

const router = useRouter();
const store = useStore();
// const { islogin } = storeToRefs(store);

const formSize = ref("default");
const ruleFormRef = ref<FormInstance>();
const ruleForm = reactive({
  phone_number: "",
  password: "",
});

const checkPhonenumber = (rule: any, value: string, callback: any) => {
  if (!value) {
    callback(new Error("请输入注册时用的邮箱或者手机号呀"));
  }
  let re = /^[0-9]*$/;
  // console.log(value.length);
  // ||value.length != 11
  if (!re.test(value)) {
    // console.log(re.test(value));
    callback(new Error("输入格式有误哟"));
  } else {
    callback();
  }
};

const checkPassword = (rule: any, value: string, callback: any) => {
  if (!value) {
    return callback(new Error("请输入密码呀"));
  }
  let re = /^[0-9a-zA-Z]*$/;

  if (!re.test(value)) {
    // console.log(re.test(value));
    return callback(new Error("输入格式有误哟"));
  } else {
    return callback();
  }
};

const rules = reactive({
  phone_number: [{ validator: checkPhonenumber, trigger: "blur" }],
  password: [{ validator: checkPassword, trigger: "blur" }],
});

const submitForm = async (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  await formEl.validate((valid, fields) => {
    if (valid) {
      let url = "http://localhost:3007/api/user_register";
      let dataObject = reactive({
        phoneNumber: ruleForm.phone_number,
        password: ruleForm.password,
      });
      try {
        LoginInPost(dataObject).then(function (res) {
          // console.log(res.data.token);
          if (res.data.status == 0) {
            store.loginIn();
            localStorage.setItem("token",res.data.token);
            router.go(-1);
          } else if (res.data.status == 403) {
            ElMessage({ message: res.data.message, offset: 200 });
          }
        });
      } catch (error) {
        console.log(error);
      }
    } else {
      console.log("error submit!", fields);
    }
  });
};
</script>

axios封装

index.js

import db from "../db/user_db.js"
import bcrypt from "bcryptjs"
import jwt from "jsonwebtoken"
import config from "../config.js"

export async function regUser(req, res) {
    try {
        // console.log(req.body);
        const data = req.body;
        // console.log(data.phoneNumber);
        const selectSql = "select * from users where phoneNumber=?";
        let [result] = await db.query(selectSql, data.phoneNumber)
            .catch(err => { console.log(err) });
        // console.log(result.length);
        //号码已占用
        if (result.length > 0) {
            throw new Error(res.send({
                status: 403,
                message: "电话号码已注册过了嗷",
            }))
        }

        //对密码进行加密
        // console.log(data.password);
        data.password = bcrypt.hashSync(data.password, 10);
        console.log(data.password);
        const insertSql = "insert into users set ?";

        const insertStr = { phoneNumber: data.phoneNumber, password: data.password };
        [result] = await db.query(insertSql, insertStr)
            .catch(err => { console.log(err) });
        // console.log(result.affectedRows);
        if (result.affectedRows != 1) {
            res.send({
                status: 403,
                message: "请求失败",
            })
        }
        res.send({
            status: 0,
            message: "请求成功",
        })
    } catch (err) {
        res.send({
            status: 403,
            message: "请求失败",
            desc: err.message
        })

    }


}

export async function loginUser(req, res) {
    try {
        // console.log(req.body);
        const data = req.body;
        // console.log(data.phoneNumber);
        const selectSql = "select * from users where phoneNumber=?";
        let [result] = await db.query(selectSql, data.phoneNumber)
            .catch(err => { console.log(err) });
        // console.log(result);
        //无注册账号
        if (result.length == 0) {
            return res.send({
                status: 403,
                message: "无注册账号",
            })
        }

        //验证密码
        // console.log(result[0].password);
        const compareResult = bcrypt.compareSync(data.password, result[0].password);
        if (compareResult == 0) {
            return res.send({
                status: 403,
                message: "密码错误",
            })
        }

        const user = { ...result[0], password: '' };
        console.log(user);
        const tokenStr = jwt.sign(user, config.jwtSecretKey, { expiresIn: "10h" });
        return res.send({
            status: 0,
            message: "登录成功",
            token: tokenStr
        })
    } catch (err) {
        return res.send({
            status: 403,
            message: "请求失败",
            desc: err.message
        })

    }
}

export async function GetInfo(req, res) {
    console.log(req.headers.authorization);
    const token = req.headers.authorization;
    if (!token) {
        return res.send({
            status: 403,
            message: "无token"
        })
    }
    try{
        const flag = jwt.verify(token, config.jwtSecretKey);
        if (flag) {
            return res.send({
                status: 0,
                message: "验证成功"
            })
        } else {
            return res.send({
                status: 403,
                message: "token错误"
            })
        }
    }catch(err){
        return res.send({
            status: 403,
            message: "token已过期"
        })
    }
    
}


request.js

// http/axios.js
import instance from "./index"
/**
 * @param {String} method  请求的方法:get、post、delete、put
 * @param {String} url     请求的url:
 * @param {Object} data    请求的参数
 * @param {Object} config  请求的配置
 * @returns {Promise}     返回一个promise对象,其实就相当于axios请求数据的返回值
 */

export const axios = ({
    method,
    url,
    data,
    config
}) => {
    method = method.toLowerCase();
    if (method == 'post') {
        return instance.post(url, data, {...config})
    } else if (method == 'get') {
        return instance.get(url, {
            params: data,
            ...config
        })
    } else if (method == 'delete') {
        return instance.delete(url, {
            params: data,
            ...config
        }, )
    } else if (method == 'put') {
        return instance.put(url, data,{...config})
    } else {
        console.error('未知的method' + method)
        return false
    }
}

Login.js

import { axios } from "../utils/request"

export const LoginInPost = (data) => {
    return axios({
        url: "http://localhost:3007/api/user_login",
        method: "post",
        data
    })
}

export const GetUsersInfo = (data) => {
    return axios({
        url: "http://localhost:3007/api/get_info",
        method: "get",
        data
    })
}

登录验证

export async function loginUser(req, res) {
    try {
        // console.log(req.body);
        const data = req.body;
        // console.log(data.phoneNumber);
        const selectSql = "select * from users where phoneNumber=?";
        let [result] = await db.query(selectSql, data.phoneNumber)
            .catch(err => { console.log(err) });
        // console.log(result);
        //无注册账号
        if (result.length == 0) {
            return res.send({
                status: 403,
                message: "无注册账号",
            })
        }

        //验证密码
        // console.log(result[0].password);
        const compareResult = bcrypt.compareSync(data.password, result[0].password);
        if (compareResult == 0) {
            return res.send({
                status: 403,
                message: "密码错误",
            })
        }

        const user = { ...result[0], password: '' };
        console.log(user);
        const tokenStr = jwt.sign(user, config.jwtSecretKey, { expiresIn: "10h" });
        return res.send({
            status: 0,
            message: "登录成功",
            token: tokenStr
        })
    } catch (err) {
        return res.send({
            status: 403,
            message: "请求失败",
            desc: err.message
        })

    }
}

github链接

想要看完整版代码以及页面效果,请移步github
https://github.com/Ki-Wi-Berry/bilibili-videos

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>