今天我们来分享一下在系统开发过程中,如何使用验证码来验证用户并完成用户注册
首先来看一下成品界面展示
说一下以上注册功能的设计:
用户手动输入用户名(全数据库唯一)、密码、确认密码、邮箱地址(单个邮箱最多可注册3个用户)、正确的邮箱验证码,即可注册
先来展示一下
输入用户信息
收到邮箱验证码
注册成功
使用验证码这种方式呢是比较常见的,我们在注册App的时候,会有手机验证码、邮箱的类似哈(发邮件是免费的),邮箱验证也能够保证其真实性,防止恶意用户非法注册
下面我们就来说一下,这一系列的实现思路
1、用户名唯一验证就省略了哈,不在此范围内
2、在用户填入信息时前端先做必传、格式验证。
3、邮箱验证通过后,点击发送验证码时,携带邮箱参数请求后端接口。
4、后端生成并发送验证码后,将验证码进行分布式存储,如存到redis,key为邮箱,value为验证码,失效时间设置3分钟。
5、用户在3分钟内收到验证码并填入注册表单,请求用户注册接口。
6、后端在收到注册请求后,首先验证参数的合法性,验证通过后,根据邮箱去redis查询验证码。
7、未查询到验证码,则说明验证码已经过期,返回验证码校验失败,查询到验证码后,与表单中的验证码进行比较,相同则继续注册逻辑,不同则返回验证码校验失败。
代码环节
1、Vue组件
{{ sysInfo.sysTitle }}注册
获取验证码 {{ time }}
注册
已有账号,去登录>>
系统检测到您是新用户,请及时更新信息
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、此案例代码较多,部分不相关的代码省略了,请周知。
4、制作不易,一键三连再走吧,您的支持永远是我最大的动力!