SringBoot后端配置验证码及接口编写,方便后期页面快速开发。
后端验证码类
public ResponseEntity<Object> getCode() {
// 获取运算的结果
Captcha captcha = loginProperties.getCaptcha();
String uuid = properties.getCodeKey() + IdUtil.simpleUUID();
//当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型
String captchaValue = captcha.text();
if (captcha.getCharType() - 1 == LoginCodeEnum.ARITHMETIC.ordinal() && captchaValue.contains(".")) {
captchaValue = captchaValue.split("\.")[0];
}
// 保存
redisUtils.set(uuid, captchaValue, loginProperties.getLoginCode().getExpiration(), TimeUnit.MINUTES);
// 验证码信息
Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
put("img", captcha.toBase64());
put("uuid", uuid);
}};
return ResponseEntity.ok(imgResult);
}
枚举类
public enum LoginCodeEnum {
/**
* 算数
*/
ARITHMETIC,
/**
* 中文
*/
CHINESE,
/**
* 中文闪图
*/
CHINESE_GIF,
/**
* 闪图
*/
GIF,
SPEC
}
@Data
public class LoginCode {
/**
* 验证码配置
*/
private LoginCodeEnum codeType;
/**
* 验证码有效期 分钟
*/
private Long expiration = 2L;
/**
* 验证码内容长度
*/
private int length = 2;
/**
* 验证码宽度
*/
private int width = 111;
/**
* 验证码高度
*/
private int height = 36;
/**
* 验证码字体
*/
private String fontName;
/**
* 字体大小
*/
private int fontSize = 25;
public LoginCodeEnum getCodeType() {
return codeType;
}
}
public class LoginProperties {
/**
* 账号单用户 登录
*/
private boolean singleLogin = false;
private LoginCode loginCode;
public static final String cacheKey = "USER-LOGIN-DATA";
public boolean isSingleLogin() {
return singleLogin;
}
/**
* 获取验证码生产类
*
* @return /
*/
public Captcha getCaptcha() {
if (Objects.isNull(loginCode)) {
loginCode = new LoginCode();
if (Objects.isNull(loginCode.getCodeType())) {
loginCode.setCodeType(LoginCodeEnum.ARITHMETIC);
}
}
return switchCaptcha(loginCode);
}
/**
* 依据配置信息生产验证码
*
* @param loginCode 验证码配置信息
* @return /
*/
private Captcha switchCaptcha(LoginCode loginCode) {
Captcha captcha;
switch (loginCode.getCodeType()) {
case ARITHMETIC:
// 算术类型 https://gitee.com/whvse/EasyCaptcha
captcha = new FixedArithmeticCaptcha(loginCode.getWidth(), loginCode.getHeight());
// 几位数运算,默认是两位
captcha.setLen(loginCode.getLength());
break;
case CHINESE:
captcha = new ChineseCaptcha(loginCode.getWidth(), loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case CHINESE_GIF:
captcha = new ChineseGifCaptcha(loginCode.getWidth(), loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case GIF:
captcha = new GifCaptcha(loginCode.getWidth(), loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case SPEC:
captcha = new SpecCaptcha(loginCode.getWidth(), loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
default:
throw new BadConfigurationException("验证码配置信息错误!正确配置查看 LoginCodeEnum ");
}
if(StringUtils.isNotBlank(loginCode.getFontName())){
captcha.setFont(new Font(loginCode.getFontName(), Font.PLAIN, loginCode.getFontSize()));
}
return captcha;
}
static class FixedArithmeticCaptcha extends ArithmeticCaptcha {
public FixedArithmeticCaptcha(int width, int height) {
super(width, height);
}
@Override
protected char[] alphas() {
// 生成随机数字和运算符
int n1 = num(1, 10), n2 = num(1, 10);
int opt = num(3);
// 计算结果
int res = new int[]{n1 + n2, n1 - n2, n1 * n2}[opt];
// 转换为字符运算符
char optChar = "+-x".charAt(opt);
this.setArithmeticString(String.format("%s%c%s=?", n1, optChar, n2));
this.chars = String.valueOf(res);
return chars.toCharArray();
}
}
}
简易的前端页面
<template>
<div class='hr-login'>
<div class="page-head">
<img src="../assets/....." alt="">
</div>
<div class="login-form">
<div class="text-logo">简易登陆页面</div>
<el-input class="phone-inp login-inp" v-model="loginForm.username" placeholder="输入手机号" clearable></el-input>
<el-input class="login-inp" placeholder="输入密码" v-model="loginForm.password" show-password></el-input>
<div class="code-inp login-inp">
<el-input class="code" v-model="loginForm.code" placeholder="输入验证码"></el-input>
<div class="get-code">
<img
:src="codeUrl"
@click="getCode"
>
</div>
</div>
<div class="protocol">
<el-checkbox v-model="agree"></el-checkbox>
<div class="protocol-text">
<span @click="agreeProtocol">我已阅读并同意</span><span class="link">法律声明及隐私权政策</span><span>和</span><span class="link">用户协议</span>
</div>
</div>
<div class="login-btn" @click="handleLogin">立即登录</div>
<div class="tip">
<span class="">未注册手机号将自动创建账户</span>
<span class="link" @click="showCustomerService">联系 ?</span>
</div>
</div>
<el-dialog :visible.sync="showDialog" width="424px">
<div class="dialog-body">
<div class="dialog-tip-text">打开微信扫一扫,添加 顾问</div>
<img src="../assets/ " alt="">
</div>
</el-dialog>
</div>
</template>
<script>
// 调用后端接口获取验证码及图片
import {getCodeImg} from "@/api/login";
import Cookies from "js-cookie";
// 加密类
import {encrypt} from "@/utils/rsaEncrypt";
import qs from "qs";
export default {
data() {
return {
codeUrl: "",
cookiePass: "",
loginForm: {
username: "",
password: "",
rememberMe: false,
code: "",
uuid: "",
},
loginRules: {
username: [
{ required: true, trigger: "blur", message: "用户名不能为空" },
],
password: [
{ required: true, trigger: "blur", message: "密码不能为空" },
],
code: [
{ required: true, trigger: "change", message: "验证码不能为空" },
],
},
loading: false,
redirect: undefined,
agree:false,
}
},
created() {
// 获取验证码
this.getCode();
// 获取用户名密码等Cookie
this.getCookie();
},
methods: {
getCode() {
getCodeImg().then((res) => {
this.codeUrl = res.img;
this.loginForm.uuid = res.uuid;
});
},
getCookie() {
const username = Cookies.get("username");
let password = Cookies.get("password");
const rememberMe = Cookies.get("rememberMe");
// 保存cookie里面的加密后的密码
this.cookiePass = password === undefined ? "" : password;
password = password === undefined ? this.loginForm.password : password;
this.loginForm = {
username: username === undefined ? this.loginForm.username : username,
password: password,
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
code: "",
};
},
handleLogin() {
const user = {
username: this.loginForm.username,
password: this.loginForm.password,
rememberMe: this.loginForm.rememberMe,
code: this.loginForm.code,
uuid: this.loginForm.uuid,
};
if (user.password !== this.cookiePass) {
user.password = encrypt(user.password);
}
this.loading = true;
if (user.rememberMe) {
Cookies.set("username", user.username, {
expires: Config.passCookieExpires,
});
Cookies.set("password", user.password, {
expires: Config.passCookieExpires,
});
Cookies.set("rememberMe", user.rememberMe, {
expires: Config.passCookieExpires,
});
} else {
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove("rememberMe");
}
//登陆成功后逻辑自行修改
this.$store
.dispatch("Login", user)
.then(() => {
this.loading = false;
this.$router.push({ path: "/" });
})
.catch(() => {
this.loading = false;
this.getCode();
});
},
submit() {
this.$router.push({ path: '/' })
}
},
}
</script>
<style lang='scss' scoped>
.hr-login {
position: relative;
.page-head {
box-sizing: border-box;
height: 80px;
line-height: 80px;
padding-left: 80px;
border-bottom: 0.5px solid #E0E0E0;
img {
width: 120px;
height: 20px;
}
}
.login-form {
width: 400px;
margin: 120px auto 0;
text-align: center;
.text-logo {
margin-bottom: 32px;
color: rgb(0 0 0 / 90%);
font-size: 32px;
font-weight: 500;
font-family: PingFang SC;
}
.code-inp {
position: relative;
.get-code {
z-index: 100;
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
width: 120px;
text-align: center;
color: rgb(0 0 0 / 30%);
font-size: 14px;
cursor: pointer;
}
}
.protocol {
display: flex;
align-items: center;
justify-content: center;
margin-top: 32px;
cursor: pointer;
.protocol-text {
margin-left: 6px;
color: rgb(0 0 0 / 30%);
font-size: 12px;
.link {
color: rgb(0 0 0 / 70%);
}
}
}
.login-btn {
width: 100%;
height: 50px;
margin-top: 32px;
line-height: 50px;
text-align: center;
background-color: #E8B957;
border-radius: 12px;
color: rgb(0 0 0 / 90%);
font-size: 16px;
font-weight: 500;
cursor: pointer;
user-select: none;
}
.tip {
margin-top: 96px;
color: rgb(0 0 0 / 50%);
font-size: 14px;
.link {
color: #E8B957;
cursor: pointer;
}
}
}
.dialog-body {
text-align: center;
padding-bottom: 30px;
.dialog-tip-text {
margin-bottom: 32px;
color: rgb(0 0 0 / 90%);
font-size: 16px;
font-weight: 500;
}
img {
width: 200px;
}
}
}
</style>
<style lang='scss'>
.hr-login {
.login-form {
.login-inp {
margin-top: 16px;
height: 48px;
.el-input__inner {
height: 48px;
}
}
.code-inp {
.login-inp .el-input__inner {
padding-right: 120px;
}
}
.el-checkbox__input .el-checkbox__inner {
border-radius: 50%;
}
.el-checkbox__input.is-checked .el-checkbox__inner {
background: #E8B957;
border-color: #E8B957;
}
.el-checkbox__input.is-focus .el-checkbox__inner {
border-color: #E8B957;
}
.el-checkbox__input .el-checkbox__inner:hover {
border-color: #E8B957;
}
}
}
</style>