登录验证码生成vue+springboot+redis+kaptcha

一 项目搭建

1:springboot搭建

1.1 导入基本依赖

注意这里版本使用2.5.1,而非2.6.6
在这里插入图片描述

1.2 pom中引入依赖

引入kaptcha

 <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
            <exclusions>
                <exclusion>
                    <artifactId>javax.servlet-api</artifactId>
                    <groupId>javax.servlet</groupId>
                </exclusion>
            </exclusions>
        </dependency>

1.3 完整的Pom依赖文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>code</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>code</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.79</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
            <exclusions>
                <exclusion>
                    <artifactId>javax.servlet-api</artifactId>
                    <groupId>javax.servlet</groupId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
</project>

1.4 配置yml文件

server:
  port: 8082 # VUE前端端口是8080,我们这里设置为8082
spring:
  redis:
    # 地址
    host: 你自己的IP
    # 端口,默认为6379
    port: 6379
    # 数据库索引
    database: 0
    # 密码
    password: 你自己的密码
    # 连接超时时间
    timeout: 10s
    # 使用lettuce链接方式
    lettuce:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 0
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池的最大数据库连接数
        max-active: 8
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms

1.5 编写Redis配置类

在这里插入图片描述
主要思路是:

1:在后端生成的验证码 要以 key-value的形式保存到redis中,将key和value保存一并发送给前端。

下列代码在后续案例中,将使用springsecurity进行验证。

2:vue前端点击登录后,将Key和验证码发送给后端

3:后端通过key 从redis中获取验证码,如果正确向下处理,不正确,返回给login.Vue中

下列代码是我从rouyi中粘贴过来的,有兴趣的可以研究,一般都是复制就OK了

RedisConfig类

package com.example.code.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * redis配置
 *
 * @author ruoyi
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        serializer.setObjectMapper(mapper);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public DefaultRedisScript<Long> limitScript()
    {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(limitScriptText());
        redisScript.setResultType(Long.class);
        return redisScript;
    }

    /**
     * 限流脚本
     */
    private String limitScriptText()
    {
        return "local key = KEYS[1]\n" +
                "local count = tonumber(ARGV[1])\n" +
                "local time = tonumber(ARGV[2])\n" +
                "local current = redis.call('get', key);\n" +
                "if current and tonumber(current) > count then\n" +
                "    return tonumber(current);\n" +
                "end\n" +
                "current = redis.call('incr', key)\n" +
                "if tonumber(current) == 1 then\n" +
                "    redis.call('expire', key, time)\n" +
                "end\n" +
                "return tonumber(current);";
    }
}

FastJson2JsonRedisSerializer

package com.example.code.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.util.Assert;

import java.nio.charset.Charset;

/**
 * Redis使用FastJson序列化
 * 
 * @author ruoyi
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
    @SuppressWarnings("unused")
    private ObjectMapper objectMapper = new ObjectMapper();

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static
    {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }

    public FastJson2JsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }

    public void setObjectMapper(ObjectMapper objectMapper)
    {
        Assert.notNull(objectMapper, "'objectMapper' must not be null");
        this.objectMapper = objectMapper;
    }

    protected JavaType getJavaType(Class<?> clazz)
    {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}

1.6 编写跨域请求的配置类

我们前台使用的是vue,两个不同的主机地址,所以这个地方必须设置跨域请求,否则会阻拦请求

package com.example.code.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addExposedHeader("Authorization");
        return corsConfiguration;
    }

    /**
     * 跨域过滤器
     */
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }
    @Override
    public void addCorsMappings(CorsRegistry corsRegistry){
        corsRegistry.addMapping("/**").exposedHeaders("Authorization")
                .allowedOrigins("*")
                .allowedMethods("GET","POST","PUT","DELETE")
                .maxAge(3600);
    }
}

2:vue前端搭建

该项目 node版本 node-v14.17.6-x64 (1)

vue2版本
在这里插入图片描述
创建项目目录,在高亮处,输入cmd按回车

输入 vue create myblog命令 创建vue项目
在这里插入图片描述

2.1 选择手动设置

在这里插入图片描述

2.2 按图示选择

在这里插入图片描述

tips

选择方式:

上下箭头 将光标调到选择项

点击 space箭头选择 (显示 * 代表选中)

选择VUE2.x版本
在这里插入图片描述
选择完该项,后一项提示你是否保存,选择n 即可。回车后,再按回车键开始安装项目所有依赖
在这里插入图片描述
如图所示 mycode是你的项目名称,使用npm run serve 运行项目

2.3 安装element

如图所示 打开项目文件夹
在这里插入图片描述
如图所示,安装element

npm i element-ui -S
在这里插入图片描述

2.4 将element导入到项目中

如下如图:打开main.js,引入如下element代码

import ElementUI from 'element-ui';

import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);

在这里插入图片描述
2.5 导入axios

如下图:输入命令 npm install --save axios vue-axios
在这里插入图片描述
同导入element一样,在main.js中导入axios组件

import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)

main.js中的完整代码

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.config.productionTip = false
Vue.use(VueAxios, axios)
Vue.use(ElementUI);
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

将红框内的文件 删除,不删除也没事,就是留着多余的 感觉不爽。

2.5 删除修改无用文件

2.5.1 删除文件

分别是:

public: index.html

src/component: HelloWord.vue

src/views: About.vue  Homr.vue

在这里插入图片描述

2.5.2 修改App.vue文件

删除红框代码
在这里插入图片描述
完整App.Vue代码

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

</style>

2.6 创建Login和Admin组件

再 view文件夹中创建Login.vue 和 Admin.vue,临时创建,看看项目能否正常启动

<template>
    <div class="login">
        this is login
    </div>
</template>
<script>
export default {
    
}
</script>

2.7 配置路由

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../views/Login.vue' //使用预加载Login视图组件
Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Login',
    component: Login
  },
  {
    path: '/admin',
    name: 'Admin',
    component: () => import('../views/Admin.vue') // 懒加载Admin组件
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

2.8 项目运行测试

在终端中启动项目

npm run serve
在这里插入图片描述

3 使用element编写登录界面,

login.vue代码

页面布局自己处理,可以直接拷贝下面的代码,也可以到element form表单项拷贝代码,

<template>
    <div class="login">
        <el-row type="flex" class="row-bg" justify="center">
            <el-col :span="24" class="loginform">
                <h2>验证码代码</h2>
                <div class="loginform">
                    
                    <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
                        <el-form-item label="姓名" prop="name" >
                            <el-input v-model="ruleForm.username" style="width:100%"></el-input>
                        </el-form-item>
                        <el-form-item label="密码" prop="name" >
                            <el-input v-model="ruleForm.password" style="width:100%"></el-input>
                        </el-form-item>
                        <el-form-item label="验证码" prop="name" >
                        <!--  关键在这里,要留出填写验证码 和 显示capImg图片的位置 -->
                            <el-input v-model="ruleForm.code" style="width:45%"></el-input>
                            <!-- 在这调用验证码图片 -->
                            <el-image :src="capImg" style="width:54%;float:right"></el-image>
                        </el-form-item>
                        
                        <el-form-item>
                            <el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button>
                        </el-form-item>
                    </el-form>
                </div>
            </el-col>
        </el-row>
    </div>
</template>
<script>
export default {
    name:'Login',
     data() {
      return {
        ruleForm: {
          username: '',
          password: '',
          code: '',
        },
        rules: {
          username: [
            { required: true, message: '请输入姓名', trigger: 'blur' },
          ],
          password: [
            { required: true, message: '请输入密码', trigger: 'change' }
          ],
          code: [
            { type: 'date', required: true, message: '请输入验证码', trigger: 'change' }
          ],
        },
        capImg:''
      };
    },
    methods: {
      submitForm(formName) {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            alert('submit!');
          } else {
            console.log('error submit!!');
            return false;
          }
        });
      }
     
    }
}
</script>
<style scope>
    .loginform{
        margin-top:20px;
        width:500px;
    }
     h2{display:block;width:100%;margin-top:25px;}
    .el-row{
       width:100%;
       height:100vh;  /* 自适应高度100% */
       justify-items: center;  
       align-items: center;  /* flex布局,页面组件中间居中 */
       background-color: #2c1d6d; /* 随便找个背景色 ,我这里是紫色背景*/
    }
    .el-col{
        background: #fff;
    }
   
</style>

4: 后台生成验证码

4.1 编写kaptcha配置类

