Skip to content
Snippets Groups Projects
Commit 7a143ba4 authored by 천 진강's avatar 천 진강
Browse files

feat: Certificate 검색에 와일드카드 조건, 이메일 기반 검색 추가

parent 55d6360c
No related branches found
No related tags found
1 merge request!15Feat/certificate
......@@ -23,6 +23,7 @@ import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Objects;
......@@ -36,6 +37,8 @@ public class CertificateLogAspect {
private final LogRepository logRepository;
private final EntityManager entityManager;
private static final DateTimeFormatter LOG_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/* Create 로깅 */
@AfterReturning(pointcut = "execution(* com.aolda.itda.service.certificate.*Service.*create*(..))"
, returning = "result")
......@@ -55,7 +58,7 @@ public class CertificateLogAspect {
String description = "domain: " + certificate.getDomain() + "\n"
+ "email: " + certificate.getEmail() + "\n"
+ "challenge: " + certificate.getChallenge() + "\n"
+ "expiresAt: " + certificate.getExpiresAt();
+ "expiresAt: " + (certificate.getExpiresAt() != null ? certificate.getExpiresAt().format(LOG_DATE_FORMATTER) : "null");
/* 로그 엔티티 저장 */
logRepository.save(Log.builder()
......@@ -89,7 +92,7 @@ public class CertificateLogAspect {
String description = "domain: " + certificate.getDomain() + "\n"
+ "email: " + certificate.getEmail() + "\n"
+ "challenge: " + certificate.getChallenge() + "\n"
+ "expiresAt: " + certificate.getExpiresAt();
+ "expiresAt: " + (certificate.getExpiresAt() != null ? certificate.getExpiresAt().format(LOG_DATE_FORMATTER) : "null");
/* 로그 엔티티 저장 */
logRepository.save(Log.builder()
......@@ -132,7 +135,8 @@ public class CertificateLogAspect {
String description = "domain: " + old.getDomain() + (old.getDomain().equals(newObj.getDomain()) ? "" : " -> " + newObj.getDomain()) + "\n"
+ "email: " + old.getEmail() + (old.getEmail().equals(newObj.getEmail()) ? "" : " -> " + newObj.getEmail()) + "\n"
+ "challenge: " + old.getChallenge() + (old.getChallenge() == newObj.getChallenge() ? "" : " -> " + newObj.getChallenge()) + "\n"
+ "expiresAt: " + old.getExpiresAt() + (old.getExpiresAt().equals(newObj.getExpiresAt()) ? "" : " -> " + newObj.getExpiresAt());
+ "expiresAt: " + (old.getExpiresAt() != null ? old.getExpiresAt().format(LOG_DATE_FORMATTER) : "null")
+ (old.getExpiresAt() != null && newObj.getExpiresAt() != null && !old.getExpiresAt().equals(newObj.getExpiresAt()) ? " -> " + newObj.getExpiresAt().format(LOG_DATE_FORMATTER) : "");
/* 로그 엔티티 저장 */
logRepository.save(Log.builder()
......
......@@ -59,8 +59,18 @@ public class CertificateController {
@GetMapping("/certificates")
public ResponseEntity<PageResp<CertificateDTO>> lists(
@RequestParam String projectId,
@RequestParam(required = false) String domain
@RequestParam(required = false) String domain,
@RequestParam(required = false) String query
) {
if (query != null) {
return ResponseEntity.ok(
certificateService.getCertificatesWithSearch(
projectId,
query
)
);
}
return ResponseEntity.ok(
certificateService.getCertificates(projectId, domain)
);
......
......@@ -16,7 +16,7 @@ import java.time.LocalDateTime;
public class CertificateDTO {
private Long id; // 인증서 고유 ID
// private String projectId; // 프로젝트 식별자
private String projectId; // 프로젝트 식별자
private String domain; // SSL 인증받을 도메인 주소
private String email;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")// 도메인 소유자의 이메일
......
......@@ -34,6 +34,7 @@ public class Certificate extends BaseTimeEntity {
@Setter
@Column(columnDefinition = "DATETIME")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime expiresAt; //인증서 만료일
@Enumerated(EnumType.STRING)
......
package com.aolda.itda.repository.certificate;
import com.aolda.itda.entity.certificate.Certificate;
import com.aolda.itda.entity.routing.Routing;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.time.LocalDateTime;
import java.util.List;
......@@ -19,10 +21,13 @@ public interface CertificateRepository extends JpaRepository<Certificate, Long>
//List<Certificate> findByExpiresAtBeforeAndIsDeleted(LocalDateTime date, Boolean isDeleted);
// 1) domain 필터링용 메서드
List<Certificate> findByProjectIdAndDomainContainingIgnoreCaseAndIsDeleted(
List<Certificate> findByProjectIdAndDomainContainingAndIsDeleted(
String projectId, String domain, Boolean isDeleted);
// 3) 만료 30일 이내 대상 조회
List<Certificate> findByExpiresAtBeforeAndIsDeleted(
LocalDateTime date, Boolean isDeleted);
@Query("SELECT r FROM Routing r WHERE r.projectId = ?1 AND r.isDeleted = ?3 AND (r.domain LIKE %?2% OR r.instanceIp LIKE %?2% OR r.name LIKE %?2%)")
List<Certificate> findWithSearch(String projectId, String query, Boolean isDeleted);
}
......@@ -2,8 +2,10 @@ package com.aolda.itda.service.certificate;
import com.aolda.itda.dto.PageResp;
import com.aolda.itda.dto.certificate.CertificateDTO;
import com.aolda.itda.dto.routing.RoutingDTO;
import com.aolda.itda.entity.certificate.Certificate;
import com.aolda.itda.entity.certificate.Challenge;
import com.aolda.itda.entity.routing.Routing;
import com.aolda.itda.exception.CustomException;
import com.aolda.itda.exception.ErrorCode;
import com.aolda.itda.repository.certificate.CertificateRepository;
......@@ -12,6 +14,7 @@ import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
......@@ -21,7 +24,10 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
@Service
@Transactional
......@@ -29,6 +35,8 @@ import java.util.List;
@Slf4j
public class CertificateService {
@Value("${spring.server.admin-project}")
private String adminProject;
private final CertificateRepository certificateRepository;
private final AuthService authService;
......@@ -41,18 +49,67 @@ public class CertificateService {
return toDTO(cert);
}
/* Certificate 목록 조회 + 검색 */
public PageResp<CertificateDTO> getCertificatesWithSearch(String projectId, String query) {
/* 입력 검증 */
if (query == null || query.isBlank()) {
return PageResp.<CertificateDTO>builder()
.contents(certificateRepository.findByProjectIdAndIsDeleted(projectId, false)
.stream()
.map(this::toDTO)
.toList()).build();
}
/* 도메인 패턴 검증 */
String domainPattern = "^(\\*\\.)?([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}$";
if (Pattern.matches(domainPattern, query) && query.startsWith("*.")) {
query = query.substring(2);
}
return PageResp.<CertificateDTO>builder()
.contents(certificateRepository.findWithSearch(projectId, query, false)
.stream()
.map(this::toDTO)
.toList()).build();
}
/** 1) 목록 조회 (domain 필터 optional) **/
public PageResp<CertificateDTO> getCertificates(String projectId, String domain) {
List<Certificate> list;
Set<Certificate> set = new HashSet<>();
// 도메인이 입력된 경우 처리
if (domain != null && !domain.isBlank()) {
list = certificateRepository
.findByProjectIdAndDomainContainingIgnoreCaseAndIsDeleted(
projectId, domain, false);
// 서브도메인이 있는 경우
if (domain.indexOf('.') != domain.lastIndexOf('.')) {
String wildcardDomain = "*." + domain.substring(domain.indexOf('.') + 1);
set.addAll(certificateRepository
.findByProjectIdAndDomainContainingAndIsDeleted(
projectId, wildcardDomain, false));
set.addAll(certificateRepository
.findByProjectIdAndDomainContainingAndIsDeleted(
projectId, domain, false));
set.addAll(certificateRepository
.findByProjectIdAndDomainContainingAndIsDeleted(
adminProject, wildcardDomain, false));
set.addAll(certificateRepository
.findByProjectIdAndDomainContainingAndIsDeleted(
adminProject, domain, false));
} else {
// 서브도메인이 없는 경우 일반 검색
set.addAll(certificateRepository
.findByProjectIdAndDomainContainingAndIsDeleted(
projectId, domain, false));
set.addAll(certificateRepository
.findByProjectIdAndDomainContainingAndIsDeleted(
adminProject, domain, false));
}
} else {
list = certificateRepository
.findByProjectIdAndIsDeleted(projectId, false);
set.addAll(certificateRepository
.findByProjectIdAndIsDeleted(projectId, false));
set.addAll(certificateRepository
.findByProjectIdAndIsDeleted(adminProject, false));
}
List<CertificateDTO> dtos = list.stream()
List<CertificateDTO> dtos = set.stream()
.map(this::toDTO)
.toList();
return PageResp.<CertificateDTO>builder()
......@@ -171,6 +228,7 @@ public class CertificateService {
.updatedAt(cert.getUpdatedAt())
.isDeleted(cert.getIsDeleted())
.apiToken(cert.getApiToken())
.projectId(cert.getProjectId())
.build();
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment