Skip to content
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 build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id 'java'
id 'org.springframework.boot' version '3.4.3'
id 'io.spring.dependency-management' version '1.1.7'
id 'org.jetbrains.kotlin.jvm'
}

group = 'com.example'
Expand Down Expand Up @@ -36,6 +37,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j:8.0.33'
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
Expand Down Expand Up @@ -65,7 +67,9 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'com.chuseok22:sejong-portal-login:1.0.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"

implementation 'org.apache.poi:poi-ooxml:5.2.3'
}

tasks.named('test') {
Expand Down
5 changes: 5 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
pluginManagement {
plugins {
id 'org.jetbrains.kotlin.jvm' version '2.1.21'
}
}
rootProject.name = 'SmartAir'
11 changes: 4 additions & 7 deletions src/main/java/com/example/enjoy/controller/HomeController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

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

@Operation(summary = "트랙 진행률 조회", description = "현재 학생의 전체 트랙 진행률을 조회합니다.")
@GetMapping("/home")
public List<TrackProgressDto> showMyProgress() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentStudentId = authentication.getName();
return trackService.calculateTrackProgress(currentStudentId);
@GetMapping("/home/{studentId}")
public List<TrackProgressDto> showMyProgress(@PathVariable String studentId) {
return trackService.calculateTrackProgress(studentId);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,43 @@
package com.example.enjoy.controller;

import org.springframework.web.bind.annotation.RestController;
import com.example.enjoy.dto.TrackProgressDto;
import com.example.enjoy.service.StudentDataService;
import com.example.enjoy.service.TrackService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@RestController
@RequestMapping("/student-data")
@RequiredArgsConstructor
public class StudentDataController {

private final StudentDataService studentDataService;
private TrackService trackService;

@Operation(
summary = "엑셀 파일 업로드",
description = "기이수 성적 엑셀 파일(.xlsx)을 업로드하고, 과목 정보를 서버에 저장합니다."
)
@ApiResponse(responseCode = "200", description = "업로드 성공")
@PostMapping(value = "/upload/{studentId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<List<TrackProgressDto>> uploadCourseExcel(
@RequestParam("file") MultipartFile file,
@PathVariable String studentId
) {
// 1. 엑셀 파싱 및 DB 저장
studentDataService.parseAndSaveCourses(file, studentId);

// 2. 트랙 진행률 계산 (업로드 직후 기준)
List<TrackProgressDto> progress = trackService.calculateTrackProgress(studentId);

// 3. 진행률 반환
return ResponseEntity.ok(progress);
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/example/enjoy/dto/ParsedCourseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.enjoy.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class ParsedCourseDto {
private String courseName;
private StudentCourseStatus status;
}
2 changes: 2 additions & 0 deletions src/main/java/com/example/enjoy/dto/TrackProgressDto.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.enjoy.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
Expand All @@ -9,6 +10,7 @@
*/
@Getter
@Setter
@AllArgsConstructor
public class TrackProgressDto {

private String trackName; // 트랙 이름 (예: "AI 콘텐츠")
Expand Down
66 changes: 66 additions & 0 deletions src/main/java/com/example/enjoy/service/StudentDataService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.example.enjoy.service;

import com.example.enjoy.dto.ParsedCourseDto;
import com.example.enjoy.dto.StudentCourseStatus;
import com.example.enjoy.entity.StudentCourse;
import com.example.enjoy.repository.StudentCourseRepository;
import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.*;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Service
@RequiredArgsConstructor
public class StudentDataService {

private final StudentCourseRepository studentCourseRepository;

public void parseAndSaveCourses(MultipartFile file, String studentId) {
List<ParsedCourseDto> parsedList = new ArrayList<>();

try (Workbook workbook = WorkbookFactory.create(file.getInputStream())) {
Sheet sheet = workbook.getSheetAt(0);
for (Row row : sheet) {
if (row.getRowNum() == 0) continue;

Cell courseCell = row.getCell(4); // 교과목명
Cell gradeCell = row.getCell(10); // 등급

if (courseCell == null || gradeCell == null) continue;

String courseName = courseCell.getStringCellValue().trim();
String grade = gradeCell.getStringCellValue().trim();

StudentCourseStatus status = mapGradeToStatus(grade);

parsedList.add(new ParsedCourseDto(courseName, status));
}
} catch (Exception e) {
throw new RuntimeException("엑셀 파일 파싱 실패: " + e.getMessage(), e);
}

List<StudentCourse> courses = parsedList.stream()
.map(dto -> StudentCourse.builder()
.studentId(studentId)
.courseName(dto.getCourseName())
.status(dto.getStatus())
.manual(false)
.createdAt(LocalDateTime.now())
.build())
.toList();

studentCourseRepository.saveAll(courses);
}

private StudentCourseStatus mapGradeToStatus(String grade) {
return switch (grade.toUpperCase()) {
case "A+", "A0", "B+", "B0", "C+", "C0", "P" -> StudentCourseStatus.COMPLETED;
case "F", "NP" -> StudentCourseStatus.FAILED;
default -> throw new IllegalArgumentException("잘못된 등급값: " + grade);
};
}
}
80 changes: 22 additions & 58 deletions src/main/java/com/example/enjoy/service/TrackService.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

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

/**
* 모든 트랙 정보를 학과별로 그룹화하여 반환하는 메서드
*/
public Map<String, List<Track>> getAllTracksGroupedByDepartment() {
// 1. DB에서 모든 트랙과 관련 과목들을 한번에 조회
List<Track> allTracks = trackRepository.findAllWithCourses();

// 2. 조회된 트랙 리스트를 '학과' 이름으로 그룹화하여 Map으로 변환 후 반환
return allTracks.stream()
.collect(Collectors.groupingBy(Track::getDepartment));
}

/**
* 학생이 이수한 과목 이름을 Set으로 반환하는 private 메서드
*/
public List<TrackProgressDto> calculateTrackProgress(String studentId) {
// 1. 학생의 이수 과목 목록 조회
Set<String> completedCourseNames = studentCourseRepository.findByStudentId(studentId)
.stream()
.map(StudentCourse::getCourseName)
.collect(Collectors.toSet());

// 2. 모든 트랙 정보 조회
List<Track> allTracks = trackRepository.findAllWithCourses();

List<TrackProgressDto> progressList = new ArrayList<>();

// 3. 각 트랙별로 진행 현황 계산
for (Track track : allTracks) {
Set<String> completedCourseNames = getCompletedCourseNames(studentId); // 이수 과목명 목록

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

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

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

// 최종 결과를 담을 DTO 객체 생성 및 데이터 세팅
TrackProgressDto progressDto = new TrackProgressDto();
progressDto.setTrackName(track.getName());
progressDto.setDepartment(track.getDepartment());

int completedCount = completedInThisTrack.size();
progressDto.setCompletedCount(completedCount);
progressDto.setRequiredCount(6); // 트랙 이수 요구 과목 수는 6개
progressDto.setCompleted(completedCount >= 6); // 6개 이상이면 true

progressDto.setCompletedCourses(completedInThisTrack);
progressDto.setRemainingCourses(remainingInThisTrack);
return new TrackProgressDto(
track.getName(),
track.getDepartment(),
completed.size(),
courses.size(),
completed.size() == courses.size(),
completed,
remaining
);
}).toList();
}

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

return progressList;
}

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