Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

강의 엑셀 업로드 API 작성 #575

Merged
merged 14 commits into from
Sep 1, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,10 @@ class AdminController(
adminService.uploadClubListExcel(file)
return ResponseEntity.status(HttpStatus.CREATED).build()
}

@PostMapping("/lecture/excel")
fun uploadLectureListExcel(@RequestPart file: MultipartFile): ResponseEntity<Unit> {
adminService.uploadLectureListExcel(file)
return ResponseEntity.status(HttpStatus.CREATED).build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ interface AdminService {
fun forceWithdraw(userIds: List<UUID>)
fun uploadStudentListExcel(file: MultipartFile)
fun uploadClubListExcel(file: MultipartFile)
fun uploadLectureListExcel(file: MultipartFile)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.springframework.transaction.annotation.Transactional
import org.springframework.web.multipart.MultipartFile
import team.msg.common.enums.ApproveStatus
import team.msg.common.enums.Field
import team.msg.common.util.KakaoUtil
import team.msg.common.util.StudentUtil
import team.msg.common.util.UserUtil
import team.msg.domain.admin.exception.InvalidCellTypeException
Expand All @@ -15,6 +16,13 @@ import team.msg.domain.club.exception.ClubNotFoundException
import team.msg.domain.club.exception.InvalidFieldException
import team.msg.domain.club.model.Club
import team.msg.domain.club.repository.ClubRepository
import team.msg.domain.lecture.enums.Semester
import team.msg.domain.lecture.model.Lecture
import team.msg.domain.lecture.model.LectureDate
import team.msg.domain.lecture.model.LectureLocation
import team.msg.domain.lecture.repository.LectureDateRepository
import team.msg.domain.lecture.repository.LectureLocationRepository
import team.msg.domain.lecture.repository.LectureRepository
import team.msg.domain.school.exception.SchoolNotFoundException
import team.msg.domain.school.repository.SchoolRepository
import team.msg.domain.student.repository.StudentRepository
Expand All @@ -23,11 +31,16 @@ import team.msg.domain.user.exception.InvalidEmailException
import team.msg.domain.user.exception.InvalidPasswordException
import team.msg.domain.user.exception.InvalidPhoneNumberException
import team.msg.domain.user.exception.UserAlreadyApprovedException
import team.msg.domain.user.exception.UserNotFoundException
import team.msg.domain.user.model.User
import team.msg.domain.user.presentation.data.response.UserResponse
import team.msg.domain.user.presentation.data.response.UsersResponse
import team.msg.domain.user.repository.UserRepository
import team.msg.global.exception.InternalServerException
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.util.*

@Service
Expand All @@ -37,7 +50,11 @@ class AdminServiceImpl(
private val studentUtil: StudentUtil,
private val clubRepository: ClubRepository,
private val studentRepository: StudentRepository,
private val schoolRepository: SchoolRepository
private val schoolRepository: SchoolRepository,
private val lectureRepository: LectureRepository,
private val lectureDateRepository: LectureDateRepository,
private val kakaoUtil: KakaoUtil,
private val lectureLocationRepository: LectureLocationRepository
) : AdminService {

/**
Expand Down Expand Up @@ -122,43 +139,43 @@ class AdminServiceImpl(
*/
@Transactional(rollbackFor = [Exception::class])
override fun uploadStudentListExcel(file: MultipartFile) {
file.inputStream.use {
val workbook = try {
WorkbookFactory.create(file.inputStream)
val workbook = file.inputStream.use {
try {
WorkbookFactory.create(it)
} catch (e: IndexOutOfBoundsException) {
throw InvalidCellTypeException("전화번호 셀 서식을 텍스트로 바꿔주세요.")
} catch (e: Exception) {
throw InternalServerException("엑셀 파일 처리 중 문제가 발생했습니다. info : [ errorMessage = ${e.message} ]")
}
}

val sheet = workbook.getSheetAt(0)
val sheet = workbook.getSheetAt(0)

sheet.forEachIndexed { index, row ->
if (index == 0)
return@forEachIndexed
sheet.forEachIndexed { index, row ->
if (index == 0)
return@forEachIndexed

if (row.getCell(0).stringCellValue == "")
return
if (row.getCell(0).stringCellValue == "")
return

val email = row.getCell(0).stringCellValue
val name = row.getCell(1).stringCellValue
val phoneNumber = row.getCell(2).stringCellValue
val password = row.getCell(3).stringCellValue
val clubName = row.getCell(4).stringCellValue
val grade = row.getCell(5).numericCellValue.toInt()
val classRoom = row.getCell(6).numericCellValue.toInt()
val number = row.getCell(7).numericCellValue.toInt()
val admissionNumber = row.getCell(8).numericCellValue.toInt()
val subscriptionGrade = row.getCell(9).numericCellValue.toInt()
val email = row.getCell(0).stringCellValue
val name = row.getCell(1).stringCellValue
val phoneNumber = row.getCell(2).stringCellValue
val password = row.getCell(3).stringCellValue
val clubName = row.getCell(4).stringCellValue
val grade = row.getCell(5).numericCellValue.toInt()
val classRoom = row.getCell(6).numericCellValue.toInt()
val number = row.getCell(7).numericCellValue.toInt()
val admissionNumber = row.getCell(8).numericCellValue.toInt()
val subscriptionGrade = row.getCell(9).numericCellValue.toInt()

validateExcelStudentData(email, phoneNumber, password)
validateExcelStudentData(email, phoneNumber, password)

val user = userUtil.createUser(email, name, phoneNumber, password, Authority.ROLE_STUDENT)
val user = userUtil.createUser(email, name, phoneNumber, password, Authority.ROLE_STUDENT)

val club = clubRepository findByName clubName
val club = clubRepository findByName clubName

studentUtil.createStudent(user, club, grade, classRoom, number, admissionNumber, subscriptionGrade)
}
studentUtil.createStudent(user, club, grade, classRoom, number, admissionNumber, subscriptionGrade)
}
}

Expand All @@ -168,49 +185,156 @@ class AdminServiceImpl(
*/
@Transactional(rollbackFor = [Exception::class])
override fun uploadClubListExcel(file: MultipartFile) {
file.inputStream.use {
val workbook = try {
WorkbookFactory.create(file.inputStream)
val workbook = file.inputStream.use {
try {
WorkbookFactory.create(it)
} catch (e: Exception) {
throw InternalServerException("엑셀 파일 처리 중 문제가 발생했습니다. info : [ errorMessage = ${e.message} ]")
}
}

val sheet = workbook.getSheetAt(0)
val sheet = workbook.getSheetAt(0)

sheet.forEachIndexed { index, row ->
if (index == 0)
return@forEachIndexed
sheet.forEachIndexed { index, row ->
if (index == 0)
return@forEachIndexed

if (row.getCell(0).stringCellValue == "")
return
if (row.getCell(0).stringCellValue == "")
return

val schoolName = row.getCell(0).stringCellValue
val clubName = row.getCell(1).stringCellValue
val field = row.getCell(2).stringCellValue
val schoolName = row.getCell(0).stringCellValue
val clubName = row.getCell(1).stringCellValue
val field = row.getCell(2).stringCellValue

val school = schoolRepository.findByName(schoolName)
?: throw SchoolNotFoundException("존재하지 않는 학교입니다. info : [ schoolName = $schoolName ]")
val school = schoolRepository.findByName(schoolName)
?: throw SchoolNotFoundException("존재하지 않는 학교입니다. info : [ schoolName = $schoolName ]")

if (clubRepository.existsByName(clubName)) {
throw AlreadyExistClubException("이미 존재하는 동아리입니다. info : [ clubName = $clubName ]")
}
if (clubRepository.existsByName(clubName)) {
throw AlreadyExistClubException("이미 존재하는 동아리입니다. info : [ clubName = $clubName ]")
}

val clubField = when (field) {
FUTURISTIC_TRANSPORTATION_EQUIPMENT -> Field.FUTURISTIC_TRANSPORTATION_EQUIPMENT
ENERGY -> Field.ENERGY
MEDICAL_HEALTHCARE -> Field.MEDICAL_HEALTHCARE
AI_CONVERGENCE -> Field.AI_CONVERGENCE
CULTURE -> Field.CULTURE
else -> throw InvalidFieldException("유효하지 않은 동아리 분야입니다. info : [ clubField = $field ]")
}
val clubField = when (field) {
FUTURISTIC_TRANSPORTATION_EQUIPMENT -> Field.FUTURISTIC_TRANSPORTATION_EQUIPMENT
ENERGY -> Field.ENERGY
MEDICAL_HEALTHCARE -> Field.MEDICAL_HEALTHCARE
AI_CONVERGENCE -> Field.AI_CONVERGENCE
CULTURE -> Field.CULTURE
else -> throw InvalidFieldException("유효하지 않은 동아리 분야입니다. info : [ clubField = $field ]")
}

val club = Club(
school = school,
name = clubName,
field = clubField
)
clubRepository.save(club)
}
}

/**
* 강의 리스트 엑셀을 업로드 하는 비지니스 로직입니다
* @param file 강의 리스트 엑셀 업로드를 위한 MultipartFile
*/
@Transactional(rollbackFor = [Exception::class])
override fun uploadLectureListExcel(file: MultipartFile) {
val workbook = file.inputStream.use {
try {
WorkbookFactory.create(it)
} catch (e: IndexOutOfBoundsException) {
throw InvalidCellTypeException("셀 서식을 텍스트로 변경해주세요.")
JuuuuHong marked this conversation as resolved.
Show resolved Hide resolved
} catch (e: Exception) {
throw InternalServerException("엑셀 파일 처리 중 문제가 발생했습니다. info : [ errorMessage = ${e.message} ]")
}
}

val club = Club(
school = school,
name = clubName,
field = clubField
)
clubRepository.save(club)
val sheet = workbook.getSheetAt(0)

sheet.forEachIndexed { index, row ->
if (index == 0 || index == 1)
return@forEachIndexed

if (row.getCell(0) == null || row.getCell(0).stringCellValue == "")
return

val name = row.getCell(0).stringCellValue
val content = row.getCell(1).stringCellValue

val instructorName = row.getCell(2).stringCellValue
val instructorEmail = row.getCell(3).stringCellValue

val instructor = userRepository.findByEmail(instructorEmail)
?: throw UserNotFoundException("존재하지 않는 강사입니다. info : [ email = $instructorEmail ]")

val type = row.getCell(4).stringCellValue
val credit = row.getCell(5).numericCellValue.toInt()
val semester = row.getCell(6).stringCellValue

val lectureSemester = when(semester) {
FIRST_YEAR_FALL_SEMESTER -> Semester.FIRST_YEAR_FALL_SEMESTER
SECOND_YEAR_SPRING_SEMESTER -> Semester.SECOND_YEAR_SPRING_SEMESTER
SECOND_YEAR_FALL_SEMESTER -> Semester.SECOND_YEAR_FALL_SEMESTER
THIRD_YEAR_SPRING_SEMESTER -> Semester.THIRD_YEAR_SPRING_SEMESTER
else -> Semester.NOT_APPLICABLE
}

val division = row.getCell(7).stringCellValue
val line = row.getCell(8).stringCellValue
val department = row.getCell(9).stringCellValue
val maxRegisteredUser = row.getCell(10).numericCellValue.toInt()

val startDate = row.getCell(11).stringCellValue.toLocalDateTime()
val endDate = row.getCell(12).stringCellValue.toLocalDateTime()
val essentialComplete = row.getCell(16).stringCellValue

val lectureEssentialComplete = essentialComplete.uppercase() == "O"

val lecture = Lecture(
id = UUID(0, 0),
name = name,
content = content,
instructor = instructorName,
user = instructor,
lectureType = type,
credit = credit,
semester = lectureSemester,
division = division,
line = line,
department = department,
maxRegisteredUser = maxRegisteredUser,
startDate = startDate,
endDate = endDate,
essentialComplete = lectureEssentialComplete
)

lectureRepository.save(lecture)

val lectureDates = row.getCell(13).stringCellValue
.split(",").map {
val timeZone = it.split(" ", "~")
LectureDate(
id = UUID(0, 0),
lecture = lecture,
completeDate = timeZone[0].toLocalDate(),
startTime = timeZone[1].toLocalTime(),
endTime = timeZone[2].toLocalTime()
)
}

lectureDateRepository.saveAll(lectureDates)

val address = row.getCell(14).stringCellValue
val addressDetails = row.getCell(15).stringCellValue

val coordinate = kakaoUtil.getCoordinate(address)
val lectureLocation = LectureLocation(
lectureId = lecture.id,
x = coordinate.first,
y = coordinate.second,
address = address,
details = addressDetails
)

lectureLocationRepository.save(lectureLocation)
}
}

Expand Down Expand Up @@ -238,5 +362,19 @@ class AdminServiceImpl(
const val MEDICAL_HEALTHCARE = "의료 헬스케어"
const val AI_CONVERGENCE = "AI 융복합"
const val CULTURE = "문화산업"

const val FIRST_YEAR_FALL_SEMESTER = "1학년 2학기"
const val SECOND_YEAR_SPRING_SEMESTER = "2학년 1학기"
const val SECOND_YEAR_FALL_SEMESTER = "2학년 2학기"
const val THIRD_YEAR_SPRING_SEMESTER = "3학년 1학기"
}

private fun String.toLocalDateTime(): LocalDateTime =
LocalDateTime.parse(this, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))

private fun String.toLocalDate(): LocalDate =
LocalDate.parse(this, DateTimeFormatter.ofPattern("yyyy-MM-dd"))

private fun String.toLocalTime(): LocalTime =
LocalTime.parse(this, DateTimeFormatter.ofPattern("HH:mm"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ class SecurityConfig(
.mvcMatchers(HttpMethod.DELETE, "/admin/withdraw").hasRole(ADMIN)
.mvcMatchers(HttpMethod.POST, "/admin/student/excel").hasRole(ADMIN)
.mvcMatchers(HttpMethod.POST, "/admin/club/excel").hasRole(ADMIN)
.mvcMatchers(HttpMethod.POST, "/admin/lecture/excel").hasRole(ADMIN)

// inquiry
.mvcMatchers(HttpMethod.POST, "/inquiry").authenticated()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ import io.mockk.just
import io.mockk.mockk
import io.mockk.verify
import team.msg.common.enums.ApproveStatus
import team.msg.common.util.KakaoUtil
import team.msg.common.util.StudentUtil
import team.msg.common.util.UserUtil
import team.msg.domain.admin.presentation.data.request.QueryUsersRequest
import team.msg.domain.club.repository.ClubRepository
import team.msg.domain.lecture.repository.LectureDateRepository
import team.msg.domain.lecture.repository.LectureLocationRepository
import team.msg.domain.lecture.repository.LectureRepository
import team.msg.domain.school.repository.SchoolRepository
import team.msg.domain.student.model.Student
import team.msg.domain.student.repository.StudentRepository
Expand All @@ -36,13 +40,21 @@ class AdminServiceImplTest : BehaviorSpec({
val clubRepository = mockk<ClubRepository>()
val studentRepository = mockk<StudentRepository>()
val schoolRepository = mockk<SchoolRepository>()
val lectureRepository = mockk<LectureRepository>()
val lectureDateRepository = mockk<LectureDateRepository>()
val lectureLocationRepository = mockk<LectureLocationRepository>()
val kakaoUtil = mockk<KakaoUtil>()
val adminServiceImpl = AdminServiceImpl(
userRepository = userRepository,
userUtil = userUtil,
studentUtil = studentUtil,
clubRepository = clubRepository,
studentRepository = studentRepository,
schoolRepository = schoolRepository
schoolRepository = schoolRepository,
lectureRepository = lectureRepository,
lectureDateRepository = lectureDateRepository,
lectureLocationRepository = lectureLocationRepository,
kakaoUtil = kakaoUtil
)

// queryUsers 테스트 코드
Expand Down
Loading
Loading