效果图

image.png
项目访问地址: www.zfc.life/sm
用户名:test
密码:test123456

话不多说上代码。

后端 【springboot】

1.1 导入maven坐标

<!-- 验证码 依赖-->
        <dependency>
            <groupId>com.github.axet</groupId>
            <artifactId>kaptcha</artifactId>
            <version>0.0.9</version>
        </dependency>
        <!-- 验证码 依赖-->

1.2 编写配置类:KaptchaConfig

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

import java.util.Properties;

/**
 * @Classname KaptchaConfig
 * @Date 2023/7/31 8:52
 * @Created ZFC
 */
@Component
public class KaptchaConfig {

    @Bean
    public DefaultKaptcha getDefaultKaptcha() {
        com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha();
        Properties properties = new Properties();
        // 图片边框
        properties.setProperty("kaptcha.border", "no");
        // 边框颜色
        properties.setProperty("kaptcha.border.color", "black");
        //边框厚度
        properties.setProperty("kaptcha.border.thickness", "0");
        // 图片宽
//        properties.setProperty("kaptcha.image.width", "160");
//        // 图片高
//        properties.setProperty("kaptcha.image.height", "60");
        //图片实现类
        properties.setProperty("kaptcha.producer.impl", "com.google.code.kaptcha.impl.DefaultKaptcha");
        //文本实现类
        properties.setProperty("kaptcha.textproducer.impl", "com.google.code.kaptcha.text.impl.DefaultTextCreator");
        //文本集合,验证码值从此集合中获取
        properties.setProperty("kaptcha.textproducer.char.string", "01234567890qwertyuiopasdfghjklzxcvbnm");
        //验证码长度
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        //字体
        properties.setProperty("kaptcha.textproducer.font.names", "楷体");
        //字体颜色
        properties.setProperty("kaptcha.textproducer.font.color", "black");
        //文字间隔
        properties.setProperty("kaptcha.textproducer.char.space", "5");
        //干扰实现类
        properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.DefaultNoise");
        //干扰颜色
        properties.setProperty("kaptcha.noise.color", "blue");
        //干扰图片样式
        properties.setProperty("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.WaterRipple");
        //背景实现类
        properties.setProperty("kaptcha.background.impl", "com.google.code.kaptcha.impl.DefaultBackground");
        //背景颜色渐变,结束颜色
        properties.setProperty("kaptcha.background.clear.to", "white");
        //文字渲染器
        properties.setProperty("kaptcha.word.impl", "com.google.code.kaptcha.text.impl.DefaultWordRenderer");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

1.3 编写获取验证码的请求方法,将redis存入redis中

	@Autowired
    private Producer producer;

	@Autowired
    private RedisUtils redisUtils;
    
    @ApiOperation("获取验证码")
    @GetMapping("/code")
    public ModelAndView code(String time, HttpServletResponse response) throws IOException {
        StringBuffer key = new StringBuffer();
        key.append(time).append("-").append(Constants.KAPTCHA_SESSION_KEY);

        //响应的过期时间,通过将Expires字段设置为0,可以告诉浏览器不要缓存这个响应,每次都需要重新请求服务器获取最新的数据。
        response.setDateHeader("Expires", 0);

        // 设置标准 HTTP1.1 无缓存标头。
        response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");

        // 设置 IE 扩展 HTTP1.1 无缓存标头(使用 addHeader)。
        response.addHeader("Cache-Control", "post-check=0, pre-check=0");

        // 设置标准 HTTP1.0 无缓存标头。
        response.setHeader("Pragma", "no-cache");

        // 返回一个 jpeg
        response.setContentType("image/jpeg");

        // 为图像创建文本
        String capText = producer.createText();

        // 为图像创建文本
        if (!StringUtils.isEmpty(time)){
            //时间戳不为空才加入缓存
            redisUtils.set(key.toString(),capText,5L);
        }
        // 用文本创建图像
        BufferedImage bi = producer.createImage(capText);

        ServletOutputStream out = response.getOutputStream();

        // 将数据写出
        ImageIO.write(bi, "jpg", out);
        try {
            out.flush();
        } finally {
            out.close();
        }
        return null;
    }

1.4 登录接口 (这一块可能不太完整,根据自己的需求来!)

	@ApiOperation("登录接口")
    @ResponseBody
    @PostMapping("/login")
    public Result login(@Validated @RequestBody LoginDto loginDto, HttpServletRequest request) {
        if (StringUtils.isEmpty(loginDto.getUsername()) || StringUtils.isEmpty(loginDto.getPassword())) {
            return new Result().setCode(HTTPStatus.BAD_REQUEST).setMessage("账号或密码不能为空");
        }
//        if (StringUtils.isEmpty(loginDto.getCode())){
//            return new Result().setCode(HTTPStatus.BAD_REQUEST).setMessage("验证码不能为空");
//        }
        Object redisCode = redisUtils.get(loginDto.getTime()+"-"+Constants.KAPTCHA_SESSION_KEY);

        if (!redisCode.toString().equals(loginDto.getCode())){
            return new Result().setCode(HTTPStatus.BAD_REQUEST).setMessage("验证码不正确");
        }

        Teacher backTeacher = teacherService.getTeacherByUserName(loginDto.getUsername());
        backTeacher.setPassword("******");
        Map<String, Object> map = new HashMap<>();
        map.put("teacher", backTeacher);
        map.put("token", token);

        //将用户信息存入redis缓存中 格式:username
        redisUtils.set(backTeacher.getUsername(), JSON.toJSONString(backTeacher));

        redisUtils.remove(loginDto.getTime()+"-"+Constants.KAPTCHA_SESSION_KEY);//移除当前验证码
        return new Result().setCode(HTTPStatus.SUCCESS).setMessage("登录成功").setData(map);
    }

1.5 工具类

RedisUtils (这个redis的工具类,个人感觉不是很好用,将就将就)

import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * <p>Redis 工具类</p>
 * @Classname RedisUtils
 * @Date 2023/7/31 8:55
 * @Created ZFC
 */
@Service
public class RedisUtils {
    @Resource
    private RedisTemplate redisTemplate;

    private static double size = Math.pow(2, 32);


    /**
     * 写入缓存
     * @param key
     * @param offset
     * @param isShow
     * @return result
     */
    public boolean setBit(String key, long offset, boolean isShow) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.setBit(key, offset, isShow);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 写入缓存
     * @param key
     * @param offset
     * @return result
     */
    public boolean getBit(String key, long offset) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            result = operations.getBit(key, offset);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 写入缓存
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 写入缓存设置时效时间
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.MINUTES);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 批量删除对应的value
     * @param keys
     */
    public void remove(final String... keys) {
        for (String key : keys) {
            remove(key);
        }
    }

    /**
     * 删除对应的value
     * @param key
     */
    public void remove(final String key) {
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }

    /**
     * 判断缓存中是否有对应的value
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 读取缓存
     * @param key
     * @return
     */
    public Object get(final String key) {
        Object result = null;
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    /**
     * 哈希 添加
     * @param key
     * @param hashKey
     * @param value
     */
    public void hmSet(String key, Object hashKey, Object value) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        hash.put(key, hashKey, value);
    }

    /**
     * 哈希获取数据
     * @param key
     * @param hashKey
     * @return
     */
    public Object hmGet(String key, Object hashKey) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        return hash.get(key, hashKey);
    }

