vue+Nodejs+Koa搭建前后端系统(七)-- 用户注册、注销

news2024/11/28 4:27:45

前言

  • 前端采用vue3
  • 前端组件库采用ElementPlus
  • 本篇文章需要结合上一篇《vue+Nodejs+Koa搭建前后端系统(六)-- 用户登录》一起看

客户端用户注册页面

添加注册页面

添加 /src/pages/register/register.vue 文件
在这里插入图片描述

安装md5

md5是加密插件,用于密码加密。安装md5

npm install --save js-md5

编写注册页面

register.vue页面代码:

<script setup lang="ts">
import { reactive, ref } from "vue";
import type { FormRules, FormInstance } from "element-plus";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";
import http from "@/http";
import md5 from "js-md5";

const router = useRouter();
//form表单的Ref(猜测:相当于VNode,继承ElementDom)
const formRef = ref<FormInstance>();
//提交表单时的参数
const formData = reactive({
  username: "",
  password: "",
  sex: "1",
  mobile: "",
  birth: null,
  email: "",
});
//表单校验
const rules = reactive<FormRules>({
  username: [{ required: true, trigger: "blur", message: "请输入用户名" }],
  password: [{ required: true, trigger: "blur", message: "请输入密码" }],
  mobile: [{ required: true, trigger: "blur", message: "请输入手机号" }],
});
//ElementPlus时间组件可选日期范围函数(参看DatePicker组件disabled-date属性)
const disabledDate = (D: Date) => {
  return D.getTime() > new Date().getTime();
};
//提交表单
const register = (formEl: FormInstance | undefined) => {
  formEl?.validate((valid, fields) => {
    if (valid) {//通过校验-向后端请求注册接口/users/register
      http
        .post("/users/register", { ...formData, password: md5(formData.password) })
        .then(async (data: any) => {
          if (data.code == 0) {
            ElMessage({
              message: data.message,
              type: "success",
            });
          } else {
            ElMessage({
              message: data.message,
              type: "error",
            });
          }
        })
        .catch((err: any) => {
          ElMessage({
            message: err.message,
            type: "error",
          });
        });
    } else {
      ElMessage({
        message: "请按提示填写信息",
        type: "error",
      });
    }
  });
};
const goLogin = () => {
  router.replace("/login");
};
</script>
<template>
  <div class="register">
    <el-form
      class="form"
      ref="formRef"
      :model="formData"
      :rules="rules"
      label-width="5em">
      <h2>用户注册</h2>
      <el-form-item prop="username" label="用户名">
        <el-input
          v-model="formData.username"
          name="register"
          placeholder="请输入用户名"
          autocomplete="new-password" />
      </el-form-item>
      <el-form-item prop="password" label="密码">
        <el-input
          v-model="formData.password"
          type="password"
          name="register"
          autocomplete="new-password"
          placeholder="请输入密码"
          show-password />
      </el-form-item>
      <el-form-item prop="sex" label="性别">
        <el-radio-group v-model="formData.sex">
          <el-radio label="1"></el-radio>
          <el-radio label="0"></el-radio>
        </el-radio-group>
      </el-form-item>
      <el-form-item prop="mobile" label="手机">
        <el-input v-model="formData.mobile" placeholder="请输入手机号" />
      </el-form-item>
      <el-form-item prop="birth" label="出生日期">
        <el-date-picker
          v-model="formData.birth"
          type="date"
          value-format="YYYY-MM-DD"
          :disabled-date="disabledDate"
          placeholder="请选择出生日期" />
      </el-form-item>
      <el-form-item prop="email" label="邮箱">
        <el-input v-model="formData.email" placeholder="请输入邮箱" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="register(formRef)">注册</el-button>
        <el-button @click="goLogin">返回登录页</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<style lang="less" scoped>
.register {
  .form {
    max-width: 400px;
    box-sizing: border-box;
    margin: auto;
    padding: 10px 40px;
  }
}
</style>

