SpringBoot3+Vue3 前后端分离项目实现登录验证码

1.实现效果

2.后端实现

2.1 Maven

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.28</version>
</dependency>

2.2 后端实现代码

package com.dragon.springboot3vue3.controller;

import cn.dev33.satoken.util.SaResult;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;


@Tag(name = "验证码接口")
@RestController
@RequestMapping("/captcha")
public class CaptchaController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Operation(summary = "获取验证码")
    @GetMapping("/get")
    public SaResult capture(){
        // 设置验证码图片长、宽、验证码字符数、干扰线宽度
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(100,30,4,10);
        // 获取验证码
        String code = lineCaptcha.getCode();
        // 存入 redis
        stringRedisTemplate.opsForValue().set("verifyCode",code,60, TimeUnit.SECONDS);
        // 转为 Base64 字符串
        String imageBase64Data = lineCaptcha.getImageBase64Data();
        return SaResult.ok().setData(imageBase64Data);
    }
}

3.前端实现

<template>
  <div class="container">
    <div class="loginContainer">
      <!-- 登录表单 -->
      <el-container v-if="loginFlag">
        <el-header>
          <h1 class="title">登录</h1>
        </el-header>
        <el-main>
          <el-form class="form" ref="loginRef" :model="loginForm" :rules="rules" label-width="auto">
            <el-form-item prop="username">
              <el-input v-model="loginForm.username" placeholder="用户名" @keydown.enter="login" :prefix-icon="User" />
            </el-form-item>
            <el-form-item prop="password">
              <el-input v-model="loginForm.password" type="password" placeholder="密码" @keydown.enter="login" :prefix-icon="Lock" />
            </el-form-item>
            <el-form-item prop="verifyCode" class="verifyCode">
              <div class="verifyCodeInput">
                <el-input v-model="loginForm.verifyCode" placeholder="验证码" @keydown.enter="login" :prefix-icon="Message" />
              </div>
              <div class="verifyCodeImage">
                <el-image :src="captchaSrc" @click="getCaptcha"></el-image>
              </div>
            </el-form-item>
          </el-form>
        </el-main>
        <el-footer>
          <div class="footer">
            <div class="flex">
              <el-checkbox v-model="loginForm.remenberFlag">记住我</el-checkbox>
              <el-link type="primary" :underline="false">忘记密码?</el-link>
            </div>
            <el-button class="button" type="primary" @click="login">登录</el-button>
            <el-link type="primary" :underline="false" @click="change">注册</el-link>
          </div>
        </el-footer>
      </el-container>

      <!-- 注册表单 -->
      <el-container v-else>
        <el-header>
          <h1 class="title">注册</h1>
        </el-header>
        <el-main>
          <el-form class="form" ref="registerRef" :model="registerForm" :rules="rules" label-position="right" label-width="auto">
            <el-form-item label="用户名" prop="username">
              <el-input v-model="registerForm.username" placeholder="请输入用户名" />
            </el-form-item>
            <el-form-item label="密码" prop="password">
              <el-input v-model="registerForm.password" type="password" placeholder="请输入密码" />
            </el-form-item>
            <el-form-item label="密码" prop="confirmPassword">
              <el-input v-model="registerForm.confirmPassword" type="password" placeholder="请输入确认密码" />
            </el-form-item>
          </el-form>
        </el-main>
        <el-footer>
          <div class="footer">
            <el-button class="button" type="primary" @click="register">注册</el-button>
            <el-link type="primary" :underline="false" @click="change">登录</el-link>
          </div>
        </el-footer>
      </el-container>
    </div>
  </div>
</template>

<script setup> 
  import {ref,reactive} from 'vue'
  import userApi from '@/api/sys/user'
  import captchaApi from '@/api/sys/captcha'
  import router from '@/router';
  import {useTokenStore} from '@/stores/token'
  import { User,Lock,Message } from '@element-plus/icons-vue';

  let loginFlag=ref(true)
  const loginRef=ref()
  let registerForm=reactive({
    username:'',
    password:'',
    confirmPassword:''
  })
  let loginForm=reactive({
    username:'',
    password:'',
    verifyCode:''
  })

  // 后端返回图片的Base64字符串
  let captchaSrc=ref()

  // 保存 registerFlag 和 registerForm 初始值(... 拷贝)
  let initRegisterForm={ ...registerForm }
  let initLoginForm={ ...loginForm }

  // 注册
  let register= async()=>{
    await userApi.register(registerForm);
    // 路由跳转
    router.push('/login');
  }

  // 登录
  let login= async()=>{
    if (loginRef.value) {
      loginRef.value.validate(async(valid) => {
        if (valid) {
          let response=await userApi.login(loginForm);
          // 缓存Token
          useTokenStore().set(response.data);
          // 路由跳转
          router.push('/home/index');
        } else {
          return false;
        }
      });
    }
  }
  
  // 跳转并清空表单
  let change= ()=>{
    if(loginFlag.value){
      loginFlag.value=false;
      // 重置函数
      Object.assign(registerForm, initRegisterForm);
    }else{
      loginFlag.value=true;
      // 重置函数
      Object.assign(loginForm, initLoginForm);
    }
  }

  // 自定义校验规则函数
  let checkConfirmPwd= (rule,value,callback)=>{
    if(value===''){
      callback(new Error("请输入确认密码"));
    }else if(value!=registerForm.password){
      callback(new Error("两次输入的密码不一致"));
    }else{
      callback();
    }
  }

  // 定义校验规则(rules.username <-> prop='username')
  let rules={
    username:[
      { required: true, message:'请输入用户名', trigger: 'blur'},
      { min:3, max:16, message:"用户名长度3-16位", trigger: 'blur'}
    ],
    password:[
      { required: true, message:'请输入密码', trigger: 'blur'},
      { min:6, max:16, message:"密码长度6-16位", trigger: 'blur'}
    ],
    confirmPassword:[
      { required: true, validator:checkConfirmPwd, trigger:'blur' }
    ],
    verifyCode:[
      { required: true, message:'请输入验证码', trigger: 'blur'}
    ],
  }

  let getCaptcha = async()=>{
    let response=await captchaApi.get();
    captchaSrc.value = response.data;
  }

  onMounted(()=>{
    getCaptcha();
  })

</script>

<style scoped lang="less">
  .container{
    background-image: url('@/assets/bg.jpg'); 
    background-repeat: no-repeat;
    background-size: cover;
    height: 100vh;
    width: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .loginContainer{
    height: 400px;
    width: 400px;
    padding: 10px;
    // background-color: #fff;
    /* 添加半透明背景色 */
    background-color: rgba(255, 255, 255, 0.5); 
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    .title{
      color: black;
      font-weight: 600;
      display:flex;
      justify-content:center;
    }
    .form{
      display:flex;
      flex-direction:column;
      justify-content:center;
    }
    .footer{
      display:flex;
      flex-direction:column;
      justify-content:center;
      .flex{
        display: flex;
        justify-content: space-between;
      }
      .button{
        width: 100%;
        margin: 10px 0;
      }
    }
  }
  .verifyCode{
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  .verifyCodeInput{
    flex: 1;
  }
  .verifyCodeImage{
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-left: 50px;
  }
</style>

4.登录时,后端校验用户名、密码、验证码

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值