liujie
2026-09-16 a3a56121a476cd23d0f29bc391c07374dc5008f2
backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java
@@ -41,6 +41,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -128,6 +129,7 @@
    public GuideUser findByPhone(String phone) {
        return userMapper.selectOne(new LambdaQueryWrapper<GuideUser>()
                .and(query -> query.eq(GuideUser::getUsername, phone).or().eq(GuideUser::getMobile, phone))
                .orderByDesc(GuideUser::getId)
                .last("LIMIT 1"));
    }
@@ -166,15 +168,17 @@
        GuideTeam team = user.getTeamId() == null ? null : teamMapper.selectById(user.getTeamId());
        List<GuideScenicConfig> scenicConfigs = scenicConfigMapper.selectList(
                new LambdaQueryWrapper<GuideScenicConfig>().eq(GuideScenicConfig::getGuideId, id).orderByAsc(GuideScenicConfig::getId));
        GuideApplication application = findLatestApplication(user);
        LocalDate birthDate = user.getBirthDate();
        Integer workYears = user.getWorkYears();
        if (birthDate == null || workYears == null) {
            GuideApplication application = findLatestApplication(user);
            if (birthDate == null && application != null) birthDate = parseBirthDate(application.getIdCardNo());
            if (workYears == null && application != null) workYears = parseWorkYears(application.getWorkYearRange());
        }
        return new GuideUserDetailResponse(user, team == null ? null : team.getTeamName(), scenicConfigs,
                birthDate, workYears);
                birthDate, workYears,
                application == null ? null : application.getAgeRange(),
                application == null ? null : application.getWorkYearRange());
    }
    private GuideApplication findLatestApplication(GuideUser user) {
@@ -280,6 +284,10 @@
        }
        String username = application.getMobile().trim();
        GuideUser existingUser = findByPhone(username);
        if (existingUser != null && existingUser.getStatus() == STATUS_CANCELLED) {
            anonymizeCancelledGuide(existingUser);
            existingUser = null;
        }
        if (existingUser != null && (existingUser.getStatus() == null || existingUser.getStatus() != 2)) {
            throw new BusinessException(409, "该手机号已存在导游账号");
        }
@@ -303,6 +311,10 @@
    @Transactional
    public GuideUserResponse createPendingFromApplication(GuideApplication application) {
        GuideUser user = findByPhone(application.getMobile());
        if (user != null && user.getStatus() == STATUS_CANCELLED) {
            anonymizeCancelledGuide(user);
            user = null;
        }
        if (user == null) {
            user = new GuideUser();
            user.setUsername(application.getMobile().trim());
@@ -314,28 +326,33 @@
            throw new BusinessException(409, "该手机号已存在导游账号");
        }
        ensureUniqueGuideIdentity(application, user);
        if (user.getStatus() == STATUS_CANCELLED) {
            user.setStatus(STATUS_PENDING);
            user.setAutoDisableWhitelisted(0);
            user.setAutoDisabled(0);
            user.setDeleted(0);
        }
        copyApplicationProfile(user, application);
        if (user.getId() == null) userMapper.insert(user); else userMapper.updateById(user);
        return GuideUserResponse.from(user);
    }
    private void anonymizeCancelledGuide(GuideUser user) {
        String deletedUsername = "deleted_" + UUID.randomUUID().toString().replace("-", "").substring(0, 24);
        userMapper.anonymizeCancelledGuide(user.getId(), deletedUsername);
    }
    private void ensureUniqueGuideIdentity(GuideApplication application, GuideUser existingUser) {
        if (StringUtils.hasText(application.getIdCardNo())) {
            // 兼容旧注销数据:释放已注销账号占用的身份证唯一索引。
            userMapper.clearCancelledIdentityByIdCard(application.getIdCardNo().trim().toUpperCase());
        }
        if (StringUtils.hasText(application.getCertificateNo())
                && userMapper.selectCount(new LambdaQueryWrapper<GuideUser>()
                .eq(GuideUser::getCertificateNo, application.getCertificateNo())
                .ne(GuideUser::getStatus, STATUS_CANCELLED)
                .ne(existingUser != null && existingUser.getId() != null,
                        GuideUser::getId, existingUser == null ? null : existingUser.getId())) > 0) {
            throw new BusinessException(409, "该导游证号已存在导游账号");
        }
        if (StringUtils.hasText(application.getIdCardNo())
                && userMapper.selectCount(new LambdaQueryWrapper<GuideUser>()
                .eq(GuideUser::getIdCardNo, application.getIdCardNo())
                .eq(GuideUser::getIdCardNo, application.getIdCardNo().trim().toUpperCase())
                .ne(GuideUser::getStatus, STATUS_CANCELLED)
                .ne(existingUser != null && existingUser.getId() != null,
                        GuideUser::getId, existingUser == null ? null : existingUser.getId())) > 0) {
            throw new BusinessException(409, "该身份证号已存在导游账号");
@@ -443,12 +460,7 @@
    @Transactional
    public GuideUserResponse changeMobile(Long id, String newPhone) {
        GuideUser user = findEnabledById(id);
        ensurePhoneAvailable(id, newPhone);
        String oldPhone = user.getMobile();
        user.setMobile(newPhone);
        if (oldPhone != null && oldPhone.equals(user.getUsername())) {
            user.setUsername(newPhone);
        }
        updateMobileAndLoginUsername(user, newPhone);
        userMapper.updateById(user);
        return GuideUserResponse.from(user);
    }
@@ -462,8 +474,8 @@
                .eq(GuideTeam::getCollectionGuideId, id)) > 0) {
            throw new BusinessException(409, "该导游已配置为讲解队收款导游,请先更换收款导游");
        }
        updateMobileAndLoginUsername(user, request.getMobile());
        user.setRealName(request.getRealName());
        user.setMobile(request.getMobile());
        user.setAvatar(request.getAvatar());
        user.setCertificateNo(request.getCertificateNo());
        user.setGender(request.getGender());
@@ -481,6 +493,17 @@
        return detail(id);
    }
    private void updateMobileAndLoginUsername(GuideUser user, String newPhone) {
        String oldPhone = user.getMobile();
        if (!java.util.Objects.equals(oldPhone, newPhone) && StringUtils.hasText(newPhone)) {
            ensurePhoneAvailable(user.getId(), newPhone);
        }
        user.setMobile(newPhone);
        if (oldPhone != null && oldPhone.equals(user.getUsername()) && StringUtils.hasText(newPhone)) {
            user.setUsername(newPhone);
        }
    }
    @Transactional
    public void updateWhitelist(Long id, boolean enabled) {
        GuideUser user = findById(id);
@@ -496,6 +519,13 @@
        userMapper.updateById(user);
    }
    @Transactional
    public void updatePremiumPermission(Long id, boolean enabled) {
        GuideUser user = findById(id);
        user.setPremiumEnabled(enabled ? 1 : 0);
        userMapper.updateById(user);
    }
    public GuideAutoDisableConfigResponse getAutoDisableConfig() {
        GuideAutoDisableConfig config = autoDisableConfigMapper.selectById(1);
        if (config == null) {