页面效果
在这里插入图片描述
在这里有个小插曲:我在编写注册页时,用户名和密码输入框总是自动填充我之前登录过的信息,查了一些博客,原因找到了。是因为注册页和登录页的的用户名和密码在el-form组件的位置和某些关键属性(如prop)一样,所以被自动填充了。解决办法也有好多,但一一试验,只有在el-input组件添加 autocomplete="new-password"属性才有效。

添加注册页面路由

/src/router/index.ts部分代码

{
    path: "/register",
    name: "Register",
    component: () => import("@/pages/register/register.vue"),
}

在这里插入图片描述

添加注册按钮

在登录页(/src/pages/login/login.vue)面添加注册按钮

<div class="register-btn-wrap">
	<el-button type="text" size="middle" @click="register">注册</el-button>
</div>

点击该按钮进入注册页面

<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
const register = () => {
  router.replace("/register");
};
</script>

另外,上一篇《vue+Nodejs+Koa搭建前后端系统(六)-- 用户登录》中,登录接口入参需要小小修改一下,即把密码加密

/**login.vue部分代码*/
import { reactive, ref } from "vue";
import http, { getToken } from "@/http";
import md5 from "js-md5";
const formData = reactive({
  username: "",
  password: "",
});
http.post("login/loginIn", {...formData,password: md5(formData.password)}).then(async (data: any) => {
/**此处省略700左右个字*/
}).catch((err: any) => {
/**此处省略80左右个字*/
});

服务端用户注册接口

新增用户表列

SQL语句

ALTER TABLE create_user 
    ADD COLUMN  mobile VARCHAR(13) COMMENT '手机号' NOT NULL,
    ADD COLUMN sex ENUM('1','0') COMMENT '性别:1-男 0-女' DEFAULT '1',
    ADD COLUMN email VARCHAR(20) COMMENT '邮箱' DEFAULT '',
    ADD COLUMN birth DATE COMMENT '生日' DEFAULT NULL;

vscode MySQL插件操作步骤
在这里插入图片描述

Nodejs加密模块

crypto是Nodejs内置的加密模块,可以新建 /plugins/crypto.js 文件,编写一个加密函数,用以方便以后调取

const crypto = require('crypto');
module.exports = function (t) {
    return crypto.createHash('md5').update(t).digest('hex')
}

Nodejs安装dayjs

npm install dayjs

dayjs中国镜像站点

添加注册路由

/routes/users.js

const { registerUser } = require("../module/user");
module.exports = [
  {
    url: "/register",
    methods: "post",
    actions: registerUser,
  }
];

编写注册接口

/module/user.js

const md5 = require('../plugins/crypto');
const dayjs = require('dayjs');

