vue3+threejs+koa可视化项目——实现登录注册(第三步)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: vue3+threejs+koa可视化项目——实现登录注册(第三步)

⭐前言

大家好,我是yma16,本文分享关于 vue3+threejs+koa可视化项目——实现登录注册。

jwt登录注册

JWT(JSON Web Token)是一种标准的身份验证和授权解决方案,它通过使用JSON格式的令牌来实现用户的身份验证和授权,避免了传统的基于会话的身份验证方案的一些问题。

JWT登录注册的原理如下:

  1. 注册:用户在注册时提供用户名和密码,服务器将用户信息保存在数据库中。密码通常需要进行哈希处理,以增加安全性。
  2. 登录:用户提供用户名和密码进行身份验证时,服务器验证用户名和密码是否匹配数据库中的记录。如果匹配成功,服务器会为该用户生成一个JWT令牌。
  3. 令牌生成:JWT由三部分组成,分别是头部(header)、载荷(payload)和签名(signature)。头部包含算法和令牌类型等信息,载荷包含用户的身份信息和其他自定义信息,签名用于验证令牌的合法性。
  4. 令牌签名:服务器使用服务器端的私钥对头部和载荷进行签名,生成签名部分。客户端接收到令牌后,可以使用服务器端的公钥进行验证,确保令牌没有被修改过。
  5. 令牌的验证和使用:客户端在每次请求时将令牌作为请求的一部分(通常是在请求头的"Authorization"字段中)发送到服务器。服务器通过解析令牌的签名部分并验证签名的合法性,确定令牌的有效性。如果通过验证,服务器可以根据令牌中的载荷部分进行用户的身份验证和授权等操作。
  6. 令牌的刷新:JWT令牌通常有一个过期时间,当令牌过期时,客户端需要重新获取新的令牌。在令牌过期之前,客户端可以使用刷新令牌来获取新的令牌,而无需重新进行身份验证。

通过JWT登录注册的方式,可以实现无状态的身份验证和授权,减少服务器的负担和数据库的访问频率。同时,JWT还可以跨多个服务进行验证和授权,提高了系统的可扩展性和安全性。

💖往期node系列文章

node_windows环境变量配置

node_npm发布包

linux_配置node

node_nvm安装配置

node笔记_http服务搭建(渲染html、json)

node笔记_读文件

node笔记_写文件

node笔记_连接mysql实现crud

node笔记_formidable实现前后端联调的文件上传

node笔记_koa框架介绍

node_koa路由

node_生成目录

node_读写excel

node笔记_读取目录的文件

node笔记——调用免费qq的smtp发送html格式邮箱

node实战——搭建带swagger接口文档的后端koa项目(node后端就业储备知识)

node实战——后端koa结合jwt连接mysql实现权限登录(node后端就业储备知识)

node实战——koa给邮件发送验证码并缓存到redis服务(node后端储备知识)

node实战——koa实现文件下载和图片/pdf/视频预览(node后端储备知识)

💖threejs系列相关文章

THREE实战1_代码重构点、线、面

THREE实战2_正交投影相机与透视相机

THREE实战3_理解光源

THREE实战4_3D纹理

THREE实战5_canvans纹理

THREE实战6_加载fbx模型

💖vue3+threejs系列

vue3+threejs可视化项目——搭建vue3+ts+antd路由布局(第一步)

vue3+threejs可视化项目——引入threejs加载钢铁侠模型(第二步)

koa后端登录注册逻辑(jwt)

用户表配置(用户名、密码、token)

