import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@Service
@Transactional
@Log4j2
@RequiredArgsConstructor // 생성자 자동 주입
public class AccessLogSearchImpl implements AccessLogSearch {
private final JPAQueryFactory queryFactory;
private final ErrorCodeRepository errorCodeRepository;
@Override
public Page<AccessLogDTO> search1(PageRequestDTO pageRequestDTO) {
QAccessLog accessLog = QAccessLog.accessLog;
BooleanBuilder builder = new BooleanBuilder();
String where = pageRequestDTO.getFilter().getWhere();
String keyword = pageRequestDTO.getFilter().getKeyword();
log.info("검색 필터 where = {}, keyword = {}", where, keyword);
if (keyword != null && !keyword.trim().isEmpty()) {
buildSearchCondition(builder, accessLog, where.trim(), keyword.trim());
}
Pageable pageable = PageRequest.of(
pageRequestDTO.getPage() - 1,
pageRequestDTO.getSize(),
Sort.by("uid").descending()
);
JPAQuery<AccessLog> query = queryFactory
.selectFrom(accessLog)
.where(builder)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(accessLog.uid.desc());
List<AccessLog> resultList = query.fetch();
long totalCount = queryFactory
.select(accessLog.count())
.from(accessLog)
.where(builder)
.fetchOne();
// errorCode 메시지 전체 Map으로 미리 조회 (N+1 제거)
Map<Integer, String> errorMap = errorCodeRepository.findAll().stream()
.collect(Collectors.toMap(ErrorCode::getCodeId, ErrorCode::getCodeNm));
List<AccessLogDTO> dtoList = IntStream.range(0, resultList.size())
.mapToObj(i -> {
AccessLog entity = resultList.get(i);
int no = (int) (totalCount - (pageable.getPageNumber() * pageable.getPageSize()) - i);
return toDTO(entity, no, errorMap);
})
.collect(Collectors.toList());
return new PageImpl<>(dtoList, pageable, totalCount);
}
private void buildSearchCondition(BooleanBuilder builder, QAccessLog accessLog, String where, String keyword) {
switch (where) {
case "userID" -> builder.and(accessLog.userid.containsIgnoreCase(keyword));
case "userNM" -> builder.and(accessLog.userNM.containsIgnoreCase(keyword));
case "ipaddr" -> builder.and(accessLog.ipaddr.containsIgnoreCase(keyword));
case "route" -> builder.and(accessLog.route.stringValue().containsIgnoreCase(keyword));
case "errorCode" -> {
List<Integer> codeIds = errorCodeRepository.findCodeIdsByCodeNmLike(keyword);
if (!codeIds.isEmpty()) {
builder.and(accessLog.errCode.in(codeIds));
} else {
builder.and(accessLog.errCode.eq(-9999)); // fallback
}
}
case "accessDate" -> {
String[] parts = keyword.split("/");
if (parts.length == 2) {
String from = parts[0].trim();
String to = parts[1].trim();
if (from.length() == 8 && to.length() == 8) {
if (from.compareTo(to) > 0) {
String temp = from;
from = to;
to = temp;
}
builder.and(accessLog.date.between(from, to));
}
} else {
builder.and(accessLog.date.startsWith(keyword));
}
}
default -> {
Set<String> allowedFields = Set.of("userid", "userNM", "ipaddr", "browser", "os", "date");
if (allowedFields.contains(where)) {
PathBuilder<AccessLog> pathBuilder = new PathBuilder<>(AccessLog.class, "accessLog");
builder.and(pathBuilder.getString(where).containsIgnoreCase(keyword));
} else {
log.warn(" 잘못된 where 필드명: '{}'. 검색 조건 무시", where);
}
}
}
}
private AccessLogDTO toDTO(AccessLog entity, int no, Map<Integer, String> errorMap) {
String errorMessage = errorCodeRepository
.findMessageByCode(entity.getErrCode())
.orElse(String.valueOf(entity.getErrCode()));
return AccessLogDTO.builder()
.no(no) // 추가
.uid(entity.getUid())
.ipaddr(MaskingUtil.ipAddressMasking(entity.getIpaddr()))
.date(InputSanitizer.displayDate(entity.getDate()))
.time(entity.getTime())
.OS(entity.getOs())
.browser(entity.getBrowser())
.userid(MaskingUtil.idMasking(entity.getUserid()))
.userNM(MaskingUtil.letterMasking(entity.getUserNM()))
.success(entity.getSuccess())
.route(entity.getRoute())
.errCode(entity.getErrCode())
.errorMessage(errorMap.getOrDefault(entity.getErrCode(), String.valueOf(entity.getErrCode())))
.build();
}
}