嘮嗑部分
今天我們來(lái)分享一下在系統(tǒng)開(kāi)發(fā)過(guò)程中,如何使用驗(yàn)證碼來(lái)驗(yàn)證用戶(hù)并完成用戶(hù)注冊(cè)
首先來(lái)看一下成品界面展示
說(shuō)一下以上注冊(cè)功能的設(shè)計(jì):
用戶(hù)手動(dòng)輸入用戶(hù)名(全數(shù)據(jù)庫(kù)唯一)、密碼、確認(rèn)密碼、郵箱地址(單個(gè)郵箱最多可注冊(cè)3個(gè)用戶(hù))、正確的郵箱驗(yàn)證碼,即可注冊(cè)
先來(lái)展示一下
輸入用戶(hù)信息
收到郵箱驗(yàn)證碼
注冊(cè)成功
言歸正傳
使用驗(yàn)證碼這種方式呢是比較常見(jiàn)的,我們?cè)谧?cè)App的時(shí)候,會(huì)有手機(jī)驗(yàn)證碼、郵箱的類(lèi)似哈(發(fā)郵件是免費(fèi)的),郵箱驗(yàn)證也能夠保證其真實(shí)性,防止惡意用戶(hù)非法注冊(cè)
下面我們就來(lái)說(shuō)一下,這一系列的實(shí)現(xiàn)思路
1、用戶(hù)名唯一驗(yàn)證就省略了哈,不在此范圍內(nèi)
2、在用戶(hù)填入信息時(shí)前端先做必傳、格式驗(yàn)證。
3、郵箱驗(yàn)證通過(guò)后,點(diǎn)擊發(fā)送驗(yàn)證碼時(shí),攜帶郵箱參數(shù)請(qǐng)求后端接口。
4、后端生成并發(fā)送驗(yàn)證碼后,將驗(yàn)證碼進(jìn)行分布式存儲(chǔ),如存到redis,key為郵箱,value為驗(yàn)證碼,失效時(shí)間設(shè)置3分鐘。
5、用戶(hù)在3分鐘內(nèi)收到驗(yàn)證碼并填入注冊(cè)表單,請(qǐng)求用戶(hù)注冊(cè)接口。
6、后端在收到注冊(cè)請(qǐng)求后,首先驗(yàn)證參數(shù)的合法性,驗(yàn)證通過(guò)后,根據(jù)郵箱去redis查詢(xún)驗(yàn)證碼。
7、未查詢(xún)到驗(yàn)證碼,則說(shuō)明驗(yàn)證碼已經(jīng)過(guò)期,返回驗(yàn)證碼校驗(yàn)失敗,查詢(xún)到驗(yàn)證碼后,與表單中的驗(yàn)證碼進(jìn)行比較,相同則繼續(xù)注冊(cè)邏輯,不同則返回驗(yàn)證碼校驗(yàn)失敗。
代碼環(huán)節(jié)
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 }}注冊(cè)</h3>
</div>
<el-form-item prop="userName">
<el-input
prefix-icon="el-icon-user"
ref="username"
v-model="registerInfo.userName"
placeholder="請(qǐng)輸入用戶(hù)名"
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="請(qǐng)輸入賬戶(hù)密碼"
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="請(qǐng)確認(rèn)密碼"
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="請(qǐng)輸入郵箱地址,每個(gè)郵箱最多可綁定3個(gè)賬號(hào)"
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="請(qǐng)輸入郵箱驗(yàn)證碼"
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">獲取驗(yàn)證碼 <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">注冊(cè)
</el-button>
<div style="text-align: right">
<el-link type="primary" :underline="false" @click.native="nativeToLogin">已有賬號(hào),去登錄>></el-link>
</div>
</el-form>
</el-dialog>
<el-dialog
title="新用戶(hù)提示"
:visible.sync="dialogVisible"
width="30%">
<span>系統(tǒng)檢測(cè)到您是新用戶(hù),請(qǐng)及時(shí)更新信息</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('登錄名須由數(shù)字、英文字母、-、下劃線(xiàn)(不包含、)組成,不大于16位'));
}
};
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('請(qǐng)輸入密碼'));
} else {
if (this.registerInfo.password !== '') {
this.$refs.registerForm.validateField('password2');
}
callback();
}
};
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('請(qǐng)?jiān)俅屋斎朊艽a'));
} 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('當(dāng)前郵箱綁定用戶(hù)已達(dá)到上限'));
}
})
} else {
callback(new Error('郵箱格式不正確'));
}
} else {
callback(new Error('郵箱為必傳項(xiàng)'));
}
};
return {
defaultUserImg: defaultUserImg,
refresh: true,
baseUrl: URL_PREFIX,
codeUrl: URL_PREFIX + '/auth/getCaptcha',
rules: {
username: [{required: true, trigger: 'blur', message: '用戶(hù)名為必填項(xiàng)'}],
password: [{required: true, trigger: 'blur', message: '密碼為必填項(xiàng)'}],
code: [{required: true, trigger: 'blur', message: '驗(yàn)證碼為必填項(xiàng)'}]
},
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: '驗(yàn)證碼為必填項(xiàng)'}]
},
dialogVisible: false
}
},
methods: {
// 用戶(hù)注冊(cè)
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('用戶(hù)注冊(cè)成功')
this.loading = false
this.registerInfo = defaultRegisterInfo
this.$refs.registerForm.resetFields()
this.nativeToLogin()
})
} else {
console.log('error submit!!')
return false
}
setTimeout(() => {
this.loading = false
}, 2000)
})
},
// 發(fā)送驗(yàn)證碼
sendEmailCode() {
this.$refs.registerForm.validateField('email', valid => {
if (!valid) {
if (this.pause) {
this.$message.error('操作太頻繁,請(qǐng)稍后再試')
} else {
sendEmailCode({email: this.registerInfo.email}).then(res => {
this.$message.success('驗(yàn)證碼發(fā)送成功,請(qǐng)注意查收!')
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
// 發(fā)送驗(yàn)證碼
export function sendEmailCode(data = {}) {
return request({
url: URL_PREFIX + '/main/user/sendEmailCode',
method: 'post',
data
})
}
// 注冊(cè)
export function register(data = {}) {
return request({
url: URL_PREFIX + '/main/user/register',
method: 'post',
data
})
}
3、服務(wù)端發(fā)送驗(yàn)證碼
@PostMapping("/user/sendEmailCode")
@ApiOperation("發(fā)送驗(yàn)證碼處理器")
public BaseResult sendEmailCode(@RequestBody @Validated SendEmailCodeDTO dto, HttpServletRequest request){
BaseResult result = BaseResult.ok();
baseService.sendEmailCode(dto, request, result);
return result;
}
實(shí)現(xiàn)類(lèi)
@Override
public void sendEmailCode(SendEmailCodeDTO dto, HttpServletRequest request, BaseResult result) {
long startTime = System.currentTimeMillis();
try {
// 驗(yàn)證碼發(fā)送頻率控制驗(yàn)證
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() + ",驗(yàn)證碼發(fā)送過(guò)于頻繁,請(qǐng)稍后再試");
} else {
// 生成6位數(shù)驗(yàn)證碼
String code = StringUtil.generatorCode(6);
// 將驗(yàn)證碼存入redis,有效期3分鐘
redisUtil.set(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE, dto.getEmail()), code, 3L, TimeUnit.MINUTES);
// 調(diào)用mail發(fā)送郵件
sendMailUtil.sendMail(dto.getEmail(), "郵箱驗(yàn)證碼", sendMailUtil.buildCodeContent(code));
// 驗(yàn)證碼發(fā)送頻率控制
redisUtil.set(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE_TIME, dto.getEmail()), dto.getEmail(), 30L, TimeUnit.SECONDS);
}
} catch (Exception e) {
log.error("郵箱驗(yàn)證碼發(fā)送失敗,{}", e);
result.setCode(CurrencyErrorEnum.OPERA_ERROR.getCode());
result.setMsg(CurrencyErrorEnum.OPERA_ERROR.getMsg());
} finally {
long endTime = System.currentTimeMillis();
log.info("【{}】【郵箱驗(yàn)證碼發(fā)送接口】【{}ms】 \n入?yún)?{}\n出參:{}", "發(fā)送驗(yàn)證碼", endTime - startTime, dto, result);
}
}
4、服務(wù)端用戶(hù)注冊(cè)接口
@PostMapping("/user/register")
@ApiOperation("新用戶(hù)注冊(cè)處理器")
public BaseResult register(@RequestBody @Validated UserRegirsterDTO dto, HttpServletRequest request){
BaseResult result = BaseResult.ok();
userService.register(dto, request, result);
return result;
}
實(shí)現(xiàn)類(lèi),非必要代碼就省略了哈
@Override
@Transactional
public void register(UserRegirsterDTO dto, HttpServletRequest request, BaseResult result) {
long startTime = System.currentTimeMillis();
try {
// 因?yàn)閭鬏斶^(guò)程中對(duì)密碼進(jìn)行加密了,先解密在驗(yàn)證長(zhǎng)度
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() + ",用戶(hù)密碼最大16個(gè)字符");
return;
}
// 去redis獲取郵箱驗(yàn)證碼
String sysCode = redisUtil.getString(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE, dto.getEmail()));
// 驗(yàn)證碼存在且與參數(shù)傳遞過(guò)來(lái)的相等
if (StringUtils.hasLength(sysCode) && sysCode.equals(dto.getCode())) {
// 清楚這個(gè)驗(yàn)證碼
redisUtil.removeKey(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE, dto.getEmail()));
// 郵箱綁定賬號(hào)數(shù)驗(yàn)證 ...
// 用戶(hù)名唯一驗(yàn)證 ...
// 用戶(hù)注冊(cè)
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);
// 用戶(hù)注冊(cè)后的一系列權(quán)限分配,初始化...
} else {
// 未查詢(xún)到驗(yàn)證碼,返回驗(yàn)證碼校驗(yàn)失敗
result.setCode(CurrencyErrorEnum.OPERA_ERROR.getCode());
result.setMsg(CurrencyErrorEnum.OPERA_ERROR.getMsg() + ",驗(yàn)證碼校驗(yàn)失敗");
}
} catch (Exception e) {
log.error("新用戶(hù)注冊(cè)失敗,{}", e);
result.setCode(CurrencyErrorEnum.OPERA_ERROR.getCode());
result.setMsg(CurrencyErrorEnum.OPERA_ERROR.getMsg());
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
} finally {
long endTime = System.currentTimeMillis();
log.info("【{}】【新用戶(hù)注冊(cè)接口】【{}ms】 \n入?yún)?{}\n出參:{}", "新增", endTime - startTime, JSON.toJSONString(dto), result);
}
}
結(jié)語(yǔ)
今天這個(gè)案例就分享到這,總結(jié)一下
1、在發(fā)送請(qǐng)求時(shí),作為前端需根據(jù)需求嚴(yán)格的對(duì)參數(shù)進(jìn)行較驗(yàn),無(wú)誤后發(fā)送請(qǐng)求。
2、作為后端來(lái)講,接口設(shè)計(jì)應(yīng)極為嚴(yán)格,需自行參數(shù)驗(yàn)證,不能相信前端,因?yàn)楹苡锌赡?,設(shè)計(jì)的接口會(huì)脫離瀏覽器被訪問(wèn)。
3、此案例代碼較多,部分不相關(guān)的代碼省略了,請(qǐng)周知。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-474462.html
4、制作不易,一鍵三連再走吧,您的支持永遠(yuǎn)是我最大的動(dòng)力!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-474462.html
到了這里,關(guān)于SpringBoot整合郵箱驗(yàn)證碼實(shí)現(xiàn)用戶(hù)注冊(cè)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!