/*
 Navicat Premium Data Transfer
 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 80016
 Source Host           : localhost:3306
 Source Schema         : threejs_data
 Target Server Type    : MySQL
 Target Server Version : 80016
 File Encoding         : 65001
 Date: 28/01/2024 23:56:13
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'id',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '加密的密码',
  `real_pwd` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '真实密码',
  `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'token',
  `lasted_login_time` datetime(0) NULL DEFAULT NULL COMMENT '最近登录时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;

💖 koa登录注册

逻辑实现

const Router = require('koa-router');
const router = new Router();
const {execMysql}=require('../../utils/mysql/index')
const {decrypt}=require('../../utils/aes/index')
const jwtToken = require("jsonwebtoken");
const {getRedisKey,setRedisConfig}=require('../../utils/redis/index');
//appKey
const {appKey}=require('../../common/const')
// 唯一字符串
function uuid() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = Math.random() * 16 | 0,
            v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}
// 当前时间
const  getCurrentTime=() =>{
    const now = new Date()
    const year = now.getFullYear()
    const month = now.getMonth()
    const date = now.getDate()
    const hour = now.getHours()
    const minutes = now.getMinutes()
    const second = now.getSeconds()
    const formatNum = (n) => {
        return n > 9 ? n.toString() : '0' + n
    }
    return `${year}-${formatNum(month + 1)}-${formatNum(date)} ${formatNum(hour)}:${formatNum(minutes)}:${formatNum(second)}`
}
// 注册
router.post('/register', async (ctx) => {
   try{
       // 解析参数
       const bodyParams =  ctx.request.body
       const {username,password,emailCode} = bodyParams;
       console.log('emailCode',emailCode)
       console.log('emailCode',emailCode)
       if(!username||!password){
           return ctx.body = {
               code: 0 ,
               msg:'username or password is null'
           };
       }
       const emailRedisCode=await getRedisKey(username)
       console.log('emailRedisCode',emailRedisCode)
       if(emailCode!==emailRedisCode){
           return ctx.body = {
               code: 0 ,
               msg:'email code is error'
           };
       }
       // 查询重复
       const search=await execMysql(`select count(1) as total from user where username='${username}';`)
       console.log('search',search)
       if(search[0].total>0){
           return ctx.body = {
               code: 0 ,
               msg:'user is exist'
           };
       }
       // id 唯一字符
       const id= uuid()
       const create_time=getCurrentTime()
       console.log('password',password)
       const real_pwd=await decrypt(password)
       console.log('real_pwd',real_pwd)
       // 插入 数据
       const createRes=await execMysql(`INSERT INTO user (id,username,password,real_pwd,create_time) VALUES ('${id}', '${username}','${password}','${real_pwd}','${create_time}');`)
       // 更新token update_time
       const token=jwtToken.sign(
           {
               username,
               password
           },
           appKey, // secret
           { expiresIn: 24 * 60 * 60 } // 60 * 60 s
       )
       const update_time=getCurrentTime()
       const tokenRes=await execMysql(`update user set token='${token}', update_time='${update_time}' where username='${username}';`)
       ctx.body = {
           code:200,
           data:{
               createSqlData:createRes,
               tokenSqlData:tokenRes
           },
           msg:' insert success',
           token:token
       };
   }
   catch (e) {
       ctx.body = {
           code:0,
           msg:JSON.stringify(e)
       };
   }
});
// 获取token
router.post('/token/gen', async (ctx) => {
    try{
        // 解析参数
        const bodyParams =  ctx.request.body
        const {username,password} = bodyParams;
        const real_pwd=await decrypt(password);
        // 查询 用户
        const search=await execMysql(`select count(1) as total from user where username='${username}' and real_pwd='${real_pwd}';`)
        if(search[0].total>0){
            // 更新token update_time
            const token=jwtToken.sign(
                {
                    username,
                    password
                },
                appKey, // secret
                { expiresIn: 24 * 60 * 60 } // 60 * 60 s
            )
            const update_time=getCurrentTime()
            // 更新token
            const tokenRes=await execMysql(`update user set token='${token}', update_time='${update_time}' where username='${username}';`)
            // 配置token
            const AUTHORIZATION='Authorization'
            ctx.set(AUTHORIZATION,token)
            return ctx.body = {
                code:200,
                msg:'login success',
                token:token
            };
        }
        ctx.body = {
            code:0,
            msg:' login fail',
        };
    }
    catch (e) {
        ctx.body = {
            code:0,
            msg:e
        };
    }
});
// token 登录
router.post('/token/login',async (ctx) => {
    try{
        // 解析参数
        const bodyParams =  ctx.request.body
        const {token} = bodyParams;
        const payload = jwtToken.verify(token, appKey);
        const {username,password} =payload
        const real_pwd=await decrypt(password);
        // 查询 用户
        const search=await execMysql(`select count(1) as total from user where username='${username}' and real_pwd='${real_pwd}';`)
        console.log(search)
        if(search[0].total>0){
            const last_login_time=getCurrentTime()
            // last_login_time  登录时间
            const tokenRes=await execMysql(`update user set lasted_login_time='${last_login_time}' where username='${username}' and password='${password}';`)
            return ctx.body = {
                code:200,
                msg:'login success',
                data:{
                    username
                }
            };
        }
        ctx.body = {
            code:0,
            msg:' login fail',
        };
    }
    catch (e) {
        console.log('e',e)
        ctx.body = {
            code:0,
            msg:JSON.stringify(e)
        };
    }
})
module.exports = router;

⭐vue3前端登录注册权限控制

💖 登录页面

login/index.vue

<template>
    <div class="container">
        <div class="loginUser-container">
            <div class="loginUser-title"></div>
            <a-form
                    :model="state.formState"
                    :label-col="state.layoutConfig.labelCol"
                    :wrapper-col="state.layoutConfig.wrapperCol"
                    :rules="state.formRule"
                    ref="formRef"
                    layout="vertical"
                    autocomplete="off"
            >
                <a-form-item label="账号" name="username">
                    <a-input
                            v-model:value="state.formState.username"
                            allowClear
                            placeholder="账号"
                            :disabled="state.spinning"
                    />
                </a-form-item>
                <a-form-item label="密码" name="password">
                    <a-input-password
                            v-model:value="state.formState.password"
                            :disabled="state.spinning"
                            allowClear
                            placeholder="请输入密码"
                    />
                </a-form-item>
                <a-form-item name="remember" :wrapper-col="state.wrapperCol">
                    <a-checkbox
                            v-model:checked="state.formState.remember"
                            :disabled="state.spinning"
                    >记住密码</a-checkbox
                    >
                </a-form-item>
                <a-form-item :wrapper-col="state.submitWrapperCol" class="submit-box">
                    <a-button
                            type="primary"
                            html-type="submit"
                            @click="loginAction"
                            :loading="state.spinning"
                            style="width: 100%; font-size: 16px; font-weight: bolder"
                    >登录</a-button
                    >
                </a-form-item>
            </a-form>
            <div class="description">
                <span class="description-prefix">没账号?</span>
                <span
                        @click="jumpRegister"
                        class="description-after"
                        :disabled="state.spinning"
                >去注册</span
                >
            </div>
        </div>
    </div>
</template>
<script lang="ts" setup>
    import { reactive, ref, onMounted } from "vue";
    import { useRouter } from "vue-router";
    import { useStore } from "vuex";
    import { message } from "ant-design-vue";
    import {aes} from '@/utils/index'
    import type { FormInstance } from "ant-design-vue";
    interface FormStateType {
        username: string;
        password: string;
        remember: boolean;
    }
    interface FormRuleType {
        username: Object;
        password: Object;
    }
    interface stateType {
        formState: FormStateType;
        formRule: FormRuleType;
        layoutConfig: any;
        wrapperCol: any;
        submitWrapperCol: any;
        spinning: boolean;
        backgroundImgUrl: string;
    }
    // 路由
    const router = useRouter();
    //store
    const store = useStore();
    const formRef = ref<FormInstance>();
    const state: stateType = reactive({
        formState: {
            username: "",
            password: "",
            remember: false,
        },
        spinning: false,
        formRule: {
            username: [{ required: true, message: "请输入账号!" }],
            password: [{ required: true, message: "请输入密码!" }],
        },
        layoutConfig: {
            labelCol: {
                span: 8,
            },
            wrapperCol: {
                span: 24,
            },
        },
        wrapperCol: { offset: 0, span: 24 },
        submitWrapperCol: { offset: 0, span: 24 },
        backgroundImgUrl:
            "http://www.yongma16.xyz/staticFile/common/img/background.png",
    });
    /**
     * 初始化表单内容
     */
    const initForm = () => {
        const userInfoItem: any = window.localStorage.getItem("userInfo");
        interface userInfoType {
            username: string;
            password: string;
            remember: boolean;
        }
        const userInfo: userInfoType = userInfoItem
            ? JSON.parse(userInfoItem)
            : {
                username: "",
                password: "",
                remember: false,
            };
        if (userInfo.username && userInfo.password) {
            state.formState.username = userInfo.username;
            state.formState.password = aes.decrypt(userInfo.password);
            state.formState.remember = userInfo.remember;
        }
    };
    /**
     * 前往注册!
     */
    const jumpRegister = () => {
        // 带 hash,结果是 /about#team
        router.push({ path: "/register" });
    };
    /**
     * 前往home!
     */
    const jumpHome = () => {
        // 带 hash,结果是 /about#team
        router.push({ path: "/" });
    };
    /**
     * 记住密码
     * @param params
     */
    const rememberAction = (params: Object) => {
        window.localStorage.setItem("userInfo", JSON.stringify(params));
    };
    /**
     * 登录
     */
    const loginAction = () => {
        if (formRef.value) {
            formRef.value.validate().then(async (res: any) => {
                state.spinning = true;
                const params = {
                    username: state.formState.username,
                    password: aes.encrypt(state.formState.password),
                };
                if (state.formState.remember) {
                    rememberAction({ ...params, remember: state.formState.remember });
                }
                try {
                    console.log('登录',params)
                    // @ts-ignore
                    await store.dispatch(
                        "user/getUserTokenAction",
                        params
                    );
                    // 跳转
                    setTimeout(() => {
                        jumpHome();
                    }, 500);
                    state.spinning = false;
                } catch (r: any) {
                    message.error(JSON.stringify(r));
                    state.spinning = false;
                    throw Error(r);
                }
            });
        }
    };
    onMounted(() => {
        //初始化
        initForm();
    });
