فهرست منبع

1.添加文件控制器和文件服务、文件服务实现类;
2.优化文件存储配置;
3.完善课程添加逻辑。

Yumin 6 سال پیش
والد
کامیت
783fcf5e40

+ 44 - 1
src/main/java/cn/minbb/edu/controller/web/CourseController.java

@@ -4,12 +4,17 @@ import cn.minbb.edu.model.Course;
 import cn.minbb.edu.model.User;
 import cn.minbb.edu.service.CourseService;
 import cn.minbb.edu.service.UserService;
+import cn.minbb.edu.storage.FileService;
+import cn.minbb.edu.storage.StorageProperties;
+import cn.minbb.edu.system.Const;
 import cn.minbb.edu.system.UserSession;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.servlet.ModelAndView;
 
 import javax.servlet.http.HttpServletRequest;
@@ -21,11 +26,13 @@ public class CourseController {
 
     private UserService userService;
     private CourseService courseService;
+    private FileService fileService;
 
     @Autowired
-    public CourseController(UserService userService, CourseService courseService) {
+    public CourseController(UserService userService, CourseService courseService, FileService fileService) {
         this.userService = userService;
         this.courseService = courseService;
+        this.fileService = fileService;
     }
 
     @GetMapping(value = "create")
@@ -34,8 +41,42 @@ public class CourseController {
         return modelAndView;
     }
 
+    @PostMapping(value = "create")
+    public ModelAndView courseCreate(
+            @RequestParam("name") String name,
+            @RequestParam("introduction") String introduction,
+            @RequestParam("remark") String remark,
+            @RequestParam("cover") MultipartFile cover,
+            @RequestParam("video") MultipartFile video,
+            ModelAndView modelAndView, HttpServletRequest request) {
+        User user = UserSession.getUserAuthentication();
+        if (null != user) {
+            int userId = user.getId();
+            String coverFilename = cover.getOriginalFilename();
+            String videoFilename = video.getOriginalFilename();
+            String filenamePrefix = userId + "-" + System.currentTimeMillis();
+            if (null != coverFilename && null != videoFilename) {
+                String coverFilenameStore = filenamePrefix + coverFilename.substring(coverFilename.lastIndexOf("."));
+                String videoFilenameStore = filenamePrefix + videoFilename.substring(videoFilename.lastIndexOf("."));
+                fileService.storeToFolder(cover, coverFilenameStore, StorageProperties.Folder.IMAGES);
+                fileService.storeToFolder(video, videoFilenameStore, StorageProperties.Folder.VIDEOS);
+                Course course = new Course();
+                course.setName(name);
+                course.setIntroduction(introduction);
+                course.setRemark(remark);
+                course.setCover(coverFilenameStore);
+                course.setVideo(videoFilenameStore);
+                course.setTeacherId(userId);
+                courseService.save(course);
+            }
+        }
+        modelAndView.setViewName("redirect:/course/center");
+        return modelAndView;
+    }
+
     @GetMapping(value = "square")
     public ModelAndView courseSquarePage(ModelAndView modelAndView, HttpServletRequest request) {
+        modelAndView.addObject("STORAGE_HOST", Const.STORAGE_HOST);
         modelAndView.setViewName("course-square");
         return modelAndView;
     }
@@ -46,6 +87,7 @@ public class CourseController {
         if (null != user) {
             List<Course> courseList = courseService.findAllByUserId(user.getId());
             modelAndView.addObject("courseList", courseList);
+            modelAndView.addObject("STORAGE_HOST", Const.STORAGE_HOST);
             modelAndView.setViewName("course-center");
         } else {
             modelAndView.setViewName("redirect:/");
@@ -60,6 +102,7 @@ public class CourseController {
         if (null != id) {
             Course course = courseService.findOneById(id);
             modelAndView.addObject("course", course);
+            modelAndView.addObject("STORAGE_HOST", Const.STORAGE_HOST);
         }
         modelAndView.setViewName("course-player");
         return modelAndView;

+ 47 - 0
src/main/java/cn/minbb/edu/controller/web/FileController.java

@@ -0,0 +1,47 @@
+package cn.minbb.edu.controller.web;
+
+import cn.minbb.edu.storage.FileService;
+import cn.minbb.edu.storage.StorageFileNotFoundException;
+import cn.minbb.edu.storage.StorageProperties;
+import org.springframework.core.io.Resource;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+@RequestMapping("file")
+public class FileController {
+
+    private FileService fileService;
+
+    public FileController(FileService fileService) {
+        this.fileService = fileService;
+    }
+
+    @GetMapping(value = "/images/{filename:.+}")
+    public ResponseEntity<Resource> loadImages(@PathVariable String filename) {
+        Resource file = fileService.loadAsResourceFromFolder(filename, StorageProperties.Folder.IMAGES);
+        return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"").body(file);
+    }
+
+    @GetMapping(value = "/videos/{filename:.+}")
+    public ResponseEntity<Resource> loadVideos(@PathVariable String filename) {
+        Resource file = fileService.loadAsResourceFromFolder(filename, StorageProperties.Folder.VIDEOS);
+        return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"").body(file);
+    }
+
+    @GetMapping(value = "/download/videos/{filename:.+}")
+    public ResponseEntity<Resource> downloadVideos(@PathVariable String filename) {
+        Resource file = fileService.loadAsResourceFromFolder(filename, StorageProperties.Folder.VIDEOS);
+        return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"").body(file);
+    }
+
+    @ExceptionHandler(value = StorageFileNotFoundException.class)
+    public ResponseEntity handleStorageFileNotFound(StorageFileNotFoundException e) {
+        return ResponseEntity.notFound().build();
+    }
+}

+ 22 - 0
src/main/java/cn/minbb/edu/storage/FileService.java

@@ -0,0 +1,22 @@
+package cn.minbb.edu.storage;
+
+import org.springframework.core.io.Resource;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+public interface FileService {
+
+    void storeToFolder(MultipartFile file, String filename, StorageProperties.Folder folder);
+
+    void deleteFromFolder(String filename, StorageProperties.Folder folder);
+
+    void deleteAll();
+
+    Path loadFromFolder(String filename, StorageProperties.Folder folder);
+
+    Stream<Path> loadAllFromFolder(StorageProperties.Folder folder);
+
+    Resource loadAsResourceFromFolder(String filename, StorageProperties.Folder folder);
+}

+ 80 - 0
src/main/java/cn/minbb/edu/storage/FileServiceImpl.java

@@ -0,0 +1,80 @@
+package cn.minbb.edu.storage;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.UrlResource;
+import org.springframework.stereotype.Service;
+import org.springframework.util.FileSystemUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.stream.Stream;
+
+@Service
+public class FileServiceImpl implements FileService {
+
+    private Logger logger = LoggerFactory.getLogger(FileServiceImpl.class);
+
+    @Override
+    public void storeToFolder(MultipartFile file, String filename, StorageProperties.Folder folder) {
+        try {
+            if (file.isEmpty()) {
+                throw new StorageException("Failed to store empty file " + file.getOriginalFilename());
+            }
+            Files.copy(file.getInputStream(), Paths.get(folder.getPath()).resolve(filename));
+        } catch (IOException e) {
+            throw new StorageException("Failed to store file " + file.getOriginalFilename(), e);
+        }
+    }
+
+    @Override
+    public void deleteFromFolder(String filename, StorageProperties.Folder folder) {
+        try {
+            Files.delete(loadFromFolder(filename, folder));
+        } catch (IOException e) {
+            throw new StorageException("Failed to delete file " + filename, e);
+        }
+    }
+
+    @Override
+    public void deleteAll() {
+        FileSystemUtils.deleteRecursively(Paths.get(StorageProperties.Folder.LOCATION.getPath()).toFile());
+    }
+
+    @Override
+    public Path loadFromFolder(String filename, StorageProperties.Folder folder) {
+        return Paths.get(folder.getPath()).resolve(filename);
+    }
+
+    @Override
+    public Stream<Path> loadAllFromFolder(StorageProperties.Folder folder) {
+        Path rootLocation = Paths.get(folder.getPath());
+        try {
+            return Files.walk(rootLocation, 1)
+                    .filter(path -> !path.equals(rootLocation))
+                    .map(path -> rootLocation.relativize(path));
+        } catch (IOException e) {
+            throw new StorageException("Failed to read stored files", e);
+        }
+    }
+
+    @Override
+    public Resource loadAsResourceFromFolder(String filename, StorageProperties.Folder folder) {
+        try {
+            Path file = loadFromFolder(filename, folder);
+            Resource resource = new UrlResource(file.toUri());
+            if (resource.exists() || resource.isReadable()) {
+                return resource;
+            } else {
+                throw new StorageFileNotFoundException("Could not read file: " + filename);
+            }
+        } catch (MalformedURLException e) {
+            throw new StorageFileNotFoundException("Could not read file: " + filename, e);
+        }
+    }
+}

+ 48 - 27
src/main/java/cn/minbb/edu/storage/StorageProperties.java

@@ -1,36 +1,57 @@
 package cn.minbb.edu.storage;
 
 import lombok.Getter;
-import lombok.Setter;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 
 @ConfigurationProperties("storage")
 public class StorageProperties {
 
-    private static String ROOT = "EDU";
-
-    @Getter
-    @Setter
-    // 存储文件的文件夹位置
-    private String location = "C:\\" + ROOT + "\\";
-
-    @Getter
-    @Setter
-    // 图片目录
-    private String images = "C:\\" + ROOT + "\\images\\";
-
-    @Getter
-    @Setter
-    // 视频目录
-    private String videos = "C:\\" + ROOT + "\\videos\\";
-
-    @Getter
-    @Setter
-    // 用户头像目录
-    private String avatars = "C:\\" + ROOT + "\\avatars\\";
-
-    @Getter
-    @Setter
-    // 文件下载目录
-    private String download = "C:\\" + ROOT + "\\download\\";
+//    private static final String ROOT = "C:\\EDU";
+//
+//    @Getter
+//    @Setter
+//    // 存储文件的文件夹位置
+//    private String location = ROOT + "\\";
+//
+//    @Getter
+//    @Setter
+//    // 图片目录
+//    private String images = ROOT + "\\images\\";
+//
+//    @Getter
+//    @Setter
+//    // 视频目录
+//    private String videos = ROOT + "\\videos\\";
+//
+//    @Getter
+//    @Setter
+//    // 用户头像目录
+//    private String avatars = ROOT + "\\avatars\\";
+//
+//    @Getter
+//    @Setter
+//    // 文件下载目录
+//    private String download = ROOT + "\\download\\";
+
+    public enum Folder {
+        // 根目录
+        ROOT("C:\\EDU"),
+        // 存储文件的文件夹位置
+        LOCATION("C:\\EDU\\"),
+        // 图片目录
+        IMAGES("C:\\EDU\\IMAGES\\"),
+        // 视频目录
+        VIDEOS("C:\\EDU\\VIDEOS\\"),
+        // 用户头像目录
+        AVATARS("C:\\EDU\\AVATARS\\"),
+        // 文件下载目录
+        DOWNLOAD("C:\\EDU\\DOWNLOAD\\");
+
+        @Getter
+        private String path;
+
+        Folder(String path) {
+            this.path = path;
+        }
+    }
 }

+ 13 - 8
src/main/java/cn/minbb/edu/storage/StorageServiceFileSystem.java

@@ -31,7 +31,8 @@ public class StorageServiceFileSystem implements StorageService {
     @Autowired
     public StorageServiceFileSystem(StorageProperties properties) {
         this.storageProperties = properties;
-        this.rootLocation = Paths.get(properties.getLocation());
+        // this.rootLocation = Paths.get(properties.getLocation());
+        this.rootLocation = Paths.get(StorageProperties.Folder.LOCATION.getPath());
     }
 
     /**
@@ -41,10 +42,14 @@ public class StorageServiceFileSystem implements StorageService {
     public void init() {
         try {
             File rootDir = rootLocation.toFile();
-            File imageDir = new File(storageProperties.getImages());
-            File videoDir = new File(storageProperties.getVideos());
-            File avatarDir = new File(storageProperties.getAvatars());
-            File downloadDir = new File(storageProperties.getDownload());
+            // File imageDir = new File(storageProperties.getImages());
+            // File videoDir = new File(storageProperties.getVideos());
+            // File avatarDir = new File(storageProperties.getAvatars());
+            // File downloadDir = new File(storageProperties.getDownload());
+            File imageDir = new File(StorageProperties.Folder.IMAGES.getPath());
+            File videoDir = new File(StorageProperties.Folder.VIDEOS.getPath());
+            File avatarDir = new File(StorageProperties.Folder.AVATARS.getPath());
+            File downloadDir = new File(StorageProperties.Folder.DOWNLOAD.getPath());
             List<File> dirList = new ArrayList<>(4);
             dirList.add(imageDir);
             dirList.add(videoDir);
@@ -163,17 +168,17 @@ public class StorageServiceFileSystem implements StorageService {
             logger.error("文件名不能为空:{}", file);
             return null;
         }
-        String fileName = System.currentTimeMillis() + originalFilename.substring(originalFilename.lastIndexOf("."));
+        String filename = System.currentTimeMillis() + originalFilename.substring(originalFilename.lastIndexOf("."));
         try {
             if (file.isEmpty()) {
                 logger.error("文件不能为空:{}", originalFilename);
                 return null;
             }
-            Files.copy(file.getInputStream(), path.resolve(fileName));
+            Files.copy(file.getInputStream(), path.resolve(filename));
         } catch (IOException e) {
             logger.error("存储失败,原因为:{}", e);
             return null;
         }
-        return fileName;
+        return filename;
     }
 }

+ 1 - 0
src/main/java/cn/minbb/edu/system/Const.java

@@ -6,4 +6,5 @@ public class Const {
     }
 
     public static final String ACTIVE = "ACTIVE";
+    public static final String STORAGE_HOST = "http://127.0.0.1/file/";
 }

+ 0 - 13
src/main/java/cn/minbb/edu/task/InitDataRunner.java

@@ -1,7 +1,6 @@
 package cn.minbb.edu.task;
 
 import cn.minbb.edu.model.Banner;
-import cn.minbb.edu.model.Course;
 import cn.minbb.edu.model.UserRole;
 import cn.minbb.edu.service.BannerService;
 import cn.minbb.edu.service.CourseService;
@@ -71,17 +70,5 @@ public class InitDataRunner implements ApplicationRunner {
             banner3.setUserId(1);
             bannerService.save(banner3);
         }
-        // 检查课程 - 初始化课程
-        List<Course> courseList = courseService.findAllByUserId(1);
-        if (courseList.isEmpty()) {
-            Course course1 = new Course();
-            course1.setName("基础教学");
-            course1.setIntroduction("测试的测试的测试的");
-            course1.setCover("/favicon.ico");
-            course1.setVideo("https://files-1252373323.cos.ap-beijing.myqcloud.com/videos/%E6%B2%B3%E5%8D%97%E6%96%B9%E8%A8%80%E7%89%88.mp4");
-            course1.setRemark("");
-            course1.setTeacherId(1);
-            courseService.save(course1);
-        }
     }
 }

+ 2 - 0
src/main/resources/application.properties

@@ -3,6 +3,8 @@ spring.profiles.active=pro
 server.port=80
 server.servlet.context-path=/
 spring.jmx.default-domain=edu
+spring.servlet.multipart.max-file-size=500MB
+spring.servlet.multipart.max-request-size=600MB
 spring.datasource.url=
 spring.datasource.username=
 spring.datasource.password=

+ 3 - 1
src/main/resources/templates/course-center.html

@@ -26,7 +26,9 @@
                 <div class="row">
                     <div class="col-xs-12 col-sm-6 col-md-4" th:each="course, iter : ${courseList}">
                         <div class="card mb-4 box-shadow">
-                            <img class="card-img-top" src="" alt="" th:src="${course.getCover()}" th:alt="${course.getName()}"/>
+                            <img class="card-img-top" src="" alt=""
+                                 th:src="${STORAGE_HOST + 'images/' + course.getCover()}"
+                                 th:alt="${course.getName()}"/>
                             <div class="card-body">
                                 <p class="card-text" th:text="${course.getIntroduction()}">课程介绍</p>
                                 <div class="d-flex justify-content-between align-items-center">

+ 40 - 1
src/main/resources/templates/course-create.html

@@ -10,7 +10,46 @@
 <body>
 <th:block layout:fragment="content">
     <div class="container" style="margin-top: 24px;">
-        创建课程
+        <div class="py-4 text-center">
+            <!-- <img class="d-block mx-auto mb-4" src="/favicon.ico" alt="" width="72" height="72"/>-->
+            <h2>创建课程</h2>
+            <!-- <p class="lead">创建属于您的课程</p>-->
+        </div>
+
+        <hr class="mb-4"/>
+
+        <div class="row">
+            <div class="col-md-12">
+                <h4 class="mb-3">课程信息</h4>
+                <form method="post" action="/course/create" enctype="multipart/form-data">
+                    <div class="mb-3">
+                        <label for="name">课程名称</label>
+                        <input class="form-control" id="name" name="name" type="text" placeholder="不超过六十个字" required autofocus/>
+                    </div>
+                    <div class="mb-3">
+                        <label for="introduction">课程介绍</label>
+                        <textarea class="form-control" id="introduction" name="introduction" placeholder="不超过两百四十个字" rows="3" required></textarea>
+                    </div>
+                    <div class="mb-3">
+                        <label for="remark">课程备注</label>
+                        <textarea class="form-control" id="remark" name="remark" placeholder="不超过两百四十个字(非必填)" rows="2"></textarea>
+                    </div>
+                    <div class="row">
+                        <div class="col-sm-12 col-md-6 mb-3">
+                            <label for="cover">课程封面</label>
+                            <input class="form-control" id="cover" name="cover" type="file" placeholder="图片大小不超过2MB" required
+                                   accept="image/png, image/jpeg, image/gif, image/jpg"/>
+                        </div>
+                        <div class="col-sm-12 col-md-6 mb-3">
+                            <label for="video">课程视频</label>
+                            <input class="form-control" id="video" name="video" type="file" placeholder="视频大小不超过500MB" required
+                                   accept="video/mp4, video/3gpp"/>
+                        </div>
+                    </div>
+                    <button class="btn btn-primary btn-lg btn-block mt-3" type="submit">创建课程</button>
+                </form>
+            </div>
+        </div>
     </div>
 </th:block>
 </body>

+ 3 - 3
src/main/resources/templates/course-player.html

@@ -20,7 +20,7 @@
     </div>
     <div th:unless="${course == null}">
         <nav class="navbar navbar-expand navbar-dark bg-dark" style="z-index: 0;">
-            <a class="navbar-brand" href="#" th:text="${course.getName()}">课程名称</a>
+            <a class="navbar-brand" href="#!" th:text="${course.getName()}">课程名称</a>
             <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExample02" aria-controls="navbarsExample02"
                     aria-expanded="false" aria-label="Toggle navigation">
                 <span class="navbar-toggler-icon"></span>
@@ -30,14 +30,14 @@
                 <ul class="navbar-nav mr-auto">
                     <li class="nav-item active">
                         <a class="nav-link">
-                            <span th:text="${#strings.abbreviate(course.getIntroduction(), 15)}"></span> <span class="sr-only">(current)</span>
+                            <span th:text="${#strings.abbreviate(course.getIntroduction(), 12)}"></span> <span class="sr-only">(current)</span>
                         </a>
                     </li>
                     <li class="nav-item"><a class="nav-link" href="/course/center">课程中心</a></li>
                 </ul>
             </div>
         </nav>
-        <video src="" controls="controls" autoplay width="100%" th:src="${course.getVideo()}"></video>
+        <video src="" controls="controls" autoplay width="100%" th:src="${STORAGE_HOST + 'videos/' + course.getVideo()}"></video>
     </div>
 </th:block>
 </body>

+ 1 - 0
src/main/resources/templates/index-about.html

@@ -15,6 +15,7 @@
 
         .marketing h2 {
             font-weight: 400;
+            margin-top: 12px;
         }
 
         .marketing .col-lg-4 p {