Skip to content

Commit cdaa0b4

Browse files
authored
Merge pull request #17 from enjoy-hack/jiwoo
feat: ---
2 parents 16b0582 + 800ad8c commit cdaa0b4

8 files changed

Lines changed: 151 additions & 66 deletions

File tree

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
id 'java'
33
id 'org.springframework.boot' version '3.4.3'
44
id 'io.spring.dependency-management' version '1.1.7'
5+
id 'org.jetbrains.kotlin.jvm'
56
}
67

78
group = 'com.example'
@@ -36,6 +37,7 @@ dependencies {
3637
implementation 'org.springframework.boot:spring-boot-starter-security'
3738
implementation 'org.springframework.boot:spring-boot-starter-web'
3839
implementation 'org.springframework.boot:spring-boot-starter-validation'
40+
implementation 'org.springframework.boot:spring-boot-starter-actuator'
3941
compileOnly 'org.projectlombok:lombok'
4042
runtimeOnly 'com.mysql:mysql-connector-j:8.0.33'
4143
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
@@ -65,7 +67,9 @@ dependencies {
6567
testImplementation 'org.springframework.boot:spring-boot-starter-test'
6668
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
6769
implementation 'com.chuseok22:sejong-portal-login:1.0.0'
70+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
6871

72+
implementation 'org.apache.poi:poi-ooxml:5.2.3'
6973
}
7074

7175
tasks.named('test') {

settings.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1+
pluginManagement {
2+
plugins {
3+
id 'org.jetbrains.kotlin.jvm' version '2.1.21'
4+
}
5+
}
16
rootProject.name = 'SmartAir'

src/main/java/com/example/enjoy/controller/HomeController.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
import io.swagger.v3.oas.annotations.Operation;
66
import io.swagger.v3.oas.annotations.tags.Tag;
77
import lombok.RequiredArgsConstructor;
8-
import org.springframework.security.core.Authentication;
9-
import org.springframework.security.core.context.SecurityContextHolder;
108
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.PathVariable;
1110
import org.springframework.web.bind.annotation.RestController;
1211
import java.util.List;
1312

@@ -19,10 +18,8 @@ public class HomeController {
1918
private final TrackService trackService;
2019

2120
@Operation(summary = "트랙 진행률 조회", description = "현재 학생의 전체 트랙 진행률을 조회합니다.")
22-
@GetMapping("/home")
23-
public List<TrackProgressDto> showMyProgress() {
24-
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
25-
String currentStudentId = authentication.getName();
26-
return trackService.calculateTrackProgress(currentStudentId);
21+
@GetMapping("/home/{studentId}")
22+
public List<TrackProgressDto> showMyProgress(@PathVariable String studentId) {
23+
return trackService.calculateTrackProgress(studentId);
2724
}
2825
}
Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,43 @@
11
package com.example.enjoy.controller;
22

3-
import org.springframework.web.bind.annotation.RestController;
3+
import com.example.enjoy.dto.TrackProgressDto;
4+
import com.example.enjoy.service.StudentDataService;
5+
import com.example.enjoy.service.TrackService;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.MediaType;
10+
import org.springframework.http.ResponseEntity;
11+
import org.springframework.web.bind.annotation.*;
12+
import org.springframework.web.multipart.MultipartFile;
13+
14+
import java.util.List;
415

516
@RestController
17+
@RequestMapping("/student-data")
18+
@RequiredArgsConstructor
619
public class StudentDataController {
20+
21+
private final StudentDataService studentDataService;
22+
private TrackService trackService;
23+
24+
@Operation(
25+
summary = "엑셀 파일 업로드",
26+
description = "기이수 성적 엑셀 파일(.xlsx)을 업로드하고, 과목 정보를 서버에 저장합니다."
27+
)
28+
@ApiResponse(responseCode = "200", description = "업로드 성공")
29+
@PostMapping(value = "/upload/{studentId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
30+
public ResponseEntity<List<TrackProgressDto>> uploadCourseExcel(
31+
@RequestParam("file") MultipartFile file,
32+
@PathVariable String studentId
33+
) {
34+
// 1. 엑셀 파싱 및 DB 저장
35+
studentDataService.parseAndSaveCourses(file, studentId);
36+
37+
// 2. 트랙 진행률 계산 (업로드 직후 기준)
38+
List<TrackProgressDto> progress = trackService.calculateTrackProgress(studentId);
39+
40+
// 3. 진행률 반환
41+
return ResponseEntity.ok(progress);
42+
}
743
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.example.enjoy.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
6+
@Getter
7+
@AllArgsConstructor
8+
public class ParsedCourseDto {
9+
private String courseName;
10+
private StudentCourseStatus status;
11+
}

src/main/java/com/example/enjoy/dto/TrackProgressDto.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.example.enjoy.dto;
22

3+
import lombok.AllArgsConstructor;
34
import lombok.Getter;
45
import lombok.Setter;
56
import java.util.List;
@@ -9,6 +10,7 @@
910
*/
1011
@Getter
1112
@Setter
13+
@AllArgsConstructor
1214
public class TrackProgressDto {
1315

1416
private String trackName; // 트랙 이름 (예: "AI 콘텐츠")
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.example.enjoy.service;
2+
3+
import com.example.enjoy.dto.ParsedCourseDto;
4+
import com.example.enjoy.dto.StudentCourseStatus;
5+
import com.example.enjoy.entity.StudentCourse;
6+
import com.example.enjoy.repository.StudentCourseRepository;
7+
import lombok.RequiredArgsConstructor;
8+
import org.apache.poi.ss.usermodel.*;
9+
import org.springframework.stereotype.Service;
10+
import org.springframework.web.multipart.MultipartFile;
11+
12+
import java.time.LocalDateTime;
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
16+
@Service
17+
@RequiredArgsConstructor
18+
public class StudentDataService {
19+
20+
private final StudentCourseRepository studentCourseRepository;
21+
22+
public void parseAndSaveCourses(MultipartFile file, String studentId) {
23+
List<ParsedCourseDto> parsedList = new ArrayList<>();
24+
25+
try (Workbook workbook = WorkbookFactory.create(file.getInputStream())) {
26+
Sheet sheet = workbook.getSheetAt(0);
27+
for (Row row : sheet) {
28+
if (row.getRowNum() == 0) continue;
29+
30+
Cell courseCell = row.getCell(4); // 교과목명
31+
Cell gradeCell = row.getCell(10); // 등급
32+
33+
if (courseCell == null || gradeCell == null) continue;
34+
35+
String courseName = courseCell.getStringCellValue().trim();
36+
String grade = gradeCell.getStringCellValue().trim();
37+
38+
StudentCourseStatus status = mapGradeToStatus(grade);
39+
40+
parsedList.add(new ParsedCourseDto(courseName, status));
41+
}
42+
} catch (Exception e) {
43+
throw new RuntimeException("엑셀 파일 파싱 실패: " + e.getMessage(), e);
44+
}
45+
46+
List<StudentCourse> courses = parsedList.stream()
47+
.map(dto -> StudentCourse.builder()
48+
.studentId(studentId)
49+
.courseName(dto.getCourseName())
50+
.status(dto.getStatus())
51+
.manual(false)
52+
.createdAt(LocalDateTime.now())
53+
.build())
54+
.toList();
55+
56+
studentCourseRepository.saveAll(courses);
57+
}
58+
59+
private StudentCourseStatus mapGradeToStatus(String grade) {
60+
return switch (grade.toUpperCase()) {
61+
case "A+", "A0", "B+", "B0", "C+", "C0", "P" -> StudentCourseStatus.COMPLETED;
62+
case "F", "NP" -> StudentCourseStatus.FAILED;
63+
default -> throw new IllegalArgumentException("잘못된 등급값: " + grade);
64+
};
65+
}
66+
}

src/main/java/com/example/enjoy/service/TrackService.java

Lines changed: 22 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
import java.util.ArrayList;
1717
import java.util.List;
18-
import java.util.Map;
1918
import java.util.Set;
2019
import java.util.stream.Collectors;
2120

@@ -26,73 +25,38 @@ public class TrackService {
2625
private final TrackRepository trackRepository;
2726
private final StudentCourseRepository studentCourseRepository; // 기존 기능
2827

29-
/**
30-
* 모든 트랙 정보를 학과별로 그룹화하여 반환하는 메서드
31-
*/
32-
public Map<String, List<Track>> getAllTracksGroupedByDepartment() {
33-
// 1. DB에서 모든 트랙과 관련 과목들을 한번에 조회
34-
List<Track> allTracks = trackRepository.findAllWithCourses();
35-
36-
// 2. 조회된 트랙 리스트를 '학과' 이름으로 그룹화하여 Map으로 변환 후 반환
37-
return allTracks.stream()
38-
.collect(Collectors.groupingBy(Track::getDepartment));
39-
}
40-
41-
/**
42-
* 학생이 이수한 과목 이름을 Set으로 반환하는 private 메서드
43-
*/
4428
public List<TrackProgressDto> calculateTrackProgress(String studentId) {
45-
// 1. 학생의 이수 과목 목록 조회
46-
Set<String> completedCourseNames = studentCourseRepository.findByStudentId(studentId)
47-
.stream()
48-
.map(StudentCourse::getCourseName)
49-
.collect(Collectors.toSet());
50-
51-
// 2. 모든 트랙 정보 조회
52-
List<Track> allTracks = trackRepository.findAllWithCourses();
53-
54-
List<TrackProgressDto> progressList = new ArrayList<>();
55-
56-
// 3. 각 트랙별로 진행 현황 계산
57-
for (Track track : allTracks) {
29+
Set<String> completedCourseNames = getCompletedCourseNames(studentId); // 이수 과목명 목록
5830

59-
// 현재 트랙에서 완료한 과목과 남은 과목을 담을 리스트 초기화
60-
List<CourseDto> completedInThisTrack = new ArrayList<>();
61-
List<CourseDto> remainingInThisTrack = new ArrayList<>();
31+
List<Track> allTracks = trackRepository.findAll();
6232

63-
// 현재 트랙에 속한 모든 교과목을 하나씩 확인
64-
for (TrackCourse trackCourse : track.getCourses()) {
33+
return allTracks.stream().map(track -> {
34+
List<TrackCourse> courses = track.getCourses(); // 이 트랙의 모든 과목
35+
List<CourseDto> completed = new ArrayList<>();
36+
List<CourseDto> remaining = new ArrayList<>();
6537

66-
// 학생이 해당 과목을 이수했는지 확인 (현재 이름 또는 과거 이름으로 체크)
67-
if (completedCourseNames.contains(trackCourse.getCourseName()) ||
68-
(trackCourse.getCourseAlias() != null && completedCourseNames.contains(trackCourse.getCourseAlias()))) {
69-
// 이수한 경우: 완료 리스트에 추가
70-
completedInThisTrack.add(new CourseDto(trackCourse.getCourseName(), trackCourse.getCourseAlias()));
38+
for (TrackCourse course : courses) {
39+
CourseDto dto = new CourseDto(course.getCourseName(), course.getCourseAlias());
40+
if (isCourseCompleted(course, completedCourseNames)) {
41+
completed.add(dto);
7142
} else {
72-
// 이수하지 않은 경우: 남은 과목 리스트에 추가
73-
remainingInThisTrack.add(new CourseDto(trackCourse.getCourseName(), trackCourse.getCourseAlias()));
43+
remaining.add(dto);
7444
}
7545
}
7646

77-
// 최종 결과를 담을 DTO 객체 생성 및 데이터 세팅
78-
TrackProgressDto progressDto = new TrackProgressDto();
79-
progressDto.setTrackName(track.getName());
80-
progressDto.setDepartment(track.getDepartment());
81-
82-
int completedCount = completedInThisTrack.size();
83-
progressDto.setCompletedCount(completedCount);
84-
progressDto.setRequiredCount(6); // 트랙 이수 요구 과목 수는 6개
85-
progressDto.setCompleted(completedCount >= 6); // 6개 이상이면 true
86-
87-
progressDto.setCompletedCourses(completedInThisTrack);
88-
progressDto.setRemainingCourses(remainingInThisTrack);
47+
return new TrackProgressDto(
48+
track.getName(),
49+
track.getDepartment(),
50+
completed.size(),
51+
courses.size(),
52+
completed.size() == courses.size(),
53+
completed,
54+
remaining
55+
);
56+
}).toList();
57+
}
8958

90-
// 완성된 DTO를 최종 결과 리스트에 추가
91-
progressList.add(progressDto);
92-
}
9359

94-
return progressList;
95-
}
9660

9761
/**
9862
* 학생이 이수한 과목 이름을 Set으로 반환하는 메서드

0 commit comments

Comments
 (0)