//注册用户
async function registerUser(ctx, next) {
  const params = ctx.request.body;
  const sql = `SELECT * FROM create_user WHERE username='${params.username}'`;
  try {
    const r = await ctx.db.query(sql);
    if (r && r[0]) {//该用户名已注册过
      ctx.response.status = 403;
      ctx.body = { message: "该用户已注册", code: 1 };
    } else {//该用户名未注册过
      if (!params.username) {
        ctx.response.status = 403;
        ctx.body = { message: "请输入用户名", code: 2 };
      } else if (!params.password) {
        ctx.response.status = 403;
        ctx.body = { message: "请添加密码", code: 3 };
      } else if (!params.mobile) {
        ctx.response.status = 403;
        ctx.body = { message: "请添加手机号", code: 4 };
      } else {//验证通过
        const nowD = dayjs();
        //当前时间,即用户注册时间:YYYY-MM-DD HH:mm:ss格式
        const nowFormat = nowD.format("YYYY-MM-DD HH:mm:ss");
        //生日:YYYY-MM-DD格式
        const birth = params.birth ? dayjs(params.birth).format("YYYY-MM-DD") : "";
        //以【用户名】+【用户注册时间】加密生成秘钥
        const secret_key = md5(params.username + nowFormat);
        //以【密码】+【用户注册时间】加密生成密码(加用户注册时间是为了提高密码的安全性,避免被暴力破解)
        const password = md5(params.password + nowFormat);
        //INSERT到数据库表的列、值集合
        const p = {
          username: params.username,
          password: password,
          create_time: nowFormat,
          secret_key: secret_key,
          mobile: params.mobile,
          sex: params.sex,
          email: params.email,
          birth: birth
        };
        //将列、值集合中值为空的过滤掉,返回过滤后的key集合
        const keys = Object.keys(p).filter(key => p[key] || p[key] === 0);
        //将过滤后的集合整理成mysql插件需要的列格式
        const columns = keys.join(",");
        //将过滤后的集合整理成mysql插件需要的值格式
        const values = keys.map(key => typeof p[key] === "string" ? `'${p[key]}'` : p[key]).join(",");
        const sql = `INSERT INTO create_user (${columns}) value(${values})`;
        try {
          const r = await ctx.db.query(sql);
          ctx.response.status = 200;
          //注册成功:将用户id和秘钥放入响应中
          ctx.body = { message: "注册成功", code: 0, data: { id: r.insertId, secret_key: secret_key } };
        } catch (e) {
          ctx.response.status = 500;
          ctx.body = { message: e, code: 99 };
        }
      }
    }
  } catch (e) {
    ctx.response.status = 500;
    ctx.body = { message: e, code: 99 };
  }
}
module.exports = {
  registerUser
};

绕过登录验证

注册接口 /users/register 应该绕过登录验证,在app.js中

/**部分代码*/
const app = new Koa();
const { verifyToken } = require("./middleware/jwt.js");
// const { takeSession, verifySession } = require("./middleware/session.js")

/**如果用token进行登录验证*/
app.use(verifyToken({ no_verify: ["/login/loginIn", "/token/refresh", "/users/register"] }));
/**如果用session进行登录验证*/
// app.use(takeSession(app));
// app.use(verifySession({ no_verify: ["/login/loginIn", "/users/register"] }))

verifyToken 中间件在上一篇《vue+Nodejs+Koa搭建前后端系统(六)-- 用户登录》中已经编写。

这样注册功能就写完了。总结一下注册流程:

  1. 客户端校验用户名、密码和手机号必填
  2. 客户端密码加密传给服务端
  3. 服务端同样校验用户名、密码和手机号必填,并且确定该用户未注册,然后将用户信息插入用户表
  4. 用户信息中,当前时间作为用户注册时间,存储的密码为客户端传来的加密密码和注册时间组合再次加密,以防被暴力破解,密钥为用户名和注册时间组合加密。其他信息跟随客户端的填写

有可能在注册时会有报错:

注册报错
在这里插入图片描述
原因:password或secret_key列的字符串长度超过了该列设置的最大长度
修改:将报错字段的类型长度修改大一些
在这里插入图片描述
修改列的SQL语句:

ALTER TABLE create_user MODIFY COLUMN password VARCHAR(64) NOT NULL COMMENT "用户密码";
ALTER TABLE create_user MODIFY COLUMN secret_key VARCHAR(64) DEFAULT "" COMMENT "密钥";

用户注销

根据登录方式不同,注销也有不同的方式

session登录方式的注销

第一步:服务端编写注销接口

/module/login.js 注销代码:

async function loginUser(ctx, next) {
/**此处省略n段代码*/
}
async function logoutUser(ctx, next) {
    ctx.session = null;
    ctx.body = { message: '登出成功', code: 0 }
}
module.exports = {
    loginUser,
    logoutUser
};

第二步:添加服务端注销路由

/routes/login.js

const { loginUser, logoutUser } = require("../module/login")
module.exports = [
  {
    url: "/loginIn",
    methods: "post",
    actions: loginUser
  },
  {
    url: "/logout",
    methods: "post",
    actions: logoutUser
  },
];

