package com.tourguide.modules.user.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.tourguide.common.api.PageResponse;
|
import com.tourguide.common.exception.BusinessException;
|
import com.tourguide.modules.user.dto.GuideAutoDisableConfigRequest;
|
import com.tourguide.modules.user.dto.GuideAutoDisableConfigResponse;
|
import com.tourguide.modules.user.dto.GuideProfileUpdateRequest;
|
import com.tourguide.modules.user.dto.GuideScenicConfigResponse;
|
import com.tourguide.modules.user.dto.GuideUserCreateRequest;
|
import com.tourguide.modules.user.dto.GuideUserDetailResponse;
|
import com.tourguide.modules.user.dto.GuideUserDetailUpdateRequest;
|
import com.tourguide.modules.user.dto.GuideUserResponse;
|
import com.tourguide.modules.user.entity.GuideAutoDisableConfig;
|
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.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.GuideUserMapper;
|
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.security.crypto.password.PasswordEncoder;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.util.StringUtils;
|
import java.time.LocalDate;
|
import java.util.Collections;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
/** 仅处理导游端账号,避免导游身份与游客或后台管理员混用。 */
|
@Service
|
public class GuideUserService {
|
public static final int STATUS_DISABLED = 0;
|
public static final int STATUS_ENABLED = 1;
|
public static final int STATUS_PENDING = 2;
|
public static final int STATUS_CANCELLED = 3;
|
private static final String DEFAULT_GUIDE_PASSWORD = "123456";
|
private final GuideUserMapper userMapper;
|
private final GuideTeamMapper teamMapper;
|
private final GuideScenicConfigMapper scenicConfigMapper;
|
private final GuideAutoDisableConfigMapper autoDisableConfigMapper;
|
private final OperationScenicSpotMapper scenicSpotMapper;
|
private final OperationCityMapper cityMapper;
|
private final PasswordEncoder passwordEncoder;
|
|
public GuideUserService(GuideUserMapper userMapper, GuideTeamMapper teamMapper,
|
GuideScenicConfigMapper scenicConfigMapper,
|
GuideAutoDisableConfigMapper autoDisableConfigMapper,
|
OperationScenicSpotMapper scenicSpotMapper,
|
OperationCityMapper cityMapper,
|
PasswordEncoder passwordEncoder) {
|
this.userMapper = userMapper;
|
this.teamMapper = teamMapper;
|
this.scenicConfigMapper = scenicConfigMapper;
|
this.autoDisableConfigMapper = autoDisableConfigMapper;
|
this.scenicSpotMapper = scenicSpotMapper;
|
this.cityMapper = cityMapper;
|
this.passwordEncoder = passwordEncoder;
|
}
|
|
public GuideUser findEnabledById(Long id) {
|
GuideUser user = userMapper.selectById(id);
|
if (user == null || user.getStatus() == null || user.getStatus() != STATUS_ENABLED) {
|
throw new BusinessException(401, "导游用户不存在或已停用");
|
}
|
return user;
|
}
|
|
public GuideUser findWorkbenchAccessibleById(Long id) {
|
GuideUser user = findById(id);
|
if (user.getStatus() == null || user.getStatus() == STATUS_CANCELLED) {
|
throw new BusinessException(401, "导游用户不存在或已注销");
|
}
|
return user;
|
}
|
|
public GuideUser findByUsername(String username) {
|
return userMapper.selectOne(new LambdaQueryWrapper<GuideUser>().eq(GuideUser::getUsername, username).last("LIMIT 1"));
|
}
|
|
public GuideUser findByPhone(String phone) {
|
return userMapper.selectOne(new LambdaQueryWrapper<GuideUser>()
|
.and(query -> query.eq(GuideUser::getUsername, phone).or().eq(GuideUser::getMobile, phone))
|
.last("LIMIT 1"));
|
}
|
|
public PageResponse<GuideUserResponse> page(long page, long size, String keyword, Integer status,
|
LocalDate startDate, LocalDate endDate, String ratingLevel,
|
String teamType, Boolean whitelisted) {
|
LambdaQueryWrapper<GuideUser> query = new LambdaQueryWrapper<GuideUser>();
|
if (StringUtils.hasText(keyword)) {
|
String searchKeyword = keyword.trim();
|
query.and(wrapper -> wrapper.like(GuideUser::getUsername, searchKeyword)
|
.or().like(GuideUser::getRealName, searchKeyword)
|
.or().like(GuideUser::getMobile, searchKeyword)
|
.or().like(GuideUser::getCertificateNo, searchKeyword));
|
}
|
query.eq(status != null, GuideUser::getStatus, status)
|
.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());
|
if ("personal".equals(teamType)) query.isNull(GuideUser::getTeamId);
|
if ("team".equals(teamType)) query.isNotNull(GuideUser::getTeamId);
|
if ("high".equals(ratingLevel)) query.ge(GuideUser::getRating, 4);
|
if ("medium".equals(ratingLevel)) query.ge(GuideUser::getRating, 3).lt(GuideUser::getRating, 4);
|
if ("low".equals(ratingLevel)) query.lt(GuideUser::getRating, 3);
|
query.orderByDesc(GuideUser::getId);
|
Page<GuideUser> result = userMapper.selectPage(new Page<GuideUser>(page, size), query);
|
Map<Long, String> teamNames = loadTeamNames(result.getRecords());
|
return new PageResponse<GuideUserResponse>(result.getRecords().stream()
|
.map(user -> GuideUserResponse.from(user, teamNames.get(user.getTeamId())))
|
.collect(Collectors.toList()),
|
result.getTotal(), result.getCurrent(), result.getSize());
|
}
|
|
public GuideUserDetailResponse detail(Long id) {
|
GuideUser user = findById(id);
|
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);
|
}
|
|
@Transactional
|
public List<GuideScenicConfigResponse> addScenicConfig(Long guideId, Long scenicSpotId) {
|
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());
|
GuideScenicConfig config = new GuideScenicConfig();
|
config.setGuideId(guideId);
|
config.setScenicName(scenicSpot.getScenicName());
|
config.setCityName(city == null ? null : city.getCityName());
|
scenicConfigMapper.upsert(config);
|
return scenicConfigs(guideId);
|
}
|
|
@Transactional
|
public List<GuideScenicConfigResponse> deleteScenicConfig(Long guideId, Long configId) {
|
findById(guideId);
|
GuideScenicConfig config = scenicConfigMapper.selectById(configId);
|
if (config == null || !guideId.equals(config.getGuideId())) {
|
throw new BusinessException(404, "导游景区配置不存在");
|
}
|
scenicConfigMapper.deleteById(configId);
|
return scenicConfigs(guideId);
|
}
|
|
private List<GuideScenicConfigResponse> scenicConfigs(Long guideId) {
|
return scenicConfigMapper.selectList(new LambdaQueryWrapper<GuideScenicConfig>()
|
.eq(GuideScenicConfig::getGuideId, guideId)
|
.orderByAsc(GuideScenicConfig::getId))
|
.stream().map(GuideScenicConfigResponse::new).collect(Collectors.toList());
|
}
|
|
public GuideUser findById(Long id) {
|
GuideUser user = userMapper.selectById(id);
|
if (user == null) { throw new BusinessException(404, "导游用户不存在"); }
|
return user;
|
}
|
|
private Map<Long, String> loadTeamNames(List<GuideUser> users) {
|
Set<Long> teamIds = users.stream().map(GuideUser::getTeamId).filter(id -> id != null).collect(Collectors.toSet());
|
if (teamIds.isEmpty()) return Collections.emptyMap();
|
Map<Long, String> names = new HashMap<Long, String>();
|
for (GuideTeam team : teamMapper.selectBatchIds(teamIds)) names.put(team.getId(), team.getTeamName());
|
return names;
|
}
|
|
@Transactional
|
public GuideUserResponse create(GuideUserCreateRequest request) {
|
if (userMapper.selectCount(new LambdaQueryWrapper<GuideUser>().eq(GuideUser::getUsername, request.getUsername())) > 0) {
|
throw new BusinessException(409, "导游用户名已存在");
|
}
|
GuideUser user = new GuideUser();
|
user.setUsername(request.getUsername());
|
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
user.setRealName(request.getRealName());
|
user.setMobile(request.getMobile());
|
user.setCertificateNo(request.getCertificateNo());
|
user.setStatus(1);
|
user.setDeleted(0);
|
userMapper.insert(user);
|
return GuideUserResponse.from(user);
|
}
|
|
/** 审核通过后创建正式导游账号,申请手机号作为登录用户名。 */
|
@Transactional
|
public GuideUserResponse createFromApprovedApplication(GuideApplication application) {
|
if (!StringUtils.hasText(application.getMobile())) {
|
throw new BusinessException(400, "申请手机号不能为空");
|
}
|
String username = application.getMobile().trim();
|
GuideUser existingUser = findByPhone(username);
|
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, "该导游证号已存在导游账号");
|
}
|
if (existingUser != null) {
|
copyApplicationProfile(existingUser, application);
|
existingUser.setStatus(1);
|
userMapper.updateById(existingUser);
|
return GuideUserResponse.from(existingUser);
|
}
|
GuideUser user = new GuideUser();
|
user.setUsername(username);
|
user.setPassword(passwordEncoder.encode(DEFAULT_GUIDE_PASSWORD));
|
copyApplicationProfile(user, application);
|
user.setStatus(1);
|
user.setDeleted(0);
|
userMapper.insert(user);
|
return GuideUserResponse.from(user);
|
}
|
|
@Transactional
|
public GuideUserResponse createPendingFromApplication(GuideApplication application) {
|
GuideUser user = findByPhone(application.getMobile());
|
if (user == null) {
|
user = new GuideUser();
|
user.setUsername(application.getMobile().trim());
|
user.setPassword(passwordEncoder.encode(DEFAULT_GUIDE_PASSWORD));
|
user.setStatus(2);
|
user.setDeleted(0);
|
} else if (user.getStatus() == null || user.getStatus() != 2) {
|
throw new BusinessException(409, "该手机号已存在导游账号");
|
}
|
copyApplicationProfile(user, application);
|
if (user.getId() == null) userMapper.insert(user); else userMapper.updateById(user);
|
return GuideUserResponse.from(user);
|
}
|
|
private void copyApplicationProfile(GuideUser user, GuideApplication application) {
|
user.setRealName(application.getRealName());
|
user.setMobile(application.getMobile());
|
user.setGender(application.getGender());
|
user.setCertificateNo(application.getCertificateNo());
|
user.setIdCardNo(application.getIdCardNo());
|
user.setIdCardFrontUrl(application.getIdCardFrontUrl());
|
user.setIdCardBackUrl(application.getIdCardBackUrl());
|
user.setGuideCertificateUrl(application.getCertificateUrl());
|
user.setQualificationCertificateUrl(application.getQualificationCertificateUrl());
|
user.setTeamId(application.getTeamId());
|
}
|
|
@Transactional
|
public GuideUserResponse updateProfile(Long id, GuideProfileUpdateRequest request) {
|
GuideUser user = findEnabledById(id);
|
user.setRealName(request.getRealName());
|
user.setAvatar(request.getAvatar());
|
user.setCertificateNo(request.getCertificateNo());
|
userMapper.updateById(user);
|
return GuideUserResponse.from(user);
|
}
|
|
@Transactional
|
public GuideUserResponse updateAvatar(Long id, String avatar) {
|
GuideUser user = findEnabledById(id);
|
user.setAvatar(avatar);
|
userMapper.updateById(user);
|
return GuideUserResponse.from(user);
|
}
|
|
public void ensurePhoneAvailable(Long currentGuideId, String phone) {
|
GuideUser existing = findByPhone(phone);
|
if (existing != null && !existing.getId().equals(currentGuideId)) {
|
throw new BusinessException(409, "该手机号已绑定其他导游账号");
|
}
|
}
|
|
@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);
|
}
|
userMapper.updateById(user);
|
return GuideUserResponse.from(user);
|
}
|
|
@Transactional
|
public GuideUserDetailResponse updateDetail(Long id, GuideUserDetailUpdateRequest request) {
|
GuideUser user = findById(id);
|
user.setRealName(request.getRealName());
|
user.setMobile(request.getMobile());
|
user.setAvatar(request.getAvatar());
|
user.setCertificateNo(request.getCertificateNo());
|
user.setGender(request.getGender());
|
user.setBirthDate(request.getBirthDate());
|
user.setWorkYears(request.getWorkYears());
|
user.setIdCardNo(request.getIdCardNo());
|
user.setIdCardFrontUrl(request.getIdCardFrontUrl());
|
user.setIdCardBackUrl(request.getIdCardBackUrl());
|
user.setGuideCertificateUrl(request.getGuideCertificateUrl());
|
user.setQualificationCertificateUrl(request.getQualificationCertificateUrl());
|
user.setTeamId(request.getTeamId());
|
user.setWechatOpenId(request.getWechatOpenId());
|
userMapper.updateById(user);
|
|
return detail(id);
|
}
|
|
@Transactional
|
public void updateWhitelist(Long id, boolean enabled) {
|
GuideUser user = findById(id);
|
user.setAutoDisableWhitelisted(enabled ? 1 : 0);
|
userMapper.updateById(user);
|
}
|
|
public GuideAutoDisableConfigResponse getAutoDisableConfig() {
|
GuideAutoDisableConfig config = autoDisableConfigMapper.selectById(1);
|
if (config == null) {
|
config = new GuideAutoDisableConfig();
|
config.setId(1);
|
config.setOrderThreshold(1);
|
config.setRatingThreshold(new java.math.BigDecimal(3.00));
|
autoDisableConfigMapper.insert(config);
|
}
|
return new GuideAutoDisableConfigResponse(config);
|
}
|
|
@Transactional
|
public GuideAutoDisableConfigResponse updateAutoDisableConfig(GuideAutoDisableConfigRequest request) {
|
GuideAutoDisableConfig config = autoDisableConfigMapper.selectById(1);
|
if (config == null) {
|
config = new GuideAutoDisableConfig();
|
config.setId(1);
|
config.setOrderThreshold(request.getOrderThreshold());
|
config.setRatingThreshold(request.getRatingThreshold());
|
autoDisableConfigMapper.insert(config);
|
} else {
|
config.setOrderThreshold(request.getOrderThreshold());
|
config.setRatingThreshold(request.getRatingThreshold());
|
autoDisableConfigMapper.updateById(config);
|
}
|
return new GuideAutoDisableConfigResponse(autoDisableConfigMapper.selectById(1));
|
}
|
|
@Transactional
|
public void updateStatus(Long id, Integer status) {
|
GuideUser user = userMapper.selectById(id);
|
if (user == null) { throw new BusinessException(404, "导游用户不存在"); }
|
if (user.getStatus() != null && user.getStatus() == STATUS_CANCELLED) {
|
throw new BusinessException(409, "已注销导游账号不可重新启用");
|
}
|
user.setStatus(status);
|
userMapper.updateById(user);
|
}
|
}
|