Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IDLE-531] 배치 작업 정상화 및 최적화 #260

Merged
merged 7 commits into from
Jan 30, 2025
Merged

Conversation

mjj111
Copy link
Member

@mjj111 mjj111 commented Jan 24, 2025

1. 📄 Summary

  • Flayway 재설정 및 배치 메타데이터 테이블 생성
  • 배치 수동 시작 API 추가
  • 배치 작업 정상화
  • 책임 별 클래스 분리
  • 배치 동작에 멀티스레드 적용
  • Tsklet을 Chunk로 변경

2. 🤔 고민했던 점

기존 배치 작업에서는 게시물 800개 기준으로 약 25-30분이 소요되었으나, 이를 개선하기 위해 여러 가지 접근을 시도했습니다. 특히, 코루틴을 통한 비동기 작업을 고민했으나, ChromeDriver가 싱글스레드 기반으로 동작하는 방식 때문에 큰 성능 향상이 없음을 확인했습니다. 이에 따라 멀티스레드를 적용하여 각 페이지마다 별도의 스레드가 크롤링 작업을 수행하도록 개선했습니다.

해결책

  1. 멀티스레드 적용
    각 페이지마다 스레드가 독립적으로 작업하도록 하여 동시 실행이 가능하게 만들었습니다. 이를 통해 크롤링 작업의 속도가 크게 향상되었습니다.

  2. Chunk 방식으로 처리
    작업을 Reader, Processor, Writer로 나누어 Chunk 형식으로 처리하며, Chunk 단위로 스레드를 생성하여 크롤링을 병렬로 수행하였습니다.

  3. Thread별 ChromeDriver 인스턴스 분리
    멀티스레드 환경에서 각 스레드가 독립적으로 작업할 수 있도록 각 스레드마다 별도의 ChromeDriver 인스턴스를 사용하도록 구현했습니다. 이를 통해 페이지마다 스레드가 작업하고, 각 스레드는 자신만의 인스턴스를 사용하여 크롤링이 이루어지도록 했습니다.

  4. 동시성 문제 해결
    AtomicInteger를 사용하여 nextPage를 원자적으로 관리함으로써 동시성 문제를 해결했습니다. 이를 통해 여러 스레드가 동시에 nextPage를 수정하지 않도록 보장했습니다.

성능 개선 결과

  • 기존: 800개 게시물 크롤링 시 25-30분 소요
  • 개선 후: 800개 게시물 크롤링 시 평균 5-6분 소요

코드 예시

@Bean
fun taskExecutor(): SimpleAsyncTaskExecutor {
    return SimpleAsyncTaskExecutor().apply {
        this.setConcurrencyLimit(4)  // 스레드 제한 설정
    }
}
var nextPage = AtomicInteger(1)  // 원자적으로 페이지 번호 관리

SimpleAsyncTaskExecutor를 사용하여 동시성 제한을 설정하고, 각 스레드가 독립적으로 작업을 할 수 있도록 구성하였습니다.
AtomicInteger를 사용하여 nextPage의 값을 원자적으로 업데이트하고, 동시에 여러 스레드가 접근해도 문제가 발생하지 않도록 했습니다.

또한 데이터 저장 시 800개가 한 번에 DB에 저장되지 않고 50개씩 들어가도록 수정하여 DB 저장 로직을 개선하였습니다.

4. 💡 알게된 점, 궁금한 점

멀티스레드를 적용하면서 동시성 문제에 대해 고민하게 되었고, 이를 해결하기 위한 방법을 배울 수 있었습니다. 각 스레드가 안전하게 동작할 수 있도록 설계하는 과정에서, AtomicInteger와 같은 원자적 연산을 활용한 동시성 관리 기법을 적용할 수 있었습니다. 이를 통해 멀티스레드 환경에서도 안전하고 효율적인 작업 처리가 가능함을 확인했습니다.

Summary by CodeRabbit

  • 데이터베이스 마이그레이션

    • 초기 데이터베이스 스키마 생성
    • 배치 처리를 위한 테이블 및 시퀀스 추가
    • 채팅, 알림, 구직자, 센터 관련 테이블 구조 정의
    • 데이터베이스 인덱스 최적화를 위한 인덱스 추가
  • 스키마 변경

    • 일부 테이블의 컬럼 타입 수정 (TEXT 타입으로 변경)
    • 전화번호 및 기타 컬럼에 대한 고유 인덱스 제거
    • 불필요한 SQL 스크립트 및 테이블 삭제

@mjj111 mjj111 added ✨기능 기능 개발 및 구현 🎯리팩토링 리팩토링 및 고도화 이슈 labels Jan 24, 2025
@mjj111 mjj111 self-assigned this Jan 24, 2025
Copy link

coderabbitai bot commented Jan 24, 2025

워크스루

이 풀 리퀘스트는 idle-domain 프로젝트의 데이터베이스 마이그레이션 스크립트들을 정리하고 통합하는 작업을 포함합니다. 여러 개의 SQL 마이그레이션 스크립트가 삭제되거나 수정되었으며, 대신 V1__init.sql, V2__add_index.sql, V3__batch_schema.sql 등의 새로운 통합 마이그레이션 스크립트가 추가되었습니다. 이 변경은 데이터베이스 스키마의 일관성과 구조를 개선하는 것을 목표로 합니다.

변경 사항

파일 변경 요약
V1__init.sql 여러 테이블 (applys, carer, center, job_posting 등) 초기 스키마 생성
V2__add_index.sql chat_room, notification, chat_message 테이블에 인덱스 추가
V3__batch_schema.sql 배치 처리를 위한 다양한 시퀀스 및 실행 테이블 생성
삭제된 마이그레이션 스크립트 V10__alter_table_carer_add_column_location.sql, V11__alter_table_notification_add_notification_type.sql, V2__create_table_chat_room.sql, V3__create_table_chat_message.sql, V4__alter_table_notification_add_column_device_token.sql, V5__create_index_receiver_id_in_notification_table.sql, V6__alter_table_carer_modify_column_type.sql, V7__alter_table_center_modify_column_type.sql, V8__alter_table_center_manager_add_index.sql, V9__alter_table_carer_add_index.sql, CrawlingJobConfig.kt, CrawlingJobPostingTasklet.kt, WorknetCrawler.kt 등 여러 스크립트 제거

가능한 관련 PR

제안된 레이블

🌍인프라


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (2)
idle-domain/src/main/resources/db/migration/V2__add_index.sql (1)

10-11: 채팅 메시지 테이블의 인덱스 최적화를 검토해주세요.

room_id와 sender_id에 대한 개별 인덱스가 생성되어 있습니다. 채팅 메시지 조회 패턴에 따라 복합 인덱스 사용을 고려해보시기 바랍니다.

다음과 같은 복합 인덱스를 고려해보세요:

-CREATE INDEX idx_room_id ON chat_message(room_id);
-CREATE INDEX idx_sender_id ON chat_message(sender_id);
+CREATE INDEX idx_room_sender ON chat_message(room_id, sender_id);
idle-domain/src/main/resources/db/migration/V1__init.sql (1)

1-215: 아키텍처 개선 제안

전체 스키마 설계에 대한 아키텍처 관점의 개선사항입니다:

  1. 배치 메타데이터 관리
  • 배치 작업의 실행 이력과 상태를 추적할 수 있는 batch_job_execution 테이블 추가 필요
  • 배치 작업의 파라미터를 저장할 수 있는 batch_job_parameters 테이블 추가 필요
  1. 위치 데이터 표준화
  • 모든 테이블에서 위치 데이터를 POINT SRID 4326 형식으로 통일
  • 위치 기반 검색을 위한 공간 인덱스 추가
  1. 상태 관리 표준화
  • 모든 entity_status를 동일한 ENUM 값으로 통일
  • 상태 변경 이력을 추적할 수 있는 audit 테이블 추가 고려

다음과 같은 배치 메타데이터 테이블 추가를 제안드립니다:

CREATE TABLE batch_job_execution (
    id BINARY(16) NOT NULL,
    job_name VARCHAR(100) NOT NULL,
    start_time TIMESTAMP,
    end_time TIMESTAMP,
    status ENUM('STARTED', 'COMPLETED', 'FAILED') NOT NULL,
    error_message TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    KEY idx_batch_job_execution_status (status, start_time)
) ENGINE=InnoDB;
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1093626 and bd4847c.

📒 Files selected for processing (13)
  • idle-domain/src/main/resources/db/migration/V10__alter_table_carer_add_column_location.sql (0 hunks)
  • idle-domain/src/main/resources/db/migration/V11__alter_table_notification_add_notification_type.sql (0 hunks)
  • idle-domain/src/main/resources/db/migration/V1__init.sql (1 hunks)
  • idle-domain/src/main/resources/db/migration/V2__add_index.sql (1 hunks)
  • idle-domain/src/main/resources/db/migration/V2__create_table_chat_room.sql (0 hunks)
  • idle-domain/src/main/resources/db/migration/V3__batch_schema.sql (1 hunks)
  • idle-domain/src/main/resources/db/migration/V3__create_table_chat_message.sql (0 hunks)
  • idle-domain/src/main/resources/db/migration/V4__alter_table_notification_add_column_device_token.sql (0 hunks)
  • idle-domain/src/main/resources/db/migration/V5__create_index_receiver_id_in_notification_table.sql (0 hunks)
  • idle-domain/src/main/resources/db/migration/V6__alter_table_carer_modify_column_type.sql (0 hunks)
  • idle-domain/src/main/resources/db/migration/V7__alter_table_center_modify_column_type.sql (0 hunks)
  • idle-domain/src/main/resources/db/migration/V8__alter_table_center_manager_add_index.sql (0 hunks)
  • idle-domain/src/main/resources/db/migration/V9__alter_table_carer_add_index.sql (0 hunks)
💤 Files with no reviewable changes (10)
  • idle-domain/src/main/resources/db/migration/V10__alter_table_carer_add_column_location.sql
  • idle-domain/src/main/resources/db/migration/V5__create_index_receiver_id_in_notification_table.sql
  • idle-domain/src/main/resources/db/migration/V11__alter_table_notification_add_notification_type.sql
  • idle-domain/src/main/resources/db/migration/V8__alter_table_center_manager_add_index.sql
  • idle-domain/src/main/resources/db/migration/V2__create_table_chat_room.sql
  • idle-domain/src/main/resources/db/migration/V6__alter_table_carer_modify_column_type.sql
  • idle-domain/src/main/resources/db/migration/V3__create_table_chat_message.sql
  • idle-domain/src/main/resources/db/migration/V9__alter_table_carer_add_index.sql
  • idle-domain/src/main/resources/db/migration/V7__alter_table_center_modify_column_type.sql
  • idle-domain/src/main/resources/db/migration/V4__alter_table_notification_add_column_device_token.sql
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and analyze
🔇 Additional comments (7)
idle-domain/src/main/resources/db/migration/V2__add_index.sql (3)

4-5: 채팅방 테이블의 인덱스 구성이 적절합니다.

sender_id와 receiver_id에 대한 인덱스 생성은 채팅방 조회 성능 향상에 도움이 될 것입니다.


7-8: 알림 테이블의 인덱스 구성이 적절합니다.

receiver_id와 created_at에 대한 인덱스는 알림 목록 조회와 시간순 정렬에 효과적입니다.


13-14: 전화번호 유니크 제약 추가가 적절합니다.

center_manager와 carer 테이블의 phone_number 컬럼에 대한 유니크 인덱스 추가는 데이터 무결성 보장에 도움이 됩니다.