第三步:客户端编写注销

/src/pages/index.vue

<script setup lang="ts">
import { ref } from "vue";
import http from "@/http";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";

const router = useRouter();
const isload = ref(false);
const list = ref([]);
const lookUser = async () => {
  const params = {};
  isload.value = true;
  await http.post("users/look", params).then((data: any) => (list.value = data.list)).catch((err: any) => {
      ElMessage({
        message: err.message,
        type: "error",
      });
    });
  isload.value = false;
};
/**注销*/
const logout = async () => {
  await http.post("login/logout").then((data: any) => {
      if (data.code == 0) {
        ElMessage({
          message: data.message,
          type: "success",
        });
        router.push("/login");
      }
    }).catch((err: any) => {
      ElMessage({
        message: err.message,
        type: "error",
      });
    });
};
lookUser();
</script>
<template>
  <div class="index">
    <el-table :data="list" style="width: 100%" v-loading="isload">
      <el-table-column prop="username" label="用户名" />
      <el-table-column prop="password" label="密码" />
      <el-table-column prop="create_time" label="创建时间" />
    </el-table>
    <el-button class="refresh-btn" @click="lookUser">刷新列表</el-button>
    <el-button class="refresh-btn" @click="logout">注销</el-button>
  </div>
</template>

服务端主动刷新token登录方式的注销

/src/pages/index.vue注销代码

import { useRouter } from "vue-router";
const router = useRouter();
const logout = async () => {
  window.localStorage.removeItem("token");
  router.push("/login");
};

客户端主动刷新token登录方式的注销

/src/pages/index.vue注销代码

const logout = async () => {
  window.localStorage.removeItem("secret_key");
  window.localStorage.removeItem("token");
  window.location.href = `/#/login`;
};

这里只需要客户端处理就可以,客户端删除本地存储token和secret_key,然后导航到登录页。

为什么用window.location.href导航到登录页,而不是vue-router?还记得上一篇文章客户端主动刷新token登录么,其通过setTimeout不断请求刷新token方法
在这里插入图片描述
如果用vue-router,其虽然导航到了登录页,但setTimeout中的方法依然在等待执行。当你再次登录成功,上一个setTimeout延时器未关闭,而一个新的延时器又被开启。

当然,可以定义一个全局变量或者是vue store用来存储延时器Id,然后在注销时清除该延时器

http.ts请求拦截

let w = window as any;
//延时器Id
w.timerId = null;
/**响应拦截器 */
http.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    if (response.headers.token) {
        window.localStorage.setItem('token', response.headers.token)
    }
    const responseUrl = response.config?.url || "";
    //刷新token
    if (responseUrl && (/token\/refresh\?secret_key=/gim).test(responseUrl)) {
        w.timerId = setTimeout(() => {
            const secret_key = window.localStorage.getItem("secret_key") || "";
            getToken(secret_key);
        }, (response.data.expiresIn - 30) * 1000)
    }
    return response.data;
}, function (error) {
    // 对响应错误做点什么
    if (error?.response.status === 401) {
        router.push("/login");
    }
    return Promise.reject(error.response.data);
});

/src/pages/index.vue注销代码

import { useRouter } from "vue-router";
const router = useRouter();
const logout = async () => {
  const w = window as any;
  //清除延时器
  if(w.timerId) clearTimeout(w.timerId);
  window.localStorage.removeItem("secret_key");
  window.localStorage.removeItem("token");
  router.push("/login");
};

参考资料:
简书:Chrome禁用自动填充autocomplete="off"无效

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/723943.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算力井喷、全球布局,亚马逊云科技生成式AI不断创新解决企业需求

时至今日&#xff0c;生成式AI在创意输出&#xff08;如写作、编程、设计&#xff09;、功能增强&#xff08;如写摘要、搜索&#xff09;、交互式体验&#xff08;Q&A、聊天&#xff09;和决策支持&#xff08;各类助理&#xff09;这四个领域已展现出惊人潜力。 在亚马逊…

