From 4f6f15b818704079ff2e4e4719b798f48d68a335 Mon Sep 17 00:00:00 2001
From: liujie <1793218484@qq.com>
Date: 星期五, 11 九月 2026 13:52:19 +0800
Subject: [PATCH] update
---
backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java | 233 +++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 215 insertions(+), 18 deletions(-)
diff --git a/backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java b/backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java
index 3400b74..8b4039c 100644
--- a/backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java
+++ b/backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java
@@ -16,25 +16,33 @@
import com.tourguide.modules.user.entity.GuideApplication;
import com.tourguide.modules.user.entity.GuideScenicConfig;
import com.tourguide.modules.user.entity.GuideTeam;
+import com.tourguide.modules.user.entity.GuideTeamScenicConfig;
import com.tourguide.modules.user.entity.GuideUser;
import com.tourguide.modules.user.mapper.GuideAutoDisableConfigMapper;
import com.tourguide.modules.user.mapper.GuideScenicConfigMapper;
import com.tourguide.modules.user.mapper.GuideTeamMapper;
+import com.tourguide.modules.user.mapper.GuideTeamScenicConfigMapper;
import com.tourguide.modules.user.mapper.GuideUserMapper;
+import com.tourguide.modules.user.mapper.GuideApplicationMapper;
import com.tourguide.modules.operation.entity.OperationCity;
import com.tourguide.modules.operation.entity.OperationScenicSpot;
import com.tourguide.modules.operation.mapper.OperationCityMapper;
import com.tourguide.modules.operation.mapper.OperationScenicSpotMapper;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
+import java.time.DateTimeException;
import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
/** 仅处理导游端账号,避免导游身份与游客或后台管理员混用。 */
@@ -45,8 +53,11 @@
public static final int STATUS_PENDING = 2;
public static final int STATUS_CANCELLED = 3;
private static final String DEFAULT_GUIDE_PASSWORD = "123456";
+ private static final Pattern WORK_YEAR_START_PATTERN = Pattern.compile("^(\\d+)");
private final GuideUserMapper userMapper;
private final GuideTeamMapper teamMapper;
+ private final GuideApplicationMapper applicationMapper;
+ private final GuideTeamScenicConfigMapper teamScenicConfigMapper;
private final GuideScenicConfigMapper scenicConfigMapper;
private final GuideAutoDisableConfigMapper autoDisableConfigMapper;
private final OperationScenicSpotMapper scenicSpotMapper;
@@ -59,8 +70,34 @@
OperationScenicSpotMapper scenicSpotMapper,
OperationCityMapper cityMapper,
PasswordEncoder passwordEncoder) {
+ this(userMapper, teamMapper, null, null, scenicConfigMapper, autoDisableConfigMapper,
+ scenicSpotMapper, cityMapper, passwordEncoder);
+ }
+
+ public GuideUserService(GuideUserMapper userMapper, GuideTeamMapper teamMapper,
+ GuideApplicationMapper applicationMapper,
+ GuideScenicConfigMapper scenicConfigMapper,
+ GuideAutoDisableConfigMapper autoDisableConfigMapper,
+ OperationScenicSpotMapper scenicSpotMapper,
+ OperationCityMapper cityMapper,
+ PasswordEncoder passwordEncoder) {
+ this(userMapper, teamMapper, applicationMapper, null, scenicConfigMapper, autoDisableConfigMapper,
+ scenicSpotMapper, cityMapper, passwordEncoder);
+ }
+
+ @Autowired
+ public GuideUserService(GuideUserMapper userMapper, GuideTeamMapper teamMapper,
+ GuideApplicationMapper applicationMapper,
+ GuideTeamScenicConfigMapper teamScenicConfigMapper,
+ GuideScenicConfigMapper scenicConfigMapper,
+ GuideAutoDisableConfigMapper autoDisableConfigMapper,
+ OperationScenicSpotMapper scenicSpotMapper,
+ OperationCityMapper cityMapper,
+ PasswordEncoder passwordEncoder) {
this.userMapper = userMapper;
this.teamMapper = teamMapper;
+ this.applicationMapper = applicationMapper;
+ this.teamScenicConfigMapper = teamScenicConfigMapper;
this.scenicConfigMapper = scenicConfigMapper;
this.autoDisableConfigMapper = autoDisableConfigMapper;
this.scenicSpotMapper = scenicSpotMapper;
@@ -106,6 +143,7 @@
.or().like(GuideUser::getCertificateNo, searchKeyword));
}
query.eq(status != null, GuideUser::getStatus, status)
+ .ne(GuideUser::getStatus, STATUS_PENDING)
.eq(whitelisted != null, GuideUser::getAutoDisableWhitelisted, Boolean.TRUE.equals(whitelisted) ? 1 : 0)
.ge(startDate != null, GuideUser::getCreatedAt, startDate == null ? null : startDate.atStartOfDay())
.lt(endDate != null, GuideUser::getCreatedAt, endDate == null ? null : endDate.plusDays(1).atStartOfDay());
@@ -128,23 +166,63 @@
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));
- return new GuideUserDetailResponse(user, team == null ? null : team.getTeamName(), scenicConfigs);
+ GuideApplication application = findLatestApplication(user);
+ LocalDate birthDate = user.getBirthDate();
+ Integer workYears = user.getWorkYears();
+ if (birthDate == null || workYears == null) {
+ 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,
+ application == null ? null : application.getAgeRange(),
+ application == null ? null : application.getWorkYearRange());
+ }
+
+ private GuideApplication findLatestApplication(GuideUser user) {
+ if (applicationMapper == null) return null;
+ if (StringUtils.hasText(user.getIdCardNo())) {
+ GuideApplication application = applicationMapper.selectOne(new LambdaQueryWrapper<GuideApplication>()
+ .eq(GuideApplication::getIdCardNo, user.getIdCardNo().trim())
+ .in(GuideApplication::getAuditStatus, 0, 1)
+ .orderByDesc(GuideApplication::getId)
+ .last("LIMIT 1"));
+ if (application != null) return application;
+ }
+ if (!StringUtils.hasText(user.getMobile())) return null;
+ return applicationMapper.selectOne(new LambdaQueryWrapper<GuideApplication>()
+ .eq(GuideApplication::getMobile, user.getMobile().trim())
+ .in(GuideApplication::getAuditStatus, 0, 1)
+ .orderByDesc(GuideApplication::getId)
+ .last("LIMIT 1"));
}
@Transactional
public List<GuideScenicConfigResponse> addScenicConfig(Long guideId, Long scenicSpotId) {
- findById(guideId);
+ GuideUser guide = findById(guideId);
OperationScenicSpot scenicSpot = scenicSpotMapper.selectById(scenicSpotId);
if (scenicSpot == null || scenicSpot.getStatus() == null || scenicSpot.getStatus() != 1) {
throw new BusinessException(404, "景区不存在或未启用");
}
OperationCity city = scenicSpot.getCityId() == null ? null : cityMapper.selectById(scenicSpot.getCityId());
+ ensureScenicBelongsToGuideTeam(guide, scenicSpot);
GuideScenicConfig config = new GuideScenicConfig();
config.setGuideId(guideId);
config.setScenicName(scenicSpot.getScenicName());
config.setCityName(city == null ? null : city.getCityName());
scenicConfigMapper.upsert(config);
return scenicConfigs(guideId);
+ }
+
+ private void ensureScenicBelongsToGuideTeam(GuideUser guide, OperationScenicSpot scenicSpot) {
+ if (guide.getTeamId() == null) return;
+ long configured = teamScenicConfigMapper.selectCount(
+ new LambdaQueryWrapper<GuideTeamScenicConfig>()
+ .eq(GuideTeamScenicConfig::getTeamId, guide.getTeamId())
+ .eq(GuideTeamScenicConfig::getScenicName, scenicSpot.getScenicName()));
+ if (configured == 0) {
+ throw new BusinessException(400, "只能配置所属讲解队下的景区");
+ }
}
@Transactional
@@ -207,12 +285,7 @@
if (existingUser != null && (existingUser.getStatus() == null || existingUser.getStatus() != 2)) {
throw new BusinessException(409, "该手机号已存在导游账号");
}
- if (StringUtils.hasText(application.getCertificateNo())
- && userMapper.selectCount(new LambdaQueryWrapper<GuideUser>()
- .eq(GuideUser::getCertificateNo, application.getCertificateNo())
- .ne(existingUser != null, GuideUser::getId, existingUser == null ? null : existingUser.getId())) > 0) {
- throw new BusinessException(409, "该导游证号已存在导游账号");
- }
+ ensureUniqueGuideIdentity(application, existingUser);
if (existingUser != null) {
copyApplicationProfile(existingUser, application);
existingUser.setStatus(1);
@@ -238,18 +311,46 @@
user.setPassword(passwordEncoder.encode(DEFAULT_GUIDE_PASSWORD));
user.setStatus(2);
user.setDeleted(0);
- } else if (user.getStatus() == null || user.getStatus() != 2) {
+ } else if (user.getStatus() == null
+ || (user.getStatus() != STATUS_PENDING && user.getStatus() != STATUS_CANCELLED)) {
throw new BusinessException(409, "该手机号已存在导游账号");
+ }
+ ensureUniqueGuideIdentity(application, user);
+ if (user.getStatus() == STATUS_CANCELLED) {
+ user.setStatus(STATUS_PENDING);
+ user.setAutoDisableWhitelisted(0);
+ user.setPremiumEnabled(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 ensureUniqueGuideIdentity(GuideApplication application, GuideUser existingUser) {
+ if (StringUtils.hasText(application.getCertificateNo())
+ && userMapper.selectCount(new LambdaQueryWrapper<GuideUser>()
+ .eq(GuideUser::getCertificateNo, application.getCertificateNo())
+ .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())
+ .ne(existingUser != null && existingUser.getId() != null,
+ GuideUser::getId, existingUser == null ? null : existingUser.getId())) > 0) {
+ throw new BusinessException(409, "该身份证号已存在导游账号");
+ }
+ }
+
private void copyApplicationProfile(GuideUser user, GuideApplication application) {
user.setRealName(application.getRealName());
user.setMobile(application.getMobile());
user.setGender(application.getGender());
+ user.setBirthDate(parseBirthDate(application.getIdCardNo()));
+ user.setWorkYears(parseWorkYears(application.getWorkYearRange()));
user.setCertificateNo(application.getCertificateNo());
user.setIdCardNo(application.getIdCardNo());
user.setIdCardFrontUrl(application.getIdCardFrontUrl());
@@ -257,6 +358,31 @@
user.setGuideCertificateUrl(application.getCertificateUrl());
user.setQualificationCertificateUrl(application.getQualificationCertificateUrl());
user.setTeamId(application.getTeamId());
+ }
+
+ private LocalDate parseBirthDate(String idCardNo) {
+ if (!StringUtils.hasText(idCardNo)) return null;
+ String normalized = idCardNo.trim().toUpperCase();
+ if (normalized.length() < 14) return null;
+ try {
+ return LocalDate.parse(normalized.substring(6, 14), DateTimeFormatter.BASIC_ISO_DATE);
+ } catch (DateTimeException exception) {
+ return null;
+ }
+ }
+
+ private Integer parseWorkYears(String workYearRange) {
+ if (!StringUtils.hasText(workYearRange)) return null;
+ String normalized = workYearRange.trim();
+ Matcher matcher = WORK_YEAR_START_PATTERN.matcher(normalized);
+ if (!matcher.find()) return null;
+ try {
+ int startYears = Integer.parseInt(matcher.group(1));
+ if (startYears > 100) return null;
+ return startYears == 1 && normalized.indexOf('-') < 0 ? 0 : startYears;
+ } catch (NumberFormatException exception) {
+ return null;
+ }
}
@Transactional
@@ -277,6 +403,39 @@
return GuideUserResponse.from(user);
}
+ @Transactional
+ public GuideUserResponse updateAvatarByAdmin(Long id, String avatar) {
+ GuideUser user = findById(id);
+ user.setAvatar(avatar);
+ userMapper.updateById(user);
+ return GuideUserResponse.from(user);
+ }
+
+ public boolean supportsDocumentType(String documentType) {
+ return "idCardFrontUrl".equals(documentType)
+ || "idCardBackUrl".equals(documentType)
+ || "guideCertificateUrl".equals(documentType)
+ || "qualificationCertificateUrl".equals(documentType);
+ }
+
+ @Transactional
+ public String updateDocumentByAdmin(Long id, String documentType, String documentUrl) {
+ GuideUser user = findById(id);
+ if ("idCardFrontUrl".equals(documentType)) {
+ user.setIdCardFrontUrl(documentUrl);
+ } else if ("idCardBackUrl".equals(documentType)) {
+ user.setIdCardBackUrl(documentUrl);
+ } else if ("guideCertificateUrl".equals(documentType)) {
+ user.setGuideCertificateUrl(documentUrl);
+ } else if ("qualificationCertificateUrl".equals(documentType)) {
+ user.setQualificationCertificateUrl(documentUrl);
+ } else {
+ throw new BusinessException(400, "不支持的证件类型");
+ }
+ userMapper.updateById(user);
+ return documentUrl;
+ }
+
public void ensurePhoneAvailable(Long currentGuideId, String phone) {
GuideUser existing = findByPhone(phone);
if (existing != null && !existing.getId().equals(currentGuideId)) {
@@ -287,21 +446,22 @@
@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);
}
@Transactional
public GuideUserDetailResponse updateDetail(Long id, GuideUserDetailUpdateRequest request) {
- GuideUser user = findById(id);
+ GuideUser user = userMapper.selectForUpdate(id);
+ if (user == null) throw new BusinessException(404, "导游用户不存在");
+ if (!java.util.Objects.equals(user.getTeamId(), request.getTeamId())
+ && teamMapper.selectCount(new LambdaQueryWrapper<GuideTeam>()
+ .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());
@@ -319,10 +479,36 @@
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);
user.setAutoDisableWhitelisted(enabled ? 1 : 0);
+ // 将因自动禁用规则停用的导游加入白名单后,立即恢复账号状态。
+ // 仅处理 autoDisabled 标记,避免误启用管理员手动停用的导游。
+ if (enabled
+ && Integer.valueOf(STATUS_DISABLED).equals(user.getStatus())
+ && Integer.valueOf(1).equals(user.getAutoDisabled())) {
+ user.setStatus(STATUS_ENABLED);
+ user.setAutoDisabled(0);
+ }
+ userMapper.updateById(user);
+ }
+
+ @Transactional
+ public void updatePremiumPermission(Long id, boolean enabled) {
+ GuideUser user = findById(id);
+ user.setPremiumEnabled(enabled ? 1 : 0);
userMapper.updateById(user);
}
@@ -357,12 +543,23 @@
@Transactional
public void updateStatus(Long id, Integer status) {
- GuideUser user = userMapper.selectById(id);
+ GuideUser user = userMapper.selectForUpdate(id);
if (user == null) { throw new BusinessException(404, "导游用户不存在"); }
+ if (!Integer.valueOf(STATUS_ENABLED).equals(status)
+ && teamMapper.selectCount(new LambdaQueryWrapper<GuideTeam>()
+ .eq(GuideTeam::getCollectionGuideId, id)) > 0) {
+ throw new BusinessException(409, "该导游已配置为讲解队收款导游,请先更换收款导游");
+ }
if (user.getStatus() != null && user.getStatus() == STATUS_CANCELLED) {
throw new BusinessException(409, "已注销导游账号不可重新启用");
}
+ if (Integer.valueOf(STATUS_ENABLED).equals(status)
+ && Integer.valueOf(1).equals(user.getAutoDisabled())
+ && !Integer.valueOf(1).equals(user.getAutoDisableWhitelisted())) {
+ throw new BusinessException(409, "该导游由自动禁用规则停用,请先在自动禁用设置中添加白名单后再启用");
+ }
user.setStatus(status);
+ user.setAutoDisabled(0);
userMapper.updateById(user);
}
}
--
Gitblit v1.7.1