Skip to content

Commit 8d7329c

Browse files
authored
Style/code review (#152)
* style: 코드 리뷰 ui 추가 * refactor: Thread.sleep 제거 * refactor: ai 프롬프트, 검증 로직 변경 * refactor: 컨슈머 로직 수정
1 parent ab438c8 commit 8d7329c

File tree

5 files changed

+107
-27
lines changed

5 files changed

+107
-27
lines changed

src/main/java/org/ezcode/codetest/infrastructure/event/config/RedisStreamConfig.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package org.ezcode.codetest.infrastructure.event.config;
22

3+
import java.net.InetAddress;
4+
import java.net.UnknownHostException;
35
import java.time.Duration;
46
import java.util.Map;
7+
import java.util.UUID;
58
import java.util.concurrent.Executor;
69

710
import org.ezcode.codetest.infrastructure.event.listener.RedisJudgeQueueConsumer;
@@ -50,7 +53,7 @@ public void initConsumerGroup() {
5053
connection.xGroupDelConsumer(
5154
"judge-queue".getBytes(),
5255
"judge-group",
53-
"consumer-1"
56+
getConsumerName().replace("consumer-", "")
5457
);
5558
return null;
5659
});
@@ -90,12 +93,21 @@ public StreamMessageListenerContainer<String, MapRecord<String, String, String>>
9093
StreamMessageListenerContainer.create(factory, options);
9194

9295
container.receive(
93-
Consumer.from("judge-group", "consumer-1"),
96+
Consumer.from("judge-group", getConsumerName()),
9497
StreamOffset.create("judge-queue", ReadOffset.lastConsumed()),
9598
consumer
9699
);
97100

98101
container.start();
99102
return container;
100103
}
104+
105+
private String getConsumerName() {
106+
try {
107+
return "consumer-" + InetAddress.getLocalHost().getHostName();
108+
} catch (UnknownHostException e) {
109+
log.warn("호스트명 확인 실패, UUID 사용: {}", e.getMessage());
110+
return "consumer-" + UUID.randomUUID().toString().substring(0, 8);
111+
}
112+
}
101113
}

