From fc113c0b151923e02c241f961995c6a3b3530115 Mon Sep 17 00:00:00 2001 From: shinae1023 Date: Tue, 16 Jun 2026 15:51:31 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[Feat]=20=EC=97=91=EC=85=80=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=A0=81=EC=9E=AC=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CorpusAdminController.java | 33 +++++++++++++++++++ .../corpus/service/CorpusImportService.java | 6 ++++ 2 files changed, 39 insertions(+) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/corpus/controller/CorpusAdminController.java b/src/main/java/com/jobdri/jobdri_api/domain/corpus/controller/CorpusAdminController.java index 291c2cd..f735d11 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/corpus/controller/CorpusAdminController.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/corpus/controller/CorpusAdminController.java @@ -14,10 +14,14 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.file.InvalidPathException; @@ -47,6 +51,18 @@ public ApiResponse importCorpus( ); } + @Operation(summary = "corpus 엑셀 업로드 적재", description = "관리자가 xlsx 파일을 직접 업로드해 corpus 원본 테이블에 적재합니다.") + @PostMapping(value = "/import/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ApiResponse importCorpusByUpload( + @RequestParam("file") MultipartFile file + ) throws IOException { + validateUploadFile(file); + return ApiResponse.onSuccess( + "업로드한 corpus 엑셀 적재에 성공했습니다.", + corpusImportService.importFromXlsx(file.getInputStream()) + ); + } + @Operation(summary = "corpus 임베딩 동기화", description = "유효한 corpus 데이터를 읽어 pgvector 테이블에 임베딩을 저장합니다.") @PostMapping("/embeddings/sync") public ApiResponse syncEmbeddings( @@ -89,4 +105,21 @@ private Path validateImportPath(String rawPath) { ); } } + + private void validateUploadFile(MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new GeneralException( + GeneralErrorCode.MISSING_PARAMETER, + "업로드할 엑셀 파일이 필요합니다." + ); + } + + String originalFilename = file.getOriginalFilename(); + if (!StringUtils.hasText(originalFilename) || !originalFilename.toLowerCase().endsWith(".xlsx")) { + throw new GeneralException( + GeneralErrorCode.INVALID_PARAMETER, + "xlsx 파일만 업로드할 수 있습니다." + ); + } + } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/corpus/service/CorpusImportService.java b/src/main/java/com/jobdri/jobdri_api/domain/corpus/service/CorpusImportService.java index 44b8edc..e842274 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/corpus/service/CorpusImportService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/corpus/service/CorpusImportService.java @@ -48,6 +48,12 @@ public CorpusImportResult importFromXlsx(Path xlsxPath) throws IOException { } } + public CorpusImportResult importFromXlsx(InputStream inputStream) throws IOException { + try (Workbook workbook = new XSSFWorkbook(inputStream)) { + return importWorkbook(workbook); + } + } + public CorpusImportResult importWorkbook(Workbook workbook) { DataFormatter formatter = new DataFormatter(); FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator(); From 9b501660f037d28aea776f1644d74f4f98014fdf Mon Sep 17 00:00:00 2001 From: shinae1023 Date: Tue, 16 Jun 2026 15:56:16 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[Refactor]=20baseEntity=20=EC=83=81?= =?UTF-8?q?=EC=86=8D=20=EA=B5=AC=EC=A1=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/analysis/entity/Analysis.java | 3 ++- .../entity/CustomQuestionCandidate.java | 9 ++----- .../domain/analysis/entity/Question.java | 3 ++- .../analysis/entity/QuestionAnalysis.java | 3 ++- .../domain/audit/entity/AuditLog.java | 13 +++------- .../domain/company/entity/Company.java | 3 ++- .../entity/CorpusClassificationMapping.java | 9 ++----- .../corpus/entity/MockJobPostingCorpus.java | 9 ++----- .../corpus/entity/MockQuestionCorpus.java | 9 ++----- .../domain/experience/entity/Experience.java | 3 ++- .../domain/jobposting/entity/JobPosting.java | 3 ++- .../jobposting/entity/MockQuestionCache.java | 3 ++- .../domain/mockapply/entity/MockApply.java | 8 ++---- .../mockapply/entity/MockApplySequence.java | 3 ++- .../payment/entity/CreditTransaction.java | 9 ++----- .../domain/payment/entity/Payment.java | 7 ++---- .../jobdri_api/domain/user/entity/User.java | 3 ++- .../global/config/JpaAuditingConfig.java | 9 +++++++ .../jobdri_api/global/entity/BaseEntity.java | 25 +++++++++++++++++++ .../global/entity/CreatedAtEntity.java | 20 +++++++++++++++ 20 files changed, 89 insertions(+), 65 deletions(-) create mode 100644 src/main/java/com/jobdri/jobdri_api/global/config/JpaAuditingConfig.java create mode 100644 src/main/java/com/jobdri/jobdri_api/global/entity/BaseEntity.java create mode 100644 src/main/java/com/jobdri/jobdri_api/global/entity/CreatedAtEntity.java diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/Analysis.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/Analysis.java index ba6852a..4080120 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/Analysis.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/Analysis.java @@ -1,6 +1,7 @@ package com.jobdri.jobdri_api.domain.analysis.entity; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; +import com.jobdri.jobdri_api.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -13,7 +14,7 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder(access = AccessLevel.PRIVATE) @Table(name = "analyses") -public class Analysis { +public class Analysis extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/CustomQuestionCandidate.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/CustomQuestionCandidate.java index 2336009..fce5d3b 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/CustomQuestionCandidate.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/CustomQuestionCandidate.java @@ -1,11 +1,10 @@ package com.jobdri.jobdri_api.domain.analysis.entity; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; +import com.jobdri.jobdri_api.global.entity.CreatedAtEntity; import jakarta.persistence.*; import lombok.*; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -20,7 +19,7 @@ ) } ) -public class CustomQuestionCandidate { +public class CustomQuestionCandidate extends CreatedAtEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -36,15 +35,11 @@ public class CustomQuestionCandidate { @Column(name = "char_limit", nullable = false) private int limit; - @Column(nullable = false) - private LocalDateTime createdAt; - public static CustomQuestionCandidate create(MockApply mockApply, String content, int limit) { return CustomQuestionCandidate.builder() .mockApply(mockApply) .content(content) .limit(limit) - .createdAt(LocalDateTime.now()) .build(); } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/Question.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/Question.java index 71efeaa..a8760e5 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/Question.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/Question.java @@ -1,6 +1,7 @@ package com.jobdri.jobdri_api.domain.analysis.entity; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; +import com.jobdri.jobdri_api.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -13,7 +14,7 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder(access = AccessLevel.PRIVATE) @Table(name = "questions") -public class Question { +public class Question extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/QuestionAnalysis.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/QuestionAnalysis.java index ab393b8..1e5fc38 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/QuestionAnalysis.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/QuestionAnalysis.java @@ -1,5 +1,6 @@ package com.jobdri.jobdri_api.domain.analysis.entity; +import com.jobdri.jobdri_api.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -9,7 +10,7 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder(access = AccessLevel.PRIVATE) @Table(name = "question_analyses") -public class QuestionAnalysis { +public class QuestionAnalysis extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/audit/entity/AuditLog.java b/src/main/java/com/jobdri/jobdri_api/domain/audit/entity/AuditLog.java index ca11e55..45cf67b 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/audit/entity/AuditLog.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/audit/entity/AuditLog.java @@ -1,6 +1,7 @@ package com.jobdri.jobdri_api.domain.audit.entity; import com.jobdri.jobdri_api.domain.user.entity.User; +import com.jobdri.jobdri_api.global.entity.CreatedAtEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -15,13 +16,11 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Entity @Table(name = "audit_logs") @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AuditLog { +public class AuditLog extends CreatedAtEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -51,9 +50,6 @@ public class AuditLog { @Column(length = 500) private String userAgent; - @Column(nullable = false) - private LocalDateTime createdAt; - @Builder(access = AccessLevel.PRIVATE) private AuditLog( User user, @@ -63,8 +59,7 @@ private AuditLog( String beforeValue, String afterValue, String ipAddress, - String userAgent, - LocalDateTime createdAt + String userAgent ) { this.user = user; this.action = action; @@ -74,7 +69,6 @@ private AuditLog( this.afterValue = afterValue; this.ipAddress = ipAddress; this.userAgent = userAgent; - this.createdAt = createdAt; } public static AuditLog create( @@ -96,7 +90,6 @@ public static AuditLog create( .afterValue(afterValue) .ipAddress(ipAddress) .userAgent(userAgent) - .createdAt(LocalDateTime.now()) .build(); } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/company/entity/Company.java b/src/main/java/com/jobdri/jobdri_api/domain/company/entity/Company.java index ac6c3b9..7af4b3f 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/company/entity/Company.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/company/entity/Company.java @@ -1,6 +1,7 @@ package com.jobdri.jobdri_api.domain.company.entity; import com.jobdri.jobdri_api.domain.jobposting.entity.JobPosting; +import com.jobdri.jobdri_api.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -13,7 +14,7 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder(access = AccessLevel.PRIVATE) @Table(name = "companies") -public class Company { +public class Company extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/corpus/entity/CorpusClassificationMapping.java b/src/main/java/com/jobdri/jobdri_api/domain/corpus/entity/CorpusClassificationMapping.java index e392139..e44b0bb 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/corpus/entity/CorpusClassificationMapping.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/corpus/entity/CorpusClassificationMapping.java @@ -1,11 +1,10 @@ package com.jobdri.jobdri_api.domain.corpus.entity; import com.jobdri.jobdri_api.domain.classification.entity.DetailClassification; +import com.jobdri.jobdri_api.global.entity.CreatedAtEntity; import jakarta.persistence.*; import lombok.*; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -20,7 +19,7 @@ ) } ) -public class CorpusClassificationMapping { +public class CorpusClassificationMapping extends CreatedAtEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -39,9 +38,6 @@ public class CorpusClassificationMapping { @JoinColumn(name = "detail_classification_id", nullable = false) private DetailClassification detailClassification; - @Column(nullable = false) - private LocalDateTime createdAt; - public static CorpusClassificationMapping create( String sourceJobGroupL1, String sourceJobFamilyL2, @@ -53,7 +49,6 @@ public static CorpusClassificationMapping create( .sourceJobFamilyL2(sourceJobFamilyL2) .sourceRoleL3(sourceRoleL3) .detailClassification(detailClassification) - .createdAt(LocalDateTime.now()) .build(); } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/corpus/entity/MockJobPostingCorpus.java b/src/main/java/com/jobdri/jobdri_api/domain/corpus/entity/MockJobPostingCorpus.java index a5a9215..69d948f 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/corpus/entity/MockJobPostingCorpus.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/corpus/entity/MockJobPostingCorpus.java @@ -2,11 +2,10 @@ import com.jobdri.jobdri_api.domain.company.entity.Company; import com.jobdri.jobdri_api.domain.classification.entity.DetailClassification; +import com.jobdri.jobdri_api.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -20,7 +19,7 @@ @Index(name = "idx_mock_job_posting_corpus_classification", columnList = "job_group_l1, job_family_l2, role_l3") } ) -public class MockJobPostingCorpus { +public class MockJobPostingCorpus extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -73,9 +72,6 @@ public class MockJobPostingCorpus { @Column(name = "invalid_reason", columnDefinition = "TEXT") private String invalidReason; - @Column(name = "created_at", nullable = false) - private LocalDateTime createdAt; - public static MockJobPostingCorpus create( String sourceAnalysisId, Company company, @@ -109,7 +105,6 @@ public static MockJobPostingCorpus create( .embeddingText(embeddingText) .validForEmbedding(validForEmbedding) .invalidReason(invalidReason) - .createdAt(LocalDateTime.now()) .build(); } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/corpus/entity/MockQuestionCorpus.java b/src/main/java/com/jobdri/jobdri_api/domain/corpus/entity/MockQuestionCorpus.java index 8c203f4..4a4cb16 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/corpus/entity/MockQuestionCorpus.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/corpus/entity/MockQuestionCorpus.java @@ -2,11 +2,10 @@ import com.jobdri.jobdri_api.domain.company.entity.Company; import com.jobdri.jobdri_api.domain.classification.entity.DetailClassification; +import com.jobdri.jobdri_api.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -21,7 +20,7 @@ @Index(name = "idx_mock_question_corpus_classification", columnList = "job_group_l1, job_family_l2, role_l3") } ) -public class MockQuestionCorpus { +public class MockQuestionCorpus extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -71,9 +70,6 @@ public class MockQuestionCorpus { @Column(name = "is_valid_for_embedding", nullable = false) private boolean validForEmbedding; - @Column(name = "created_at", nullable = false) - private LocalDateTime createdAt; - public static MockQuestionCorpus create( String sourceQuestionId, String sourceAnalysisId, @@ -105,7 +101,6 @@ public static MockQuestionCorpus create( .questionText(questionText) .embeddingText(embeddingText) .validForEmbedding(validForEmbedding) - .createdAt(LocalDateTime.now()) .build(); } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/experience/entity/Experience.java b/src/main/java/com/jobdri/jobdri_api/domain/experience/entity/Experience.java index bb6392f..a8f359a 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/experience/entity/Experience.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/experience/entity/Experience.java @@ -2,6 +2,7 @@ import com.jobdri.jobdri_api.domain.skill.entity.Skill; import com.jobdri.jobdri_api.domain.user.entity.User; +import com.jobdri.jobdri_api.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -15,7 +16,7 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder(access = AccessLevel.PRIVATE) @Table(name = "experiences") -public class Experience { +public class Experience extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/entity/JobPosting.java b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/entity/JobPosting.java index 608838b..f5fa84c 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/entity/JobPosting.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/entity/JobPosting.java @@ -4,6 +4,7 @@ import com.jobdri.jobdri_api.domain.company.entity.Company; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; import com.jobdri.jobdri_api.domain.user.entity.User; +import com.jobdri.jobdri_api.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -16,7 +17,7 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder(access = AccessLevel.PRIVATE) @Table(name = "job_postings") -public class JobPosting { +public class JobPosting extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/entity/MockQuestionCache.java b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/entity/MockQuestionCache.java index c7ccb69..7f79b6d 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/entity/MockQuestionCache.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/entity/MockQuestionCache.java @@ -2,6 +2,7 @@ import com.jobdri.jobdri_api.domain.classification.entity.DetailClassification; import com.jobdri.jobdri_api.domain.company.entity.Company; +import com.jobdri.jobdri_api.global.entity.BaseEntity; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; @@ -36,7 +37,7 @@ columnNames = {"company_id", "detail_classification_id", "prompt_version"} ) ) -public class MockQuestionCache { +public class MockQuestionCache extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/entity/MockApply.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/entity/MockApply.java index b3d6eee..190ae18 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/entity/MockApply.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/entity/MockApply.java @@ -4,10 +4,10 @@ import com.jobdri.jobdri_api.domain.jobposting.entity.JobPosting; import com.jobdri.jobdri_api.domain.analysis.entity.Question; import com.jobdri.jobdri_api.domain.user.entity.User; +import com.jobdri.jobdri_api.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -23,7 +23,7 @@ columnNames = {"user_id", "job_posting_id", "sequence"} ) ) -public class MockApply { +public class MockApply extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -47,9 +47,6 @@ public class MockApply { private Integer sequence; - @Column(nullable = false) - private LocalDateTime createdAt; - @OneToOne(mappedBy = "mockApply", cascade = CascadeType.ALL, orphanRemoval = true) private Analysis analysis; @@ -68,7 +65,6 @@ public static MockApply create(User user, JobPosting jobPosting, ApplyType apply .applyType(applyType) .status(MockApplyStatus.APPLICATION_CREATED) .sequence(sequence) - .createdAt(LocalDateTime.now()) .build(); } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/entity/MockApplySequence.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/entity/MockApplySequence.java index c443134..ee5dd62 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/entity/MockApplySequence.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/entity/MockApplySequence.java @@ -1,5 +1,6 @@ package com.jobdri.jobdri_api.domain.mockapply.entity; +import com.jobdri.jobdri_api.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -19,7 +20,7 @@ columnNames = {"user_id", "job_posting_id"} ) ) -public class MockApplySequence { +public class MockApplySequence extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/payment/entity/CreditTransaction.java b/src/main/java/com/jobdri/jobdri_api/domain/payment/entity/CreditTransaction.java index 260fa26..6c8d647 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/payment/entity/CreditTransaction.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/payment/entity/CreditTransaction.java @@ -1,18 +1,17 @@ package com.jobdri.jobdri_api.domain.payment.entity; import com.jobdri.jobdri_api.domain.user.entity.User; +import com.jobdri.jobdri_api.global.entity.CreatedAtEntity; import jakarta.persistence.*; import lombok.*; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder(access = AccessLevel.PRIVATE) @Table(name = "credit_transactions") -public class CreditTransaction { +public class CreditTransaction extends CreatedAtEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -37,9 +36,6 @@ public class CreditTransaction { private String referenceId; - @Column(nullable = false) - private LocalDateTime createdAt; - public static CreditTransaction create( User user, CreditTransactionType type, @@ -55,7 +51,6 @@ public static CreditTransaction create( .balanceAfter(balanceAfter) .description(description) .referenceId(referenceId) - .createdAt(LocalDateTime.now()) .build(); } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/payment/entity/Payment.java b/src/main/java/com/jobdri/jobdri_api/domain/payment/entity/Payment.java index 8c339dc..dc31d8e 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/payment/entity/Payment.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/payment/entity/Payment.java @@ -1,6 +1,7 @@ package com.jobdri.jobdri_api.domain.payment.entity; import com.jobdri.jobdri_api.domain.user.entity.User; +import com.jobdri.jobdri_api.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -12,7 +13,7 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder(access = AccessLevel.PRIVATE) @Table(name = "payments") -public class Payment { +public class Payment extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -44,9 +45,6 @@ public class Payment { @Column(nullable = false) private PaymentStatus status; - @Column(nullable = false) - private LocalDateTime createdAt; - private LocalDateTime approvedAt; public static Payment createPending( @@ -65,7 +63,6 @@ public static Payment createPending( .creditAmount(creditAmount) .price(price) .status(PaymentStatus.PENDING) - .createdAt(LocalDateTime.now()) .build(); } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/user/entity/User.java b/src/main/java/com/jobdri/jobdri_api/domain/user/entity/User.java index 7c9babd..7e7b113 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/user/entity/User.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/user/entity/User.java @@ -4,6 +4,7 @@ import com.jobdri.jobdri_api.domain.jobposting.entity.JobPosting; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; import com.jobdri.jobdri_api.domain.payment.entity.Payment; +import com.jobdri.jobdri_api.global.entity.BaseEntity; import jakarta.persistence.*; import jakarta.validation.constraints.Email; import lombok.*; @@ -17,7 +18,7 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder(access = AccessLevel.PRIVATE) @Table(name = "users") -public class User { +public class User extends BaseEntity { @Id @GeneratedValue(strategy = jakarta.persistence.GenerationType.IDENTITY) diff --git a/src/main/java/com/jobdri/jobdri_api/global/config/JpaAuditingConfig.java b/src/main/java/com/jobdri/jobdri_api/global/config/JpaAuditingConfig.java new file mode 100644 index 0000000..1427ed9 --- /dev/null +++ b/src/main/java/com/jobdri/jobdri_api/global/config/JpaAuditingConfig.java @@ -0,0 +1,9 @@ +package com.jobdri.jobdri_api.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@Configuration +@EnableJpaAuditing +public class JpaAuditingConfig { +} diff --git a/src/main/java/com/jobdri/jobdri_api/global/entity/BaseEntity.java b/src/main/java/com/jobdri/jobdri_api/global/entity/BaseEntity.java new file mode 100644 index 0000000..221b01f --- /dev/null +++ b/src/main/java/com/jobdri/jobdri_api/global/entity/BaseEntity.java @@ -0,0 +1,25 @@ +package com.jobdri.jobdri_api.global.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseEntity { + + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/jobdri/jobdri_api/global/entity/CreatedAtEntity.java b/src/main/java/com/jobdri/jobdri_api/global/entity/CreatedAtEntity.java new file mode 100644 index 0000000..1be2658 --- /dev/null +++ b/src/main/java/com/jobdri/jobdri_api/global/entity/CreatedAtEntity.java @@ -0,0 +1,20 @@ +package com.jobdri.jobdri_api.global.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class CreatedAtEntity { + + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; +} From bde539b87533f582aaa8d124b29caea613616e93 Mon Sep 17 00:00:00 2001 From: shinae1023 Date: Tue, 16 Jun 2026 16:11:34 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[Fix]=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CorpusAdminController.java | 17 ++++++++++++----- .../corpus/service/CorpusImportService.java | 3 ++- .../jobdri_api/global/entity/BaseEntity.java | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/corpus/controller/CorpusAdminController.java b/src/main/java/com/jobdri/jobdri_api/domain/corpus/controller/CorpusAdminController.java index f735d11..1543ce4 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/corpus/controller/CorpusAdminController.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/corpus/controller/CorpusAdminController.java @@ -55,12 +55,19 @@ public ApiResponse importCorpus( @PostMapping(value = "/import/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ApiResponse importCorpusByUpload( @RequestParam("file") MultipartFile file - ) throws IOException { + ) { validateUploadFile(file); - return ApiResponse.onSuccess( - "업로드한 corpus 엑셀 적재에 성공했습니다.", - corpusImportService.importFromXlsx(file.getInputStream()) - ); + try { + return ApiResponse.onSuccess( + "업로드한 corpus 엑셀 적재에 성공했습니다.", + corpusImportService.importFromXlsx(file.getInputStream()) + ); + } catch (IOException e) { + throw new GeneralException( + GeneralErrorCode.INVALID_PARAMETER, + "파일 형식이 올바르지 않습니다." + ); + } } @Operation(summary = "corpus 임베딩 동기화", description = "유효한 corpus 데이터를 읽어 pgvector 테이블에 임베딩을 저장합니다.") diff --git a/src/main/java/com/jobdri/jobdri_api/domain/corpus/service/CorpusImportService.java b/src/main/java/com/jobdri/jobdri_api/domain/corpus/service/CorpusImportService.java index e842274..cf86b6c 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/corpus/service/CorpusImportService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/corpus/service/CorpusImportService.java @@ -49,7 +49,8 @@ public CorpusImportResult importFromXlsx(Path xlsxPath) throws IOException { } public CorpusImportResult importFromXlsx(InputStream inputStream) throws IOException { - try (Workbook workbook = new XSSFWorkbook(inputStream)) { + try (InputStream managedInputStream = inputStream; + Workbook workbook = new XSSFWorkbook(managedInputStream)) { return importWorkbook(workbook); } } diff --git a/src/main/java/com/jobdri/jobdri_api/global/entity/BaseEntity.java b/src/main/java/com/jobdri/jobdri_api/global/entity/BaseEntity.java index 221b01f..3ff08c5 100644 --- a/src/main/java/com/jobdri/jobdri_api/global/entity/BaseEntity.java +++ b/src/main/java/com/jobdri/jobdri_api/global/entity/BaseEntity.java @@ -20,6 +20,6 @@ public abstract class BaseEntity { private LocalDateTime createdAt; @LastModifiedDate - @Column(name = "updated_at", nullable = false) + @Column(name = "updated_at") private LocalDateTime updatedAt; }