package com.tourguide.modules.user.service;
|
|
import com.tourguide.common.exception.BusinessException;
|
import com.tourguide.modules.user.dto.GuidePhoneChangeTokenResponse;
|
import com.tourguide.modules.user.dto.GuideUserResponse;
|
import com.tourguide.modules.user.dto.SmsCodeResponse;
|
import com.tourguide.modules.user.entity.GuideUser;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.stereotype.Service;
|
|
import java.time.Duration;
|
import java.util.UUID;
|
|
/** 管理导游手机号更换流程,避免绕过旧手机号验证直接修改账号。 */
|
@Service
|
public class GuidePhoneChangeService {
|
private static final String OLD_PHONE_SCENE = "change-old";
|
private static final String NEW_PHONE_SCENE = "change-new";
|
private static final String TOKEN_KEY_PREFIX = "tourguide:guide-phone-change:token:";
|
private static final Duration TOKEN_TTL = Duration.ofMinutes(10);
|
|
private final StringRedisTemplate redisTemplate;
|
private final GuideVerificationCodeService verificationCodeService;
|
private final GuideUserService userService;
|
|
public GuidePhoneChangeService(StringRedisTemplate redisTemplate,
|
GuideVerificationCodeService verificationCodeService,
|
GuideUserService userService) {
|
this.redisTemplate = redisTemplate;
|
this.verificationCodeService = verificationCodeService;
|
this.userService = userService;
|
}
|
|
public SmsCodeResponse sendOldPhoneCode(Long guideId, String phone) {
|
requireCurrentPhone(guideId, phone);
|
return verificationCodeService.send(phone, OLD_PHONE_SCENE);
|
}
|
|
public GuidePhoneChangeTokenResponse verifyOldPhone(Long guideId, String phone, String code) {
|
requireCurrentPhone(guideId, phone);
|
verificationCodeService.verifyAndConsume(phone, code, OLD_PHONE_SCENE);
|
String token = UUID.randomUUID().toString().replace("-", "");
|
redisTemplate.opsForValue().set(tokenKey(token), String.valueOf(guideId), TOKEN_TTL);
|
return new GuidePhoneChangeTokenResponse(token, TOKEN_TTL.getSeconds());
|
}
|
|
public SmsCodeResponse sendNewPhoneCode(Long guideId, String changeToken, String newPhone) {
|
requireValidToken(guideId, changeToken);
|
requireNewPhone(guideId, newPhone);
|
return verificationCodeService.send(newPhone, NEW_PHONE_SCENE);
|
}
|
|
public GuideUserResponse changePhone(Long guideId, String changeToken, String newPhone, String code) {
|
requireValidToken(guideId, changeToken);
|
requireNewPhone(guideId, newPhone);
|
verificationCodeService.verifyAndConsume(newPhone, code, NEW_PHONE_SCENE);
|
GuideUserResponse response = userService.changeMobile(guideId, newPhone);
|
redisTemplate.delete(tokenKey(changeToken));
|
return response;
|
}
|
|
private void requireCurrentPhone(Long guideId, String phone) {
|
GuideUser user = userService.findEnabledById(guideId);
|
if (user.getMobile() == null || !user.getMobile().equals(phone)) {
|
throw new BusinessException(400, "旧手机号与当前账号不一致");
|
}
|
}
|
|
private void requireValidToken(Long guideId, String changeToken) {
|
String tokenGuideId = redisTemplate.opsForValue().get(tokenKey(changeToken));
|
if (!String.valueOf(guideId).equals(tokenGuideId)) {
|
throw new BusinessException(401, "换号验证已失效,请重新验证旧手机号");
|
}
|
}
|
|
private void requireNewPhone(Long guideId, String newPhone) {
|
GuideUser user = userService.findEnabledById(guideId);
|
if (newPhone.equals(user.getMobile())) {
|
throw new BusinessException(400, "新手机号不能与当前手机号相同");
|
}
|
userService.ensurePhoneAvailable(guideId, newPhone);
|
}
|
|
private String tokenKey(String token) {
|
return TOKEN_KEY_PREFIX + token;
|
}
|
}
|