src/main/java/org/ezcode/codetest/infrastructure/event/listener/RedisJudgeQueueConsumer.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ public void onMessage(MapRecord<String, String, String> message) {
3636

3737
try {
3838
log.info("[컨슈머 수신] {}", msg.sessionKey());
39-
Thread.sleep(6000);
4039
submissionService.processSubmissionAsync(msg);
4140

4241
log.info("[컨슈머 ACK] messageId={}", message.getId());

src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIMessageBuilder.java

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
@Component
1010
class OpenAIMessageBuilder {
1111

12-
private static final String MODEL_NAME = "o4-mini";
12+
private static final String MODEL_NAME = "gpt-4o";
1313

1414
private static final String PREFIX = """
1515
당신은 코딩 테스트 사이트의 코드 리뷰어입니다.
1616
아래 **정확히** 이 형식을 지켜 응답하세요:
17-
'시간 복잡도:', '코드 총평:' 같은 제목은 **제목**과 같이 볼드체로 변환합니다.
17+
'코드 총평' 같은 제목은 **제목** 과 같이 볼드체로 변환합니다.
18+
**[중요] 오답일 경우 시간 복잡도 항목은 절대 금지입니다.**
19+
만약 형식을 지켜 응답하지 않으면 시스템은 응답을 폐기합니다.
1820
""".stripIndent();
1921

2022
private static final String SUFFIX = """
@@ -63,30 +65,38 @@ private String buildSystemPrompt(boolean isCorrect) {
6365
String body;
6466
if (isCorrect) {
6567
body = """
66-
- 시간 복잡도: Big-O 표기법으로만 답하세요. **단, N과 M을 같다고 가정하고 n으로 표기하세요.**
68+
**시간 복잡도**: Big-O 표기법으로만 답하세요.
69+
**단, N과 M을 같다고 가정하고 n으로 표기하세요.**
6770
코드에 포함된 중첩 루프(depth)에 따라 O(N^k) 형태로 정확히 표기해주세요.
6871
**for 루프뿐만 아니라 while 루프도 모두 중첩(depth)에 포함**하여, 코드에 실제로 있는 루프 개수만큼 exponent를 세십시오.
6972
예) for-for-for ⇒ O(n³), for-for-while ⇒ O(n³), for-for-for-for-while ⇒ O(n⁵)
7073
\n
71-
- 코드 총평:
72-
각 문장은 한 탭(\t) 들여쓰기 + '- '로 시작.
73-
문장 끝에만 마침표를 붙이세요.
74+
**코드 총평**:
75+
각 문장은 '- '로 시작.
76+
문장 끝에만 마침표를 붙이고 줄바꿈 하세요.
7477
\n
75-
- 조금 더 개선할 수 있는 방안:
76-
각 문장은 한 탭(\t) 들여쓰기 + '- '로 시작.
77-
문장 끝에만 마침표를 붙이세요.
78+
**조금 더 개선할 수 있는 방안**:
79+
각 문장은 '- '로 시작.
80+
문장 끝에만 마침표를 붙이고 줄바꿈 하세요.
7881
""".stripIndent();
7982
} else {
8083
body = """
81-
- 코드 총평:
82-
각 문장은 한 탭(\t) 들여쓰기 + '- '로 시작.
83-
문장 끝에만 마침표를 붙이세요.
84+
**코드 총평**:
85+
각 문장은 '- '로 시작.
86+
문장 끝에만 마침표를 붙이고 줄바꿈 하세요.
8487
\n
85-
- 공부하면 좋은 키워드:
88+
**공부하면 좋은 키워드**:
8689
1. 첫 번째 키워드
8790
2. 두 번째 키워드
8891
3. 세 번째 키워드
8992
… 필요한 만큼 번호를 늘려주세요.
93+
\n
94+
**조금 더 개선할 수 있는 방안**:
95+
각 문장은 '- '로 시작.
96+
문장 끝에만 마침표를 붙이고 줄바꿈 하세요.
97+
98+
절대 시간 복잡도라는 단어도 쓰지 마세요.
99+
Big-O 표기법도 절대 포함하지 마세요.
90100
""".stripIndent();
91101
}
92102

src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIResponseValidator.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,20 @@ protected boolean isValidFormat(String content, boolean isCorrect) {
88
if (content == null)
99
return false;
1010

11-
if (isCorrect) {
12-
return content.contains("시간 복잡도:") &&
13-
content.contains("코드 총평:") &&
14-
content.contains("조금 더 개선할 수 있는 방안:");
11+
if (!isCorrect) {
12+
13+
boolean containsRequired = content.contains("**코드 총평**:") &&
14+
content.contains("**공부하면 좋은 키워드**:")&&
15+
content.contains("**조금 더 개선할 수 있는 방안**:");
16+
17+
boolean containsForbidden = content.contains("**시간 복잡도**:") ||
18+
content.toLowerCase().contains("**시간복잡도:**");
19+
20+
return containsRequired && !containsForbidden;
1521
}
1622

17-
return content.contains("코드 총평:") &&
18-
content.contains("공부하면 좋은 키워드:");
23+
return content.contains("**시간 복잡도**:") &&
24+
content.contains("**코드 총평**:") &&
25+
content.contains("**조금 더 개선할 수 있는 방안**:");
1926
}
2027
}

src/main/resources/templates/test-submit.html

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,24 @@
121121
background-color: #00b472;
122122
}
123123

124+
button:disabled {
125+
background-color: #555;
126+
cursor: not-allowed;
127+
}
128+
129+
.review-box {
130+
margin-top: 16px;
131+
background-color: #2a2d3d;
132+
padding: 20px;
133+
border-radius: 10px;
134+
color: #f0f2f5;
135+
}
136+
137+
.review-box strong,
138+
.review-box b {
139+
color: #00d084;
140+
}
141+
124142
.result-box {
125143
background-color: #1e212f;
126144
padding: 16px;
@@ -288,13 +306,15 @@ <h2>코드 제출</h2>
288306
<div id="editor" class="cm-theme" style="height: 300px;"></div>
289307
</div>
290308
<button onclick="submitCode()">코드 제출 (WebSocket)</button>
309+
<button id="reviewBtn" onclick="requestReview()" disabled>리뷰 요청 (AI)</button>
291310
</div>
292311
<div class="section">
293312
<h3>채점 결과</h3>
294313
<div class="git-sidebar">
295314
<div class="git-status-dot" id="gitDot"></div>
296315
</div>
297316
<div id="judgeResult" class="result-box">여기에 테스트 결과가 표시됩니다</div>
317+
<div id="reviewBox" class="review-box" style="display: none"></div>
298318
</div>
299319
</div>
300320
</div>
@@ -305,7 +325,11 @@ <h3>채점 결과</h3>
305325
<script defer>
306326
let stompClient;
307327
let connected = false;
328+
let finalIsCorrect = null;
329+
308330
const gitDot = document.getElementById('gitDot');
331+
const reviewBtn = document.getElementById('reviewBtn');
332+
const reviewBox = document.getElementById('reviewBox');
309333
const probTitleEl = document.getElementById('prob-title');
310334
const probDescEl = document.getElementById('prob-desc');
311335
const probTimeEl = document.getElementById('prob-time');
@@ -354,6 +378,9 @@ <h3>채점 결과</h3>
354378
const payload = {languageId, sourceCode};
355379
const judgeEl = document.getElementById('judgeResult');
356380
judgeEl.innerHTML = '';
381+
reviewBox.style.display = 'none';
382+
reviewBtn.disabled = true;
383+
finalIsCorrect = null;
357384

358385
try {
359386
const res = await fetch(`/api/problems/${pid}/submit-ws`, {
@@ -487,14 +514,39 @@ <h3>채점 결과</h3>
487514
sum.className = 'final-summary';
488515
sum.innerHTML = `<strong>최종:</strong> ${res.passedCount}/${res.totalCount} 통과 — ${res.message}`;
489516
document.getElementById('judgeResult').appendChild(sum);
517+
finalIsCorrect = res.passedCount === res.totalCount;
518+
reviewBtn.disabled = false;
490519
}
491520

492-
fetch('/html/header.html')
493-
.then(res => res.text())
494-
.then(data => {
495-
document.getElementById('header-placeholder').innerHTML = data;
496-
});
521+
async function requestReview() {
522+
if (!window.problemId || finalIsCorrect === null) return;
523+
const languageId = +document.getElementById('language').value;
524+
const sourceCode = window.getEditorCode();
525+
try {
526+
const res = await fetch(`/api/problems/${window.problemId}/review`, {
527+
method: 'POST',
528+
headers: {
529+
'Content-Type': 'application/json',
530+
...tokenHeader()
531+
},
532+
body: JSON.stringify({ languageId, sourceCode, isCorrect: finalIsCorrect })
533+
});
534+
const json = await res.json();
535+
const content = json.result?.reviewContent || json.reviewContent;
536+
reviewBox.style.display = 'block';
537+
const markdownSafe = content
538+
.replace(/\n[\t ]*-/g, '\n-') // 리스트용 하이픈 앞 공백 제거
539+
.replace(/\n/g, ' \n'); // 줄바꿈을 마크다운 줄바꿈으로 변환
540+
reviewBox.innerHTML = marked.parse(markdownSafe || '리뷰 결과가 없습니다.');
541+
console.log(JSON.stringify(content));
542+
} catch (e) {
543+
console.error('리뷰 요청 실패', e);
544+
reviewBox.style.display = 'block';
545+
reviewBox.textContent = '리뷰 요청 중 오류가 발생했습니다';
546+
}
547+
}
497548
</script>
549+
498550
<script src="/js/headerUtils.js"></script>
499551
<script src="/js/footerUtils.js"></script>
500552
<script>

0 commit comments

Comments
 (0)