Commit f7faa07282032d0f32ad6d0d89f8efef687cc1b1

Authored by 云中非
1 parent df7dae58

feat: 密码找回

1、密码找回短信验证码接口
2、密码找回修改密码接口
1 1 package org.thingsboard.server.controller.yunteng;
2 2
  3 +import io.swagger.annotations.ApiOperation;
3 4 import lombok.RequiredArgsConstructor;
4 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 11 import org.thingsboard.server.dao.yunteng.service.YtSmsService;
  12 +import org.thingsboard.server.dao.yunteng.service.YtUserService;
10 13
11 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 19 public class YtNoAuthController {
17 20
18 21 private final YtSmsService smsService;
  22 + private final YtUserService userService;
19 23
20 24 @PostMapping("/sendLoginSmsCode/{phoneNumber}")
21 25 public boolean sendVerificationCode(@PathVariable("phoneNumber") String phoneNumber) {
22 26 Assert.isTrue(
23 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 10 @RestController
11 11 @RequestMapping("/api/yt/tenant")
12 12 @RequiredArgsConstructor
  13 +@Deprecated
13 14 public class YtTenantController extends BaseController {
14 15
15 16 private final YtUserService userService;
... ...
... ... @@ -106,9 +106,10 @@ public class YtSmsServiceImpl implements YtSmsService {
106 106 return false;
107 107 }
108 108
  109 +
109 110 @Override
110 111 @Transactional
111   - public boolean sendLoginSmsCode(String phoneNumber) {
  112 + public boolean sendSmsCode(String phoneNumber,MsgTemplatePurposeEnum purpose) {
112 113 // 检查手机号码是否存在系统,以免乱发消息
113 114 if (userMapper
114 115 .selectList(new QueryWrapper<User>().lambda().eq(User::getPhoneNumber, phoneNumber))
... ... @@ -117,7 +118,7 @@ public class YtSmsServiceImpl implements YtSmsService {
117 118 }
118 119 // 获取是否有验证码存在,防止发送数量过多
119 120 String key =
120   - MsgTemplatePurposeEnum.FOR_LOGIN.name()
  121 + purpose.name()
121 122 + DEFAULT_DELIMITER
122 123 + MessageTypeEnum.PHONE_MESSAGE.name()
123 124 + DEFAULT_DELIMITER
... ... @@ -139,7 +140,7 @@ public class YtSmsServiceImpl implements YtSmsService {
139 140 messageTemplateMapper.selectList(
140 141 new QueryWrapper<MessageTemplate>()
141 142 .lambda()
142   - .eq(MessageTemplate::getTemplatePurpose, MsgTemplatePurposeEnum.FOR_LOGIN.name())
  143 + .eq(MessageTemplate::getTemplatePurpose, purpose.name())
143 144 .eq(MessageTemplate::getMessageType, MessageTypeEnum.PHONE_MESSAGE.name()));
144 145 if (messageTemplates.isEmpty()) {
145 146 throw new YtDataValidationException("no sms provider config");
... ... @@ -152,7 +153,7 @@ public class YtSmsServiceImpl implements YtSmsService {
152 153 smsReqDTO.setParams(params);
153 154 smsReqDTO.setPhoneNumbers(phoneNumber);
154 155 smsReqDTO.setId(messageTemplate.getId());
155   - smsReqDTO.setTemplatePurpose(MsgTemplatePurposeEnum.FOR_LOGIN.name());
  156 + smsReqDTO.setTemplatePurpose(purpose.name());
156 157 if (this.sendSms(smsReqDTO)) {
157 158 cacheUtils.put(MOBILE_LOGIN_SMS_CODE, key, new CodeTTL(code, System.currentTimeMillis()));
158 159 return true;
... ...
... ... @@ -11,28 +11,34 @@ import lombok.RequiredArgsConstructor;
11 11 import lombok.extern.slf4j.Slf4j;
12 12 import org.apache.commons.lang3.RandomStringUtils;
13 13 import org.apache.commons.lang3.StringUtils;
  14 +import org.springframework.context.ApplicationEventPublisher;
14 15 import org.springframework.scheduling.annotation.Async;
15 16 import org.springframework.security.access.AccessDeniedException;
  17 +import org.springframework.security.authentication.BadCredentialsException;
16 18 import org.springframework.security.crypto.password.PasswordEncoder;
17 19 import org.springframework.stereotype.Service;
18 20 import org.springframework.transaction.annotation.Transactional;
  21 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
19 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 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 28 import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
22 29 import org.thingsboard.server.common.data.yunteng.constant.ModelConstants;
  30 +import org.thingsboard.server.common.data.yunteng.core.cache.CacheUtils;
23 31 import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException;
24 32 import org.thingsboard.server.common.data.yunteng.core.exception.NoneTenantAssetException;
25 33 import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
26 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 36 import org.thingsboard.server.common.data.yunteng.enums.MessageTypeEnum;
32 37 import org.thingsboard.server.common.data.yunteng.enums.MsgTemplatePurposeEnum;
33 38 import org.thingsboard.server.common.data.yunteng.enums.UserStatusEnum;
34 39 import org.thingsboard.server.common.data.yunteng.utils.ReflectUtils;
35 40 import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData;
  41 +import org.thingsboard.server.dao.user.UserService;
36 42 import org.thingsboard.server.dao.yunteng.entities.*;
37 43 import org.thingsboard.server.dao.yunteng.mapper.*;
38 44 import org.thingsboard.server.dao.yunteng.service.*;
... ... @@ -43,8 +49,9 @@ import java.util.*;
43 49 import java.util.concurrent.CompletableFuture;
44 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 55 import static org.thingsboard.server.common.data.yunteng.constant.ModelConstants.TablePropertyMapping.*;
49 56
50 57 @Service
... ... @@ -68,6 +75,10 @@ public class YtUserServiceImpl extends AbstractBaseService<UserMapper, User>
68 75 public static final String ACTIVATE_URL_PATTERN = "%s/api/noauth/activate?activateToken=%s";
69 76 private final PasswordEncoder passwordEncoder;
70 77
  78 + private CacheUtils cacheUtils;
  79 + private final UserService tbUserService;
  80 + private final ApplicationEventPublisher eventPublisher;
  81 +
71 82 @Override
72 83 public List<UserDetailsDTO> findUserDetailsByUsername(String username) {
73 84 // 多个租户可能存在多个username相同的情况
... ... @@ -427,6 +438,52 @@ public class YtUserServiceImpl extends AbstractBaseService<UserMapper, User>
427 438 }
428 439
429 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 487 public List<UserDetailsDTO> getUserByPhoneNumber(String phoneNumber) {
431 488 return baseMapper.findUserDetailsByPhoneNumber(phoneNumber);
432 489 }
... ... @@ -586,6 +643,11 @@ public class YtUserServiceImpl extends AbstractBaseService<UserMapper, User>
586 643 @Override
587 644 public User validateChangePasswordAccount(AccountReqDTO accountReqDTO) {
588 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 651 if (null == user
590 652 || StringUtils.isEmpty(accountReqDTO.getPassword())
591 653 || StringUtils.isEmpty(accountReqDTO.getResetPassword())) {
... ... @@ -597,7 +659,6 @@ public class YtUserServiceImpl extends AbstractBaseService<UserMapper, User>
597 659 throw new YtDataValidationException(ErrorMessage.USERNAME_PASSWORD_INCORRECT.getMessage());
598 660 }
599 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 2
3 3
4 4 import org.thingsboard.server.common.data.yunteng.dto.request.SmsReqDTO;
  5 +import org.thingsboard.server.common.data.yunteng.enums.MsgTemplatePurposeEnum;
5 6
6 7 public interface YtSmsService {
7 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 42
43 43 void resetPassword(String userId, boolean isPtSysadmin, String tenantId);
44 44
  45 + void forgetPassword(String phoneNumber,AccountReqDTO forget);
  46 +
45 47 List<UserDetailsDTO> getUserByPhoneNumber(String phoneNumber);
46 48
47 49 void validateUserNameAndPhoneNumberAndEmail(UserDTO userDTO);
... ...