From 64e210440de232bd98ad89c7f17373695b141018 Mon Sep 17 00:00:00 2001 From: wooh Date: Sat, 23 May 2026 05:43:22 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[Feat]=20=EC=A3=BC=EC=9A=94=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EC=9E=91=EC=97=85=20=EA=B0=90=EC=82=AC=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EA=B8=B0=EB=A1=9D=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AuditLog 엔티티와 Repository, Service 추가 - 요청 IP와 User-Agent 자동 수집 처리 - 공고 생성/수정, 모의 서류 지원 생성 감사로그 기록 - 문항 후보 추가, 문항 선택 저장, 답변 저장 감사로그 기록 - 자소서 분석 실행 결과 감사로그 기록 - 변경 전/후 값을 JSON 문자열로 저장하도록 구현 --- .../analysis/service/AnalysisService.java | 31 +++++- .../analysis/service/QuestionService.java | 56 +++++++++- .../domain/audit/entity/AuditLog.java | 102 ++++++++++++++++++ .../audit/repository/AuditLogRepository.java | 7 ++ .../domain/audit/service/AuditLogService.java | 95 ++++++++++++++++ .../jobposting/service/JobPostingService.java | 46 +++++++- .../mockapply/service/MockApplyService.java | 41 ++++++- 7 files changed, 370 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/jobdri/jobdri_api/domain/audit/entity/AuditLog.java create mode 100644 src/main/java/com/jobdri/jobdri_api/domain/audit/repository/AuditLogRepository.java create mode 100644 src/main/java/com/jobdri/jobdri_api/domain/audit/service/AuditLogService.java diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java index 85b2ace..d16f302 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java @@ -11,6 +11,7 @@ import com.jobdri.jobdri_api.domain.analysis.repository.AnalysisRepository; import com.jobdri.jobdri_api.domain.analysis.repository.QuestionAnalysisRepository; import com.jobdri.jobdri_api.domain.analysis.repository.QuestionRepository; +import com.jobdri.jobdri_api.domain.audit.service.AuditLogService; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApplyStatus; import com.jobdri.jobdri_api.domain.mockapply.repository.MockApplyRepository; @@ -42,6 +43,7 @@ public class AnalysisService { private final QuestionAnalysisRepository questionAnalysisRepository; private final AnalysisAiClient analysisAiClient; private final CreditService creditService; + private final AuditLogService auditLogService; @Transactional public AnalysisResponse analyze(User user, Long mockApplyId) { @@ -78,7 +80,24 @@ public AnalysisResponse analyze(User user, Long mockApplyId) { questionAnalysisRepository.saveAll(questionAnalyses); mockApply.updateStatus(MockApplyStatus.COMPLETED); - return getAnalysis(user, mockApplyId); + AnalysisResponse response = toResponse(mockApply, analysis, questions, questionAnalyses); + auditLogService.record( + user, + "ANALYSIS_RUN", + "MOCK_APPLY", + mockApply.getId(), + null, + new AnalysisAuditValue( + analysis.getId(), + analysis.getScore(), + analysis.getJobFit(), + analysis.getImpact(), + analysis.getCompleteness(), + questionAnalyses.size() + ) + ); + + return response; } catch (RuntimeException e) { creditService.refund(user, 1, "자소서 분석 실패 환불", referenceId); throw e; @@ -223,4 +242,14 @@ private QuestionAnalysisStatus normalizeStatus(String status) { return QuestionAnalysisStatus.MENTIONED; } } + + private record AnalysisAuditValue( + Long analysisId, + int score, + int jobFit, + int impact, + int completeness, + int highlightCount + ) { + } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java index 7ad7fb8..d8cb95a 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java @@ -11,6 +11,7 @@ import com.jobdri.jobdri_api.domain.analysis.entity.Question; import com.jobdri.jobdri_api.domain.analysis.repository.CustomQuestionCandidateRepository; import com.jobdri.jobdri_api.domain.analysis.repository.QuestionRepository; +import com.jobdri.jobdri_api.domain.audit.service.AuditLogService; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApplyStatus; import com.jobdri.jobdri_api.domain.mockapply.repository.MockApplyRepository; @@ -50,6 +51,7 @@ public class QuestionService { private final MockApplyRepository mockApplyRepository; private final QuestionRepository questionRepository; private final CustomQuestionCandidateRepository customQuestionCandidateRepository; + private final AuditLogService auditLogService; public List getQuestionCandidates(User user, Long mockApplyId) { MockApply mockApply = getOwnedMockApply(user, mockApplyId); @@ -94,13 +96,23 @@ public QuestionCandidateResponse addCustomQuestionCandidate( CustomQuestionCandidate candidate = findOrCreateCustomCandidate(mockApply, content, request.charLimit()); boolean selected = questionRepository.existsByMockApplyIdAndContent(mockApply.getId(), candidate.getContent()); - return new QuestionCandidateResponse( + QuestionCandidateResponse response = new QuestionCandidateResponse( candidate.getId(), candidate.getContent(), candidate.getLimit(), selected, true ); + auditLogService.record( + user, + "CUSTOM_QUESTION_CANDIDATE_ADD", + "MOCK_APPLY", + mockApply.getId(), + null, + response + ); + + return response; } private CustomQuestionCandidate findOrCreateCustomCandidate( @@ -150,6 +162,9 @@ public QuestionSelectionResponse saveSelectedQuestions( validateSelectionCount(request.questions().size()); List existingQuestions = questionRepository.findAllByMockApplyId(mockApply.getId()); + List beforeQuestions = existingQuestions.stream() + .map(QuestionAuditValue::from) + .toList(); questionRepository.deleteAll(existingQuestions); List questions = request.questions().stream() @@ -163,11 +178,21 @@ public QuestionSelectionResponse saveSelectedQuestions( List savedQuestions = questionRepository.saveAll(questions); mockApply.updateStatus(MockApplyStatus.ANSWER_WRITE); - return new QuestionSelectionResponse( + QuestionSelectionResponse response = new QuestionSelectionResponse( mockApply.getId(), mockApply.getStatus(), savedQuestions.stream().map(QuestionResponse::from).toList() ); + auditLogService.record( + user, + "QUESTION_SELECTION_SAVE", + "MOCK_APPLY", + mockApply.getId(), + beforeQuestions, + savedQuestions.stream().map(QuestionAuditValue::from).toList() + ); + + return response; } @Transactional @@ -180,6 +205,9 @@ public QuestionAnswerResponse saveAnswers( List questions = questionRepository.findAllByMockApplyIdOrderByIdAsc(mockApply.getId()); Map questionMap = questions.stream() .collect(Collectors.toMap(Question::getId, Function.identity())); + List beforeAnswers = questions.stream() + .map(QuestionAnswerAuditValue::from) + .toList(); for (QuestionAnswerSaveRequest.AnswerItem item : request.answers()) { Question question = questionMap.get(item.questionId()); @@ -192,11 +220,21 @@ public QuestionAnswerResponse saveAnswers( question.updateAnswer(normalizeAnswer(item.answer())); } - return new QuestionAnswerResponse( + QuestionAnswerResponse response = new QuestionAnswerResponse( mockApply.getId(), mockApply.getStatus(), questions.stream().map(QuestionResponse::from).toList() ); + auditLogService.record( + user, + "QUESTION_ANSWER_SAVE", + "MOCK_APPLY", + mockApply.getId(), + beforeAnswers, + questions.stream().map(QuestionAnswerAuditValue::from).toList() + ); + + return response; } private MockApply getOwnedMockApply(User user, Long mockApplyId) { @@ -246,4 +284,16 @@ private String normalizeAnswer(String answer) { private record QuestionCandidate(Long id, String content, int charLimit) { } + + private record QuestionAuditValue(Long questionId, String content, int charLimit) { + private static QuestionAuditValue from(Question question) { + return new QuestionAuditValue(question.getId(), question.getContent(), question.getLimit()); + } + } + + private record QuestionAnswerAuditValue(Long questionId, String answer) { + private static QuestionAnswerAuditValue from(Question question) { + return new QuestionAnswerAuditValue(question.getId(), question.getAnswer()); + } + } } 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 new file mode 100644 index 0000000..ca11e55 --- /dev/null +++ b/src/main/java/com/jobdri/jobdri_api/domain/audit/entity/AuditLog.java @@ -0,0 +1,102 @@ +package com.jobdri.jobdri_api.domain.audit.entity; + +import com.jobdri.jobdri_api.domain.user.entity.User; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "audit_logs") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AuditLog { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @Column(nullable = false, length = 80) + private String action; + + @Column(nullable = false, length = 80) + private String targetType; + + private Long targetId; + + @Column(columnDefinition = "TEXT") + private String beforeValue; + + @Column(columnDefinition = "TEXT") + private String afterValue; + + @Column(length = 100) + private String ipAddress; + + @Column(length = 500) + private String userAgent; + + @Column(nullable = false) + private LocalDateTime createdAt; + + @Builder(access = AccessLevel.PRIVATE) + private AuditLog( + User user, + String action, + String targetType, + Long targetId, + String beforeValue, + String afterValue, + String ipAddress, + String userAgent, + LocalDateTime createdAt + ) { + this.user = user; + this.action = action; + this.targetType = targetType; + this.targetId = targetId; + this.beforeValue = beforeValue; + this.afterValue = afterValue; + this.ipAddress = ipAddress; + this.userAgent = userAgent; + this.createdAt = createdAt; + } + + public static AuditLog create( + User user, + String action, + String targetType, + Long targetId, + String beforeValue, + String afterValue, + String ipAddress, + String userAgent + ) { + return AuditLog.builder() + .user(user) + .action(action) + .targetType(targetType) + .targetId(targetId) + .beforeValue(beforeValue) + .afterValue(afterValue) + .ipAddress(ipAddress) + .userAgent(userAgent) + .createdAt(LocalDateTime.now()) + .build(); + } +} diff --git a/src/main/java/com/jobdri/jobdri_api/domain/audit/repository/AuditLogRepository.java b/src/main/java/com/jobdri/jobdri_api/domain/audit/repository/AuditLogRepository.java new file mode 100644 index 0000000..47dd4ef --- /dev/null +++ b/src/main/java/com/jobdri/jobdri_api/domain/audit/repository/AuditLogRepository.java @@ -0,0 +1,7 @@ +package com.jobdri.jobdri_api.domain.audit.repository; + +import com.jobdri.jobdri_api.domain.audit.entity.AuditLog; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AuditLogRepository extends JpaRepository { +} diff --git a/src/main/java/com/jobdri/jobdri_api/domain/audit/service/AuditLogService.java b/src/main/java/com/jobdri/jobdri_api/domain/audit/service/AuditLogService.java new file mode 100644 index 0000000..ddc734e --- /dev/null +++ b/src/main/java/com/jobdri/jobdri_api/domain/audit/service/AuditLogService.java @@ -0,0 +1,95 @@ +package com.jobdri.jobdri_api.domain.audit.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jobdri.jobdri_api.domain.audit.entity.AuditLog; +import com.jobdri.jobdri_api.domain.audit.repository.AuditLogRepository; +import com.jobdri.jobdri_api.domain.user.entity.User; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +@Service +@RequiredArgsConstructor +public class AuditLogService { + + private static final int MAX_USER_AGENT_LENGTH = 500; + + private final AuditLogRepository auditLogRepository; + private final ObjectMapper objectMapper; + + @Transactional(propagation = Propagation.MANDATORY) + public void record( + User user, + String action, + String targetType, + Long targetId, + Object beforeValue, + Object afterValue + ) { + HttpServletRequest request = currentRequest(); + auditLogRepository.save(AuditLog.create( + user, + action, + targetType, + targetId, + toJson(beforeValue), + toJson(afterValue), + resolveIpAddress(request), + truncate(resolveUserAgent(request), MAX_USER_AGENT_LENGTH) + )); + } + + private HttpServletRequest currentRequest() { + if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attributes) { + return attributes.getRequest(); + } + return null; + } + + private String resolveIpAddress(HttpServletRequest request) { + if (request == null) { + return null; + } + + String forwardedFor = request.getHeader("X-Forwarded-For"); + if (forwardedFor != null && !forwardedFor.isBlank()) { + return forwardedFor.split(",")[0].trim(); + } + String realIp = request.getHeader("X-Real-IP"); + if (realIp != null && !realIp.isBlank()) { + return realIp.trim(); + } + return request.getRemoteAddr(); + } + + private String resolveUserAgent(HttpServletRequest request) { + if (request == null) { + return null; + } + return request.getHeader("User-Agent"); + } + + private String toJson(Object value) { + if (value == null) { + return null; + } + + try { + return objectMapper.writeValueAsString(value); + } catch (JsonProcessingException e) { + return String.valueOf(value); + } + } + + private String truncate(String value, int maxLength) { + if (value == null || value.length() <= maxLength) { + return value; + } + return value.substring(0, maxLength); + } +} diff --git a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingService.java b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingService.java index b04a9c4..9c9da75 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingService.java @@ -2,6 +2,7 @@ import com.jobdri.jobdri_api.domain.classification.entity.DetailClassification; import com.jobdri.jobdri_api.domain.classification.repository.DetailClassificationRepository; +import com.jobdri.jobdri_api.domain.audit.service.AuditLogService; import com.jobdri.jobdri_api.domain.company.entity.Company; import com.jobdri.jobdri_api.domain.company.repository.CompanyRepository; import com.jobdri.jobdri_api.domain.jobposting.dto.request.JobPostingCreateRequest; @@ -28,6 +29,7 @@ public class JobPostingService { private final CompanyRepository companyRepository; private final DetailClassificationRepository detailClassificationRepository; private final UserService userService; + private final AuditLogService auditLogService; @Transactional public JobPostingResponse createJobPosting(User user, JobPostingCreateRequest request) { @@ -44,13 +46,25 @@ public JobPostingResponse createJobPosting(User user, JobPostingCreateRequest re request.preferred() ); - return JobPostingResponse.from(jobPostingRepository.save(jobPosting)); + JobPosting savedJobPosting = jobPostingRepository.save(jobPosting); + JobPostingResponse response = JobPostingResponse.from(savedJobPosting); + auditLogService.record( + validatedUser, + "JOB_POSTING_CREATE", + "JOB_POSTING", + savedJobPosting.getId(), + null, + JobPostingAuditValue.from(savedJobPosting) + ); + + return response; } @Transactional public JobPostingResponse updateJobPosting(User user, Long jobPostingId, JobPostingUpdateRequest request) { User validatedUser = userService.validateUser(user); JobPosting jobPosting = getOwnedJobPosting(validatedUser, jobPostingId); + JobPostingAuditValue beforeValue = JobPostingAuditValue.from(jobPosting); Company company = findOrCreateCompany(request.companyName(), request.companySize()); DetailClassification detailClassification = findDetailClassification(request.detailClassificationId()); @@ -63,6 +77,14 @@ public JobPostingResponse updateJobPosting(User user, Long jobPostingId, JobPost request.requirement(), request.preferred() ); + auditLogService.record( + validatedUser, + "JOB_POSTING_UPDATE", + "JOB_POSTING", + jobPosting.getId(), + beforeValue, + JobPostingAuditValue.from(jobPosting) + ); return JobPostingResponse.from(jobPosting); } @@ -112,4 +134,26 @@ private DetailClassification findDetailClassification(Long detailClassificationI "해당 소분류를 찾을 수 없습니다. detailClassificationId=" + detailClassificationId )); } + + private record JobPostingAuditValue( + Long jobPostingId, + Long companyId, + String companyName, + Long detailClassificationId, + String task, + String requirement, + String preferred + ) { + private static JobPostingAuditValue from(JobPosting jobPosting) { + return new JobPostingAuditValue( + jobPosting.getId(), + jobPosting.getCompany().getId(), + jobPosting.getCompany().getName(), + jobPosting.getDetailClassification().getId(), + jobPosting.getTask(), + jobPosting.getRequirement(), + jobPosting.getPreferred() + ); + } + } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java index cd2b1b4..153eeb6 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java @@ -2,6 +2,7 @@ import com.jobdri.jobdri_api.domain.company.entity.Company; import com.jobdri.jobdri_api.domain.company.repository.CompanyRepository; +import com.jobdri.jobdri_api.domain.audit.service.AuditLogService; import com.jobdri.jobdri_api.domain.jobposting.dto.request.JobPostingCreateRequest; import com.jobdri.jobdri_api.domain.jobposting.dto.response.JobPostingResponse; import com.jobdri.jobdri_api.domain.jobposting.dto.response.JobPostingMockGenerateResponse; @@ -40,6 +41,7 @@ public class MockApplyService { private final MockJobPostingGenerationService mockJobPostingGenerationService; private final JobPostingService jobPostingService; private final UserService userService; + private final AuditLogService auditLogService; @Transactional public MockApplyCreateResponse createActualApply(User user, Long jobPostingId) { @@ -47,7 +49,10 @@ public MockApplyCreateResponse createActualApply(User user, Long jobPostingId) { JobPosting jobPosting = jobPostingService.getOwnedJobPosting(validatedUser, jobPostingId); MockApply mockApply = MockApply.create(validatedUser, jobPosting, ApplyType.ACTUAL); - return MockApplyCreateResponse.from(mockApplyRepository.save(mockApply)); + MockApply savedMockApply = mockApplyRepository.save(mockApply); + MockApplyCreateResponse response = MockApplyCreateResponse.from(savedMockApply); + recordMockApplyCreated(validatedUser, savedMockApply); + return response; } @Transactional @@ -56,7 +61,10 @@ public MockApplyCreateResponse createMockApplyFromJobPosting(User user, Long job JobPosting jobPosting = jobPostingService.getOwnedJobPosting(validatedUser, jobPostingId); MockApply mockApply = MockApply.create(validatedUser, jobPosting, ApplyType.MOCK); - return MockApplyCreateResponse.from(mockApplyRepository.save(mockApply)); + MockApply savedMockApply = mockApplyRepository.save(mockApply); + MockApplyCreateResponse response = MockApplyCreateResponse.from(savedMockApply); + recordMockApplyCreated(validatedUser, savedMockApply); + return response; } @Transactional @@ -87,7 +95,10 @@ public MockApplyCreateResponse createMockApply(User user, MockApplyCreateMockReq )); MockApply mockApply = MockApply.create(validatedUser, savedJobPosting, ApplyType.MOCK); - return MockApplyCreateResponse.from(mockApplyRepository.save(mockApply)); + MockApply savedMockApply = mockApplyRepository.save(mockApply); + MockApplyCreateResponse response = MockApplyCreateResponse.from(savedMockApply); + recordMockApplyCreated(validatedUser, savedMockApply); + return response; } public JobPostingResponse getMockApplyJobPosting(User user, Long mockApplyId) { @@ -162,4 +173,28 @@ private MockApply getOwnedMockApply(User user, Long mockApplyId) { return mockApply; } + + private void recordMockApplyCreated(User user, MockApply mockApply) { + auditLogService.record( + user, + "MOCK_APPLY_CREATE", + "MOCK_APPLY", + mockApply.getId(), + null, + new MockApplyAuditValue( + mockApply.getId(), + mockApply.getJobPosting().getId(), + mockApply.getApplyType().name(), + mockApply.getStatus().name() + ) + ); + } + + private record MockApplyAuditValue( + Long mockApplyId, + Long jobPostingId, + String applyType, + String status + ) { + } } From 2995178ff932dfd2fdd3f91afed3eea893bc9eb1 Mon Sep 17 00:00:00 2001 From: wooh Date: Sat, 23 May 2026 06:14:30 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[Fix]=20=ED=86=A0=EC=8A=A4=20=EA=B2=B0?= =?UTF-8?q?=EC=A0=9C=20=EC=8A=B9=EC=9D=B8=20=EC=9D=91=EB=8B=B5=20=ED=8C=8C?= =?UTF-8?q?=EC=8B=B1=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../analysis/service/AnalysisService.java | 32 +---- .../analysis/service/QuestionService.java | 60 ++------- .../audit/annotation/AuditLogEvent.java | 16 +++ .../domain/audit/aop/AuditLogAspect.java | 117 ++++++++++++++++++ .../domain/audit/service/AuditLogService.java | 3 +- .../jobposting/service/JobPostingService.java | 49 +------- .../mockapply/service/MockApplyService.java | 45 ++----- .../dto/toss/TossPaymentConfirmResponse.java | 3 + 8 files changed, 158 insertions(+), 167 deletions(-) create mode 100644 src/main/java/com/jobdri/jobdri_api/domain/audit/annotation/AuditLogEvent.java create mode 100644 src/main/java/com/jobdri/jobdri_api/domain/audit/aop/AuditLogAspect.java diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java index d16f302..4b91102 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java @@ -11,7 +11,7 @@ import com.jobdri.jobdri_api.domain.analysis.repository.AnalysisRepository; import com.jobdri.jobdri_api.domain.analysis.repository.QuestionAnalysisRepository; import com.jobdri.jobdri_api.domain.analysis.repository.QuestionRepository; -import com.jobdri.jobdri_api.domain.audit.service.AuditLogService; +import com.jobdri.jobdri_api.domain.audit.annotation.AuditLogEvent; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApplyStatus; import com.jobdri.jobdri_api.domain.mockapply.repository.MockApplyRepository; @@ -43,9 +43,9 @@ public class AnalysisService { private final QuestionAnalysisRepository questionAnalysisRepository; private final AnalysisAiClient analysisAiClient; private final CreditService creditService; - private final AuditLogService auditLogService; @Transactional + @AuditLogEvent(action = "ANALYSIS_RUN", targetType = "MOCK_APPLY", targetId = "#arg1") public AnalysisResponse analyze(User user, Long mockApplyId) { MockApply mockApply = getOwnedMockApply(user, mockApplyId); List questions = questionRepository.findAllByMockApplyIdOrderByIdAsc(mockApply.getId()); @@ -80,24 +80,7 @@ public AnalysisResponse analyze(User user, Long mockApplyId) { questionAnalysisRepository.saveAll(questionAnalyses); mockApply.updateStatus(MockApplyStatus.COMPLETED); - AnalysisResponse response = toResponse(mockApply, analysis, questions, questionAnalyses); - auditLogService.record( - user, - "ANALYSIS_RUN", - "MOCK_APPLY", - mockApply.getId(), - null, - new AnalysisAuditValue( - analysis.getId(), - analysis.getScore(), - analysis.getJobFit(), - analysis.getImpact(), - analysis.getCompleteness(), - questionAnalyses.size() - ) - ); - - return response; + return toResponse(mockApply, analysis, questions, questionAnalyses); } catch (RuntimeException e) { creditService.refund(user, 1, "자소서 분석 실패 환불", referenceId); throw e; @@ -243,13 +226,4 @@ private QuestionAnalysisStatus normalizeStatus(String status) { } } - private record AnalysisAuditValue( - Long analysisId, - int score, - int jobFit, - int impact, - int completeness, - int highlightCount - ) { - } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java index d8cb95a..1acaad1 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.java @@ -11,7 +11,7 @@ import com.jobdri.jobdri_api.domain.analysis.entity.Question; import com.jobdri.jobdri_api.domain.analysis.repository.CustomQuestionCandidateRepository; import com.jobdri.jobdri_api.domain.analysis.repository.QuestionRepository; -import com.jobdri.jobdri_api.domain.audit.service.AuditLogService; +import com.jobdri.jobdri_api.domain.audit.annotation.AuditLogEvent; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApplyStatus; import com.jobdri.jobdri_api.domain.mockapply.repository.MockApplyRepository; @@ -51,7 +51,6 @@ public class QuestionService { private final MockApplyRepository mockApplyRepository; private final QuestionRepository questionRepository; private final CustomQuestionCandidateRepository customQuestionCandidateRepository; - private final AuditLogService auditLogService; public List getQuestionCandidates(User user, Long mockApplyId) { MockApply mockApply = getOwnedMockApply(user, mockApplyId); @@ -84,6 +83,7 @@ public List getQuestionCandidates(User user, Long moc } @Transactional + @AuditLogEvent(action = "CUSTOM_QUESTION_CANDIDATE_ADD", targetType = "MOCK_APPLY", targetId = "#arg1") public QuestionCandidateResponse addCustomQuestionCandidate( User user, Long mockApplyId, @@ -96,23 +96,13 @@ public QuestionCandidateResponse addCustomQuestionCandidate( CustomQuestionCandidate candidate = findOrCreateCustomCandidate(mockApply, content, request.charLimit()); boolean selected = questionRepository.existsByMockApplyIdAndContent(mockApply.getId(), candidate.getContent()); - QuestionCandidateResponse response = new QuestionCandidateResponse( + return new QuestionCandidateResponse( candidate.getId(), candidate.getContent(), candidate.getLimit(), selected, true ); - auditLogService.record( - user, - "CUSTOM_QUESTION_CANDIDATE_ADD", - "MOCK_APPLY", - mockApply.getId(), - null, - response - ); - - return response; } private CustomQuestionCandidate findOrCreateCustomCandidate( @@ -153,6 +143,7 @@ public QuestionSelectionResponse getSelectedQuestions(User user, Long mockApplyI } @Transactional + @AuditLogEvent(action = "QUESTION_SELECTION_SAVE", targetType = "MOCK_APPLY", targetId = "#arg1") public QuestionSelectionResponse saveSelectedQuestions( User user, Long mockApplyId, @@ -162,9 +153,6 @@ public QuestionSelectionResponse saveSelectedQuestions( validateSelectionCount(request.questions().size()); List existingQuestions = questionRepository.findAllByMockApplyId(mockApply.getId()); - List beforeQuestions = existingQuestions.stream() - .map(QuestionAuditValue::from) - .toList(); questionRepository.deleteAll(existingQuestions); List questions = request.questions().stream() @@ -178,24 +166,15 @@ public QuestionSelectionResponse saveSelectedQuestions( List savedQuestions = questionRepository.saveAll(questions); mockApply.updateStatus(MockApplyStatus.ANSWER_WRITE); - QuestionSelectionResponse response = new QuestionSelectionResponse( + return new QuestionSelectionResponse( mockApply.getId(), mockApply.getStatus(), savedQuestions.stream().map(QuestionResponse::from).toList() ); - auditLogService.record( - user, - "QUESTION_SELECTION_SAVE", - "MOCK_APPLY", - mockApply.getId(), - beforeQuestions, - savedQuestions.stream().map(QuestionAuditValue::from).toList() - ); - - return response; } @Transactional + @AuditLogEvent(action = "QUESTION_ANSWER_SAVE", targetType = "MOCK_APPLY", targetId = "#arg1") public QuestionAnswerResponse saveAnswers( User user, Long mockApplyId, @@ -205,9 +184,6 @@ public QuestionAnswerResponse saveAnswers( List questions = questionRepository.findAllByMockApplyIdOrderByIdAsc(mockApply.getId()); Map questionMap = questions.stream() .collect(Collectors.toMap(Question::getId, Function.identity())); - List beforeAnswers = questions.stream() - .map(QuestionAnswerAuditValue::from) - .toList(); for (QuestionAnswerSaveRequest.AnswerItem item : request.answers()) { Question question = questionMap.get(item.questionId()); @@ -220,21 +196,11 @@ public QuestionAnswerResponse saveAnswers( question.updateAnswer(normalizeAnswer(item.answer())); } - QuestionAnswerResponse response = new QuestionAnswerResponse( + return new QuestionAnswerResponse( mockApply.getId(), mockApply.getStatus(), questions.stream().map(QuestionResponse::from).toList() ); - auditLogService.record( - user, - "QUESTION_ANSWER_SAVE", - "MOCK_APPLY", - mockApply.getId(), - beforeAnswers, - questions.stream().map(QuestionAnswerAuditValue::from).toList() - ); - - return response; } private MockApply getOwnedMockApply(User user, Long mockApplyId) { @@ -284,16 +250,4 @@ private String normalizeAnswer(String answer) { private record QuestionCandidate(Long id, String content, int charLimit) { } - - private record QuestionAuditValue(Long questionId, String content, int charLimit) { - private static QuestionAuditValue from(Question question) { - return new QuestionAuditValue(question.getId(), question.getContent(), question.getLimit()); - } - } - - private record QuestionAnswerAuditValue(Long questionId, String answer) { - private static QuestionAnswerAuditValue from(Question question) { - return new QuestionAnswerAuditValue(question.getId(), question.getAnswer()); - } - } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/audit/annotation/AuditLogEvent.java b/src/main/java/com/jobdri/jobdri_api/domain/audit/annotation/AuditLogEvent.java new file mode 100644 index 0000000..dfe211d --- /dev/null +++ b/src/main/java/com/jobdri/jobdri_api/domain/audit/annotation/AuditLogEvent.java @@ -0,0 +1,16 @@ +package com.jobdri.jobdri_api.domain.audit.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuditLogEvent { + String action(); + + String targetType(); + + String targetId() default ""; +} diff --git a/src/main/java/com/jobdri/jobdri_api/domain/audit/aop/AuditLogAspect.java b/src/main/java/com/jobdri/jobdri_api/domain/audit/aop/AuditLogAspect.java new file mode 100644 index 0000000..a3de64c --- /dev/null +++ b/src/main/java/com/jobdri/jobdri_api/domain/audit/aop/AuditLogAspect.java @@ -0,0 +1,117 @@ +package com.jobdri.jobdri_api.domain.audit.aop; + +import com.jobdri.jobdri_api.domain.audit.annotation.AuditLogEvent; +import com.jobdri.jobdri_api.domain.audit.service.AuditLogService; +import com.jobdri.jobdri_api.domain.user.entity.User; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.context.expression.MethodBasedEvaluationContext; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.Map; + +@Aspect +@Component +@Slf4j +@RequiredArgsConstructor +public class AuditLogAspect { + + private static final String RESULT_VARIABLE = "result"; + + private final AuditLogService auditLogService; + private final ExpressionParser expressionParser = new SpelExpressionParser(); + private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + + @Around("@annotation(auditLogEvent)") + public Object recordAuditLog(ProceedingJoinPoint joinPoint, AuditLogEvent auditLogEvent) throws Throwable { + Map beforeValue = extractParameters(joinPoint); + + Object result = joinPoint.proceed(); + + try { + auditLogService.record( + extractUser(joinPoint), + auditLogEvent.action(), + auditLogEvent.targetType(), + evaluateTargetId(joinPoint, auditLogEvent.targetId(), result), + beforeValue, + result + ); + } catch (RuntimeException e) { + log.warn("Audit log recording failed. action={}, method={}", + auditLogEvent.action(), + joinPoint.getSignature().toShortString(), + e + ); + throw e; + } + + return result; + } + + private User extractUser(ProceedingJoinPoint joinPoint) { + for (Object arg : joinPoint.getArgs()) { + if (arg instanceof User user) { + return user; + } + } + return null; + } + + private Long evaluateTargetId(ProceedingJoinPoint joinPoint, String targetIdExpression, Object result) { + if (targetIdExpression == null || targetIdExpression.isBlank()) { + return null; + } + + Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); + MethodBasedEvaluationContext context = new MethodBasedEvaluationContext( + null, + method, + joinPoint.getArgs(), + parameterNameDiscoverer + ); + Object[] args = joinPoint.getArgs(); + for (int i = 0; i < args.length; i++) { + context.setVariable("arg" + i, args[i]); + context.setVariable("p" + i, args[i]); + } + context.setVariable(RESULT_VARIABLE, result); + + Object value = expressionParser.parseExpression(targetIdExpression).getValue(context); + if (value instanceof Number number) { + return number.longValue(); + } + if (value instanceof String string && !string.isBlank()) { + return Long.parseLong(string); + } + return null; + } + + private Map extractParameters(ProceedingJoinPoint joinPoint) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + String[] parameterNames = signature.getParameterNames(); + Object[] args = joinPoint.getArgs(); + + Map parameters = new LinkedHashMap<>(); + for (int i = 0; i < args.length; i++) { + if (args[i] instanceof User) { + continue; + } + String parameterName = parameterNames != null && i < parameterNames.length + ? parameterNames[i] + : "arg" + i; + parameters.put(parameterName, args[i]); + } + return parameters; + } +} diff --git a/src/main/java/com/jobdri/jobdri_api/domain/audit/service/AuditLogService.java b/src/main/java/com/jobdri/jobdri_api/domain/audit/service/AuditLogService.java index ddc734e..e6913aa 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/audit/service/AuditLogService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/audit/service/AuditLogService.java @@ -8,7 +8,6 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -22,7 +21,7 @@ public class AuditLogService { private final AuditLogRepository auditLogRepository; private final ObjectMapper objectMapper; - @Transactional(propagation = Propagation.MANDATORY) + @Transactional public void record( User user, String action, diff --git a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingService.java b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingService.java index 9c9da75..3406f4b 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingService.java @@ -2,7 +2,7 @@ import com.jobdri.jobdri_api.domain.classification.entity.DetailClassification; import com.jobdri.jobdri_api.domain.classification.repository.DetailClassificationRepository; -import com.jobdri.jobdri_api.domain.audit.service.AuditLogService; +import com.jobdri.jobdri_api.domain.audit.annotation.AuditLogEvent; import com.jobdri.jobdri_api.domain.company.entity.Company; import com.jobdri.jobdri_api.domain.company.repository.CompanyRepository; import com.jobdri.jobdri_api.domain.jobposting.dto.request.JobPostingCreateRequest; @@ -29,9 +29,9 @@ public class JobPostingService { private final CompanyRepository companyRepository; private final DetailClassificationRepository detailClassificationRepository; private final UserService userService; - private final AuditLogService auditLogService; @Transactional + @AuditLogEvent(action = "JOB_POSTING_CREATE", targetType = "JOB_POSTING", targetId = "#result.jobPostingId()") public JobPostingResponse createJobPosting(User user, JobPostingCreateRequest request) { User validatedUser = userService.validateUser(user); Company company = findOrCreateCompany(request.companyName(), request.companySize()); @@ -46,25 +46,14 @@ public JobPostingResponse createJobPosting(User user, JobPostingCreateRequest re request.preferred() ); - JobPosting savedJobPosting = jobPostingRepository.save(jobPosting); - JobPostingResponse response = JobPostingResponse.from(savedJobPosting); - auditLogService.record( - validatedUser, - "JOB_POSTING_CREATE", - "JOB_POSTING", - savedJobPosting.getId(), - null, - JobPostingAuditValue.from(savedJobPosting) - ); - - return response; + return JobPostingResponse.from(jobPostingRepository.save(jobPosting)); } @Transactional + @AuditLogEvent(action = "JOB_POSTING_UPDATE", targetType = "JOB_POSTING", targetId = "#arg1") public JobPostingResponse updateJobPosting(User user, Long jobPostingId, JobPostingUpdateRequest request) { User validatedUser = userService.validateUser(user); JobPosting jobPosting = getOwnedJobPosting(validatedUser, jobPostingId); - JobPostingAuditValue beforeValue = JobPostingAuditValue.from(jobPosting); Company company = findOrCreateCompany(request.companyName(), request.companySize()); DetailClassification detailClassification = findDetailClassification(request.detailClassificationId()); @@ -77,14 +66,6 @@ public JobPostingResponse updateJobPosting(User user, Long jobPostingId, JobPost request.requirement(), request.preferred() ); - auditLogService.record( - validatedUser, - "JOB_POSTING_UPDATE", - "JOB_POSTING", - jobPosting.getId(), - beforeValue, - JobPostingAuditValue.from(jobPosting) - ); return JobPostingResponse.from(jobPosting); } @@ -134,26 +115,4 @@ private DetailClassification findDetailClassification(Long detailClassificationI "해당 소분류를 찾을 수 없습니다. detailClassificationId=" + detailClassificationId )); } - - private record JobPostingAuditValue( - Long jobPostingId, - Long companyId, - String companyName, - Long detailClassificationId, - String task, - String requirement, - String preferred - ) { - private static JobPostingAuditValue from(JobPosting jobPosting) { - return new JobPostingAuditValue( - jobPosting.getId(), - jobPosting.getCompany().getId(), - jobPosting.getCompany().getName(), - jobPosting.getDetailClassification().getId(), - jobPosting.getTask(), - jobPosting.getRequirement(), - jobPosting.getPreferred() - ); - } - } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java index 153eeb6..ef58858 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java @@ -2,7 +2,7 @@ import com.jobdri.jobdri_api.domain.company.entity.Company; import com.jobdri.jobdri_api.domain.company.repository.CompanyRepository; -import com.jobdri.jobdri_api.domain.audit.service.AuditLogService; +import com.jobdri.jobdri_api.domain.audit.annotation.AuditLogEvent; import com.jobdri.jobdri_api.domain.jobposting.dto.request.JobPostingCreateRequest; import com.jobdri.jobdri_api.domain.jobposting.dto.response.JobPostingResponse; import com.jobdri.jobdri_api.domain.jobposting.dto.response.JobPostingMockGenerateResponse; @@ -41,33 +41,29 @@ public class MockApplyService { private final MockJobPostingGenerationService mockJobPostingGenerationService; private final JobPostingService jobPostingService; private final UserService userService; - private final AuditLogService auditLogService; @Transactional + @AuditLogEvent(action = "MOCK_APPLY_CREATE", targetType = "MOCK_APPLY", targetId = "#result.mockApplyId()") public MockApplyCreateResponse createActualApply(User user, Long jobPostingId) { User validatedUser = userService.validateUser(user); JobPosting jobPosting = jobPostingService.getOwnedJobPosting(validatedUser, jobPostingId); MockApply mockApply = MockApply.create(validatedUser, jobPosting, ApplyType.ACTUAL); - MockApply savedMockApply = mockApplyRepository.save(mockApply); - MockApplyCreateResponse response = MockApplyCreateResponse.from(savedMockApply); - recordMockApplyCreated(validatedUser, savedMockApply); - return response; + return MockApplyCreateResponse.from(mockApplyRepository.save(mockApply)); } @Transactional + @AuditLogEvent(action = "MOCK_APPLY_CREATE", targetType = "MOCK_APPLY", targetId = "#result.mockApplyId()") public MockApplyCreateResponse createMockApplyFromJobPosting(User user, Long jobPostingId) { User validatedUser = userService.validateUser(user); JobPosting jobPosting = jobPostingService.getOwnedJobPosting(validatedUser, jobPostingId); MockApply mockApply = MockApply.create(validatedUser, jobPosting, ApplyType.MOCK); - MockApply savedMockApply = mockApplyRepository.save(mockApply); - MockApplyCreateResponse response = MockApplyCreateResponse.from(savedMockApply); - recordMockApplyCreated(validatedUser, savedMockApply); - return response; + return MockApplyCreateResponse.from(mockApplyRepository.save(mockApply)); } @Transactional + @AuditLogEvent(action = "MOCK_APPLY_CREATE", targetType = "MOCK_APPLY", targetId = "#result.mockApplyId()") public MockApplyCreateResponse createMockApply(User user, MockApplyCreateMockRequest request) { User validatedUser = userService.validateUser(user); Company company = companyRepository.findById(request.companyId()) @@ -95,10 +91,7 @@ public MockApplyCreateResponse createMockApply(User user, MockApplyCreateMockReq )); MockApply mockApply = MockApply.create(validatedUser, savedJobPosting, ApplyType.MOCK); - MockApply savedMockApply = mockApplyRepository.save(mockApply); - MockApplyCreateResponse response = MockApplyCreateResponse.from(savedMockApply); - recordMockApplyCreated(validatedUser, savedMockApply); - return response; + return MockApplyCreateResponse.from(mockApplyRepository.save(mockApply)); } public JobPostingResponse getMockApplyJobPosting(User user, Long mockApplyId) { @@ -173,28 +166,4 @@ private MockApply getOwnedMockApply(User user, Long mockApplyId) { return mockApply; } - - private void recordMockApplyCreated(User user, MockApply mockApply) { - auditLogService.record( - user, - "MOCK_APPLY_CREATE", - "MOCK_APPLY", - mockApply.getId(), - null, - new MockApplyAuditValue( - mockApply.getId(), - mockApply.getJobPosting().getId(), - mockApply.getApplyType().name(), - mockApply.getStatus().name() - ) - ); - } - - private record MockApplyAuditValue( - Long mockApplyId, - Long jobPostingId, - String applyType, - String status - ) { - } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/payment/dto/toss/TossPaymentConfirmResponse.java b/src/main/java/com/jobdri/jobdri_api/domain/payment/dto/toss/TossPaymentConfirmResponse.java index 66438e6..781d7ff 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/payment/dto/toss/TossPaymentConfirmResponse.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/payment/dto/toss/TossPaymentConfirmResponse.java @@ -1,5 +1,8 @@ package com.jobdri.jobdri_api.domain.payment.dto.toss; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) public record TossPaymentConfirmResponse( String paymentKey, String orderId, From c9d13f3de9b410f060ce6be3de2cb3d6e590c772 Mon Sep 17 00:00:00 2001 From: wooh Date: Sat, 23 May 2026 06:21:16 +0900 Subject: [PATCH 3/3] =?UTF-8?q?Fix:=20=EA=B3=B5=EA=B3=A0=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B0=90=EC=82=AC=20=EB=A1=9C=EA=B7=B8=20targetId?= =?UTF-8?q?=20=ED=91=9C=ED=98=84=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JobPostingResponse는 record가 아니라 Lombok getter 기반 DTO이므로 감사 로그 SpEL 표현식을 #result.jobPostingId()에서 #result.getJobPostingId()로 수정했습니다. 모의 서류 지원 생성 시 내부에서 공고 생성 로직이 실행되면서 감사 로그 targetId 평가가 실패하던 문제를 해결했습니다. 검증: ./gradlew test 통과 --- .../jobdri_api/domain/jobposting/service/JobPostingService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingService.java b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingService.java index 3406f4b..9305a3d 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingService.java @@ -31,7 +31,7 @@ public class JobPostingService { private final UserService userService; @Transactional - @AuditLogEvent(action = "JOB_POSTING_CREATE", targetType = "JOB_POSTING", targetId = "#result.jobPostingId()") + @AuditLogEvent(action = "JOB_POSTING_CREATE", targetType = "JOB_POSTING", targetId = "#result.getJobPostingId()") public JobPostingResponse createJobPosting(User user, JobPostingCreateRequest request) { User validatedUser = userService.validateUser(user); Company company = findOrCreateCompany(request.companyName(), request.companySize());