Commit f7faa07282032d0f32ad6d0d89f8efef687cc1b1

Authored by 云中非
1 parent df7dae58

feat: 密码找回

1、密码找回短信验证码接口
2、密码找回修改密码接口
1 package org.thingsboard.server.controller.yunteng; 1 package org.thingsboard.server.controller.yunteng;
2 2
  3 +import io.swagger.annotations.ApiOperation;
3 import lombok.RequiredArgsConstructor; 4 import lombok.RequiredArgsConstructor;
4 import org.springframework.util.Assert; 5 import org.springframework.util.Assert;
5 -import org.springframework.web.bind.annotation.PathVariable;  
6 -import org.springframework.web.bind.annotation.PostMapping;  
7 -import org.springframework.web.bind.annotation.RequestMapping;  
8 -import org.springframework.web.bind.annotation.RestController; 6 +import org.springframework.web.bind.annotation.*;
  7 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  8 +import org.thingsboard.server.common.data.yunteng.dto.request.AccountReqDTO;
  9 +import org.thingsboard.server.common.data.yunteng.dto.request.SendResetPasswordEmailMsg;
  10 +import org.thingsboard.server.common.data.yunteng.enums.MsgTemplatePurposeEnum;
9 import org.thingsboard.server.dao.yunteng.service.YtSmsService; 11 import org.thingsboard.server.dao.yunteng.service.YtSmsService;
  12 +import org.thingsboard.server.dao.yunteng.service.YtUserService;
10 13
11 import static org.thingsboard.server.common.data.yunteng.constant.FastIotConstants.CHINA_MOBILE_PATTERN; 14 import static org.thingsboard.server.common.data.yunteng.constant.FastIotConstants.CHINA_MOBILE_PATTERN;
12 15
@@ -16,11 +19,24 @@ import static org.thingsboard.server.common.data.yunteng.constant.FastIotConstan @@ -16,11 +19,24 @@ import static org.thingsboard.server.common.data.yunteng.constant.FastIotConstan
16 public class YtNoAuthController { 19 public class YtNoAuthController {
17 20
18 private final YtSmsService smsService; 21 private final YtSmsService smsService;
  22 + private final YtUserService userService;
19 23
20 @PostMapping("/sendLoginSmsCode/{phoneNumber}") 24 @PostMapping("/sendLoginSmsCode/{phoneNumber}")
21 public boolean sendVerificationCode(@PathVariable("phoneNumber") String phoneNumber) { 25 public boolean sendVerificationCode(@PathVariable("phoneNumber") String phoneNumber) {
22 Assert.isTrue( 26 Assert.isTrue(
23 CHINA_MOBILE_PATTERN.matcher(phoneNumber).matches(), "please input correct phone number"); 27 CHINA_MOBILE_PATTERN.matcher(phoneNumber).matches(), "please input correct phone number");
24 - return smsService.sendLoginSmsCode(phoneNumber); 28 + return smsService.sendSmsCode(phoneNumber, MsgTemplatePurposeEnum.FOR_LOGIN);
  29 + }
  30 +
  31 + @PostMapping("/reset/{userId}")
  32 + @ApiOperation("密码找回")
  33 + public void saveForgetPassword(@PathVariable("phoneNumber") String phoneNumber,@RequestBody AccountReqDTO forget) throws ThingsboardException {
  34 + userService.forgetPassword(phoneNumber,forget);
  35 + }
  36 +
  37 + @PostMapping("/resetCode/{phoneNumber}")
  38 + @ApiOperation("密码找回短信验证码")
  39 + public void forgetPasswordCode(@PathVariable("phoneNumber") String phoneNumber) {
  40 + smsService.sendSmsCode(phoneNumber, MsgTemplatePurposeEnum.FOR_FORGET_PASSWORD);
25 } 41 }
26 } 42 }
@@ -10,6 +10,7 @@ import org.thingsboard.server.dao.yunteng.service.YtUserService; @@ -10,6 +10,7 @@ import org.thingsboard.server.dao.yunteng.service.YtUserService;
10 @RestController 10 @RestController
11 @RequestMapping("/api/yt/tenant") 11 @RequestMapping("/api/yt/tenant")
12 @RequiredArgsConstructor 12 @RequiredArgsConstructor
  13 +@Deprecated