전화번호 중복 데이터가 있는지 확인이 필요합니다:

✅ Verification successful

전화번호 유니크 제약 조건 추가가 적절합니다

코드베이스 분석 결과, 전화번호는 사용자 식별과 인증에 핵심적으로 사용되고 있어 유니크 제약 조건 추가가 필수적입니다. SMS 인증, 사용자 인증, 계정 관리 등에서 전화번호의 유일성이 요구됩니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: 전화번호 중복 데이터 확인을 위한 SQL 스크립트 생성

cat << 'EOF' > check_duplicates.sql
SELECT phone_number, COUNT(*) 
FROM center_manager 
GROUP BY phone_number 
HAVING COUNT(*) > 1;

SELECT phone_number, COUNT(*) 
FROM carer 
GROUP BY phone_number 
HAVING COUNT(*) > 1;
EOF

echo "중복 전화번호 확인을 위해 생성된 SQL 스크립트:"
cat check_duplicates.sql

Length of output: 283


Script:

#!/bin/bash
# Search for phone number related code and validation
echo "=== Phone number validation and handling ==="
rg -i "phone.*number" -g "!*.sql" -A 3 -B 3

echo -e "\n=== Database schema and entity definitions ==="
rg -i "phone.*number.*column" -g "!*.sql" -A 3 -B 3

echo -e "\n=== Test cases for phone numbers ==="
rg -i "phone.*number.*test" -g "!*.sql" -A 3 -B 3

Length of output: 69007

idle-domain/src/main/resources/db/migration/V3__batch_schema.sql (4)

4-10: BATCH_JOB_INSTANCE 테이블 구조가 적절합니다.

JOB_NAME과 JOB_KEY의 유니크 제약조건이 잘 설정되어 있어 중복 작업 인스턴스 생성을 방지할 수 있습니다.


12-25: 배치 작업 실행 관련 테이블의 외래 키 제약조건이 올바르게 설정되었습니다.

BATCH_JOB_EXECUTION과 BATCH_JOB_EXECUTION_PARAMS 테이블이 BATCH_JOB_INSTANCE와 적절하게 연결되어 있습니다. EXIT_MESSAGE 필드의 길이가 충분히 설정되어 있어 상세한 오류 메시지를 저장할 수 있습니다.

Also applies to: 27-35


37-59: 스텝 실행 관련 테이블의 메트릭 필드가 잘 정의되어 있습니다.

BATCH_STEP_EXECUTION 테이블에 READ_COUNT, WRITE_COUNT 등 중요한 메트릭 필드들이 포함되어 있어 모니터링과 디버깅에 유용할 것 같습니다. BATCH_STEP_EXECUTION_CONTEXT와의 관계도 올바르게 설정되어 있습니다.

Also applies to: 61-67


69-75: 작업 컨텍스트 테이블의 SERIALIZED_CONTEXT 필드 타입을 검토해주세요.

TEXT 타입은 대용량 데이터를 저장할 수 있지만, 데이터 크기가 매우 클 경우 성능 이슈가 발생할 수 있습니다. MEDIUMTEXT나 LONGTEXT 사용을 고려해보시기 바랍니다.

✅ Verification successful

SERIALIZED_CONTEXT 필드의 TEXT 타입이 적절합니다.

현재 구현된 배치 작업(CrawlingJobPosting)의 특성을 분석한 결과:

  • 단순한 작업 포스팅 데이터 처리
  • 복잡한 상태 관리나 대용량 데이터 처리가 없음
  • 기본 Spring Batch 컨텍스트 크기만 사용

MySQL TEXT 타입(최대 65,535바이트)으로 충분하며, MEDIUMTEXT나 LONGTEXT로의 변경은 불필요한 리소스 낭비가 될 수 있습니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if there are any existing batch jobs with large context data
rg -l "spring.batch" . | while read -r file; do
  if [[ -f "$file" ]]; then
    echo "Checking $file for batch job configurations..."
    cat "$file" | grep -A 5 "spring.batch"
  fi
done

Length of output: 662


Script:

#!/bin/bash
# Search for batch job implementations and context usage
echo "=== Searching for Job implementations ==="
rg -t kotlin "class.*Job" ./idle-batch -A 5

echo -e "\n=== Searching for ExecutionContext usage ==="
rg -t kotlin "ExecutionContext" ./idle-batch

echo -e "\n=== Searching for @StepScope or @JobScope annotations ==="
rg -t kotlin "@(Step|Job)Scope" ./idle-batch

Length of output: 3188

Comment on lines +77 to +99
CREATE TABLE BATCH_STEP_EXECUTION_SEQ (
ID BIGINT NOT NULL,
UNIQUE_KEY CHAR(1) NOT NULL,
constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_STEP_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_STEP_EXECUTION_SEQ);

CREATE TABLE BATCH_JOB_EXECUTION_SEQ (
ID BIGINT NOT NULL,
UNIQUE_KEY CHAR(1) NOT NULL,
constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_JOB_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_EXECUTION_SEQ);

CREATE TABLE BATCH_JOB_SEQ (
ID BIGINT NOT NULL,
UNIQUE_KEY CHAR(1) NOT NULL,
constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_JOB_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_SEQ);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

시퀀스 테이블의 초기화 로직이 안전하게 구현되었습니다.

각 시퀀스 테이블(BATCH_STEP_EXECUTION_SEQ, BATCH_JOB_EXECUTION_SEQ, BATCH_JOB_SEQ)에 대해 중복 INSERT를 방지하는 NOT EXISTS 조건이 잘 적용되어 있습니다.

하지만 시퀀스 테이블들의 UNIQUE_KEY 제약조건명이 모두 동일한 'UNIQUE_KEY_UN'을 사용하고 있어 문제가 될 수 있습니다.

각 테이블별로 고유한 제약조건명을 사용하도록 수정해주세요:

-                                         constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
+                                         constraint STEP_EXEC_SEQ_UNQ unique (UNIQUE_KEY)
-                                         constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
+                                         constraint JOB_EXEC_SEQ_UNQ unique (UNIQUE_KEY)
-                                         constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
+                                         constraint JOB_SEQ_UNQ unique (UNIQUE_KEY)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CREATE TABLE BATCH_STEP_EXECUTION_SEQ (
ID BIGINT NOT NULL,
UNIQUE_KEY CHAR(1) NOT NULL,
constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;
INSERT INTO BATCH_STEP_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_STEP_EXECUTION_SEQ);
CREATE TABLE BATCH_JOB_EXECUTION_SEQ (
ID BIGINT NOT NULL,
UNIQUE_KEY CHAR(1) NOT NULL,
constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;
INSERT INTO BATCH_JOB_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_EXECUTION_SEQ);
CREATE TABLE BATCH_JOB_SEQ (
ID BIGINT NOT NULL,
UNIQUE_KEY CHAR(1) NOT NULL,
constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;
INSERT INTO BATCH_JOB_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_SEQ);
CREATE TABLE BATCH_STEP_EXECUTION_SEQ (
ID BIGINT NOT NULL,
UNIQUE_KEY CHAR(1) NOT NULL,
constraint STEP_EXEC_SEQ_UNQ unique (UNIQUE_KEY)
) ENGINE=InnoDB;
INSERT INTO BATCH_STEP_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_STEP_EXECUTION_SEQ);
CREATE TABLE BATCH_JOB_EXECUTION_SEQ (
ID BIGINT NOT NULL,
UNIQUE_KEY CHAR(1) NOT NULL,
constraint JOB_EXEC_SEQ_UNQ unique (UNIQUE_KEY)
) ENGINE=InnoDB;
INSERT INTO BATCH_JOB_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_EXECUTION_SEQ);
CREATE TABLE BATCH_JOB_SEQ (
ID BIGINT NOT NULL,
UNIQUE_KEY CHAR(1) NOT NULL,
constraint JOB_SEQ_UNQ unique (UNIQUE_KEY)
) ENGINE=InnoDB;
INSERT INTO BATCH_JOB_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_SEQ);

Comment on lines +89 to +215
PRIMARY KEY (id)
) engine=InnoDB;

create table device_token (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
user_id BINARY(16),
device_token VARCHAR(255),
entity_status VARCHAR(255),
user_type VARCHAR(255),
PRIMARY KEY (id)
) engine=InnoDB;

create table job_posting (
apply_deadline date,
birth_year INT NOT NULL,
care_level INT NOT NULL,
is_bowel_assistance bit NOT NULL,
is_experience_preferred bit NOT NULL,
is_meal_assistance bit NOT NULL,
is_walking_assistance bit NOT NULL,
pay_amount int NOT NULL,
weight int,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
center_id BINARY(16) NOT NULL,
id BINARY(16) NOT NULL,
client_name VARCHAR(255) NOT NULL,
disease TEXT,
end_time VARCHAR(255) NOT NULL,
extra_requirement TEXT,
lot_number_address VARCHAR(255) NOT NULL,
road_name_address VARCHAR(255) NOT NULL,
start_time VARCHAR(255) NOT NULL,
apply_deadline_type ENUM ('LIMITED','UNLIMITED') NOT NULL,
entity_status VARCHAR(255),
gender ENUM ('MAN','WOMAN') NOT NULL,
job_posting_status ENUM ('IN_PROGRESS','COMPLETED') NOT NULL,
location POINT SRID 4326 NOT NULL,
mental_status ENUM ('NORMAL','EARLY_STAGE','OVER_MIDDLE_STAGE') NOT NULL,
pay_type ENUM ('HOURLY','WEEKLY','MONTHLY') NOT NULL,
PRIMARY KEY (id)
) engine=InnoDB;

create table job_posting_apply_method (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
job_posting_id BINARY(16) NOT NULL,
apply_method ENUM ('CALLING','APP') NOT NULL,
entity_status VARCHAR(255),
PRIMARY KEY (id)
) engine=InnoDB;

create table job_posting_favorite (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
carer_id BINARY(16) NOT NULL,
id BINARY(16) NOT NULL,
job_posting_id BINARY(16) NOT NULL,
entity_status VARCHAR(255),
job_posting_type ENUM ('WORKNET','CAREMEET') NOT NULL,
PRIMARY KEY (id)
) engine=InnoDB;

create table job_posting_life_assistance (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
job_posting_id BINARY(16) NOT NULL,
entity_status VARCHAR(255),
life_assistance ENUM ('CLEANING','LAUNDRY','WALKING','HEALTH','TALKING','NONE') NOT NULL,
PRIMARY KEY (id)
) engine=InnoDB;

create table job_posting_weekday (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
job_posting_id BINARY(16) NOT NULL,
entity_status VARCHAR(255),
weekday ENUM ('MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY','SATURDAY','SUNDAY') NOT NULL,
PRIMARY KEY (id)
) engine=InnoDB;

create table notification (
is_read VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
receiver_id BINARY(16),
body VARCHAR(255),
image_url VARCHAR(255),
notification_details_json json,
title VARCHAR(255),
entity_status VARCHAR(255),
notification_type ENUM ('APPLICANT','CENTER_AUTHENTICATION','NEW_JOB_POSTING'),
PRIMARY KEY (id)
) engine=InnoDB;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

전반적인 테이블 구조 개선사항

남은 테이블들에서 다음과 같은 공통적인 문제점들이 발견되었습니다:

  1. 상태 필드들이 일관성 없이 VARCHAR로 정의되어 있습니다
  2. 외래 키 제약 조건이 누락되었습니다
  3. 효율적인 조회를 위한 인덱스가 부족합니다

