ruoyi-vue添加手机号验证码注册功能
1. 在阿里云中开通sms并申请签名模板
申请模板通过后在控制台中得到以下信息并添加进ruoyi-admin/resource/application.yml中:
aliSms:
accessKeyId: *** # 阿里云 accessKeyId
accessKeySecret: *** # 阿里云 accessKeySecret
signName: *** # 模板对应的签名名称
templateCode: *** # 使用的模板Code
2. 编写短信验证码发送工具
在ruoyi-framework下
2.1 添加依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.18.1</version>
</dependency>
2.2 创建SmsVerificationUtils
package com.ruoyi.framework.sms;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.profile.DefaultProfile;
import com.ruoyi.common.core.redis.RedisCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* 短信验证码发送工具
*/
@Component
public class SmsVerificationUtils {
private static final Logger log = LoggerFactory.getLogger(SmsVerificationUtils.class);
/**
* 阿里云 accessKeyId
*/
@Value("${aliSms.accessKeyId}")
private String accessKeyId;
/**
* 阿里云 secret
*/
@Value("${aliSms.accessKeySecret}")
private String accessKeySecret;
/**
* 阿里云签名
*/
@Value("${aliSms.signName}")
private String signName;
/**
* 阿里云短信模板Code
*/
@Value("${aliSms.templateCode}")
private String templateCode;
@Autowired
private RedisCache redisCache;
/**
* 从Redis中根据手机号获取验证码
*
* @param key 手机号
* @return 返回获取的验证码
*/
public String get(String key) {
Object code = redisCache.getCacheObject(key);
if (code != null) {
return redisCache.getCacheObject(key).toString();
}
return null;
}
/**
* 删除Redis中的验证码
*
* @param key 手机号
*/
public void delete(String key) {
redisCache.deleteObject(key);
}
/**
* 发送短信手机短信验证码
*
* @param phoneNum 手机号
* @return 返回发送状态
*/
public String send(String phoneNum) {
DefaultProfile profile = DefaultProfile.getProfile("default", accessKeyId, accessKeySecret);
IAcsClient client = new DefaultAcsClient(profile);
Random random = new Random();
String randomNumber = String.valueOf(100000 + random.nextInt(900000));
CommonRequest request = new CommonRequest();
request.setProtocol(ProtocolType.HTTPS);
request.setMethod(MethodType.POST);
request.setDomain("dysmsapi.aliyuncs.com");
request.setVersion("2017-05-25");
request.setAction("SendSms");
request.putQueryParameter("RegionId", "cn-guangzhou");
request.putQueryParameter("PhoneNumbers", phoneNum);
request.putQueryParameter("SignName", signName);
request.putQueryParameter("TemplateCode", templateCode);
request.putQueryParameter("TemplateParam", "{\"code\": " + randomNumber + "}");
try {
System.out.println("生成6位验证码为:【" + randomNumber + "】");
// 将验证码放入Redis缓存中 有效时间为5分钟
redisCache.setCacheObject(phoneNum, randomNumber, 5, TimeUnit.MINUTES);
CommonResponse response = client.getCommonResponse(request);
String data = response.getData();
return JSONObject.parseObject(data).getString("Message");
}
catch (ServerException e) {
e.printStackTrace();
return null;
}
catch (ClientException e) {
e.printStackTrace();
return null;
}
}
}
3. 修改注册逻辑
3.1 修改SysRegisterController
添加发送短信验证码接口
@Autowired
private SmsVerificationUtils smsVerificationUtils;
/**
* 发送验证码
* @param phoneNumber 手机号
* @return 返回发送状态
*/
@GetMapping("/sendVerificationCode")
public AjaxResult send(@RequestParam("phoneNumber") String phoneNumber) {
String send = smsVerificationUtils.send(phoneNumber);
if ("OK".equals(send)) {
return AjaxResult.success();
}
return AjaxResult.error("短信验证码发送失败", send);
}
3.2 开放发送验证码接口权限
3.3 修改LoginBody实体类
package com.ruoyi.common.core.domain.model;
/**
* 用户登录对象
*
* @author ruoyi
*/
public class LoginBody
{
/**
* 用户名
*/
private String username;
/**
* 用户密码
*/
private String password;
/**
* 手机号
*/
private String phonenumber;
/**
* 验证码
*/
private String code;
/**
* 唯一标识
*/
private String uuid;
public String getPhonenumber() {
return phonenumber;
}
public void setPhonenumber(String phonenumber) {
this.phonenumber = phonenumber;
}
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public String getCode()
{
return code;
}
public void setCode(String code)
{
this.code = code;
}
public String getUuid()
{
return uuid;
}
public void setUuid(String uuid)
{
this.uuid = uuid;
}
}
在其中添加了phonenumber属性
3.4 修改SysUserServiceImpl
修改校验手机号是否唯一的方法
3.5 修改SysRegisterService
package com.ruoyi.framework.web.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;
/**
* 注册校验方法
*
* @author ruoyi
*/
@Component
public class SysRegisterService
{
@Autowired
private ISysUserService userService;
@Autowired
private ISysConfigService configService;
@Autowired
private RedisCache redisCache;
/**
* 注册
*/
public String register(RegisterBody registerBody)
{
String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword();
SysUser sysUser = new SysUser();
sysUser.setUserName(username);
String phonenumber = registerBody.getPhonenumber();
sysUser.setPhonenumber(phonenumber);
String check = codeCheck(phonenumber, registerBody.getCode());
if (StringUtils.isEmpty(username))
{
msg = "用户名不能为空";
}
else if (StringUtils.isEmpty(password))
{
msg = "用户密码不能为空";
}
else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
msg = "账户长度必须在2到20个字符之间";
}
else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
msg = "密码长度必须在5到20个字符之间";
}
else if (!check.equals("success"))
{
msg = check;
}
else if (!userService.checkPhoneUnique(sysUser))
{
msg = "该手机号已被注册";
}
else
{
redisCache.deleteObject(phonenumber);
sysUser.setNickName(username);
sysUser.setPassword(SecurityUtils.encryptPassword(password));
boolean regFlag = userService.registerUser(sysUser);
if (!regFlag)
{
msg = "注册失败,请联系系统管理人员";
}
else
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success")));
}
}
return msg;
}
/**
* 验证码校验
* @param phonenumber 手机号
* @param code 验证码
* @return
*/
public String codeCheck(String phonenumber, String code){
Boolean bool = redisCache.hasKey(phonenumber);
if (bool != null && bool) {
String redisCode = redisCache.getCacheObject(phonenumber);
if (redisCode.equals(code)){
return "success";
}else {
return "验证码不匹配";
}
}else {
return "验证码已过期";
}
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
* @return 结果
*/
public void validateCaptcha(String username, String code, String uuid)
{
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
String captcha = redisCache.getCacheObject(verifyKey);
redisCache.deleteObject(verifyKey);
if (captcha == null)
{
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha))
{
throw new CaptchaException();
}
}
}
4. 前端修改
在api/login.js下添加接口
// 发送验证码
export function sendCode(phoneNumber) {
return request({
url: '/sendVerificationCode?phoneNumber=' + phoneNumber,
headers: {
isToken: false
},
method: 'get'
})
}
在login.vue登录页面中打开注册开关
修改register.vue注册页面
<template>
<div class="register">
<el-form ref="registerForm" :model="registerForm" :rules="registerRules" class="register-form">
<h3 class="title">自由人后台管理系统</h3>
<el-form-item prop="username">
<el-input v-model="registerForm.username" type="text" auto-complete="off" placeholder="账号">
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="registerForm.password"
type="password"
auto-complete="off"
placeholder="密码"
@keyup.enter.native="handleRegister"
>
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="registerForm.confirmPassword"
type="password"
auto-complete="off"
placeholder="确认密码"
@keyup.enter.native="handleRegister"
>
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="phonenumber">
<el-input
v-model="registerForm.phonenumber"
auto-complete="off"
placeholder="请输入手机号"
/>
</el-form-item>
<el-form-item prop="code">
<el-input
v-model="registerForm.code"
auto-complete="off"
placeholder="请输入验证码"
style="width: 63%"
/>
<div class="register-code">
<el-button @click="send" :disabled="exTime !== 60">{{exText}}</el-button>
</div>
</el-form-item>
<el-form-item style="width:100%;">
<el-button
:loading="loading"
size="medium"
type="primary"
style="width:100%;"
@click.native.prevent="handleRegister"
>
<span v-if="!loading">注 册</span>
<span v-else>注 册 中...</span>
</el-button>
<div style="float: right;">
<router-link class="link-type" :to="'/login'">使用已有账户登录</router-link>
</div>
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-register-footer">
<span>Copyright © 2018-2024 ruoyi.vip All Rights Reserved.</span>
</div>
</div>
</template>
<script>
import { getCodeImg, register, sendCode } from "@/api/login";
export default {
name: "Register",
data() {
const equalToPassword = (rule, value, callback) => {
if (this.registerForm.password !== value) {
callback(new Error("两次输入的密码不一致"));
} else {
callback();
}
};
return {
codeUrl: "",
registerForm: {
username: "",
password: "",
confirmPassword: "",
code: "",
uuid: "",
phonenumber: ""
},
registerRules: {
username: [
{ required: true, trigger: "blur", message: "请输入您的账号" },
{ min: 2, max: 20, message: '用户账号长度必须介于 2 和 20 之间', trigger: 'blur' }
],
password: [
{ required: true, trigger: "blur", message: "请输入您的密码" },
{ min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" },
{ pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }
],
confirmPassword: [
{ required: true, trigger: "blur", message: "请再次输入您的密码" },
{ required: true, validator: equalToPassword, trigger: "blur" }
],
code: [{ required: true, trigger: "change", message: "请输入验证码" }]
},
loading: false,
captchaEnabled: true,
exText: '发送验证码',
exTime: 60,
timerId: null
};
},
created() {
//this.getCode();
},
methods: {
send() {
const telCheck = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/
if (this.registerForm.phonenumber != null && telCheck.test(this.registerForm.phonenumber)) {
this.countDown();
sendCode(this.registerForm.phonenumber).then(res => {
if (res.code === 200) {
this.$message.success("短信发送成功!");
}
console.log(res);
})
}else {
this.$message.error("手机号输入不正确!");
}
},
getCode() {
getCodeImg().then(res => {
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
if (this.captchaEnabled) {
this.codeUrl = "data:image/gif;base64," + res.img;
this.registerForm.uuid = res.uuid;
}
});
},
countDown() {
this.timerId = setInterval(() => {
this.exTime--;
}, 1000);
},
handleRegister() {
this.$refs.registerForm.validate(valid => {
if (valid) {
this.loading = true;
register(this.registerForm).then(res => {
const username = this.registerForm.username;
this.$alert("<font color='red'>恭喜你,您的账号 " + username + " 注册成功!</font>", '系统提示', {
dangerouslyUseHTMLString: true,
type: 'success'
}).then(() => {
this.$router.push("/login");
}).catch(() => {});
}).catch(() => {
this.loading = false;
if (this.captchaEnabled) {
this.getCode();
}
})
}
});
}
},
watch: {
exTime: {
handler(val) {
if (val === 60 || val === 0) {
if (this.timerId) clearInterval(this.timerId);
this.exText = "发送验证码";
this.exTime = 60;
}else {
this.exText = val + "秒后重试";
}
}
}
}
};
</script>
<style rel="stylesheet/scss" lang="scss">
.register {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-color: #1f2d3d;
//background-image: url("../assets/images/login-background.jpg");
//background-size: cover;
}
.title {
margin: 0px auto 30px auto;
text-align: center;
color: #707070;
}
.register-form {
border-radius: 6px;
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
.el-input {
height: 38px;
input {
height: 38px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 2px;
}
}
.register-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
.register-code {
width: 33%;
height: 38px;
float: right;
img {
cursor: pointer;
vertical-align: middle;
}
}
.el-register-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial;
font-size: 12px;
letter-spacing: 1px;
}
.register-code-img {
height: 38px;
}
</style>
5. 开放系统注册功能
在数据库中找到sys_config表并将config_value改为true