完成重置密码功能

This commit is contained in:
NagoColer 2023-04-11 21:10:02 +08:00
parent ab91c7b5a6
commit 88ce641fda
6 changed files with 209 additions and 85 deletions

View File

@ -23,10 +23,20 @@ public class AuthorizeController {
@Resource
AuthorizeService service;
@PostMapping("/valid-email")
public RestBean<String> validateEmail(@Pattern(regexp = EMAIL_REGEX) @RequestParam("email") String email,
@PostMapping("/valid-register-email")
public RestBean<String> validateRegisterEmail(@Pattern(regexp = EMAIL_REGEX) @RequestParam("email") String email,
HttpSession session){
String s = service.sendValidateEmail(email, session.getId());
String s = service.sendValidateEmail(email, session.getId(), false);
if (s == null)
return RestBean.success("邮件已发送,请注意查收");
else
return RestBean.failure(400, s);
}
@PostMapping("/valid-reset-email")
public RestBean<String> validateResetEmail(@Pattern(regexp = EMAIL_REGEX) @RequestParam("email") String email,
HttpSession session){
String s = service.sendValidateEmail(email, session.getId(), true);
if (s == null)
return RestBean.success("邮件已发送,请注意查收");
else
@ -46,5 +56,30 @@ public class AuthorizeController {
return RestBean.failure(400, s);
}
@PostMapping("/start-reset")
public RestBean<String> startReset(@Pattern(regexp = EMAIL_REGEX) @RequestParam("email") String email,
@Length(min = 6, max = 6) @RequestParam("code") String code,
HttpSession session) {
String s = service.validateOnly(email, code, session.getId());
if(s == null) {
session.setAttribute("reset-password", email);
return RestBean.success();
} else {
return RestBean.failure(400, s);
}
}
@PostMapping("/do-reset")
public RestBean<String> resetPassword(@Length(min = 6, max = 16) @RequestParam("password") String password,
HttpSession session){
String email = (String) session.getAttribute("reset-password");
if(email == null) {
return RestBean.failure(401, "请先完成邮箱验证");
} else if(service.resetPassword(password, email)){
session.removeAttribute("reset-password");
return RestBean.success("密码重置成功");
} else {
return RestBean.failure(500, "内部错误,请联系管理员");
}
}
}

View File

@ -4,6 +4,7 @@ import com.example.entity.Account;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
@Mapper
public interface UserMapper {
@ -12,4 +13,7 @@ public interface UserMapper {
@Insert("insert into db_account (email, username, password) values (#{email}, #{username}, #{password})")
int createAccount(String username, String password, String email);
@Update("update db_account set password = #{password} where email = #{email}")
int resetPasswordByEmail(String password, String email);
}

View File

@ -3,6 +3,8 @@ package com.example.service;
import org.springframework.security.core.userdetails.UserDetailsService;
public interface AuthorizeService extends UserDetailsService {
String sendValidateEmail(String email, String sessionId);
String sendValidateEmail(String email, String sessionId, boolean hasAccount);
String validateAndRegister(String username, String password, String email, String code, String sessionId);
String validateOnly(String email, String code, String sessionId);
boolean resetPassword(String password, String email);
}

View File

@ -51,14 +51,15 @@ public class AuthorizeServiceImpl implements AuthorizeService {
}
@Override
public String sendValidateEmail(String email, String sessionId) {
String key = "email:" + sessionId + ":" + email;
public String sendValidateEmail(String email, String sessionId, boolean hasAccount) {
String key = "email:" + sessionId + ":" + email + ":" +hasAccount;
if(Boolean.TRUE.equals(template.hasKey(key))) {
Long expire = Optional.ofNullable(template.getExpire(key, TimeUnit.SECONDS)).orElse(0L);
if(expire > 120) return "请求频繁,请稍后再试";
}
if(mapper.findAccountByNameOrEmail(email) != null)
return "此邮箱已被其他用户注册";
Account account = mapper.findAccountByNameOrEmail(email);
if(hasAccount && account == null) return "没有此邮件地址的账户";
if(!hasAccount && account != null) return "此邮箱已被其他用户注册";
Random random = new Random();
int code = random.nextInt(899999) + 100000;
SimpleMailMessage message = new SimpleMailMessage();
@ -78,11 +79,14 @@ public class AuthorizeServiceImpl implements AuthorizeService {
@Override
public String validateAndRegister(String username, String password, String email, String code, String sessionId) {
String key = "email:" + sessionId + ":" + email;
String key = "email:" + sessionId + ":" + email + ":false";
if(Boolean.TRUE.equals(template.hasKey(key))) {
String s = template.opsForValue().get(key);
if(s == null) return "验证码失效,请重新请求";
if(s.equals(code)) {
Account account = mapper.findAccountByNameOrEmail(username);
if(account != null) return "此用户名已被注册,请更换用户名";
template.delete(key);
password = encoder.encode(password);
if (mapper.createAccount(username, password, email) > 0) {
return null;
@ -96,4 +100,27 @@ public class AuthorizeServiceImpl implements AuthorizeService {
return "请先请求一封验证码邮件";
}
}
@Override
public String validateOnly(String email, String code, String sessionId) {
String key = "email:" + sessionId + ":" + email + ":true";
if(Boolean.TRUE.equals(template.hasKey(key))) {
String s = template.opsForValue().get(key);
if(s == null) return "验证码失效,请重新请求";
if(s.equals(code)) {
template.delete(key);
return null;
} else {
return "验证码错误,请检查后再提交";
}
} else {
return "请先请求一封验证码邮件";
}
}
@Override
public boolean resetPassword(String password, String email) {
password = encoder.encode(password);
return mapper.resetPasswordByEmail(password, email) > 0;
}
}

View File

@ -1,89 +1,96 @@
<template>
<div style="margin: 30px 20px">
<el-steps :active="active" finish-status="success" align-center>
<el-step title="验证电子邮件" />
<el-step title="重新设定密码" />
</el-steps>
<div>
<div style="margin: 30px 20px">
<el-steps :active="active" finish-status="success" align-center>
<el-step title="验证电子邮件" />
<el-step title="重新设定密码" />
</el-steps>
</div>
<transition name="el-fade-in-linear" mode="out-in">
<div style="text-align: center;margin: 0 20px;height: 100%" v-if="active === 0">
<div style="margin-top: 80px">
<div style="font-size: 25px;font-weight: bold">重置密码</div>
<div style="font-size: 14px;color: grey">请输入需要重置密码的电子邮件地址</div>
</div>
<div style="margin-top: 50px">
<el-form :model="form" :rules="rules" @validate="onValidate" ref="formRef">
<el-form-item prop="email">
<el-input v-model="form.email" type="email" placeholder="电子邮件地址">
<template #prefix>
<el-icon><Message /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-row :gutter="10" style="width: 100%">
<el-col :span="17">
<el-input v-model="form.code" :maxlength="6" type="text" placeholder="请输入验证码">
<template #prefix>
<el-icon><EditPen /></el-icon>
</template>
</el-input>
</el-col>
<el-col :span="5">
<el-button type="success" @click="validateEmail"
:disabled="!isEmailValid || coldTime > 0">
{{coldTime > 0 ? '请稍后 ' + coldTime + ' 秒' : '获取验证码'}}
</el-button>
</el-col>
</el-row>
</el-form-item>
</el-form>
</div>
<div style="margin-top: 70px">
<el-button @click="startReset()" style="width: 270px;" type="danger" plain>开始重置密码</el-button>
</div>
</div>
</transition>
<transition name="el-fade-in-linear" mode="out-in">
<div style="text-align: center;margin: 0 20px;height: 100%" v-if="active === 1">
<div style="margin-top: 80px">
<div style="font-size: 25px;font-weight: bold">重置密码</div>
<div style="font-size: 14px;color: grey">请填写您的新密码务必牢记防止丢失</div>
</div>
<div style="margin-top: 50px">
<el-form :model="form" :rules="rules" @validate="onValidate" ref="formRef">
<el-form-item prop="password">
<el-input v-model="form.password" :maxlength="16" type="password" placeholder="新密码">
<template #prefix>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password_repeat">
<el-input v-model="form.password_repeat" :maxlength="16" type="password" placeholder="重复新密码">
<template #prefix>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
</el-form>
</div>
<div style="margin-top: 70px">
<el-button @click="doReset()" style="width: 270px;" type="danger" plain>立即重置密码</el-button>
</div>
</div>
</transition>
</div>
<transition name="el-fade-in-linear" mode="out-in">
<div style="text-align: center;margin: 0 20px;height: 100%" v-if="active === 0">
<div style="margin-top: 80px">
<div style="font-size: 25px;font-weight: bold">重置密码</div>
<div style="font-size: 14px;color: grey">请输入需要重置密码的电子邮件地址</div>
</div>
<div style="margin-top: 50px">
<el-form :model="form" :rules="rules" @validate="onValidate" ref="formRef">
<el-form-item prop="email">
<el-input v-model="form.email" type="email" placeholder="电子邮件地址">
<template #prefix>
<el-icon><Message /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-row :gutter="10" style="width: 100%">
<el-col :span="17">
<el-input v-model="form.code" :maxlength="6" type="text" placeholder="请输入验证码">
<template #prefix>
<el-icon><EditPen /></el-icon>
</template>
</el-input>
</el-col>
<el-col :span="5">
<el-button type="success" @click="validateEmail"
:disabled="!isEmailValid || coldTime > 0">
{{coldTime > 0 ? '请稍后 ' + coldTime + ' 秒' : '获取验证码'}}
</el-button>
</el-col>
</el-row>
</el-form-item>
</el-form>
</div>
<div style="margin-top: 70px">
<el-button @click="active = 1" style="width: 270px;" type="danger" plain>开始重置密码</el-button>
</div>
</div>
</transition>
<transition name="el-fade-in-linear" mode="out-in">
<div style="text-align: center;margin: 0 20px;height: 100%" v-if="active === 1">
<div style="margin-top: 80px">
<div style="font-size: 25px;font-weight: bold">重置密码</div>
<div style="font-size: 14px;color: grey">请填写您的新密码务必牢记防止丢失</div>
</div>
<div style="margin-top: 50px">
<el-form :model="form" :rules="rules" @validate="onValidate" ref="formRef">
<el-form-item prop="password">
<el-input v-model="form.password" :maxlength="16" type="password" placeholder="密码">
<template #prefix>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password_repeat">
<el-input v-model="form.password_repeat" :maxlength="16" type="password" placeholder="重复密码">
<template #prefix>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
</el-form>
</div>
<div style="margin-top: 70px">
<el-button @click="active = 1" style="width: 270px;" type="danger" plain>立即重置密码</el-button>
</div>
</div>
</transition>
</template>
<script setup>
import {reactive, ref} from "vue";
import {EditPen, Lock, Message} from "@element-plus/icons-vue";
import {post} from "@/net";
import {ElMessage} from "element-plus";
import router from "@/router";
const active = ref(0)
const form = reactive({
email: '',
code: ''
email: '',
code: '',
password: '',
password_repeat: '',
})
const validatePassword = (rule, value, callback) => {
@ -113,6 +120,55 @@ const rules = {
],
}
const formRef = ref()
const isEmailValid = ref(false)
const coldTime = ref(0)
const onValidate = (prop, isValid) => {
if(prop === 'email')
isEmailValid.value = isValid
}
const validateEmail = () => {
post('/api/auth/valid-reset-email', {
email: form.email
}, (message) => {
ElMessage.success(message)
coldTime.value = 60
setInterval(() => coldTime.value--, 1000)
})
}
const startReset = () => {
formRef.value.validate((isValid) => {
if(isValid) {
post('/api/auth/start-reset', {
email: form.email,
code: form.code
}, () => {
active.value++
})
} else {
ElMessage.warning('请填写电子邮件地址和验证码')
}
})
}
const doReset = () => {
formRef.value.validate((isValid) => {
if(isValid) {
post('/api/auth/do-reset', {
password: form.password
}, (message) => {
ElMessage.success(message)
router.push('/')
})
} else {
ElMessage.warning('请填写新的密码')
}
})
}
</script>
<style scoped>

View File

@ -147,7 +147,7 @@ const register = () => {
}
const validateEmail = () => {
post('/api/auth/valid-email', {
post('/api/auth/valid-register-email', {
email: form.email
}, (message) => {
ElMessage.success(message)