diff --git a/backend/Dockerfile b/backend/Dockerfile index 08fdb22a..2f25faee 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,9 @@ -FROM openjdk:17-jdk +FROM eclipse-temurin:17-jdk + +RUN apt-get update \ + && apt-get install -y --no-install-recommends libreoffice \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* COPY build/libs/backend-0.0.1-SNAPSHOT.jar /app.jar diff --git a/backend/src/main/java/org/example/backend/domain/lecture/entity/Lecture.java b/backend/src/main/java/org/example/backend/domain/lecture/entity/Lecture.java index 2260e5fa..6e7c2369 100644 --- a/backend/src/main/java/org/example/backend/domain/lecture/entity/Lecture.java +++ b/backend/src/main/java/org/example/backend/domain/lecture/entity/Lecture.java @@ -39,8 +39,10 @@ public class Lecture extends BaseEntity { @Column(name = "audio_url") private String audioUrl; + @Column(name = "is_lecture_start") private Boolean isLectureStart; + @Column(name = "save_audio") private Boolean saveAudio; @Column(name = "lecture_date", nullable = false) diff --git a/backend/src/main/java/org/example/backend/domain/lecture/service/LectureServiceImpl.java b/backend/src/main/java/org/example/backend/domain/lecture/service/LectureServiceImpl.java index 341b33ed..e4a03301 100644 --- a/backend/src/main/java/org/example/backend/domain/lecture/service/LectureServiceImpl.java +++ b/backend/src/main/java/org/example/backend/domain/lecture/service/LectureServiceImpl.java @@ -152,25 +152,28 @@ public void deleteLecture(UUID lectureId) { //녹음본 저장 public LectureRecordingResponseDTO uploadLectureRecording(UUID lectureId, MultipartFile file) { + Lecture lecture = lectureRepository.findById(lectureId) .orElseThrow(() -> new LectureException(LectureErrorCode.LECTURE_NOT_FOUND)); - String key = "recordings/" + lectureId +"/" + UUID.randomUUID() + "/" + file.getOriginalFilename(); + String safeLectureName = lecture.getLectureName().replaceAll("\\s+", "_"); + String uploadFileName = safeLectureName + ".mp3"; + + String key = "recordings/" + lectureId + "/" + UUID.randomUUID() + "/" + uploadFileName; try { - s3Service.uploadFile(file, key); // private 업로드 + s3Service.uploadFile(file, key); } catch (IOException e) { throw new S3Exception(S3ErrorCode.UPLOAD_FAIL); } lecture.setAudioUrl(key); lectureRepository.save(lecture); - - String audioName = key.substring(key.lastIndexOf('/') + 1); + lecture.setSaveAudio(true); return LectureRecordingResponseDTO.builder() .lectureId(lecture.getId()) - .audioName(audioName) + .audioName(uploadFileName) .audioUrl(s3Service.getPresignedUrl(key)) .build(); } diff --git a/backend/src/main/java/org/example/backend/domain/lectureNote/converter/FileToMultipartFileConverter.java b/backend/src/main/java/org/example/backend/domain/lectureNote/converter/FileToMultipartFileConverter.java new file mode 100644 index 00000000..c3d0e707 --- /dev/null +++ b/backend/src/main/java/org/example/backend/domain/lectureNote/converter/FileToMultipartFileConverter.java @@ -0,0 +1,58 @@ +package org.example.backend.domain.lectureNote.converter; + +import org.springframework.web.multipart.MultipartFile; +import java.io.*; + +public class FileToMultipartFileConverter implements MultipartFile { + + private final File file; + private final String contentType; + + // file -> multipartFile 변환 + public FileToMultipartFileConverter(File file, String contentType) { + this.file = file; + this.contentType = contentType; + } + + @Override + public String getName() { + return file.getName(); + } + + @Override + public String getOriginalFilename() { + return file.getName(); + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public boolean isEmpty() { + return file.length() == 0; + } + + @Override + public long getSize() { + return file.length(); + } + + @Override + public byte[] getBytes() throws IOException { + return java.nio.file.Files.readAllBytes(file.toPath()); + } + + @Override + public InputStream getInputStream() throws IOException { + return new FileInputStream(file); + } + + @Override + public void transferTo(File dest) throws IOException { + try (InputStream in = getInputStream(); OutputStream out = new FileOutputStream(dest)) { + in.transferTo(out); + } + } +} diff --git a/backend/src/main/java/org/example/backend/domain/lectureNote/converter/LibreOfficeConverter.java b/backend/src/main/java/org/example/backend/domain/lectureNote/converter/LibreOfficeConverter.java new file mode 100644 index 00000000..5dcdc3f6 --- /dev/null +++ b/backend/src/main/java/org/example/backend/domain/lectureNote/converter/LibreOfficeConverter.java @@ -0,0 +1,40 @@ +package org.example.backend.domain.lectureNote.converter; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; + +@Component +public class LibreOfficeConverter { + + // pptx -> pdf 변환 + public File convertPptxToPdf(File pptxFile) throws IOException { + try { + String outputDir = pptxFile.getParent(); + + String baseName = pptxFile.getName().replaceAll("(?i)\\.pptx$", ""); + File outputPdf = new File(outputDir, baseName + ".pdf"); + + ProcessBuilder pb = new ProcessBuilder( + "libreoffice", "--headless", + "--convert-to", "pdf:writer_pdf_Export", + pptxFile.getAbsolutePath(), + "--outdir", outputDir + ); + Process process = pb.start(); + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new IOException("LibreOffice 변환 실패: exitCode=" + exitCode); + } + + if (!outputPdf.exists()) { + throw new IOException("LibreOffice 변환 실패: 출력 PDF 없음"); + } + + return outputPdf; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("LibreOffice 변환 중 인터럽트 발생", e); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/example/backend/domain/lectureNote/service/LectureNoteServiceImpl.java b/backend/src/main/java/org/example/backend/domain/lectureNote/service/LectureNoteServiceImpl.java index 19566053..5e98f862 100644 --- a/backend/src/main/java/org/example/backend/domain/lectureNote/service/LectureNoteServiceImpl.java +++ b/backend/src/main/java/org/example/backend/domain/lectureNote/service/LectureNoteServiceImpl.java @@ -5,6 +5,8 @@ import org.example.backend.domain.classroom.exception.ClassroomErrorCode; import org.example.backend.domain.classroom.exception.ClassroomException; import org.example.backend.domain.classroom.repository.ClassroomRepository; +import org.example.backend.domain.lectureNote.converter.FileToMultipartFileConverter; +import org.example.backend.domain.lectureNote.converter.LibreOfficeConverter; import org.example.backend.domain.lectureNote.dto.response.LectureNoteKeyResponseDTO; import org.example.backend.domain.lectureNote.dto.response.LectureNoteResponseDTO; import org.example.backend.domain.lectureNote.entity.LectureNote; @@ -17,6 +19,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -29,31 +32,67 @@ public class LectureNoteServiceImpl implements LectureNoteService { private final S3Service s3Service; private final LectureNoteRepository lectureNoteRepository; - private final ClassroomRepository classroomRepository; // ✅ 추가 + private final ClassroomRepository classroomRepository; private final LectureNoteMappingRepository lectureNoteMappingRepository; + private final LibreOfficeConverter libreOfficeConverter; public List uploadLectureNotes(UUID classId, List files) throws IOException { - // 1. 여러 파일에 대해 처리 List lectureNotes = new ArrayList<>(); - // 2. 각 파일 업로드 처리 for (MultipartFile file : files) { - // 1) S3에 업로드 - String key = "lecture_note/" + classId + "/" + UUID.randomUUID() + "/" + file.getOriginalFilename(); - String fileUrl = s3Service.uploadFile(file, key); - - // 2) classId로 Classroom 엔티티 조회 - Classroom classroom = classroomRepository.findById(classId) - .orElseThrow(() -> new ClassroomException(ClassroomErrorCode.CLASS_NOT_FOUND)); - - // 3) LectureNote 객체 생성 - LectureNote lectureNote = LectureNote.builder() - .noteUrl(key) - .classroom(classroom) // Classroom 객체 넣기 - .build(); - - // 4) LectureNote 저장 - lectureNotes.add(lectureNoteRepository.save(lectureNote)); + String originalFilename = Objects.requireNonNull(file.getOriginalFilename(), "파일명이 필요합니다"); + boolean isPptx = originalFilename.toLowerCase().endsWith(".pptx"); + + File tempSrc = null; + File convertedPdf = null; + String uploadFileName; + String key; + MultipartFile pdfMultipart; + + try { + if (isPptx) { + // MultipartFile -> File + tempSrc = new File(System.getProperty("java.io.tmpdir"), originalFilename); + file.transferTo(tempSrc); + + try { + // PPTX → PDF 변환 + convertedPdf = libreOfficeConverter.convertPptxToPdf(tempSrc); + + // File -> MultipartFile + pdfMultipart = new FileToMultipartFileConverter(convertedPdf, "application/pdf"); + } catch (Exception e) { + throw new IOException("PPTX→PDF 변환 실패: " + originalFilename, e); + } + + uploadFileName = convertedPdf.getName(); + + // S3 업로드 + key = "lecture_note/" + classId + "/" + UUID.randomUUID() + "/" + uploadFileName; + s3Service.uploadFile(pdfMultipart, key); + + } else { + // pptx 외 다른 파일들 그대로 업로드 + uploadFileName = originalFilename; + key = "lecture_note/" + classId + "/" + UUID.randomUUID() + "/" + uploadFileName; + s3Service.uploadFile(file, key); + } + + Classroom classroom = classroomRepository.findById(classId) + .orElseThrow(() -> new ClassroomException(ClassroomErrorCode.CLASS_NOT_FOUND)); + + LectureNote lectureNote = LectureNote.builder() + .noteUrl(key) + .classroom(classroom) + .build(); + + lectureNotes.add(lectureNoteRepository.save(lectureNote)); + + } finally { + // pdf로 변환하며 생긴 임시 파일 삭제 + if (tempSrc != null && tempSrc.exists()) tempSrc.delete(); + if (convertedPdf != null && convertedPdf.exists()) convertedPdf.delete(); + } } return lectureNotes;