</script>
<style lang="less">
    .background {
        /*background: #1890ff;  !* fallback for old browsers *!*/
        /*background: -webkit-linear-gradient(to top, #000C40, #F0F2F0);  !* Chrome 10-25, Safari 5.1-6 *!*/
        /*background: linear-gradient(to top, #000C40, #F0F2F0); !* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ *!*/
        /*background-image: url("http://yongma16.xyz/staticFile/common/img/background.png");*/
        /*background-repeat: no-repeat;*/
        /*background-size: 100%;*/
    }
    .container {
        /*background: #262626;*/
        background-clip: border-box;
        position: absolute;
        width: 100vw;
        height: 100vh;
        .background();
    }
    .loginUser-container {
        position: absolute;
        min-width: 400px;
        min-height: 350px;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
    }
    .submit-box {
        text-align: center;
        width: 100%;
        margin: 0 auto;
    }
    .loginUser-container {
        background-color: rgba(255, 255, 255, 0.8);
        border-radius: 10px;
        padding: 0 20px;
    }
    .loginUser-title {
        margin-top: 20px;
        width: 100%;
        text-align: center;
        font-weight: bolder;
        font-size: 16px;
    }
    .description {
        margin-top: 20px;
        width: 100%;
        text-align: center;
        .description-after {
            color: #1890ff;
            cursor: pointer;
        }
    }
