Skip to content

Commit dba1fb1

Browse files
authored
Merge pull request #323 from KW-ClassLog/Feat/#322/convert-lecture-to-pdf
✨ Feat/#322 강의자료 pptx pdf로 변환, 녹음본 강의 제목으로 저장
2 parents f0c9f2e + dc5d458 commit dba1fb1

File tree

6 files changed

+172
-25
lines changed

6 files changed

+172
-25
lines changed

backend/Dockerfile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
FROM openjdk:17-jdk
1+
FROM eclipse-temurin:17-jdk
2+
3+
RUN apt-get update \
4+
&& apt-get install -y --no-install-recommends libreoffice \
5+
&& apt-get clean \
6+
&& rm -rf /var/lib/apt/lists/*
27

38
COPY build/libs/backend-0.0.1-SNAPSHOT.jar /app.jar
49

backend/src/main/java/org/example/backend/domain/lecture/entity/Lecture.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ public class Lecture extends BaseEntity {
4040
@Column(name = "audio_url")
4141
private String audioUrl;
4242

43+
@Column(name = "is_lecture_start")
4344
private Boolean isLectureStart;
4445

46+
@Column(name = "save_audio")
4547
private Boolean saveAudio;
4648

4749
@Column(name = "lecture_date", nullable = false)

backend/src/main/java/org/example/backend/domain/lecture/service/LectureServiceImpl.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,25 +152,28 @@ public void deleteLecture(UUID lectureId) {
152152

153153
//녹음본 저장
154154
public LectureRecordingResponseDTO uploadLectureRecording(UUID lectureId, MultipartFile file) {
155+
155156
Lecture lecture = lectureRepository.findById(lectureId)
156157
.orElseThrow(() -> new LectureException(LectureErrorCode.LECTURE_NOT_FOUND));
157158

158-
String key = "recordings/" + lectureId +"/" + UUID.randomUUID() + "/" + file.getOriginalFilename();
159+
String safeLectureName = lecture.getLectureName().replaceAll("\\s+", "_");
160+
String uploadFileName = safeLectureName + ".mp3";
161+
162+
String key = "recordings/" + lectureId + "/" + UUID.randomUUID() + "/" + uploadFileName;
159163

160164
try {
161-
s3Service.uploadFile(file, key); // private 업로드
165+
s3Service.uploadFile(file, key);
162166
} catch (IOException e) {
163167
throw new S3Exception(S3ErrorCode.UPLOAD_FAIL);
164168
}
165169

166170
lecture.setAudioUrl(key);
167171
lectureRepository.save(lecture);
168-
169-
String audioName = key.substring(key.lastIndexOf('/') + 1);
172+
lecture.setSaveAudio(true);
170173

171174
return LectureRecordingResponseDTO.builder()
172175
.lectureId(lecture.getId())
173-
.audioName(audioName)
176+
.audioName(uploadFileName)
174177
.audioUrl(s3Service.getPresignedUrl(key))
175178
.build();
176179
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.example.backend.domain.lectureNote.converter;
2+
3+
import org.springframework.web.multipart.MultipartFile;
4+
import java.io.*;
5+
6+
public class FileToMultipartFileConverter implements MultipartFile {
7+
8+
private final File file;
9+
private final String contentType;
10+
11+
// file -> multipartFile 변환
12+
public FileToMultipartFileConverter(File file, String contentType) {
13+
this.file = file;
14+
this.contentType = contentType;
15+
}
16+
17+
@Override
18+
public String getName() {
19+
return file.getName();
20+
}
21+
22+
@Override
23+
public String getOriginalFilename() {
24+
return file.getName();
25+
}
26+
27+
@Override
28+
public String getContentType() {
29+
return contentType;
30+
}
31+
32+
@Override
33+
public boolean isEmpty() {
34+
return file.length() == 0;
35+
}
36+
37+
@Override
38+
public long getSize() {
39+
return file.length();
40+
}
41+
42+
@Override
43+
public byte[] getBytes() throws IOException {
44+
return java.nio.file.Files.readAllBytes(file.toPath());
45+
}
46+
47+
@Override
48+
public InputStream getInputStream() throws IOException {
49+
return new FileInputStream(file);
50+
}
51+
52+
@Override
53+
public void transferTo(File dest) throws IOException {
54+
try (InputStream in = getInputStream(); OutputStream out = new FileOutputStream(dest)) {
55+
in.transferTo(out);
56+
}
57+
}
58+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.example.backend.domain.lectureNote.converter;
2+
import org.springframework.stereotype.Component;
3+
4+
import java.io.File;
5+
import java.io.IOException;
6+
7+
@Component
8+
public class LibreOfficeConverter {
9+
10+
// pptx -> pdf 변환
11+
public File convertPptxToPdf(File pptxFile) throws IOException {
12+
try {
13+
String outputDir = pptxFile.getParent();
14+
15+
String baseName = pptxFile.getName().replaceAll("(?i)\\.pptx$", "");
16+
File outputPdf = new File(outputDir, baseName + ".pdf");
17+
18+
ProcessBuilder pb = new ProcessBuilder(
19+
"libreoffice", "--headless",
20+
"--convert-to", "pdf:writer_pdf_Export",
21+
pptxFile.getAbsolutePath(),
22+
"--outdir", outputDir
23+
);
24+
Process process = pb.start();
25+
int exitCode = process.waitFor();
26+
if (exitCode != 0) {
27+
throw new IOException("LibreOffice 변환 실패: exitCode=" + exitCode);
28+
}
29+
30+
if (!outputPdf.exists()) {
31+
throw new IOException("LibreOffice 변환 실패: 출력 PDF 없음");
32+
}
33+
34+
return outputPdf;
35+
} catch (InterruptedException e) {
36+
Thread.currentThread().interrupt();
37+
throw new IOException("LibreOffice 변환 중 인터럽트 발생", e);
38+
}
39+
}
40+
}

backend/src/main/java/org/example/backend/domain/lectureNote/service/LectureNoteServiceImpl.java

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import org.example.backend.domain.classroom.exception.ClassroomErrorCode;
66
import org.example.backend.domain.classroom.exception.ClassroomException;
77
import org.example.backend.domain.classroom.repository.ClassroomRepository;
8+
import org.example.backend.domain.lectureNote.converter.FileToMultipartFileConverter;
9+
import org.example.backend.domain.lectureNote.converter.LibreOfficeConverter;
810
import org.example.backend.domain.lectureNote.dto.response.LectureNoteKeyResponseDTO;
911
import org.example.backend.domain.lectureNote.dto.response.LectureNoteResponseDTO;
1012
import org.example.backend.domain.lectureNote.entity.LectureNote;
@@ -17,6 +19,7 @@
1719
import org.springframework.stereotype.Service;
1820
import org.springframework.web.multipart.MultipartFile;
1921

22+
import java.io.File;
2023
import java.io.IOException;
2124
import java.util.ArrayList;
2225
import java.util.List;
@@ -29,31 +32,67 @@ public class LectureNoteServiceImpl implements LectureNoteService {
2932

3033
private final S3Service s3Service;
3134
private final LectureNoteRepository lectureNoteRepository;
32-
private final ClassroomRepository classroomRepository; // ✅ 추가
35+
private final ClassroomRepository classroomRepository;
3336
private final LectureNoteMappingRepository lectureNoteMappingRepository;
37+
private final LibreOfficeConverter libreOfficeConverter;
3438

3539
public List<LectureNote> uploadLectureNotes(UUID classId, List<MultipartFile> files) throws IOException {
36-
// 1. 여러 파일에 대해 처리
3740
List<LectureNote> lectureNotes = new ArrayList<>();
3841

39-
// 2. 각 파일 업로드 처리
4042
for (MultipartFile file : files) {
41-
// 1) S3에 업로드
42-
String key = "lecture_note/" + classId + "/" + UUID.randomUUID() + "/" + file.getOriginalFilename();
43-
String fileUrl = s3Service.uploadFile(file, key);
44-
45-
// 2) classId로 Classroom 엔티티 조회
46-
Classroom classroom = classroomRepository.findById(classId)
47-
.orElseThrow(() -> new ClassroomException(ClassroomErrorCode.CLASS_NOT_FOUND));
48-
49-
// 3) LectureNote 객체 생성
50-
LectureNote lectureNote = LectureNote.builder()
51-
.noteUrl(key)
52-
.classroom(classroom) // Classroom 객체 넣기
53-
.build();
54-
55-
// 4) LectureNote 저장
56-
lectureNotes.add(lectureNoteRepository.save(lectureNote));
43+
String originalFilename = Objects.requireNonNull(file.getOriginalFilename(), "파일명이 필요합니다");
44+
boolean isPptx = originalFilename.toLowerCase().endsWith(".pptx");
45+
46+
File tempSrc = null;
47+
File convertedPdf = null;
48+
String uploadFileName;
49+
String key;
50+
MultipartFile pdfMultipart;
51+
52+
try {
53+
if (isPptx) {
54+
// MultipartFile -> File
55+
tempSrc = new File(System.getProperty("java.io.tmpdir"), originalFilename);
56+
file.transferTo(tempSrc);
57+
58+
try {
59+
// PPTX → PDF 변환
60+
convertedPdf = libreOfficeConverter.convertPptxToPdf(tempSrc);
61+
62+
// File -> MultipartFile
63+
pdfMultipart = new FileToMultipartFileConverter(convertedPdf, "application/pdf");
64+
} catch (Exception e) {
65+
throw new IOException("PPTX→PDF 변환 실패: " + originalFilename, e);
66+
}
67+
68+
uploadFileName = convertedPdf.getName();
69+
70+
// S3 업로드
71+
key = "lecture_note/" + classId + "/" + UUID.randomUUID() + "/" + uploadFileName;
72+
s3Service.uploadFile(pdfMultipart, key);
73+
74+
} else {
75+
// pptx 외 다른 파일들 그대로 업로드
76+
uploadFileName = originalFilename;
77+
key = "lecture_note/" + classId + "/" + UUID.randomUUID() + "/" + uploadFileName;
78+
s3Service.uploadFile(file, key);
79+
}
80+
81+
Classroom classroom = classroomRepository.findById(classId)
82+
.orElseThrow(() -> new ClassroomException(ClassroomErrorCode.CLASS_NOT_FOUND));
83+
84+
LectureNote lectureNote = LectureNote.builder()
85+
.noteUrl(key)
86+
.classroom(classroom)
87+
.build();
88+
89+
lectureNotes.add(lectureNoteRepository.save(lectureNote));
90+
91+
} finally {
92+
// pdf로 변환하며 생긴 임시 파일 삭제
93+
if (tempSrc != null && tempSrc.exists()) tempSrc.delete();
94+
if (convertedPdf != null && convertedPdf.exists()) convertedPdf.delete();
95+
}
5796
}
5897

5998
return lectureNotes;

0 commit comments

Comments
 (0)