liujie
2026-08-21 ad5db85e540947e82c7a4d5612879b3b3ecdf459
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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;
    }
}