package com.sinata.appointment.service;
|
|
import com.alibaba.excel.EasyExcel;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.sinata.appointment.config.MiniStpUtil;
|
import com.sinata.appointment.constants.BizErrorCode;
|
import com.sinata.appointment.constants.MiniUserType;
|
import com.sinata.appointment.dto.VisitorUserDetailVo;
|
import com.sinata.appointment.dto.VisitorUserExportRow;
|
import com.sinata.appointment.dto.VisitorUserListVo;
|
import com.sinata.appointment.dto.VisitorUserStatusPatchRequest;
|
import com.sinata.appointment.dto.VisitorUserUpdateRequest;
|
import com.sinata.appointment.entity.AppointmentOrder;
|
import com.sinata.appointment.entity.MpUser;
|
import com.sinata.appointment.exception.BizException;
|
import com.sinata.appointment.mapper.AppointmentOrderMapper;
|
import com.sinata.appointment.mapper.MpUserMapper;
|
import com.sinata.appointment.util.MobileMaskUtil;
|
import com.sinata.appointment.util.PhoneNormalize;
|
import com.sinata.appointment.util.UuidAssert;
|
import java.io.ByteArrayOutputStream;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.format.DateTimeFormatter;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.util.StringUtils;
|
|
@Service
|
@RequiredArgsConstructor
|
public class VisitorUserManageApplicationService {
|
|
private static final DateTimeFormatter EXPORT_TIME_FORMATTER =
|
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
private final MpUserMapper mpUserMapper;
|
private final AppointmentOrderMapper appointmentOrderMapper;
|
|
public IPage<VisitorUserListVo> page(
|
long page,
|
long size,
|
String userName,
|
String mobile,
|
String companyName,
|
LocalDate registerStartDate,
|
LocalDate registerEndDate,
|
Integer status) {
|
Page<MpUser> result =
|
mpUserMapper.selectPage(
|
new Page<>(page, size),
|
buildVisitorQuery(
|
userName, mobile, companyName, registerStartDate, registerEndDate, status));
|
List<Long> userIds = result.getRecords().stream().map(MpUser::getId).filter(Objects::nonNull).toList();
|
Map<Long, Long> bookingCountMap = loadBookingCountMap(userIds);
|
return result.convert(user -> toListVo(user, bookingCountMap));
|
}
|
|
public byte[] exportExcel(
|
String userName,
|
String mobile,
|
String companyName,
|
LocalDate registerStartDate,
|
LocalDate registerEndDate,
|
Integer status) {
|
List<MpUser> users =
|
mpUserMapper.selectList(
|
buildVisitorQuery(
|
userName, mobile, companyName, registerStartDate, registerEndDate, status));
|
List<Long> userIds = users.stream().map(MpUser::getId).filter(Objects::nonNull).toList();
|
Map<Long, Long> bookingCountMap = loadBookingCountMap(userIds);
|
List<VisitorUserExportRow> rows =
|
users.stream().map(user -> toExportRow(user, bookingCountMap)).toList();
|
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
EasyExcel.write(bos, VisitorUserExportRow.class).sheet("访客信息").doWrite(rows);
|
return bos.toByteArray();
|
} catch (Exception e) {
|
return new byte[0];
|
}
|
}
|
|
public VisitorUserDetailVo detail(Long id, String uuid) {
|
MpUser user = loadVisitorUserWithUuid(id, uuid);
|
VisitorUserDetailVo vo = new VisitorUserDetailVo();
|
vo.setId(user.getId());
|
vo.setUuid(user.getUuid());
|
vo.setUserName(firstNonBlank(user.getNickName(), "—"));
|
vo.setMobile(user.getMobile());
|
vo.setGender(trimToNull(user.getGender()));
|
vo.setCompanyName(trimToNull(user.getCompanyName()));
|
vo.setStatus(user.getStatus() == null ? 1 : user.getStatus());
|
return vo;
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public void update(Long id, VisitorUserUpdateRequest request) {
|
MpUser user = loadVisitorUserWithUuid(id, request.getUuid());
|
String userName = trimRequired(request.getUserName(), "用户名称不能为空");
|
if (userName.length() > 30) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "用户名称不能超过30个字");
|
}
|
String mobile = trimRequired(request.getMobile(), "用户电话不能为空");
|
if (!mobile.matches("^\\d{11}$")) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "用户电话须为11位数字");
|
}
|
assertMobileUnique(mobile, user.getId());
|
String gender = trimToNull(request.getGender());
|
if (StringUtils.hasText(gender) && !"男".equals(gender) && !"女".equals(gender)) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "性别仅支持男/女");
|
}
|
String companyName = trimToNull(request.getCompanyName());
|
|
user.setNickName(userName);
|
user.setMobile(mobile);
|
user.setGender(gender);
|
user.setCompanyName(companyName);
|
mpUserMapper.updateById(user);
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public void patchStatus(Long id, VisitorUserStatusPatchRequest request) {
|
MpUser user = loadVisitorUserWithUuid(id, request.getUuid());
|
Integer status = request.getStatus();
|
if (status == null || (status != 0 && status != 1)) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "状态无效");
|
}
|
user.setStatus(status);
|
mpUserMapper.updateById(user);
|
if (status == 0) {
|
kickMiniUserOffline(user.getId());
|
}
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public void delete(Long id, String uuid) {
|
MpUser user = loadVisitorUserWithUuid(id, uuid);
|
int affected = mpUserMapper.deleteById(user.getId());
|
if (affected <= 0) {
|
throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "删除失败,请刷新后重试");
|
}
|
kickMiniUserOffline(user.getId());
|
}
|
|
private Map<Long, Long> loadBookingCountMap(List<Long> userIds) {
|
Map<Long, Long> result = new HashMap<>();
|
if (userIds == null || userIds.isEmpty()) {
|
return result;
|
}
|
List<AppointmentOrder> rows =
|
appointmentOrderMapper.selectList(
|
new LambdaQueryWrapper<AppointmentOrder>()
|
.eq(AppointmentOrder::getDelFlag, 0)
|
.in(AppointmentOrder::getSubmitterId, userIds));
|
for (AppointmentOrder row : rows) {
|
if (row.getSubmitterId() != null) {
|
result.merge(row.getSubmitterId(), 1L, Long::sum);
|
}
|
}
|
return result;
|
}
|
|
private VisitorUserListVo toListVo(MpUser user, Map<Long, Long> bookingCountMap) {
|
VisitorUserListVo vo = new VisitorUserListVo();
|
vo.setId(user.getId());
|
vo.setUuid(user.getUuid());
|
vo.setUserName(firstNonBlank(user.getNickName(), "—"));
|
vo.setGender(defaultText(trimToNull(user.getGender())));
|
vo.setMobileMasked(MobileMaskUtil.mask(user.getMobile()));
|
vo.setCompanyName(defaultText(trimToNull(user.getCompanyName())));
|
vo.setBookingCount(bookingCountMap.getOrDefault(user.getId(), 0L));
|
vo.setStatus(user.getStatus() == null ? 1 : user.getStatus());
|
vo.setCreateTime(user.getCreateTime());
|
return vo;
|
}
|
|
private VisitorUserExportRow toExportRow(MpUser user, Map<Long, Long> bookingCountMap) {
|
VisitorUserExportRow row = new VisitorUserExportRow();
|
row.setUserName(firstNonBlank(user.getNickName(), "—"));
|
row.setGender(defaultText(trimToNull(user.getGender())));
|
row.setMobile(defaultText(trimToNull(user.getMobile())));
|
row.setCompanyName(defaultText(trimToNull(user.getCompanyName())));
|
row.setBookingCount(bookingCountMap.getOrDefault(user.getId(), 0L));
|
row.setStatusText(user.getStatus() != null && user.getStatus() == 0 ? "禁用" : "启用");
|
row.setCreateTime(formatDateTime(user.getCreateTime()));
|
return row;
|
}
|
|
private LambdaQueryWrapper<MpUser> buildVisitorQuery(
|
String userName,
|
String mobile,
|
String companyName,
|
LocalDate registerStartDate,
|
LocalDate registerEndDate,
|
Integer status) {
|
LambdaQueryWrapper<MpUser> query = new LambdaQueryWrapper<>();
|
query.eq(MpUser::getDelFlag, 0).eq(MpUser::getUserType, MiniUserType.VISITOR);
|
query.like(StringUtils.hasText(userName), MpUser::getNickName, trim(userName));
|
query.like(StringUtils.hasText(mobile), MpUser::getMobile, trim(mobile));
|
query.like(StringUtils.hasText(companyName), MpUser::getCompanyName, trim(companyName));
|
query.eq(status != null, MpUser::getStatus, status);
|
query.ge(registerStartDate != null, MpUser::getCreateTime, atStart(registerStartDate));
|
query.lt(registerEndDate != null, MpUser::getCreateTime, atNextDay(registerEndDate));
|
query.orderByDesc(MpUser::getCreateTime).orderByDesc(MpUser::getId);
|
return query;
|
}
|
|
private void assertMobileUnique(String mobileRaw, Long excludeUserId) {
|
String normalized = PhoneNormalize.mainland(mobileRaw);
|
List<MpUser> rows =
|
mpUserMapper.selectList(
|
new LambdaQueryWrapper<MpUser>()
|
.eq(MpUser::getDelFlag, 0)
|
.isNotNull(MpUser::getMobile));
|
for (MpUser row : rows) {
|
if (excludeUserId != null && excludeUserId.equals(row.getId())) {
|
continue;
|
}
|
if (normalized.equals(PhoneNormalize.mainland(row.getMobile()))) {
|
throw new BizException(BizErrorCode.MP_STAFF_MOBILE_EXISTS, "手机号已存在");
|
}
|
}
|
}
|
|
private MpUser loadVisitorUserWithUuid(Long id, String uuid) {
|
MpUser user = mpUserMapper.selectById(id);
|
if (user == null || (user.getDelFlag() != null && user.getDelFlag() == 1)) {
|
throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "资源不存在或无权访问");
|
}
|
if (!MiniUserType.VISITOR.equals(user.getUserType())) {
|
throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "资源不存在或无权访问");
|
}
|
UuidAssert.match(user, uuid);
|
return user;
|
}
|
|
private static void kickMiniUserOffline(Long mpUserId) {
|
try {
|
MiniStpUtil.kickout(mpUserId);
|
} catch (Exception ignored) {
|
}
|
}
|
|
private static LocalDateTime atStart(LocalDate date) {
|
return date == null ? null : date.atStartOfDay();
|
}
|
|
private static LocalDateTime atNextDay(LocalDate date) {
|
return date == null ? null : date.plusDays(1).atStartOfDay();
|
}
|
|
private static String trim(String value) {
|
return value == null ? null : value.trim();
|
}
|
|
private static String trimToNull(String value) {
|
String trimmed = trim(value);
|
return StringUtils.hasText(trimmed) ? trimmed : null;
|
}
|
|
private static String trimRequired(String value, String message) {
|
String trimmed = trimToNull(value);
|
if (!StringUtils.hasText(trimmed)) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, message);
|
}
|
return trimmed;
|
}
|
|
private static String defaultText(String value) {
|
return StringUtils.hasText(value) ? value : "—";
|
}
|
|
private static String formatDateTime(LocalDateTime value) {
|
return value == null ? "—" : EXPORT_TIME_FORMATTER.format(value);
|
}
|
|
@SafeVarargs
|
private static String firstNonBlank(String... values) {
|
if (values == null) {
|
return null;
|
}
|
for (String value : values) {
|
if (StringUtils.hasText(value)) {
|
return value.trim();
|
}
|
}
|
return null;
|
}
|
}
|