修改课表模块

This commit is contained in:
yoyuzh
2026-03-14 22:55:07 +08:00
parent 6cff15f8dc
commit 033ac5bee4
22 changed files with 2730 additions and 115 deletions

View File

@@ -35,6 +35,12 @@ public class User {
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
@Column(name = "last_school_student_id", length = 64)
private String lastSchoolStudentId;
@Column(name = "last_school_semester", length = 64)
private String lastSchoolSemester;
@PrePersist
public void prePersist() {
if (createdAt == null) {
@@ -81,4 +87,20 @@ public class User {
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public String getLastSchoolStudentId() {
return lastSchoolStudentId;
}
public void setLastSchoolStudentId(String lastSchoolStudentId) {
this.lastSchoolStudentId = lastSchoolStudentId;
}
public String getLastSchoolSemester() {
return lastSchoolSemester;
}
public void setLastSchoolSemester(String lastSchoolSemester) {
this.lastSchoolSemester = lastSchoolSemester;
}
}

View File

@@ -3,9 +3,12 @@ package com.yoyuzh.cqu;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface CourseRepository extends JpaRepository<Course, Long> {
List<Course> findByUserIdAndStudentIdAndSemesterOrderByDayOfWeekAscStartTimeAsc(Long userId, String studentId, String semester);
Optional<Course> findTopByUserIdOrderByCreatedAtDesc(Long userId);
void deleteByUserIdAndStudentIdAndSemester(Long userId, String studentId, String semester);
}

View File

@@ -26,16 +26,24 @@ public class CquController {
@GetMapping("/schedule")
public ApiResponse<List<CourseResponse>> schedule(@AuthenticationPrincipal UserDetails userDetails,
@RequestParam String semester,
@RequestParam String studentId) {
return ApiResponse.success(cquDataService.getSchedule(resolveUser(userDetails), semester, studentId));
@RequestParam String studentId,
@RequestParam(defaultValue = "false") boolean refresh) {
return ApiResponse.success(cquDataService.getSchedule(resolveUser(userDetails), semester, studentId, refresh));
}
@Operation(summary = "获取成绩")
@GetMapping("/grades")
public ApiResponse<List<GradeResponse>> grades(@AuthenticationPrincipal UserDetails userDetails,
@RequestParam String semester,
@RequestParam String studentId) {
return ApiResponse.success(cquDataService.getGrades(resolveUser(userDetails), semester, studentId));
@RequestParam String studentId,
@RequestParam(defaultValue = "false") boolean refresh) {
return ApiResponse.success(cquDataService.getGrades(resolveUser(userDetails), semester, studentId, refresh));
}
@Operation(summary = "获取最近一次教务数据")
@GetMapping("/latest")
public ApiResponse<LatestSchoolDataResponse> latest(@AuthenticationPrincipal UserDetails userDetails) {
return ApiResponse.success(cquDataService.getLatest(resolveUser(userDetails)));
}
private User resolveUser(UserDetails userDetails) {

View File

@@ -1,5 +1,6 @@
package com.yoyuzh.cqu;
import com.yoyuzh.auth.UserRepository;
import com.yoyuzh.auth.User;
import com.yoyuzh.common.BusinessException;
import com.yoyuzh.common.ErrorCode;
@@ -10,6 +11,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
@RequiredArgsConstructor
@@ -18,47 +20,82 @@ public class CquDataService {
private final CquApiClient cquApiClient;
private final CourseRepository courseRepository;
private final GradeRepository gradeRepository;
private final UserRepository userRepository;
private final CquApiProperties cquApiProperties;
@Transactional
public List<CourseResponse> getSchedule(User user, String semester, String studentId) {
return getSchedule(user, semester, studentId, false);
}
@Transactional
public List<CourseResponse> getSchedule(User user, String semester, String studentId, boolean refresh) {
requireLoginIfNecessary(user);
if (user != null && !refresh) {
List<CourseResponse> stored = readSavedSchedule(user.getId(), studentId, semester);
if (!stored.isEmpty()) {
rememberLastSchoolQuery(user, studentId, semester);
return stored;
}
}
List<CourseResponse> responses = cquApiClient.fetchSchedule(semester, studentId).stream()
.map(this::toCourseResponse)
.toList();
if (user != null) {
saveCourses(user, semester, studentId, responses);
return courseRepository.findByUserIdAndStudentIdAndSemesterOrderByDayOfWeekAscStartTimeAsc(
user.getId(), studentId, semester)
.stream()
.map(item -> new CourseResponse(
item.getCourseName(),
item.getTeacher(),
item.getClassroom(),
item.getDayOfWeek(),
item.getStartTime(),
item.getEndTime()))
.toList();
rememberLastSchoolQuery(user, studentId, semester);
return readSavedSchedule(user.getId(), studentId, semester);
}
return responses;
}
@Transactional
public List<GradeResponse> getGrades(User user, String semester, String studentId) {
return getGrades(user, semester, studentId, false);
}
@Transactional
public List<GradeResponse> getGrades(User user, String semester, String studentId, boolean refresh) {
requireLoginIfNecessary(user);
if (user != null && !refresh
&& gradeRepository.existsByUserIdAndStudentIdAndSemester(user.getId(), studentId, semester)) {
rememberLastSchoolQuery(user, studentId, semester);
return readSavedGrades(user.getId(), studentId);
}
List<GradeResponse> responses = cquApiClient.fetchGrades(semester, studentId).stream()
.map(this::toGradeResponse)
.toList();
if (user != null) {
saveGrades(user, semester, studentId, responses);
return gradeRepository.findByUserIdAndStudentIdOrderBySemesterAscGradeDesc(user.getId(), studentId)
.stream()
.map(item -> new GradeResponse(item.getCourseName(), item.getGrade(), item.getSemester()))
.toList();
rememberLastSchoolQuery(user, studentId, semester);
return readSavedGrades(user.getId(), studentId);
}
return responses;
}
@Transactional
public LatestSchoolDataResponse getLatest(User user) {
requireLoginIfNecessary(user);
if (user == null) {
return null;
}
QueryContext context = resolveLatestContext(user);
if (context == null) {
return null;
}
List<CourseResponse> schedule = readSavedSchedule(user.getId(), context.studentId(), context.semester());
List<GradeResponse> grades = readSavedGrades(user.getId(), context.studentId());
if (schedule.isEmpty() && grades.isEmpty()) {
return null;
}
return new LatestSchoolDataResponse(context.studentId(), context.semester(), schedule, grades);
}
private void requireLoginIfNecessary(User user) {
if (cquApiProperties.isRequireLogin() && user == null) {
throw new BusinessException(ErrorCode.NOT_LOGGED_IN, "该接口需要登录后访问");
@@ -97,6 +134,77 @@ public class CquDataService {
}).toList());
}
private List<CourseResponse> readSavedSchedule(Long userId, String studentId, String semester) {
return courseRepository.findByUserIdAndStudentIdAndSemesterOrderByDayOfWeekAscStartTimeAsc(
userId, studentId, semester)
.stream()
.map(item -> new CourseResponse(
item.getCourseName(),
item.getTeacher(),
item.getClassroom(),
item.getDayOfWeek(),
item.getStartTime(),
item.getEndTime()))
.toList();
}
private List<GradeResponse> readSavedGrades(Long userId, String studentId) {
return gradeRepository.findByUserIdAndStudentIdOrderBySemesterAscGradeDesc(userId, studentId)
.stream()
.map(item -> new GradeResponse(item.getCourseName(), item.getGrade(), item.getSemester()))
.toList();
}
private void rememberLastSchoolQuery(User user, String studentId, String semester) {
boolean changed = false;
if (!semester.equals(user.getLastSchoolSemester())) {
user.setLastSchoolSemester(semester);
changed = true;
}
if (!studentId.equals(user.getLastSchoolStudentId())) {
user.setLastSchoolStudentId(studentId);
changed = true;
}
if (changed) {
userRepository.save(user);
}
}
private QueryContext resolveLatestContext(User user) {
if (hasText(user.getLastSchoolStudentId()) && hasText(user.getLastSchoolSemester())) {
return new QueryContext(user.getLastSchoolStudentId(), user.getLastSchoolSemester());
}
Optional<Course> latestCourse = courseRepository.findTopByUserIdOrderByCreatedAtDesc(user.getId());
Optional<Grade> latestGrade = gradeRepository.findTopByUserIdOrderByCreatedAtDesc(user.getId());
if (latestCourse.isEmpty() && latestGrade.isEmpty()) {
return null;
}
QueryContext context;
if (latestGrade.isEmpty()) {
context = new QueryContext(latestCourse.get().getStudentId(), latestCourse.get().getSemester());
} else if (latestCourse.isEmpty()) {
context = new QueryContext(latestGrade.get().getStudentId(), latestGrade.get().getSemester());
} else if (latestCourse.get().getCreatedAt().isAfter(latestGrade.get().getCreatedAt())) {
context = new QueryContext(latestCourse.get().getStudentId(), latestCourse.get().getSemester());
} else {
context = new QueryContext(latestGrade.get().getStudentId(), latestGrade.get().getSemester());
}
if (hasText(context.studentId()) && hasText(context.semester())) {
user.setLastSchoolStudentId(context.studentId());
user.setLastSchoolSemester(context.semester());
userRepository.save(user);
return context;
}
return null;
}
private boolean hasText(String value) {
return value != null && !value.isBlank();
}
private CourseResponse toCourseResponse(Map<String, Object> source) {
return new CourseResponse(
stringValue(source, "courseName"),
@@ -128,4 +236,7 @@ public class CquDataService {
Object value = source.get(key);
return value == null ? null : Double.parseDouble(value.toString());
}
private record QueryContext(String studentId, String semester) {
}
}

View File

@@ -3,9 +3,14 @@ package com.yoyuzh.cqu;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface GradeRepository extends JpaRepository<Grade, Long> {
List<Grade> findByUserIdAndStudentIdOrderBySemesterAscGradeDesc(Long userId, String studentId);
boolean existsByUserIdAndStudentIdAndSemester(Long userId, String studentId, String semester);
Optional<Grade> findTopByUserIdOrderByCreatedAtDesc(Long userId);
void deleteByUserIdAndStudentIdAndSemester(Long userId, String studentId, String semester);
}

View File

@@ -0,0 +1,11 @@
package com.yoyuzh.cqu;
import java.util.List;
public record LatestSchoolDataResponse(
String studentId,
String semester,
List<CourseResponse> schedule,
List<GradeResponse> grades
) {
}