diff --git a/src/main/java/com/aolda/itda/aspect/RoutingLogAspect.java b/src/main/java/com/aolda/itda/aspect/RoutingLogAspect.java index a3a67d2dce9f65af87807d2c758eb3a225090b32..97f7de498a71b9a29ce396fe10f7404d31b151d1 100644 --- a/src/main/java/com/aolda/itda/aspect/RoutingLogAspect.java +++ b/src/main/java/com/aolda/itda/aspect/RoutingLogAspect.java @@ -61,7 +61,7 @@ public class RoutingLogAspect { + "ip: " + routing.getInstanceIp() + "\n" + "port: " + routing.getInstancePort() + "\n" + (routing.getCertificate() != null ? ("certificateId: " + routing.getCertificate().getCertificateId() + "\n") : "") - + "caching: " + routing.getCaching() + "\n"; + + "caching: " + routing.getCaching(); /* 로그 엔티티 저장 */ logRepository.save(Log.builder() @@ -97,7 +97,7 @@ public class RoutingLogAspect { + "ip: " + routing.getInstanceIp() + "\n" + "port: " + routing.getInstancePort() + "\n" + (routing.getCertificate() != null ? ("certificateId: " + routing.getCertificate().getCertificateId() + "\n") : "") - + "caching: " + routing.getCaching() + "\n"; + + "caching: " + routing.getCaching(); /* 로그 엔티티 저장 */ logRepository.save(Log.builder() diff --git a/src/main/java/com/aolda/itda/config/AuthInterceptor.java b/src/main/java/com/aolda/itda/config/AuthFilter.java similarity index 55% rename from src/main/java/com/aolda/itda/config/AuthInterceptor.java rename to src/main/java/com/aolda/itda/config/AuthFilter.java index 3a95f8a4e329d27457a75d18fac600f6fe84a836..f810c2c7452b862c85b64cd1a51dd6ff130189b8 100644 --- a/src/main/java/com/aolda/itda/config/AuthInterceptor.java +++ b/src/main/java/com/aolda/itda/config/AuthFilter.java @@ -1,67 +1,76 @@ -package com.aolda.itda.config; - -import com.aolda.itda.dto.auth.IdAndNameDTO; -import com.aolda.itda.exception.CustomException; -import com.aolda.itda.exception.ErrorCode; -import com.aolda.itda.service.AuthService; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; - -import java.util.List; -import java.util.Map; - -@RequiredArgsConstructor -@Component -@Slf4j -public class AuthInterceptor implements HandlerInterceptor { - - private final AuthService authService; - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - String token = request.getHeader("X-Subject-Token"); - - /* 토큰 헤더 검증 */ - if (token == null || token.isEmpty()) { - throw new CustomException(ErrorCode.INVALID_TOKEN, request.getRequestURI()); - } - - /* 유효 토큰 검증 */ - String userId = authService.validateTokenAndGetUserId(token); - if (userId == null) { - log.error("Token validation failed for URI {}: {}", request.getRequestURI(), request.getRemoteAddr()); - throw new CustomException(ErrorCode.INVALID_TOKEN, request.getRequestURI()); - } - - /* 프로젝트 권한 검증 */ - String projectId = request.getParameter("projectId"); - if (projectId != null) { - - try { - String role = authService.getBestRoleWithinProject(token, projectId).get("role"); - if (!role.equals("admin")) { - log.error("Unauthorized Token for URI {}: {}", request.getRequestURI(), request.getRemoteAddr()); - throw new CustomException(ErrorCode.UNAUTHORIZED_USER, request.getRequestURI()); - } - } catch (Exception e) { - throw new CustomException(ErrorCode.UNAUTHORIZED_USER, request.getRequestURI()); - } - - - - } - - /* 프로젝트 리스트 조회 */ - List<String> projects = authService.getProjectsWithUser(Map.of("id", userId, "token", token)) - .stream().map(IdAndNameDTO::getId) - .toList(); - request.setAttribute("projects", projects); - request.setAttribute("user", Map.of("id", userId, "token", token)); - return true; - - } -} +package com.aolda.itda.config; + +import com.aolda.itda.dto.auth.IdAndNameDTO; +import com.aolda.itda.exception.CustomException; +import com.aolda.itda.exception.ErrorCode; +import com.aolda.itda.service.AuthService; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +@RequiredArgsConstructor +@Component +@Slf4j +public class AuthFilter extends OncePerRequestFilter { + + private final AuthService authService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + if (request.getRequestURI().contains("/api/auth")) { + filterChain.doFilter(request, response); + return; + } + + String token = request.getHeader("X-Subject-Token"); + + // 토큰 헤더 검증 + if (token == null || token.isEmpty()) { + throw new CustomException(ErrorCode.INVALID_TOKEN, request.getRequestURI()); + } + + // 유효 토큰 검증 + String userId = authService.validateTokenAndGetUserId(token); + if (userId == null) { + log.error("Token validation failed for URI {}: {}", request.getRequestURI(), request.getRemoteAddr()); + throw new CustomException(ErrorCode.INVALID_TOKEN, request.getRequestURI()); + } + + // 프로젝트 권한 검증 + String projectId = request.getParameter("projectId"); + if (projectId != null) { + try { + authService.getBestRoleWithinProject(token, projectId).get("role"); + if (!request.getMethod().equals("GET") && !authService.getBestRoleWithinProject(token, projectId).get("role").equals("admin")) { + throw new CustomException(ErrorCode.UNAUTHORIZED_USER, request.getRequestURI()); + } + } catch (Exception e) { + throw new CustomException(ErrorCode.UNAUTHORIZED_USER, request.getRequestURI()); + } + } + + // 프로젝트 리스트 조회 + List<String> projects; + if (authService.isAdmin(Map.of("id", userId, "token", token))) { + projects = authService.getAllProjects(token).stream().map(IdAndNameDTO::getId).toList(); + } else { + projects = authService.getProjectsWithUser(Map.of("id", userId, "token", token)).stream().map(IdAndNameDTO::getId).toList(); + } + + request.setAttribute("projects", projects); + request.setAttribute("user", Map.of("id", userId, "token", token)); + + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/com/aolda/itda/config/LoggingFilter.java b/src/main/java/com/aolda/itda/config/LoggingFilter.java index 0dd73b1b546e2f8f11829424f62d6783681a187e..8c5c2f0e090df6f13d35f4c7df103849b9ea9c8e 100644 --- a/src/main/java/com/aolda/itda/config/LoggingFilter.java +++ b/src/main/java/com/aolda/itda/config/LoggingFilter.java @@ -1,51 +1,57 @@ -package com.aolda.itda.config; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.util.ContentCachingRequestWrapper; - -import java.io.IOException; - -@Component -public class LoggingFilter extends OncePerRequestFilter { - - private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class); - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - // Request Body를 읽을 수 있도록 래핑 - ContentCachingRequestWrapper cachingRequest = new ContentCachingRequestWrapper(request); - System.out.println("필터 적용"); - filterChain.doFilter(cachingRequest, response); - - // 로그 기록 - logRequest(cachingRequest); - System.out.println("왜 안돼ㅐ"); - } - - private void logRequest(ContentCachingRequestWrapper request) { - System.out.println("되는거 맞아?"); - String ip = request.getRemoteAddr(); - String method = request.getMethod(); - String uri = request.getRequestURI(); - String queryString = request.getQueryString(); - String body = getRequestBody(request); - - logger.info("IP: {}, Method: {}, URI: {}, Query Params: {}, Request Body: {}", - ip, method, uri, (queryString != null ? queryString : "None"), - (!body.isEmpty() ? body : "None")); - } - - private String getRequestBody(ContentCachingRequestWrapper request) { - byte[] buf = request.getContentAsByteArray(); - return (buf.length > 0) ? new String(buf) : ""; - } -} +package com.aolda.itda.config; + +import com.aolda.itda.exception.CustomException; +import com.aolda.itda.exception.ErrorCode; +import com.aolda.itda.service.AuthService; +import com.fasterxml.jackson.core.JsonProcessingException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingRequestWrapper; + +import java.io.IOException; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class LoggingFilter extends OncePerRequestFilter { + + private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class); + private final AuthService authService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + // Request Body를 읽을 수 있도록 래핑 + ContentCachingRequestWrapper cachingRequest = new ContentCachingRequestWrapper(request); + filterChain.doFilter(cachingRequest, response); + + // 로그 기록 + logRequest(cachingRequest); + } + + private void logRequest(ContentCachingRequestWrapper request) throws JsonProcessingException { + String ip = request.getRemoteAddr(); + String method = request.getMethod(); + String uri = request.getRequestURI(); + String queryString = request.getQueryString(); + String body = getRequestBody(request); + Map<String, String> user = (Map<String, String>) request.getAttribute("user"); + + logger.info("IP: {}, Method: {}, URI: {}, Query Params: {}, User: {}, Request Body: {}", + ip, method, uri, (queryString != null ? queryString : "None"), (user != null ? user.get("id") : "None"), + (!body.isEmpty() ? body : "None")); + } + + private String getRequestBody(ContentCachingRequestWrapper request) { + byte[] buf = request.getContentAsByteArray(); + return (buf.length > 0) ? new String(buf) : ""; + } +} diff --git a/src/main/java/com/aolda/itda/config/WebConfig.java b/src/main/java/com/aolda/itda/config/WebConfig.java index e3893aeecd8b181adc3bc069fbb7724fef9c38ab..a3559c4628d3c5854fc8386cbb1d173943f9ea42 100644 --- a/src/main/java/com/aolda/itda/config/WebConfig.java +++ b/src/main/java/com/aolda/itda/config/WebConfig.java @@ -3,17 +3,18 @@ package com.aolda.itda.config; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; +import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { - private final AuthInterceptor authInterceptor; + private final AuthFilter authFilter; + private final LoggingFilter loggingFilter; @Override public void addCorsMappings(CorsRegistry registry) { // 스프링단에서 cors 설정 @@ -26,13 +27,23 @@ public class WebConfig implements WebMvcConfigurer { ; } - @Override - public void addInterceptors(InterceptorRegistry registry) { - String[] excludeAuth = {"/error", "/api/auth/*" }; - registry.addInterceptor(authInterceptor) - .addPathPatterns("/**") - .excludePathPatterns(excludeAuth); + @Bean + public FilterRegistrationBean<AuthFilter> authFilterRegistration() { + FilterRegistrationBean<AuthFilter> registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(authFilter); + registrationBean.setOrder(1); // AuthFilter의 순서를 1로 설정 + registrationBean.addUrlPatterns("/*"); + return registrationBean; + } + + @Bean + public FilterRegistrationBean<LoggingFilter> loggingFilterRegistration() { + FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(loggingFilter); + registrationBean.setOrder(2); // LoggingFilter의 순서를 2로 설정 + registrationBean.addUrlPatterns("/*"); + return registrationBean; } @Bean diff --git a/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java b/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java index 36e6f9a8138da9fee0f76d6028770260d9fb5679..999707aed33a9f559d2c396c6532df50aada5173 100644 --- a/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java +++ b/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java @@ -30,8 +30,9 @@ public class ForwardingController { } @GetMapping("/forwardings") - public ResponseEntity<Object> lists(@RequestParam String projectId) { - return ResponseEntity.ok(forwardingService.getForwardings(projectId)); + public ResponseEntity<Object> lists(@RequestParam String projectId, + @RequestParam(required = false) String query) { + return ResponseEntity.ok(forwardingService.getForwardingsWithSearch(projectId, query)); } @PatchMapping("/forwarding") diff --git a/src/main/java/com/aolda/itda/controller/main/MainController.java b/src/main/java/com/aolda/itda/controller/main/MainController.java new file mode 100644 index 0000000000000000000000000000000000000000..371cce5c41e82deb7dbe8bf10ac0c618d52e5a74 --- /dev/null +++ b/src/main/java/com/aolda/itda/controller/main/MainController.java @@ -0,0 +1,33 @@ +package com.aolda.itda.controller.main; + +import com.aolda.itda.service.main.MainService; +import com.fasterxml.jackson.core.JsonProcessingException; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class MainController { + + private final MainService mainService; + + @GetMapping("/projects") + public ResponseEntity<Object> projects(HttpServletRequest request) throws JsonProcessingException { + return ResponseEntity.ok(mainService.getAllProjects((Map<String, String>) request.getSession().getAttribute("user"))); + } + + @GetMapping("/main") + public ResponseEntity<Object> mainInfo(@RequestParam String projectId, HttpServletRequest request) { + return ResponseEntity.ok(mainService.getMainInfo(projectId, (List<String>) request.getAttribute("projects"))); + } + +} diff --git a/src/main/java/com/aolda/itda/controller/routing/RoutingController.java b/src/main/java/com/aolda/itda/controller/routing/RoutingController.java index 4b9d551ada34e56464d8b95e1577adff3f456d36..c96e9106937f891a66aa19b03d4278b61bb824ae 100644 --- a/src/main/java/com/aolda/itda/controller/routing/RoutingController.java +++ b/src/main/java/com/aolda/itda/controller/routing/RoutingController.java @@ -31,8 +31,9 @@ public class RoutingController { } @GetMapping("/routings") - public ResponseEntity<Object> lists(@RequestParam String projectId) { - return ResponseEntity.ok(routingService.getRoutings(projectId)); + public ResponseEntity<Object> lists(@RequestParam String projectId, + @RequestParam(required = false) String query) { + return ResponseEntity.ok(routingService.getRoutingsWithSearch(projectId, query)); } @PatchMapping("/routing") diff --git a/src/main/java/com/aolda/itda/dto/forwarding/ForwardingDTO.java b/src/main/java/com/aolda/itda/dto/forwarding/ForwardingDTO.java index 3302d0a3b20500ec9731028aaa3390c5959a542a..be003b6ca78d1f4e317c391559fd41e2c4a304ad 100644 --- a/src/main/java/com/aolda/itda/dto/forwarding/ForwardingDTO.java +++ b/src/main/java/com/aolda/itda/dto/forwarding/ForwardingDTO.java @@ -1,5 +1,6 @@ package com.aolda.itda.dto.forwarding; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; @@ -40,6 +41,10 @@ public class ForwardingDTO { @NotBlank(message = "name 값이 존재하지 않습니다") private String name; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private LocalDateTime createdAt; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private LocalDateTime updatedAt; } diff --git a/src/main/java/com/aolda/itda/dto/log/LogDTO.java b/src/main/java/com/aolda/itda/dto/log/LogDTO.java index 589545518431c85d093ca695b3cb97778fb9bac0..9946028fa254c24ea5197f8002c610ad02a083b5 100644 --- a/src/main/java/com/aolda/itda/dto/log/LogDTO.java +++ b/src/main/java/com/aolda/itda/dto/log/LogDTO.java @@ -3,6 +3,7 @@ package com.aolda.itda.dto.log; import com.aolda.itda.dto.auth.IdAndNameDTO; import com.aolda.itda.entity.log.Action; import com.aolda.itda.entity.log.ObjectType; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import com.querydsl.core.annotations.QueryProjection; import lombok.AllArgsConstructor; @@ -22,7 +23,11 @@ public class LogDTO { private Action action; private ObjectType type; private Long objectId; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private String description; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private LocalDateTime createdAt; @QueryProjection diff --git a/src/main/java/com/aolda/itda/dto/main/MainInfoDTO.java b/src/main/java/com/aolda/itda/dto/main/MainInfoDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..cbcdcd583831431a778791d15c8551ac834d5fe5 --- /dev/null +++ b/src/main/java/com/aolda/itda/dto/main/MainInfoDTO.java @@ -0,0 +1,20 @@ +package com.aolda.itda.dto.main; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MainInfoDTO { + + private Long routing; + private Long forwarding; + private Long certificate; + +} diff --git a/src/main/java/com/aolda/itda/dto/routing/RoutingDTO.java b/src/main/java/com/aolda/itda/dto/routing/RoutingDTO.java index 6bc48d87f455151d743711bd40037496257fb940..985056ea3771b0a0fc3b7afc2db890c039f4db84 100644 --- a/src/main/java/com/aolda/itda/dto/routing/RoutingDTO.java +++ b/src/main/java/com/aolda/itda/dto/routing/RoutingDTO.java @@ -1,5 +1,6 @@ package com.aolda.itda.dto.routing; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -28,8 +29,10 @@ public class RoutingDTO { @NotNull private Long certificateId; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private LocalDateTime createdAt; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private LocalDateTime updatedAt; @NotNull diff --git a/src/main/java/com/aolda/itda/entity/BaseTimeEntity.java b/src/main/java/com/aolda/itda/entity/BaseTimeEntity.java index 6cfa1fde46f85bae5cf6c3ae7b2e543002db0ad9..b62af84f1afea4909e4af4e7cb72848ddd0feee1 100644 --- a/src/main/java/com/aolda/itda/entity/BaseTimeEntity.java +++ b/src/main/java/com/aolda/itda/entity/BaseTimeEntity.java @@ -17,12 +17,12 @@ import java.time.LocalDateTime; public abstract class BaseTimeEntity { @CreatedDate - @Column(updatable = false) + @Column(updatable = false, columnDefinition = "DATETIME") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private LocalDateTime createdAt; @LastModifiedDate - @Column(name = "updated_at") + @Column(name = "updated_at", columnDefinition = "DATETIME") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private LocalDateTime updatedAt; diff --git a/src/main/java/com/aolda/itda/entity/certificate/Certificate.java b/src/main/java/com/aolda/itda/entity/certificate/Certificate.java index a23cf858c8926a19b36a1b81df45b3b0bfd033f3..85b1f29b492b15fa7e213d6f94ee8f683d55c61e 100644 --- a/src/main/java/com/aolda/itda/entity/certificate/Certificate.java +++ b/src/main/java/com/aolda/itda/entity/certificate/Certificate.java @@ -27,10 +27,13 @@ public class Certificate extends BaseTimeEntity { @JoinColumn(nullable = false, name = "user_id") private User user; + @Column(length = 64) private String projectId; + @Column(length = 64) private String domain; + @Column(length = 64) private String email; private LocalDateTime expiredAt; @@ -40,6 +43,7 @@ public class Certificate extends BaseTimeEntity { private Boolean isDeleted; + @Column(length = 256) private String description; public String formatDomain() { diff --git a/src/main/java/com/aolda/itda/entity/certificate/Challenge.java b/src/main/java/com/aolda/itda/entity/certificate/Challenge.java index 14713bffb86a0eb1a8d67162420bad39d6837b96..41f607e0289f9533cf6bc18c106e9d6037f1abed 100644 --- a/src/main/java/com/aolda/itda/entity/certificate/Challenge.java +++ b/src/main/java/com/aolda/itda/entity/certificate/Challenge.java @@ -1,5 +1,15 @@ package com.aolda.itda.entity.certificate; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; + +@JsonFormat(shape = JsonFormat.Shape.STRING) public enum Challenge { - HTTP, DNS_CLOUDFLARE + HTTP, DNS_CLOUDFLARE; + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(); + } } diff --git a/src/main/java/com/aolda/itda/entity/forwarding/Forwarding.java b/src/main/java/com/aolda/itda/entity/forwarding/Forwarding.java index 0e5e50559860fbebf6cc5378fa91bd6fcefa00fa..0ba96d651f09690952337da5a311e12d8f1f51e6 100644 --- a/src/main/java/com/aolda/itda/entity/forwarding/Forwarding.java +++ b/src/main/java/com/aolda/itda/entity/forwarding/Forwarding.java @@ -20,18 +20,24 @@ public class Forwarding extends BaseTimeEntity { @Column(nullable = false) private Long forwardingId; + @Column(length = 64) private String projectId; + @Column(length = 32) private String serverIp; + @Column(length = 8) private String serverPort; + @Column(length = 32) private String instanceIp; + @Column(length = 8) private String instancePort; private Boolean isDeleted; + @Column(length = 256) private String name; public Forwarding(Forwarding forwarding) { diff --git a/src/main/java/com/aolda/itda/entity/log/Action.java b/src/main/java/com/aolda/itda/entity/log/Action.java index a620f87a62fbb79faaf169690041b21c94c404f1..448a65e6e29e793da43011c1f40fd64b01659dae 100644 --- a/src/main/java/com/aolda/itda/entity/log/Action.java +++ b/src/main/java/com/aolda/itda/entity/log/Action.java @@ -1,5 +1,15 @@ package com.aolda.itda.entity.log; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; + +@JsonFormat(shape = JsonFormat.Shape.STRING) public enum Action { - CREATE, UPDATE, DELETE + CREATE, UPDATE, DELETE; + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(); + } } diff --git a/src/main/java/com/aolda/itda/entity/log/Log.java b/src/main/java/com/aolda/itda/entity/log/Log.java index 9832ab291bdd2b8aaf5a8b6ea2d6823e71a55df7..6bf607497bb95f9ba71ca677dbcb9af5d7b9a36d 100644 --- a/src/main/java/com/aolda/itda/entity/log/Log.java +++ b/src/main/java/com/aolda/itda/entity/log/Log.java @@ -27,16 +27,20 @@ public class Log extends BaseTimeEntity { @JoinColumn(name = "user_id", nullable = false) private User user; + @Column(length = 64) private String projectId; @Enumerated(EnumType.STRING) private ObjectType objectType; + @Column(length = 64) private Long objectId; @Enumerated(EnumType.STRING) private Action action; + @Lob + @Column(length = 1024) private String description; public LogDTO toLogDTO() { diff --git a/src/main/java/com/aolda/itda/entity/log/ObjectType.java b/src/main/java/com/aolda/itda/entity/log/ObjectType.java index 531031551afbfc5af9770dfc66cec886210715f4..c91f27911dfc4c661be7ab343b03c2391ddb1a56 100644 --- a/src/main/java/com/aolda/itda/entity/log/ObjectType.java +++ b/src/main/java/com/aolda/itda/entity/log/ObjectType.java @@ -1,5 +1,15 @@ package com.aolda.itda.entity.log; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; + +@JsonFormat(shape = JsonFormat.Shape.STRING) public enum ObjectType { - ROUTING, CERTIFICATE, FORWARDING + ROUTING, CERTIFICATE, FORWARDING; + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(); + } } diff --git a/src/main/java/com/aolda/itda/entity/routing/Routing.java b/src/main/java/com/aolda/itda/entity/routing/Routing.java index de13d82d149e0ec8c6acd60c162b9e980eb1bc91..94ecb5517358ec3445d5604207c6a28822c3b142 100644 --- a/src/main/java/com/aolda/itda/entity/routing/Routing.java +++ b/src/main/java/com/aolda/itda/entity/routing/Routing.java @@ -28,18 +28,23 @@ public class Routing extends BaseTimeEntity { @JoinColumn(name = "certificate_id") private Certificate certificate; + @Column(length = 64) private String projectId; + @Column(length = 64) private String domain; + @Column(length = 32) private String instanceIp; + @Column(length = 8) private String instancePort; private Boolean isDeleted; private Boolean caching; + @Column(length = 256) private String name; public RoutingDTO toRoutingDTO() { diff --git a/src/main/java/com/aolda/itda/repository/forwarding/ForwardingRepository.java b/src/main/java/com/aolda/itda/repository/forwarding/ForwardingRepository.java index c461762a59bc4cce3ca6fbde0f1de3277a4d7293..9a330f0dbc0fe111dd99aed57e29a960ff9f5031 100644 --- a/src/main/java/com/aolda/itda/repository/forwarding/ForwardingRepository.java +++ b/src/main/java/com/aolda/itda/repository/forwarding/ForwardingRepository.java @@ -2,6 +2,7 @@ package com.aolda.itda.repository.forwarding; import com.aolda.itda.entity.forwarding.Forwarding; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import java.util.List; import java.util.Optional; @@ -11,4 +12,7 @@ public interface ForwardingRepository extends JpaRepository<Forwarding, Long> { Optional<Forwarding> findByForwardingIdAndIsDeleted(Long forwardingId, Boolean isDeleted); Boolean existsByInstanceIpAndInstancePortAndIsDeleted(String instanceIp, String instancePort, Boolean isDeleted); Boolean existsByServerPortAndIsDeleted(String serverPort, Boolean isDeleted); + + @Query("SELECT f FROM Forwarding f WHERE f.projectId = ?1 AND f.isDeleted = ?3 AND (f.instanceIp LIKE %?2% OR f.serverPort LIKE %?2% OR f.name LIKE %?2%)") + List<Forwarding> findWithSearch(String projectId, String query, Boolean isDeleted); } diff --git a/src/main/java/com/aolda/itda/repository/log/LogQueryDSL.java b/src/main/java/com/aolda/itda/repository/log/LogQueryDSL.java index ceba4b235799104d9b590a3d4c47aed6a92e11bc..19feb191a3f4f9dfaab6bb8b8c2925756a69c244 100644 --- a/src/main/java/com/aolda/itda/repository/log/LogQueryDSL.java +++ b/src/main/java/com/aolda/itda/repository/log/LogQueryDSL.java @@ -86,7 +86,7 @@ public class LogQueryDSL { /* 사용자 ID 조건 */ if (username != null) { - builder.and(log.user.keystoneUsername.eq(username)); + builder.and(log.user.keystoneUsername.contains(username)); } /* CUD 조건 */ diff --git a/src/main/java/com/aolda/itda/repository/routing/RoutingRepository.java b/src/main/java/com/aolda/itda/repository/routing/RoutingRepository.java index 046296c2df3937d920b4816734d16ce8267b6a1f..44b88aecdc26ff76583c01149a9862503a0c8d79 100644 --- a/src/main/java/com/aolda/itda/repository/routing/RoutingRepository.java +++ b/src/main/java/com/aolda/itda/repository/routing/RoutingRepository.java @@ -3,6 +3,7 @@ package com.aolda.itda.repository.routing; import com.aolda.itda.entity.forwarding.Forwarding; import com.aolda.itda.entity.routing.Routing; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import java.util.List; import java.util.Optional; @@ -11,4 +12,7 @@ public interface RoutingRepository extends JpaRepository<Routing, Long> { List<Routing> findByProjectIdAndIsDeleted(String projectId, Boolean isDeleted); Optional<Routing> findByRoutingIdAndIsDeleted(Long routingId, Boolean isDeleted); Boolean existsByDomainAndIsDeleted(String domain, 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<Routing> findWithSearch(String projectId, String query, Boolean isDeleted); } diff --git a/src/main/java/com/aolda/itda/service/AuthService.java b/src/main/java/com/aolda/itda/service/AuthService.java index 6a25ebc9aba5f91ec4159eb77b75ccf2998ff098..4c1740ec38b088511b9e4cf68afc54ac71fb365f 100644 --- a/src/main/java/com/aolda/itda/service/AuthService.java +++ b/src/main/java/com/aolda/itda/service/AuthService.java @@ -96,7 +96,6 @@ public class AuthService { try { res = restTemplate.postForEntity(url, requestEntity, Map.class); } catch (Exception e) { - e.printStackTrace(); throw new CustomException(ErrorCode.INVALID_USER_INFO); } Map<String, Object> resToken = (Map<String, Object>) res.getBody().get("token"); @@ -140,7 +139,7 @@ public class AuthService { try { requestEntity = new HttpEntity<>(requestBody, headers); res = restTemplate.postForEntity(url, requestEntity, Map.class); - } catch (RuntimeException e) { + } catch (Exception e) { return null; } @@ -185,8 +184,7 @@ public class AuthService { } catch (HttpClientErrorException.Forbidden e) { return unscopedToken; } - catch (RuntimeException e) { - e.printStackTrace(); + catch (Exception e) { throw new CustomException(ErrorCode.INVALID_TOKEN); } @@ -290,13 +288,40 @@ public class AuthService { ResponseEntity<String> res; try { res = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class); - } catch (HttpClientErrorException.NotFound e) { + } catch (Exception e) { throw new CustomException(ErrorCode.INVALID_TOKEN); } return objectMapper.readTree(res.getBody()).path("token").path("user").path("id").asText(); } + public List<IdAndNameDTO> getAllProjects(String token) throws JsonProcessingException { + String url = keystone + "/projects"; + HttpHeaders headers = new HttpHeaders(); + headers.set("X-Auth-Token", token); + HttpEntity<String> requestEntity = new HttpEntity<>(headers); + ResponseEntity<String> res; + try { + res = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class); + } catch (Exception e) { + throw new CustomException(ErrorCode.INVALID_TOKEN); + } + + JsonNode node = objectMapper.readTree(res.getBody()); + ArrayNode arrayNode = (ArrayNode) node.get("projects"); + + List<IdAndNameDTO> lists = new ArrayList<>(); + + for (JsonNode assignment : arrayNode) { + String projectId = assignment.path("id").asText(); + String projectName = assignment.path("name").asText(); + lists.add(new IdAndNameDTO(projectId, projectName)); + } + + return lists; + + } + public void validateProjectAuth(List<String> projects, String projectId) { if (projects != null && !projects.contains(projectId)) { throw new CustomException(ErrorCode.UNAUTHORIZED_USER); @@ -311,8 +336,7 @@ public class AuthService { ResponseEntity<String> res; try { res = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class); - } catch (RuntimeException e) { - e.printStackTrace(); + } catch (Exception e) { return false; } JsonNode node = objectMapper.readTree(res.getBody()).path("role_assignments"); diff --git a/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java b/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java index cae38c3229e0d8d221136c726f4452d315dbe8ec..e6fc9d45ee66e4f74ac7898bd2c43de0544de86a 100644 --- a/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java +++ b/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java @@ -27,6 +27,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.List; +import java.util.regex.Pattern; @Service @Transactional @@ -52,11 +53,20 @@ public class ForwardingService { return forwarding.toForwardingDTO(); } - /* 포트포워딩 목록 조회 */ - public PageResp<ForwardingDTO> getForwardings(String projectId) { + /* 포트포워딩 목록 조회 + 검색 */ + public PageResp<ForwardingDTO> getForwardingsWithSearch(String projectId, String query) { + + /* 입력 검증 */ + if (query == null || query.isBlank()) { + return PageResp.<ForwardingDTO>builder() + .contents(forwardingRepository.findByProjectIdAndIsDeleted(projectId, false) + .stream() + .map(Forwarding::toForwardingDTO) + .toList()).build(); + } return PageResp.<ForwardingDTO>builder() - .contents(forwardingRepository.findByProjectIdAndIsDeleted(projectId, false) + .contents(forwardingRepository.findWithSearch(projectId, query, false) .stream() .map(Forwarding::toForwardingDTO) .toList()).build(); diff --git a/src/main/java/com/aolda/itda/service/main/MainService.java b/src/main/java/com/aolda/itda/service/main/MainService.java new file mode 100644 index 0000000000000000000000000000000000000000..8a4345eb4ab48685fbd7e80e5b6900bbd433bffa --- /dev/null +++ b/src/main/java/com/aolda/itda/service/main/MainService.java @@ -0,0 +1,61 @@ +package com.aolda.itda.service.main; + +import com.aolda.itda.dto.PageResp; +import com.aolda.itda.dto.auth.IdAndNameDTO; +import com.aolda.itda.dto.main.MainInfoDTO; +import com.aolda.itda.repository.certificate.CertificateRepository; +import com.aolda.itda.repository.forwarding.ForwardingRepository; +import com.aolda.itda.repository.routing.RoutingRepository; +import com.aolda.itda.service.AuthService; +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; + +@Service +@Transactional +@RequiredArgsConstructor +public class MainService { + + private final AuthService authService; + private final RoutingRepository routingRepository; + private final ForwardingRepository forwardingRepository; + private final CertificateRepository certificateRepository; + + /* 메인 페이지에 필요한 정보 반환 */ + public MainInfoDTO getMainInfo(String projectId, List<String> projects) { + + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, projectId); + + /* 카운팅 */ + Long routing = (long) routingRepository.findByProjectIdAndIsDeleted(projectId, false).size(); + Long forwarding = (long) forwardingRepository.findByProjectIdAndIsDeleted(projectId, false).size(); + Long certificate = 0L; + + return MainInfoDTO.builder() + .routing(routing) + .forwarding(forwarding) + .certificate(certificate) + .build(); + } + + /* 접근 가능한 프로젝트 조회 */ + public PageResp<IdAndNameDTO> getAllProjects(Map<String, String> user) throws JsonProcessingException { + + List<IdAndNameDTO> projects; + if (authService.isAdmin(user)) { + projects = authService.getAllProjects(user.get("token")); + } + + else { + projects = authService.getProjectsWithUser(user); + } + + return PageResp.<IdAndNameDTO>builder() + .contents(projects).build(); + } +} diff --git a/src/main/java/com/aolda/itda/service/routing/RoutingService.java b/src/main/java/com/aolda/itda/service/routing/RoutingService.java index 769278d2c6aca1a1f7e4f7afb11a3281e8e7fd07..d01eb022236db9162bee3b62907bcca07fbfa05f 100644 --- a/src/main/java/com/aolda/itda/service/routing/RoutingService.java +++ b/src/main/java/com/aolda/itda/service/routing/RoutingService.java @@ -28,6 +28,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.List; +import java.util.regex.Pattern; @Service @Transactional @@ -52,10 +53,26 @@ public class RoutingService { return routing.toRoutingDTO(); } - /* Routing 목록 조회 */ - public PageResp<RoutingDTO> getRoutings(String projectId) { + /* Routing 목록 조회 + 검색 */ + public PageResp<RoutingDTO> getRoutingsWithSearch(String projectId, String query) { + + /* 입력 검증 */ + if (query == null || query.isBlank()) { + return PageResp.<RoutingDTO>builder() + .contents(routingRepository.findByProjectIdAndIsDeleted(projectId, false) + .stream() + .map(Routing::toRoutingDTO) + .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.<RoutingDTO>builder() - .contents(routingRepository.findByProjectIdAndIsDeleted(projectId, false) + .contents(routingRepository.findWithSearch(projectId, query, false) .stream() .map(Routing::toRoutingDTO) .toList()).build(); diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index a8acbeebcfc82e73b691923bbf927ec007d9648e..39e9337fdac44b059f0a540fc6764747ed91d193 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -1,90 +1,90 @@ -<?xml version="1.0" encoding="UTF-8"?> -<configuration> - - <property name="MAX_FILE_SIZE" value="10MB" /> - <property name="TOTAL_SIZE" value="1GB" /> - <property name="MAX_HISTORY" value="30" /> - - <!-- 콘솔에 출력할 로그 형식 --> - <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> - <encoder> - <pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%thread] %-5level %logger{36} - %msg%n</pattern> - </encoder> - </appender> - - <!-- INFO 로그 파일 저장 (1개당 10MB, 5개까지 유지, 이후 압축) --> - <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> - <file>/data/logs/info.log</file> - <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> - <fileNamePattern>/data/logs/info.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> - <maxHistory>${MAX_HISTORY}</maxHistory> - <totalSizeCap>${TOTAL_SIZE}</totalSizeCap> - <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> - <maxFileSize>${MAX_FILE_SIZE}</maxFileSize> - </timeBasedFileNamingAndTriggeringPolicy> - </rollingPolicy> - <encoder> - <pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%thread] %-5level %logger{36} - %msg%n</pattern> - </encoder> - <!-- INFO 레벨만 허용 --> - <filter class="ch.qos.logback.classic.filter.LevelFilter"> - <level>INFO</level> - <onMatch>ACCEPT</onMatch> - <onMismatch>DENY</onMismatch> - </filter> - </appender> - - <!-- WARN 로그 파일 저장 --> - <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> - <file>/data/logs/warn.log</file> - <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> - <fileNamePattern>/data/logs/warn.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> - <maxHistory>${MAX_HISTORY}</maxHistory> - <totalSizeCap>${TOTAL_SIZE}</totalSizeCap> - <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> - <maxFileSize>${MAX_FILE_SIZE}</maxFileSize> - </timeBasedFileNamingAndTriggeringPolicy> - </rollingPolicy> - <encoder> - <pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%thread] %-5level %logger{36} - %msg%n</pattern> - </encoder> - <!-- WARN 레벨만 허용 --> - <filter class="ch.qos.logback.classic.filter.LevelFilter"> - <level>WARN</level> - <onMatch>ACCEPT</onMatch> - <onMismatch>DENY</onMismatch> - </filter> - </appender> - - <!-- ERROR 로그 파일 저장 --> - <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> - <file>/data/logs/error.log</file> - <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> - <fileNamePattern>/data/logs/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> - <maxHistory>${MAX_HISTORY}</maxHistory> - <totalSizeCap>${TOTAL_SIZE}</totalSizeCap> - <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> - <maxFileSize>${MAX_FILE_SIZE}</maxFileSize> - </timeBasedFileNamingAndTriggeringPolicy> - </rollingPolicy> - <encoder> - <pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%thread] %-5level %logger{36} - %msg%n</pattern> - </encoder> - <!-- ERROR 레벨만 허용 --> - <filter class="ch.qos.logback.classic.filter.LevelFilter"> - <level>ERROR</level> - <onMatch>ACCEPT</onMatch> - <onMismatch>DENY</onMismatch> - </filter> - </appender> - - <logger name="com.aolda.itda" additivity="false"> - <!-- 각 Appender 참조 (필터는 Appender 내부에 정의됨) --> - <appender-ref ref="INFO_FILE"/> - <appender-ref ref="WARN_FILE"/> - <appender-ref ref="ERROR_FILE"/> - <!-- 콘솔 출력 --> - <appender-ref ref="CONSOLE"/> - </logger> - +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + + <property name="MAX_FILE_SIZE" value="10MB" /> + <property name="TOTAL_SIZE" value="1GB" /> + <property name="MAX_HISTORY" value="30" /> + + <!-- 콘솔에 출력할 로그 형식 --> + <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%thread] %-5level %logger{36} - %msg%n</pattern> + </encoder> + </appender> + + <!-- INFO 로그 파일 저장 (1개당 10MB, 5개까지 유지, 이후 압축) --> + <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <file>/data/logs/info.log</file> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <fileNamePattern>/data/logs/info.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> + <maxHistory>${MAX_HISTORY}</maxHistory> + <totalSizeCap>${TOTAL_SIZE}</totalSizeCap> + <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> + <maxFileSize>${MAX_FILE_SIZE}</maxFileSize> + </timeBasedFileNamingAndTriggeringPolicy> + </rollingPolicy> + <encoder> + <pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%thread] %-5level %logger{36} - %msg%n</pattern> + </encoder> + <!-- INFO 레벨만 허용 --> + <filter class="ch.qos.logback.classic.filter.LevelFilter"> + <level>INFO</level> + <onMatch>ACCEPT</onMatch> + <onMismatch>DENY</onMismatch> + </filter> + </appender> + + <!-- WARN 로그 파일 저장 --> + <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <file>/data/logs/warn.log</file> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <fileNamePattern>/data/logs/warn.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> + <maxHistory>${MAX_HISTORY}</maxHistory> + <totalSizeCap>${TOTAL_SIZE}</totalSizeCap> + <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> + <maxFileSize>${MAX_FILE_SIZE}</maxFileSize> + </timeBasedFileNamingAndTriggeringPolicy> + </rollingPolicy> + <encoder> + <pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%thread] %-5level %logger{36} - %msg%n</pattern> + </encoder> + <!-- WARN 레벨만 허용 --> + <filter class="ch.qos.logback.classic.filter.LevelFilter"> + <level>WARN</level> + <onMatch>ACCEPT</onMatch> + <onMismatch>DENY</onMismatch> + </filter> + </appender> + + <!-- ERROR 로그 파일 저장 --> + <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <file>/data/logs/error.log</file> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <fileNamePattern>/data/logs/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> + <maxHistory>${MAX_HISTORY}</maxHistory> + <totalSizeCap>${TOTAL_SIZE}</totalSizeCap> + <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> + <maxFileSize>${MAX_FILE_SIZE}</maxFileSize> + </timeBasedFileNamingAndTriggeringPolicy> + </rollingPolicy> + <encoder> + <pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%thread] %-5level %logger{36} - %msg%n</pattern> + </encoder> + <!-- ERROR 레벨만 허용 --> + <filter class="ch.qos.logback.classic.filter.LevelFilter"> + <level>ERROR</level> + <onMatch>ACCEPT</onMatch> + <onMismatch>DENY</onMismatch> + </filter> + </appender> + + <logger name="com.aolda.itda" additivity="false"> + <!-- 각 Appender 참조 (필터는 Appender 내부에 정의됨) --> + <appender-ref ref="INFO_FILE"/> + <appender-ref ref="WARN_FILE"/> + <appender-ref ref="ERROR_FILE"/> + <!-- 콘솔 출력 --> + <appender-ref ref="CONSOLE"/> + </logger> + </configuration> \ No newline at end of file