Parcourir la source

1.添加若干页面控制器;
2.添加和修改若干实体类对应的数据仓库及数据服务类和实现类;
3.添加数据存储配置类、服务类和实现类;
4.添加排序工具;
5.添加若干前端页面。

Yumin il y a 6 ans
Parent
commit
f6c1e91179
42 fichiers modifiés avec 1143 ajouts et 51 suppressions
  1. 14 0
      src/main/java/cn/minbb/edu/EDUApplication.java
  2. 1 0
      src/main/java/cn/minbb/edu/config/WebSecurityConfig.java
  3. 13 0
      src/main/java/cn/minbb/edu/controller/UniqueNameGenerator.java
  4. 28 0
      src/main/java/cn/minbb/edu/controller/rest/MainController.java
  5. 28 0
      src/main/java/cn/minbb/edu/controller/web/AdminController.java
  6. 67 0
      src/main/java/cn/minbb/edu/controller/web/CourseController.java
  7. 40 2
      src/main/java/cn/minbb/edu/controller/web/MainController.java
  8. 28 0
      src/main/java/cn/minbb/edu/controller/web/UserController.java
  9. 86 0
      src/main/java/cn/minbb/edu/model/Banner.java
  10. 9 0
      src/main/java/cn/minbb/edu/model/Course.java
  11. 5 0
      src/main/java/cn/minbb/edu/model/Homework.java
  12. 5 0
      src/main/java/cn/minbb/edu/model/Study.java
  13. 9 0
      src/main/java/cn/minbb/edu/model/User.java
  14. 11 0
      src/main/java/cn/minbb/edu/model/repository/BannerRepository.java
  15. 11 0
      src/main/java/cn/minbb/edu/service/BannerService.java
  16. 6 0
      src/main/java/cn/minbb/edu/service/CourseService.java
  17. 32 0
      src/main/java/cn/minbb/edu/service/impl/BannerServiceImpl.java
  18. 20 0
      src/main/java/cn/minbb/edu/service/impl/CourseServiceImpl.java
  19. 1 0
      src/main/java/cn/minbb/edu/service/impl/HomeworkServiceImpl.java
  20. 1 0
      src/main/java/cn/minbb/edu/service/impl/StudyServiceImpl.java
  21. 12 0
      src/main/java/cn/minbb/edu/storage/StorageException.java
  22. 12 0
      src/main/java/cn/minbb/edu/storage/StorageFileNotFoundException.java
  23. 36 0
      src/main/java/cn/minbb/edu/storage/StorageProperties.java
  24. 24 0
      src/main/java/cn/minbb/edu/storage/StorageService.java
  25. 179 0
      src/main/java/cn/minbb/edu/storage/StorageServiceFileSystem.java
  26. 9 0
      src/main/java/cn/minbb/edu/system/Const.java
  27. 54 1
      src/main/java/cn/minbb/edu/task/InitDataRunner.java
  28. 50 0
      src/main/java/cn/minbb/edu/util/SortTools.java
  29. 1 1
      src/main/resources/application-dev.properties
  30. 1 1
      src/main/resources/application-pro.properties
  31. 17 0
      src/main/resources/templates/admin-config.html
  32. 56 0
      src/main/resources/templates/course-center.html
  33. 17 0
      src/main/resources/templates/course-create.html
  34. 44 0
      src/main/resources/templates/course-player.html
  35. 17 0
      src/main/resources/templates/course-square.html
  36. 15 6
      src/main/resources/templates/fragments/header.html
  37. 121 0
      src/main/resources/templates/index-about.html
  38. 42 37
      src/main/resources/templates/index.html
  39. 2 1
      src/main/resources/templates/layouts/layout.html
  40. 1 1
      src/main/resources/templates/sign-in.html
  41. 1 1
      src/main/resources/templates/sign-up.html
  42. 17 0
      src/main/resources/templates/user-center.html

+ 14 - 0
src/main/java/cn/minbb/edu/EDUApplication.java

@@ -1,12 +1,21 @@
 package cn.minbb.edu;
 
+import cn.minbb.edu.controller.UniqueNameGenerator;
+import cn.minbb.edu.storage.StorageProperties;
+import cn.minbb.edu.storage.StorageService;
 import org.springframework.boot.Banner;
+import org.springframework.boot.CommandLineRunner;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
 
 @SpringBootApplication
+@EnableConfigurationProperties(StorageProperties.class)
+@ComponentScan(nameGenerator = UniqueNameGenerator.class)
 public class EDUApplication extends SpringBootServletInitializer {
 
     public static void main(String[] args) {
@@ -19,4 +28,9 @@ public class EDUApplication extends SpringBootServletInitializer {
     protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
         return builder.sources(EDUApplication.class);
     }
+
+    @Bean
+    CommandLineRunner init(StorageService storageService) {
+        return args -> storageService.init();
+    }
 }

+ 1 - 0
src/main/java/cn/minbb/edu/config/WebSecurityConfig.java

@@ -45,6 +45,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 .accessDeniedHandler(new AccessDenied())
                 .and().csrf().disable().authorizeRequests()
                 .antMatchers("/user/**").access("hasRole('USER')")
+                .antMatchers("/course/**").access("hasRole('USER') and hasRole('TEACHER')")
                 .antMatchers("/admin/**").access("hasRole('USER') and  hasRole('ADMIN')")
                 .antMatchers("/swagger-ui.html").access("hasRole('ADMIN')")
                 .antMatchers("/css/**", "/js/**", "/images/**", "/fonts/*").permitAll()

+ 13 - 0
src/main/java/cn/minbb/edu/controller/UniqueNameGenerator.java

@@ -0,0 +1,13 @@
+package cn.minbb.edu.controller;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.context.annotation.AnnotationBeanNameGenerator;
+
+public class UniqueNameGenerator extends AnnotationBeanNameGenerator {
+    @Override
+    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
+        // 全限定类名
+        return definition.getBeanClassName();
+    }
+}

+ 28 - 0
src/main/java/cn/minbb/edu/controller/rest/MainController.java

@@ -0,0 +1,28 @@
+package cn.minbb.edu.controller.rest;
+
+import cn.minbb.edu.data.ResponseResult;
+import cn.minbb.edu.model.Banner;
+import cn.minbb.edu.service.BannerService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("")
+public class MainController {
+
+    private BannerService bannerService;
+
+    public MainController(BannerService bannerService) {
+        this.bannerService = bannerService;
+    }
+
+    @GetMapping("banners")
+    public ResponseResult<Banner> banners() {
+        ResponseResult<Banner> result = new ResponseResult<>();
+        result.setSuccess(true);
+        result.setDataset(bannerService.findAll());
+        result.setMessage("获取成功");
+        return result;
+    }
+}

