效果图
项目访问地址: http://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
<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
<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&的解决方法 亲测有用
我这里简单的记录下怎么使用:具体点击链接查看!
效果图
可以看到默认存数据到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;
}
}