13 public class YtTenantController extends BaseController { 14 public class YtTenantController extends BaseController {
14 15
15 private final YtUserService userService; 16 private final YtUserService userService;
@@ -106,9 +106,10 @@ public class YtSmsServiceImpl implements YtSmsService { @@ -106,9 +106,10 @@ public class YtSmsServiceImpl implements YtSmsService {
106 return false; 106 return false;
107 } 107 }
108 108
  109 +
109 @Override 110 @Override
110 @Transactional 111 @Transactional
111 - public boolean sendLoginSmsCode(String phoneNumber) { 112 + public boolean sendSmsCode(String phoneNumber,MsgTemplatePurposeEnum purpose) {
112 // 检查手机号码是否存在系统,以免乱发消息 113 // 检查手机号码是否存在系统,以免乱发消息
113 if (userMapper 114 if (userMapper
114 .selectList(new QueryWrapper<User>().lambda().eq(User::getPhoneNumber, phoneNumber)) 115 .selectList(new QueryWrapper<User>().lambda().eq(User::getPhoneNumber, phoneNumber))
@@ -117,7 +118,7 @@ public class YtSmsServiceImpl implements YtSmsService { @@ -117,7 +118,7 @@ public class YtSmsServiceImpl implements YtSmsService {
117 } 118 }
118 // 获取是否有验证码存在,防止发送数量过多 119 // 获取是否有验证码存在,防止发送数量过多
119 String key = 120 String key =
120 - MsgTemplatePurposeEnum.FOR_LOGIN.name() 121 + purpose.name()
121 + DEFAULT_DELIMITER 122 + DEFAULT_DELIMITER
122 + MessageTypeEnum.PHONE_MESSAGE.name() 123 + MessageTypeEnum.PHONE_MESSAGE.name()
123 + DEFAULT_DELIMITER 124 + DEFAULT_DELIMITER
@@ -139,7 +140,7 @@ public class YtSmsServiceImpl implements YtSmsService { @@ -139,7 +140,7 @@ public class YtSmsServiceImpl implements YtSmsService {
139 messageTemplateMapper.selectList( 140 messageTemplateMapper.selectList(
140 new QueryWrapper<MessageTemplate>() 141 new QueryWrapper<MessageTemplate>()
141 .lambda() 142 .lambda()
142 - .eq(MessageTemplate::getTemplatePurpose, MsgTemplatePurposeEnum.FOR_LOGIN.name()) 143 + .eq(MessageTemplate::getTemplatePurpose, purpose.name())
143 .eq(MessageTemplate::getMessageType, MessageTypeEnum.PHONE_MESSAGE.name())); 144 .eq(MessageTemplate::getMessageType, MessageTypeEnum.PHONE_MESSAGE.name()));
144 if (messageTemplates.isEmpty()) { 145 if (messageTemplates.isEmpty()) {
145 throw new YtDataValidationException("no sms provider config"); 146 throw new YtDataValidationException("no sms provider config");
@@ -152,7 +153,7 @@ public class YtSmsServiceImpl implements YtSmsService { @@ -152,7 +153,7 @@ public class YtSmsServiceImpl implements YtSmsService {
152 smsReqDTO.setParams(params); 153 smsReqDTO.setParams(params);
153 smsReqDTO.setPhoneNumbers(phoneNumber); 154 smsReqDTO.setPhoneNumbers(phoneNumber);
154 smsReqDTO.setId(messageTemplate.getId()); 155 smsReqDTO.setId(messageTemplate.getId());
155 - smsReqDTO.setTemplatePurpose(MsgTemplatePurposeEnum.FOR_LOGIN.name()); 156 + smsReqDTO.setTemplatePurpose(purpose.name());
156 if (this.sendSms(smsReqDTO)) { 157 if (this.sendSms(smsReqDTO)) {
157 cacheUtils.put(MOBILE_LOGIN_SMS_CODE, key, new CodeTTL(code, System.currentTimeMillis())); 158 cacheUtils.put(MOBILE_LOGIN_SMS_CODE, key, new CodeTTL(code, System.currentTimeMillis()));
158 return true; 159 return true;
@@ -11,28 +11,34 @@ import lombok.RequiredArgsConstructor; @@ -11,28 +11,34 @@ import lombok.RequiredArgsConstructor;
11 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
12 import org.apache.commons.lang3.RandomStringUtils; 12 import org.apache.commons.lang3.RandomStringUtils;
13 import org.apache.commons.lang3.StringUtils; 13 import org.apache.commons.lang3.StringUtils;
  14 +import org.springframework.context.ApplicationEventPublisher;
14 import org.springframework.scheduling.annotation.Async; 15 import org.springframework.scheduling.annotation.Async;
15 import org.springframework.security.access.AccessDeniedException; 16 import org.springframework.security.access.AccessDeniedException;
  17 +import org.springframework.security.authentication.BadCredentialsException;
16 import org.springframework.security.crypto.password.PasswordEncoder; 18 import org.springframework.security.crypto.password.PasswordEncoder;
17 import org.springframework.stereotype.Service; 19 import org.springframework.stereotype.Service;
18 import org.springframework.transaction.annotation.Transactional; 20 import org.springframework.transaction.annotation.Transactional;
  21 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
19 import org.thingsboard.server.common.data.id.EntityId; 22 import org.thingsboard.server.common.data.id.EntityId;
  23 +import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.id.UserId;
20 import org.thingsboard.server.common.data.query.TsValue; 25 import org.thingsboard.server.common.data.query.TsValue;
  26 +import org.thingsboard.server.common.data.security.UserCredentials;
  27 +import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
21 import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; 28 import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
22 import org.thingsboard.server.common.data.yunteng.constant.ModelConstants; 29 import org.thingsboard.server.common.data.yunteng.constant.ModelConstants;
  30 +import org.thingsboard.server.common.data.yunteng.core.cache.CacheUtils;
23 import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException; 31 import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException;
24 import org.thingsboard.server.common.data.yunteng.core.exception.NoneTenantAssetException; 32 import org.thingsboard.server.common.data.yunteng.core.exception.NoneTenantAssetException;
25 import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; 33 import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
26 import org.thingsboard.server.common.data.yunteng.dto.*; 34 import org.thingsboard.server.common.data.yunteng.dto.*;
27 -import org.thingsboard.server.common.data.yunteng.dto.request.AccountReqDTO;  
28 -import org.thingsboard.server.common.data.yunteng.dto.request.RoleOrOrganizationReqDTO;  
29 -import org.thingsboard.server.common.data.yunteng.dto.request.SendResetPasswordEmailMsg;  
30 -import org.thingsboard.server.common.data.yunteng.dto.request.SmsReqDTO; 35 +import org.thingsboard.server.common.data.yunteng.dto.request.*;
31 import org.thingsboard.server.common.data.yunteng.enums.MessageTypeEnum; 36 import org.thingsboard.server.common.data.yunteng.enums.MessageTypeEnum;
32 import org.thingsboard.server.common.data.yunteng.enums.MsgTemplatePurposeEnum; 37 import org.thingsboard.server.common.data.yunteng.enums.MsgTemplatePurposeEnum;
33 import org.thingsboard.server.common.data.yunteng.enums.UserStatusEnum; 38 import org.thingsboard.server.common.data.yunteng.enums.UserStatusEnum;
34 import org.thingsboard.server.common.data.yunteng.utils.ReflectUtils; 39 import org.thingsboard.server.common.data.yunteng.utils.ReflectUtils;
35 import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData; 40 import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData;
  41 +import org.thingsboard.server.dao.user.UserService;
36 import org.thingsboard.server.dao.yunteng.entities.*; 42 import org.thingsboard.server.dao.yunteng.entities.*;
37 import org.thingsboard.server.dao.yunteng.mapper.*; 43 import org.thingsboard.server.dao.yunteng.mapper.*;
38 import org.thingsboard.server.dao.yunteng.service.*; 44 import org.thingsboard.server.dao.yunteng.service.*;
@@ -43,8 +49,9 @@ import java.util.*; @@ -43,8 +49,9 @@ import java.util.*;
43 import java.util.concurrent.CompletableFuture; 49 import java.util.concurrent.CompletableFuture;
44 import java.util.stream.Collectors; 50 import java.util.stream.Collectors;
45 51
46 -import static org.thingsboard.server.common.data.yunteng.constant.FastIotConstants.CHINA_MOBILE_PATTERN;  
47 -import static org.thingsboard.server.common.data.yunteng.constant.FastIotConstants.EMAIL_PATTERN; 52 +import static org.thingsboard.server.common.data.yunteng.constant.FastIotConstants.*;
  53 +import static org.thingsboard.server.common.data.yunteng.constant.FastIotConstants.CacheConfigKey.MOBILE_LOGIN_SMS_CODE;
  54 +import static org.thingsboard.server.common.data.yunteng.constant.FastIotConstants.DEFAULT_DELIMITER;
48 import static org.thingsboard.server.common.data.yunteng.constant.ModelConstants.TablePropertyMapping.*; 55 import static org.thingsboard.server.common.data.yunteng.constant.ModelConstants.TablePropertyMapping.*;
49 56
50 @Service 57 @Service
@@ -68,6 +75,10 @@ public class YtUserServiceImpl extends AbstractBaseService<UserMapper, User> @@ -68,6 +75,10 @@ public class YtUserServiceImpl extends AbstractBaseService<UserMapper, User>
68 public static final String ACTIVATE_URL_PATTERN = "%s/api/noauth/activate?activateToken=%s"; 75 public static final String ACTIVATE_URL_PATTERN = "%s/api/noauth/activate?activateToken=%s";
69 private final PasswordEncoder passwordEncoder; 76 private final PasswordEncoder passwordEncoder;
70 77
  78 + private CacheUtils cacheUtils;
  79 + private final UserService tbUserService;
  80 + private final ApplicationEventPublisher eventPublisher;
  81 +
71 @Override 82 @Override
72 public List<UserDetailsDTO> findUserDetailsByUsername(String username) { 83 public List<UserDetailsDTO> findUserDetailsByUsername(String username) {
73 // 多个租户可能存在多个username相同的情况 84 // 多个租户可能存在多个username相同的情况
@@ -427,6 +438,52 @@ public class YtUserServiceImpl extends AbstractBaseService<UserMapper, User> @@ -427,6 +438,52 @@ public class YtUserServiceImpl extends AbstractBaseService<UserMapper, User>
427 } 438 }
428 439
429 @Override 440 @Override
  441 + public void forgetPassword(String phoneNumber,AccountReqDTO forget) {
  442 + String key =
  443 + MsgTemplatePurposeEnum.FOR_FORGET_PASSWORD.name()
  444 + + DEFAULT_DELIMITER
  445 + + MessageTypeEnum.PHONE_MESSAGE.name()
  446 + + DEFAULT_DELIMITER
  447 + + phoneNumber;
  448 + boolean correct =
  449 + cacheUtils
  450 + .get(MOBILE_LOGIN_SMS_CODE, key)
  451 + .map(
  452 + o -> {
  453 + CodeTTL codeTTL = (CodeTTL) o;
  454 + if (System.currentTimeMillis() - codeTTL.getSendTs() < 5 * 60 * 1000) {
  455 + return Objects.equals(codeTTL.getCode(), forget.getUserId());
  456 + } else {
  457 + return false;
  458 + }
  459 + })
  460 + .orElse(false);
  461 + if (!correct) {
  462 + throw new BadCredentialsException("验证码不正确");
  463 + }
  464 + String pwd = forget.getPassword();
  465 + if (StringUtils.isEmpty(pwd)
  466 + || StringUtils.isEmpty(forget.getResetPassword())
  467 + || !pwd.equals(forget.getResetPassword())) {
  468 + throw new YtDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  469 + }
  470 +
  471 + User user = baseMapper.selectOne(new QueryWrapper<User>().lambda().eq(User::getPhoneNumber, phoneNumber));
  472 +
  473 + UserId userId = new UserId(UUID.fromString(user.getTbUser()));
  474 + UserCredentials userCredentials =
  475 + tbUserService.findUserCredentialsByUserId(TenantId.SYS_TENANT_ID, userId);
  476 +
  477 + String encodePwd = passwordEncoder.encode(pwd);
  478 + userCredentials.setPassword(encodePwd);
  479 + user.setPassword(encodePwd);
  480 + tbUserService.replaceUserCredentials(new TenantId(UUID.fromString(user.getTenantId())), userCredentials);
  481 + eventPublisher.publishEvent(new UserAuthDataChangedEvent(userId));
  482 +
  483 + changePassword(user);
  484 + }
  485 +
  486 + @Override
430 public List<UserDetailsDTO> getUserByPhoneNumber(String phoneNumber) { 487 public List<UserDetailsDTO> getUserByPhoneNumber(String phoneNumber) {
431 return baseMapper.findUserDetailsByPhoneNumber(phoneNumber); 488 return baseMapper.findUserDetailsByPhoneNumber(phoneNumber);
432 } 489 }
@@ -586,6 +643,11 @@ public class YtUserServiceImpl extends AbstractBaseService<UserMapper, User> @@ -586,6 +643,11 @@ public class YtUserServiceImpl extends AbstractBaseService<UserMapper, User>
586 @Override 643 @Override
587 public User validateChangePasswordAccount(AccountReqDTO accountReqDTO) { 644 public User validateChangePasswordAccount(AccountReqDTO accountReqDTO) {
588 User user = baseMapper.selectById(accountReqDTO.getUserId()); 645 User user = baseMapper.selectById(accountReqDTO.getUserId());
  646 + checkPassword(accountReqDTO, user);
  647 + return user;
  648 + }
  649 +
  650 + private void checkPassword(AccountReqDTO accountReqDTO, User user) {
589 if (null == user 651 if (null == user
590 || StringUtils.isEmpty(accountReqDTO.getPassword()) 652 || StringUtils.isEmpty(accountReqDTO.getPassword())
591 || StringUtils.isEmpty(accountReqDTO.getResetPassword())) { 653 || StringUtils.isEmpty(accountReqDTO.getResetPassword())) {
@@ -597,7 +659,6 @@ public class YtUserServiceImpl extends AbstractBaseService<UserMapper, User> @@ -597,7 +659,6 @@ public class YtUserServiceImpl extends AbstractBaseService<UserMapper, User>
597 throw new YtDataValidationException(ErrorMessage.USERNAME_PASSWORD_INCORRECT.getMessage()); 659 throw new YtDataValidationException(ErrorMessage.USERNAME_PASSWORD_INCORRECT.getMessage());
598 } 660 }
599 user.setPassword(accountReqDTO.getResetPassword()); 661 user.setPassword(accountReqDTO.getResetPassword());
600 - return user;  
601 } 662 }
602 663
603 /** 664 /**
@@ -2,9 +2,16 @@ package org.thingsboard.server.dao.yunteng.service; @@ -2,9 +2,16 @@ package org.thingsboard.server.dao.yunteng.service;
2 2
3 3
4 import org.thingsboard.server.common.data.yunteng.dto.request.SmsReqDTO; 4 import org.thingsboard.server.common.data.yunteng.dto.request.SmsReqDTO;
  5 +import org.thingsboard.server.common.data.yunteng.enums.MsgTemplatePurposeEnum;
5 6
6 public interface YtSmsService { 7 public interface YtSmsService {
7 boolean sendSms(SmsReqDTO smsReqDTO); 8 boolean sendSms(SmsReqDTO smsReqDTO);
8 9
9 - boolean sendLoginSmsCode(String phoneNumber); 10 + /**
  11 + * 推送短信验证码
  12 + * @param phoneNumber 手机号
  13 + * @param purpose 模板用途
  14 + * @return
  15 + */
  16 + boolean sendSmsCode(String phoneNumber, MsgTemplatePurposeEnum purpose);
10 } 17 }
@@ -42,6 +42,8 @@ public interface YtUserService { @@ -42,6 +42,8 @@ public interface YtUserService {
42 42
43 void resetPassword(String userId, boolean isPtSysadmin, String tenantId); 43 void resetPassword(String userId, boolean isPtSysadmin, String tenantId);
44 44
  45 + void forgetPassword(String phoneNumber,AccountReqDTO forget);
  46 +
45 List<UserDetailsDTO> getUserByPhoneNumber(String phoneNumber); 47 List<UserDetailsDTO> getUserByPhoneNumber(String phoneNumber);
46 48
47 void validateUserNameAndPhoneNumberAndEmail(UserDTO userDTO); 49 void validateUserNameAndPhoneNumberAndEmail(UserDTO userDTO);