</style>

登录页面

💖 注册页面

register/index.vue

<template>
    <a-spin tip="登录中..." :spinning="state.spinning">
        <!--    <div-->
        <!--      class="container"-->
        <!--      :style="{-->
        <!--        backgroundImage:url(state.backgroundImgUrl),-->
        <!--      }"-->
        <!--    >-->
        <div class="container">
            <div class="register-container">
                <div class="register-title"></div>
                <a-form
                        :model="state.formState"
                        :label-col="state.layoutConfig.labelCol"
                        :wrapper-col="state.layoutConfig.wrapperCol"
                        :rules="state.formRule"
                        ref="formRef"
                        layout="vertical"
                        autocomplete="off"
                >
                    <a-row>
                        <a-col :span="16">
                            <a-form-item label="邮箱" name="username">
                                <a-input
                                        v-model:value="state.formState.username"
                                        allowClear
                                        placeholder="请输入邮箱"
                                />
                            </a-form-item>
                        </a-col>
                        <a-col :span="8">
                            <a-form-item style="margin-top:31.53px;text-align: right">
                                <a-button @click="sendEmail" type="primary" :loading="state.loadingEmailCode" >
                                    {{state.awaitTime>0?`${state.awaitTime}s`:'发送验证码'}}
                                </a-button>
                            </a-form-item>
                        </a-col>
                    </a-row>
                    <a-form-item label="验证码" name="code">
                        <a-input
                                :disabled="state.isErrorEmail"
                                v-model:value="state.formState.code"
                                allowClear
                                placeholder="请输入验证码"
                        />
                    </a-form-item>
                    <a-form-item label="密码" name="password">
                        <a-input-password
                                :disabled="state.isErrorEmail"
                                v-model:value="state.formState.password"
                                allowClear
                                placeholder="请输入验证码"
                        />
                    </a-form-item>
                    <a-form-item label="确认密码" name="passwordBack">
                        <a-input-password
                                :disabled="state.isErrorEmail"
                                v-model:value="state.formState.passwordBack"
                                allowClear
                                placeholder="请确认密码"
                        />
                    </a-form-item>
                    <a-form-item :wrapper-col="state.submitWrapperCol" class="submit-box">
                        <a-button
                                type="primary"
                                html-type="submit"
                                @click="registerAction"
                                :loading="state.spinning"
                                style="width: 100%; font-weight: bolder"
                        >注册</a-button
                        >
                    </a-form-item>
                </a-form>
                <div class="description">
                    <span class="description-prefix">有账号?</span>
                    <span @click="jumpLogin" class="description-after">去登录</span>
                </div>
            </div>
        </div>
    </a-spin>