    /**
     * 列表添加
     * @param k
     * @param v
     */
    public void lPush(String k, Object v) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.rightPush(k, v);
    }

    /**
     * 列表获取
     * @param k
     * @param l
     * @param l1
     * @return
     */
    public List<Object> lRange(String k, long l, long l1) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        return list.range(k, l, l1);
    }

    /**
     * 集合添加
     * @param key
     * @param value
     */
    public void add(String key, Object value) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        set.add(key, value);
    }

    /**
     * 集合获取
     * @param key
     * @return
     */
    public Set<Object> setMembers(String key) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        return set.members(key);
    }

    /**
     * 有序集合添加
     * @param key
     * @param value
     * @param scoure
     */
    public void zAdd(String key, Object value, double scoure) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        zset.add(key, value, scoure);
    }

    /**
     * 有序集合获取
     * @param key
     * @param scoure
     * @param scoure1
     * @return
     */
    public Set<Object> rangeByScore(String key, double scoure, double scoure1) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        redisTemplate.opsForValue();
        return zset.rangeByScore(key, scoure, scoure1);
    }


    /**
     * 第一次加载的时候将数据加载到 redis 中
     * @param name
     */
    public void saveDataToRedis(String name) {
        double index = Math.abs(name.hashCode() % size);
        long indexLong = new Double(index).longValue();
        boolean availableUsers = setBit("availableUsers", indexLong, true);
    }

    /**
     * 第一次加载的时候将数据加载到redis中
     * @param name
     * @return
     */
    public boolean getDataToRedis(String name) {
        double index = Math.abs(name.hashCode() % size);
        long indexLong = new Double(index).longValue();
        return getBit("availableUsers", indexLong);
    }

    /**
     * 有序集合获取排名
     * @param key   集合名称
     * @param value 值
     */
    public Long zRank(String key, Object value) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return zset.rank(key, value);
    }


    /**
     * 有序集合获取排名
     * @param key
     */
    public Set<ZSetOperations.TypedTuple<Object>> zRankWithScore(String key, long start, long end) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<Object>> ret = zset.rangeWithScores(key, start, end);
        return ret;
    }

    /**
     * 有序集合添加
     * @param key
     * @param value
     */
    public Double zSetScore(String key, Object value) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return zset.score(key, value);
    }


    /**
     * 有序集合添加分数
     * @param key
     * @param value
     * @param scoure
     */
    public void incrementScore(String key, Object value, double scoure) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        zset.incrementScore(key, value, scoure);
    }


    /**
     * 有序集合获取排名
     * @param key
     */
    public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithScore(String key, long start, long end) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeByScoreWithScores(key, start, end);
        return ret;
    }

    /**
     * 有序集合获取排名
     * @param key
     */
    public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithRank(String key, long start, long end) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeWithScores(key, start, end);
        return ret;
    }
}

其他的就不展示了,根据自己的需求来!

前端 【vue】

这个我就放重要代码了,其他的没什么好看的。

1.1 template

image.png

<template>
  <div>
    <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
      <el-form-item label="用户名" prop="username">
        <el-input type="text" v-model="ruleForm.username" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="密码" prop="password">
        <el-input type="password" v-model="ruleForm.password" autocomplete="off"></el-input>
      </el-form-item>
  
      <el-form-item label="验证码" prop="code">
        <el-input v-model="ruleForm.code" placeholder="请输入验证码" maxlength="4" style="width: 180px;float: left;"></el-input>
  
        <div class="Verification" @click="clickVerification"  style="float: right;">
          <img :src="VerificationImg" style="height:40px;width:110px;"/>
        </div>
  
      </el-form-item>
  
      <el-form-item>
        <el-button type="primary" @click="submitForm('ruleForm')" :loading="loginLoading"  style="float: left;">
          <span>{{ loginBtn }}</span>
        </el-button>
        <!-- <el-button @click="resetForm('ruleForm')">重置</el-button> -->
      </el-form-item>
    </el-form>
  </div>
</template>

1.2 script

image.png

<script>
export default {
  name: "userLogin",
  data() {
    return {
      ruleForm: {
        username: 'zhongfucheng',
        password: 'admin',
        code: "",
        time: ""
      },
      VerificationImg: "",
      rules: {
        username: [
          { required: true, message: '请输入账号', trigger: 'blur' },
          { min: 2, max: 18, message: '长度在 2 到 18 个字符', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '请输入密码', trigger: 'blur' },
          { min: 5, max: 18, message: '长度在 5 到 18 个字符', trigger: 'blur' }
        ],
        code: [
          { required: true, message: '请输入验证码', trigger: 'blur' },
          { min: 4, max: 4, message: '请输入正确的验证码', trigger: 'blur' }
        ]
      },
      loginBtn: '登录',
      loginLoading: false,
    };
  },
  mounted() {
    this.clickVerification()
  },
  methods: {
    submitForm(formName) {
      this.$refs[formName].validate((valid) => {
        let then = this
        if (valid) {
          this.loginBtn = "登陆中..."
          this.loginLoading = true
          this.$axios.post("/login", this.ruleForm).then(res => {
            this.$store.commit("SET_USERINFO", res.data.data.teacher)
            this.$store.commit("SET_TOKEN", res.data.data.token)
            this.$router.replace("/")
          }).catch(()=> {
            //总会执行
            then.loginLoading = false
            then.loginBtn = '登录'
            this.clickVerification()
          })
        } else {
          return false;
        }
      });
    },
    clickVerification() {
      this.ruleForm.time = Date.now()
      this.VerificationImg = "/apis/code?time=" + this.ruleForm.time
    }
    // resetForm(formName) {
    //   this.$refs[formName].resetFields();
    // }
  }
}
</script>

