package nju.seec.helper.service.impl; import com.google.common.collect.ImmutableSet; import lombok.AllArgsConstructor; import lombok.SneakyThrows; import nju.seec.helper.dao.ChooseDAO; import nju.seec.helper.dao.CourseDAO; import nju.seec.helper.dao.SlideDAO; import nju.seec.helper.dto.LoginUser; import nju.seec.helper.dto.SlideDTO; import nju.seec.helper.dto.SlideFileDTO; import nju.seec.helper.dto.SlideStateDTO; import nju.seec.helper.entity.Course; import nju.seec.helper.entity.Slide; import nju.seec.helper.service.MessageService; import nju.seec.helper.service.QuizService; import nju.seec.helper.service.SlideService; import nju.seec.helper.service.util.AuthUtils; import nju.seec.helper.util.RedisCacheUtils; import nju.seec.helper.util.Consts; import nju.seec.helper.util.FileUtils; import nju.seec.helper.util.enums.ExceptionType; import nju.seec.helper.util.enums.MessageType; import nju.seec.helper.util.enums.SlideState; import nju.seec.helper.util.exception.HelperException; import nju.seec.helper.vo.SlideVO; import org.apache.pdfbox.pdmodel.PDDocument; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; /** * @author cst */ @Service public class SlideServiceImpl implements SlideService { private static final String SLIDE_STORE_DIR = "slide"; private static final long SLIDE_URL_LIVING_SECONDS = 20 * 60; private final CourseDAO courseDAO; private final SlideDAO slideDAO; private final ChooseDAO chooseDAO; private final QuizService quizService; private final FileUtils fileUtils; private final RedisCacheUtils cacheUtils; private final MessageService messageService; @Autowired public SlideServiceImpl(CourseDAO courseDAO, SlideDAO slideDAO, ChooseDAO chooseDAO, FileUtils fileUtils, RedisCacheUtils cacheUtils, MessageService messageService, QuizService quizService) { this.courseDAO = courseDAO; this.slideDAO = slideDAO; this.chooseDAO = chooseDAO; this.fileUtils = fileUtils; this.cacheUtils = cacheUtils; this.messageService = messageService; this.quizService = quizService; } @Transactional(rollbackFor = Exception.class) @SneakyThrows @Override public SlideVO createSlide(LoginUser teacher, SlideDTO slideDTO) { Course course = courseDAO.findCourseById(slideDTO.getCourseId()); AuthUtils.checkDataAuth(teacher.getId(), course.getTeacherId(), "您无权创建该课程的课件"); checkSameName(0L, slideDTO.getCourseId(), slideDTO.getName()); Slide slide = new Slide() .setCourseId(slideDTO.getCourseId()) .setCourseName(course.getName()) .setTeacherId(teacher.getId()) .setTeacherName(teacher.getName()) .setName(slideDTO.getName()) .setState(slideDTO.getState()); MultipartFile file = slideDTO.getFile(); FileInfo fileInfo = getFileInfo(file); slide.setPages(fileInfo.pages); slide.setObjectName("temp"); slide = slideDAO.save(slide); String objectName = getObjectName(slide.getId(), slide.getName(), fileInfo.suffix); slide.setObjectName(objectName); slide = slideDAO.save(slide); fileUtils.upload(objectName, file.getInputStream()); return new SlideVO(slide); } @Transactional(rollbackFor = Exception.class) @Override public SlideVO modifySlide(LoginUser user, SlideDTO slideDTO) { Slide slide = slideDAO.findSlideById(slideDTO.getId()); AuthUtils.checkDataAuth(user.getId(), slide.getTeacherId(), "您无权修改该课件"); checkSameName(slide.getId(), slide.getCourseId(), slideDTO.getName()); slide.setName(slideDTO.getName()); String oldObjectName = slide.getObjectName(); String newObjectName = getObjectName(slide.getId(), slide.getName(), oldObjectName.substring(oldObjectName.lastIndexOf('.') + 1)); slide.setObjectName(newObjectName); slide = slideDAO.save(slide); if (!oldObjectName.equals(newObjectName)) { fileUtils.copy(oldObjectName, newObjectName); fileUtils.delete(oldObjectName); } return new SlideVO(slide); } @Transactional(rollbackFor = Exception.class) @Override public void modifySlideState(LoginUser user, SlideStateDTO slideStateDTO) { Slide slide = slideDAO.findSlideById(slideStateDTO.getId()); AuthUtils.checkDataAuth(user.getId(), slide.getTeacherId(), "您无权修改该课件状态"); slide.setState(slideStateDTO.getState()); slide = slideDAO.save(slide); // 修改Slide状态同时推进quiz状态 quizService.modifySlideState(slide); // 增加通知 if (slide.getState() == SlideState.IN_CLASS) { Set studentIds = chooseDAO.findStudentIdsByCourseId(slide.getCourseId()); messageService.createMessage(studentIds, MessageType.SLIDE_IN_CLASS, String.format("课程 [%s] 的课件 [%s] 已开课", slide.getCourseName(), slide.getName()), slide.getId()); } } @Transactional(rollbackFor = Exception.class) @SneakyThrows @Override public void modifySlideFile(LoginUser user, SlideFileDTO slideFileDTO) { Slide slide = slideDAO.findSlideById(slideFileDTO.getId()); AuthUtils.checkDataAuth(user.getId(), slide.getTeacherId(), "您无权修改该课件文件"); MultipartFile file = slideFileDTO.getFile(); FileInfo fileInfo = getFileInfo(file); slide.setPages(fileInfo.pages); String oldObjectName = slide.getObjectName(); String newObjectName = getObjectName(slide.getId(), slide.getName(), fileInfo.suffix); slide.setObjectName(newObjectName); slideDAO.save(slide); fileUtils.upload(newObjectName, file.getInputStream()); if (!oldObjectName.equals(newObjectName)) { fileUtils.delete(oldObjectName); } } @Transactional(rollbackFor = Exception.class) @Override public void removeSlide(LoginUser user, Long slideId) { Slide slide = slideDAO.findSlideById(slideId); AuthUtils.checkDataAuth(user.getId(), slide.getTeacherId(), "您无权删除该课件"); slide.setDeleteAt(System.currentTimeMillis()); slideDAO.save(slide); } @SuppressWarnings("all") @SneakyThrows private FileInfo getFileInfo(MultipartFile file) { switch (Objects.requireNonNull(file.getContentType())) { case "application/pdf": // 解析页数 PDDocument doc = PDDocument.load(file.getInputStream()); return FileInfo.of(doc.getNumberOfPages(), "pdf"); default: throw HelperException.of(ExceptionType.PARAM_ERROR, "不支持的文件类型"); } } @AllArgsConstructor(staticName = "of") private static class FileInfo { private int pages; private String suffix; } private String getObjectName(Long slideId, String slideName, String suffix) { return SLIDE_STORE_DIR + File.separator + slideId + File.separator + slideName + "." + suffix; } @Transactional(readOnly = true) @Override public Page teacherGetSlides(LoginUser teacher, String key, Pageable pageable) { return slideDAO.findByTeacherIdAndNameContains(teacher.getId(), key, pageable).map(SlideVO::new); } @Transactional(readOnly = true) @Override public Page teacherGetSlidesByCourse(LoginUser teacher, Long courseId, String key, Pageable pageable) { return slideDAO.findByCourseIdAndTeacherIdAndNameContains(courseId, teacher.getId(), key, pageable).map(SlideVO::new); } @Transactional(readOnly = true) @Override public Page studentGetSlidesByCourse(LoginUser student, Long courseId, String key, Pageable pageable) { if (!chooseDAO.existsByStudentIdAndCourseId(student.getId(), courseId)) { throw HelperException.of(ExceptionType.FORBIDDEN, "你无权查看该课程下的课件"); } return slideDAO.findByCourseIdAndStateNotInAndNameContains(courseId, ImmutableSet.of(SlideState.DRAFT), key, pageable).map(SlideVO::new); } @Transactional(readOnly = true) @Override public String getSlideUrl(LoginUser user, Long slideId) { Slide slide = slideDAO.findSlideById(slideId); checkDataAccessAuth(user, slide); String url = cacheUtils.get(Consts.SLIDE_URL_CACHE_NAME, slide.getObjectName()); if (url != null) { return url; } url = fileUtils.getUrl(slide.getObjectName(), SLIDE_URL_LIVING_SECONDS); cacheUtils.set(Consts.SLIDE_URL_CACHE_NAME, slide.getObjectName(), url, SLIDE_URL_LIVING_SECONDS, TimeUnit.SECONDS); return url; } @Transactional(readOnly = true) @Override public SlideVO getOneSlide(LoginUser user, Long slideId) { Slide slide = slideDAO.findSlideById(slideId); checkDataAccessAuth(user, slide); return new SlideVO(slide); } /** * 检查同名课件 */ private void checkSameName(Long slideId, Long courseId, String name) { if (slideDAO.existsByCourseIdAndName(slideId, courseId, name)) { throw HelperException.of(ExceptionType.CONFLICT, "该课件名已使用"); } } private void checkDataAccessAuth(LoginUser user, Slide slide) { HelperException forbiddenEx = HelperException.of(ExceptionType.FORBIDDEN, "您无权访问该课件"); switch (user.getType()) { case TEACHER: if (!slide.getTeacherId().equals(user.getId())) { throw forbiddenEx; } break; case STUDENT: if (!chooseDAO.existsByStudentIdAndCourseId(user.getId(), slide.getCourseId()) || slide.getState() == SlideState.DRAFT) { throw forbiddenEx; } break; default: throw forbiddenEx; } } }