</template>
<script lang="ts" setup>
    import { reactive, ref } from "vue";
    import { useRouter } from "vue-router";
    import { message } from "ant-design-vue";
    import { registerUser,getEmailCode } from "@/service/user/index";
    import {aes} from '@/utils/index'
    import type { Rule } from "ant-design-vue/es/form";
    import type { FormInstance } from "ant-design-vue";
    import { useStore } from "vuex";
    interface FormStateType {
        username: string;
        password: string;
        passwordBack: string;
        code:string;
        remember: boolean;
    }
    interface FormRuleType {
        username: Object;
        password: Object;
    }
    interface stateType {
        formState: FormStateType;
        formRule: FormRuleType;
        layoutConfig: any;
        wrapperCol: any;
        submitWrapperCol: any;
        spinning: boolean;
        backgroundImgUrl: string;
        isErrorEmail:boolean,
        remoteEmailCode:string,
        loadingEmailCode:boolean,
        awaitTime:number
    }
    // 路由
    const router = useRouter();
    //store
    const store = useStore();
    const formRef = ref<FormInstance>();
    const state: stateType = reactive({
        formState: {
            code:'',
            username: "1432448610@qq.com",
            password: "",
            passwordBack: "",
            remember: false,
        },
        spinning: false,
        formRule: {
            username: [{ required: true, message: "请输入邮箱!" },{validator:validatorEmail,trigger:'blur'}],
            code: [{ required: false, message: "请输入验证码!" }],
            password: [{ required: true, message: "请输入密码!" }],
            passwordBack: [
                { required: true },
                { validator: validatePass, trigger: "blur" },
            ],
        },
        layoutConfig: {
            labelCol: {
                span: 8,
            },
            wrapperCol: {
                span: 24,
            },
        },
        awaitTime:-1,
        remoteEmailCode:'',
        isErrorEmail:true,
        loadingEmailCode:false,
        wrapperCol: { offset: 0, span: 24 },
        submitWrapperCol: { offset: 0, span: 24 },
        backgroundImgUrl:
            "http://www.yongma16.xyz/staticFile/common/img/background.png",
    });
    // sleep
    const sleep=(delay:number)=>{
        return new Promise(resolve=>setTimeout(resolve,delay*1000))
    }
    /**
     * 前往登录!
     */
    const jumpLogin = () => {
        // 带 hash,结果是 /about#team
        router.push({ path: "/login" });
    };
    // 确认密码
    async function validatePass(_rule: Rule, value: string) {
        if (value === "") {
            return Promise.reject("请确认密码!");
        } else if (value !== state.formState.password) {
            return Promise.reject("密码不一致!");
        } else {
            return Promise.resolve();
        }
    }
    // 校验邮箱
    async function validatorEmail(_rule: Rule, value: string) {
        if (value === "") {
            state.isErrorEmail=true
            return Promise.reject("请输入邮箱!");
        } else {
            const reg = /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/;
            if (!reg.test(value)) {
                state.isErrorEmail=true
                return Promise.reject("邮箱格式不正确!");
            }
            state.isErrorEmail=false
            return Promise.resolve();
        }
    }
    // 校验邮箱
    async function validatorEmailCode(_rule: Rule, value: string) {
        if (value!==state.remoteEmailCode) {
            return Promise.reject("验证码不正确!");
        }
        return Promise.resolve();
    }
    /**
     * 前往home!
     */
    const jumpHome = () => {
        // 带 hash,结果是 /about#team
        router.push({ path: "/" });
    };
    /**
     * 注册
     */
    const registerAction = () => {
        if (formRef.value) {
            formRef.value.validate().then((res: any) => {
                state.spinning = true;
                const params = {
                    username: state.formState.username,
                    password: aes.encrypt(state.formState.password),
                    emailCode:state.formState.code
                };
                registerUser(params)
                    .then((res: any) => {
                        state.spinning = false;
                        const { data: response } = res;
                        console.log('response',response)
                        if (response.code === 200) {
                            store.commit("user/setUserToken", response.data);
                            // 跳转
                            setTimeout(()=>{
                                jumpHome();
                            },500)
                            message.success(response.message);
                        } else {
                            message.warning(response.msg);
                        }
                    })
                    .catch((r: any) => {
                        state.spinning = false;
                        message.error(JSON.stringify(r));
                        throw Error(r);
                    });
            });
        }
    };
    const delayTime=async ()=>{
        if (state.awaitTime>0) {
            await sleep(1)
            state.awaitTime-=1
            delayTime()
        }
    };
    const sendEmail=async ()=>{
        if(state.awaitTime>0){
            return
        }
        if(!state.formState.username){
            return message.warn('请输入邮箱!')
        }
        // if(state.isErrorEmail)
        // {
        //     return message.warn('请检查邮箱格式!')
        // }
        try{
            state.loadingEmailCode=true
            const res=await getEmailCode({
                email:state.formState.username
            })
            if(res?.data?.data?.emailRes?.code==200){
                // state.remoteEmailCode=res.data.data.code
                state.isErrorEmail=false
                //  倒计时
                state.awaitTime=10
                message.success('发送邮件成功!请查收\t'+state.formState.username)
                delayTime()
            }
            else{
                state.isErrorEmail=false
                message.warn(res.data.data.emailRes.msg.response)
            }
        }
        catch (e) {
            message.error(JSON.stringify(e))
        }
        finally {
            state.loadingEmailCode=false
        }
    }
