「Springboot」发送邮件、重置密码业务实战

关注我的微信公众号:后端技术漫谈

不定期推送关于后端开发、爬虫、算法题、数据结构方面的原创技术文章,以及生活中的逸闻趣事。

我目前是一名后端开发工程师。主要关注后端开发,数据安全,网络爬虫,物联网,边缘计算等方向。

原创博客主要内容

前言

忘记密码并通过邮件重置密码是一个常见的业务需求,在开发我的个人小项目过程中,也需要用到这个业务,今天就给大家带来一个业务实战。

开发环境

业务流程

根据controller中函数分为两个部分:

  1. 用户申请重置邮件:
  1. 用户重置密码

实战

  1. pom.xml添加email依赖


 org.springframework.boot
 spring-boot-starter-mail

  1. 添加pm_validate表结构

其中reset_token由UUID生成,type默认为resetPassword(方便以后新增需求),user_id为用户表用户id

-- ----------------------------
-- Table structure for pm_validate
-- ----------------------------
DROP TABLE IF EXISTS `pm_validate`;
CREATE TABLE `pm_validate` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `user_id` int(11) NOT NULL,
 `email` varchar(40) NOT NULL,
 `reset_token` varchar(40) NOT NULL,
 `type` varchar(20) NOT NULL,
 `gmt_create` datetime DEFAULT NULL,
 `gmt_modified` datetime DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

生成或编写对应pojo和mapper。,由于我使用了mybatis-generator插件,需要运行插件生成对应pojo和mapper。

  1. 修改application.properties,添加邮箱配置
# 发送邮件配置
spring.mail.host=smtp.gmail.com
spring.mail.username=xxxxxx@gmail.com
spring.mail.password=xxxxxxx
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
  1. 编写controller和service
@RestController
@RequestMapping(value = "/validate")
public class ValidateController {

 @Autowired
 private ValidateService validateService;

 @Autowired
 private UserService userService;

 @Value("${spring.mail.username}")
 private String from;

 /**
 * 发送忘记密码邮件请求,每日申请次数不超过5次,每次申请间隔不低于1分钟
 * @param email
 * @param request
 * @return
 */
 @ApiOperation(value = "发送忘记密码邮件", notes = "发送忘记密码邮件")
 @RequestMapping(value = "/sendValidationEmail", method = {RequestMethod.POST})
 public ResponseData sendValidationEmail(@ApiParam("邮箱地址") @RequestParam("email") String email,
 HttpServletRequest request){
 ResponseData responseData = new ResponseData<>();
 List users = userService.findUserByEmail(email);
 if (users == null){
 responseData.jsonFill(2, "该邮箱所属用户不存在", null);
 }else {
 if (validateService.sendValidateLimitation(email, 5,1)){
 // 若允许重置密码,则在pm_validate表中插入一行数据,带有token
 Validate validate = new Validate();
 validateService.insertNewResetRecord(validate, users.get(0), UUID.randomUUID().toString());
 // 设置邮件内容
 String appUrl = request.getScheme() + "://" + request.getServerName();
 SimpleMailMessage passwordResetEmail = new SimpleMailMessage();
 passwordResetEmail.setFrom(from);
 passwordResetEmail.setTo(email);
 passwordResetEmail.setSubject("【电商价格监控】忘记密码");
 passwordResetEmail.setText("您正在申请重置密码,请点击此链接重置密码: 
" + appUrl + "/validate/reset?token=" + validate.getResetToken());
 validateService.sendPasswordResetEmail(passwordResetEmail);
 responseData.jsonFill(1, null, null);
 }else {
 responseData.jsonFill(2,"操作过于频繁,请稍后再试!",null);
 }
 }
 return responseData;
 }

 /**
 * 将url的token和数据库里的token匹配,成功后便可修改密码,token有效期为60分钟
 * @param token
 * @param password
 * @param confirmPassword
 * @return
 */
 @ApiOperation(value = "重置密码", notes = "重置密码")
 @RequestMapping(value = "/resetPassword", method = RequestMethod.POST)
 public ResponseData resetPassword(@ApiParam("token") @RequestParam("token") String token,
 @ApiParam("密码") @RequestParam("password") String password,
 @ApiParam("密码确认") @RequestParam("confirmPassword") String confirmPassword){
 ResponseData responseData = new ResponseData<>();
 // 通过token找到validate记录
 List validates = validateService.findUserByResetToken(token);
 if (validates == null){
 responseData.jsonFill(2,"该重置请求不存在",null);
 }else {
 Validate validate = validates.get(0);
 if (validateService.validateLimitation(validate.getEmail(), Long.MAX_VALUE, 60, token)){
 Integer userId = validate.getUserId();
 if (password.equals(confirmPassword)) {
 userService.updatePassword(password, userId);
 responseData.jsonFill(1, null,null);
 }else {
 responseData.jsonFill(2,"确认密码和密码不一致,请重新输入", null);
 }
 }else {
 responseData.jsonFill(2,"该链接失效",null);
 }
 }
 return responseData;
 }
}
public interface ValidateService {
 void sendPasswordResetEmail(SimpleMailMessage email);
 int insertNewResetRecord(Validate validate, User users, String token);
 List findUserByResetToken(String resetToken);
 boolean validateLimitation(String email, long requestPerDay, long interval, String token);
 boolean sendValidateLimitation(String email, long requestPerDay, long interval);
}
@Service
public class ValidateServiceImpl implements ValidateService {

