package com.sinata.appointment.service; 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.dto.OpLogListVo; import com.sinata.appointment.entity.BizOpLog; import com.sinata.appointment.entity.MpUser; import com.sinata.appointment.entity.SysUser; import com.sinata.appointment.mapper.BizOpLogMapper; import com.sinata.appointment.mapper.MpUserMapper; import com.sinata.appointment.mapper.SysUserMapper; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.slf4j.MDC; import org.springframework.scheduling.annotation.Async; import org.springframework.util.StringUtils; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class OpLogApplicationService { public static final String MDC_TRACE = "traceId"; public static final String SOURCE_ADMIN = "ADMIN"; public static final String SOURCE_MINI_EMPLOYEE = "MINI_EMPLOYEE"; private final BizOpLogMapper bizOpLogMapper; private final SysUserMapper sysUserMapper; private final MpUserMapper mpUserMapper; public IPage page(long page, long size, String sourceType) { Page pager = new Page<>(page, size); LambdaQueryWrapper query = new LambdaQueryWrapper() .eq(BizOpLog::getDelFlag, 0) .orderByDesc(BizOpLog::getOpTime) .orderByDesc(BizOpLog::getId); String normalizedSourceType = normalizeSourceType(sourceType); if (normalizedSourceType != null) { query.eq(BizOpLog::getSourceType, normalizedSourceType); } IPage logPage = bizOpLogMapper.selectPage(pager, query); Page result = new Page<>(logPage.getCurrent(), logPage.getSize(), logPage.getTotal()); List records = logPage.getRecords(); result.setRecords(toListVos(records)); return result; } @Async("applicationTaskExecutor") public void recordAsync( String sourceType, Long operatorUserId, String module, String action, String resourceType, String resourceId, String detailJson) { String normalizedSourceType = normalizeSourceType(sourceType); if (!StringUtils.hasText(normalizedSourceType)) { return; } if (SOURCE_MINI_EMPLOYEE.equals(normalizedSourceType) && !isEmployee(operatorUserId)) { return; } BizOpLog log = new BizOpLog(); log.setSourceType(normalizedSourceType); log.setOperatorUserId(operatorUserId); log.setModule(module); log.setAction(action); log.setResourceType(resourceType); log.setResourceId(resourceId); log.setDetailJson(detailJson); log.setOpTime(LocalDateTime.now()); log.setTraceId(MDC.get(MDC_TRACE)); bizOpLogMapper.insert(log); } private List toListVos(List records) { if (records == null || records.isEmpty()) { return List.of(); } List adminUserIds = records.stream() .filter(item -> SOURCE_ADMIN.equals(item.getSourceType())) .map(BizOpLog::getOperatorUserId) .filter(Objects::nonNull) .distinct() .toList(); List mpUserIds = records.stream() .filter(item -> SOURCE_MINI_EMPLOYEE.equals(item.getSourceType())) .map(BizOpLog::getOperatorUserId) .filter(Objects::nonNull) .distinct() .toList(); Map adminNameMap = loadAdminNameMap(adminUserIds); Map mpNameMap = loadMpNameMap(mpUserIds); return records.stream() .map(item -> toListVo(item, adminNameMap, mpNameMap)) .toList(); } private OpLogListVo toListVo( BizOpLog item, Map adminNameMap, Map mpNameMap) { OpLogListVo vo = new OpLogListVo(); vo.setId(item.getId()); vo.setSourceType(item.getSourceType()); vo.setSourceTypeText(resolveSourceTypeText(item.getSourceType())); vo.setOperatorUserId(item.getOperatorUserId()); vo.setModule(item.getModule()); vo.setAction(item.getAction()); vo.setResourceType(item.getResourceType()); vo.setResourceId(item.getResourceId()); vo.setDetailJson(item.getDetailJson()); vo.setTraceId(item.getTraceId()); vo.setOpTime(item.getOpTime()); if (SOURCE_MINI_EMPLOYEE.equals(item.getSourceType())) { vo.setOperatorName(mpNameMap.getOrDefault(item.getOperatorUserId(), "—")); } else { vo.setOperatorName(adminNameMap.getOrDefault(item.getOperatorUserId(), "—")); } return vo; } private Map loadAdminNameMap(List userIds) { if (userIds == null || userIds.isEmpty()) { return Map.of(); } return sysUserMapper.selectBatchIds(userIds).stream() .collect( Collectors.toMap( SysUser::getId, user -> firstNonBlank(user.getRealName(), user.getUsername(), "—"), (a, _b) -> a)); } private Map loadMpNameMap(List userIds) { if (userIds == null || userIds.isEmpty()) { return Map.of(); } return mpUserMapper.selectBatchIds(userIds).stream() .filter(user -> "EMPLOYEE".equals(user.getUserType())) .collect( Collectors.toMap( MpUser::getId, user -> firstNonBlank(user.getNickName(), user.getMobile(), "—"), (a, _b) -> a)); } private boolean isEmployee(Long operatorUserId) { if (operatorUserId == null) { return false; } MpUser user = mpUserMapper.selectById(operatorUserId); return user != null && "EMPLOYEE".equals(user.getUserType()); } private String normalizeSourceType(String sourceType) { if (!StringUtils.hasText(sourceType)) { return null; } String normalized = sourceType.trim().toUpperCase(); if (SOURCE_ADMIN.equals(normalized)) { return SOURCE_ADMIN; } if (SOURCE_MINI_EMPLOYEE.equals(normalized)) { return SOURCE_MINI_EMPLOYEE; } return null; } private String resolveSourceTypeText(String sourceType) { if (SOURCE_MINI_EMPLOYEE.equals(sourceType)) { return "员工端"; } return "管理后台"; } private String firstNonBlank(String... values) { for (String value : values) { if (StringUtils.hasText(value)) { return value.trim(); } } return "—"; } }