</script>
<style lang="less">
    .background {
        /*background: #1890ff;  !* fallback for old browsers *!*/
        /*background: -webkit-linear-gradient(to top, #000C40, #F0F2F0);  !* Chrome 10-25, Safari 5.1-6 *!*/
        /*background: linear-gradient(to top, #000C40, #F0F2F0); !* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ *!*/
    }
    .container {
        /*background: #262626;*/
        background-clip: border-box;
        position: absolute;
        width: 100vw;
        height: 100vh;
        .background();
    }
    .register-container {
        position: absolute;
        min-width: 400px;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        box-sizing: border-box;
    }
    .submit-box {
        text-align: center;
        width: 100%;
        margin: 0 auto;
    }
    .register-container {
        background-color: rgba(255, 255, 255, 0.8);
        border-radius: 10px;
        padding: 0 20px;
    }
    .register-title {
        /*background: #1890ff;*/
        /*color:#fff;*/
        width: 100%;
        text-align: center;
        font-weight: bolder;
        padding: 20px;
        font-size: 24px;
    }
    .description {
        margin-top: 20px;
        width: 100%;
        text-align: center;
        .description-after {
            color: #1890ff;
            cursor: pointer;
        }
    }
</style>

注册页面

注册发送邮件

⭐总结

前端vue3