软件测试外包

目录 前言&#xff1a; 评估软件测试服务提供商 为什么将测试外包出去 测试服务外包应当考虑因素 参与模式 地理位置 服务协议 灵活性 质量改进 如何选择测试外包服务商 需要什么外包 调查 互动 结论 前言&#xff1a; 在当今的软件开发行业中&#xff0c;越来越…

k8s Volume之Persistent Volume持久卷

一、Persistent Volume yaml详情&#xff1a; kind: PersistentVolume apiVersion: v1 metadata:# PV卷名称name: nfs-mvn-repo spec:# 容量capacity:# 存储大小storage: 20Ginfs:server: 192.168.80.170path: /srv/nfs/disk/mvn-repo# 该卷支持的访问模式accessModes:- ReadW…

SPEC CPU 2006 在 CentOS 5.0 x86_64 古老系统测试【2】

上一篇 SPEC CPU 2006 在 CentOS 5.0 x86_64 古老系统测试_hkNaruto的博客-CSDN博客 虚拟机时间&#xff0c;一天后获得结果 由于ssh版本太低&#xff0c;采用nc把文件拷贝出来 结果 SPEC CFP2006 Result Copyright 2006-2023 Standard Performance Evaluation Corporatio…

MES系统

MES系统&#xff0c;全称制造执行系统(Manufacturing Execution System)&#xff0c;是一种用于实时监控、追踪和控制生产过程的计算机化系统。 SAP Fiori概览 相信工作中接触过SAP的人&#xff0c;肯定对SAP Fiori不陌生。那什么是SAP Fiori呢&#xff1f; SAP Fiori是一种用于…

基于STM32单片机的智能视力保护台灯设计

硬件方案 智能台灯以专门感应人体红外信号的红外传感器为基础&#xff0c;这意味着仅当有人的时候&#xff0c;红外传感器才输出一个信号&#xff0c;经放大处理后达到单片机的输入门限电压&#xff0c;单片机开始运行&#xff0c;台灯自动点亮。当人离开的时候&#xff0c;单…

CSRF漏洞

前言 作者简介&#xff1a;不知名白帽&#xff0c;网络安全学习者。 博客主页&#xff1a;不知名白帽的博客_CSDN博客-网络安全,CTF,内网渗透领域博主 网络安全交流社区&#xff1a;https://bbs.csdn.net/forums/angluoanquan 目录 CSRF漏洞原理 CSRF常见分类 GET型 POST型…

GIT版本控制常规性操作演示汇总

文章目录 GIT基本操作GIT配置个人信息配置&#xff1a;GIT查看个人信息配置&#xff1a;GIT的三大区域GIT回滚&#xff1a;git resetGIT恢复日志&#xff1a;git reflogGIT三大区域转换GIT新建分支GIT合并分支GIT删除分支码云上创建项目GIT变基&#xff1a;git rebase合并提交记…

轮询-实时调用接口

https://www.cnblogs.com/zhangliang88/p/16254460.htmlhttps://www.cnblogs.com/zhangliang88/p/16254460.html

软件测试 | Selenium对多浏览器处理

在执行自动化测试过程中&#xff0c;我们往往会针对不同的浏览器做兼容性测试&#xff0c;可以通过对测试用例代码的改造&#xff0c;实现对不同浏览器的自动化兼容性测试。 注&#xff1a;实现对不同浏览器的自动化兼容性测试&#xff0c;需要先将各个浏览器的驱动在PC端配置…

基于PyQt5的桌面图像调试仿真平台开发(15)图像融合

系列文章目录 基于PyQt5的桌面图像调试仿真平台开发(1)环境搭建 基于PyQt5的桌面图像调试仿真平台开发(2)UI设计和控件绑定 基于PyQt5的桌面图像调试仿真平台开发(3)黑电平处理 基于PyQt5的桌面图像调试仿真平台开发(4)白平衡处理 基于PyQt5的桌面图像调试仿真平台开发(5)…