모든 테이블에 대해 다음과 같은 패턴으로 수정이 필요합니다:

  1. 상태값은 ENUM으로 정의
entity_status ENUM ('ACTIVE', 'INACTIVE', 'DELETED') NOT NULL DEFAULT 'ACTIVE'
  1. 관계 설정
-- job_posting_favorite 예시
FOREIGN KEY (job_posting_id) REFERENCES job_posting(id),
FOREIGN KEY (carer_id) REFERENCES carer(id)
  1. 인덱스 추가
-- notification 예시
KEY idx_notification_receiver_created (receiver_id, created_at DESC)
  1. 위치 데이터 중복 제거
-- crawled_job_posting 예시
- center_address VARCHAR(255),
+ location POINT SRID 4326 NOT NULL

Comment on lines +4 to +13
create table applys (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
carer_id BINARY(16) NOT NULL,
id BINARY(16) NOT NULL,
job_posting_id BINARY(16) NOT NULL,
apply_method_type ENUM ('CALLING','APP') NOT NULL,
entity_status VARCHAR(255),
PRIMARY KEY (id)
) engine=InnoDB;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

applys 테이블의 구조적 문제점 개선 필요

다음과 같은 문제점들이 발견되었습니다:

  1. carer_idjob_posting_id에 대한 외래 키 제약 조건이 누락되었습니다
  2. 테이블 이름이 관례를 따르지 않습니다 (applys -> apply)
  3. entity_status 컬럼이 제약이 없는 VARCHAR로 정의되어 있습니다

다음과 같이 수정하는 것을 제안드립니다:

 create table applys (
     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     carer_id BINARY(16) NOT NULL,
     id BINARY(16) NOT NULL,
     job_posting_id BINARY(16) NOT NULL,
     apply_method_type ENUM ('CALLING','APP') NOT NULL,
-    entity_status VARCHAR(255),
+    entity_status ENUM ('ACTIVE', 'INACTIVE', 'DELETED') NOT NULL DEFAULT 'ACTIVE',
-    PRIMARY KEY (id)
+    PRIMARY KEY (id),
+    FOREIGN KEY (carer_id) REFERENCES carer(id),
+    FOREIGN KEY (job_posting_id) REFERENCES job_posting(id)
 ) engine=InnoDB;

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +38 to +51
create table center (
latitude DECIMAL(14, 10) NOT NULL,
longitude DECIMAL(14, 10) NOT NULL,
id BINARY(16) NOT NULL,
business_registration_number VARCHAR(255) NOT NULL,
center_name VARCHAR(255) NOT NULL,
detailed_address VARCHAR(255) NOT NULL,
introduce TEXT,
lot_number_address VARCHAR(255) NOT NULL,
office_number VARCHAR(255) NOT NULL,
profile_image_url VARCHAR(255),
road_name_address VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
) engine=InnoDB;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

center 테이블의 위치 데이터 중복 및 유니크 제약조건 누락

다음과 같은 문제점들이 발견되었습니다:

  1. 위치 정보가 중복되어 있습니다 (latitude/longitude)
  2. business_registration_number에 대한 유니크 제약조건이 누락되었습니다
  3. center_name에 대한 검색 인덱스가 누락되었습니다

다음과 같이 수정하는 것을 제안드립니다:

 create table center (
-    latitude DECIMAL(14, 10) NOT NULL,
-    longitude DECIMAL(14, 10) NOT NULL,
     id BINARY(16) NOT NULL,
     business_registration_number VARCHAR(255) NOT NULL,
     center_name VARCHAR(255) NOT NULL,
     detailed_address VARCHAR(255) NOT NULL,
     introduce TEXT,
     lot_number_address VARCHAR(255) NOT NULL,
     office_number VARCHAR(255) NOT NULL,
     profile_image_url VARCHAR(255),
     road_name_address VARCHAR(255) NOT NULL,
-    PRIMARY KEY (id)
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_center_business_number (business_registration_number),
+    KEY idx_center_name (center_name)
 ) engine=InnoDB;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
create table center (
latitude DECIMAL(14, 10) NOT NULL,
longitude DECIMAL(14, 10) NOT NULL,
id BINARY(16) NOT NULL,
business_registration_number VARCHAR(255) NOT NULL,
center_name VARCHAR(255) NOT NULL,
detailed_address VARCHAR(255) NOT NULL,
introduce TEXT,
lot_number_address VARCHAR(255) NOT NULL,
office_number VARCHAR(255) NOT NULL,
profile_image_url VARCHAR(255),
road_name_address VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
) engine=InnoDB;
create table center (
id BINARY(16) NOT NULL,
business_registration_number VARCHAR(255) NOT NULL,
center_name VARCHAR(255) NOT NULL,
detailed_address VARCHAR(255) NOT NULL,
introduce TEXT,
lot_number_address VARCHAR(255) NOT NULL,
office_number VARCHAR(255) NOT NULL,
profile_image_url VARCHAR(255),
road_name_address VARCHAR(255) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_center_business_number (business_registration_number),
KEY idx_center_name (center_name)
) engine=InnoDB;

Comment on lines +53 to +65
create table center_manager (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
center_business_registration_number VARCHAR(255) NOT NULL,
identifier VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
phone_number VARCHAR(255) NOT NULL,
entity_status VARCHAR(255),
status VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
) engine=InnoDB;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

center_manager 테이블의 보안 및 관계 설정 문제

다음과 같은 보안 및 구조적 문제가 있습니다:

  1. password 컬럼이 암호화 여부를 명시하지 않습니다
  2. center_business_registration_numbercenter 테이블과 연결되어 있지 않습니다
  3. 상태 필드들이 VARCHAR로 정의되어 있습니다

다음과 같이 수정하는 것을 제안드립니다:

 create table center_manager (
     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     id BINARY(16) NOT NULL,
     center_business_registration_number VARCHAR(255) NOT NULL,
     identifier VARCHAR(255) NOT NULL,
     name VARCHAR(255) NOT NULL,
-    password VARCHAR(255) NOT NULL,
+    password_hash VARCHAR(255) NOT NULL COMMENT '암호화된 비밀번호',
     phone_number VARCHAR(255) NOT NULL,
-    entity_status VARCHAR(255),
+    entity_status ENUM ('ACTIVE', 'INACTIVE', 'DELETED') NOT NULL DEFAULT 'ACTIVE',
-    status VARCHAR(255) NOT NULL,
+    status ENUM ('PENDING', 'APPROVED', 'REJECTED') NOT NULL,
-    PRIMARY KEY (id)
+    PRIMARY KEY (id),
+    FOREIGN KEY (center_business_registration_number) 
+        REFERENCES center(business_registration_number),
+    UNIQUE KEY uk_center_manager_identifier (identifier)
 ) engine=InnoDB;

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +15 to +36
create table carer (
birth_year INT NOT NULL,
experience_year INT,
latitude DECIMAL(14, 10) NOT NULL,
longitude DECIMAL(14, 10) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
introduce TEXT,
lot_number_address VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
phone_number VARCHAR(255) NOT NULL,
profile_image_url VARCHAR(255),
road_name_address VARCHAR(255) NOT NULL,
speciality TEXT,
carer_account_status VARCHAR(20) NOT NULL,
entity_status VARCHAR(255),
gender VARCHAR(255) NOT NULL,
job_search_status VARCHAR(20) NOT NULL,
location POINT SRID 4326 NOT NULL,
PRIMARY KEY (id)
) engine=InnoDB;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

carer 테이블의 위치 데이터 중복 및 인덱스 누락

다음과 같은 개선사항들이 필요합니다:

  1. 위치 정보가 중복되어 있습니다 (latitude/longitude와 POINT)
  2. phone_number에 대한 유니크 인덱스가 누락되었습니다
  3. 상태 필드들이 VARCHAR로 정의되어 있습니다

다음과 같이 수정하는 것을 제안드립니다:

 create table carer (
     birth_year INT NOT NULL,
     experience_year INT,
-    latitude DECIMAL(14, 10) NOT NULL,
-    longitude DECIMAL(14, 10) NOT NULL,
     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     id BINARY(16) NOT NULL,
     introduce TEXT,
     lot_number_address VARCHAR(255) NOT NULL,
     name VARCHAR(255) NOT NULL,
     phone_number VARCHAR(255) NOT NULL,
     profile_image_url VARCHAR(255),
     road_name_address VARCHAR(255) NOT NULL,
     speciality TEXT,
-    carer_account_status VARCHAR(20) NOT NULL,
+    carer_account_status ENUM ('ACTIVE', 'SUSPENDED', 'DELETED') NOT NULL,
-    entity_status VARCHAR(255),
+    entity_status ENUM ('ACTIVE', 'INACTIVE', 'DELETED') NOT NULL DEFAULT 'ACTIVE',
-    gender VARCHAR(255) NOT NULL,
+    gender ENUM ('MAN', 'WOMAN') NOT NULL,
-    job_search_status VARCHAR(20) NOT NULL,
+    job_search_status ENUM ('SEARCHING', 'NOT_SEARCHING') NOT NULL,
     location POINT SRID 4326 NOT NULL,
-    PRIMARY KEY (id)
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_carer_phone_number (phone_number)
 ) engine=InnoDB;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
create table carer (
birth_year INT NOT NULL,
experience_year INT,
latitude DECIMAL(14, 10) NOT NULL,
longitude DECIMAL(14, 10) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
introduce TEXT,
lot_number_address VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
phone_number VARCHAR(255) NOT NULL,
profile_image_url VARCHAR(255),
road_name_address VARCHAR(255) NOT NULL,
speciality TEXT,
carer_account_status VARCHAR(20) NOT NULL,
entity_status VARCHAR(255),
gender VARCHAR(255) NOT NULL,
job_search_status VARCHAR(20) NOT NULL,
location POINT SRID 4326 NOT NULL,
PRIMARY KEY (id)
) engine=InnoDB;
create table carer (
birth_year INT NOT NULL,
experience_year INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
introduce TEXT,
lot_number_address VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
phone_number VARCHAR(255) NOT NULL,
profile_image_url VARCHAR(255),
road_name_address VARCHAR(255) NOT NULL,
speciality TEXT,
carer_account_status ENUM ('ACTIVE', 'SUSPENDED', 'DELETED') NOT NULL,
entity_status ENUM ('ACTIVE', 'INACTIVE', 'DELETED') NOT NULL DEFAULT 'ACTIVE',
gender ENUM ('MAN', 'WOMAN') NOT NULL,
job_search_status ENUM ('SEARCHING', 'NOT_SEARCHING') NOT NULL,
location POINT SRID 4326 NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_carer_phone_number (phone_number)
) engine=InnoDB;

Comment on lines +67 to +87
create table chat_message (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
room_id BINARY(16),
sender_id BINARY(16),
contents JSON NOT NULL,
entity_status VARCHAR(255),
sender_type ENUM ('USER','SYSTEM'),
PRIMARY KEY (id)
) ENGINE=InnoDB;

create table chat_room (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
receiver_id BINARY(16) NOT NULL,
sender_id BINARY(16) NOT NULL,
entity_status VARCHAR(255),
PRIMARY KEY (id)
) engine=InnoDB;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

채팅 관련 테이블의 관계 설정 및 인덱스 누락

chat_messagechat_room 테이블에 다음과 같은 문제점들이 있습니다:

  1. 외래 키 제약 조건이 누락되었습니다
  2. chat_messageroom_idsender_id가 NULL을 허용합니다
  3. 효율적인 조회를 위한 인덱스가 누락되었습니다

