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

文章目录

    • ⭐前言
      • 💖往期node系列文章
      • 💖threejs系列相关文章
      • 💖vue3+threejs系列
    • ⭐koa后端登录注册逻辑(jwt)
      • 💖 koa登录注册
    • ⭐vue3前端登录注册权限控制
      • 💖 登录页面
      • 💖 注册页面
    • ⭐总结
    • ⭐结束

yma16-logo

⭐前言

大家好,我是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>

注册页面
register-page
注册发送邮件
sendEmailCode

⭐总结

前端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 令牌中检索经过身份验证的用户信息。

⭐结束

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

shot

👍 点赞,是我创作的动力!
⭐️ 收藏,是我努力的方向!
✏️ 评论,是我进步的财富!
💖 最后,感谢你的阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AcWing.883.高斯消元解线性方程组

输入一个包含 n 个方程 n 个未知数的线性方程组。 方程组中的系数为实数。 求解这个方程组。 下图为一个包含 m 个方程 n 个未知数的线性方程组示例&#xff1a; 输入格式 第一行包含整数 n n n。 接下来 n n n 行&#xff0c;每行包含 n 1 n1 n1 个实数&#xff0c;表…

01背包问题 动态规划

01背包问题 动态规划 01背包问题 动态规划写了点代码 C#实现程序运行结果代码和程序已经上传 01背包问题 动态规划 很有意思的问题。 写了点代码 C#实现 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Ta…

二进制漏洞挖掘之ret2text栈溢出

栈溢出产生的主要原因是对一些边界未进行严格检查&#xff0c;攻击者可以通过覆盖函数的返回地址执行任意代码。栈溢出漏洞主要的利用方式是ROP&#xff08;Return Oriented Programming&#xff0c;返回导向编程&#xff09;&#xff0c;通过覆盖返回地址&#xff0c;使程序跳…

【01】Linux 基本操作指令

带⭐的为重要指令 &#x1f308; 01、ls 展示当前目录下所有文件&#x1f308; 02、pwd 显示用户当前所在路径&#x1f308; 03、cd 进入指定目录&#x1f308; 04、touch 新建文件&#x1f308; 05、tree 以树形结构展示所有文件⭐ 06、mkdir 新建目录⭐ 07、rmdir 删除目录⭐…

C++进阶(八)红黑树

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、红黑树的概念二、红黑树的性质三、红黑树结构四、红黑树的插入操作1、情况一2、情况二3、…

vue3开发,axios发送请求是携带params参数的避坑

vue3开发,axios发送请求是携带params参数的避坑&#xff01;今天一直报错&#xff0c;点击新增购物车&#xff0c;报错&#xff0c; 【Uncaught (in promise) TypeError: target must be an object】。查询了网上的资料说的都不对。都没有解决。最终还是被我整明白了。 网上网…

力扣之2621.睡眠函数

/*** param {number} millis* return {Promise}*/ async function sleep(millis) {return new Promise(resolve > setTimeout(resolve, millis)); }/** * let t Date.now()* sleep(100).then(() > console.log(Date.now() - t)) // 100*/ 这样的异步休眠功能在实际应用…

页面通过Vue进行整体页面不同语言切换 i18n库

目录 引入 如何做到 下载i18n库 构建整体翻译文件结构 语言包文件 i18n配置文件 把i18n挂载到vue实例上 添加按钮点击事件切换语言 引入 我们现在有这样一个要求,我们想要对我们开发的网页进行国际化操作,也就是我们不仅要有中文,还要有英文等。用户可以随时进行不同语言…

DB之家:数据库开发工程师的衣柜(云原生时代数据库性能优化点子集合)

基础数据结构 布隆过滤器&#xff1a; modular bloom filter 减少布隆过滤器所需要的内存。参考文献&#xff1a;Mun, J. H., Zhu, Z., Raman, A., & Athanassoulis, M. (n.d.). LSM-Trees Under (Memory) Pressure. 基础算法 字符串压缩 FSST算法 利用向量化计算加…