AMEYA360:尼得科nidec恩布拉科变频压缩机介绍

能源安全、能源价格方面的挑战与气候危机交织 提高能源效率比以往任何时候都更为紧迫。 能源效率是应对当今全球能源危机的核心 也是能源价值创造的重要体现。 尼得科恩布拉科大金 带来制冷方面的节能研究 LMSEY系列冷库一体机使用变频调速可 节能约14%! 全球制冷技术供应商、尼…

浅谈配电能效平台在城市地下综合管廊的应用

摘要:电气设计是综合管廊设计的重要组成部分&#xff0c;文章对综合管廊的断面设计、供配电系统和缆线设计进行了分析阐述&#xff0c;并结合设计案例&#xff0c;简要总结了综合管廊电气设计要点。 关键词:综合管廊&#xff1b;断面设计&#xff1b;供配电系统&#xff1b;缆…

微信小程序如何进行开发?

文章目录 0.引言1.注册微信公众平台账号2.准备微信开发者工具3.创建微信小程序并预览 0.引言 笔者编程一般编得较多的是桌面软件&#xff0c;有时也会编手机软件&#xff0c;这些软件都必须安装才能使用&#xff0c;这限制了软件的推广。而现有社交软件如微信使用得较广泛&…

使用GitHub Actions 来进行项目远程服务器部署

由于项目源码是托管在github的&#xff0c;而部署是放在远程服务器上&#xff0c;并且使用nginx部署。 现在的部署流程时&#xff0c;需要更新时&#xff0c;在本地切换到master分支&#xff0c;执行构建操作&#xff0c;拿到构建出的dist目录&#xff0c;将其上传到远程服务的…

从GitLab拉取并运行项目

从GitLab拉取并运行项目 序Git项目运行运行报错 总结教训 序 搭建好前端基础环境后&#xff0c;开始尝试从单位项目组拉取项目尝试本地运行。 Git Git相关配置&#xff1a;一篇学会Git版本管理 先申请Git账号&#xff0c;随后由上级分配权限拉入该项目组。 通过git clone ……

年终奖大幅缩水,是去还是留?

一、年终奖的历史和意义 互联网行业是中国经济发展的一个重要支柱&#xff0c;这也使得互联网公司的年终奖一直是许多从业者最为关注的话题之一。随着行业的不断发展&#xff0c;互联网公司的年终奖也在不断提高&#xff0c;逐渐成为了企业吸引和留住人才的重要手段。在互联网…

基于archlinux的mabox-linux桌面系统,内存资源消耗只有278M!

今天安装了基于archlinux的mabox-linux桌面系统&#xff0c;内存资源消耗只有278M&#xff01;实在是惊到我了。先上图&#xff0c;改天有空再深入体验一下。 安装后发现没有1920X1080这个分辨率&#xff0c;设置的命令如下&#xff1a; 一&#xff0c;xrandr #查看本机支持的…

短期面试突击攻略大全!2023最全Java面试题目合集

这两年的面试难度确实要比往年高处很多。很多小伙伴投递了上千份简历&#xff0c;只有几家公司约面试。排除个人简历的因素&#xff0c;这在往年都是不太常见的。 大厂缩招&#xff0c;于是很多往年能进大厂的人只能去卷中小厂&#xff0c;搞得层层内卷。 比如往年能有一万个人…

旅游卡分销小程序开发

旅游业的不断发展&#xff0c;旅游卡分销小程序成为了越来越多旅游公司的选择。旅游卡分销小程序是一种在线分销平台&#xff0c;它允许游客购买旅游卡并分销给其他人&#xff0c;从而获得佣金。本文将探讨旅游卡分销小程序的开发。 一、确定旅游卡分销小程序的需求 在…