[Springboot]發送郵件、重置密碼業務實戰

前言

忘記密碼並經過郵件重置密碼是一個常見的業務需求,在開發個人我的小項目過程當中,也須要用到這個業務,今天就給你們帶來一個業務實戰。前端

開發環境

  • springboot:1.5.16.RELEASE

業務流程

根據controller中函數分爲兩個部分:java

  1. 用戶申請重置郵件:
  • 用戶在頁面中輸入郵箱
  • 服務器檢查是否容許重置(郵箱所指向用戶是否存在,重置是否過於頻繁,重置是否到達日請求上限)
  • 驗證經過後,想validate表寫入申請記錄,包含token,用戶郵箱和id
  • 發送郵件(包含帶有token的連接)
  • 用戶點擊郵件內鏈接
  • 跳轉到新密碼輸入網頁
  • 提交重置密碼請求(POST中包含token,新密碼)
  1. 用戶重置密碼
  • 服務器驗證token(token是否過時,該用戶是否發起過其它新token)
  • 經過validate表記錄查找用戶id,修改用戶密碼

實戰

  1. pom.xml添加email依賴
<!--郵件: email-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
複製代碼
  1. 添加pm_validate表結構

其中reset_token由UUID生成,type默認爲resetPassword(方便之後新增需求),user_id爲用戶表用戶idgit

-- ----------------------------
-- 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。github

  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
  • ValidateController
@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<String> sendValidationEmail(@ApiParam("郵箱地址") @RequestParam("email") String email,
                                               HttpServletRequest request){
        ResponseData<String> responseData = new ResponseData<>();
        List<User> 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("您正在申請重置密碼,請點擊此連接重置密碼: \n" + 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<String> resetPassword(@ApiParam("token") @RequestParam("token") String token,
                                              @ApiParam("密碼") @RequestParam("password") String password,
                                              @ApiParam("密碼確認") @RequestParam("confirmPassword") String confirmPassword){
        ResponseData<String> responseData = new ResponseData<>();
        // 經過token找到validate記錄
        List<Validate> 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;
    }
}
複製代碼
  • ValidateService
public interface ValidateService {
    void sendPasswordResetEmail(SimpleMailMessage email);
    int insertNewResetRecord(Validate validate, User users, String token);
    List<Validate> findUserByResetToken(String resetToken);
    boolean validateLimitation(String email, long requestPerDay, long interval, String token);
    boolean sendValidateLimitation(String email, long requestPerDay, long interval);
}
複製代碼
  • ValidateServiceImpl
@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<Validate> 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<Validate> 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<Validate> 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爬蟲等技術。算法

來微信和我聊聊:yangzd1102spring

Github:github.com/qqxx6661數據庫

原創博客主要內容

  • 筆試面試複習知識點手冊
  • Leetcode算法題解析(前150題)
  • 劍指offer算法題解析
  • Python爬蟲相關技術分析和實戰
  • 後臺開發相關技術分析和實戰

同步更新如下博客json

1. Csdn安全

blog.csdn.net/qqxx6661

擁有專欄:Leetcode題解(Java/Python)、Python爬蟲開發、面試助攻手冊

2. 知乎

www.zhihu.com/people/yang…

擁有專欄:碼農面試助攻手冊

3. 掘金

juejin.im/user/5b4801…

4. 簡書

www.jianshu.com/u/b5f225ca2…

我的公衆號:Rude3Knife

我的公衆號:Rude3Knife

若是文章對你有幫助,不妨收藏起來並轉發給您的朋友們~

我的項目:電商價格監控網站

本人長期維護的我的項目,徹底免費,請你們多多支持。

實現功能

  • 京東商品監控:設置商品ID和預期價格,當商品價格【低於】設定的預期價格後自動發送郵件提醒用戶。(一小時之內)
  • 京東品類商品監控:用戶訂閱特定品類後,該類降價幅度大於7折的【自營商品】會被選出併發送郵件提醒用戶。
  • 品類商品瀏覽,商品歷史價格曲線,商品歷史最高最低價
  • 持續更新中...

網站地址

pricemonitor.online/

相關文章
相關標籤/搜索