多线程代码案例之单例模式

作者简介&#xff1a; zoro-1&#xff0c;目前大二&#xff0c;正在学习Java&#xff0c;数据结构&#xff0c;javaee等 作者主页&#xff1a; zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; 多线程代码案例之单例模式 单例…

【Node-RED】node-red-contrib-opcua-server模块使用(3)

【Node-RED】node-red-contrib-opcua-server模块使用&#xff08;3&#xff09; 前言node-red-contrib-iiot-opcuanode-red-contrib-lativnode-red-contrib-nupmes 前言 在前面博文【Node-RED】node-red-contrib-opcua-server模块使用&#xff08;1&#xff09;我们有提及过&a…

ICMPv6报文解析及NAT处理

ICMPv6报文概述 参考RFC4443和RFC2460 ICMPv6报文是IPv6在internal control management protocol&#xff08;ICMP&#xff09;的基础之上做了一些改动&#xff0c;得到了ICMPv6协议&#xff0c;IPv6的next_header为58。 Message general format 每条ICMPv6消息之前都有一个…

lombok导致的IndexOutOfBoundsException

一、问题描述 ERROR 25152 --- [1.190-81-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.mybatis.spring.MyBatisSyste…

MacBook安装虚拟机VMware Fusion

MacBook安装虚拟机VMware Fusion 官方下载地址: https://customerconnect.vmware.com/cn/downloads/info/slug/desktop_end_user_computing/vmware_fusion/11_0 介绍 之前的版本都要收费,现在出了对个人免费的版本, 棋哥给的破解版的版本是8,升级系统后用不了了. 官方去下载…

thinkadmin操作栏审核通过(操作确认),审核驳回(录入信息)

录入信息页面 {extend name="../../admin/view/main"}{block name=content} <style>textarea {font-size: 16px;padding: 10px;border: 1px solid #ccc;

EtherNET主站转Profinet网关实现EtherNET协议和Profinet协议相互转换

北京兴达易控EtherNET主站转Profinet网关是一种能将EtherNET协议和Profinet协议相互转换的设备&#xff0c;将网络通信技术与工业自动化技术完美结合。它不仅简化了通信的复杂度&#xff0c;而且提高了系统的可靠性和稳定性。 作为一个EtherNET主站转Profinet网关&#xff0c;它…

Python实现avif图片转jpg格式并识别图片中的文字

文章目录 一、图片识别文字1、导包2、代码实现3、运行效果 二、avif格式图片转jpg格式1、导包2、代码实现3、运行效果4、注意事项 三、Python实现avif图片转jpg格式并识别文字全部代码 在做数据分析的时候有些数据是从图片上去获取的&#xff0c;这就需要去识别图片上的文字。P…

软考(高级)在犹豫是否需要报班,不知大家有什么建议?

据我观察&#xff0c;软考是一门可以通过自学掌握的考试&#xff0c;并不争议。然而&#xff0c;尽管如此&#xff0c;我还是不建议大部分同学选择自学&#xff0c;因为相比报班而言&#xff0c;自学的成本反而较高。软考的难度并不低&#xff0c;往年的总体通过率仅为20%&…

IP关联是什么?有什么后果?如何防止电商账号因IP关联被封?

在跨境电商的世界里&#xff0c;IP关联给多账号运营的商家带来了挑战。比如&#xff0c;亚马逊IP关联规则的执行对于那些经营多个店铺的卖家来说可能是一个不小的障碍。IP关联的影响不只是限于亚马逊&#xff0c;其他平台如Instagram、Facebook也有类似的机制&#xff0c;在之前…

oracle数仓rac两个节点查询耗时不一致问题处理

问题描述 数据库节点1查询比节点2查询慢。现场操作应用发现发现同一sql语句在节点2上只要2分钟左右&#xff0c;在节点1&#xff0c;该条sql执行要超过30分钟。 处理过程 根据问题&#xff0c;初步判断是由于错误的执行计划&#xff0c;导致性能问题&#xff0c;但实际上对两…