a. 路由拦截

b. store缓存token

后端koa

a. jwt配置

b. jwt的白名单(发送验证码、注册、获取token)

Koa JWT is a middleware for Koa.js, a web application framework for Node.js. JWT, short for JSON Web Token, is a compact and self-contained mechanism for securely transmitting information between parties as a JSON object.

Koa JWT provides authentication and authorization functionalities using JWT in Koa applications. It allows you to generate and verify JWT tokens, protect routes from unauthorized access, and retrieve authenticated user information from the JWT token.

Koa JWT 是 Koa.js 的中间件,Koa 是 Node.js 的 Web 应用程序框架。JWT 是 JSON Web 令牌的缩写,是一种紧凑且独立的机制,用于将信息作为 JSON 对象在各方之间安全地传输。

Koa JWT 在 Koa 应用程序中使用 JWT 提供身份验证和授权功能。它允许您生成和验证 JWT 令牌,保护路由免受未经授权的访问,并从 JWT 令牌中检索经过身份验证的用户信息。

⭐结束

本文分享到这结束,如有错误或者不足之处欢迎指出!

目录
相关文章
|
22天前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
126 64
|
22天前
|
JavaScript 前端开发 API
Vue 3 中 v-model 与 Vue 2 中 v-model 的区别是什么?
总的来说,Vue 3 中的 `v-model` 在灵活性、与组合式 API 的结合、对自定义组件的支持等方面都有了明显的提升和改进,使其更适应现代前端开发的需求和趋势。但需要注意的是,在迁移过程中可能需要对一些代码进行调整和适配。
101 60
|
22天前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
28 8
|
21天前
|
存储 JavaScript 数据管理
除了provide/inject,Vue3中还有哪些方式可以避免v-model的循环引用?
需要注意的是,在实际开发中,应根据具体的项目需求和组件结构来选择合适的方式来避免`v-model`的循环引用。同时,要综合考虑代码的可读性、可维护性和性能等因素,以确保系统的稳定和高效运行。
21 1
|
21天前
|
JavaScript
Vue3中使用provide/inject来避免v-model的循环引用
`provide`和`inject`是 Vue 3 中非常有用的特性,在处理一些复杂的组件间通信问题时,可以提供一种灵活的解决方案。通过合理使用它们,可以帮助我们更好地避免`v-model`的循环引用问题,提高代码的质量和可维护性。
32 1
|
22天前
|
JavaScript
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
|
25天前
|
JavaScript 前端开发 API
从Vue 2到Vue 3的演进
从Vue 2到Vue 3的演进
37 0
|
25天前
|
JavaScript API 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
27天前
|
JavaScript 前端开发 开发者
vue 数据驱动视图
总之,Vue 数据驱动视图是一种先进的理念和技术,它为前端开发带来了巨大的便利和优势。通过理解和应用这一特性,开发者能够构建出更加动态、高效、用户体验良好的前端应用。在不断发展的前端领域中,数据驱动视图将继续发挥重要作用,推动着应用界面的不断创新和进化。
|
1天前
|
JavaScript 关系型数据库 MySQL
基于VUE的校园二手交易平台系统设计与实现毕业设计论文模板
基于Vue的校园二手交易平台是一款专为校园用户设计的在线交易系统,提供简洁高效、安全可靠的二手商品买卖环境。平台利用Vue框架的响应式数据绑定和组件化特性,实现用户友好的界面,方便商品浏览、发布与管理。该系统采用Node.js、MySQL及B/S架构,确保稳定性和多功能模块设计,涵盖管理员和用户功能模块,促进物品循环使用,降低开销,提升环保意识,助力绿色校园文化建设。