+ 28 - 0
src/main/java/cn/minbb/edu/controller/web/AdminController.java

@@ -0,0 +1,28 @@
+package cn.minbb.edu.controller.web;
+
+import cn.minbb.edu.service.UserService;
+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.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Controller
+@RequestMapping("admin")
+public class AdminController {
+
+    private UserService userService;
+
+    @Autowired
+    public AdminController(UserService userService) {
+        this.userService = userService;
+    }
+
+    @GetMapping(value = "config")
+    public ModelAndView configPage(ModelAndView modelAndView, HttpServletRequest request) {
+        modelAndView.setViewName("admin-config");
+        return modelAndView;
+    }
+}

+ 67 - 0
src/main/java/cn/minbb/edu/controller/web/CourseController.java

@@ -0,0 +1,67 @@
+package cn.minbb.edu.controller.web;
+
+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.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.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+
+@Controller
+@RequestMapping("course")
+public class CourseController {
+
+    private UserService userService;
+    private CourseService courseService;
+
+    @Autowired
+    public CourseController(UserService userService, CourseService courseService) {
+        this.userService = userService;
+        this.courseService = courseService;
+    }
+
+    @GetMapping(value = "create")
+    public ModelAndView courseCreatePage(ModelAndView modelAndView, HttpServletRequest request) {
+        modelAndView.setViewName("course-create");
+        return modelAndView;
+    }
+
+    @GetMapping(value = "square")
+    public ModelAndView courseSquarePage(ModelAndView modelAndView, HttpServletRequest request) {
+        modelAndView.setViewName("course-square");
+        return modelAndView;
+    }
+
+    @GetMapping(value = "center")
+    public ModelAndView courseCenterPage(ModelAndView modelAndView, HttpServletRequest request) {
+        User user = UserSession.getUserAuthentication();
+        if (null != user) {
+            List<Course> courseList = courseService.findAllByUserId(user.getId());
+            modelAndView.addObject("courseList", courseList);
+            modelAndView.setViewName("course-center");
+        } else {
+            modelAndView.setViewName("redirect:/");
+        }
+        return modelAndView;
+    }
+
+    @GetMapping(value = "player")
+    public ModelAndView coursePlayerPage(
+            @RequestParam(value = "id", required = false) Integer id,
+            ModelAndView modelAndView, HttpServletRequest request) {
+        if (null != id) {
+            Course course = courseService.findOneById(id);
+            modelAndView.addObject("course", course);
+        }
+        modelAndView.setViewName("course-player");
+        return modelAndView;
+    }
+}

+ 40 - 2
src/main/java/cn/minbb/edu/controller/web/MainController.java

@@ -1,31 +1,69 @@
 package cn.minbb.edu.controller.web;
 
+import cn.minbb.edu.model.Banner;
+import cn.minbb.edu.service.BannerService;
 import cn.minbb.edu.service.UserService;
+import cn.minbb.edu.system.Const;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.servlet.ModelAndView;
 
 import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.List;
 
 @Controller
