Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feat/#80 Debate 요약 및 저장 기능 구현 #111

Merged
merged 14 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backend/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'

testAnnotationProcessor "org.mapstruct:mapstruct-processor:1.5.5.Final"
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}

tasks.named('bootBuildImage') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.rollthedice.backend.domain.debate.dto.request.DebateRoomRequest;
import com.rollthedice.backend.domain.debate.dto.response.DebateMessageResponse;
import com.rollthedice.backend.domain.debate.dto.response.DebateRoomResponse;
import com.rollthedice.backend.domain.debate.dto.response.DebateSummaryResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
Expand Down Expand Up @@ -104,6 +105,23 @@ List<DebateMessageResponse> getDebateMessages(
Long roomId
);

@Operation(
summary = "토론 요약",
description = "토론방의 토론 메세지들을 요약합니다.",
security = {@SecurityRequirement(name = "access_token")},
tags = {"debate_message"}
)
@ApiResponse(
responseCode = "200",
description = "OK"
)
DebateSummaryResponse getSummarizedDebate(
@Parameter(in = ParameterIn.PATH, description = "토론방 ID", required = true)
Long roomId
);





}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.rollthedice.backend.domain.debate.dto.request.DebateRoomRequest;
import com.rollthedice.backend.domain.debate.dto.response.DebateMessageResponse;
import com.rollthedice.backend.domain.debate.dto.response.DebateRoomResponse;
import com.rollthedice.backend.domain.debate.dto.response.DebateSummaryResponse;
import com.rollthedice.backend.domain.debate.service.DebateMessageService;
import com.rollthedice.backend.domain.debate.service.DebateRoomService;
import jakarta.validation.Valid;
Expand Down Expand Up @@ -62,4 +63,11 @@ public void saveAIDebateMessage(@PathVariable final Long roomId, @RequestBody fi
public List<DebateMessageResponse> getDebateMessages(@PathVariable final Long roomId) {
return debateMessageService.getDebateMessages(roomId);
}

@ResponseStatus(HttpStatus.OK)
@GetMapping("/summary/{roomId}")
@Override
public DebateSummaryResponse getSummarizedDebate(@PathVariable final Long roomId) {
return debateRoomService.summaryDebate(roomId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.rollthedice.backend.domain.debate.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DebateSummaryResponse {
Long roomId;

@Schema(description = "4문장의 요약된 토론 내용")
String summary;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;

@Getter
@Entity
Expand All @@ -17,6 +18,10 @@ public class DebateRoom extends BaseTimeEntity {
private Long id;

private String topic;
private String summary;

@ColumnDefault("false")
private boolean isClosed;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
Expand All @@ -27,4 +32,12 @@ public DebateRoom(Member member, String topic) {
this.member = member;
this.topic = topic;
}

public void closeDebate() {
this.isClosed = true;
}

public void updateSummary(String summary) {
this.summary = summary;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.rollthedice.backend.domain.debate.service;

import com.rollthedice.backend.global.error.ErrorCode;
import com.rollthedice.backend.global.error.exception.ExternalApiException;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import org.apache.tomcat.util.json.JSONParser;
import org.apache.tomcat.util.json.ParseException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;

@Slf4j
@Service
public class ClovaSummary {
private static final String KOREAN = "ko";
private static final int POLITE_TONE = 2;
private static final int SUMMARY_COUNT = 4;

private static final String API_URL = "https://naveropenapi.apigw.ntruss.com/text-summary/v1/summarize";


@Value("${clova.secret-key}")
private String SECRET;

@Value("${clova.client-id}")
private String CLIENT_ID;

public String summaryDebate(String messages) {
log.info("요약할 메세지: {}" ,messages);
try {
URL url = new URL(API_URL);
HttpURLConnection connection = createRequestHeader(url);
createRequestBody(connection, messages);

log.info("정상1");
StringBuilder response = getResponse(connection);
log.info("정상2");
return parseResponse(response);
} catch (Exception e) {
e.printStackTrace();
throw new ExternalApiException(ErrorCode.CLOVA_API_ERROR);
}
}

private HttpURLConnection createRequestHeader(URL url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json;");
connection.setRequestProperty("X-NCP-APIGW-API-KEY-ID", CLIENT_ID);
connection.setRequestProperty("X-NCP-APIGW-API-KEY", SECRET);
return connection;
}

private void createRequestBody(HttpURLConnection connection, String content) throws IOException {
JSONObject document = new JSONObject();
document.put("content", content);

JSONObject option = new JSONObject();
option.put("language", KOREAN);
option.put("tone", POLITE_TONE);
option.put("summaryCount", SUMMARY_COUNT);

JSONObject requestObject = new JSONObject();
requestObject.put("document", document);
requestObject.put("option", option);

connection.connect();
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.write(requestObject.toString().getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}


private StringBuilder getResponse(HttpURLConnection connection) throws IOException {
BufferedReader reader = checkResponse(connection);
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
return response;
}

private BufferedReader checkResponse(HttpURLConnection connection) throws IOException {
int responseCode = connection.getResponseCode();

return getResponseResult(connection, responseCode);
}

private BufferedReader getResponseResult(HttpURLConnection connection, int responseCode) throws IOException {
if (HttpStatusCode.valueOf(responseCode).is2xxSuccessful()) {
return new BufferedReader(new InputStreamReader(connection.getInputStream()));
}
log.error("Clova Api error response code: {}", responseCode);
return new BufferedReader(new InputStreamReader(connection.getErrorStream()));
}


private String parseResponse(StringBuilder response) throws ParseException {
JSONParser parser = new JSONParser(response.toString());
LinkedHashMap<String, String> hashMap = (LinkedHashMap<String, String>) parser.parse();
JSONObject parsed = new JSONObject(hashMap);
return parsed.get("summary").toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@

import com.rollthedice.backend.domain.debate.dto.request.DebateMessageRequest;
import com.rollthedice.backend.domain.debate.dto.response.DebateMessageResponse;
import com.rollthedice.backend.domain.debate.dto.response.DebateSummaryResponse;
import com.rollthedice.backend.domain.debate.entity.DebateRoom;
import com.rollthedice.backend.domain.debate.exception.DebateRoomNotFoundException;
import com.rollthedice.backend.domain.debate.mapper.DebateMessageMapper;
import com.rollthedice.backend.domain.debate.repository.DebateRoomRepository;
import com.rollthedice.backend.domain.news.repository.DebateMessageRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@RequiredArgsConstructor
@Service
public class DebateMessageService {
Expand Down Expand Up @@ -44,4 +47,11 @@ public List<DebateMessageResponse> getDebateMessages(Long roomId) {
.stream().map(debateMessageMapper::toResponse)
.collect(Collectors.toList());
}

public StringBuilder getAllMessages(Long roomId) {
StringBuilder sb = new StringBuilder();
getDebateMessages(roomId)
.forEach(message -> sb.append(message.getMessage()));
return sb;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.rollthedice.backend.domain.debate.dto.request.DebateRoomRequest;
import com.rollthedice.backend.domain.debate.dto.response.DebateRoomResponse;
import com.rollthedice.backend.domain.debate.dto.response.DebateSummaryResponse;
import com.rollthedice.backend.domain.debate.exception.DebateRoomNotFoundException;
import com.rollthedice.backend.domain.debate.mapper.DebateRoomMapper;
import com.rollthedice.backend.domain.debate.repository.DebateRoomRepository;
import com.rollthedice.backend.domain.member.entity.Member;
Expand All @@ -21,6 +23,7 @@ public class DebateRoomService {
private final DebateRoomMapper debateRoomMapper;
private final DebateRoomRepository debateRoomRepository;
private final DebateMessageService debateMessageService;
private final ClovaSummary clovaSummary;


@Transactional
Expand All @@ -42,4 +45,18 @@ public void deleteDebateRoom(Long roomId) {
debateMessageService.deleteAllDebateMessages(roomId);
debateRoomRepository.deleteById(roomId);
}

@Transactional
public DebateSummaryResponse summaryDebate(final Long roomId) {
StringBuilder sb = debateMessageService.getAllMessages(roomId);
String summary = clovaSummary.summaryDebate(sb.toString());
debateRoomRepository.findById(roomId).orElseThrow(DebateRoomNotFoundException::new)
.updateSummary(summary);

return DebateSummaryResponse.builder()
.roomId(roomId)
.summary(summary)
.build();
}

}
Loading