From 5877ad6f04909f4ef956e8e5c88ee58ddcdc15b5 Mon Sep 17 00:00:00 2001 From: YunJaeHoon Date: Mon, 23 Dec 2024 20:49:51 +0900 Subject: [PATCH 1/2] =?UTF-8?q?FTR:=20time=5Flocation=20Parser=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../example/Devkor_project/dto/CourseDto.java | 17 +++++ .../Devkor_project/entity/TimeLocation.java | 29 ++++++++ .../Devkor_project/exception/ErrorCode.java | 4 +- .../Devkor_project/service/AdminService.java | 2 +- .../Devkor_project/util/ClassTime.java | 36 ++++++++++ .../util/TimeLocationParser.java | 72 +++++++++++++++++++ 7 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/example/Devkor_project/entity/TimeLocation.java create mode 100644 src/main/java/com/example/Devkor_project/util/ClassTime.java create mode 100644 src/main/java/com/example/Devkor_project/util/TimeLocationParser.java diff --git a/README.md b/README.md index 7e3b975..427bf84 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Academ (Back-end) -Academ Back-end repository 입니다. +Academ Back-end repository입니다. --- diff --git a/src/main/java/com/example/Devkor_project/dto/CourseDto.java b/src/main/java/com/example/Devkor_project/dto/CourseDto.java index 5c1e8bf..b8da0a7 100644 --- a/src/main/java/com/example/Devkor_project/dto/CourseDto.java +++ b/src/main/java/com/example/Devkor_project/dto/CourseDto.java @@ -307,6 +307,23 @@ public static class CheckSynchronization private int delete_count; } + @AllArgsConstructor + @NoArgsConstructor + @Getter + @ToString + @Builder + public static class TimeLocation + { + @Schema(description = "요일") + private String day; + @Schema(description = "시작 교시") + private Integer startPeriod; + @Schema(description = "끝 교시") + private Integer endPeriod; + @Schema(description = "강의실") + private String location; + } + public static CourseDto.Basic entityToBasic(Course course, CourseRating courseRating, Boolean isBookmark) { return Basic.builder() .course_id(course.getCourse_id()) diff --git a/src/main/java/com/example/Devkor_project/entity/TimeLocation.java b/src/main/java/com/example/Devkor_project/entity/TimeLocation.java new file mode 100644 index 0000000..2634b70 --- /dev/null +++ b/src/main/java/com/example/Devkor_project/entity/TimeLocation.java @@ -0,0 +1,29 @@ +package com.example.Devkor_project.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; + +@Entity +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +@ToString +public class TimeLocation +{ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long timeLocation_id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "course_id", nullable = false) + @OnDelete(action = OnDeleteAction.CASCADE) + private Course course_id; + + @Column(nullable = true) private String day; + @Column(nullable = true) private int startPeriod; + @Column(nullable = true) private int endPeriod; + @Column(nullable = true) private String location; +} diff --git a/src/main/java/com/example/Devkor_project/exception/ErrorCode.java b/src/main/java/com/example/Devkor_project/exception/ErrorCode.java index f24c171..9493bd3 100644 --- a/src/main/java/com/example/Devkor_project/exception/ErrorCode.java +++ b/src/main/java/com/example/Devkor_project/exception/ErrorCode.java @@ -40,7 +40,9 @@ public enum ErrorCode NOT_ENOUGH_POINT(HttpStatus.BAD_REQUEST, "포인트가 부족합니다."), WRONG_PASSWORD(HttpStatus.BAD_REQUEST, "비밀번호가 틀렸습니다."), TRAFFIC_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 기간 동안 요청이 들어오지 않았습니다."), - UNKNOWN_NOT_FOUND(HttpStatus.NOT_FOUND, "알 수 없음 계정이 존재하지 않습니다."); + UNKNOWN_NOT_FOUND(HttpStatus.NOT_FOUND, "알 수 없음 계정이 존재하지 않습니다."), + INVALID_PERIOD(HttpStatus.BAD_REQUEST, "해당 교시는 유효하지 않습니다."), + INVALID_TIME_LOCATION(HttpStatus.BAD_REQUEST, "해당 시간 및 장소 정보는 유효하지 않습니다."); private final HttpStatus httpStatus; private final String message; diff --git a/src/main/java/com/example/Devkor_project/service/AdminService.java b/src/main/java/com/example/Devkor_project/service/AdminService.java index 89da76a..0bc3de5 100644 --- a/src/main/java/com/example/Devkor_project/service/AdminService.java +++ b/src/main/java/com/example/Devkor_project/service/AdminService.java @@ -51,7 +51,7 @@ public CourseDto.CheckSynchronization checkCourseSynchronization(CrawlingDto.Syn int delete_count = 0; // 삭제한 강의 정보 개수 // 현재 데이터베이스에 존재하는 해당 연도와 학기의 모든 강의 정보 - // 현재 데이터베이스에는 존재하지만, 크롤링한 데이터에는 존재하지 않는 강의 정보를 데이터베이스에서 삭제하지 위함 + // 현재 데이터베이스에는 존재하지만, 크롤링한 데이터에는 존재하지 않는 강의 정보를 데이터베이스에서 삭제하기 위함 // 즉, 삭제할 강의 리스트 List allCourseInDatabase = courseRepository.findCourseByYearAndSemester(dto.getYear(), dto.getSemester()); diff --git a/src/main/java/com/example/Devkor_project/util/ClassTime.java b/src/main/java/com/example/Devkor_project/util/ClassTime.java new file mode 100644 index 0000000..0c7f642 --- /dev/null +++ b/src/main/java/com/example/Devkor_project/util/ClassTime.java @@ -0,0 +1,36 @@ +package com.example.Devkor_project.util; + +import com.example.Devkor_project.exception.AppException; +import com.example.Devkor_project.exception.ErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalTime; + +@AllArgsConstructor +@Getter +public enum ClassTime +{ + PERIOD_1(1, LocalTime.of(9, 0), LocalTime.of(10, 15)), + PERIOD_2(2, LocalTime.of(10, 30), LocalTime.of(11, 45)), + PERIOD_3(3, LocalTime.of(12, 0), LocalTime.of(13, 15)), + PERIOD_4(4, LocalTime.of(13, 30), LocalTime.of(14, 45)), + PERIOD_5(5, LocalTime.of(15, 0), LocalTime.of(16, 15)), + PERIOD_6(6, LocalTime.of(16, 30), LocalTime.of(17, 45)), + PERIOD_7(7, LocalTime.of(18, 0), LocalTime.of(19, 15)), + PERIOD_8(8, LocalTime.of(19, 30), LocalTime.of(20, 45)); + + private final int period; + private final LocalTime startTime; + private final LocalTime endTime; + + public static ClassTime fromPeriod(int period) { + for (ClassTime classTime : values()) { + if (classTime.getPeriod() == period) { + return classTime; + } + } + throw new AppException(ErrorCode.INVALID_PERIOD, period); + } +} + diff --git a/src/main/java/com/example/Devkor_project/util/TimeLocationParser.java b/src/main/java/com/example/Devkor_project/util/TimeLocationParser.java new file mode 100644 index 0000000..f375a1b --- /dev/null +++ b/src/main/java/com/example/Devkor_project/util/TimeLocationParser.java @@ -0,0 +1,72 @@ +package com.example.Devkor_project.util; + +import com.example.Devkor_project.dto.CourseDto; +import com.example.Devkor_project.exception.AppException; +import com.example.Devkor_project.exception.ErrorCode; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TimeLocationParser { + + /** + * Parses a time_location string to List. + * For example: "화(6-8) 로봇융합관 301호\n목(6)" -> [ + * { + * day: "화" + * startPeriod: 6 + * endPeriod: 8 + * location: "로봇융합관 301호" + * }, + * { + * day: "목" + * startPeriod: 6 + * endPeriod: 6 + * location: null + * } + * ] + * + * @param timeLocations The time_location string to parse. + * @return List representing List of { day, startPeriod, endPeriod, location }. + */ + public static List parseTimeLocation(String timeLocations) + { + if (timeLocations == null || timeLocations.isBlank()) + return null; + + List timeLocationList = new ArrayList<>(); + String[] lines = timeLocations.split("\\n"); + + // Regular expression to extract start and end periods + Pattern pattern = Pattern.compile("([가-힣])\\((\\d+)(?:-(\\d+))?\\)(?:\\s(.+))?|([가-힣])"); + + for (String line : lines) { + Matcher matcher = pattern.matcher(line.trim()); + + if (matcher.find()) { + String day = matcher.group(1) != null ? matcher.group(1) : matcher.group(5); + String start = matcher.group(2); + String end = matcher.group(3); + String location = matcher.group(4); + + Integer startPeriod = (start != null) ? Integer.parseInt(start) : null; + Integer endPeriod = (end != null) ? Integer.valueOf(Integer.parseInt(end)) : startPeriod; + + CourseDto.TimeLocation timeLocation = CourseDto.TimeLocation.builder() + .day(day) + .startPeriod(startPeriod) + .endPeriod(endPeriod) + .location(location) + .build(); + + timeLocationList.add(timeLocation); + } else { + throw new AppException(ErrorCode.INVALID_TIME_LOCATION, "Invalid format: " + line); + } + } + + return timeLocationList; + } +} From 16b5cc44264f097d5aa237f996f684ab7cffa60b Mon Sep 17 00:00:00 2001 From: YunJaeHoon Date: Mon, 23 Dec 2024 21:35:33 +0900 Subject: [PATCH 2/2] =?UTF-8?q?FTR:=20=EA=B0=95=EC=9D=98=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=B0=8F=20=EC=9E=A5=EC=86=8C=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminController.java | 23 +++++++++++++ .../Devkor_project/entity/TimeLocation.java | 4 +-- .../repository/TimeLocationRepository.java | 7 ++++ .../Devkor_project/service/AdminService.java | 33 +++++++++++++++++++ .../util/TimeLocationParser.java | 18 +++++++--- 5 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/example/Devkor_project/repository/TimeLocationRepository.java diff --git a/src/main/java/com/example/Devkor_project/controller/AdminController.java b/src/main/java/com/example/Devkor_project/controller/AdminController.java index f7c4fd3..a6169f6 100644 --- a/src/main/java/com/example/Devkor_project/controller/AdminController.java +++ b/src/main/java/com/example/Devkor_project/controller/AdminController.java @@ -207,4 +207,27 @@ public ResponseEntity createTestAccount(@Valid @RequestBody ); } + /* 강의 시간 및 장소 정보 동기화 컨트톨러 */ + @PostMapping("/api/admin/course-time-location-synchronization") + @Operation(summary = "강의 시간 및 장소 정보 동기화") + @Parameters(value = { + @Parameter(in = ParameterIn.HEADER, name = "Authorization", description = "Bearer {access token}"), + }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200"), + }) + public ResponseEntity checkTimeLocationSynchronization(@Valid @RequestBody CrawlingDto.Synchronization dto) + { + adminService.checkTimeLocationSynchronization(dto); + + return ResponseEntity.status(HttpStatus.OK) + .body( + ResponseDto.Success.builder() + .message("강의 시간 및 장소 동기화를 성공적으로 수행하였습니다.") + .data(null) + .version(versionProvider.getVersion()) + .build() + ); + } + } diff --git a/src/main/java/com/example/Devkor_project/entity/TimeLocation.java b/src/main/java/com/example/Devkor_project/entity/TimeLocation.java index 2634b70..bf0a8c7 100644 --- a/src/main/java/com/example/Devkor_project/entity/TimeLocation.java +++ b/src/main/java/com/example/Devkor_project/entity/TimeLocation.java @@ -23,7 +23,7 @@ public class TimeLocation private Course course_id; @Column(nullable = true) private String day; - @Column(nullable = true) private int startPeriod; - @Column(nullable = true) private int endPeriod; + @Column(nullable = true) private Integer startPeriod; + @Column(nullable = true) private Integer endPeriod; @Column(nullable = true) private String location; } diff --git a/src/main/java/com/example/Devkor_project/repository/TimeLocationRepository.java b/src/main/java/com/example/Devkor_project/repository/TimeLocationRepository.java new file mode 100644 index 0000000..3c3e04d --- /dev/null +++ b/src/main/java/com/example/Devkor_project/repository/TimeLocationRepository.java @@ -0,0 +1,7 @@ +package com.example.Devkor_project.repository; + +import com.example.Devkor_project.entity.TimeLocation; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TimeLocationRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/Devkor_project/service/AdminService.java b/src/main/java/com/example/Devkor_project/service/AdminService.java index 0bc3de5..f8501f8 100644 --- a/src/main/java/com/example/Devkor_project/service/AdminService.java +++ b/src/main/java/com/example/Devkor_project/service/AdminService.java @@ -21,9 +21,12 @@ import org.springframework.web.reactive.function.client.WebClient; import java.security.Principal; +import java.sql.Time; import java.time.LocalDate; import java.util.*; +import static com.example.Devkor_project.util.TimeLocationParser.parseTimeLocation; + @Service @RequiredArgsConstructor @Slf4j @@ -37,6 +40,7 @@ public class AdminService private final CommentReportRepository commentReportRepository; private final ProfileRepository profileRepository; private final TrafficRepository trafficRepository; + private final TimeLocationRepository timeLocationRepository; private final CourseService courseService; @@ -399,4 +403,33 @@ public void createTestAccount(ProfileDto.CreateTestAccount dto) // 해당 엔티티를 데이터베이스에 저장 profileRepository.save(profile); } + + /* 강의 시간 및 장소 동기화 서비스 */ + @Transactional + public void checkTimeLocationSynchronization(CrawlingDto.Synchronization dto) + { + // 현재 데이터베이스에 존재하는 해당 연도와 학기의 모든 강의 정보 + List allCourseInDatabase = courseRepository.findCourseByYearAndSemester(dto.getYear(), dto.getSemester()); + + for(Course course:allCourseInDatabase) + { + // 해당 강의의 time_location 정보를 CourseDto.TimeLocation 리스트로 변환 + List timeLocations = parseTimeLocation(course.getTime_location()); + + // TimeLocation 엔티티 추가 + if (timeLocations != null && !timeLocations.isEmpty()) { + for (CourseDto.TimeLocation timeLocationDto : timeLocations) { + TimeLocation timeLocation = TimeLocation.builder() + .course_id(course) + .day(timeLocationDto.getDay()) + .startPeriod(timeLocationDto.getStartPeriod()) + .endPeriod(timeLocationDto.getEndPeriod()) + .location(timeLocationDto.getLocation()) + .build(); + + timeLocationRepository.save(timeLocation); + } + } + } + } } diff --git a/src/main/java/com/example/Devkor_project/util/TimeLocationParser.java b/src/main/java/com/example/Devkor_project/util/TimeLocationParser.java index f375a1b..87c1e82 100644 --- a/src/main/java/com/example/Devkor_project/util/TimeLocationParser.java +++ b/src/main/java/com/example/Devkor_project/util/TimeLocationParser.java @@ -40,19 +40,27 @@ public static List parseTimeLocation(String timeLocation String[] lines = timeLocations.split("\\n"); // Regular expression to extract start and end periods - Pattern pattern = Pattern.compile("([가-힣])\\((\\d+)(?:-(\\d+))?\\)(?:\\s(.+))?|([가-힣])"); + Pattern pattern = Pattern.compile("([가-힣])\\((\\d+)(?:-(\\d+))?\\)(?:\\s(.+))?|([가-힣])\\s?(\\S.*)?|\\((\\d+)-(\\d+)\\)"); for (String line : lines) { Matcher matcher = pattern.matcher(line.trim()); if (matcher.find()) { - String day = matcher.group(1) != null ? matcher.group(1) : matcher.group(5); + String day = matcher.group(1) != null ? matcher.group(1) : matcher.group(5); // 요일 추출 String start = matcher.group(2); String end = matcher.group(3); - String location = matcher.group(4); + String location = matcher.group(4) != null ? matcher.group(4) : matcher.group(6); // 강의실 추출 + + // "요일"과 "시작교시-끝교시"가 모두 없는 경우 처리 (예: (8-10) 형태) + if (matcher.group(7) != null) { + start = matcher.group(7); + end = matcher.group(8); + day = null; + location = null; + } Integer startPeriod = (start != null) ? Integer.parseInt(start) : null; - Integer endPeriod = (end != null) ? Integer.valueOf(Integer.parseInt(end)) : startPeriod; + Integer endPeriod = (start != null && end != null) ? Integer.valueOf(Integer.parseInt(end)) : startPeriod; CourseDto.TimeLocation timeLocation = CourseDto.TimeLocation.builder() .day(day) @@ -63,7 +71,7 @@ public static List parseTimeLocation(String timeLocation timeLocationList.add(timeLocation); } else { - throw new AppException(ErrorCode.INVALID_TIME_LOCATION, "Invalid format: " + line); + throw new AppException(ErrorCode.INVALID_TIME_LOCATION, "Invalid format: " + timeLocations); } }