 @Autowired
 private JavaMailSender javaMailSender;

 @Autowired
 private ValidateMapper validateMapper;

 /**
 * 发送邮件:@Async进行异步调用发送邮件接口
 * @param email
 */
 @Override
 @Async
 public void sendPasswordResetEmail(SimpleMailMessage email){
 javaMailSender.send(email);
 }

 /**
 * 在pm_validate表中插入一条validate记录,userid,email属性来自pm_user表,token由UUID生成
 * @param validate
 * @param users
 * @param token
 * @return
 */
 @Override
 public int insertNewResetRecord(Validate validate, User users, String token){
 validate.setUserId(users.getId());
 validate.setEmail(users.getEmail());
 validate.setResetToken(token);
 validate.setType("passwordReset");
 validate.setGmtCreate(new Date());
 validate.setGmtModified(new Date());
 return validateMapper.insert(validate);
 }

 /**
 * pm_validate表中,通过token查找重置申请记录
 * @param token
 * @return
 */
 @Override
 public List findUserByResetToken(String token){
 ValidateExample validateExample = new ValidateExample();
 ValidateExample.Criteria criteria = validateExample.createCriteria();
 criteria.andResetTokenEqualTo(token);
 return validateMapper.selectByExample(validateExample);
 }

 /**
 * 验证是否发送重置邮件:每个email的重置密码每日请求上限为requestPerDay次,与上一次的请求时间间隔为interval分钟。
 * @param email
 * @param requestPerDay
 * @param interval
 * @return
 */
 @Override
 public boolean sendValidateLimitation(String email, long requestPerDay, long interval){
 ValidateExample validateExample = new ValidateExample();
 ValidateExample.Criteria criteria= validateExample.createCriteria();
 criteria.andEmailEqualTo(email);
 List validates = validateMapper.selectByExample(validateExample);
 // 若查无记录,意味着第一次申请,直接放行
 if (validates.isEmpty()) {
 return true;
 }
 // 有记录,则判定是否频繁申请以及是否达到日均请求上线
 long countTodayValidation = validates.stream().filter(x->DateUtils.isSameDay(x.getGmtModified(), new Date())).count();
 Optional validate = validates.stream().map(Validate::getGmtModified).max(Date::compareTo);
 Date dateOfLastRequest = new Date();
 if (validate.isPresent()) dateOfLastRequest = (Date) validate.get();
 long intervalForLastRequest = new Date().getTime() - dateOfLastRequest.getTime();

 return countTodayValidation <= requestPerDay && intervalForLastRequest >= interval * 60 * 1000;
 }

 /**
 * 验证连接是否失效:链接有两种情况失效 1.超时 2.最近请求的一次链接自动覆盖之前的链接(待看代码)
 * @param email
 * @param requestPerDay
 * @param interval
 * @return
 */
 @Override
 public boolean validateLimitation(String email, long requestPerDay, long interval, String token){
 ValidateExample validateExample = new ValidateExample();
 ValidateExample.Criteria criteria= validateExample.createCriteria();
 criteria.andEmailEqualTo(email);
 List validates = validateMapper.selectByExample(validateExample);
 // 有记录才会调用该函数,只需判断是否超时
 Optional validate = validates.stream().map(Validate::getGmtModified).max(Date::compareTo);
 Date dateOfLastRequest = new Date();
 if (validate.isPresent()) dateOfLastRequest = (Date) validate.get();
 long intervalForLastRequest = new Date().getTime() - dateOfLastRequest.getTime();

 Optional lastRequestToken = validates.stream().filter(x-> x.getResetToken().equals(token)).map(Validate::getGmtModified).findAny();
 Date dateOfLastRequestToken = new Date();
 if (lastRequestToken.isPresent()) {
 dateOfLastRequestToken = (Date) lastRequestToken.get();
 }
 return intervalForLastRequest <= interval * 60 * 1000 && dateOfLastRequest == dateOfLastRequestToken;
 }
}

结语

如上实现了整个重置密码流程,前端网页自行设计实现。

关注我

我是蛮三刀把刀,目前为后台开发工程师。主要关注后台开发,网络安全,Python爬虫等技术。

来微信和我聊聊:yangzd1102

Github:https://github.com/qqxx6661

原创博客主要内容

同步更新以下博客

1. Csdn

http://blog.csdn.net/qqxx6661

拥有专栏:Leetcode题解(Java/Python)、Python爬虫开发、面试助攻手册

2. 知乎

https://www.zhihu.com/people/yang-zhen-dong-1/

拥有专栏:码农面试助攻手册

3. 掘金

https://juejin.im/user/5b48015ce51d45191462ba55

4. 简书

https://www.jianshu.com/u/b5f225ca2376

个人公众号:后端数据漫谈

展开阅读全文

页面更新:2024-03-08

标签:实战   密码   菜鸟   爬虫   题解   技术文章   算法   频繁   后台   邮箱   邮件   手册   链接   业务   数据   数码   用户

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top