Skip to content

Commit ac7cfa1

Browse files
authored
Merge pull request #31 from CodIN-INU/develop
#29 feat : Load Lecture Metadata (keyword, tags, pre_course)
2 parents 888835a + a2b941b commit ac7cfa1

5 files changed

Lines changed: 138 additions & 0 deletions

File tree

src/main/java/inu/codin/codin/domain/lecture/controller/LectureUploadController.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package inu.codin.codin.domain.lecture.controller;
22

3+
import inu.codin.codin.domain.lecture.dto.MetaMode;
34
import inu.codin.codin.domain.lecture.service.LectureUploadService;
45
import inu.codin.codin.global.common.response.SingleResponse;
56
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.Parameter;
68
import io.swagger.v3.oas.annotations.tags.Tag;
79
import lombok.RequiredArgsConstructor;
810
import org.springframework.http.HttpStatus;
@@ -49,5 +51,26 @@ public ResponseEntity<SingleResponse<?>> uploadNewSemesterRooms(@RequestParam("e
4951

5052
}
5153

54+
@Operation(
55+
summary = "강의 메타(키워드/태그/선수과목) 엑셀 업로드",
56+
description = "'단과대약어_연도_학기_meta'로 설정하여 업로드 ex) info_25_2_meta.xlxs. 기본값은 모두 true (즉 전체 처리)."
57+
)
58+
@PostMapping(value = "/meta", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
59+
@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')")
60+
public ResponseEntity<SingleResponse<?>> uploadLectureMeta(
61+
@Parameter(description = "업로드할 엑셀 파일 (.xlsx)")
62+
@RequestParam("excelFile") MultipartFile file,
63+
64+
@Parameter(description = "처리 모드 (ALL | KEYWORDS | TAGS | PRE_COURSES). 기본값: ALL")
65+
@RequestParam(name = "mode", defaultValue = "ALL") MetaMode mode
66+
) {
67+
lectureUploadService.uploadLectureMeta(file, mode);
68+
return ResponseEntity.status(HttpStatus.CREATED)
69+
.body(new SingleResponse<>(201,
70+
file.getOriginalFilename()+"의 태그,키워드,선수 과목등 메타데이터 포함 엑셀파일 업데이트",
71+
null));
72+
73+
}
74+
5275

5376
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package inu.codin.codin.domain.lecture.dto;
2+
3+
public enum MetaMode {
4+
ALL, // 키워드+태그+선수과목
5+
KEYWORDS, // 키워드만
6+
TAGS, // 태그만
7+
PRE_COURSES // 선수과목만
8+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package inu.codin.codin.domain.lecture.entity;
2+
3+
import jakarta.persistence.*;
4+
import lombok.AccessLevel;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
8+
import java.util.List;
9+
10+
@Entity
11+
@Getter
12+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
13+
public class Keyword {
14+
@Id
15+
@GeneratedValue(strategy = GenerationType.IDENTITY)
16+
private Long id;
17+
private String keywordDescription;
18+
19+
@OneToMany(mappedBy = "keyword", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
20+
private List<LectureKeyword> lectureKeywords;
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package inu.codin.codin.domain.lecture.entity;
2+
3+
import jakarta.persistence.*;
4+
import lombok.AccessLevel;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
8+
@Entity
9+
@Getter
10+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
11+
public class LectureKeyword {
12+
13+
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
14+
private Long id;
15+
16+
@ManyToOne(fetch = FetchType.LAZY)
17+
@JoinColumn(name = "lecture_id")
18+
private Lecture lecture;
19+
20+
@ManyToOne(fetch = FetchType.LAZY)
21+
@JoinColumn(name = "keyword_id")
22+
private Keyword keyword;
23+
}

src/main/java/inu/codin/codin/domain/lecture/service/LectureUploadService.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package inu.codin.codin.domain.lecture.service;
22

33
import inu.codin.codin.domain.elasticsearch.indexer.LectureStartupIndexer;
4+
import inu.codin.codin.domain.lecture.dto.MetaMode;
45
import inu.codin.codin.domain.lecture.exception.LectureErrorCode;
56
import inu.codin.codin.domain.lecture.exception.LectureUploadException;
67
import lombok.RequiredArgsConstructor;
@@ -12,6 +13,9 @@
1213
import java.io.File;
1314
import java.io.FileOutputStream;
1415
import java.io.IOException;
16+
import java.nio.file.Paths;
17+
import java.util.ArrayList;
18+
import java.util.List;
1519

1620
@Slf4j
1721
@Service
@@ -28,6 +32,8 @@ public class LectureUploadService {
2832

2933
private String ROOM_PROGRAM = "dayTimeOfRoom.py";
3034
private String LECTURE_PROGRAM = "infoOfLecture.py";
35+
private static final String META_PROGRAM = "load_metadata.py";
36+
3137

3238
public void uploadNewSemesterLectures(MultipartFile file){
3339
try {
@@ -68,6 +74,63 @@ public void uploadNewSemesterRooms(MultipartFile file) {
6874
}
6975
}
7076

77+
public void uploadLectureMeta(MultipartFile file, MetaMode mode) {
78+
try {
79+
saveFile(file); // 기존 그대로 사용
80+
executeMetaLoader(file, mode);
81+
log.info("[uploadLectureMeta] {} 메타 업데이트 완료", file.getOriginalFilename());
82+
83+
try { indexer.lectureIndex(); } catch (Exception e) {
84+
log.warn("[uploadLectureMeta] 색인 갱신 경고: {}", e.getMessage());
85+
}
86+
87+
} catch (LectureUploadException e) {
88+
throw e;
89+
} catch (Exception e) {
90+
log.error(e.getMessage(), e);
91+
throw new LectureUploadException(LectureErrorCode.LECTURE_UPLOAD_FAIL, e.getMessage());
92+
}
93+
}
94+
95+
96+
/** 메타 로더 실행 헬퍼: load_metadata.py <excel> [--keywords] [--tags] [--pre-courses] | --all */
97+
private void executeMetaLoader(MultipartFile file,MetaMode mode) {
98+
String scriptPath = Paths.get(UPLOAD_DIR, META_PROGRAM).toString();
99+
String excelPath = Paths.get(UPLOAD_DIR, file.getOriginalFilename()).toString();
100+
101+
List<String> cmd = new ArrayList<>();
102+
cmd.add(PYTHON_DIR); // ex) /usr/bin/python3 or venv/bin/python
103+
cmd.add(scriptPath); // load_metadata.py 절대/상대 경로
104+
cmd.add(excelPath); // 업로드된 엑셀 파일 경로
105+
106+
switch (mode) {
107+
case ALL -> cmd.add("--all");
108+
case KEYWORDS -> cmd.add("--keywords");
109+
case TAGS -> cmd.add("--tags");
110+
case PRE_COURSES -> cmd.add("--pre-courses");
111+
}
112+
ProcessBuilder pb = new ProcessBuilder(cmd);
113+
pb.redirectErrorStream(true);
114+
115+
try {
116+
Process p = pb.start();
117+
StringBuilder out = new StringBuilder();
118+
try (var br = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream()))) {
119+
String line;
120+
while ((line = br.readLine()) != null) {
121+
out.append(line).append('\n');
122+
log.debug("[Python Output] {}", line);
123+
}
124+
}
125+
int exit = p.waitFor();
126+
if (exit != 0) {
127+
throw new LectureUploadException(LectureErrorCode.LECTURE_UPLOAD_FAIL, out.toString());
128+
}
129+
} catch (IOException | InterruptedException e) {
130+
throw new LectureUploadException(LectureErrorCode.LECTURE_UPLOAD_FAIL, e.getMessage());
131+
}
132+
}
133+
71134
private void saveFile(MultipartFile file) {
72135
String originalName = file.getOriginalFilename();
73136
if (originalName == null || originalName.isBlank()) {

0 commit comments

Comments
 (0)