다음과 같이 수정하는 것을 제안드립니다:

 create table chat_message (
     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     id BINARY(16) NOT NULL,
-    room_id BINARY(16),
+    room_id BINARY(16) NOT NULL,
-    sender_id BINARY(16),
+    sender_id BINARY(16) NOT NULL,
     contents JSON NOT NULL,
-    entity_status VARCHAR(255),
+    entity_status ENUM ('ACTIVE', 'DELETED') NOT NULL DEFAULT 'ACTIVE',
     sender_type ENUM ('USER','SYSTEM'),
-    PRIMARY KEY (id)
+    PRIMARY KEY (id),
+    FOREIGN KEY (room_id) REFERENCES chat_room(id),
+    KEY idx_chat_message_room_created (room_id, created_at DESC)
 ) ENGINE=InnoDB;

 create table chat_room (
     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     id BINARY(16) NOT NULL,
     receiver_id BINARY(16) NOT NULL,
     sender_id BINARY(16) NOT NULL,
-    entity_status VARCHAR(255),
+    entity_status ENUM ('ACTIVE', 'DELETED') NOT NULL DEFAULT 'ACTIVE',
-    PRIMARY KEY (id)
+    PRIMARY KEY (id),
+    KEY idx_chat_room_participants (sender_id, receiver_id)
 ) engine=InnoDB;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
create table chat_message (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
room_id BINARY(16),
sender_id BINARY(16),
contents JSON NOT NULL,
entity_status VARCHAR(255),
sender_type ENUM ('USER','SYSTEM'),
PRIMARY KEY (id)
) ENGINE=InnoDB;
create table chat_room (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
receiver_id BINARY(16) NOT NULL,
sender_id BINARY(16) NOT NULL,
entity_status VARCHAR(255),
PRIMARY KEY (id)
) engine=InnoDB;
create table chat_message (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
room_id BINARY(16) NOT NULL,
sender_id BINARY(16) NOT NULL,
contents JSON NOT NULL,
entity_status ENUM ('ACTIVE', 'DELETED') NOT NULL DEFAULT 'ACTIVE',
sender_type ENUM ('USER','SYSTEM'),
PRIMARY KEY (id),
FOREIGN KEY (room_id) REFERENCES chat_room(id),
KEY idx_chat_message_room_created (room_id, created_at DESC)
) ENGINE=InnoDB;
create table chat_room (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
id BINARY(16) NOT NULL,
receiver_id BINARY(16) NOT NULL,
sender_id BINARY(16) NOT NULL,
entity_status ENUM ('ACTIVE', 'DELETED') NOT NULL DEFAULT 'ACTIVE',
PRIMARY KEY (id),
KEY idx_chat_room_participants (sender_id, receiver_id)
) engine=InnoDB;

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

🧹 Nitpick comments (3)
idle-batch/src/main/kotlin/com/swm/idle/batch/step/PostingReader.kt (1)

34-45: 메서드 문서화가 필요합니다.

getAmount와 getPageUrl 메서드의 역할과 동작을 설명하는 KDoc 문서화가 있으면 좋을 것 같습니다.

다음과 같이 수정하는 것을 제안드립니다:

+    /**
+     * 현재 페이지에서 크롤링할 게시물 수를 계산합니다.
+     * 마지막 페이지인 경우 남은 게시물 수를 반환합니다.
+     *
+     * @param currentPage 현재 페이지 번호
+     * @return 크롤링할 게시물 수
+     */
     private fun getAmount(currentPage: Int): Int {
         if (currentPage == pageCount && lastPageJobPostingCount > 0) {
             return lastPageJobPostingCount
         }
         return CrawlerConsts.JOB_POSTING_COUNT_PER_PAGE.getIntValue()
     }

