package com.njuzr.eaibackend.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.njuzr.eaibackend.dto.course.CourseDTO; import com.njuzr.eaibackend.dto.course.CourseQueryDTO; import com.njuzr.eaibackend.dto.course.CourseUpdateDTO; import com.njuzr.eaibackend.dto.course.EnrollDTO; import com.njuzr.eaibackend.enums.AssignmentCompletionStatus; import com.njuzr.eaibackend.enums.ClassStudentState; import com.njuzr.eaibackend.enums.Role; import com.njuzr.eaibackend.exception.MyException; import com.njuzr.eaibackend.mapper.*; import com.njuzr.eaibackend.po.*; import com.njuzr.eaibackend.po.Class; import com.njuzr.eaibackend.service.CourseService; import com.njuzr.eaibackend.utils.ConvertUtil; import com.njuzr.eaibackend.utils.ModelMapperUtil; import com.njuzr.eaibackend.utils.PageMapperUtil; import com.njuzr.eaibackend.vo.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; /** * @author: Leonezhurui * @Date: 2024/3/5 - 22:09 * @Package: EAI-Backend */ @Slf4j @Service public class CourseServiceImpl implements CourseService { private final CourseMapper courseMapper; private final UserMapper userMapper; private final CourseStudentMapper courseStudentMapper; private final AssignmentMapper assignmentMapper; private final StudentAssignmentMapper studentAssignmentMapper; private final ClassStudentMapper classStudentMapper; private final ClassMapper classMapper; @Autowired public CourseServiceImpl(CourseMapper courseMapper, UserMapper userMapper, CourseStudentMapper courseStudentMapper, AssignmentMapper assignmentMapper, StudentAssignmentMapper studentAssignmentMapper, ClassStudentMapper classStudentMapper, ClassMapper classMapper) { this.courseMapper = courseMapper; this.userMapper = userMapper; this.courseStudentMapper = courseStudentMapper; this.assignmentMapper = assignmentMapper; this.studentAssignmentMapper = studentAssignmentMapper; this.classStudentMapper = classStudentMapper; this.classMapper = classMapper; } @Override @CacheEvict(value = "coursesPages", allEntries = true) public CourseVO createCourse(CourseDTO courseDTO) { // 类型转换 Course targetCourse = convertToPO(courseDTO); targetCourse.setCreateTime(new Date()); // 插入数据库 int status = courseMapper.insert(targetCourse); if (status == 0) { log.error("创建课程失败,数据库插入错误!"); throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"创建课程错误"); } return convertToVO(targetCourse); } @Override // @Cacheable(value = "coursePages", key = "#page.getCurrent() + '_' + #page.getSize() + '_' + #courseQueryDTO.isEmpty()", unless = "#page.getSize() <= 1 || !#courseQueryDTO.isEmpty()") public IPage findCoursesPage(Page page, CourseQueryDTO courseQueryDTO) { log.info("课程信息从数据库查询: {}", courseQueryDTO.toString()); QueryWrapper wrapper = getCourseQueryWrapper(courseQueryDTO); IPage courses = courseMapper.selectPage(page, wrapper); return PageMapperUtil.convert(courses, this::convertToVO); } /** * 设置查询条件 * @param courseQueryDTO * @return */ private static QueryWrapper getCourseQueryWrapper(CourseQueryDTO courseQueryDTO) { QueryWrapper wrapper = new QueryWrapper<>(); if (courseQueryDTO.getCourseId() != null) { // courseId直接判断是否相等 wrapper.eq("course_id", courseQueryDTO.getCourseId()); } // 兼容两种id的查询方式 if(courseQueryDTO.getTeacherId() != null && courseQueryDTO.getTeacherPid() != null){ wrapper.and(wq -> wq .eq(courseQueryDTO.getTeacherPid() != null, "teacher_portal_id", courseQueryDTO.getTeacherPid()) .or() .eq(courseQueryDTO.getTeacherId() != null, "teacher_ids", courseQueryDTO.getTeacherId().toString()) ); } else { if (courseQueryDTO.getTeacherPid() != null ) { // courseId直接判断是否相等 System.out.println(courseQueryDTO.getTeacherPid()); wrapper.eq("teacher_portal_id", courseQueryDTO.getTeacherPid()); } if(courseQueryDTO.getTeacherId() != null){ System.out.println(courseQueryDTO.getTeacherPid()); wrapper.eq("teacher_ids", courseQueryDTO.getTeacherId()); } } // if (courseQueryDTO.getTeacherId() != null) { // 模糊匹配teacherIds中是否包含这个字段 // wrapper.apply("FIND_IN_SET({0}, teacher_ids) > 0", courseQueryDTO.getTeacherId()); // // wrapper.like("teacher_ids", "%" + courseQueryDTO.getTeacherId() + "%"); // 模糊匹配 // } if (courseQueryDTO.getCourseName() != null) { // 模糊匹配课程名字 wrapper.like("course_name", courseQueryDTO.getCourseName()); } if (courseQueryDTO.getStudentId() != null) { // 联表查询学生所选课程 // 使用安全的参数绑定方式 wrapper.apply("course_id IN (SELECT course_id FROM course_student WHERE student_id = {0})", courseQueryDTO.getStudentId()); } if (courseQueryDTO.getSemester() != null && !courseQueryDTO.getSemester().trim().isEmpty()) { // 课程时间过滤,转换成具体日期 String semester = courseQueryDTO.getSemester(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); LocalDate startDate, endDate; // 解析输入字符串获取学年和学期 try { String[] parts = semester.split("学年第"); String[] years = parts[0].split("-"); int yearStart = Integer.parseInt(years[0]); int semesterType = parts[1].startsWith("一") ? 1 : 2; // 根据学期计算开始和结束日期 if (semesterType == 1) { startDate = LocalDate.of(yearStart, 9, 1); endDate = LocalDate.of(yearStart + 1, 3, 1); } else { startDate = LocalDate.of(yearStart + 1, 3, 1); endDate = LocalDate.of(yearStart + 1, 9, 1); } } catch (Exception e) { log.error(e.getMessage()); throw MyException.create(HttpStatus.BAD_REQUEST, "学期字符串解析错误"); } wrapper.ge("start_time", startDate.format(formatter)) .le("end_time", endDate.format(formatter)); } return wrapper; } /** * 更新课程信息 * @param courseUpdateDTO */ @Override @CacheEvict(value = "coursePages", allEntries = true) public CourseVO updateCourse(Long userId, Long courseId, CourseUpdateDTO courseUpdateDTO) { User targetUser = userMapper.selectById(userId); Course targetCourse = courseMapper.selectById(courseId); if (targetCourse == null) throw MyException.create(HttpStatus.BAD_REQUEST, "课程不存在"); // 兼容两种id的权限判断方式 if (targetUser.getRole() != Role.ADMIN && !Objects.equals(targetCourse.getTeacherPid(), targetUser.getPid()) && !targetCourse.getTeacherIds().contains(String.valueOf(userId))) throw MyException.create(HttpStatus.BAD_REQUEST, "无权限更新"); Course opeCourse = convertToPO(courseUpdateDTO); opeCourse.setCourseId(courseId); try { int code = courseMapper.updateById(opeCourse); if (code == 0) { throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新课程失败"); } } catch (Exception e) { log.error(e.getMessage()); throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新课程失败"); } return convertToVO(courseMapper.selectById(courseId)); } /** * 门户更新课程信息 * @param userPid * @param courseId * @param courseUpdateDTO * @return */ @Override @CacheEvict(value = "coursePages", allEntries = true) public CourseVO updateCourseByPortal(Long userId, Long userPid, Long courseId, CourseUpdateDTO courseUpdateDTO) { User targetUser; if (userPid == null){ targetUser = userMapper.selectById(userId); } else { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("pid", userPid); targetUser = userMapper.selectOne(queryWrapper); } Course targetCourse = courseMapper.selectById(courseId); if (targetCourse == null) throw MyException.create(HttpStatus.BAD_REQUEST, "课程不存在"); if (targetUser.getRole() != Role.ADMIN) { List teacherIds = Collections.singletonList(Optional.ofNullable(targetCourse.getTeacherIds()) .orElse(Collections.emptyList().toString())); String userIdStr = String.valueOf(targetUser.getId()); boolean hasTeacherId = teacherIds.contains(userIdStr); boolean pidMatches = Objects.equals(targetCourse.getTeacherPid(), targetUser.getPid()); if (!hasTeacherId && !pidMatches) { throw MyException.create(HttpStatus.BAD_REQUEST, "无权限更新"); } } Course opeCourse = convertToPO(courseUpdateDTO); opeCourse.setCourseId(courseId); try { int code = courseMapper.updateById(opeCourse); if (code == 0) { throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新课程失败"); } } catch (Exception e) { log.error(e.getMessage()); throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新课程失败"); } return convertToVO(courseMapper.selectById(courseId)); } /** * 删除课程 * @param user * @param courseId */ @Override @Transactional @CacheEvict(value = "coursePages", allEntries = true) public void deleteById(MyUserDetails user, Long courseId) { Course course = courseMapper.selectById(courseId); if (course == null) { throw MyException.create(HttpStatus.BAD_REQUEST, "课程不存在"); } if (user.getRole() != Role.ADMIN) { List teacherIds = Collections.singletonList(Optional.ofNullable(course.getTeacherIds()) .orElse(Collections.emptyList().toString())); String userIdStr = String.valueOf(user.getId()); boolean hasTeacherId = teacherIds.contains(userIdStr); boolean pidMatches = Objects.equals(course.getTeacherPid(), user.getPid()); if (!hasTeacherId && !pidMatches) { throw MyException.create(HttpStatus.BAD_REQUEST, "无权限创建"); } } try { // 首先找到选了该课程的所有学生 List students = courseStudentMapper.selectStudentsByCourseId(courseId); // 所有学生执行退课操作 for(User student : students) { dropCourse(student.getId(), courseId); } // 删除该课程下的所有assignment信息 QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("course_id", courseId); List assignments = assignmentMapper.selectList(wrapper); for(Assignment assignment : assignments) { assignmentMapper.deleteById(assignment.getAssignmentId()); } // 删除课程信息 int code = courseMapper.deleteById(courseId); if (code == 0) { throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "删除课程失败"); } } catch (Exception e) { log.error(e.getMessage()); throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "删除课程失败"); } } /** * 学生选课。给course_student表添加记录。 * @param enrollDTO */ /** @Override @Transactional public void enroll(EnrollDTO enrollDTO) { Course target = courseMapper.selectById(enrollDTO.getCourseId()); if (target == null) throw MyException.create(HttpStatus.BAD_REQUEST, "课程不存在"); if (!target.getEnrollCode().equals(enrollDTO.getEnrollCode())) throw MyException.create(HttpStatus.BAD_REQUEST, "选课码错误"); Enrollment enrollment = ModelMapperUtil.map(enrollDTO, Enrollment.class); courseStudentMapper.insert(enrollment); Long studentId = enrollDTO.getStudentId(); Long courseId = enrollDTO.getCourseId(); ClassStudent classStudent = classStudentMapper.findClassStudentByStudentAndCourse(studentId, courseId); if (classStudent == null) { log.warn("未找到学生 {} 在课程 {} 中的班级记录", studentId, courseId); throw MyException.create(HttpStatus.BAD_REQUEST, "老师未将学生添加到课程班级,无法选课"); } int result = classStudentMapper.updateStateById(classStudent.getId(), 1); if (result > 0) { log.info("成功更新学生 {} 在课程 {} 中的加入状态", studentId, courseId); return; } log.error("更新学生 {} 在课程 {} 中的加入状态失败", studentId, courseId); throw MyException.create(HttpStatus.BAD_REQUEST, "失败"); } */ /** * 获取某课程的所有选课学生信息 * @param page * @param teacherId * @param courseId * @return */ @Override public IPage getEnrollmentsByCourse(Page page, Long teacherId, Long courseId) { Course target = courseMapper.selectById(courseId); if (target == null) throw MyException.create(HttpStatus.BAD_REQUEST, "课程不存在"); if (!target.getTeacherIds().contains(String.valueOf(teacherId))) throw MyException.create(HttpStatus.BAD_REQUEST, "无权限查看"); Page students = courseStudentMapper.selectStudentsByCourseId(page, courseId); return PageMapperUtil.convert(students, student -> ModelMapperUtil.map(student, UserVO.class)); } @Override public IPage getEnrollmentsByStudent(Page page, Long studentId) { User student = userMapper.selectById(studentId); if (student == null) throw MyException.create(HttpStatus.BAD_REQUEST, "用户不存在"); Page courses = courseStudentMapper.selectCoursesByStudentId(page, studentId); return PageMapperUtil.convert(courses, this::convertToVO); } /** * 学生退课 * @param studentId * @param courseId */ @Override @Transactional public void dropCourse(Long studentId, Long courseId) { // 首先找到该课程的所有作业信息 QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("course_id", courseId); List assignments = assignmentMapper.selectList(wrapper); // 删除学生的所有作业记录 for(Assignment assignment : assignments) { // 修改对应参与数量和批改数量 Engagement engage = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignment.getAssignmentId()); if(engage.getStatus() == AssignmentCompletionStatus.CORRECTED || engage.getStatus() == AssignmentCompletionStatus.PUBLISHED){ assignment.setCorrectNumber(assignment.getCorrectNumber() - 1); } assignment.setEngageNumber(assignment.getEngageNumber() - 1); assignmentMapper.updateById(assignment); studentAssignmentMapper.delete(studentId, assignment.getAssignmentId()); } // 学生退课 int code = courseStudentMapper.dropCourse(studentId, courseId); if (code == 0) { throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "退课失败"); } } /** * 将DTO转换成PO * @param courseDTO * @return */ private Course convertToPO(CourseDTO courseDTO) { Course course = new Course(); BeanUtils.copyProperties(courseDTO, course); if(courseDTO.getTeacherIds() != null){ course.setTeacherIds(ConvertUtil.listToString(courseDTO.getTeacherIds())); } if (courseDTO.getCourseImages() != null) course.setCourseImages(ConvertUtil.listToString(courseDTO.getCourseImages())); return course; } private Course convertToPO(CourseUpdateDTO updateDTO) { Course course = new Course(); BeanUtils.copyProperties(updateDTO, course); if (updateDTO.getTeacherIds() != null) course.setTeacherIds(ConvertUtil.listToString(updateDTO.getTeacherIds())); if (updateDTO.getCourseImages() != null) course.setCourseImages(ConvertUtil.listToString(updateDTO.getCourseImages())); return course; } /** * 通过班级码选课 * @param enrollDTO 包含学生ID和课程ID */ @Override @Transactional public void enrollByClassCode(EnrollDTO enrollDTO) { // 获取班级码 String classCode = enrollDTO.getEnrollCode(); Long studentId = enrollDTO.getStudentId(); Long courseId = enrollDTO.getCourseId(); // 根据班级码查询班级信息 QueryWrapper classWrapper = new QueryWrapper<>(); classWrapper.eq("class_code", classCode); Class targetClass = classMapper.selectOne(classWrapper); if (targetClass == null) { throw MyException.create(HttpStatus.BAD_REQUEST, "班级码不存在"); } // 验证班级是否属于指定课程 if (!targetClass.getCourseId().equals(courseId)) { throw MyException.create(HttpStatus.BAD_REQUEST, "班级码与课程不匹配"); } Long classId = targetClass.getClassId(); // 检查学生是否已经加入了该课程下的其他班级 QueryWrapper csWrapper = new QueryWrapper<>(); csWrapper.inSql("class_id", "SELECT class_id FROM class WHERE course_id = " + courseId); csWrapper.inSql("official_number", "SELECT official_number FROM users WHERE id = " + studentId); ClassStudent existingClassStudent = classStudentMapper.selectOne(csWrapper); if (existingClassStudent != null) { throw MyException.create(HttpStatus.BAD_REQUEST, "学生已经加入了该课程下的一个班级"); } // 检查学生是否已经选过该课程 QueryWrapper enrollmentWrapper = new QueryWrapper<>(); enrollmentWrapper.eq("student_id", studentId); enrollmentWrapper.eq("course_id", courseId); Enrollment existingEnrollment = courseStudentMapper.selectOne(enrollmentWrapper); if (existingEnrollment == null) { // 创建新的选课记录 Enrollment enrollment = new Enrollment(); enrollment.setStudentId(studentId); enrollment.setCourseId(courseId); int status = courseStudentMapper.insert(enrollment); if (status == 0) { log.error("选课失败,数据库插入错误!"); throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "选课错误"); } } // 将学生加入班级 ClassStudent classStudent = new ClassStudent(); classStudent.setClassId(classId); classStudent.setState(ClassStudentState.JOINED); // 已加入状态 // 获取学生信息 User student = userMapper.selectById(studentId); if (student != null) { classStudent.setOfficialNumber(student.getOfficialNumber()); classStudent.setStuName(student.getName()); } int csStatus = classStudentMapper.insert(classStudent); if (csStatus == 0) { log.error("加入班级失败,数据库插入错误!"); throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "加入班级错误"); } // 更新班级人数 classMapper.increaseStuNumber(classId, 1); } /** * 将PO转换成VO * @param course * @return */ private CourseVO convertToVO(Course course) { CourseVO res = new CourseVO(); BeanUtils.copyProperties(course, res); if(course.getCourseImages() != null) res.setCourseImages(ConvertUtil.stringToList(course.getCourseImages(), String::valueOf)); // 组装教师名字 List teacherNames = new ArrayList<>(); // 1.getTeacherIds if(StringUtils.isNotBlank(course.getTeacherIds())){ res.setTeacherIds(ConvertUtil.stringToList(course.getTeacherIds(), Long::valueOf)); for (Long teacherId : res.getTeacherIds()) { teacherNames.add(userMapper.selectNameById(teacherId)); } if (course.getCourseImages() != null) { res.setCourseImages(ConvertUtil.stringToList(course.getCourseImages(), String::valueOf)); } } // 2. getTeacherPid if(StringUtils.isBlank(course.getTeacherIds()) && StringUtils.isNotBlank(course.getTeacherPid())){ res.setTeacherPid(Long.valueOf(course.getTeacherPid())); teacherNames.add(userMapper.selectNameByPid(Long.valueOf(course.getTeacherPid()))); } res.setTeacherNames(teacherNames); return res; } @Override public StudentCourseHomeworkVO getStudentCourseAssignments(Long studentId, Long courseId) { // 1. 验证学生是否存在 User student = userMapper.selectById(studentId); if (student == null) { throw new RuntimeException("学生不存在"); } // 2. 验证课程是否存在 Course course = courseMapper.selectById(courseId); if (course == null) { throw new RuntimeException("课程不存在"); } // 3. 查询该课程下的所有作业 List courseAssignments = assignmentMapper.selectByCourseId(courseId); if (courseAssignments.isEmpty()) { StudentCourseHomeworkVO result = new StudentCourseHomeworkVO(); result.setStudentId(studentId); result.setOfficialNumber(student.getOfficialNumber()); result.setStuName(student.getName()); return result; } // 4. 查询学生在该课程下的所有作业提交情况 List assignmentIds = courseAssignments.stream() .map(Assignment::getAssignmentId) .collect(Collectors.toList()); List studentAssignments = studentAssignmentMapper.findByStudentIdAndAssignmentIds(studentId, assignmentIds); // 5. 构建作业ID到提交情况的映射 Map assignmentMap = studentAssignments.stream() .collect(Collectors.toMap( StudentAssignment::getAssignmentId, item -> item )); // 6. 组装作业列表 List assignmentVOs = courseAssignments.stream() .map(assignment -> { StudentCourseAssignmentVO vo = new StudentCourseAssignmentVO(); vo.setAssignmentId(assignment.getAssignmentId()); vo.setAssignmentName(assignment.getAssignmentName()); // 处理作业状态和分数 StudentAssignment studentAssignment = assignmentMap.get(assignment.getAssignmentId()); if (studentAssignment == null) { vo.setStatus(AssignmentCompletionStatus.NOT_SUBMITTED); vo.setScore(0); } else { vo.setStatus(AssignmentCompletionStatus.fromValue(studentAssignment.getStatus())); vo.setScore(studentAssignment.getScore() != null ? studentAssignment.getScore().intValue() : 0); } return vo; }) .collect(Collectors.toList()); // 7. 组装返回结果 StudentCourseHomeworkVO result = new StudentCourseHomeworkVO(); result.setStudentId(studentId); result.setOfficialNumber(student.getOfficialNumber()); result.setStuName(student.getName()); result.setAssignments(assignmentVOs); return result; } }