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

448 lines
16 KiB
Java
Raw Normal View History

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";
}
}
}