+    /**
+     * 현재 페이지의 URL을 생성합니다.
+     *
+     * @param currentPage 현재 페이지 번호
+     * @return 크롤링할 페이지의 URL
+     */
     private fun getPageUrl(currentPage: Int): String {
idle-infrastructure/client/src/main/kotlin/com/swm/idle/infrastructure/client/geocode/service/GeoCodeService.kt (1)

49-52: 초기화 로직 개선이 필요합니다.

@PostConstruct를 사용한 초기화가 명시적이지 않으며, 인스턴스 참조를 companion object에 전달하는 방식이 안전하지 않을 수 있습니다.

다음과 같이 수정하는 것을 제안드립니다:

     @PostConstruct
     fun init() {
-        initialize(geoCodeClient, geoCodeProperties)
+        initialize(this)
+        log.info("GeoCodeService가 성공적으로 초기화되었습니다.")
     }
idle-batch/src/main/kotlin/com/swm/idle/batch/job/JobConfig.kt (1)

58-62: 동시성 제한이 하드코딩되어 있음

동시성 제한이 하드코딩되어 있어 환경에 따른 유연한 설정이 어렵습니다.

다음과 같이 설정을 외부화하는 것을 추천드립니다:

+    @Value("\${batch.concurrency.limit:4}")
+    private val concurrencyLimit: Int = 4
+
     @Bean
     fun taskExecutor(): SimpleAsyncTaskExecutor {
         return SimpleAsyncTaskExecutor().apply {
-            this.setConcurrencyLimit(4)
+            this.setConcurrencyLimit(concurrencyLimit)
         }
     }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd4847c and 03a6b1b.

📒 Files selected for processing (14)
  • idle-batch/src/main/kotlin/com/swm/idle/batch/common/launcher/CrawlingJobLauncher.kt (2 hunks)
  • idle-batch/src/main/kotlin/com/swm/idle/batch/crawler/CrawlerConsts.kt (1 hunks)
  • idle-batch/src/main/kotlin/com/swm/idle/batch/crawler/DriverInitializer.kt (1 hunks)
  • idle-batch/src/main/kotlin/com/swm/idle/batch/crawler/WorknetPageCrawler.kt (1 hunks)
  • idle-batch/src/main/kotlin/com/swm/idle/batch/crawler/WorknetPostCrawler.kt (1 hunks)
  • idle-batch/src/main/kotlin/com/swm/idle/batch/job/CrawlingJobConfig.kt (0 hunks)
  • idle-batch/src/main/kotlin/com/swm/idle/batch/job/CrawlingJobPostingTasklet.kt (0 hunks)
  • idle-batch/src/main/kotlin/com/swm/idle/batch/job/JobConfig.kt (1 hunks)
  • idle-batch/src/main/kotlin/com/swm/idle/batch/step/PostingProcessor.kt (1 hunks)
  • idle-batch/src/main/kotlin/com/swm/idle/batch/step/PostingReader.kt (1 hunks)
  • idle-batch/src/main/kotlin/com/swm/idle/batch/util/WorknetCrawler.kt (0 hunks)
  • idle-infrastructure/client/src/main/kotlin/com/swm/idle/infrastructure/client/geocode/service/GeoCodeService.kt (2 hunks)
  • idle-presentation/src/main/kotlin/com/swm/idle/presentation/batch/api/BatchApi.kt (1 hunks)
  • idle-presentation/src/main/kotlin/com/swm/idle/presentation/batch/controller/BatchController.kt (1 hunks)
💤 Files with no reviewable changes (3)
  • idle-batch/src/main/kotlin/com/swm/idle/batch/util/WorknetCrawler.kt
  • idle-batch/src/main/kotlin/com/swm/idle/batch/job/CrawlingJobPostingTasklet.kt
  • idle-batch/src/main/kotlin/com/swm/idle/batch/job/CrawlingJobConfig.kt
🧰 Additional context used
🪛 detekt (1.23.7)
idle-batch/src/main/kotlin/com/swm/idle/batch/crawler/WorknetPostCrawler.kt

[warning] 32-32: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and analyze
🔇 Additional comments (2)
idle-batch/src/main/kotlin/com/swm/idle/batch/step/PostingProcessor.kt (2)

9-14: 깔끔한 상수 정의와 인터페이스 구현입니다!

상수들이 명확하게 정의되어 있고, ItemProcessor 인터페이스의 구현이 잘 되어있습니다.


31-35: 주소 추출 로직이 깔끔합니다!

단일 책임 원칙을 잘 따르고 있으며, 정규식을 활용한 주소 추출이 효율적으로 구현되어 있습니다.

Comment on lines +7 to +12
@RestController
class BatchController(
private val jobLauncher: CrawlingJobLauncher,
) : BatchApi {
override fun launchBatch() = jobLauncher.jobStart()
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

⚠️ Potential issue

배치 API 엔드포인트의 보안 취약점 확인됨

코드 검토 결과 배치 작업 실행 엔드포인트(/api/v2/batch)에 다음과 같은 보안 취약점이 확인되었습니다:

  1. 인증/인가 메커니즘 부재

    • @PreAuthorize 또는 @Secured 어노테이션 추가 필요
    • 관리자 권한 확인 로직 구현 필요
  2. HTTP GET 메소드 사용

    • 중요 작업 실행은 POST로 변경 권장
  3. 접근 제어 부재

    • IP 화이트리스팅 구현 필요
    • 요청 빈도 제한(Rate Limiting) 추가 필요

Swagger에서 @hidden으로 처리되어 있으나, 이는 문서 숨김일 뿐 실제 보안 통제가 아닙니다.

🔗 Analysis chain

배치 작업 실행 엔드포인트에 보안 제어 추가 필요

배치 작업을 수동으로 실행할 수 있는 엔드포인트가 노출되어 있습니다. 이는 잠재적인 보안 위험이 될 수 있습니다. 다음과 같은 보안 조치를 고려해주세요:

  1. 관리자 권한 확인
  2. IP 화이트리스팅
  3. 요청 제한(Rate limiting)

다음 스크립트로 현재 구현된 보안 조치를 확인할 수 있습니다:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: 보안 관련 구현 확인

# 권한 검사 구현 확인
rg -l "hasRole|hasAuthority|@Secured|@PreAuthorize" 

# IP 제한 구현 확인
rg -l "WebSecurity|SecurityConfig|FilterChain"

Length of output: 1441


Script:

#!/bin/bash
# Check BatchApi interface and security config details
ast-grep --pattern 'interface BatchApi {
  $$$
}'

# Look for security configuration details
rg -A 10 "SecurityConfig|@Configuration.*Security" 

# Check if there are any rate limiting implementations
rg -l "RateLimit|@RateLimit"

Length of output: 135


Script:

#!/bin/bash
# Find BatchApi interface definition
fd BatchApi

# Check for security configurations in batch-related files
rg -A 5 "@Security|@Secured|@PreAuthorize" idle-presentation/src/main/kotlin/com/swm/idle/presentation/batch/

# Look for general security configurations
rg -A 10 "configure.*Security|@EnableWebSecurity"

Length of output: 259


Script:

#!/bin/bash
# Check BatchApi interface content
cat idle-presentation/src/main/kotlin/com/swm/idle/presentation/batch/api/BatchApi.kt

Length of output: 782

Comment on lines +15 to +19
@Hidden
@Operation(summary = "배치 엔트포인트 실행 API")
@GetMapping
@ResponseStatus(HttpStatus.OK)
fun launchBatch()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

배치 작업 실행 결과 반환 타입 명시 필요

현재 launchBatch() 메소드는 반환 타입이 지정되어 있지 않습니다. 배치 작업의 실행 상태나 결과를 클라이언트에게 전달하기 위해 적절한 응답 타입을 정의하는 것이 좋습니다.

다음과 같은 개선을 제안합니다:

-    fun launchBatch()
+    fun launchBatch(): BatchExecutionResponse

추가로 응답 클래스를 정의해주세요:

data class BatchExecutionResponse(
    val status: String,
    val message: String,
    val timestamp: Long
)

Comment on lines +27 to +33
fun jobStart() {
val jobParameters: JobParameters = JobParametersBuilder()
.addLong("timestamp", System.currentTimeMillis())
.toJobParameters()

jobLauncher.run(jobRegistry.getJob("crawlingJob"), jobParameters)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

코드 중복 제거 및 예외 처리 개선 필요

  1. scheduleJob()jobStart() 메소드에서 JobParameters 생성 로직이 중복됩니다.
  2. "crawlingJob"이 매직 스트링으로 사용되고 있습니다.
  3. jobStart()에서 예외 처리가 누락되었습니다.

다음과 같은 개선을 제안합니다:

+    private fun createJobParameters(): JobParameters =
+        JobParametersBuilder()
+            .addLong("timestamp", System.currentTimeMillis())
+            .toJobParameters()
+
+    private companion object {
+        const val CRAWLING_JOB_NAME = "crawlingJob"
+    }
+
     fun jobStart() {
-        val jobParameters: JobParameters = JobParametersBuilder()
-            .addLong("timestamp", System.currentTimeMillis())
-            .toJobParameters()
-
-        jobLauncher.run(jobRegistry.getJob("crawlingJob"), jobParameters)
+        runCatching {
+            jobLauncher.run(
+                jobRegistry.getJob(CRAWLING_JOB_NAME),
+                createJobParameters()
+            )
+        }.onFailure { e ->
+            throw BatchJobException("배치 작업 실행 중 오류 발생", e)
+        }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun jobStart() {
val jobParameters: JobParameters = JobParametersBuilder()
.addLong("timestamp", System.currentTimeMillis())
.toJobParameters()
jobLauncher.run(jobRegistry.getJob("crawlingJob"), jobParameters)
}
private fun createJobParameters(): JobParameters =
JobParametersBuilder()
.addLong("timestamp", System.currentTimeMillis())
.toJobParameters()
private companion object {
const val CRAWLING_JOB_NAME = "crawlingJob"
}
fun jobStart() {
runCatching {
jobLauncher.run(
jobRegistry.getJob(CRAWLING_JOB_NAME),
createJobParameters()
)
}.onFailure { e ->
throw BatchJobException("배치 작업 실행 중 오류 발생", e)
}
}

Comment on lines +12 to +28
fun init(): ChromeDriver {
return runCatching {
ChromeDriver(
ChromeDriverService.Builder()
.usingDriverExecutable(File(System.getenv("CHROMEDRIVER_BIN")))
.build()
.also { logger.info { System.getenv("CHROMEDRIVER_BIN") } },
ChromeOptions().apply {
addArguments(*CrawlerConsts.getChromOptions())
setBinary(System.getenv("CHROME_BIN"))
}.also { logger.info { System.getenv("CHROME_BIN")} }
)
}.getOrElse {
logger.error { "ChromeDriver initialization failed: ${it.message}" }
throw RuntimeException("ChromeDriver initialization failed, application will exit.") // 이후 코드가 실행되지 않도록 예외 던짐
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

환경 변수 검증 및 리소스 정리 로직 추가 필요

  1. 환경 변수 CHROMEDRIVER_BINCHROME_BIN의 존재 여부와 유효성을 검증해야 합니다.
  2. ChromeDriver 인스턴스의 정리(cleanup) 로직이 필요합니다.
  3. 예외 메시지가 한글로 작성되어 있습니다 (국제화 고려).

다음과 같은 개선을 제안합니다:

+    private fun validateEnvironment() {
+        val requiredEnvVars = listOf("CHROMEDRIVER_BIN", "CHROME_BIN")
+        val missingEnvVars = requiredEnvVars.filter { System.getenv(it).isNullOrBlank() }
+        
+        if (missingEnvVars.isNotEmpty()) {
+            throw IllegalStateException("Required environment variables not set: $missingEnvVars")
+        }
+    }
+
     fun init(): ChromeDriver {
+        validateEnvironment()
         return runCatching {
             ChromeDriver(
                 ChromeDriverService.Builder()
                    .usingDriverExecutable(File(System.getenv("CHROMEDRIVER_BIN")))
                    .build()
                    .also { logger.info { System.getenv("CHROMEDRIVER_BIN") } },
                ChromeOptions().apply {
                    addArguments(*CrawlerConsts.getChromOptions())
                    setBinary(System.getenv("CHROME_BIN"))
                }.also { logger.info { System.getenv("CHROME_BIN")} }
            )
         }.getOrElse {
             logger.error { "ChromeDriver initialization failed: ${it.message}" }
-            throw RuntimeException("ChromeDriver initialization failed, application will exit.") // 이후 코드가 실행되지 않도록 예외 던짐
+            throw RuntimeException("ChromeDriver initialization failed, application will exit.")
         }
     }
+
+    fun shutdown(driver: ChromeDriver) {
+        runCatching {
+            driver.quit()
+        }.onFailure {
+            logger.error { "Failed to shutdown ChromeDriver: ${it.message}" }
+        }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun init(): ChromeDriver {
return runCatching {
ChromeDriver(
ChromeDriverService.Builder()
.usingDriverExecutable(File(System.getenv("CHROMEDRIVER_BIN")))
.build()
.also { logger.info { System.getenv("CHROMEDRIVER_BIN") } },
ChromeOptions().apply {
addArguments(*CrawlerConsts.getChromOptions())
setBinary(System.getenv("CHROME_BIN"))
}.also { logger.info { System.getenv("CHROME_BIN")} }
)
}.getOrElse {
logger.error { "ChromeDriver initialization failed: ${it.message}" }
throw RuntimeException("ChromeDriver initialization failed, application will exit.") // 이후 코드가 실행되지 않도록 예외 던짐
}
}
private fun validateEnvironment() {
val requiredEnvVars = listOf("CHROMEDRIVER_BIN", "CHROME_BIN")
val missingEnvVars = requiredEnvVars.filter { System.getenv(it).isNullOrBlank() }
if (missingEnvVars.isNotEmpty()) {
throw IllegalStateException("Required environment variables not set: $missingEnvVars")
}
}
fun init(): ChromeDriver {
validateEnvironment()
return runCatching {
ChromeDriver(
ChromeDriverService.Builder()
.usingDriverExecutable(File(System.getenv("CHROMEDRIVER_BIN")))
.build()
.also { logger.info { System.getenv("CHROMEDRIVER_BIN") } },
ChromeOptions().apply {
addArguments(*CrawlerConsts.getChromOptions())
setBinary(System.getenv("CHROME_BIN"))
}.also { logger.info { System.getenv("CHROME_BIN")} }
)
}.getOrElse {
logger.error { "ChromeDriver initialization failed: ${it.message}" }
throw RuntimeException("ChromeDriver initialization failed, application will exit.")
}
}
fun shutdown(driver: ChromeDriver) {
runCatching {
driver.quit()
}.onFailure {
logger.error { "Failed to shutdown ChromeDriver: ${it.message}" }
}
}

Comment on lines +16 to +29
override fun process(item: List<CrawledJobPostingDto>): List<CrawledJobPosting> {
return item.asSequence().mapNotNull {
val roadNameAddress = extractRoadNameAddress(it.clientAddress)
val geoInfo = GeoCodeService.search(roadNameAddress)
if (geoInfo.addresses.isEmpty()) {
return@mapNotNull null
}
val clientLocation = PointConverter.convertToPoint(
latitude = geoInfo.addresses[0].y.toDouble(),
longitude = geoInfo.addresses[0].x.toDouble(),
)
it.toDomain(clientLocation)
}.toList()
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

GeoCodeService 호출에 대한 예외 처리가 필요합니다.

GeoCodeService.search() 호출 시 발생할 수 있는 예외 상황에 대한 처리가 없습니다. 또한, 배치 작업의 진행 상황을 추적하기 위한 로깅도 추가하면 좋을 것 같습니다.

다음과 같이 수정하는 것을 제안드립니다:

 override fun process(item: List<CrawledJobPostingDto>): List<CrawledJobPosting> {
+    log.info("Processing ${item.size} job postings")
     return item.asSequence().mapNotNull {
         val roadNameAddress = extractRoadNameAddress(it.clientAddress)
+        try {
             val geoInfo = GeoCodeService.search(roadNameAddress)
             if (geoInfo.addresses.isEmpty()) {
+                log.warn("No address found for: $roadNameAddress")
                 return@mapNotNull null
             }
             val clientLocation = PointConverter.convertToPoint(
                 latitude = geoInfo.addresses[0].y.toDouble(),
                 longitude = geoInfo.addresses[0].x.toDouble(),
             )
             it.toDomain(clientLocation)
+        } catch (e: Exception) {
+            log.error("Error processing address: $roadNameAddress", e)
+            return@mapNotNull null
+        }
     }.toList()
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override fun process(item: List<CrawledJobPostingDto>): List<CrawledJobPosting> {
return item.asSequence().mapNotNull {
val roadNameAddress = extractRoadNameAddress(it.clientAddress)
val geoInfo = GeoCodeService.search(roadNameAddress)
if (geoInfo.addresses.isEmpty()) {
return@mapNotNull null
}
val clientLocation = PointConverter.convertToPoint(
latitude = geoInfo.addresses[0].y.toDouble(),
longitude = geoInfo.addresses[0].x.toDouble(),
)
it.toDomain(clientLocation)
}.toList()
}
override fun process(item: List<CrawledJobPostingDto>): List<CrawledJobPosting> {
log.info("Processing ${item.size} job postings")
return item.asSequence().mapNotNull {
val roadNameAddress = extractRoadNameAddress(it.clientAddress)
try {
val geoInfo = GeoCodeService.search(roadNameAddress)
if (geoInfo.addresses.isEmpty()) {
log.warn("No address found for: $roadNameAddress")
return@mapNotNull null
}
val clientLocation = PointConverter.convertToPoint(
latitude = geoInfo.addresses[0].y.toDouble(),
longitude = geoInfo.addresses[0].x.toDouble(),
)
it.toDomain(clientLocation)
} catch (e: Exception) {
log.error("Error processing address: $roadNameAddress", e)
return@mapNotNull null
}
}.toList()
}

Comment on lines +4 to +5
CRAWLING_TARGET_URL_FORMAT("CRAWLING_TARGET_URL_FORMAT","https://www.work24.go.kr/wk/a/b/1200/retriveDtlEmpSrchList.do?basicSetupYn=&careerTo=&keywordJobCd=&occupation=&seqNo=&cloDateEndtParam=&payGbn=&templateInfo=&rot2WorkYn=&shsyWorkSecd=&srcKeywordParam=%EC%9A%94%EC%96%91%EB%B3%B4%ED%98%B8%EC%82%AC&resultCnt=50&keywordJobCont=&cert=&moreButtonYn=Y&minPay=&codeDepth2Info=11000&currentPageNo=1&eventNo=&mode=&major=&resrDutyExcYn=&eodwYn=&sortField=DATE&staArea=&sortOrderBy=DESC&keyword=%EC%9A%94%EC%96%91%EB%B3%B4%ED%98%B8%EC%82%AC&termSearchGbn=all&carrEssYns=&benefitSrchAndOr=O&disableEmpHopeGbn=&actServExcYn=&keywordStaAreaNm=&maxPay=&emailApplyYn=&codeDepth1Info=11000&keywordEtcYn=&regDateStdtParam={yesterday}&publDutyExcYn=&keywordJobCdSeqNo=&viewType=&exJobsCd=&templateDepthNmInfo=&region=&employGbn=&empTpGbcd=&computerPreferential=&infaYn=&cloDateStdtParam=&siteClcd=WORK&searchMode=Y&birthFromYY=&indArea=&careerTypes=&subEmpHopeYn=&tlmgYn=&academicGbn=&templateDepthNoInfo=&foriegn=&entryRoute=&mealOfferClcd=&basicSetupYnChk=&station=&holidayGbn=&srcKeyword=%EC%9A%94%EC%96%91%EB%B3%B4%ED%98%B8%EC%82%AC&academicGbnoEdu=noEdu&enterPriseGbn=all&cloTermSearchGbn=all&birthToYY=&keywordWantedTitle=&stationNm=&benefitGbn=&notSrcKeywordParam=&keywordFlag=&notSrcKeyword=&essCertChk=&depth2SelCode=&keywordBusiNm=&preferentialGbn=&rot3WorkYn=&regDateEndtParam={yesterday}&pfMatterPreferential=&pageIndex={pageIndex}&termContractMmcnt=&careerFrom=&laborHrShortYn=#scrollLoc"),
JOB_POSTING_COUNT_PER_PAGE("JOB_POSTING_COUNT_PER_PAGE","50"),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

URL 및 페이지 크기가 하드코딩되어 있음

URL과 페이지 크기가 하드코딩되어 있어 환경별 설정이 어렵습니다.

설정을 외부화하고 환경별로 다르게 설정할 수 있도록 수정이 필요합니다:

-    CRAWLING_TARGET_URL_FORMAT("CRAWLING_TARGET_URL_FORMAT","https://www.work24.go.kr/wk/a/b/1200/retriveDtlEmpSrchList.do?basicSetupYn=&careerTo=&keywordJobCd=&occupation=&seqNo=&cloDateEndtParam=&payGbn=&templateInfo=&rot2WorkYn=&shsyWorkSecd=&srcKeywordParam=%EC%9A%94%EC%96%91%EB%B3%B4%ED%98%B8%EC%82%AC&resultCnt=50&keywordJobCont=&cert=&moreButtonYn=Y&minPay=&codeDepth2Info=11000&currentPageNo=1&eventNo=&mode=&major=&resrDutyExcYn=&eodwYn=&sortField=DATE&staArea=&sortOrderBy=DESC&keyword=%EC%9A%94%EC%96%91%EB%B3%B4%ED%98%B8%EC%82%AC&termSearchGbn=all&carrEssYns=&benefitSrchAndOr=O&disableEmpHopeGbn=&actServExcYn=&keywordStaAreaNm=&maxPay=&emailApplyYn=&codeDepth1Info=11000&keywordEtcYn=&regDateStdtParam={yesterday}&publDutyExcYn=&keywordJobCdSeqNo=&viewType=&exJobsCd=&templateDepthNmInfo=&region=&employGbn=&empTpGbcd=&computerPreferential=&infaYn=&cloDateStdtParam=&siteClcd=WORK&searchMode=Y&birthFromYY=&indArea=&careerTypes=&subEmpHopeYn=&tlmgYn=&academicGbn=&templateDepthNoInfo=&foriegn=&entryRoute=&mealOfferClcd=&basicSetupYnChk=&station=&holidayGbn=&srcKeyword=%EC%9A%94%EC%96%91%EB%B3%B4%ED%98%B8%EC%82%AC&academicGbnoEdu=noEdu&enterPriseGbn=all&cloTermSearchGbn=all&birthToYY=&keywordWantedTitle=&stationNm=&benefitGbn=&notSrcKeywordParam=&keywordFlag=&notSrcKeyword=&essCertChk=&depth2SelCode=&keywordBusiNm=&preferentialGbn=&rot3WorkYn=&regDateEndtParam={yesterday}&pfMatterPreferential=&pageIndex={pageIndex}&termContractMmcnt=&careerFrom=&laborHrShortYn=#scrollLoc"),
-    JOB_POSTING_COUNT_PER_PAGE("JOB_POSTING_COUNT_PER_PAGE","50"),
+    CRAWLING_TARGET_URL_FORMAT("CRAWLING_TARGET_URL_FORMAT", System.getenv("CRAWLING_TARGET_URL_FORMAT") ?: DEFAULT_URL),
+    JOB_POSTING_COUNT_PER_PAGE("JOB_POSTING_COUNT_PER_PAGE", System.getenv("JOB_POSTING_COUNT_PER_PAGE") ?: "50"),

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +112 to +123
private fun extractAddress(vararg cons: CrawlerConsts): String {
for (con in cons) {
runCatching {
val address = driver.findElement(By.xpath(con.value)).text
return address.replace("지도보기", "").trim().replace(Regex("\\(\\d{5}\\)"), "").trim()
} .getOrElse { e ->
logError(con.location)
throw e
}
}
throw NoSuchElementException("Center address not found using any of the provided XPaths")
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

주소 추출 로직 개선 필요

주소 추출 시 모든 XPath가 실패할 경우 예외가 발생하며, 정규식 처리가 복잡합니다.

다음과 같이 로직을 개선하세요:

     private fun extractAddress(vararg cons: CrawlerConsts): String {
+        val addressPattern = Regex("\\(\\d{5}\\)|지도보기")
         for (con in cons) {
             runCatching {
                 val address = driver.findElement(By.xpath(con.value)).text
-                return address.replace("지도보기", "").trim().replace(Regex("\\(\\d{5}\\)"), "").trim()
+                return address.replace(addressPattern, "").trim()
             } .getOrElse { e ->
                 logError(con.location)
-                throw e
+                logger.warn(e) { "${con.location} 주소 추출 실패" }
+                continue
             }
         }
-        throw NoSuchElementException("Center address not found using any of the provided XPaths")
+        return "주소 정보 없음"
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private fun extractAddress(vararg cons: CrawlerConsts): String {
for (con in cons) {
runCatching {
val address = driver.findElement(By.xpath(con.value)).text
return address.replace("지도보기", "").trim().replace(Regex("\\(\\d{5}\\)"), "").trim()
} .getOrElse { e ->
logError(con.location)
throw e
}
}
throw NoSuchElementException("Center address not found using any of the provided XPaths")
}
private fun extractAddress(vararg cons: CrawlerConsts): String {
val addressPattern = Regex("\\(\\d{5}\\)|지도보기")
for (con in cons) {
runCatching {
val address = driver.findElement(By.xpath(con.value)).text
return address.replace(addressPattern, "").trim()
} .getOrElse { e ->
logError(con.location)
logger.warn(e) { "${con.location} 주소 추출 실패" }
continue
}
}
return "주소 정보 없음"
}

Comment on lines +29 to +34
try {
val post: CrawledJobPostingDto = createPost()
crawledPostings.add(post)
} catch (e: Exception) {
logger.warn { "실패" }
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

예외 처리 개선 필요

예외가 발생했을 때 단순히 "실패"라는 메시지만 로깅하고 있어 디버깅이 어렵습니다.

다음과 같이 예외 처리를 개선하세요:

             try {
                 val post: CrawledJobPostingDto = createPost()
                 crawledPostings.add(post)
             } catch (e: Exception) {
-                logger.warn { "실패" }
+                logger.error(e) { "게시물 크롤링 실패 - URL: ${driver.currentUrl}" }
+                errorCountMap["crawl_failure"] = errorCountMap.getOrDefault("crawl_failure", 0) + 1
             }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 detekt (1.23.7)

[warning] 32-32: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

Comment on lines +16 to +17
private var driver: WebDriver = DriverInitializer.init()
private var errorCountMap: MutableMap<String, Int> = mutableMapOf()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

리소스 관리 개선 필요

WebDriver 인스턴스가 명시적으로 닫히지 않을 수 있으며, 예외 발생 시 리소스 누수가 발생할 수 있습니다.

AutoCloseable을 구현하여 리소스 관리를 개선하세요:

-class WorknetPostCrawler {
+class WorknetPostCrawler : AutoCloseable {
     private val logger = KotlinLogging.logger { }
     private var driver: WebDriver = DriverInitializer.init()
     private var errorCountMap: MutableMap<String, Int> = mutableMapOf()
+
+    override fun close() {
+        try {
+            driver.quit()
+        } catch (e: Exception) {
+            logger.error(e) { "WebDriver 종료 중 오류 발생" }
+        }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private var driver: WebDriver = DriverInitializer.init()
private var errorCountMap: MutableMap<String, Int> = mutableMapOf()
class WorknetPostCrawler : AutoCloseable {
private val logger = KotlinLogging.logger { }
private var driver: WebDriver = DriverInitializer.init()
private var errorCountMap: MutableMap<String, Int> = mutableMapOf()
override fun close() {
try {
driver.quit()
} catch (e: Exception) {
logger.error(e) { "WebDriver 종료 중 오류 발생" }
}
}

Comment on lines +138 to +141
private fun backWindow(originalWindow: String?) {
driver.close()
driver.switchTo().window(originalWindow)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

윈도우 관리 안전성 개선 필요

윈도우 전환 시 originalWindow가 null일 경우 처리가 없습니다.

다음과 같이 안전성을 개선하세요:

     private fun backWindow(originalWindow: String?) {
+        if (originalWindow == null) {
+            logger.error { "원본 윈도우 핸들이 null입니다" }
+            return
+        }
         driver.close()
-        driver.switchTo().window(originalWindow)
+        runCatching {
+            driver.switchTo().window(originalWindow)
+        }.onFailure { e ->
+            logger.error(e) { "원본 윈도우로 전환 실패" }
+            throw IllegalStateException("원본 윈도우로 전환할 수 없습니다", e)
+        }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private fun backWindow(originalWindow: String?) {
driver.close()
driver.switchTo().window(originalWindow)
}
private fun backWindow(originalWindow: String?) {
if (originalWindow == null) {
logger.error { "원본 윈도우 핸들이 null입니다" }
return
}
driver.close()
runCatching {
driver.switchTo().window(originalWindow)
}.onFailure { e ->
logger.error(e) { "원본 윈도우로 전환 실패" }
throw IllegalStateException("원본 윈도우로 전환할 수 없습니다", e)
}
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
idle-batch/src/main/kotlin/com/swm/idle/batch/crawler/WorknetPageCrawler.kt (2)

15-35: 🛠️ Refactor suggestion

리소스 관리와 날짜 처리 개선이 필요합니다.

  1. WebDriver 리소스가 예외 발생 시에도 안전하게 해제되어야 합니다.
  2. 크롤링 대상 날짜를 유연하게 설정할 수 있어야 합니다.

다음과 같이 수정하는 것을 제안드립니다:

+    companion object {
+        private const val CRAWLING_DATE_FORMAT = "yyyyMMdd"
+    }
+
     fun initCounts(reader: PostingReader) {
+        try {
             reader.crawlingUrl = CrawlerConsts.CRAWLING_TARGET_URL_FORMAT.value
-                    .replace("{yesterday}", LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))
+                    .replace("{yesterday}", LocalDate.now().format(DateTimeFormatter.ofPattern(CRAWLING_DATE_FORMAT)))
                     .replace("{pageIndex}", "1")

             moveToPage(reader)

             reader.postingCount = driver
                 .findElement(By.xpath(CrawlerConsts.JOB_POSTING_COUNT.value))
                 .text.toInt()
                 .takeIf { it > 0 }
                 ?: run {
-                    driver.quit()
                     throw Exception("크롤링 할 공고가 없습니다.")
                 }

             reader.pageCount = (reader.postingCount + CrawlerConsts.JOB_POSTING_COUNT_PER_PAGE.getIntValue() - 1) /
                     CrawlerConsts.JOB_POSTING_COUNT_PER_PAGE.getIntValue()
             reader.lastPageJobPostingCount = reader.postingCount % CrawlerConsts.JOB_POSTING_COUNT_PER_PAGE.getIntValue()
-            driver.quit()
+        } finally {
+            close()
+        }
     }

37-43: 🛠️ Refactor suggestion

타임아웃 설정 개선과 예외 처리가 필요합니다.

WebDriver 타임아웃이 하드코딩되어 있으며, 요소를 찾지 못했을 때의 예외 처리가 부족합니다.

다음과 같이 수정하는 것을 제안드립니다:

+    companion object {
+        private const val DRIVER_TIMEOUT_SECONDS = 10L
+    }
+
     private fun moveToPage(reader: PostingReader) {
+        try {
             driver.get(reader.crawlingUrl)
-            WebDriverWait(driver, Duration.ofSeconds(10))
+            WebDriverWait(driver, Duration.ofSeconds(DRIVER_TIMEOUT_SECONDS))
                 .also {
                     it.until(ExpectedConditions.visibilityOfElementLocated(By.xpath(CrawlerConsts.JOB_POSTING_COUNT.value)))
                 }
+        } catch (e: Exception) {
+            throw BatchException("페이지 로딩 중 오류가 발생했습니다: ${reader.crawlingUrl}", e)
+        }
     }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03a6b1b and 1a66b16.

📒 Files selected for processing (1)
  • idle-batch/src/main/kotlin/com/swm/idle/batch/crawler/WorknetPageCrawler.kt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and analyze

Comment on lines +12 to +14
class WorknetPageCrawler {
private var driver: WebDriver = DriverInitializer.init()

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

리소스 관리를 위해 AutoCloseable 구현이 필요합니다.

WebDriver 리소스의 안전한 해제를 보장하기 위해 AutoCloseable을 구현하는 것이 좋습니다.

다음과 같이 수정하는 것을 제안드립니다:

-class WorknetPageCrawler {
+class WorknetPageCrawler : AutoCloseable {
     private var driver: WebDriver = DriverInitializer.init()
+
+    override fun close() {
+        driver.quit()
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class WorknetPageCrawler {
private var driver: WebDriver = DriverInitializer.init()
class WorknetPageCrawler : AutoCloseable {
private var driver: WebDriver = DriverInitializer.init()
override fun close() {
driver.quit()
}

@mjj111 mjj111 merged commit ec970c1 into develop Jan 30, 2025
5 checks passed
@mjj111 mjj111 deleted the feat/IDLE-531 branch January 30, 2025 12:49
mjj111 added a commit that referenced this pull request Jan 30, 2025
* [IDLE-000] Production CI 스크립트 작성

* [IDLE-000] 센터 공고 수정 API 내 접수 방법 null 비허용

* [IDLE-000] 공고 지원자 조회 시, 삭제된 유저는 조회되지 않도록 수정

* [IDLE-363] 센터 관리자 전화 인증 요청 API

* [IDLE-363] 개발 환경 ddl-auto 옵션 임시로 create 옵션으로 변경

* [IDLE-365] 공고 크롤러 selenium 로직 수정 및 로컬 동작 확인

* [IDLE-365] 테스트를 위한 스케줄러 기준 시각 변경

* [IDLE-365] batch job enable 옵션 비활성화

* [IDLE-365] ddl-auto update로 변경

* [IDLE-365] 크롤링 공고 필드 null 비허용

* [IDLE-365] 스케줄러 시간 02시로 설정

* [IDLE-366] spring batch selenium 크롤링을 위한 빌드 스크립트 수정

* [IDLE-366] worknet 사이트가 고용 24 사이트로 통합됨에 따라, 크롤링 스크립트 수정

* [IDLE-366] 크롤러 동작 스케줄링 시간 변경

* [IDLE-366] 스프링 초기 실행 시, spring batch 자동 실행 방지 옵션 추가

* [IDLE-366] spring batch 5 버전에서 업데이트 된 변경사항 적용 및 별도의 JobLauncher 구현

* [IDLE-366] 불필요 의존성 및 옵션 제거

* [IDLE-366] 크롤링 조회 API 내 entity status 필드 추가 및 쿼리 수정

* [IDLE-358] 운영 환경 CD 구축 및 운영 환경 profile 설정 추가

* [IDLE-358] 운영 환경 docker run 실행 시 예외 처리

* [IDLE-000] 크롤링 전체 조회 DTO 생성자 추가

* [IDLE-000] 공고 전체 조회 fetchJoin() 중복 이슈 해결을 위한 subquery 분리

* [IDLE-000] AI 코드리뷰 coderabbit 도입

* [IDLE-000] 크롤링 공고 반경범위 조회 필터를 위한 where절 추가

* [IDLE-000] TimeZone 설정이 적용되지 않는 문제 해결

* [IDLE-000] TimeZone 지정 및 @EnableScheduling 설정

* [IDLE-000] TimeZone 설정 제거

* [IDLE-000] 공고 전체 조회 쿼리 롤백

* [IDLE-000] 기존 공고 내 location을 기반으로 위.경도 값을 decoding하도록 변경

* [IDLE-000] 배포 전 최종 QA

* [IDLE-000] 크롤링 공고 생성일자 필드 type 변경(timestamp -> date)

* [IDLE-000] Redis 비밀번호 설정 추가 (#171)

* [IDLE-000] Redis 비밀번호 설정 추가

* [IDLE-000] yaml 파일에 password 필드 추가

* [IDLE-000] redis local default password 설정

* [IDLE-000] compose 파일 내, 비밀번호 지정 command 설정

* [IDLE-000] Redis 볼륨 설정

* [IDLE-000] ci triggering branch 임시 변경

* [IDLE-000] ci triggering branch 롤백

* [IDLE-000] 테스트 심사 통과를 위한 전화번호 검증 로직 추가

* [IDLE-000] 테스트 심사 통과를 위한 전화번호 검증 로직 추가

* [IDLE-389] 센터 인증 요청 이벤트 발생 시, 디스코드 웹훅 알림을 전송하는 로직 작성

* [IDLE-389] 사업자 등록번호 client properties 클래스명 변경

* [IDLE-389] 코드 리뷰 반영

* [IDLE-000] 트랜잭션 전파 레벨 변경(REQUIRED -> REQUIRES_NEW)

* [IDLE-396] FCM 모듈 추가 및 firebase 의존성 설정

* readme 제목 수정

* readme 제목 수정

* [IDLE-399] FCM Device Token 관리 API

* [IDLE-399] 알림 도메인 설계

* [IDLE-000] fcm service 설정값 주입을 위한 디렉토리 구조 변경

* [IDLE-000] fcm service 설정값 주입을 위한 디렉토리 구조 변경

* [IDLE-400] 채용 공고 지원자 발생 시, 센터 관리자에게 알림을 발송한다.

* [IDLE-400] fcm 모듈 설정 추가

* [IDLE-400] 공고 지원자 발생 시, 모든 센터 관리자들에게 다중 알림을 발송한다.

* [IDLE-415] 알림 조회 처리 API

* [IDLE-417] 읽지 않은 알림 수 집계 API

* [IDLE-418] 알림 목록 조회 API

* [IDLE-418] 피드백 반영

* [IDLE-423] soft-delete가 적용된 즐겨찾기 entity에서, 즐겨찾기 해제 후 다시 설정하는 경우 발생하는 버그를 해결한다.

* [IDLE-424] 요양 보호사 공고 전체 조회 시, 삭제된 공고가 함께 보이는 문제 해결

* [IDLE-400] 채용 공고 지원자 발생 시, 센터 관리자에게 알림을 발송한다. (#183)

* [IDLE-400] 채용 공고 지원자 발생 시, 센터 관리자에게 알림을 발송한다.

* [IDLE-400] fcm 모듈 설정 추가

* [IDLE-400] 공고 지원자 발생 시, 모든 센터 관리자들에게 다중 알림을 발송한다.

* [IDLE-400] 채용 공고 지원자 발생 시, 센터 관리자에게 알림을 발송한다.

* [IDLE-400] fcm 모듈 설정 추가

* [IDLE-400] 공고 지원자 발생 시, 모든 센터 관리자들에게 다중 알림을 발송한다.

* [IDLE-415] 알림 조회 처리 API

* [IDLE-417] 읽지 않은 알림 수 집계 API

* [IDLE-418] 알림 목록 조회 API

* [IDLE-418] 피드백 반영

* [IDLE-423] soft-delete가 적용된 즐겨찾기 entity에서, 즐겨찾기 해제 후 다시 설정하는 경우 발생하는 버그를 해결한다.

* [IDLE-424] 요양 보호사 공고 전체 조회 시, 삭제된 공고가 함께 보이는 문제 해결

* [IDLE-400] 알림 명세 변경

* [IDLE-400] fcm 설정 파일 디렉토리 path 변경

* [IDLE-400] ddl 옵션 변경

* [IDLE-400] ci/cd 옵션 변경

* [IDLE-000] ci triggering branch 복구

* [IDLE-000] develop 환경에서 fcm service 설정 파일 path 수정

* [IDLE-000] fcm service json 파일명 변경

* [IDLE-000] firebase service key 설정 경로 체크를 위해, cd 스크립트를 수정합니다.

* Update README.md

* [IDLE-000] fcm service key 경로를 class path 경로로 수정 시도

* [IDLE-000] ci triggering branch 임시 변경

* [IDLE-000] firebaseApp 초기화 임시 비활성화

* [IDLE-000] firebaseApp 초기화 임시 비활성화

* [IDLE-000] firebaseApp 초기화 임시 비활성화

* [IDLE-000] file path 앞에 ./ 제외

* [IDLE-000] 절대 경로로 변경 시도

* [IDLE-000] fcm service key 생성 path 수정

* [IDLE-000] fcm service key json file 생성 스크립트 작성

* [IDLE-000] ci triggering branch develop으로 롤백

* [IDLE-000] 불필요 스크립트 제거

* [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가 (#197)

* [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가

* [IDLE-000] ci triggering branch 수정

* [IDLE-000] ci triggering branch 수정

* [IDLE-000] firebase app 초기화 로직 주석 처리

* [IDLE-000] firebase app 초기화 로직 주석 처리

* [IDLE-000] firebase app 초기화 로직 주석 처리

* [IDLE-000] file 대신 string으로 주입받아 초기화하는 방식으로 전환

* [IDLE-000] 일반 string 대신 base64 인코딩 된 문자열을 주입하도록 처리

* [IDLE-000] 일반 string 주입으로 rollback

* [IDLE-000] json string log 추가

* [IDLE-000] base64 문자열로 재 변경

* [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가

* [IDLE-429] DB 형상관리를 위한 Flyway 적용

* [IDLE-429] Flyway latest version으로 설정

* [IDLE-429] 피드백 반영

* [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가

* [IDLE-000] 알림 조회 처리 로직에 @transactional 추가

* [IDLE-000] 알림 조회 시, 생성 시각 기준이 아닌 uuid v7 id 기준으로 내림차순 정렬하도록 변경

* [IDLE-000] CI 트리거 브랜치 복구

* [IDLE-454] 테스트를 위한 크롤링 수행 시각 변경 및 로깅 추가

* [IDLE-000] 공고 범위 검색 내 중복 데이터 발생 방지

* [IDLE-000] ci 트리거 브랜치 변경

* [IDLE-000] ci 트리거 브랜치 롤백

* [IDLE-000] monitoring 모듈 추가 및 actuator, prometheus 의존성 추가

* [IDLE-456] monitoring.yml 작성

* [IDLE-456] monitoring profile 추가

* [IDLE-456] actuator dependency group name 수정

* [IDLE-000] 크롤링 주기 하루 2회로 변경

* [IDLE-000] 불필요 로그 제거

* [IDLE-000] 인증번호 SMS 내용 수정

* [IDLE-000] classPath 하위 yaml 파일 확장자 모두 .yml로 통일

* [IDLE-000] 즐겨찾기 facade service에 트랜잭션 추가 (#206)

* [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가

* [IDLE-000] facade service에 transaction 추가

* [IDLE-461] 유저가 다중 디바이스에서 알림을 받을 수 있도록 개선한다. (#207)

* [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가

* [IDLE-461] 한 유저가 다중 디바이스 설정이 가능하도록 한다.

* [IDLE-000] firebase config의 현재 경로를 출력하도록 print문 추가

* [IDLE-000] presentation module의 gradle 파일에 monitoring 모듈 dependency 추가

* [IDLE-000] deviceToken 서비스에서 트랜잭션 어노테이션 추가

* [IDLE-000] batch 테스트를 위해 30분마다 동작하도록 변경

* [IDLE-475] 채팅, 채팅방 도메인 설계

* [IDLE-475] 채팅 메세지 최소, 최대 길이 제한 설정

* [IDLE-000] Batch 메타데이터 테이블 스크립트 변경 및 크롤링 수행 주기 설정

* [IDLE-000] 운영 환경 CD 스크립트에서 불필요한 step 제거

* [IDLE-000] 도텐브 파일 공백 제거 스크립트 작성, 크롤링 대상일자 전날 등록된 공고로 변경

* [IDLE-000] 크롤링 기준 시각 15시로 임시 변경

* [IDLE-000] cd env 파일 생성 스크립트 변경

* [IDLE-492] 동일 유저가 여러 개의 디바이스를 사용 시, 알림이 중복해서 누적되는 현상

* [IDLE-493] flyway 스크립트 오탈자 수정

* [IDLE-493] flyway 스크립트 오탈자 수정

* [IDLE-494] 요양 보호사 및 센터 프로필에서 긴 텍스트를 입력 가능한 항목을 TEXT 컬럼으로 지정한다.

* [IDLE-495] 요양 보호사는 마감된 공고에 지원이 불가능하다.

* [IDLE-495] 공고 마감 처리 메서드명 변경 complete -> completed

* [IDLE-496] 스프링 프로파일 지정을 위한 환경변수 주입

* [IDLE-496] 로그 일부 수정

* [IDLE-000] 안드로이드 app link를 위한 asset 추가

* [IDLE-190] 센터 관리자 인증 요청 목록 조회 API

* [IDLE-000] 크롤링 주기 변경 및 테스트용 에러 로그 추가

* [IDLE-000] 크롤링 대상 사이트에 알림창(alert)이 뜨는 케이스에 대한 처리

* [IDLE-509] 크롤링 전체 조회 시, 공고가 중복 노출되는 현상 해결

* [IDLE-509] 피드백 반영

* [IDLE-509] 사용하지 않는 하위 서비스 의존성 제거

* [IDLE-000] 크롤링 진행 시각 저녁 11시로 변경

* [IDLE-000] 크롤링 공고 즐겨찾기 조회 로직 버그 수정

* [IDLE-000] 요양 보호사 및 센터 관리자 전화번호 컬럼에 unique index 추가

* [IDLE-512] 센터 관리자 인증 요청 event 변경 및 NotificationInfo 인터페이스 패키지 이동

* [IDLE-512] 센터 관리자 인증 요청 event 변경 및 NotificationInfo 인터페이스 패키지 이동

* [IDLE-513] 요양 보호사 location 필드 추가 및 기존 데이터 마이그레이션

* [IDLE-513] 센터 관리자 공고 등록 시, 주변 요양보호사에게 FCM 알림을 일괄 전송한다.

* [IDLE-513] 센터 관리자 공고 등록 시, 주변 요양보호사에게 FCM 알림을 일괄 전송한다.

* [IDLE-000] notification type enum 속성 추가

* [IDLE-000] 센터 공고 등록 알림 제목 및 프로필 url null로 수정

* [IDLE-000] Readme 업데이트

* [IDLE-504] 센터 관리자 인증 승인 및 거절 API

* [IDLE-456] prometheus, grafana 설정을 위한 monitoring.yml 설정

* [IDLE-476] 웹소켓, Redis pub/sub을 이용한 채팅 전송 기능 (#217)

* [IDLE-476] 웹소켓 dependency 추가

* [IDLE-476] 웹소켓, Redis pub/sub을 이용한 채팅 전송 기능

* [IDLE-476] 불필요 클래스 제거

* [IDLE-476] 채팅 메세지 생성 책임을 하위 도메인에서 생성하도록 수정

* [IDLE-476] hash 역직렬화 시 필요한 처리를 Serializer 설정 추가

* [IDLE-476] json 역직렬화 시, 특수문자 허용

* [IDLE-476] websocket stomp 엔드포인트 노출 설정 변경

* [IDLE-476] 채팅 메세지 길이 정책 적용

* [IDLE-518] bastion서버를 통해 production 서버로 접근 후, 배포하도록 설정 (#252)

* [IDLE-518] ECR 레파지토리 변경 (#254)

* [IDLE-518] bastion서버를 통해 production 서버로 접근 후, 배포하도록 설정

* [IDLE-518] ECR 레파지토리 변경

* [IDLE-518] docker-compose 파일 위치 변경 (#256)

* [IDLE-518] bastion서버를 통해 production 서버로 접근 후, 배포하도록 설정

* [IDLE-518] ECR 레파지토리 변경

* [IDLE-518] docker-compose 파일 경로 변경

* [IDLE-518] 소스파일 경로 변경

* [IDLE-518] target 경로 변경

* [IDLE-518] 소스파일 경로 변경

* [IDLE-518] 타겟파일 경로 원상복구

* [IDLE-518] 타겟파일 경로 수정

* [IDLE-518] compose 파일 실행 경로 수정

* [IDLE-518] 하드코딩된 변수를 secrets로 변경

* [IDLE-518] 하드코딩된 변수를 secrets로 변경

* [IDLE-518] 하드코딩된 변수를 secrets로 변경

* [IDLE-518] Configuration Env file 스탭 추가

* [IDLE-518] secrets로 변경

* [IDLE-518] CI/CD재가동 전, 수동 실행으로 변경 (#257)

* [IDLE-518] bastion서버를 통해 production 서버로 접근 후, 배포하도록 설정

* [IDLE-518] ECR 레파지토리 변경

* [IDLE-518] docker-compose 파일 경로 변경

* [IDLE-518] 소스파일 경로 변경

* [IDLE-518] target 경로 변경

* [IDLE-518] 소스파일 경로 변경

* [IDLE-518] 타겟파일 경로 원상복구

* [IDLE-518] 타겟파일 경로 수정

* [IDLE-518] compose 파일 실행 경로 수정

* [IDLE-518] 하드코딩된 변수를 secrets로 변경

* [IDLE-518] 하드코딩된 변수를 secrets로 변경

* [IDLE-518] 하드코딩된 변수를 secrets로 변경

* [IDLE-518] Configuration Env file 스탭 추가

* [IDLE-518] secrets로 변경

* [IDLE-518] CI/CD 재가동 전 수동으로 수정

* [IDLE-518] CI/CD 재가동 전 수동으로 수정

* [IDLE-518] ECR 레파지토리 작성

* [IDLE-518] 서버 재구축 및 CI/CD 재가동 (#258)

* [IDLE-518] bastion서버를 통해 production 서버로 접근 후, 배포하도록 설정

* [IDLE-518] ECR 레파지토리 변경

* [IDLE-518] docker-compose 파일 경로 변경

* [IDLE-518] 소스파일 경로 변경

* [IDLE-518] target 경로 변경

* [IDLE-518] 소스파일 경로 변경

* [IDLE-518] 타겟파일 경로 원상복구

* [IDLE-518] 타겟파일 경로 수정

* [IDLE-518] compose 파일 실행 경로 수정

* [IDLE-518] 하드코딩된 변수를 secrets로 변경

* [IDLE-518] 하드코딩된 변수를 secrets로 변경

* [IDLE-518] 하드코딩된 변수를 secrets로 변경

* [IDLE-518] Configuration Env file 스탭 추가

* [IDLE-518] secrets로 변경

* [IDLE-518] CI/CD 재가동 전 수동으로 수정

* [IDLE-518] CI/CD 재가동 전 수동으로 수정

* [IDLE-518] ECR 레파지토리 작성

* [IDLE-518] .env 파일 경로 변경

* [IDLE-518] 레지스트리 내용 변경

* [IDLE-518] DB 이름을 caremeet으로 변경

* [IDLE-518] 서비스 간 통신을 위한 컨테이너간 네트워크 설정 추가

* [IDLE-518] ddl create 적용

* [IDLE-518] private_key.pem 파일을 통해 접근하도록 수정

* [IDLE-518] 호스트키 체크 우회 수정

* [IDLE-518] SSH 세션에 터미널을 할당하도록 옵션 추가

* [IDLE-518] 터널링 방법을 수정

* [IDLE-518] Private Subnet에 존재하는 서버의 22번 포트를 로컬 2222 포트로 연결

* [IDLE-518] .env파일 생성하는 명령어 수정

* [IDLE-518] 들여쓰기 수정

* [IDLE-518] INPUT_으로 시작하는 항목, .env 파일에 제외

* [IDLE-518] null 값을 제외하는 처리를 추가

* [IDLE-518] 병합 전에 null 값을 제외하는 처리를 추가

* [IDLE-518] context 출력문 추가

* [IDLE-518] JSON 형식으로 변환한 후, SSH 스크립트 내부에서 이를 파일로 저장

* [IDLE-518] .env 파일에 INSTANCE_PEM_KEY가 들어가지 않도록 수정

* [IDLE-518] ddl을 validate으로 수정

* [IDLE-518] 브랜치 push시에 동작하도록 수정

* [IDLE-531] 배치 작업 정상화 (#260)

* [IDLE-534] Flayway 재설정 및 배치 메타데이터 테이블 생성

* [IDLE-548] Tasklet을 Chunk로 변경

* [IDLE-549] 배치 실행 API 추가

* [IDLE-533] GeoCodeService 전환 메서드 static으로 전환

* [IDLE-533] 책임별 클래스 분리

* [IDLE-547] 멀티스레드 적용 및 공유자원 분리

* [IDLE-531] 크롤링 기준 날짜 변경

---------

Co-authored-by: wonjunYou <[email protected]>
Co-authored-by: Wonjun You <[email protected]>
@mjj111 mjj111 changed the title [IDLE-531] 배치 작업 정상화 [IDLE-531] 배치 작업 정상화 및 최적화 Feb 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨기능 기능 개발 및 구현 🎯리팩토링 리팩토링 및 고도화 이슈
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant