Conversation
* [feat]: fcm 메세지 분류용 messageType 필드 추가 * [feat]: 테스트 학사일정 알림 전송 api 구현 * [test]: 테스트 학사일정 알림 전송 api 테스트 구현 * [refactor]: MessageType enum화 * [refactor]: 학사일정 테스트 step 객체로 관리 * [refactor]: putData 맵 하나로 통합
개요관리자 API에 학사일정 테스트 알림 발송 엔드포인트가 추가되었으며, 메시지 타입 구분을 위한 열거형이 도입되었습니다. 또한 부동산학과 공지사항 파싱 로직이 일반화된 최신 페이지 파서로 통합되었습니다. 변경사항
시퀀스 다이어그램sequenceDiagram
participant Client
participant AdminAPI as Admin API<br/>Controller
participant AdminService as Admin<br/>Command Service
participant EventAdapter as Firebase<br/>Message Adapter
participant EventBus as Spring<br/>Event Bus
participant MessageListener as Message Admin<br/>Event Listener
participant FirebaseService as Firebase<br/>Notification Service
participant Firebase as Firebase<br/>Cloud Messaging
Client->>AdminAPI: POST /api/v2/admin/academic/dev<br/>(title, body)
AdminAPI->>AdminService: createAcademicTestNotification<br/>(command)
AdminService->>EventAdapter: sendAcademicTestNotification<br/>(title, body)
EventAdapter->>EventBus: raise<br/>AcademicTestNotificationEvent
EventBus->>MessageListener: sendAcademicTestNotificationEvent<br/>(event)
MessageListener->>FirebaseService: sendAcademicTestNotification<br/>(command)
FirebaseService->>Firebase: send(message)<br/>to ACADEMIC_EVENT_TOPIC_dev
Firebase-->>FirebaseService: success/exception
FirebaseService-->>MessageListener: complete
MessageListener-->>EventBus: async complete
EventBus-->>EventAdapter: event processed
AdminService-->>AdminAPI: success
AdminAPI-->>Client: 200 OK<br/>success response
관련된 가능성 있는 PR
제안된 레이블
시
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java (1)
1-209: RealEstateDept에 대한 테스트 커버리지를 추가하세요.
RealEstateNoticeHtmlParser관련 테스트가 제거되었지만,LatestPageNoticeHtmlParser가 이제 RealEstateDept를 지원하는지 검증하는 테스트가 추가되지 않았습니다. RealEstateDept의 HTML을 사용하여LatestPageNoticeHtmlParser.parse()가 올바르게 작동하는지 확인하는 테스트를 추가하는 것을 권장합니다.🔎 테스트 추가 예시
src/test/resources/notice/디렉토리에real-estate-notice-2024.html파일을 추가하고, 다음과 같은 테스트를 추가하세요:@DisplayName("부동산학과 공지사항을 LatestPageNoticeHtmlParser로 분석한다") @Test void RealEstateNoticeWithLatestPageParser() throws IOException { // given Document doc = Jsoup.parse(TestFileLoader.loadHtmlFile("src/test/resources/notice/real-estate-notice-2024.html")); String viewUrl = "https://realestate.konkuk.ac.kr/bbs/realestate/{siteId}/{noticeId}/artclView.do"; // when RowsDto rowsDto = new LatestPageNoticeHtmlParser().parse(doc); List<CommonNoticeFormatDto> important = rowsDto.buildImportantRowList(viewUrl); List<CommonNoticeFormatDto> normal = rowsDto.buildNormalRowList(viewUrl); // then assertAll( () -> assertThat(important).isNotEmpty(), () -> assertThat(normal).isNotEmpty() // 실제 데이터에 맞는 검증 추가 ); }
🧹 Nitpick comments (3)
src/main/java/com/kustacks/kuring/message/application/service/FirebaseNotificationService.java (1)
70-90: 데이터 페이로드 생성 방식을 다른 메서드와 일관되게 개선하세요.이 메서드는
Map.of()를 사용하여 데이터 페이로드를 수동으로 생성하지만, 같은 파일의createMessageFromCommand(라인 157)와createMessageFromDto(라인 170)는objectMapper.convertValue()를 사용합니다.일관성 문제:
Map.of()는 null 값이 있으면NullPointerException을 발생시킵니다objectMapper방식은 null을 안전하게 처리하고 command의 모든 필드를 자동으로 포함합니다- 수동 매핑은 향후 필드가 추가될 때 누락될 위험이 있습니다
🔎 objectMapper를 사용한 리팩토링 제안
@Override public void sendAcademicTestNotification(AcademicTestNotificationCommand command) { try { Message newMessage = Message.builder() .setNotification(Notification.builder() .setTitle(command.title()) .setBody(command.body()) .build()) - .putAllData(Map.of( - "title", command.title(), - "body", command.body(), - "messageType", ACADEMIC.getValue() - )) + .putAllData(objectMapper.convertValue(command, Map.class)) + .putData("messageType", ACADEMIC.getValue()) .setTopic(serverProperties.addDevSuffix(ACADEMIC_EVENT_TOPIC)) .build(); firebaseMessagingPort.send(newMessage); } catch (FirebaseMessagingException exception) { throw new FirebaseMessageSendException(); } }src/main/java/com/kustacks/kuring/notice/domain/DepartmentName.java (1)
56-56: 호스트 프리픽스 변경에 대한 실제 영향 범위 재검토 필요코드베이스 검증 결과, 모든
REAL_ESTATE참조는getHostPrefix()메서드를 통해 동적으로 호출되고 있으며, 하드코딩된"rest"프리픽스 참조는 발견되지 않았습니다.HOST_PREFIX_MAP은 enum 값으로부터 자동 생성되고, 모든 부서 클래스가 동일한 패턴을 따르고 있습니다.코드 레벨에서는 프리픽스 변경이 안전하게 전파되지만, 다음 영역에 대해서만 확인이 필요합니다:
- 외부 시스템이나 클라이언트에서
"rest"프리픽스를 하드코딩하여 참조하는 경우- 배포 후 기존 캐시 또는 외부 데이터 소스 검증
코드 내 하드코딩된 참조는 없으므로, 외부 통합점과 배포 절차에 대한 확인으로 충분합니다.
src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParser.java (1)
13-15: 사용되지 않는 파라미터를 제거하는 것을 고려하세요.
deptInfo파라미터가 더 이상 사용되지 않습니다. 이는 인터페이스나 추상 클래스의 메서드를 오버라이드하는 경우라면 불가피하지만, 그렇지 않다면 제거를 고려해볼 수 있습니다.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (26)
src/main/java/com/kustacks/kuring/admin/adapter/in/web/AdminCommandApiV2.javasrc/main/java/com/kustacks/kuring/admin/adapter/in/web/dto/AcademicTestNotificationRequest.javasrc/main/java/com/kustacks/kuring/admin/adapter/out/event/AdminFirebaseMessageAdapter.javasrc/main/java/com/kustacks/kuring/admin/application/port/in/AdminCommandUseCase.javasrc/main/java/com/kustacks/kuring/admin/application/port/out/AdminEventPort.javasrc/main/java/com/kustacks/kuring/admin/application/service/AdminCommandService.javasrc/main/java/com/kustacks/kuring/calendar/application/service/AcademicEventNotificationService.javasrc/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.javasrc/main/java/com/kustacks/kuring/message/adapter/in/event/MessageAdminEventListener.javasrc/main/java/com/kustacks/kuring/message/adapter/in/event/dto/AcademicTestNotificationEvent.javasrc/main/java/com/kustacks/kuring/message/application/port/in/FirebaseWithAdminUseCase.javasrc/main/java/com/kustacks/kuring/message/application/port/in/dto/AcademicTestNotificationCommand.javasrc/main/java/com/kustacks/kuring/message/application/service/FirebaseNotificationService.javasrc/main/java/com/kustacks/kuring/message/domain/MessageType.javasrc/main/java/com/kustacks/kuring/notice/domain/DepartmentName.javasrc/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParser.javasrc/main/java/com/kustacks/kuring/worker/parser/notice/RealEstateNoticeHtmlParser.javasrc/main/java/com/kustacks/kuring/worker/scrap/client/notice/RealEstateNoticeApiClient.javasrc/main/java/com/kustacks/kuring/worker/scrap/client/notice/property/RealEstateNoticeProperties.javasrc/main/java/com/kustacks/kuring/worker/scrap/deptinfo/real_estate/RealEstateDept.javasrc/main/java/com/kustacks/kuring/worker/update/notice/DepartmentGraduationNoticeUpdater.javasrc/main/java/com/kustacks/kuring/worker/update/notice/DepartmentNoticeUpdater.javasrc/main/resources/config/environments/common.ymlsrc/test/java/com/kustacks/kuring/acceptance/AdminAcceptanceTest.javasrc/test/java/com/kustacks/kuring/acceptance/AdminStep.javasrc/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java
💤 Files with no reviewable changes (6)
- src/main/java/com/kustacks/kuring/worker/parser/notice/RealEstateNoticeHtmlParser.java
- src/main/resources/config/environments/common.yml
- src/main/java/com/kustacks/kuring/worker/update/notice/DepartmentNoticeUpdater.java
- src/main/java/com/kustacks/kuring/worker/update/notice/DepartmentGraduationNoticeUpdater.java
- src/main/java/com/kustacks/kuring/worker/scrap/client/notice/RealEstateNoticeApiClient.java
- src/main/java/com/kustacks/kuring/worker/scrap/client/notice/property/RealEstateNoticeProperties.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java (1)
src/main/java/com/kustacks/kuring/worker/parser/notice/RowsDto.java (1)
RowsDto(10-55)
src/test/java/com/kustacks/kuring/acceptance/AdminAcceptanceTest.java (1)
src/test/java/com/kustacks/kuring/acceptance/AdminStep.java (1)
AdminStep(14-157)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build and analyze
🔇 Additional comments (23)
src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java (2)
33-33: 새로운 응답 코드 추가가 적절합니다.
ADMIN_TEST_ACADEMIC_CREATE_SUCCESS상수가 기존 패턴(Line 27-28의 다른 ADMIN_TEST_ 상수들)과 일관되게 추가되었으며, 메시지가 명확합니다.
56-56: 포매팅 변경이 확인되었습니다.의미론적 변경 없이 포매팅만 개선되었습니다.
src/test/java/com/kustacks/kuring/acceptance/AdminAcceptanceTest.java (1)
155-175: 학사일정 테스트 알림 acceptance 테스트가 적절합니다.테스트가 given-when-then 패턴을 따르며, 다른 테스트들(Line 98-122, Line 129-153)과 구조적으로 일관성이 있습니다. 응답 검증도 적절하게 수행됩니다.
src/main/java/com/kustacks/kuring/calendar/application/service/AcademicEventNotificationService.java (1)
19-19: FCM 메시지에 메시지 타입 필드 추가는 적절합니다.ACADEMIC 타입의 메시지를 분류하기 위해
messageType필드를 FCM 메시지에 추가한 변경사항입니다. 이는 기존의 ADMIN 및 NOTICE 타입과 함께 일관성 있게 적용되는 패턴입니다.
MessageType.ACADEMICenum 상수가 올바르게 정의됨 (값: "academic")getValue()메서드가 Lombok의@Getter를 통해 제공됨- Static import가 유효한 상수를 올바르게 참조함
- 다른 알림 서비스(
FirebaseNotificationService)와 동일한 패턴으로 구현됨src/main/java/com/kustacks/kuring/admin/application/port/out/AdminEventPort.java (1)
17-17: 학사일정 알림용 포트 메서드가 적절히 추가되었습니다.인터페이스 메서드
sendAcademicTestNotification의 설계가 예상대로 구현되어 있습니다. 구현체(AdminFirebaseMessageAdapter)에서AcademicTestNotificationEvent(title, body)를 발행하므로 URL 파라미터가 필요 없으며, 학사일정 알림 특성상FirebaseMessageSendException발생 및 선언이 불필요합니다. 메서드 시그니처는 구현 요구사항과 일치하고 기존 이벤트 기반 패턴을 일관되게 따릅니다.src/main/java/com/kustacks/kuring/admin/application/service/AdminCommandService.java (1)
86-92: 테스트 학사일정 알림 메서드 구현이 명확하며 다른 admin 메서드들과 패턴이 일관적입니다.비밀번호 검증이 누락된 것처럼 보이나, 이는 설계 의도입니다. 엔드포인트를 확인해보니
/notices/dev(테스트 공지)와/academic/dev(테스트 학사일정)는 역할 기반 접근 제어(@Secured(AdminRole.ROLE_ROOT))로만 보호되고,/notices/prod(실제 공지)만 추가로 비밀번호 검증을 수행합니다. 테스트 엔드포인트에서는 개발 목적상 추가 검증을 요구하지 않는 일관된 패턴이므로 현재 구현이 적절합니다.src/main/java/com/kustacks/kuring/admin/adapter/out/event/AdminFirebaseMessageAdapter.java (2)
5-5: LGTM!새로운 이벤트 타입에 대한 import가 올바르게 추가되었습니다.
42-45: 이 코드는 문제가 없습니다. 예외 처리 일관성이 확인되었습니다.
sendAcademicTestNotification이FirebaseMessageSendException을 선언하지 않은 것은 의도적인 설계 선택입니다. AdminEventPort 인터페이스(라인 17)에서 명확하게 예외를 선언하지 않으며, 이는 메서드가 비동기 이벤트 발행 패턴(Events.raise())을 사용하기 때문입니다. 반면sendTestNotificationByAdmin(라인 8-15)은 동기식 직접 실행이므로 예외를 선언합니다. 두 메서드는 서로 다른 아키텍처 패턴을 따르고 있으며, 구현이 인터페이스와 정확히 일치합니다.src/main/java/com/kustacks/kuring/admin/application/port/in/AdminCommandUseCase.java (1)
7-7: LGTM!인터페이스 메서드 추가가 기존 패턴을 잘 따르고 있으며, 명명 규칙도 일관성 있게 적용되었습니다.
Also applies to: 23-24
src/main/java/com/kustacks/kuring/message/adapter/in/event/dto/AcademicTestNotificationEvent.java (1)
1-12: LGTM!이벤트 레코드가 간결하게 잘 구현되었으며,
toCommand()메서드를 통한 변환 로직도 명확합니다.src/test/java/com/kustacks/kuring/acceptance/AdminStep.java (1)
3-3: LGTM!테스트 헬퍼 메서드가 파일의 기존 패턴을 일관성 있게 따르고 있으며, RestAssured 설정도 적절합니다.
Also applies to: 142-155
src/main/java/com/kustacks/kuring/message/domain/MessageType.java (1)
1-15: ACADEMIC 타입의 통합이 올바르게 구현되었습니다.검증 결과, MessageType 열거형의 새로운 ACADEMIC 타입이 코드베이스에 일관되게 적용되어 있습니다. switch 문이 사용되지 않으므로 누락된 케이스가 없으며, 모든 메시지 타입(NOTICE, ADMIN, ACADEMIC)에서 getValue() 메서드가 일관되게 호출됩니다. FirebaseNotificationService와 AcademicEventNotificationService에서 ACADEMIC 타입이 올바르게 임포트되고 사용되고 있습니다.
src/main/java/com/kustacks/kuring/admin/adapter/in/web/dto/AcademicTestNotificationRequest.java (1)
9-11: LGTM!커맨드 변환 로직이 명확하고 단순합니다.
src/main/java/com/kustacks/kuring/message/application/port/in/FirebaseWithAdminUseCase.java (1)
3-3: LGTM!새로운 메서드 추가가 기존 인터페이스 패턴과 일관성 있게 구현되었습니다.
Also applies to: 12-12
src/main/java/com/kustacks/kuring/message/application/port/in/dto/AcademicTestNotificationCommand.java (1)
3-6: LGTM!커맨드 DTO가 단순하고 명확합니다. 다만 입력 검증은 어댑터 레이어(AcademicTestNotificationRequest)에서 수행되어야 합니다.
src/main/java/com/kustacks/kuring/admin/adapter/in/web/AdminCommandApiV2.java (1)
80-83: LGTM!엔드포인트의 보안 설정과 API 문서화가 적절합니다. ROLE_ROOT 권한으로 제한되어 있고, 테스트 용도임이 명확하게 표시되어 있습니다.
src/main/java/com/kustacks/kuring/message/adapter/in/event/MessageAdminEventListener.java (1)
42-48: @async 사용이 sendTestNotificationEvent와는 일관성 있지만, sendAlertEvent와의 차이를 명확히 문서화하세요.
sendAcademicTestNotificationEvent는sendTestNotificationEvent와 동일하게@Async로 표시되어 있습니다. 다만, 같은 클래스의sendAlertEvent는 동기 방식으로 처리됩니다.
- sendNotificationEvent: @async
- sendTestNotificationEvent: @async
- sendAcademicTestNotificationEvent: @async ✓
- sendAlertEvent: 동기
테스트 알림의 비동기 처리가 의도된 설계인지, 아니면 alert 처리 시간 임계성에 따른 구분인지 코드나 문서에 명시되지 않았습니다. 현재 구현은 테스트 알림끼리는 일관성 있으나, 이러한 선택 기준을 주석으로 명확히 기록해야 합니다.
src/main/java/com/kustacks/kuring/message/application/service/FirebaseNotificationService.java (2)
158-158: 좋은 개선 사항입니다!관리자 알림에
messageType필드를 추가하여 클라이언트가 메시지 유형을 구분할 수 있게 되었습니다. 구현이 명확하고 올바릅니다.
171-171: 좋은 개선 사항입니다!공지 알림에
messageType필드를 추가하여 클라이언트가 메시지 유형을 구분할 수 있게 되었습니다. 라인 158의 관리자 알림과 일관된 패턴을 따르고 있습니다.src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/real_estate/RealEstateDept.java (3)
3-3: LGTM! 특정 클래스에서 범용 클래스로의 리팩토링
RealEstateNoticeApiClient및RealEstateNoticeHtmlParser에서LatestPageNoticeApiClient및LatestPageNoticeHtmlParser로의 전환은 코드 재사용성을 개선하는 좋은 리팩토링입니다. 생성자 파라미터가 올바르게 업데이트되었고 필드 할당도 일치합니다.Also applies to: 5-5, 18-26
30-30: LGTM! 하드코딩된 값 제거로 유지보수성 향상
staffScrapInfo초기화에서 하드코딩된"kure"문자열을REAL_ESTATE.getHostPrefix()로 대체한 것은 좋은 개선입니다. 이제 라인 31, 33과 일관된 패턴을 따르며 DRY 원칙을 준수합니다.
34-34: LGTM! 누락된 필드 할당 추가
latestPageGraduateNoticeApiClient필드 할당이 적절하게 추가되었습니다. 생성자 파라미터가 올바르게 필드에 할당됩니다.src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java (1)
4-6: 명시적 임포트로 변경한 점이 좋습니다.와일드카드 임포트를 명시적 임포트로 변경하여 코드 가독성과 유지보수성이 향상되었습니다.
| @Operation(summary = "테스트 학사일정 알림 전송", description = "테스트 학사일정 알림을 전송합니다, 실제 운영시 사용하지 않습니다") | ||
| @SecurityRequirement(name = "JWT") | ||
| @Secured(AdminRole.ROLE_ROOT) | ||
| @PostMapping("/academic/dev") | ||
| public ResponseEntity<BaseResponse<String>> sendAcademicTest( | ||
| @RequestBody AcademicTestNotificationRequest request | ||
| ) { | ||
| adminCommandUseCase.createAcademicTestNotification(request.toCommand()); | ||
| return ResponseEntity.ok().body(new BaseResponse<>(ADMIN_TEST_ACADEMIC_CREATE_SUCCESS, null)); | ||
| } |
There was a problem hiding this comment.
요청 검증 어노테이션이 누락되었습니다.
@RequestBody에 @Valid 어노테이션이 없어 AcademicTestNotificationRequest의 검증 규칙이 실행되지 않습니다. 이는 첫 번째 파일에서 지적한 검증 문제와 연관되어 있습니다.
🔎 수정 제안
+import jakarta.validation.Valid;
+
@Operation(summary = "테스트 학사일정 알림 전송", description = "테스트 학사일정 알림을 전송합니다, 실제 운영시 사용하지 않습니다")
@SecurityRequirement(name = "JWT")
@Secured(AdminRole.ROLE_ROOT)
@PostMapping("/academic/dev")
public ResponseEntity<BaseResponse<String>> sendAcademicTest(
- @RequestBody AcademicTestNotificationRequest request
+ @Valid @RequestBody AcademicTestNotificationRequest request
) {
adminCommandUseCase.createAcademicTestNotification(request.toCommand());
return ResponseEntity.ok().body(new BaseResponse<>(ADMIN_TEST_ACADEMIC_CREATE_SUCCESS, null));
}🤖 Prompt for AI Agents
In src/main/java/com/kustacks/kuring/admin/adapter/in/web/AdminCommandApiV2.java
around lines 80 to 89, the controller method parameter for
AcademicTestNotificationRequest is missing the @Valid annotation so its
validation constraints won't run; add @Valid before @RequestBody (i.e., change
to @Valid @RequestBody AcademicTestNotificationRequest request) and ensure the
correct javax.validation.Valid or jakarta.validation.Valid import is present at
the top of the file.
| public record AcademicTestNotificationRequest( | ||
| String title, | ||
| String body | ||
| ) { |
There was a problem hiding this comment.
입력 검증이 누락되었습니다.
title과 body 필드에 검증 어노테이션이 없어 null이나 빈 문자열이 전달될 수 있습니다. 이는 Firebase 알림 전송 시 런타임 오류를 발생시킬 수 있습니다.
🔎 검증 추가를 위한 제안
package com.kustacks.kuring.admin.adapter.in.web.dto;
import com.kustacks.kuring.message.application.port.in.dto.AcademicTestNotificationCommand;
+import jakarta.validation.constraints.NotBlank;
public record AcademicTestNotificationRequest(
+ @NotBlank(message = "제목은 필수입니다")
String title,
+ @NotBlank(message = "본문은 필수입니다")
String body
) {
public AcademicTestNotificationCommand toCommand() {
return new AcademicTestNotificationCommand(title, body);
}
}API 컨트롤러에도 @Valid 어노테이션을 추가해야 합니다:
public ResponseEntity<BaseResponse<String>> sendAcademicTest(
@Valid @RequestBody AcademicTestNotificationRequest request
)🤖 Prompt for AI Agents
In
src/main/java/com/kustacks/kuring/admin/adapter/in/web/dto/AcademicTestNotificationRequest.java
around lines 5 to 8, the record lacks input validation for title and body
allowing null/empty values; add validation annotations (e.g. @NotBlank) to both
fields and import the appropriate javax.validation constraints so the request
object enforces non-null/non-empty strings, and update the API controller method
that receives this DTO to annotate the parameter with @Valid (and keep
@RequestBody) so Spring triggers validation on incoming requests.
| public boolean support(DeptInfo deptInfo) { | ||
| return !(deptInfo instanceof RealEstateDept); | ||
| return true; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 파일 구조 탐색
echo "=== 프로젝트 구조 ==="
find . -type f -name "*.java" | grep -E "(RealEstate|LatestPageNotice)" | head -20
echo -e "\n=== 전체 parser 관련 파일 목록 ==="
git ls-files | grep -E "parser.*notice" | head -20Repository: ku-ring/ku-ring-backend-web
Length of output: 1344
🏁 Script executed:
#!/bin/bash
# RealEstateDept 정의 확인
echo "=== RealEstateDept.java 내용 ==="
cat -n ./src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/real_estate/RealEstateDept.java
echo -e "\n=== LatestPageNoticeHtmlParser.java 전체 내용 ==="
cat -n ./src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParser.javaRepository: ku-ring/ku-ring-backend-web
Length of output: 3781
🏁 Script executed:
#!/bin/bash
# 테스트 파일 확인
echo "=== LatestPageNoticeApiClientTest.java 내용 ==="
cat -n ./src/test/java/com/kustacks/kuring/worker/scrap/client/notice/LatestPageNoticeApiClientTest.java
echo -e "\n=== 다른 parser 구현 비교 (NoticeHtmlParserTemplate 및 다른 구현체) ==="
rg -nA 20 "class.*NoticeHtmlParser" src/main/java/com/kustacks/kuring/worker/parser/notice/ --type=java
echo -e "\n=== DeptInfo와 support 메서드 사용처 ==="
rg -n "support\(" src/main/java/com/kustacks/kuring/ --type=java | grep -E "(support|DeptInfo)" | head -20Repository: ku-ring/ku-ring-backend-web
Length of output: 11288
🏁 Script executed:
#!/bin/bash
# NoticeHtmlParserTemplate의 support 메서드 추상화 확인
echo "=== NoticeHtmlParserTemplate.java 전체 ==="
cat -n ./src/main/java/com/kustacks/kuring/worker/parser/notice/NoticeHtmlParserTemplate.java
echo -e "\n=== RealEstateDept HTML 구조 테스트 자료 확인 ==="
find . -type f -name "*.html" | grep -i real
find . -type f -name "*.html" | grep -i estate
echo -e "\n=== 테스트 리소스 전체 ==="
find ./src/test/resources -type f -name "*.html" | head -20Repository: ku-ring/ku-ring-backend-web
Length of output: 3299
🏁 Script executed:
#!/bin/bash
# RealEstate 테스트 HTML 구조 확인
echo "=== realestate.html 첫 200줄 ==="
head -200 ./src/test/resources/notice/realestate.html
echo -e "\n=== 테이블 구조 검색 ==="
grep -n "board-table\|tbody\|notice" ./src/test/resources/notice/realestate.html | head -30
echo -e "\n=== onclick 속성 및 파싱 관련 요소 검색 ==="
grep -n "jf_viewArtcl\|onclick" ./src/test/resources/notice/realestate.html | head -20Repository: ku-ring/ku-ring-backend-web
Length of output: 10128
🏁 Script executed:
#!/bin/bash
# RealEstate HTML 테이블 구조 상세 확인
echo "=== RealEstate HTML 테이블 행 구조 ==="
sed -n '180,250p' ./src/test/resources/notice/realestate.html
echo -e "\n=== 파서가 찾는 .board-table 클래스 존재 여부 ==="
grep "board-table" ./src/test/resources/notice/realestate.html
echo -e "\n=== CSE 테스트 HTML (다른 부서)와 비교 - 테이블 클래스 ==="
grep -n "board-table\|<table" ./src/test/resources/notice/cse-notice-2024.html | head -10
echo -e "\n=== CSE HTML의 실제 테이블 구조 ==="
sed -n '1,100p' ./src/test/resources/notice/cse-notice-2024.html | grep -A 30 "<table"Repository: ku-ring/ku-ring-backend-web
Length of output: 1954
🏁 Script executed:
#!/bin/bash
# CSE HTML 테이블 구조 전체 확인
echo "=== CSE HTML 테이블 및 tbody 구조 ==="
sed -n '1,150p' ./src/test/resources/notice/cse-notice-2024.html | grep -A 50 '<table'
echo -e "\n=== RealEstate 파일에서 .notice 클래스 확인 ==="
grep -n "notice" ./src/test/resources/notice/realestate.html
echo -e "\n=== LatestPageNoticeHtmlParser 테스트 확인 ==="
find . -type f -name "*Test.java" | xargs grep -l "LatestPageNoticeHtmlParser\|LatestPageNoticeApiClient" 2>/dev/null
echo -e "\n=== 현재 사용 중인 parser 선택 로직 ==="
rg -n "filter.*support\(" src/main/ --type=java -A 2 -B 2Repository: ku-ring/ku-ring-backend-web
Length of output: 5370
🏁 Script executed:
#!/bin/bash
# 공지 파서 선택 로직 확인
echo "=== NoticeScraper 또는 유사 파일 찾기 ==="
find . -type f -name "*Scraper.java" | head -20
echo -e "\n=== 공지 관련 scraper/client 파일 ==="
find . -type f -name "*Notice*.java" -path "*/scrap/*" | head -20
echo -e "\n=== NoticeHtmlParserTemplateTest 내용 ==="
cat -n ./src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java
echo -e "\n=== parser 선택 로직 찾기 ==="
rg -n "NoticeHtmlParser\|notice.*parser\|htmlParser" src/main/java/com/kustacks/kuring/worker/scrap/ --type=java | grep -i "filter\|stream\|select" | head -20Repository: ku-ring/ku-ring-backend-web
Length of output: 17562
🏁 Script executed:
#!/bin/bash
# DepartmentNoticeScraperTemplate 확인
echo "=== DepartmentNoticeScraperTemplate.java ==="
cat -n ./src/main/java/com/kustacks/kuring/worker/scrap/DepartmentNoticeScraperTemplate.java
echo -e "\n=== RealEstate parser 현재 구현 확인 ==="
rg -n "class.*RealEstate" src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/real_estate/ --type=java -A 20 | head -80Repository: ku-ring/ku-ring-backend-web
Length of output: 7397
🏁 Script executed:
#!/bin/bash
# CSE HTML 구조와 selectors 검증
echo "=== CSE HTML에서 .board-table 확인 ==="
grep -A 50 'class="board-table' ./src/test/resources/notice/cse-notice-2024.html | head -60
echo -e "\n=== .notice 클래스를 갖는 행 찾기 (CSE) ==="
grep -n 'class.*notice' ./src/test/resources/notice/cse-notice-2024.html
echo -e "\n=== onclick 속성과 jf_viewArtcl 확인 (CSE) ==="
grep -n "jf_viewArtcl\|onclick" ./src/test/resources/notice/cse-notice-2024.html | head -20
echo -e "\n=== DeptInfo의 parse 메서드 구현 ==="
rg -n "public.*RowsDto parse\|protected.*RowsDto parse" src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/ --type=java -A 5Repository: ku-ring/ku-ring-backend-web
Length of output: 3627
🏁 Script executed:
#!/bin/bash
# DeptInfo parse 메서드 확인
echo "=== DeptInfo 클래스 확인 ==="
find . -type f -name "DeptInfo.java" -path "*/deptinfo/*"
cat -n ./src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/DeptInfo.java | head -100
echo -e "\n=== RealEstate HTML 첫 번째 행의 링크 구조 ==="
sed -n '188,210p' ./src/test/resources/notice/realestate.html
echo -e "\n=== CSE HTML 첫 번째 중요 공지의 td 구조 ==="
sed -n '380,410p' ./src/test/resources/notice/cse-notice-2024.html
echo -e "\n=== RealEstate HTML 파싱 테스트 여부 ==="
grep -n "realestate\|RealEstate" src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.javaRepository: ku-ring/ku-ring-backend-web
Length of output: 5997
🏁 Script executed:
#!/bin/bash
# RealEstate HTML 상세 구조 확인
echo "=== RealEstate HTML에서 tbody, tr, 클래스 구조 확인 ==="
sed -n '175,220p' ./src/test/resources/notice/realestate.html
echo -e "\n=== RealEstate의 td 개수 및 구조 ==="
sed -n '183,197p' ./src/test/resources/notice/realestate.html | grep -E "td|<strong|<a"
echo -e "\n=== CSE HTML의 td 개수 확인 ==="
sed -n '380,410p' ./src/test/resources/notice/cse-notice-2024.html | grep -E "td|<strong|<a"
echo -e "\n=== extractNoticeFromRow에서 사용하는 인덱스 확인 ==="
grep -A 15 "extractNoticeFromRow" ./src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParser.java
echo -e "\n=== RealEstate가 사용할 수 있는 파서 있는지 확인 ==="
find src/main/java/com/kustacks/kuring/worker/parser -name "*HtmlParser.java" -exec grep -l "support" {} \; | xargs -I {} sh -c 'echo "=== {} ===" && grep -A 3 "public.*support"' {}Repository: ku-ring/ku-ring-backend-web
Length of output: 2780
🏁 Script executed:
#!/bin/bash
# 최종 검증: RealEstate HTML 구조와 파서의 호환성 확인
echo "=== RealEstate HTML에 .board-table 클래스가 있는지 확인 ==="
grep -c "class=\"board-table" ./src/test/resources/notice/realestate.html || echo "Not found: 0 matches"
echo -e "\n=== RealEstate HTML 테이블 클래스 확인 ==="
grep -n "<table" ./src/test/resources/notice/realestate.html
echo -e "\n=== RealEstate 행의 정확한 td 구조 (첫 번째 행) ==="
python3 << 'EOF'
import re
with open('./src/test/resources/notice/realestate.html', 'r', encoding='utf-8') as f:
content = f.read()
# Find first tr in tbody
tbody_match = re.search(r'<tbody>.*?<tr>(.*?)</tr>', content, re.DOTALL)
if tbody_match:
first_row = tbody_match.group(1)
tds = re.findall(r'<td[^>]*>(.*?)</td>', first_row, re.DOTALL)
for i, td in enumerate(tds):
print(f"td[{i}]: {td[:100]}...")
# Check for onclick attributes
onclick_matches = re.findall(r'onclick="[^"]*"', content)
print(f"\nonclick 속성 개수: {len(onclick_matches)}")
if onclick_matches:
print(f"예시: {onclick_matches[0]}")
# Check for jf_viewArtcl
jf_matches = re.findall(r'jf_viewArtcl\([^)]*\)', content)
print(f"jf_viewArtcl 호출 개수: {len(jf_matches)}")
EOF
echo -e "\n=== CSE HTML와 RealEstate HTML 구조 비교 ==="
python3 << 'EOF'
import re
# CSE
with open('./src/test/resources/notice/cse-notice-2024.html', 'r', encoding='utf-8') as f:
cse_content = f.read()
cse_table = re.search(r'<table[^>]*class="([^"]*)"', cse_content)
cse_jf = len(re.findall(r'jf_viewArtcl', cse_content))
# RealEstate
with open('./src/test/resources/notice/realestate.html', 'r', encoding='utf-8') as f:
re_content = f.read()
re_table = re.search(r'<table[^>]*>', re_content)
re_jf = len(re.findall(r'jf_viewArtcl', re_content))
print(f"CSE table class: {cse_table.group(1) if cse_table else 'No class'}")
print(f"CSE jf_viewArtcl 개수: {cse_jf}")
print(f"\nRealEstate table: {re_table.group(0) if re_table else 'Not found'}")
print(f"RealEstate jf_viewArtcl 개수: {re_jf}")
EOFRepository: ku-ring/ku-ring-backend-web
Length of output: 157
🏁 Script executed:
#!/bin/bash
# 간단한 확인
echo "=== RealEstate HTML에 board-table 클래스 있는지 ==="
grep "board-table" ./src/test/resources/notice/realestate.html && echo "FOUND" || echo "NOT FOUND"
echo -e "\n=== RealEstate HTML 테이블 태그 ==="
grep -m1 "<table" ./src/test/resources/notice/realestate.html
echo -e "\n=== RealEstate HTML에 jf_viewArtcl 있는지 ==="
grep "jf_viewArtcl" ./src/test/resources/notice/realestate.html && echo "FOUND" || echo "NOT FOUND"
echo -e "\n=== CSE에 jf_viewArtcl 있는지 ==="
grep -c "jf_viewArtcl" ./src/test/resources/notice/cse-notice-2024.html
echo -e "\n=== RealEstate 행의 정확한 td 개수 ==="
sed -n '183,202p' ./src/test/resources/notice/realestate.html | wc -lRepository: ku-ring/ku-ring-backend-web
Length of output: 289
RealEstateDept HTML 구조와 파서 호환성 문제를 검증하세요.
RealEstateDept의 HTML 구조가 이 파서와 호환되지 않습니다. RealEstate 페이지에는 .board-table 클래스가 없고 jf_viewArtcl() 함수 호출도 없으며, 링크는 단순 href 속성만 포함합니다. 반면 LatestPageNoticeHtmlParser는 .board-table > tbody > tr 선택자와 onclick 속성의 jf_viewArtcl() 파싱에 의존합니다. support() 메서드가 return true로 변경되면 RealEstateDept가 이 파서를 사용하게 되어 파싱이 실패합니다.
파서 코드
protected Elements selectImportantRows(Document document) {
return document.select(".board-table > tbody > tr").select(".notice");
}
protected String[] extractNoticeFromRow(Element row) {
Elements tds = row.getElementsByTag("td");
String[] splitResults = tds.get(1).select("a").attr("onclick")
.replace("jf_viewArtcl('", "")
.replace("')", "")
.split("', '");
...
}



version 2.16.2
Summary by CodeRabbit
릴리스 노트
New Features
Tests
Refactor
Chores
✏️ Tip: You can customize this high-level summary in your review settings.