注意:根据自己的需求来,这个代码不完善,只是一个参考!!!

redis键值出现 \xac\xed\x00\x05t\x00&的解决方法:

我参考的:redis键值出现 \xac\xed\x00\x05t\x00&的解决方法 亲测有用

我这里简单的记录下怎么使用:具体点击链接查看!

效果图

image.png
可以看到默认存数据到redis中是这样的,然后我删还删不掉,只能清空数据库才可以…

解决方法

1.1 导入maven坐标

	<!--解决Cacheable序列化乱码后,JSON序列化器不支持localdatetime格式的时间-->
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.14.2</version>
        </dependency>

1.2 编写配置类 RedisConfig

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * <p>RedisConfig 此类用于:Redis相关配置,用于解决存入Redis中值乱码问题 </p>
 * @Classname RedisConfig
 * @Date 2023/7/31 14:43
 * @Created ZFC
 */
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    /**
     * 如果key和value都使用的StringRedisSerializer序列化器,则推荐使用StringRedisTemplate
     *
     * 配置Redis的Key和Value的序列化器
     * @param redisTemplate 从容器中获取RedisTemplate
     * @return 修改后的RedisTemple
     */
    @Bean
    public RedisTemplate<Object, Object> redisStringTemplate(RedisTemplate<Object, Object> redisTemplate) throws InterruptedException {
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        // 如果手动将Value转换成了JSON,就不要再用JSON序列化器了。
        // redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        redisTemplate.setValueSerializer(stringRedisSerializer);
		/**
         * 这是一个 Java Spring Boot 应用程序中的 RedisTemplate 配置类,用于配置 RedisTemplate 实例的序列化器(serializer)。
         * 在 Redis 中,所有的数据都是二进制的。当我们将数据存储到 Redis 中时,需要将其序列化为二进制格式。而当我们从 Redis 中获取数据时,需要将其反序列化为原始数据格式。
         * 在这个配置类中,我们使用了 StringRedisSerializer 作为 Key 和 Value 的序列化器。这意味着,我们在将数据存储到 Redis 中时,将 Key 和 Value 转换为字符串格式,并将其序列化为二进制格式。当我们从 Redis 中获取数据时,将其反序列化为字符串格式,并将其转换为原始数据格式。
         * 需要注意的是,如果我们手动将 Value 转换成了 JSON 格式,就不要再使用 Jackson2JsonRedisSerializer 序列化器了,因为这会导致 JSON 格式的数据再次被序列化为二进制格式,从而导致数据格式错误。
         */
        return redisTemplate;
    }
}

实现序列化后的效果图

image.png