package com.chinaweal.youfool.course.controller; import cn.dev33.satoken.stp.StpUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import com.chinaweal.youfool.course.entity.Course; import com.chinaweal.youfool.course.entity.CourseVideo; import com.chinaweal.youfool.course.entity.CourseAttachment; import com.chinaweal.youfool.course.entity.CourseComment; import com.chinaweal.youfool.course.service.CourseService; import com.chinaweal.youfool.course.service.CourseVideoService; import com.chinaweal.youfool.course.service.CourseAttachmentService; import com.chinaweal.youfool.course.service.CourseCommentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import com.chinaweal.youfool.framework.springboot.rest.RestResult; import com.chinaweal.youfool.framework.springboot.rest.BaseResultCode; /** * 页面控制器 * * @author lroyia * @since 2025/10/24 **/ @Controller public class PageController { @Autowired private CourseService courseService; @Autowired private CourseVideoService courseVideoService; @Autowired private CourseAttachmentService courseAttachmentService; @Autowired private CourseCommentService courseCommentService; /** * 登录页面 * * @return 登录页面 */ @GetMapping("/login") public String loginPage() { return "login"; } /** * 首页(带课程列表分页) * * @param pageNum 页码 * @param pageSize 每页大小 * @param courseName 课程名称搜索 * @param model 模型对象 * @return 首页 */ @GetMapping("/") public String index(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String courseName, Model model) { if (!StpUtil.isLogin()) { return "redirect:/course/login"; } // 获取课程分页数据 IPage coursePage = courseService.getCoursePage(pageNum, pageSize, courseName, null); // 添加数据到模型 model.addAttribute("coursePage", coursePage); model.addAttribute("courseName", courseName); model.addAttribute("pageNum", pageNum); model.addAttribute("pageSize", pageSize); return "index"; } /** * 首页(带课程列表分页) * * @param pageNum 页码 * @param pageSize 每页大小 * @param courseName 课程名称搜索 * @param model 模型对象 * @return 首页 */ @GetMapping("/index") public String indexPage(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String courseName, Model model) { if (!StpUtil.isLogin()) { return "redirect:/course/login"; } // 获取课程分页数据 IPage coursePage = courseService.getCoursePage(pageNum, pageSize, courseName, null); // 添加数据到模型 model.addAttribute("coursePage", coursePage); model.addAttribute("courseName", courseName); model.addAttribute("pageNum", pageNum); model.addAttribute("pageSize", pageSize); return "index"; } /** * 课程详情页面 * * @param id 课程ID * @param commentPage 评论页码 * @param model 模型对象 * @return 课程详情页面 */ @GetMapping("/detail/{id}") public String courseDetail(@PathVariable String id, @RequestParam(defaultValue = "1") Integer commentPage, Model model) { if (!StpUtil.isLogin()) { return "redirect:/course/login"; } // 获取课程基本信息 Course course = courseService.getById(id); if (course == null) { return "redirect:/course/index"; } // 获取课程视频信息 CourseVideo courseVideo = courseVideoService.getCourseMainVideo(id); // 获取课程附件列表 java.util.List attachments = courseAttachmentService.getAttachmentsByCourseId(id); // 获取评论分页数据 IPage comments = courseCommentService.getCommentsByCourseId(id, commentPage, 10); // 添加数据到模型 model.addAttribute("course", course); model.addAttribute("courseVideo", courseVideo); model.addAttribute("attachments", attachments); model.addAttribute("comments", comments); model.addAttribute("commentPage", commentPage); return "course-detail"; } /** * 添加课程评论 * * @param commentData 评论数据 * @return 添加结果 */ @PostMapping("/comment/add") @ResponseBody public RestResult addComment(@RequestBody java.util.Map commentData) { if (!StpUtil.isLogin()) { return RestResult.error(BaseResultCode.BUSINESS_LOGIC_ERROR, "请先登录"); } try { String courseId = (String) commentData.get("courseId"); String commentText = (String) commentData.get("commentText"); String parentId = (String) commentData.get("parentId"); CourseComment comment = new CourseComment(); comment.setCourseId(courseId); comment.setCommentText(commentText); comment.setParentId(parentId); comment.setCreateBy(StpUtil.getLoginIdAsString()); boolean success = courseCommentService.save(comment); return success ? RestResult.ok(true) : RestResult.error(BaseResultCode.BUSINESS_LOGIC_ERROR, "评论发表失败"); } catch (Exception e) { return RestResult.error(BaseResultCode.BUSINESS_LOGIC_ERROR, "评论发表失败:" + e.getMessage()); } } /** * 下载课程附件 * * @param attachmentId 附件ID * @return 附件文件 */ @GetMapping("/attachment/download/{attachmentId}") public org.springframework.http.ResponseEntity downloadAttachment(@PathVariable String attachmentId) { if (!StpUtil.isLogin()) { return org.springframework.http.ResponseEntity.status(org.springframework.http.HttpStatus.FOUND) .location(ServletUriComponentsBuilder.fromPath("/course/login").build().toUri()) .build(); } CourseAttachment attachment = courseAttachmentService.getById(attachmentId); if (attachment == null) { return org.springframework.http.ResponseEntity.notFound().build(); } try { java.io.File file = new java.io.File(attachment.getAbsoluteName()); if (!file.exists()) { return org.springframework.http.ResponseEntity.notFound().build(); } org.springframework.core.io.Resource resource = new org.springframework.core.io.UrlResource(file.toURI()); return org.springframework.http.ResponseEntity.ok() .header(org.springframework.http.HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + new String(attachment.getFileName().getBytes("UTF-8"), "ISO-8859-1") + "\"") .header(org.springframework.http.HttpHeaders.CONTENT_TYPE, "application/octet-stream") .body(resource); } catch (Exception e) { return org.springframework.http.ResponseEntity.status(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR).build(); } } /** * 流式播放课程视频 * * @param attachmentId 附件ID * @return 视频文件流 */ @GetMapping("/course/video/stream/{attachmentId}") public org.springframework.http.ResponseEntity streamVideo(@PathVariable String attachmentId) { if (!StpUtil.isLogin()) { return org.springframework.http.ResponseEntity.status(org.springframework.http.HttpStatus.FOUND) .location(ServletUriComponentsBuilder.fromPath("/course/login").build().toUri()) .build(); } CourseAttachment attachment = courseAttachmentService.getById(attachmentId); if (attachment == null) { return org.springframework.http.ResponseEntity.notFound().build(); } try { java.io.File file = new java.io.File(attachment.getAbsoluteName()); if (!file.exists()) { return org.springframework.http.ResponseEntity.notFound().build(); } org.springframework.core.io.Resource resource = new org.springframework.core.io.UrlResource(file.toURI()); String contentType = determineVideoContentType(attachment.getFileFormat()); return org.springframework.http.ResponseEntity.ok() .header(org.springframework.http.HttpHeaders.CONTENT_TYPE, contentType) .header(org.springframework.http.HttpHeaders.ACCEPT_RANGES, "bytes") .body(resource); } catch (Exception e) { return org.springframework.http.ResponseEntity.status(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR).build(); } } /** * 根据文件格式确定视频内容类型 * * @param fileFormat 文件格式 * @return MIME类型 */ private String determineVideoContentType(String fileFormat) { if (fileFormat == null) { return "video/mp4"; } switch (fileFormat.toLowerCase()) { case "mp4": return "video/mp4"; case "webm": return "video/webm"; case "ogg": return "video/ogg"; case "avi": return "video/x-msvideo"; case "mov": return "video/quicktime"; case "wmv": return "video/x-ms-wmv"; case "flv": return "video/x-flv"; default: return "video/mp4"; } } }