重置密码
本文档详细介绍了系统中重置密码功能的接口设计、流程说明及代码实现。该功能主要分为两个步骤:
- 获取验证码:用户输入邮箱后,系统向该邮箱发送验证码;
- 重置密码:用户将邮箱、验证码及新密码提交到后端,后端验证验证码有效后更新密码。
1. 重置密码流程
重置密码的流程主要分为以下两个步骤:
输入邮箱获取验证码
用户通过提交邮箱地址,调用接口获取验证码,验证码将发送至指定邮箱。提交邮箱、验证码、密码到后端
用户将接收到的验证码、邮箱及新密码一起提交到后端接口,后端对验证码进行验证,验证成功后更新数据库中的用户密码。
2. 相关接口参数
在重置密码功能中,主要涉及两个接口:
2.1 发送验证码接口
调用该接口用于向指定邮箱发送验证码。
POST /api/v1/sendVerificationCode
{
"email":"litongjava001@gmail.com"
}
2.2 重置密码接口
调用该接口提交邮箱、验证码和新密码,完成密码重置。
POST /api/v1/user/resetPassword
{
"email":"litongjava001@gmail.com",
"password":"Litong@2516",
"code":"108503"
}
文档中还提供了收到的邮件示例,邮件内容如下:
Dear User,
Your code is:282531
If you did not request this verification, please disregard this email.
Best regards,
The College Bot AI Team
3. 代码实现
以下部分代码展示了整个重置密码功能的实现细节,包括发送验证码、验证验证码以及更新密码等步骤。请注意,以下代码均未做修改,仅增加了解释说明。
3.1 邮箱验证码发送接口
在代码中定义了一个 EmailRequest
类,用于接收邮箱地址的请求数据。
package com.litongjava.tio.boot.admin.vo;
public class EmailRequest {
private String email;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
独立提供的接口主要用于向指定邮箱发送验证码,客户端可以主动调用该接口(例如“重新发送验证码”)。
package com.litongjava.tio.boot.admin.handler;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.tio.boot.admin.services.AppEmailService;
import com.litongjava.tio.boot.admin.vo.EmailRequest;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.util.CORSUtils;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.tio.utils.json.JsonUtils;
public class EmailVerificationHandler {
// 发送验证码邮件
public HttpResponse sendVerificationCode(HttpRequest request) {
HttpResponse response = TioRequestContext.getResponse();
String origin = request.getOrigin();
CORSUtils.enableCORS(response);
String body = request.getBodyString();
EmailRequest req = JsonUtils.parse(body, EmailRequest.class);
AppEmailService emailService = Aop.get(AppEmailService.class);
boolean sent = emailService.sendVerificationCodeEmail(req.getEmail(), origin);
if (sent) {
return response.setJson(RespBodyVo.ok());
}
return Resps.json(response, RespBodyVo.fail());
}
// 发送验证码邮件
public HttpResponse sendVerification(HttpRequest request) {
HttpResponse response = TioRequestContext.getResponse();
String origin = request.getOrigin();
CORSUtils.enableCORS(response);
String body = request.getBodyString();
EmailRequest req = JsonUtils.parse(body, EmailRequest.class);
AppEmailService emailService = Aop.get(AppEmailService.class);
boolean sent = emailService.sendVerificationEmail(req.getEmail(), origin);
if (sent) {
return response.setJson(RespBodyVo.ok());
}
return Resps.json(response, RespBodyVo.fail());
}
// 验证邮箱验证码
public HttpResponse verifyEmail(HttpRequest request) {
HttpResponse response = TioRequestContext.getResponse();
CORSUtils.enableCORS(response);
String email = request.getParameter("email");
String code = request.getParameter("code");
AppEmailService emailService = Aop.get(AppEmailService.class);
boolean verified = emailService.verifyEmailCode(email, code);
if (verified) {
return Resps.json(response, RespBodyVo.ok());
}
return Resps.json(response, RespBodyVo.fail());
}
}
3.1.2 邮件服务(AppEmailService)
AppEmailService
类负责生成验证码、保存验证码记录、发送邮件以及验证验证码是否正确且未过期。下面首先介绍验证码实体对象 EmailVerification
:
package com.litongjava.tio.boot.admin.vo;
import java.sql.Timestamp;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmailVerification {
private int id;
private String email;
private String verificationCode;
private Timestamp createTime;
private Timestamp expireTime;
private boolean verified;
}
接下来是 AppEmailService
类的主要实现代码:
package com.litongjava.tio.boot.admin.services;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import com.litongjava.db.activerecord.Db;
import com.litongjava.tio.boot.admin.vo.EmailVerification;
import com.litongjava.tio.boot.email.EmailSender;
import com.litongjava.tio.boot.server.TioBootServer;
public class AppEmailService {
// 发送验证邮件,并在数据库保存验证码记录
public boolean sendVerificationEmail(String email, String origin) {
// 生成验证码(例如 6 位数字或随机字符串)
String code = String.valueOf((int) (Math.random() * 900000 + 100000));
// 设置过期时间(例如 15 分钟后)
LocalDateTime expireTime = LocalDateTime.now().plusMinutes(15);
OffsetDateTime atOffset = expireTime.atOffset(ZoneOffset.UTC);
// 保存验证码记录
String insertSql = "INSERT INTO app_email_verification (email, verification_code, expire_time) VALUES (?,?,?)";
int rows = Db.updateBySql(insertSql, email, code, atOffset);
if (rows > 0) {
EmailSender emailSender = TioBootServer.me().getEmailSender();
if (emailSender != null) {
//
return emailSender.sendVerificationEmail(email, origin, code);
}
}
return false;
}
public boolean sendVerificationCodeEmail(String email, String origin) {
// 生成验证码(例如 6 位数字或随机字符串)
String code = String.valueOf((int) (Math.random() * 900000 + 100000));
// 设置过期时间(例如 15 分钟后)
LocalDateTime expireTime = LocalDateTime.now().plusMinutes(15);
OffsetDateTime atOffset = expireTime.atOffset(ZoneOffset.UTC);
// 保存验证码记录
String insertSql = "INSERT INTO app_email_verification (email, verification_code, expire_time) VALUES (?,?,?)";
int rows = Db.updateBySql(insertSql, email, code, atOffset);
if (rows > 0) {
EmailSender emailSender = TioBootServer.me().getEmailSender();
if (emailSender != null) {
//
return emailSender.sendVerificationCodeEmail(email, origin, code);
}
}
return false;
}
//验证邮箱验证码是否正确且未过期
public boolean verifyEmailCode(String email, String code) {
String sql = "SELECT * FROM app_email_verification WHERE email=? AND verification_code=? AND verified=FALSE AND expire_time > CURRENT_TIMESTAMP";
EmailVerification ev = Db.findFirst(EmailVerification.class, sql, email, code);
if (ev != null) {
// 更新记录状态为已验证
String updateSql = "UPDATE app_email_verification SET verified=TRUE WHERE id=?";
Db.updateBySql(updateSql, ev.getId());
// 同时更新用户表中 email_verified 字段(假设用户 id 与 email 相同)
String updateUser = "UPDATE app_users SET email_verified=TRUE WHERE email=?";
Db.updateBySql(updateUser, email);
return true;
}
return false;
}
}
在上面的代码中,验证码生成采用随机生成 6 位数字,同时设置验证码有效期为 15 分钟;在验证码验证成功后,不仅更新验证码的状态,还更新用户表中的 email_verified
字段,确保邮箱验证的一致性。
3.1.3 邮件发送实现(MyGetEmailSender)
MyGetEmailSender
类实现了 EmailSender
接口,负责调用实际的邮件发送工具(例如 LarkSuit 或 Mailjet)发送邮件。这里同时提供了发送验证邮件和验证码邮件的两种实现方式。
package com.litongjava.myget.email;
import com.jfinal.kit.Kv;
import com.jfinal.template.Template;
import com.litongjava.myget.utils.MailJetUtils;
import com.litongjava.template.EmailEngine;
import com.litongjava.tio.boot.admin.mail.LarkSuitEmailUtils;
import com.litongjava.tio.boot.email.EmailSender;
import com.mailjet.client.MailjetResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MyGetEmailSender implements EmailSender {
@Override
public boolean send(String to, String subject, String content) {
return false;
}
@Override
public boolean sendVerificationEmail(String email, String origin, String code) {
Template template = EmailEngine.getTemplate("register_mail.txt");
String link = origin + "/verification/email?email=%s&code=%s";
link = String.format(link, email, code);
String content = template.renderToString(Kv.by("link", link));
// 从邮箱地址中提取用户名作为收件人姓名
String name = email.split("@")[0];
String subject = "College Bot AI Email Verification";
// 发送 HTML 格式的邀请邮件
return sendWithLark(name, email, subject, content);
//return sendWithMailjet(name, email, subject,content);
}
private boolean sendWithLark(String name, String email, String subject, String content) {
try {
LarkSuitEmailUtils.send(email, subject, content);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
private boolean sendWithMailjet(String name, String email, String subject, String content) {
try {
MailjetResponse response = MailJetUtils.sendText(name, email, subject, content);
int status = response.getStatus();
if (status == 200) {
return true;
} else {
log.error(response.getRawResponseContent());
return false;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean sendVerificationCodeEmail(String email, String origin, String code) {
Template template = EmailEngine.getTemplate("verification_code_mail.txt");
String content = template.renderToString(Kv.by("code", code));
// 从邮箱地址中提取用户名作为收件人姓名
String name = email.split("@")[0];
String subject = "College Bot AI Email Verification";
// 发送 HTML 格式的邀请邮件
return sendWithLark(name, email, subject, content);
}
}
3.1.4 邮件模板(verification_code_mail.txt)
邮件模板文件内容如下,该模板用于发送验证码邮件。模板中使用占位符 #(code)
来插入实际验证码。
Dear User,
Your code is:#(code)
If you did not request this verification, please disregard this email.
Best regards,
The College Bot AI Team
3.1.5 接口注册
最后,通过路由将发送验证码接口注册到 /api/v1/sendVerificationCode
路径上。
r.add("/api/v1/sendVerificationCode", emailVerificationHandler::sendVerificationCode);
3.2 重置密码
重置密码部分涉及到接收用户的请求数据、验证验证码、验证邮箱和密码格式,并更新用户密码。
3.2.1 请求对象 UserResetPasswordRequest
该对象用于封装重置密码请求的数据,包括邮箱、密码和验证码。
package com.litongjava.tio.boot.admin.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserResetPasswordRequest {
private String email,password;
private String code;
}
3.2.2 重置密码 Handler
在 resetPassword
方法中,将请求数据解析为 UserResetPasswordRequest
对象,并调用 AppUserService
进行密码重置处理。
public HttpResponse resetPassword(HttpRequest request) {
HttpResponse response = TioRequestContext.getResponse();
String bodyString = request.getBodyString();
UserResetPasswordRequest userResetPassword = JsonUtils.parse(bodyString, UserResetPasswordRequest.class);
AppUserService appUserService = Aop.get(AppUserService.class);
RespBodyVo vo = appUserService.resetPassword(userResetPassword);
return response.setJson(vo);
}
3.2.3 AppUserService 中的重置密码实现
在 AppUserService
类中,resetPassword
方法主要完成以下步骤:
- 数据验证:验证邮箱格式和密码格式是否正确;若不符合要求,返回失败信息。
- 用户存在性判断:检查指定邮箱的用户是否存在(示例代码中有关于邮箱重复的判断)。
- 验证码验证:调用
AppEmailService.verifyEmailCode
方法验证验证码是否正确且未过期。 - 密码更新:生成密码盐和密码哈希值(采用 SHA-256 算法),更新数据库中对应的用户密码信息。
package com.litongjava.tio.boot.admin.services;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.codec.digest.DigestUtils;
import com.litongjava.db.activerecord.Db;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.model.validate.ValidateResult;
import com.litongjava.tio.boot.admin.costants.AppConstant;
import com.litongjava.tio.boot.admin.costants.TioBootAdminTableNames;
import com.litongjava.tio.boot.admin.vo.AppUser;
import com.litongjava.tio.boot.admin.vo.UserResetPasswordRequest;
import com.litongjava.tio.utils.environment.EnvUtils;
import com.litongjava.tio.utils.jwt.JwtUtils;
import com.litongjava.tio.utils.snowflake.SnowflakeIdUtils;
import com.litongjava.tio.utils.validator.EmailValidator;
import com.litongjava.tio.utils.validator.PasswordValidator;
public RespBodyVo resetPassword(UserResetPasswordRequest req) {
List<ValidateResult> validateResults = new ArrayList<>();
boolean ok = true;
String email = req.getEmail();
String code = req.getCode();
boolean validate = EmailValidator.validate(email);
if (!validate) {
ValidateResult validateResult = ValidateResult.by("eamil", "Failed to valiate email:" + email);
validateResults.add(validateResult);
ok = false;
}
String password = req.getPassword();
validate = PasswordValidator.validate(password);
if (!validate) {
ValidateResult validateResult = ValidateResult.by("password", "Failed to valiate password:" + password);
validateResults.add(validateResult);
ok = false;
}
if (!ok) {
return RespBodyVo.failData(validateResults);
}
boolean exists = Db.exists(TioBootAdminTableNames.app_users, "email", email);
if (exists) {
ValidateResult validateResult = ValidateResult.by("eamil", "Eamil already taken" + email);
validateResults.add(validateResult);
}
if (!ok) {
return RespBodyVo.failData(validateResults);
}
boolean verify = Aop.get(AppEmailService.class).verifyEmailCode(email, code);
if (!verify) {
return RespBodyVo.fail("Failed to verify code");
}
// 生成加盐字符串(示例中直接使用随机数,实际可使用更复杂逻辑)
String salt = String.valueOf(System.currentTimeMillis());
// 生成密码哈希(密码+盐)
String passwordHash = DigestUtils.sha256Hex(password + salt);
String updateSql = "update app_users set password_salt=?, password_hash=? where email=?";
Db.updateBySql(updateSql, salt, passwordHash, email);
return RespBodyVo.ok();
}
3.2.4 接口注册
将重置密码接口注册到 /api/v1/user/resetPassword
路径上,确保前端调用时能够正确访问到该功能。
r.add("/api/v1/user/resetPassword", appUserHandler::resetPassword);
总结
本文档对重置密码功能的实现进行了详细介绍,包含以下几个方面:
- 流程说明:从获取验证码到重置密码的整体流程说明;
- 接口说明:详细描述了发送验证码和重置密码的请求参数;
- 代码实现:分别展示了邮箱验证码发送、邮件服务实现、验证码验证以及用户密码更新的具体代码;
- 技术要点:包括验证码生成、有效期控制、密码盐及哈希计算、数据验证等关键技术实现点。
通过本文档,开发者可以全面了解并掌握重置密码功能的实现逻辑,为后续系统维护和功能扩展提供了有力的支持。