-@RequestMapping
+@RequestMapping("")
 public class MainController {
 
     private UserService userService;
+    private BannerService bannerService;
 
     @Autowired
-    public MainController(UserService userService) {
+    public MainController(UserService userService, BannerService bannerService) {
         this.userService = userService;
+        this.bannerService = bannerService;
     }
 
     @GetMapping(value = "")
     public ModelAndView indexPage(ModelAndView modelAndView, HttpServletRequest request) {
+        List<Banner> bannerList = bannerService.findAll();
+        modelAndView.addObject("bannerList", bannerList);
+        modelAndView.addObject(Const.ACTIVE, "index");
         modelAndView.setViewName("index");
         return modelAndView;
     }
 
+    @GetMapping(value = "about")
+    public ModelAndView aboutPage(ModelAndView modelAndView, HttpServletRequest request) {
+        modelAndView.addObject(Const.ACTIVE, "about");
+        modelAndView.setViewName("index-about");
+        return modelAndView;
+    }
+
+    @GetMapping(value = "download")
+    public ResponseEntity<InputStreamResource> downloadPage(HttpServletRequest request) throws IOException {
+        // String apkPath = "C:\\Evaluation\\download\\apk\\";
+        // FileSystemResource file = new FileSystemResource(apkPath);
+        ClassPathResource file = new ClassPathResource("/static/favicon.ico", this.getClass());
+        HttpHeaders headers = new HttpHeaders();
+        headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
+        headers.add("Content-Disposition", String.format("attachment; filename=\"%s\"", file.getFilename()));
+        headers.add("Pragma", "no-cache");
+        headers.add("Expires", "0");
+        return ResponseEntity.ok().headers(headers).contentLength(file.contentLength())
+                .contentType(MediaType.parseMediaType("application/octet-stream"))
+                .contentType(MediaType.parseMediaType("application/x-shockwave-flash"))
+                .body(new InputStreamResource(file.getInputStream()));
+    }
+
     @GetMapping(value = {"register", "sign-up"})
     public ModelAndView signUp(ModelAndView modelAndView, HttpServletRequest request) {
         modelAndView.setViewName("sign-up");

+ 28 - 0
src/main/java/cn/minbb/edu/controller/web/UserController.java

@@ -0,0 +1,28 @@
+package cn.minbb.edu.controller.web;
+
+import cn.minbb.edu.service.UserService;
+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.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Controller
+@RequestMapping("user")
+public class UserController {
+
+    private UserService userService;
+
+    @Autowired
+    public UserController(UserService userService) {
+        this.userService = userService;
+    }
+
+    @GetMapping(value = "center")
+    public ModelAndView userCenterPage(ModelAndView modelAndView, HttpServletRequest request) {
+        modelAndView.setViewName("user-center");
+        return modelAndView;
+    }
+}

+ 86 - 0
src/main/java/cn/minbb/edu/model/Banner.java

@@ -0,0 +1,86 @@
+package cn.minbb.edu.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.util.Date;
+
+@Entity
+@Table(name = "banner")
+@NoArgsConstructor
+@AllArgsConstructor
+public class Banner implements Serializable {
+
+    @Getter
+    @Setter
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id", nullable = false, columnDefinition = "INT COMMENT '轮播实体'")
+    private Integer id;
+
+    @Getter
+    @Setter
+    @Column(name = "title", nullable = false, columnDefinition = "VARCHAR(64) COMMENT '标题'")
+    private String title;
+
+    @Getter
+    @Setter
+    @Column(name = "subtitle", nullable = true, columnDefinition = "VARCHAR(255) COMMENT '子标题'")
+    private String subtitle;
+
+    @Getter
+    @Setter
+    @Column(name = "action", nullable = false, columnDefinition = "VARCHAR(16) COMMENT '动作提示'")
+    private String action;
+
+    @Getter
+    @Setter
+    @Column(name = "cover", columnDefinition = "VARCHAR(255) COMMENT '封面'")
+    private String cover;
+
+    @Getter
+    @Setter
+    @Column(name = "path", columnDefinition = "VARCHAR(255) COMMENT '路径'")
+    private String path;
+
+    @Getter
+    @Setter
+    @Column(name = "remark", columnDefinition = "VARCHAR(255) COMMENT '备注'")
+    private String remark;
+
+    @Getter
+    @Setter
+    @Column(name = "user_id", nullable = false, columnDefinition = "INT COMMENT '关联用户'")
+    private Integer userId;
+
+    @Getter
+    @Setter
+    @Column(name = "is_enabled", nullable = false, columnDefinition = "TINYINT DEFAULT '1' COMMENT '已启用'")
+    private Boolean isEnabled;
+
+    @Getter
+    @Setter
+    @Column(name = "created_at", columnDefinition = "DATETIME COMMENT '创建时间'")
+    @CreationTimestamp
+    private Date createdAt;
+
+    @Getter
+    @Setter
+    @Column(name = "updated_at", columnDefinition = "DATETIME COMMENT '更新时间'")
+    @UpdateTimestamp
+    private Date updatedAt;
+
+    @Version
+    @Column(name = "version", columnDefinition = "INTEGER COMMENT '版本号'")
+    public Integer version;
+
+    public Banner(Boolean isEnabled) {
+        this.isEnabled = isEnabled;
+    }
+}

+ 9 - 0
src/main/java/cn/minbb/edu/model/Course.java

@@ -54,6 +54,11 @@ public class Course implements Serializable {
     @Column(name = "teacher_id", nullable = false, columnDefinition = "INT COMMENT '关联用户(教师)'")
     private Integer teacherId;
 
+    @Getter
+    @Setter
+    @Column(name = "is_enabled", nullable = false, columnDefinition = "TINYINT DEFAULT '1' COMMENT '已启用'")
+    private Boolean isEnabled;
+
     @Getter
     @Setter
     @Column(name = "created_at", columnDefinition = "DATETIME COMMENT '创建时间'")
@@ -74,4 +79,8 @@ public class Course implements Serializable {
     @Setter
     @Transient
     private User voTeacher;
+
+    public Course(Boolean isEnabled) {
+        this.isEnabled = isEnabled;
+    }
 }

+ 5 - 0
src/main/java/cn/minbb/edu/model/Homework.java

@@ -39,6 +39,11 @@ public class Homework implements Serializable {
     @Column(name = "remark", columnDefinition = "VARCHAR(255) COMMENT '备注'")
     private String remark;
 
+    @Getter
+    @Setter
+    @Column(name = "is_enabled", nullable = false, columnDefinition = "TINYINT DEFAULT '1' COMMENT '已启用'")
+    private Boolean isEnabled;
+
     @Getter
     @Setter
     @Column(name = "created_at", columnDefinition = "DATETIME COMMENT '创建时间'")

+ 5 - 0
src/main/java/cn/minbb/edu/model/Study.java

@@ -39,6 +39,11 @@ public class Study implements Serializable {
     @Column(name = "remark", columnDefinition = "VARCHAR(255) COMMENT '备注'")
     private String remark;
 
+    @Getter
+    @Setter
+    @Column(name = "is_enabled", nullable = false, columnDefinition = "TINYINT DEFAULT '1' COMMENT '已启用'")
+    private Boolean isEnabled;
+
     @Getter
     @Setter
     @Column(name = "created_at", columnDefinition = "DATETIME COMMENT '创建时间'")

+ 9 - 0
src/main/java/cn/minbb/edu/model/User.java

@@ -161,4 +161,13 @@ public class User implements UserDetails {
     public boolean isEnabled() {
         return this.isEnabled;
     }
+
+    public boolean hasRole(UserRole.Role role) {
+        for (UserRole userRole : getUserRoleSet()) {
+            if (userRole.getRole().equals(role)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }

+ 11 - 0
src/main/java/cn/minbb/edu/model/repository/BannerRepository.java

@@ -0,0 +1,11 @@
+package cn.minbb.edu.model.repository;
+
+import cn.minbb.edu.model.Banner;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface BannerRepository extends JpaRepository<Banner, Integer> {
+    List<Banner> findAllByIsEnabledTrue(Sort sort);
+}

+ 11 - 0
src/main/java/cn/minbb/edu/service/BannerService.java

@@ -0,0 +1,11 @@
+package cn.minbb.edu.service;
+
+import cn.minbb.edu.model.Banner;
+
+import java.util.List;
+
+public interface BannerService {
+    Banner save(Banner banner);
+
+    List<Banner> findAll();
+}

+ 6 - 0
src/main/java/cn/minbb/edu/service/CourseService.java

@@ -2,6 +2,12 @@ package cn.minbb.edu.service;
 
 import cn.minbb.edu.model.Course;
 
+import java.util.List;
+
 public interface CourseService {
     Course save(Course course);
+
+    Course findOneById(Integer id);
+
+    List<Course> findAllByUserId(Integer userId);
 }

+ 32 - 0
src/main/java/cn/minbb/edu/service/impl/BannerServiceImpl.java

@@ -0,0 +1,32 @@
+package cn.minbb.edu.service.impl;
+
+import cn.minbb.edu.model.Banner;
+import cn.minbb.edu.model.repository.BannerRepository;
+import cn.minbb.edu.service.BannerService;
+import cn.minbb.edu.util.SortTools;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class BannerServiceImpl implements BannerService {
+
+    private BannerRepository bannerRepository;
+
+    @Autowired
+    public BannerServiceImpl(BannerRepository bannerRepository) {
+        this.bannerRepository = bannerRepository;
+    }
+
+    @Override
+    public Banner save(Banner banner) {
+        banner.setIsEnabled(true);
+        return bannerRepository.save(banner);
+    }
+
+    @Override
+    public List<Banner> findAll() {
+        return bannerRepository.findAllByIsEnabledTrue(SortTools.createdAtDown());
+    }
+}

+ 20 - 0
src/main/java/cn/minbb/edu/service/impl/CourseServiceImpl.java

@@ -3,9 +3,13 @@ package cn.minbb.edu.service.impl;
 import cn.minbb.edu.model.Course;
 import cn.minbb.edu.model.repository.CourseRepository;
 import cn.minbb.edu.service.CourseService;
+import cn.minbb.edu.util.SortTools;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
 import org.springframework.stereotype.Service;
 
+import java.util.List;
+
 @Service
 public class CourseServiceImpl implements CourseService {
 
@@ -18,6 +22,22 @@ public class CourseServiceImpl implements CourseService {
 
     @Override
     public Course save(Course course) {
+        course.setIsEnabled(true);
         return courseRepository.save(course);
     }
+
+    @Override
+    public Course findOneById(Integer id) {
+        Course course = new Course(true);
+        course.setId(id);
+        Example<Course> example = Example.of(course);
+        return courseRepository.findOne(example).orElse(null);
+    }
+
+    @Override
+    public List<Course> findAllByUserId(Integer userId) {
+        Course course = new Course(true);
+        course.setTeacherId(userId);
+        return courseRepository.findAll(Example.of(course), SortTools.createdAtDown());
+    }
 }

+ 1 - 0
src/main/java/cn/minbb/edu/service/impl/HomeworkServiceImpl.java

@@ -18,6 +18,7 @@ public class HomeworkServiceImpl implements HomeworkService {
 
     @Override
     public Homework save(Homework homework) {
+        homework.setIsEnabled(true);
         return homeworkRepository.save(homework);
     }
 }

+ 1 - 0
src/main/java/cn/minbb/edu/service/impl/StudyServiceImpl.java

@@ -18,6 +18,7 @@ public class StudyServiceImpl implements StudyService {
 
     @Override
     public Study save(Study study) {
+        study.setIsEnabled(true);
         return studyRepository.save(study);
     }
 }

+ 12 - 0
src/main/java/cn/minbb/edu/storage/StorageException.java

@@ -0,0 +1,12 @@
+package cn.minbb.edu.storage;
+
+public class StorageException extends RuntimeException {
+
+    public StorageException(String message) {
+        super(message);
+    }
+
+    public StorageException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

+ 12 - 0
src/main/java/cn/minbb/edu/storage/StorageFileNotFoundException.java

@@ -0,0 +1,12 @@
+package cn.minbb.edu.storage;
+
+public class StorageFileNotFoundException extends StorageException {
+
+    public StorageFileNotFoundException(String message) {
+        super(message);
+    }
+
+    public StorageFileNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

+ 36 - 0
src/main/java/cn/minbb/edu/storage/StorageProperties.java

@@ -0,0 +1,36 @@
+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\\";
+}

+ 24 - 0
src/main/java/cn/minbb/edu/storage/StorageService.java

@@ -0,0 +1,24 @@
+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 StorageService {
+
+    void init();
+
+    void store(MultipartFile file);
+
+    void delete(String fileName);
+
+    void deleteAll();
+
+    Path load(String fileName);
+
+    Stream<Path> loadAll();
+
+    Resource loadAsResource(String fileName);
+}

+ 179 - 0
src/main/java/cn/minbb/edu/storage/StorageServiceFileSystem.java

@@ -0,0 +1,179 @@
+package cn.minbb.edu.storage;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+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.File;
+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.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+@Service
+public class StorageServiceFileSystem implements StorageService {
+
+    private Logger logger = LoggerFactory.getLogger(StorageServiceFileSystem.class);
+
+    private Path rootLocation;
+
+    private StorageProperties storageProperties;
+
+    @Autowired
+    public StorageServiceFileSystem(StorageProperties properties) {
+        this.storageProperties = properties;
+        this.rootLocation = Paths.get(properties.getLocation());
+    }
+
+    /**
+     * 初始化存储空间(各文件夹)
+     */
+    @Override
+    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());
+            List<File> dirList = new ArrayList<>(4);
+            dirList.add(imageDir);
+            dirList.add(videoDir);
+            dirList.add(avatarDir);
+            dirList.add(downloadDir);
+            if (!rootDir.exists()) {
+                Files.createDirectory(rootLocation);
+            }
+            for (File dir : dirList) {
+                if (!dir.exists()) {
+                    Files.createDirectory(dir.toPath());
+                }
+            }
+        } catch (IOException e) {
+            throw new StorageException("初始化存储空间失败!", e);
+        }
+    }
+
+    /**
+     * 存储文件
+     *
+     * @param file MultipartFile
+     */
+    @Override
+    public void store(MultipartFile file) {
+        try {
+            if (file.isEmpty()) {
+                throw new StorageException("Failed to store empty file " + file.getOriginalFilename());
+            }
+            Files.copy(file.getInputStream(), this.rootLocation.resolve(file.getOriginalFilename()));
+        } catch (IOException e) {
+            throw new StorageException("Failed to store file " + file.getOriginalFilename(), e);
+        }
+    }
+
+    /**
+     * 删除单个文件
+     *
+     * @param filename 文件名
+     */
+    @Override
+    public void delete(String filename) {
+        try {
+            Files.delete(load(filename));
+        } catch (IOException e) {
+            throw new StorageException("Failed to delete file " + filename, e);
+        }
+    }
+
+    /**
+     * 删除存储空间全部文件(递归删除)
+     */
+    @Override
+    public void deleteAll() {
+        FileSystemUtils.deleteRecursively(rootLocation.toFile());
+    }
+
+    /**
+     * 通过文件名加载路径
+     *
+     * @param filename 文件名
+     * @return Path
+     */
+    @Override
+    public Path load(String filename) {
+        return rootLocation.resolve(filename);
+    }
+
+    /**
+     * 加载全部文件(通过遍历文件夹而非数据库文件名)
+     *
+     * @return Stream<Path>
+     */
+    @Override
+    public Stream<Path> loadAll() {
+        try {
+            return Files.walk(this.rootLocation, 1)
+                    .filter(path -> !path.equals(this.rootLocation))
+                    .map(path -> rootLocation.relativize(path));
+        } catch (IOException e) {
+            throw new StorageException("Failed to read stored files", e);
+        }
+    }
+
+    /**
+     * 通过文件名加载资源
+     *
+     * @param filename 文件名
+     * @return 文件资源
+     */
+    @Override
+    public Resource loadAsResource(String filename) {
+        try {
+            Path file = load(filename);
+            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);
+        }
+    }
+
+    /**
+     * 存储文件到文件夹
+     *
+     * @param file 文件
+     * @param path 存储路径
+     * @return 存储成功返回文件名 失败返回空
+     */
+    public String storeToDictionary(MultipartFile file, Path path) {
+        String originalFilename = file.getOriginalFilename();
+        if (null == originalFilename) {
+            logger.error("文件名不能为空:{}", file);
+            return null;
+        }
+        String fileName = System.currentTimeMillis() + originalFilename.substring(originalFilename.lastIndexOf("."));
+        try {
+            if (file.isEmpty()) {
+                logger.error("文件不能为空:{}", originalFilename);
+                return null;
+            }
+            Files.copy(file.getInputStream(), path.resolve(fileName));
+        } catch (IOException e) {
+            logger.error("存储失败,原因为:{}", e);
+            return null;
+        }
+        return fileName;
+    }
+}

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

@@ -0,0 +1,9 @@
+package cn.minbb.edu.system;
+
+public class Const {
+
+    private Const() {
+    }
+
+    public static final String ACTIVE = "ACTIVE";
+}

+ 54 - 1
src/main/java/cn/minbb/edu/task/InitDataRunner.java

@@ -1,6 +1,10 @@
 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;
 import cn.minbb.edu.service.UserRoleService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -9,6 +13,8 @@ import org.springframework.boot.ApplicationRunner;
 import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 
+import java.util.List;
+
 @Order(1)
 @Component
 public class InitDataRunner implements ApplicationRunner {
@@ -16,9 +22,13 @@ public class InitDataRunner implements ApplicationRunner {
     private Logger logger = LoggerFactory.getLogger(InitDataRunner.class);
 
     private UserRoleService userRoleService;
+    private CourseService courseService;
+    private BannerService bannerService;
 
-    public InitDataRunner(UserRoleService userRoleService) {
+    public InitDataRunner(UserRoleService userRoleService, CourseService courseService, BannerService bannerService) {
         this.userRoleService = userRoleService;
+        this.courseService = courseService;
+        this.bannerService = bannerService;
     }
 
     @Override
@@ -30,5 +40,48 @@ public class InitDataRunner implements ApplicationRunner {
                 userRoleService.saveOne(new UserRole(role, role.getDescription()));
             }
         }
+        // 检查主页轮播 - 初始化轮播数据
+        List<Banner> bannerList = bannerService.findAll();
+        if (bannerList.isEmpty()) {
+            Banner banner1 = new Banner();
+            banner1.setTitle("一一一");
+            banner1.setSubtitle("一一一一一一一一一一一一一一一一一一一一一");
+            banner1.setAction("现在去学习");
+            banner1.setCover("");
+            banner1.setPath("");
+            banner1.setRemark("一");
+            banner1.setUserId(1);
+            bannerService.save(banner1);
+            Banner banner2 = new Banner();
+            banner2.setTitle("二二二");
+            banner2.setSubtitle("二二二二二二二二二二二二二二二二二二二二二");
+            banner2.setAction("了解更多");
+            banner2.setCover("");
+            banner2.setPath("");
+            banner2.setRemark("二");
+            banner2.setUserId(1);
+            bannerService.save(banner2);
+            Banner banner3 = new Banner();
+            banner3.setTitle("三三三");
+            banner3.setSubtitle("三三三三三三三三三三三三三三三三三三三三三");
+            banner3.setAction("了解更多");
+            banner3.setCover("");
+            banner3.setPath("");
+            banner3.setRemark("三");
+            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);
+        }
     }
 }

+ 50 - 0
src/main/java/cn/minbb/edu/util/SortTools.java

@@ -0,0 +1,50 @@
+package cn.minbb.edu.util;
+
+import org.springframework.data.domain.Sort;
+
+public class SortTools {
+
+    private SortTools() {
+    }
+
+    public static Sort idUp() {
+        return new Sort(Sort.Direction.ASC, "id");
+    }
+
+    public static Sort idDown() {
+        return new Sort(Sort.Direction.DESC, "id");
+    }
+
+    public static Sort createdAtUp() {
+        return new Sort(Sort.Direction.ASC, "createdAt");
+    }
+
+    public static Sort createdAtDown() {
+        return new Sort(Sort.Direction.DESC, "createdAt");
+    }
+
+    public static Sort basicAscSort(String sort) {
+        return new Sort(Sort.Direction.ASC, sort);
+    }
+
+    public static Sort basicDescSort(String sort) {
+        return new Sort(Sort.Direction.DESC, sort);
+    }
+
+    /**
+     * 基本排序
+     *
+     * @param order 默认降序 - desc
+     * @param sort  默认ID - id
+     * @return 排序规则
+     */
+    public static Sort basicSort(String order, String sort) {
+        if (order == null) {
+            order = "desc";
+        }
+        if (sort == null) {
+            sort = "id";
+        }
+        return new Sort(Sort.Direction.fromString(order), sort);
+    }
+}

+ 1 - 1
src/main/resources/application-dev.properties

@@ -1,6 +1,6 @@
 # ¿ª·¢»·¾³
 server.servlet.context-path=/
 # MySQL
-spring.datasource.url=jdbc:mysql://127.0.0.1:3306/edu?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
+spring.datasource.url=jdbc:mysql://127.0.0.1:3306/edu?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
 spring.datasource.username=
 spring.datasource.password=

+ 1 - 1
src/main/resources/application-pro.properties

@@ -1,6 +1,6 @@
 # Éú²ú»·¾³
 server.servlet.context-path=/
 # MySQL
-spring.datasource.url=jdbc:mysql://www.minbb.cn:3306/edu?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
+spring.datasource.url=jdbc:mysql://www.minbb.cn:3306/edu?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
 spring.datasource.username=Yumin
 spring.datasource.password=Wang19970305

+ 17 - 0
src/main/resources/templates/admin-config.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="zh-CN" xmlns="http://www.w3.org/1999/html"
+      xmlns:th="http://www.thymeleaf.org"
+      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{layouts/layout}">
+<head>
+    <meta charset="UTF-8"/>
+    <title>系统配置 - 知学教育</title>
+</head>
+<body>
+<th:block layout:fragment="content">
+    <div class="container" style="margin-top: 24px;">
+        知学教育APP,您身边的教育专家。
+    </div>
+</th:block>
+</body>
+</html>

+ 56 - 0
src/main/resources/templates/course-center.html

@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="zh-CN" xmlns="http://www.w3.org/1999/html"
+      xmlns:th="http://www.thymeleaf.org"
+      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{layouts/layout}">
+<head>
+    <meta charset="UTF-8"/>
+    <title>课程中心 - 知学教育</title>
+</head>
+<body>
+<th:block layout:fragment="content">
+    <div class="container" style="margin-top: 96px;">
+        <section class="jumbotron text-center" style="padding: 48px;">
+            <div class="container">
+                <h1 class="jumbotron-heading">课程中心</h1>
+                <p class="lead text-muted" style="padding: 0;">在这里添加您的课程</p>
+                <p style="margin-bottom: 0;">
+                    <a href="/course/create" class="btn btn-primary my-2">创建课程</a>
+                    <a href="#" class="btn btn-secondary my-2">管理课程</a>
+                </p>
+            </div>
+        </section>
+
+        <div class="album py-5 bg-light">
+            <div class="container">
+                <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()}"/>
+                            <div class="card-body">
+                                <p class="card-text" th:text="${course.getIntroduction()}">课程介绍</p>
+                                <div class="d-flex justify-content-between align-items-center">
+                                    <small class="text-muted" th:text="${#dates.format(course.getCreatedAt(), 'yyyy-MM-dd HH:mm:ss')}"></small>
+                                    <div class="btn-group">
+                                        <button type="button" class="btn btn-sm btn-outline-secondary">编辑</button>
+                                        <button class="btn btn-sm btn-outline-secondary" type="button" onclick="viewer(this);"
+                                                th:data-id="${course.getId()}">查看
+                                        </button>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <script>
+        function viewer(obj) {
+            window.location.href = "/course/player?id=" + obj.dataset.id;
+        }
+    </script>
+</th:block>
+</body>
+</html>

+ 17 - 0
src/main/resources/templates/course-create.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="zh-CN" xmlns="http://www.w3.org/1999/html"
+      xmlns:th="http://www.thymeleaf.org"
+      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{layouts/layout}">
+<head>
+    <meta charset="UTF-8"/>
+    <title>创建课程 - 知学教育</title>
+</head>
+<body>
+<th:block layout:fragment="content">
+    <div class="container" style="margin-top: 24px;">
+        创建课程
+    </div>
+</th:block>
+</body>
+</html>

+ 44 - 0
src/main/resources/templates/course-player.html

@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="zh-CN" xmlns="http://www.w3.org/1999/html"
+      xmlns:th="http://www.thymeleaf.org"
+      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{layouts/layout}">
+<head>
+    <meta charset="UTF-8"/>
+    <title th:if="${course == null}">课程不存在 - 知学教育</title>
+    <title th:unless="${course == null}" th:text="${course.getName()} + ' - 知学教育'"></title>
+</head>
+<body>
+<th:block layout:fragment="content">
+    <div class="jumbotron" th:if="${course == null}">
+        <div class="col-sm-8 mx-auto">
+            <h1>抱歉哦</h1>
+            <p>您要查找的课程不存在</p>
+            <p>建议您去课程广场看看哦,那里有更多优质课程等着您!</p>
+            <p><a class="btn btn-primary" href="/course/square" role="button">课程广场 »</a></p>
+        </div>
+    </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>
+            <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>
+            </button>
+
+            <div class="collapse navbar-collapse" id="navbarsExample02">
+                <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>
+                        </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>
+    </div>
+</th:block>
+</body>
+</html>

+ 17 - 0
src/main/resources/templates/course-square.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="zh-CN" xmlns="http://www.w3.org/1999/html"
+      xmlns:th="http://www.thymeleaf.org"
+      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{layouts/layout}">
+<head>
+    <meta charset="UTF-8"/>
+    <title>课程广场 - 知学教育</title>
+</head>
+<body>
+<th:block layout:fragment="content">
+    <div class="container" style="margin-top: 24px;">
+        知学教育APP,您身边的教育专家。
+    </div>
+</th:block>
+</body>
+</html>

+ 15 - 6
src/main/resources/templates/fragments/header.html

@@ -9,7 +9,7 @@
     <link rel="stylesheet" href="https://unpkg.com/bootstrap-material-design@4.1.1/dist/css/bootstrap-material-design.min.css"/>
 </head>
 <body>
-<nav class="navbar navbar-expand-lg navbar-light bg-light" th:fragment="header">
+<nav class="navbar navbar-expand-lg navbar-light fixed-top bg-light" th:fragment="header">
     <a class="navbar-brand" href="/">知学教育</a>
     <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-supported-content"
             aria-controls="navbar-supported-content" aria-expanded="false" aria-label="Toggle navigation">
@@ -18,11 +18,17 @@
 
     <div class="collapse navbar-collapse" id="navbar-supported-content">
         <ul class="navbar-nav mr-auto">
-            <li class="nav-item active">
+            <li class="nav-item" th:classappend="${ACTIVE == 'index'}? 'active'">
                 <a class="nav-link" href="/">首页<span class="sr-only">(current)</span></a>
             </li>
+            <li class="nav-item" th:class="${ACTIVE == 'about'}? 'active'">
+                <a class="nav-link" href="/about">关于</a>
+            </li>
             <li class="nav-item">
-                <a class="nav-link" href="#">链接</a>
+                <a class="nav-link" href="#!" data-toggle="tooltip" data-placement="bottom" data-html="true"
+                   title="<img src='../static/favicon.ico' alt='下载二维码'/><p>Version: 1.0</p>">
+                    下载二维码
+                </a>
             </li>
         </ul>
         <form class="form-inline my-2 my-lg-0">
@@ -39,8 +45,12 @@
                     用户名
                 </a>
                 <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
-                    <a class="dropdown-item" href="#">用户中心</a>
-                    <a class="dropdown-item" href="#">课程中心</a>
+                    <a class="dropdown-item" href="/user/center">用户中心</a>
+                    <a class="dropdown-item" href="/course/center">课程中心</a>
+                    <div th:if="${user.hasRole(T(cn.minbb.edu.model.UserRole.Role).ADMIN)}">
+                        <div class="dropdown-divider"></div>
+                        <a class="dropdown-item" href="/admin/config">系统配置</a>
+                    </div>
                     <div class="dropdown-divider"></div>
                     <a class="dropdown-item" href="/sign-out">退出登录</a>
                 </div>
@@ -54,7 +64,6 @@
 <script src="https://unpkg.com/bootstrap-material-design@4.1.1/dist/js/bootstrap-material-design.js"></script>
 <script>
     $(document).ready(function () {
-        $('body').bootstrapMaterialDesign();
     });
 </script>
 </body>

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

@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html lang="zh-CN" xmlns="http://www.w3.org/1999/html"
+      xmlns:th="http://www.thymeleaf.org"
+      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{layouts/layout}">
+<head>
+    <meta charset="UTF-8"/>
+    <title>关于 - 知学教育</title>
+
+    <style>
+        .marketing .col-lg-4 {
+            margin-bottom: 1.5rem;
+            text-align: center;
+        }
+
+        .marketing h2 {
+            font-weight: 400;
+        }
+
+        .marketing .col-lg-4 p {
+            margin-right: .75rem;
+            margin-left: .75rem;
+        }
+    </style>
+</head>
+<body>
+<th:block layout:fragment="content">
+    <div class="jumbotron">
+        <div class="container">
+            <h1 class="display-3">知学教育APP</h1>
+            <p>This is a template for a simple marketing or informational website. It includes a large callout called a jumbotron and three
+                supporting pieces of content. Use it as a starting point to create something more unique.
+            </p>
+            <hr class="my-4">
+            <p>It uses utility classes for typography and spacing to space content out within the larger container.</p>
+            <p><a class="btn btn-primary btn-lg" href="/download" role="button">立即下载 &raquo;</a></p>
+        </div>
+    </div>
+
+    <div class="container marketing">
+        <div class="row">
+            <div class="col-lg-4">
+                <img class="rounded-circle" src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="
+                     alt="Generic placeholder image" width="140" height="140"/>
+                <h2>Heading</h2>
+                <p>Donec sed odio dui. Etiam porta sem malesuada magna mollis euismod. Nullam id dolor id nibh ultricies vehicula ut id elit. Morbi
+                    leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna.</p>
+                <p><a class="btn btn-secondary" href="#" role="button">View details &raquo;</a></p>
+            </div>
+
+            <div class="col-lg-4">
+                <img class="rounded-circle" src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="
+                     alt="Generic placeholder image" width="140" height="140"/>
+                <h2>Heading</h2>
+                <p>Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cras mattis consectetur purus sit
+                    amet fermentum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh.</p>
+                <p><a class="btn btn-secondary" href="#" role="button">View details &raquo;</a></p>
+            </div>
+
+            <div class="col-lg-4">
+                <img class="rounded-circle" src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="
+                     alt="Generic placeholder image" width="140" height="140"/>
+                <h2>Heading</h2>
+                <p>Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper.
+                    Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p>
+                <p><a class="btn btn-secondary" href="#" role="button">View details &raquo;</a></p>
+            </div>
+        </div>
+
+        <hr class="featurette-divider">
+
+        <div class="row featurette">
+            <div class="col-md-7">
+                <h2 class="featurette-heading">First featurette heading. <span class="text-muted">It'll blow your mind.</span></h2>
+                <p class="lead">Donec ullamcorper nulla non metus auctor fringilla. Vestibulum id ligula porta felis euismod semper. Praesent commodo
+                    cursus magna, vel scelerisque nisl consectetur. Fusce dapibus, tellus ac cursus commodo.</p>
+            </div>
+            <div class="col-md-5">
+                <img class="featurette-image img-fluid mx-auto"
+                     data-src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" alt=""/>
+            </div>
+        </div>
+
+        <hr class="featurette-divider">
+
+        <div class="row featurette">
+            <div class="col-md-7 order-md-2">
+                <h2 class="featurette-heading">Oh yeah, it's that good. <span class="text-muted">See for yourself.</span></h2>
+                <p class="lead">Donec ullamcorper nulla non metus auctor fringilla. Vestibulum id ligula porta felis euismod semper. Praesent commodo
+                    cursus magna, vel scelerisque nisl consectetur. Fusce dapibus, tellus ac cursus commodo.</p>
+            </div>
+            <div class="col-md-5 order-md-1">
+                <img class="featurette-image img-fluid mx-auto"
+                     data-src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" alt=""/>
+            </div>
+        </div>
+
+        <hr class="featurette-divider">
+
+        <div class="row featurette">
+            <div class="col-md-7">
+                <h2 class="featurette-heading">
+                    And lastly, this one.
+                    <span class="text-muted">Checkmate.</span>
+                </h2>
+                <p class="lead">
+                    Donec ullamcorper nulla non metus auctor fringilla. Vestibulum id ligula porta felis euismod semper. Praesent commodo
+                    cursus magna, vel scelerisque nisl consectetur. Fusce dapibus, tellus ac cursus commodo.
+                </p>
+            </div>
+            <div class="col-md-5">
+                <img class="featurette-image img-fluid mx-auto"
+                     data-src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" alt=""/>
+            </div>
+        </div>
+
+        <hr class="featurette-divider">
+    </div>
+</th:block>
+</body>
+</html>

+ 42 - 37
src/main/resources/templates/index.html

@@ -5,7 +5,7 @@
       layout:decorate="~{layouts/layout}">
 <head>
     <meta charset="UTF-8"/>
-    <title>知学教育</title>
+    <title>首页 - 知学教育</title>
 
     <style>
         /* Since positioning the image, we need to help out the caption */
@@ -81,59 +81,64 @@
 </head>
 <body>
 <th:block layout:fragment="content">
-    <div id="index-arousel" class="carousel slide" data-ride="carousel">
+    <div class="carousel slide" id="index-carousel" data-ride="carousel">
         <ol class="carousel-indicators">
-            <li data-target="#index-arousel" data-slide-to="0" class=""></li>
-            <li data-target="#index-arousel" data-slide-to="1" class="active"></li>
-            <li data-target="#index-arousel" data-slide-to="2" class=""></li>
+            <li data-target="#index-carousel" th:each="banner, stat : ${bannerList}"
+                th:data-slide-to="${stat.index}" th:class="${stat.index == 0}? 'active'"></li>
         </ol>
         <div class="carousel-inner">
-            <div class="carousel-item">
-                <img class="first-slide" src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" alt="First slide">
+            <div class="carousel-item" th:each="banner, stat : ${bannerList}" th:classappend="${stat.index == 0}? 'active'">
+                <img class="first-slide" src="" alt="" th:src="${banner.getCover()}" th:alt="${banner.getRemark()}"/>
                 <div class="container">
-                    <div class="carousel-caption text-left">
-                        <h1>Example headline.</h1>
-                        <p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam
-                            id dolor id nibh ultricies vehicula ut id elit.</p>
-                        <p><a class="btn btn-lg btn-primary" href="#" role="button">现在去学习</a></p>
-                    </div>
-                </div>
-            </div>
-            <div class="carousel-item active">
-                <img class="second-slide" src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" alt="Second slide">
-                <div class="container">
-                    <div class="carousel-caption">
-                        <h1>Another example headline.</h1>
-                        <p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam
-                            id dolor id nibh ultricies vehicula ut id elit.</p>
-                        <p><a class="btn btn-lg btn-primary" href="#" role="button">了解更多</a></p>
-                    </div>
-                </div>
-            </div>
-            <div class="carousel-item">
-                <img class="third-slide" src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" alt="Third slide">
-                <div class="container">
-                    <div class="carousel-caption text-right">
-                        <h1>One more for good measure.</h1>
-                        <p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam
-                            id dolor id nibh ultricies vehicula ut id elit.</p>
-                        <p><a class="btn btn-lg btn-primary" href="#" role="button">了解更多</a></p>
+                    <div class="carousel-caption" th:classappend="${stat.count % 3 == 1}? 'text-left' : (${stat.count % 3 == 2}? '' : 'text-right')">
+                        <h1 th:text="${banner.getTitle()}"></h1>
+                        <p th:text="${banner.getSubtitle()}"></p>
+                        <p>
+                            <a class="btn btn-lg btn-success" href="" role="button" th:href="${banner.getPath()}" th:text="${banner.getAction()}"></a>
+                        </p>
                     </div>
                 </div>
             </div>
         </div>
-        <a class="carousel-control-prev" href="#index-arousel" role="button" data-slide="prev">
+        <a class="carousel-control-prev" href="#index-carousel" role="button" data-slide="prev">
             <span class="carousel-control-prev-icon" aria-hidden="true"></span>
             <span class="sr-only">Previous</span>
         </a>
-        <a class="carousel-control-next" href="#index-arousel" role="button" data-slide="next">
+        <a class="carousel-control-next" href="#index-carousel" role="button" data-slide="next">
             <span class="carousel-control-next-icon" aria-hidden="true"></span>
             <span class="sr-only">Next</span>
         </a>
     </div>
 
     <div class="container" style="margin-top: 24px;">
-        知学教育APP
+        <h1>知学教育APP,您身边的教育专家。</h1>
+        <br/>
+        <h3>1、项目背景:</h3>
+        <p>
+            随着网络技术的飞速发展和网络全球化的逐步推进,移动智能设备已经广泛进入人们的日常生活并成为其中不可或缺的一部分。人们的学习需求对移动智能终端及移动互联网的依赖也随之越来越严重。在这种趋势下,移动学习成为了被广泛采用并接受的新型学习方式。
+        </p>
+        <h3>2、主要内容:</h3>
+        <p>
+            ①在认真参考和阅读有关资料的基础上,对使用学习系统需求分析进行较为全面、细致的描述。②给出相关软件设计的整体结构,工作流程,功能模块设置、系统性能要求。③结合最前沿的软件开发技术,对部分应用实例进行设计与实现。
+        </p>
+        <h3>3、现有条件:</h3>
+        <p>
+            硬件配备:个人电脑一台,支持连接到互联网的智能手机一部,阿里云服务器。
+            <br/>
+            软件配置:系统采用Microsoft公司的Windows 10企业版,安装有Office 2016系列办公软件,IntelliJ IDEA,Android Studio,MySQL等等, 服务器环境支持Apache Tomcat 9.0以及Nginx运行环境。
+            <br/>
+            资料准备:第一行代码、上网查阅相关资料、借阅相关书籍。
+        </p>
+        <h3>4、时间安排:</h3>
+        <p>
+            第1-2周熟悉题目,对Android编程技术进行熟悉以及对学习系统的设计进行分析,完成开题报告、文献综述以及需求分析。第3-5周完成总体设计,根据应用需要搭建软件框架。第6-9周初步完成系统详细设计,实现全部应用功能。第10-12周对系统进行细节完善和优化。第13-16周根据设计过程中的记录文档及其功能编写毕业论文。
+        </p>
+        <h3>5、预期成果及表现形式:</h3>
+        <p>
+            预期成果:为用户提供功能完全的学习系统的软件服务,使用户可以通过体学习系统学习各种资料等。
+            <br/>
+            表现形式:以友好的图形化界面实现该系统,方便各个用户的使用,提交毕业设计论文。
+        </p>
     </div>
 </th:block>
 </body>

+ 2 - 1
src/main/resources/templates/layouts/layout.html

@@ -24,7 +24,7 @@
 <body style="margin-bottom: 60px;">
 <header th:replace="~{fragments/header :: header}"></header>
 
-<div style="margin-top: 1px; margin-bottom: 96px;">
+<div style="margin-top: 57px; margin-bottom: 96px;">
     <div layout:fragment="content">
         <p>内容</p>
     </div>
@@ -38,6 +38,7 @@
 <script>
     $(document).ready(function () {
         $('body').bootstrapMaterialDesign();
+        $('[data-toggle="tooltip"]').tooltip();
     });
 </script>
 </body>

+ 1 - 1
src/main/resources/templates/sign-in.html

@@ -5,7 +5,7 @@
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
-    <title>用户登录</title>
+    <title>用户登录 - 知学教育</title>
 
     <link href="https://unpkg.com/bootstrap-material-design@4.1.1/dist/css/bootstrap-material-design.min.css" rel="stylesheet"/>
     <link rel="stylesheet" href="../static/css/user-sign.css" th:href="@{/css/user-sign.css}"/>

+ 1 - 1
src/main/resources/templates/sign-up.html

@@ -6,7 +6,7 @@
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
-    <title>用户注册</title>
+    <title>用户注册 - 知学教育</title>
 
     <link href="https://unpkg.com/bootstrap-material-design@4.1.1/dist/css/bootstrap-material-design.min.css" rel="stylesheet"/>
     <link rel="stylesheet" href="../static/css/user-sign.css" th:href="@{/css/user-sign.css}"/>

+ 17 - 0
src/main/resources/templates/user-center.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="zh-CN" xmlns="http://www.w3.org/1999/html"
+      xmlns:th="http://www.thymeleaf.org"
+      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{layouts/layout}">
+<head>
+    <meta charset="UTF-8"/>
+    <title>用户中心 - 知学教育</title>
+</head>
+<body>
+<th:block layout:fragment="content">
+    <div class="container" style="margin-top: 24px;">
+        知学教育APP,您身边的教育专家。
+    </div>
+</th:block>
+</body>
+</html>