2025-10-24 14:03:39 +08:00
|
|
|
|
package com.chinaweal.youfool.course.controller;
|
|
|
|
|
|
|
2025-10-24 14:16:02 +08:00
|
|
|
|
import cn.dev33.satoken.stp.StpUtil;
|
2025-10-31 16:48:17 +08:00
|
|
|
|
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;
|
2025-10-24 14:03:39 +08:00
|
|
|
|
import org.springframework.stereotype.Controller;
|
2025-10-31 16:48:17 +08:00
|
|
|
|
import org.springframework.ui.Model;
|
2025-10-24 14:03:39 +08:00
|
|
|
|
import org.springframework.web.bind.annotation.GetMapping;
|
2025-10-31 16:48:17 +08:00
|
|
|
|
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;
|
2025-10-24 14:03:39 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 页面控制器
|
|
|
|
|
|
*
|
|
|
|
|
|
* @author lroyia
|
|
|
|
|
|
* @since 2025/10/24
|
|
|
|
|
|
**/
|
|
|
|
|
|
@Controller
|
|
|
|
|
|
public class PageController {
|
|
|
|
|
|
|
2025-10-31 16:48:17 +08:00
|
|
|
|
@Autowired
|
|
|
|
|
|
private CourseService courseService;
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
|
private CourseVideoService courseVideoService;
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
|
private CourseAttachmentService courseAttachmentService;
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
|
private CourseCommentService courseCommentService;
|
|
|
|
|
|
|
2025-10-24 14:03:39 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 登录页面
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return 登录页面
|
|
|
|
|
|
*/
|
|
|
|
|
|
@GetMapping("/login")
|
|
|
|
|
|
public String loginPage() {
|
|
|
|
|
|
return "login";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-31 16:48:17 +08:00
|
|
|
|
* 首页(带课程列表分页)
|
2025-10-24 14:03:39 +08:00
|
|
|
|
*
|
2025-10-31 16:48:17 +08:00
|
|
|
|
* @param pageNum 页码
|
|
|
|
|
|
* @param pageSize 每页大小
|
|
|
|
|
|
* @param courseName 课程名称搜索
|
|
|
|
|
|
* @param model 模型对象
|
2025-10-24 14:03:39 +08:00
|
|
|
|
* @return 首页
|
|
|
|
|
|
*/
|
|
|
|
|
|
@GetMapping("/")
|
2025-10-31 16:48:17 +08:00
|
|
|
|
public String index(@RequestParam(defaultValue = "1") Integer pageNum,
|
|
|
|
|
|
@RequestParam(defaultValue = "10") Integer pageSize,
|
|
|
|
|
|
@RequestParam(required = false) String courseName,
|
|
|
|
|
|
Model model) {
|
2025-10-24 14:16:02 +08:00
|
|
|
|
if (!StpUtil.isLogin()) {
|
|
|
|
|
|
return "redirect:/course/login";
|
|
|
|
|
|
}
|
2025-10-31 16:48:17 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取课程分页数据
|
|
|
|
|
|
IPage<Course> coursePage = courseService.getCoursePage(pageNum, pageSize, courseName, null);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加数据到模型
|
|
|
|
|
|
model.addAttribute("coursePage", coursePage);
|
|
|
|
|
|
model.addAttribute("courseName", courseName);
|
|
|
|
|
|
model.addAttribute("pageNum", pageNum);
|
|
|
|
|
|
model.addAttribute("pageSize", pageSize);
|
|
|
|
|
|
|
2025-10-24 14:03:39 +08:00
|
|
|
|
return "index";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-31 16:48:17 +08:00
|
|
|
|
* 首页(带课程列表分页)
|
2025-10-24 14:03:39 +08:00
|
|
|
|
*
|
2025-10-31 16:48:17 +08:00
|
|
|
|
* @param pageNum 页码
|
|
|
|
|
|
* @param pageSize 每页大小
|
|
|
|
|
|
* @param courseName 课程名称搜索
|
|
|
|
|
|
* @param model 模型对象
|
2025-10-24 14:03:39 +08:00
|
|
|
|
* @return 首页
|
|
|
|
|
|
*/
|
|
|
|
|
|
@GetMapping("/index")
|
2025-10-31 16:48:17 +08:00
|
|
|
|
public String indexPage(@RequestParam(defaultValue = "1") Integer pageNum,
|
|
|
|
|
|
@RequestParam(defaultValue = "10") Integer pageSize,
|
|
|
|
|
|
@RequestParam(required = false) String courseName,
|
|
|
|
|
|
Model model) {
|
2025-10-24 14:16:02 +08:00
|
|
|
|
if (!StpUtil.isLogin()) {
|
|
|
|
|
|
return "redirect:/course/login";
|
|
|
|
|
|
}
|
2025-10-31 16:48:17 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取课程分页数据
|
|
|
|
|
|
IPage<Course> coursePage = courseService.getCoursePage(pageNum, pageSize, courseName, null);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加数据到模型
|
|
|
|
|
|
model.addAttribute("coursePage", coursePage);
|
|
|
|
|
|
model.addAttribute("courseName", courseName);
|
|
|
|
|
|
model.addAttribute("pageNum", pageNum);
|
|
|
|
|
|
model.addAttribute("pageSize", pageSize);
|
|
|
|
|
|
|
2025-10-24 14:03:39 +08:00
|
|
|
|
return "index";
|
|
|
|
|
|
}
|
2025-10-31 16:48:17 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 课程详情页面
|
|
|
|
|
|
*
|
|
|
|
|
|
* @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<CourseAttachment> attachments = courseAttachmentService.getAttachmentsByCourseId(id);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取评论分页数据
|
|
|
|
|
|
IPage<CourseComment> 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<Boolean> addComment(@RequestBody java.util.Map<String, Object> 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<org.springframework.core.io.Resource> 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<org.springframework.core.io.Resource> 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";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-24 14:03:39 +08:00
|
|
|
|
}
|