[Feat] 주요 변경 작업 감사로그 기록 기능 추가#74
Conversation
- AuditLog 엔티티와 Repository, Service 추가 - 요청 IP와 User-Agent 자동 수집 처리 - 공고 생성/수정, 모의 서류 지원 생성 감사로그 기록 - 문항 후보 추가, 문항 선택 저장, 답변 저장 감사로그 기록 - 자소서 분석 실행 결과 감사로그 기록 - 변경 전/후 값을 JSON 문자열로 저장하도록 구현
📝 WalkthroughWalkthroughThe PR introduces a complete audit logging infrastructure and integrates it across multiple services. It adds a persistent ChangesAudit Logging Infrastructure and Integration
Sequence DiagramsequenceDiagram
participant Service
participant AuditLogService
participant RequestContext
participant ObjectMapper
participant AuditLogRepository
Service->>AuditLogService: record(user, action, targetType, targetId, beforeValue, afterValue)
AuditLogService->>RequestContext: currentRequest()
RequestContext-->>AuditLogService: HttpServletRequest
AuditLogService->>ObjectMapper: toJson(beforeValue)
ObjectMapper-->>AuditLogService: serialized beforeValue
AuditLogService->>ObjectMapper: toJson(afterValue)
ObjectMapper-->>AuditLogService: serialized afterValue
AuditLogService->>AuditLogService: resolveIpAddress(request)
AuditLogService->>AuditLogService: resolveUserAgent(request)
AuditLogService->>AuditLogRepository: save(AuditLog)
AuditLogRepository-->>AuditLogService: persisted AuditLog
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java`:
- Around line 84-103: The auditLogService.record(...) call is inside the same
try that catches RuntimeException and triggers creditService.refund(...),
causing successful analyses to be refunded if the audit log fails; update
AnalysisService so audit logging failures do not trigger refunds by moving the
auditLogService.record(...) outside the main try/catch that wraps analysis
execution or by wrapping only the audit call in its own try/catch that logs
audit errors (but does not call creditService.refund) — keep
creditService.refund(...) only in the branch that genuinely represents analysis
execution failure.
In
`@src/main/java/com/jobdri/jobdri_api/domain/audit/service/AuditLogService.java`:
- Around line 82-86: The current catch in AuditLogService that returns
String.valueOf(value) can produce non-JSON output; change the fallback to
produce a valid JSON string instead — call
objectMapper.writeValueAsString(String.valueOf(value)) in the catch so
beforeValue/afterValue remain JSON, and if that secondary call can fail, fall
back to returning a safely quoted/escaped JSON string (i.e., ensure the final
return is a JSON string value). Update the try/catch around
objectMapper.writeValueAsString(value) accordingly so the fallback preserves
JSON format.
- Around line 59-67: The current IP capture in AuditLogService reads
client-supplied headers ("X-Forwarded-For", "X-Real-IP") which can be spoofed;
change the implementation of the IP extraction (the method that calls
request.getHeader(...) in AuditLogService) to stop trusting those headers and
always use the proxy-resolved address returned by request.getRemoteAddr();
remove or ignore usage of forwardedFor.split(...) and realIp.trim() in this
method so audit entries rely solely on request.getRemoteAddr() at this layer.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ecb9db7a-481b-4266-b983-7bb91e795615
📒 Files selected for processing (7)
src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.javasrc/main/java/com/jobdri/jobdri_api/domain/analysis/service/QuestionService.javasrc/main/java/com/jobdri/jobdri_api/domain/audit/entity/AuditLog.javasrc/main/java/com/jobdri/jobdri_api/domain/audit/repository/AuditLogRepository.javasrc/main/java/com/jobdri/jobdri_api/domain/audit/service/AuditLogService.javasrc/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingService.javasrc/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java
| 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; |
There was a problem hiding this comment.
Audit failure currently triggers an incorrect credit refund path.
At Line 84, auditLogService.record(...) is inside the same try as the analysis execution, and Line 101 catches all RuntimeExceptions. If audit logging fails after analysis is already saved, Line 102 refunds credit even though the analysis actually succeeded.
Suggested fix
- 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;
+ AnalysisResponse response = toResponse(mockApply, analysis, questions, questionAnalyses);
+ try {
+ auditLogService.record(
+ user,
+ "ANALYSIS_RUN",
+ "MOCK_APPLY",
+ mockApply.getId(),
+ null,
+ new AnalysisAuditValue(
+ analysis.getId(),
+ analysis.getScore(),
+ analysis.getJobFit(),
+ analysis.getImpact(),
+ analysis.getCompleteness(),
+ questionAnalyses.size()
+ )
+ );
+ } catch (RuntimeException ignored) {
+ // 감사로그 실패가 분석 성공/크레딧 정산을 깨지 않도록 분리
+ }
+ return response;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java`
around lines 84 - 103, The auditLogService.record(...) call is inside the same
try that catches RuntimeException and triggers creditService.refund(...),
causing successful analyses to be refunded if the audit log fails; update
AnalysisService so audit logging failures do not trigger refunds by moving the
auditLogService.record(...) outside the main try/catch that wraps analysis
execution or by wrapping only the audit call in its own try/catch that logs
audit errors (but does not call creditService.refund) — keep
creditService.refund(...) only in the branch that genuinely represents analysis
execution failure.
| 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(); |
There was a problem hiding this comment.
Harden IP capture against header spoofing.
Line 59–66 trust client-supplied forwarding headers directly, so audit IP can be forged. For audit integrity, only use trusted proxy-resolved remote address at this layer.
Suggested fix
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();
}📝 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.
| 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(); | |
| return request.getRemoteAddr(); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/jobdri/jobdri_api/domain/audit/service/AuditLogService.java`
around lines 59 - 67, The current IP capture in AuditLogService reads
client-supplied headers ("X-Forwarded-For", "X-Real-IP") which can be spoofed;
change the implementation of the IP extraction (the method that calls
request.getHeader(...) in AuditLogService) to stop trusting those headers and
always use the proxy-resolved address returned by request.getRemoteAddr();
remove or ignore usage of forwardedFor.split(...) and realIp.trim() in this
method so audit entries rely solely on request.getRemoteAddr() at this layer.
| try { | ||
| return objectMapper.writeValueAsString(value); | ||
| } catch (JsonProcessingException e) { | ||
| return String.valueOf(value); | ||
| } |
There was a problem hiding this comment.
Keep beforeValue/afterValue strictly JSON on serialization fallback.
Line 85 currently returns raw String.valueOf(value), which can store non-JSON text and break downstream parsing expectations.
Suggested fix
try {
return objectMapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
- return String.valueOf(value);
+ try {
+ return objectMapper.writeValueAsString(String.valueOf(value));
+ } catch (JsonProcessingException ignored) {
+ return "\"<unserializable>\"";
+ }
}
}📝 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.
| try { | |
| return objectMapper.writeValueAsString(value); | |
| } catch (JsonProcessingException e) { | |
| return String.valueOf(value); | |
| } | |
| try { | |
| return objectMapper.writeValueAsString(value); | |
| } catch (JsonProcessingException e) { | |
| try { | |
| return objectMapper.writeValueAsString(String.valueOf(value)); | |
| } catch (JsonProcessingException ignored) { | |
| return "\"<unserializable>\""; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/jobdri/jobdri_api/domain/audit/service/AuditLogService.java`
around lines 82 - 86, The current catch in AuditLogService that returns
String.valueOf(value) can produce non-JSON output; change the fallback to
produce a valid JSON string instead — call
objectMapper.writeValueAsString(String.valueOf(value)) in the catch so
beforeValue/afterValue remain JSON, and if that secondary call can fail, fall
back to returning a safely quoted/escaped JSON string (i.e., ensure the final
return is a JSON string value). Update the try/catch around
objectMapper.writeValueAsString(value) accordingly so the fallback preserves
JSON format.
✨ 어떤 이유로 PR를 하셨나요?
📋 세부 내용 - 왜 해당 PR이 필요한지 작업 내용을 자세하게 설명해주세요
📸 작업 화면 스크린샷
🚨 관련 이슈 번호 [#73]
Summary by CodeRabbit