youfool-course/src/main/java/com/chinaweal/youfool/course/controller/PageController.java

448 lines
16 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import com.chinaweal.youfool.framework.springboot.rest.RestResult;
import com.chinaweal.youfool.framework.springboot.rest.BaseResultCode;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import java.io.File;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.util.UUID;
/**
* 页面控制器
*
* @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<Course> 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<Course> 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<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();
}
}
/**
* 发布课程页面
*
* @return 发布课程页面
*/
@GetMapping("/publish")
public String publishCoursePage(Model model) {
if (!StpUtil.isLogin()) {
return "redirect:/course/login";
}
return "course-publish";
}
/**
* 发布课程
*
* @param courseName 课程名称
* @param courseDesc 课程描述
* @param videoFile 课程视频文件(可选)
* @param attachment 课程附件文件(可选)
* @param model 模型对象
* @return 发布结果
*/
@PostMapping("/publish")
@DSTransactional
@ResponseBody
public RestResult<String> publishCourse(
@RequestParam String courseName,
@RequestParam String courseDesc,
@RequestParam(required = false) MultipartFile videoFile,
@RequestParam(required = false) MultipartFile attachment,
Model model) {
if (!StpUtil.isLogin()) {
return RestResult.error(BaseResultCode.BUSINESS_LOGIC_ERROR, "请先登录");
}
if (courseName == null || courseName.trim().isEmpty()) {
return RestResult.error(BaseResultCode.BUSINESS_LOGIC_ERROR, "课程标题不能为空");
}
if (courseDesc == null || courseDesc.trim().isEmpty()) {
return RestResult.error(BaseResultCode.BUSINESS_LOGIC_ERROR, "课程描述不能为空");
}
try {
// 创建课程
Course course = new Course();
course.setCourseName(courseName.trim());
course.setCourseDesc(courseDesc.trim());
course.setCourseDate(LocalDate.now());
course.setCreateBy(StpUtil.getLoginIdAsString());
course.setCreateTime(LocalDateTime.now());
boolean courseSaved = courseService.save(course);
if (!courseSaved) {
return RestResult.error(BaseResultCode.BUSINESS_LOGIC_ERROR, "创建课程失败");
}
String courseId = course.getId();
// 处理视频文件
if (videoFile != null && !videoFile.isEmpty()) {
String videoAttachmentId = saveUploadFile(videoFile, courseId, "video");
if (videoAttachmentId != null) {
CourseVideo courseVideo = new CourseVideo();
courseVideo.setCourseId(courseId);
courseVideo.setSortIdx(1);
courseVideo.setVideoName(videoFile.getOriginalFilename());
courseVideo.setAttachmentId(videoAttachmentId);
courseVideo.setCreateBy(StpUtil.getLoginIdAsString());
courseVideo.setCreateTime(LocalDateTime.now());
courseVideoService.save(courseVideo);
}
}
// 处理附件文件
if (attachment != null && !attachment.isEmpty()) {
saveUploadFile(attachment, courseId, "attachment");
}
return RestResult.ok(courseId, "课程发布成功");
} catch (Exception e) {
e.printStackTrace();
return RestResult.error(BaseResultCode.BUSINESS_LOGIC_ERROR, "课程发布失败:" + e.getMessage());
}
}
/**
* 保存上传的文件
*
* @param file 上传的文件
* @param courseId 课程ID
* @param type 文件类型video或attachment
* @return 附件ID
*/
private String saveUploadFile(MultipartFile file, String courseId, String type) {
try {
if (file.isEmpty()) {
return null;
}
String originalFilename = file.getOriginalFilename();
if (originalFilename == null) {
return null;
}
String fileFormat = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).toLowerCase();
String newFileName = UUID.randomUUID().toString() + "." + fileFormat;
// 创建上传目录
String uploadDir = "../uploads/courses/" + LocalDate.now().getYear() + "/" +
String.format("%02d", LocalDate.now().getMonthValue()) + "/" +
String.format("%02d", LocalDate.now().getDayOfMonth());
Path uploadPath = Paths.get(uploadDir);
if (!uploadPath.toFile().exists()) {
uploadPath.toFile().mkdirs();
}
String absolutePath = uploadDir + "/" + newFileName;
// 保存文件
File dest = new File(absolutePath);
file.transferTo(dest);
// 创建附件记录
CourseAttachment attachment = new CourseAttachment();
attachment.setCourseId(courseId);
attachment.setFileName(originalFilename);
attachment.setFileFormat(fileFormat);
attachment.setAbsoluteName(absolutePath);
attachment.setFileSize(file.getSize());
attachment.setCreateBy(StpUtil.getLoginIdAsString());
attachment.setCreateTime(LocalDateTime.now());
boolean saved = courseAttachmentService.save(attachment);
return saved ? attachment.getId() : null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 流式播放课程视频
*
* @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";
}
}
}