该代码向容器注册DefaultKaptcha, 到网上就能找到 无需自己编写,主要功能是设置生成图片的各种属性

package com.example.code.config;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

@Configuration
public class KaptchaConfig {
    @Bean
    public DefaultKaptcha defaultKaptcha(){
        DefaultKaptcha dk = new DefaultKaptcha();
        Properties properties = new Properties();
        // 图片边框
        properties.setProperty("kaptcha.border", "no");
        // 图片高
        properties.setProperty("kaptcha.image.height", "40");
        // session key
        properties.setProperty("kaptcha.session.key", "code");
        // 验证码长度
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        // 字体
        properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
        // 字体大小
        properties.setProperty("kaptcha.textproducer.font.size", "25");
        // 向config中注册配置属性
        Config config = new Config(properties);
        dk.setConfig(config);
        return dk;
    }
}

我们需要知道是,属性来自于com.google.code.kaptcha.util.Config 这个类
在这里插入图片描述

4.2 编写验证码生成类

主要步骤为:

1: 使用defaultKaptcha类 生成验证码

2: 将该验证码保存在redis中,供后续验证时调用,同时设置有效期为30分种

3: 把生成的图像文件写入到字节流中声明,该图片为jpg文件

4: 使用base64对生成的字节流进行转码并生成图像字符串

5: 将转码后的文件返回到前台

package com.example.code.controller;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@RestController
public class KaptchaController {
    @Autowired
    private DefaultKaptcha defaultKaptcha;
    @Autowired
    private RedisTemplate redisTemplate;
    @GetMapping("kaptcha")
    public Map<String,String> getKaptcha() throws IOException {
        // 以当前毫秒数生成随机key,注意高并发情况下,这不是一种好的选择
        String key = System.currentTimeMillis()+"";
        String text = defaultKaptcha.createText();
        // 将生成的验证码保存到redis中,并设置有效期为30分种
        redisTemplate.opsForValue().set(key,text,60*30, TimeUnit.SECONDS);
        BufferedImage image = defaultKaptcha.createImage(text);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image,"png",outputStream);
        BASE64Encoder encoder = new BASE64Encoder();
        String encode = encoder.encode(outputStream.toByteArray());
        encode= "data:image/png;base64,"+encode;
        Map<String,String> codes = new HashMap<>();
        codes.put("key",key);
        codes.put("code",encode);
        return codes;
    }
}

5:前端VUE调用

1:生成请求函数

在这里插入图片描述
页面加载的时候,通过mounted钩子函数自动调用getCapImg()方法

注意:

调用验证码请求的链接必须添加data参数,参数的值是当前时间,防止缓存不产生变化。

2:设置点击事件

当需要更换验证码图片时,可以点击验证码 调用getCapImg()函数
在这里插入图片描述

6:完成后的效果

在这里插入图片描述
代码下载地址
vue+springboot+kaptcha生成验证码

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Vue、Spring Boot、Redis和MySQL是一组流行的技术堆栈,通常用于构建现代化的Web应用程序。它们各自提供了不同的功能和优势。 Vue是一个流行的JavaScript框架,用于构建前端应用程序。Vue具有轻量级、易于学习和使用的特点,同时提供了强大的功能,如组件化、响应式数据绑定和虚拟DOM等。Vue可以与其他后端框架和数据存储技术(如Spring Boot、Redis和MySQL)集成使用。 Spring Boot是一个用于开发Java Web应用程序的框架。它使开发人员能够使用简单的配置和约定来快速构建和部署Web应用程序。Spring Boot的强大功能包括依赖注入、自动配置和可扩展性。Spring Boot还支持多种数据库和缓存技术,如MySQL和RedisRedis是一个流行的存储系统,用于缓存数据和处理时序数据。Redis具有高性能、高可用性和可扩展性等特点,可用于构建海量数据的缓存层、消息队列、会话存储等。 MySQL是一个流行的开源关系型数据库,广泛应用于Web应用程序中。MySQL具有稳定、高性能和可伸缩性等特点,可用于存储各种类型的数据,并提供了丰富的功能和工具来管理和查询数据。 因此,Vue、Spring Boot、Redis和MySQL是一组非常流行的技术堆栈,可用于构建现代化、高性能和可扩展的Web应用程序。它们的综合使用可以提高Web应用程序的开发效率、代码质量和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技能侠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值