SlideServiceImpl.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. package nju.seec.helper.service.impl;
  2. import com.google.common.collect.ImmutableSet;
  3. import lombok.AllArgsConstructor;
  4. import lombok.SneakyThrows;
  5. import nju.seec.helper.dao.ChooseDAO;
  6. import nju.seec.helper.dao.CourseDAO;
  7. import nju.seec.helper.dao.SlideDAO;
  8. import nju.seec.helper.dao.UserDAO;
  9. import nju.seec.helper.dto.LoginUser;
  10. import nju.seec.helper.dto.SlideDTO;
  11. import nju.seec.helper.dto.SlideFileDTO;
  12. import nju.seec.helper.dto.SlideStateDTO;
  13. import nju.seec.helper.entity.Course;
  14. import nju.seec.helper.entity.Slide;
  15. import nju.seec.helper.service.MessageService;
  16. import nju.seec.helper.service.SlideService;
  17. import nju.seec.helper.service.util.AuthUtils;
  18. import nju.seec.helper.service.util.StringUtils;
  19. import nju.seec.helper.util.CacheUtils;
  20. import nju.seec.helper.util.Consts;
  21. import nju.seec.helper.util.FileUtils;
  22. import nju.seec.helper.util.enums.ExceptionType;
  23. import nju.seec.helper.util.enums.MessageType;
  24. import nju.seec.helper.util.enums.SlideState;
  25. import nju.seec.helper.util.exception.HelperException;
  26. import nju.seec.helper.vo.SlideVO;
  27. import org.apache.pdfbox.pdmodel.PDDocument;
  28. import org.springframework.data.domain.Page;
  29. import org.springframework.data.domain.Pageable;
  30. import org.springframework.data.jpa.domain.Specification;
  31. import org.springframework.stereotype.Service;
  32. import org.springframework.transaction.annotation.Transactional;
  33. import org.springframework.web.multipart.MultipartFile;
  34. import java.io.File;
  35. import java.util.Objects;
  36. import java.util.Set;
  37. import java.util.concurrent.TimeUnit;
  38. /**
  39. * @author cst
  40. */
  41. @Service
  42. public class SlideServiceImpl implements SlideService {
  43. private static final String SLIDE_STORE_DIR = "slide";
  44. private static final long SLIDE_URL_LIVING_SECONDS = 20 * 60;
  45. private final CourseDAO courseDAO;
  46. private final SlideDAO slideDAO;
  47. private final ChooseDAO chooseDAO;
  48. private final UserDAO userDAO;
  49. private final FileUtils fileUtils;
  50. private final CacheUtils cacheUtils;
  51. private final MessageService messageService;
  52. public SlideServiceImpl(CourseDAO courseDAO, SlideDAO slideDAO, ChooseDAO chooseDAO, UserDAO userDAO, FileUtils fileUtils, CacheUtils cacheUtils, MessageService messageService) {
  53. this.courseDAO = courseDAO;
  54. this.slideDAO = slideDAO;
  55. this.chooseDAO = chooseDAO;
  56. this.userDAO = userDAO;
  57. this.fileUtils = fileUtils;
  58. this.cacheUtils = cacheUtils;
  59. this.messageService = messageService;
  60. }
  61. @Transactional(rollbackFor = Exception.class)
  62. @SneakyThrows
  63. @Override
  64. public SlideVO createSlide(LoginUser teacher, SlideDTO slideDTO) {
  65. Course course = courseDAO.findCourseById(slideDTO.getCourseId());
  66. AuthUtils.checkDataAuth(teacher.getId(), course.getTeacher().getId(), "您无权创建该课程的课件");
  67. checkSameName(0L, slideDTO.getCourseId(), slideDTO.getName());
  68. Slide slide = new Slide()
  69. .setCourse(course)
  70. .setTeacher(userDAO.findUserById(teacher.getId()))
  71. .setName(slideDTO.getName())
  72. .setState(slideDTO.getState());
  73. MultipartFile file = slideDTO.getFile();
  74. FileInfo fileInfo = getFileInfo(file);
  75. slide.setPages(fileInfo.pages);
  76. slide.setObjectName("temp");
  77. slide = slideDAO.save(slide);
  78. String objectName = getObjectName(slide.getId(), slide.getName(), fileInfo.suffix);
  79. slide.setObjectName(objectName);
  80. slide = slideDAO.save(slide);
  81. fileUtils.upload(objectName, file.getInputStream());
  82. return new SlideVO(slide);
  83. }
  84. @Transactional(rollbackFor = Exception.class)
  85. @Override
  86. public SlideVO modifySlide(LoginUser user, SlideDTO slideDTO) {
  87. Slide slide = slideDAO.findSlideById(slideDTO.getId());
  88. AuthUtils.checkDataAuth(user.getId(), slide.getTeacher().getId(), "您无权修改该课件");
  89. checkSameName(slide.getId(), slide.getCourse().getId(), slideDTO.getName());
  90. slide.setName(slideDTO.getName());
  91. String oldObjectName = slide.getObjectName();
  92. String newObjectName = getObjectName(slide.getId(), slide.getName(), oldObjectName.substring(oldObjectName.lastIndexOf('.') + 1));
  93. slide.setObjectName(newObjectName);
  94. slide = slideDAO.save(slide);
  95. if (!oldObjectName.equals(newObjectName)) {
  96. fileUtils.copy(oldObjectName, newObjectName);
  97. fileUtils.delete(oldObjectName);
  98. }
  99. return new SlideVO(slide);
  100. }
  101. @Transactional(rollbackFor = Exception.class)
  102. @Override
  103. public void modifySlideState(LoginUser user, SlideStateDTO slideStateDTO) {
  104. Slide slide = slideDAO.findSlideById(slideStateDTO.getId());
  105. AuthUtils.checkDataAuth(user.getId(), slide.getTeacher().getId(), "您无权修改该课件状态");
  106. slide.setState(slideStateDTO.getState());
  107. slide = slideDAO.save(slide);
  108. // 增加通知
  109. if (slide.getState() == SlideState.IN_CLASS) {
  110. Set<Long> studentIds = chooseDAO.findStudentIdsByCourseId(slide.getCourse().getId());
  111. messageService.createMessage(studentIds, MessageType.SLIDE_IN_CLASS, String.format("课程 [%s] 的课件 [%s] 已开课", slide.getCourse().getName(), slide.getName()), slide.getId());
  112. }
  113. }
  114. @Transactional(rollbackFor = Exception.class)
  115. @SneakyThrows
  116. @Override
  117. public void modifySlideFile(LoginUser user, SlideFileDTO slideFileDTO) {
  118. Slide slide = slideDAO.findSlideById(slideFileDTO.getId());
  119. AuthUtils.checkDataAuth(user.getId(), slide.getTeacher().getId(), "您无权修改该课件文件");
  120. MultipartFile file = slideFileDTO.getFile();
  121. FileInfo fileInfo = getFileInfo(file);
  122. slide.setPages(fileInfo.pages);
  123. String oldObjectName = slide.getObjectName();
  124. String newObjectName = getObjectName(slide.getId(), slide.getName(), fileInfo.suffix);
  125. slide.setObjectName(newObjectName);
  126. slideDAO.save(slide);
  127. fileUtils.upload(newObjectName, file.getInputStream());
  128. if (!oldObjectName.equals(newObjectName)) {
  129. fileUtils.delete(oldObjectName);
  130. }
  131. }
  132. @Transactional(rollbackFor = Exception.class)
  133. @Override
  134. public void removeSlide(LoginUser user, Long slideId) {
  135. Slide slide = slideDAO.findSlideById(slideId);
  136. AuthUtils.checkDataAuth(user.getId(), slide.getTeacher().getId(), "您无权删除该课件");
  137. slide.setDeleteAt(System.currentTimeMillis());
  138. slideDAO.save(slide);
  139. }
  140. @SuppressWarnings("all")
  141. @SneakyThrows
  142. private FileInfo getFileInfo(MultipartFile file) {
  143. switch (Objects.requireNonNull(file.getContentType())) {
  144. case "application/pdf":
  145. // 解析页数
  146. PDDocument doc = PDDocument.load(file.getInputStream());
  147. return FileInfo.of(doc.getNumberOfPages(), "pdf");
  148. default:
  149. throw HelperException.of(ExceptionType.PARAM_ERROR, "不支持的文件类型");
  150. }
  151. }
  152. @AllArgsConstructor(staticName = "of")
  153. private static class FileInfo {
  154. private int pages;
  155. private String suffix;
  156. }
  157. private String getObjectName(Long slideId, String slideName, String suffix) {
  158. return SLIDE_STORE_DIR + File.separator + slideId + File.separator + slideName + "." + suffix;
  159. }
  160. @Transactional(readOnly = true)
  161. @Override
  162. public Page<SlideVO> teacherGetSlides(LoginUser teacher, String key, Pageable pageable) {
  163. return findByTeacherIdAndKey(teacher.getId(), key, pageable).map(SlideVO::new);
  164. }
  165. @Transactional(readOnly = true)
  166. @Override
  167. public Page<SlideVO> teacherGetSlidesByCourse(LoginUser teacher, Long courseId, String key, Pageable pageable) {
  168. return findByCourseIdAndTeacherIdAndKey(courseId, teacher.getId(), key, pageable).map(SlideVO::new);
  169. }
  170. @Transactional(readOnly = true)
  171. @Override
  172. public Page<SlideVO> studentGetSlidesByCourse(LoginUser student, Long courseId, String key, Pageable pageable) {
  173. if (!chooseDAO.existsByStudentIdAndCourseId(student.getId(), courseId)) {
  174. throw HelperException.of(ExceptionType.FORBIDDEN, "你无权查看该课程下的课件");
  175. }
  176. return findByCourseIdAndKeyAndStateNotIn(courseId, ImmutableSet.of(SlideState.DRAFT), key, pageable).map(SlideVO::new);
  177. }
  178. @Transactional(readOnly = true)
  179. @Override
  180. public String getSlideUrl(LoginUser user, Long slideId) {
  181. Slide slide = slideDAO.findSlideById(slideId);
  182. checkDataAccessAuth(user, slide);
  183. String url = cacheUtils.get(Consts.SLIDE_URL_CACHE_NAME, slide.getObjectName());
  184. if (url != null) {
  185. return url;
  186. }
  187. url = fileUtils.getUrl(slide.getObjectName(), SLIDE_URL_LIVING_SECONDS);
  188. cacheUtils.set(Consts.SLIDE_URL_CACHE_NAME, slide.getObjectName(), url, SLIDE_URL_LIVING_SECONDS, TimeUnit.SECONDS);
  189. return url;
  190. }
  191. @Transactional(readOnly = true)
  192. @Override
  193. public SlideVO getOneSlide(LoginUser user, Long slideId) {
  194. Slide slide = slideDAO.findSlideById(slideId);
  195. checkDataAccessAuth(user, slide);
  196. return new SlideVO(slide);
  197. }
  198. /**
  199. * 检查同名课件
  200. */
  201. private void checkSameName(Long slideId, Long courseId, String name) {
  202. long exists = slideDAO.count((Specification<Slide>) (root, query, cb) -> cb.and(
  203. cb.notEqual(root.get("id"), slideId),
  204. cb.equal(root.get("deleteAt"), 0L),
  205. cb.equal(root.get("courseId"), courseId),
  206. cb.equal(root.get("name"), name)));
  207. if (exists > 0) {
  208. throw HelperException.of(ExceptionType.CONFLICT, "该课件名已使用");
  209. }
  210. }
  211. private Page<Slide> findByTeacherIdAndKey(Long teacherId, String key, Pageable pageable) {
  212. return slideDAO.findAll(
  213. (Specification<Slide>) (root, query, cb) -> cb.and(
  214. cb.equal(root.get("deleteAt"), 0L),
  215. cb.equal(root.get("teacher").get("id"), teacherId),
  216. cb.like(root.get("name"), StringUtils.keyPattern(key))),
  217. pageable);
  218. }
  219. private Page<Slide> findByCourseIdAndTeacherIdAndKey(Long courseId, Long teacherId, String key, Pageable pageable) {
  220. return slideDAO.findAll(
  221. (Specification<Slide>) (root, query, cb) -> cb.and(
  222. cb.equal(root.get("deleteAt"), 0L),
  223. cb.equal(root.get("courseId"), courseId),
  224. cb.equal(root.get("teacher").get("id"), teacherId),
  225. cb.like(root.get("name"), StringUtils.keyPattern(key))),
  226. pageable);
  227. }
  228. private Page<Slide> findByCourseIdAndKeyAndStateNotIn(Long courseId, Set<SlideState> exclusiveStates, String key, Pageable pageable) {
  229. return slideDAO.findAll(
  230. (Specification<Slide>) (root, query, cb) -> cb.and(
  231. cb.equal(root.get("deleteAt"), 0L),
  232. cb.equal(root.get("courseId"), courseId),
  233. cb.not(root.get("state").in(exclusiveStates)),
  234. cb.like(root.get("name"), StringUtils.keyPattern(key))
  235. ),
  236. pageable);
  237. }
  238. private void checkDataAccessAuth(LoginUser user, Slide slide) {
  239. HelperException forbiddenEx = HelperException.of(ExceptionType.FORBIDDEN, "您无权访问该课件");
  240. switch (user.getType()) {
  241. case TEACHER:
  242. if (!slide.getTeacher().getId().equals(user.getId())) {
  243. throw forbiddenEx;
  244. }
  245. break;
  246. case STUDENT:
  247. if (!chooseDAO.existsByStudentIdAndCourseId(user.getId(), slide.getCourse().getId())
  248. || slide.getState() == SlideState.DRAFT) {
  249. throw forbiddenEx;
  250. }
  251. break;
  252. default:
  253. throw forbiddenEx;
  254. }
  255. }
  256. }