唠嗑部分
今天我们来分享一下在系统开发过程中,如何使用验证码来验证用户并完成用户注册
首先来看一下成品界面展示
说一下以上注册功能的设计:
用户手动输入用户名(全数据库唯一)、密码、确认密码、邮箱地址(单个邮箱最多可注册3个用户)、正确的邮箱验证码,即可注册
先来展示一下
输入用户信息
收到邮箱验证码
注册成功
言归正传
使用验证码这种方式呢是比较常见的,我们在注册App的时候,会有手机验证码、邮箱的类似哈(发邮件是免费的),邮箱验证也能够保证其真实性,防止恶意用户非法注册
下面我们就来说一下,这一系列的实现思路
1、用户名唯一验证就省略了哈,不在此范围内
2、在用户填入信息时前端先做必传、格式验证。
3、邮箱验证通过后,点击发送验证码时,携带邮箱参数请求后端接口。
4、后端生成并发送验证码后,将验证码进行分布式存储,如存到redis,key为邮箱,value为验证码,失效时间设置3分钟。
5、用户在3分钟内收到验证码并填入注册表单,请求用户注册接口。
6、后端在收到注册请求后,首先验证参数的合法性,验证通过后,根据邮箱去redis查询验证码。
7、未查询到验证码,则说明验证码已经过期,返回验证码校验失败,查询到验证码后,与表单中的验证码进行比较,相同则继续注册逻辑,不同则返回验证码校验失败。
代码环节
1、Vue组件
<template>
<div class="header-all">
<!--...-->
<el-dialog :visible.sync="loginFlag" width="500px" :close-on-click-modal="false" :show-close="true">
<!--登录表单省略...-->
<el-form ref="registerForm" v-show="!login" :model="registerInfo" :rules="registerRules" class="register-form"
autocomplete="on"
label-position="left">
<div class="title-container">
<h3 class="title" style="text-align: center;font-size: 20px;margin-bottom: 15px;">
{{ sysInfo.sysTitle }}注册</h3>
</div>
<el-form-item prop="userName">
<el-input
prefix-icon="el-icon-user"
ref="username"
v-model="registerInfo.userName"
placeholder="请输入用户名"
name="userName"
type="text"
tabindex="1"
autocomplete="on"
/>
</el-form-item>
<el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
<el-form-item prop="password">
<el-input
show-password
prefix-icon="el-icon-lock"
ref="password"
v-model="registerInfo.password"
placeholder="请输入账户密码"
name="password"
tabindex="2"
autocomplete="on"
@blur="capsTooltip = false"
/>
<span class="show-pwd">
</span>
</el-form-item>
</el-tooltip>
<el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
<el-form-item prop="password2">
<el-input
show-password
prefix-icon="el-icon-lock"
ref="password2"
v-model="registerInfo.password2"
placeholder="请确认密码"
name="password2"
tabindex="2"
autocomplete="on"
@blur="capsTooltip = false"
/>
<span class="show-pwd">
</span>
</el-form-item>
</el-tooltip>
<el-form-item prop="email">
<el-input
prefix-icon="el-icon-message"
ref="email"
v-model="registerInfo.email"
placeholder="请输入邮箱地址,每个邮箱最多可绑定3个账号"
name="email"
type="text"
tabindex="1"
autocomplete="on"
/>
</el-form-item>
<el-form-item prop="code">
<el-row :gutter="20">
<el-col :span="12">
<el-input
ref="code"
type="text"
prefix-icon="el-icon-key"
v-model="registerInfo.code"
placeholder="请输入邮箱验证码"
name="code"
tabindex="2"
autocomplete="on"
@keyup.enter.native="registerHandle"
/>
</el-col>
<el-col :span="6" :offset="3">
<el-button type="primary" size="small" :disabled="pause" @click="sendEmailCode">获取验证码 <span v-if="pause">{{ time }}</span>
</el-button>
</el-col>
</el-row>
</el-form-item>
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;"
@click.native.prevent="registerHandle">注册
</el-button>
<div style="text-align: right">
<el-link type="primary" :underline="false" @click.native="nativeToLogin">已有账号,去登录>></el-link>
</div>
</el-form>
</el-dialog>
<el-dialog
title="新用户提示"
:visible.sync="dialogVisible"
width="30%">
<span>系统检测到您是新用户,请及时更新信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="toUpdateSelfInfo">去更新</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
<!--引入...-->
const defaultRegisterInfo = {
userName: '',
password: '',
password2: '',
email: '',
code: ''
}
export default {
<!--...-->
data() {
var checkUserName = (rule, value, callback) => {
const pattern = /^[A-Za-z0-9-_]+$/;
if (!value) {
return callback(new Error('登录名不能为空'));
}
if (pattern.test(value) && value.length <= 16) {
checkUserNameExist({userName: value}).then(res => {
if (res.data) {
callback(new Error('登录名已存在'));
} else {
callback();
}
})
} else {
callback(new Error('登录名须由数字、英文字母、-、下划线(不包含、)组成,不大于16位'));
}
};
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
} else {
if (this.registerInfo.password !== '') {
this.$refs.registerForm.validateField('password2');
}
callback();
}
};
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== this.registerInfo.password) {
callback(new Error('两次输入密码不一致!'));
} else {
callback();
}
};
var validateEmail = (rule, value, callback) => {
const pattern = /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/;
if (value && value !== '') {
if (pattern.test(value)) {
checkEmailBindStatus({email: value}).then(res => {
if (res.data) {
callback();
} else {
callback(new Error('当前邮箱绑定用户已达到上限'));
}
})
} else {
callback(new Error('邮箱格式不正确'));
}
} else {
callback(new Error('邮箱为必传项'));
}
};
return {
defaultUserImg: defaultUserImg,
refresh: true,
baseUrl: URL_PREFIX,
codeUrl: URL_PREFIX + '/auth/getCaptcha',
rules: {
username: [{required: true, trigger: 'blur', message: '用户名为必填项'}],
password: [{required: true, trigger: 'blur', message: '密码为必填项'}],
code: [{required: true, trigger: 'blur', message: '验证码为必填项'}]
},
loading: false,
registerInfo: {
userName: '',
password: '',
password2: '',
email: '',
code: ''
},
time: 30,
pause: false,
registerRules: {
userName: [{validator: checkUserName, trigger: 'blur'}],
password: [{validator: validatePass, trigger: 'blur'}],
password2: [{validator: validatePass2, trigger: 'blur'}],
email: [{validator: validateEmail, trigger: 'blur'}],
code: [{required: true, trigger: 'blur', message: '验证码为必填项'}]
},
dialogVisible: false
}
},
methods: {
// 用户注册
registerHandle() {
this.$refs.registerForm.validate(valid => {
if (valid) {
this.loading = true
const registerInfo = {
...this.registerInfo,
password: this.$encruption(this.registerInfo.password),
password2: this.$encruption(this.registerInfo.password2)
}
register(registerInfo).then(res => {
this.$message.success('用户注册成功')
this.loading = false
this.registerInfo = defaultRegisterInfo
this.$refs.registerForm.resetFields()
this.nativeToLogin()
})
} else {
console.log('error submit!!')
return false
}
setTimeout(() => {
this.loading = false
}, 2000)
})
},
// 发送验证码
sendEmailCode() {
this.$refs.registerForm.validateField('email', valid => {
if (!valid) {
if (this.pause) {
this.$message.error('操作太频繁,请稍后再试')
} else {
sendEmailCode({email: this.registerInfo.email}).then(res => {
this.$message.success('验证码发送成功,请注意查收!')
this.pause = true
const timer = setInterval(() => {
if (this.time > 0) {
this.time = this.time - 1;
}
if (this.time <= 0) {
this.pause = false;
clearInterval(timer)
this.time = 30
}
}, 1000)
})
}
}
});
}
}
}
</script>
<style scoped lang="less">
<!--css省略-->
</style>
2、api
// 发送验证码
export function sendEmailCode(data = {}) {
return request({
url: URL_PREFIX + '/main/user/sendEmailCode',
method: 'post',
data
})
}
// 注册
export function register(data = {}) {
return request({
url: URL_PREFIX + '/main/user/register',
method: 'post',
data
})
}
3、服务端发送验证码
@PostMapping("/user/sendEmailCode")
@ApiOperation("发送验证码处理器")
public BaseResult sendEmailCode(@RequestBody @Validated SendEmailCodeDTO dto, HttpServletRequest request){
BaseResult result = BaseResult.ok();
baseService.sendEmailCode(dto, request, result);
return result;
}
实现类
@Override
public void sendEmailCode(SendEmailCodeDTO dto, HttpServletRequest request, BaseResult result) {
long startTime = System.currentTimeMillis();
try {
// 验证码发送频率控制验证
String string = redisUtil.getString(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE_TIME, dto.getEmail()));
if (StringUtils.hasLength(string)) {
result.setCode(CurrencyErrorEnum.OPERA_ERROR.getCode());
result.setMsg(CurrencyErrorEnum.OPERA_ERROR.getMsg() + ",验证码发送过于频繁,请稍后再试");
} else {
// 生成6位数验证码
String code = StringUtil.generatorCode(6);
// 将验证码存入redis,有效期3分钟
redisUtil.set(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE, dto.getEmail()), code, 3L, TimeUnit.MINUTES);
// 调用mail发送邮件
sendMailUtil.sendMail(dto.getEmail(), "邮箱验证码", sendMailUtil.buildCodeContent(code));
// 验证码发送频率控制
redisUtil.set(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE_TIME, dto.getEmail()), dto.getEmail(), 30L, TimeUnit.SECONDS);
}
} catch (Exception e) {
log.error("邮箱验证码发送失败,{}", e);
result.setCode(CurrencyErrorEnum.OPERA_ERROR.getCode());
result.setMsg(CurrencyErrorEnum.OPERA_ERROR.getMsg());
} finally {
long endTime = System.currentTimeMillis();
log.info("【{}】【邮箱验证码发送接口】【{}ms】 \n入参:{}\n出参:{}", "发送验证码", endTime - startTime, dto, result);
}
}
4、服务端用户注册接口
@PostMapping("/user/register")
@ApiOperation("新用户注册处理器")
public BaseResult register(@RequestBody @Validated UserRegirsterDTO dto, HttpServletRequest request){
BaseResult result = BaseResult.ok();
userService.register(dto, request, result);
return result;
}
实现类,非必要代码就省略了哈
@Override
@Transactional
public void register(UserRegirsterDTO dto, HttpServletRequest request, BaseResult result) {
long startTime = System.currentTimeMillis();
try {
// 因为传输过程中对密码进行加密了,先解密在验证长度
String password = RSAUtil.decrypt(dto.getPassword(), commonConfig.getRsaPrivateKey());
if (password.length() > 16) {
result.setCode(CurrencyErrorEnum.REQUEST_PARAMETER_ERROR.getCode());
result.setMsg(CurrencyErrorEnum.REQUEST_PARAMETER_ERROR.getMsg() + ",用户密码最大16个字符");
return;
}
// 去redis获取邮箱验证码
String sysCode = redisUtil.getString(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE, dto.getEmail()));
// 验证码存在且与参数传递过来的相等
if (StringUtils.hasLength(sysCode) && sysCode.equals(dto.getCode())) {
// 清楚这个验证码
redisUtil.removeKey(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE, dto.getEmail()));
// 邮箱绑定账号数验证 ...
// 用户名唯一验证 ...
// 用户注册
User user = new User();
BeanUtils.copyProperties(dto, user);
user.setCreateTime(LocalDateTime.now());
user.setUserId(IdUtil.simpleUUID());
user.setPassword(passwordEncoder.encode(password));
int insert = userMapper.insert(user);
// 用户注册后的一系列权限分配,初始化...
} else {
// 未查询到验证码,返回验证码校验失败
result.setCode(CurrencyErrorEnum.OPERA_ERROR.getCode());
result.setMsg(CurrencyErrorEnum.OPERA_ERROR.getMsg() + ",验证码校验失败");
}
} catch (Exception e) {
log.error("新用户注册失败,{}", e);
result.setCode(CurrencyErrorEnum.OPERA_ERROR.getCode());
result.setMsg(CurrencyErrorEnum.OPERA_ERROR.getMsg());
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
} finally {
long endTime = System.currentTimeMillis();
log.info("【{}】【新用户注册接口】【{}ms】 \n入参:{}\n出参:{}", "新增", endTime - startTime, JSON.toJSONString(dto), result);
}
}
结语
今天这个案例就分享到这,总结一下
1、在发送请求时,作为前端需根据需求严格的对参数进行较验,无误后发送请求。
2、作为后端来讲,接口设计应极为严格,需自行参数验证,不能相信前端,因为很有可能,设计的接口会脱离浏览器被访问。
3、此案例代码较多,部分不相关的代码省略了,请周知。文章来源:https://www.toymoban.com/news/detail-474462.html
4、制作不易,一键三连再走吧,您的支持永远是我最大的动力!文章来源地址https://www.toymoban.com/news/detail-474462.html
到了这里,关于SpringBoot整合邮箱验证码实现用户注册的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!