Browse Source

1. 新建实体类
2. 新建事件处理程序
3. 新建页面模板
4. 新建注册登录页面

王育民 5 years ago
parent
commit
96c7ff0b28
30 changed files with 1134 additions and 47 deletions
  1. 6 17
      pom.xml
  2. 2 0
      src/main/java/cn/minbb/job/JobApplication.java
  3. 25 0
      src/main/java/cn/minbb/job/config/AuditorAwareConfig.java
  4. 1 26
      src/main/java/cn/minbb/job/config/WebSecurityConfig.java
  5. 2 1
      src/main/java/cn/minbb/job/controller/web/MainController.java
  6. 24 0
      src/main/java/cn/minbb/job/controller/web/SignController.java
  7. 67 0
      src/main/java/cn/minbb/job/data/Const.java
  8. 104 0
      src/main/java/cn/minbb/job/data/ResponseResult.java
  9. 24 0
      src/main/java/cn/minbb/job/handler/AccessDenied.java
  10. 33 0
      src/main/java/cn/minbb/job/handler/AppExceptionHandler.java
  11. 47 0
      src/main/java/cn/minbb/job/handler/LoginAuthenticationFailureHandler.java
  12. 57 0
      src/main/java/cn/minbb/job/handler/LoginAuthenticationSuccessHandler.java
  13. 19 0
      src/main/java/cn/minbb/job/handler/LoginUrlEntryPoint.java
  14. 45 0
      src/main/java/cn/minbb/job/handler/LogoutSuccessHandler.java
  15. 52 0
      src/main/java/cn/minbb/job/model/Auditable.java
  16. 51 0
      src/main/java/cn/minbb/job/model/Banner.java
  17. 4 0
      src/main/java/cn/minbb/job/model/Resume.java
  18. 7 0
      src/main/java/cn/minbb/job/model/repository/BannerRepository.java
  19. 9 0
      src/main/java/cn/minbb/job/service/BannerService.java
  20. 27 0
      src/main/java/cn/minbb/job/service/impl/BannerServiceImpl.java
  21. 1 1
      src/main/java/cn/minbb/job/util/RequestUtil.java
  22. 3 0
      src/main/resources/application.properties
  23. 122 0
      src/main/resources/static/css/user-sign.css
  24. 67 0
      src/main/resources/templates/fragments/footer.html
  25. 70 0
      src/main/resources/templates/fragments/header.html
  26. 11 2
      src/main/resources/templates/index.html
  27. 45 0
      src/main/resources/templates/layouts/layout.html
  28. 92 0
      src/main/resources/templates/sign-in.html
  29. 100 0
      src/main/resources/templates/sign-up.html
  30. 17 0
      src/main/resources/templates/user-center.html

+ 6 - 17
pom.xml

@@ -102,11 +102,6 @@
             <artifactId>spring-boot-starter-actuator</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>de.codecentric</groupId>
-            <artifactId>spring-boot-admin-starter-client</artifactId>
-        </dependency>
-
         <!-- 服务注册与发现中心 Eureka Client https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
         <dependency>
             <groupId>org.springframework.cloud</groupId>
@@ -115,15 +110,16 @@
         </dependency>
 
         <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-devtools</artifactId>
-            <scope>runtime</scope>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
             <optional>true</optional>
         </dependency>
 
+        <!-- 热部署 -->
         <dependency>
-            <groupId>org.projectlombok</groupId>
-            <artifactId>lombok</artifactId>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <scope>runtime</scope>
             <optional>true</optional>
         </dependency>
 
@@ -150,13 +146,6 @@
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
-            <dependency>
-                <groupId>de.codecentric</groupId>
-                <artifactId>spring-boot-admin-dependencies</artifactId>
-                <version>2.2.2</version>
-                <type>pom</type>
-                <scope>import</scope>
-            </dependency>
         </dependencies>
     </dependencyManagement>
 

+ 2 - 0
src/main/java/cn/minbb/job/JobApplication.java

@@ -4,8 +4,10 @@ import cn.minbb.job.controller.UniqueNameGenerator;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.context.annotation.ComponentScan;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
 
 @ComponentScan(nameGenerator = UniqueNameGenerator.class)
+@EnableJpaAuditing
 @SpringBootApplication
 public class JobApplication {
 

+ 25 - 0
src/main/java/cn/minbb/job/config/AuditorAwareConfig.java

@@ -0,0 +1,25 @@
+package cn.minbb.job.config;
+
+import lombok.extern.log4j.Log4j2;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.domain.AuditorAware;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.util.Optional;
+
+@Configuration
+@Log4j2
+public class AuditorAwareConfig implements AuditorAware<String> {
+    @Override
+    public Optional<String> getCurrentAuditor() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        log.info("审计用户:" + authentication);
+        return Optional.ofNullable(SecurityContextHolder.getContext())
+                .map(SecurityContext::getAuthentication)
+                .filter(Authentication::isAuthenticated)
+                .map(Authentication::getPrincipal)
+                .map(String.class::cast);
+    }
+}

+ 1 - 26
src/main/java/cn/minbb/job/config/WebSecurityConfig.java

@@ -6,13 +6,7 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
-import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
-import org.springframework.security.web.util.matcher.RegexRequestMatcher;
-import org.springframework.security.web.util.matcher.RequestMatcher;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.regex.Pattern;
 
 @Configuration
 @EnableWebSecurity
@@ -28,25 +22,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 .anyRequest().permitAll()
                 .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
                 .logoutSuccessUrl("/logout")
-                .and().csrf()
-                .csrfTokenRepository(new HttpSessionCsrfTokenRepository())
-                .requireCsrfProtectionMatcher(new RequestMatcher() {
-                    // 放行这几种请求
-                    private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
-                    // 放行 REST 请求,当然后面 REST 与 WEB 将会分开,到时这里可以删除
-                    private RegexRequestMatcher unprotectedMatcher = new RegexRequestMatcher("^/rest/.*", null);
-
-                    @Override
-                    public boolean matches(HttpServletRequest httpServletRequest) {
-                        if (allowedMethods.matcher(httpServletRequest.getMethod()).matches()) {
-                            return false;
-                        }
-                        String servletPath = httpServletRequest.getServletPath();
-                        if (servletPath.contains("/druid")) {
-                            return false;
-                        }
-                        return !unprotectedMatcher.matches(httpServletRequest);
-                    }
-                });
+                .and().csrf().disable();
     }
 }

+ 2 - 1
src/main/java/cn/minbb/job/controller/web/MainController.java

@@ -1,5 +1,6 @@
 package cn.minbb.job.controller.web;
 
+import cn.minbb.job.data.Const;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -13,7 +14,7 @@ public class MainController {
 
     @GetMapping("")
     public ModelAndView indexPage(ModelAndView modelAndView) {
-        modelAndView.setViewName("index");
+        modelAndView.setViewName(Const.ViewName.VIEW_INDEX);
         return modelAndView;
     }
 }

+ 24 - 0
src/main/java/cn/minbb/job/controller/web/SignController.java

@@ -0,0 +1,24 @@
+package cn.minbb.job.controller.web;
+
+import cn.minbb.job.data.Const;
+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;
+
+@Controller
+@RequestMapping("")
+public class SignController {
+
+    @GetMapping("/login")
+    public ModelAndView loginPage(ModelAndView modelAndView) {
+        modelAndView.setViewName(Const.ViewName.VIEW_SIGN_IN);
+        return modelAndView;
+    }
+
+    @GetMapping("/register")
+    public ModelAndView registerPage(ModelAndView modelAndView) {
+        modelAndView.setViewName(Const.ViewName.VIEW_SIGN_UP);
+        return modelAndView;
+    }
+}

+ 67 - 0
src/main/java/cn/minbb/job/data/Const.java

@@ -0,0 +1,67 @@
+package cn.minbb.job.data;
+
+/**
+ * 全局常量
+ */
+public class Const {
+
+    private Const() {
+    }
+
+    /**
+     * 返回码常量
+     */
+    public interface ReturnCode {
+        Integer SUCCESS = 0;
+        Integer FAILED = -1;
+        Integer REQUEST_TOO_BUSY = -2;
+        Integer ERROR = -9;
+    }
+
+    /**
+     * 返回信息常量
+     */
+    public interface ReturnMessage {
+        String SUCCESS = "请求成功";
+        String PARAMS_ERROR = "参数校验错误";
+        String OBJECT_IS_NULL = "对象为空";
+        String REQUEST_TOO_BUSY = "请求过于频繁";
+        String ERROR = "服务器内部错误";
+        String REMOTE_SERVICE_INVOKE_ERROR = "远程服务调用错误";
+    }
+
+    /**
+     * 应用常量
+     */
+    public interface Application {
+    }
+
+    /**
+     * 视图名称常量
+     */
+    public interface ViewName {
+        String VIEW_REDIRECT = "redirect:/";
+        String VIEW_ERROR = "error";
+        String VIEW_INDEX = "index";
+        String VIEW_SIGN_IN = "sign-in";
+        String VIEW_SIGN_UP = "sign-up";
+        String VIEW_ABOUT = "about";
+        String VIEW_FEEDBACK = "feedback";
+    }
+
+    /**
+     * 键常量
+     */
+    public interface Key {
+        String KEY_USER = "USER";
+        String KEY_ACTIVE = "ACTIVE";
+        String KEY_TITLE = "TITLE";
+        String KEY_SUBTITLE = "SUBTITLE";
+        String KEY_PAGE = "PAGE";
+        String KEY_TOTAL = "TOTAL";
+        String KEY_APP_NAME = "APP_NAME";
+        String KEY_DATA = "DATA";
+        String KEY_DATASET = "DATASET";
+        String KEY_REMARK = "REMARK";
+    }
+}

+ 104 - 0
src/main/java/cn/minbb/job/data/ResponseResult.java

@@ -0,0 +1,104 @@
+package cn.minbb.job.data;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+@AllArgsConstructor
+@ApiModel(value = "请求结果数据", description = "请求结果数据模型")
+@Data
+@NoArgsConstructor
+public class ResponseResult<T extends Serializable> implements Serializable {
+
+    @ApiModelProperty(value = "是否处理成功", name = "success", example = "true")
+    private Boolean success = Boolean.TRUE;
+
+    @ApiModelProperty(value = "返回码", name = "code", example = "0")
+    private Integer code = 0;
+
+    @ApiModelProperty(value = "提示信息", name = "message")
+    private String message = "";
+
+    @ApiModelProperty(value = "返回数据", name = "data")
+    private T data;
+
+    @ApiModelProperty(value = "返回数据集", name = "dataset")
+    private Collection<T> dataset;
+
+    @ApiModelProperty(value = "返回对象", name = "object")
+    private Object object;
+
+    public static <T extends Serializable> ResponseResult.Builder<T> ok(Boolean success) {
+        return new ResultBuilder<>(success);
+    }
+
+    private static class ResultBuilder<T extends Serializable> implements Builder<T> {
+
+        private Boolean success;
+        private Integer code;
+        private String message;
+        private T data;
+        private Collection<T> dataset;
+        private Object object;
+
+        public ResultBuilder(Boolean success) {
+            this.code = Const.ReturnCode.SUCCESS;
+            this.message = Const.ReturnMessage.SUCCESS;
+            this.success = success;
+        }
+
+        @Override
+        public Builder<T> code(Integer code) {
+            this.code = code;
+            return this;
+        }
+
+        @Override
+        public Builder<T> message(String message) {
+            this.message = message;
+            return this;
+        }
+
+        @Override
+        public Builder<T> data(T data) {
+            this.data = data;
+            return this;
+        }
+
+        @Override
+        public Builder<T> dataset(Collection<T> dataset) {
+            this.dataset = dataset;
+            return this;
+        }
+
+        @Override
+        public Builder<T> object(Object object) {
+            this.object = object;
+            return this;
+        }
+
+        @Override
+        public ResponseResult<T> build() {
+            return new ResponseResult<>(success, code, message, data, dataset, object);
+        }
+    }
+
+    public interface Builder<T extends Serializable> {
+        Builder<T> code(Integer code);
+
+        Builder<T> message(String message);
+
+        Builder<T> data(T data);
+
+        Builder<T> dataset(Collection<T> dataset);
+
+        Builder<T> object(Object object);
+
+        ResponseResult<T> build();
+    }
+}

+ 24 - 0
src/main/java/cn/minbb/job/handler/AccessDenied.java

@@ -0,0 +1,24 @@
+package cn.minbb.job.handler;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.WebAttributes;
+import org.springframework.security.web.access.AccessDeniedHandler;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 当一个已授权(或已登陆)的用户请求访问他权限之外的资源时, handle 方法将会被调用
+ */
+public class AccessDenied implements AccessDeniedHandler {
+
+    @Override
+    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
+        httpServletRequest.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, new Exception("访问权限不足"));
+        httpServletResponse.setStatus(HttpStatus.OK.value());
+        httpServletResponse.sendRedirect("/login?error=true");
+    }
+}

+ 33 - 0
src/main/java/cn/minbb/job/handler/AppExceptionHandler.java

@@ -0,0 +1,33 @@
+package cn.minbb.job.handler;
+
+import cn.minbb.job.data.Const;
+import cn.minbb.job.data.ResponseResult;
+import cn.minbb.job.util.RequestUtil;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Log4j2
+@RestControllerAdvice
+public class AppExceptionHandler {
+
+    @ExceptionHandler(value = Exception.class)
+    public Object errorHandler(HttpServletRequest request, HttpServletResponse response, Exception e) throws Exception {
+        e.printStackTrace();
+        String error = "全局捕获异常\n" + e.getMessage();
+        log.error(error);
+        if (RequestUtil.isAjax(request)) {
+            return ResponseResult.ok(false).code(Const.ReturnCode.FAILED).message(e.getMessage()).build();
+        } else {
+            ModelAndView modelAndView = new ModelAndView();
+            modelAndView.addObject("exception", e);
+            modelAndView.addObject("url", request.getRequestURL());
+            modelAndView.setViewName("app-error");
+            return modelAndView;
+        }
+    }
+}

+ 47 - 0
src/main/java/cn/minbb/job/handler/LoginAuthenticationFailureHandler.java

@@ -0,0 +1,47 @@
+package cn.minbb.job.handler;
+
+import cn.minbb.job.data.Const;
+import cn.minbb.job.data.ResponseResult;
+import cn.minbb.job.util.RequestUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.WebAttributes;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+@Log4j2
+public class LoginAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
+
+    private final ObjectMapper objectMapper;
+
+    @Autowired
+    public LoginAuthenticationFailureHandler(ObjectMapper objectMapper) {
+        this.objectMapper = objectMapper;
+    }
+
+    @Override
+    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
+        String message = objectMapper.writeValueAsString(exception.getMessage());
+        log.info("用户登录失败 = {}", message);
+        if (RequestUtil.isAjax(request)) {
+            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
+            response.setContentType("application/json;charset=UTF-8");
+            response.getWriter().write(objectMapper.writeValueAsString(
+                    ResponseResult.ok(false).code(Const.ReturnCode.FAILED).message(exception.getMessage()).build()));
+        } else {
+            // 异常写入 Session
+            request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
+            response.setStatus(HttpStatus.OK.value());
+            response.sendRedirect("/sign-in?error=true");
+        }
+    }
+}

+ 57 - 0
src/main/java/cn/minbb/job/handler/LoginAuthenticationSuccessHandler.java

@@ -0,0 +1,57 @@
+package cn.minbb.job.handler;
+
+import cn.minbb.job.util.RequestUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
+import org.springframework.security.web.savedrequest.SavedRequest;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+@Log4j2
+public class LoginAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
+
+    private final ObjectMapper objectMapper;
+
+    @Autowired
+    public LoginAuthenticationSuccessHandler(ObjectMapper objectMapper) {
+        this.objectMapper = objectMapper;
+    }
+
+    // Authentication 封装认证信息
+    @Override
+    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
+        String auth = objectMapper.writeValueAsString(authentication);
+        log.info("用户登陆成功 = {}", auth);
+        // 登录方式不同,Authentication 不同
+        if (RequestUtil.isAjax(request)) {
+            response.setContentType("application/json;charset=UTF-8");
+            // 把 authentication 对象转成 json 格式字符串通过 response 以 application/json;charset=UTF-8 格式写到响应里面去
+            response.getWriter().write(objectMapper.writeValueAsString(authentication));
+        } else {
+            // 跳转到登录之前访问的页面
+            response.setStatus(HttpStatus.OK.value());
+            SavedRequest savedRequest = (SavedRequest) request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST");
+            if (savedRequest != null) {
+                response.sendRedirect(savedRequest.getRedirectUrl());
+                request.getSession().removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
+            } else {
+                // 重定向页面需要在登录页设置路径参数
+                String redirect = (String) request.getSession().getAttribute("REDIRECT");
+                if (null != redirect) {
+                    response.sendRedirect(redirect);
+                } else {
+                    response.sendRedirect("/");
+                }
+            }
+        }
+    }
+}

+ 19 - 0
src/main/java/cn/minbb/job/handler/LoginUrlEntryPoint.java

@@ -0,0 +1,19 @@
+package cn.minbb.job.handler;
+
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 当一个未授权的用户请求非公有资源时, commence 方法将会被调用
+ */
+public class LoginUrlEntryPoint implements AuthenticationEntryPoint {
+    @Override
+    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
+        httpServletResponse.sendRedirect("/login");
+    }
+}

+ 45 - 0
src/main/java/cn/minbb/job/handler/LogoutSuccessHandler.java

@@ -0,0 +1,45 @@
+package cn.minbb.job.handler;
+
+import cn.minbb.job.util.RequestUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * SimpleUrlLogoutSuccessHandler - 重定向到设置的URL地址。默认的地址是 /login?logout
+ * HttpStatusReturningLogoutSuccessHandler - REST API 场景,允许设置一个返回给客户端的 HTTP 状态码(默认返回200)来替换重定向到 URL 这个动作
+ */
+@Component
+@Log4j2
+public class LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
+
+    private final ObjectMapper objectMapper;
+
+    @Autowired
+    public LogoutSuccessHandler(ObjectMapper objectMapper) {
+        this.objectMapper = objectMapper;
+    }
+
+    @Override
+    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
+        String auth = objectMapper.writeValueAsString(authentication);
+        log.info("用户登出成功 = {}", auth);
+        if (RequestUtil.isAjax(request)) {
+            response.setContentType("application/json;charset=UTF-8");
+            response.getWriter().write(objectMapper.writeValueAsString(authentication));
+        } else {
+            response.setStatus(HttpStatus.OK.value());
+            response.sendRedirect("/sign-in?logout=true");
+            super.onLogoutSuccess(request, response, authentication);
+        }
+    }
+}

+ 52 - 0
src/main/java/cn/minbb/job/model/Auditable.java

@@ -0,0 +1,52 @@
+package cn.minbb.job.model;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.data.annotation.CreatedBy;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedBy;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.*;
+import java.util.Date;
+
+@EntityListeners(AuditingEntityListener.class)
+@MappedSuperclass
+public abstract class Auditable {
+
+    @Getter
+    @Setter
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id", nullable = false, columnDefinition = "INT COMMENT '自增主键'")
+    private Integer id;
+
+    @Getter
+    @Setter
+    @CreatedBy
+    @Column(name = "created_by", columnDefinition = "VARCHAR(200) COMMENT '创建者'")
+    protected String createdBy;
+
+    @Getter
+    @Setter
+    @CreatedDate
+    @Column(name = "created_date", nullable = false, updatable = false, columnDefinition = "DATETIME COMMENT '创建日期'")
+    protected Date createdDate;
+
+    @Getter
+    @Setter
+    @LastModifiedBy
+    @Column(name = "last_modified_by", columnDefinition = "VARCHAR(200) COMMENT '最后修改者'")
+    protected String lastModifiedBy;
+
+    @Getter
+    @Setter
+    @LastModifiedDate
+    @Column(name = "last_modified_date", nullable = false, columnDefinition = "DATETIME COMMENT '最后修改日期'")
+    protected Date lastModifiedDate;
+
+    @Version
+    @Column(name = "version", nullable = false, columnDefinition = "INT COMMENT '版本号'")
+    protected Integer version;
+}

+ 51 - 0
src/main/java/cn/minbb/job/model/Banner.java

@@ -0,0 +1,51 @@
+package cn.minbb.job.model;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+/**
+ * 轮播图
+ */
+@Data
+@Entity
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@Table(name = "banner",
+        indexes = {
+                @Index(name = "index_title", columnList = "title"),
+                @Index(name = "index_subtitle", columnList = "subtitle"),
+                @Index(name = "index_type", columnList = "type")
+        }
+)
+public class Banner extends Auditable {
+
+    @Column(name = "title", columnDefinition = "VARCHAR(32) COMMENT '标题'")
+    private String title;
+
+    @Column(name = "subtitle", columnDefinition = "VARCHAR(64) COMMENT '子标题'")
+    private String subtitle;
+
+    @Column(name = "image", columnDefinition = "VARCHAR(255) COMMENT '图片链接'")
+    private String image;
+
+    @Column(name = "url", columnDefinition = "VARCHAR(255) COMMENT '链接'")
+    private String url;
+
+    @Column(name = "priority", columnDefinition = "INT COMMENT '优先级'")
+    private Integer priority;
+
+    @Column(name = "type", columnDefinition = "VARCHAR(32) COMMENT '类型'")
+    private String type;
+
+    @Column(name = "remark", columnDefinition = "VARCHAR(255) COMMENT '备注'")
+    private String remark;
+
+    @Column(name = "is_enabled", nullable = false, columnDefinition = "TINYINT DEFAULT '1' COMMENT '启用'")
+    private Boolean isEnabled;
+}

+ 4 - 0
src/main/java/cn/minbb/job/model/Resume.java

@@ -0,0 +1,4 @@
+package cn.minbb.job.model;
+
+public class Resume {
+}

+ 7 - 0
src/main/java/cn/minbb/job/model/repository/BannerRepository.java

@@ -0,0 +1,7 @@
+package cn.minbb.job.model.repository;
+
+import cn.minbb.job.model.Banner;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface BannerRepository extends JpaRepository<Banner, Integer> {
+}

+ 9 - 0
src/main/java/cn/minbb/job/service/BannerService.java

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

+ 27 - 0
src/main/java/cn/minbb/job/service/impl/BannerServiceImpl.java

@@ -0,0 +1,27 @@
+package cn.minbb.job.service.impl;
+
+import cn.minbb.job.model.Banner;
+import cn.minbb.job.model.repository.BannerRepository;
+import cn.minbb.job.service.BannerService;
+import cn.minbb.job.util.SortTool;
+import org.springframework.data.domain.Example;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class BannerServiceImpl implements BannerService {
+
+    private final BannerRepository bannerRepository;
+
+    public BannerServiceImpl(BannerRepository bannerRepository) {
+        this.bannerRepository = bannerRepository;
+    }
+
+    @Override
+    public List<Banner> findAll() {
+        Banner banner = new Banner();
+        banner.setIsEnabled(Boolean.TRUE);
+        return bannerRepository.findAll(Example.of(banner), SortTool.priorityDown());
+    }
+}

+ 1 - 1
src/main/java/cn/minbb/job/util/RequestUtil.java

@@ -8,7 +8,7 @@ public class RequestUtil {
      * 判断请求是否为 Ajax 请求
      *
      * @param request HttpServletRequest
-     * @return 是 true 否 false
+     * @return 是否为 Ajax 请求
      */
     public static boolean isAjax(HttpServletRequest request) {
         return (null != request.getHeader("X-Requested-With") && "XMLHttpRequest".equals(request.getHeader("X-Requested-With")));

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

@@ -75,3 +75,6 @@ logging.pattern.dateformat=yyyy-MM-dd HH:mm:ss.SSS
 spring.security.user.name=admin
 spring.security.user.password=admin+++
 spring.security.user.roles=ADMIN
+## ÈȲ¿Êð Devtools
+spring.devtools.add-properties=true
+spring.devtools.livereload.port=10020

+ 122 - 0
src/main/resources/static/css/user-sign.css

@@ -0,0 +1,122 @@
+html, body {
+    height: 100%;
+}
+
+body {
+    display: -ms-flexbox;
+    display: -webkit-box;
+    display: flex;
+    -ms-flex-align: center;
+    -ms-flex-pack: center;
+    -webkit-box-align: center;
+    align-items: center;
+    -webkit-box-pack: center;
+    justify-content: center;
+    padding-top: 40px;
+    padding-bottom: 40px;
+    background-color: #f5f5f5;
+}
+
+a {
+    color: gray;
+    font-size: small;
+}
+
+a:hover {
+    color: darkorange;
+    text-decoration: none;
+}
+
+.form-login {
+    width: 100%;
+    max-width: 420px;
+    padding: 15px;
+    margin: 0 auto;
+}
+
+.form-login .form-control {
+    position: relative;
+    box-sizing: border-box;
+    height: auto;
+    padding: 10px;
+    font-size: 16px;
+}
+
+.form-login .form-control:focus {
+    z-index: 2;
+}
+
+.form-login input[id="username"] {
+    margin-top: 16px;
+    margin-bottom: 0;
+    border-bottom-right-radius: 0;
+    border-bottom-left-radius: 0;
+}
+
+.form-login input[type="password"] {
+    margin-bottom: 10px;
+    border-top-left-radius: 0;
+    border-top-right-radius: 0;
+}
+
+
+/****** 浮动标签 ******/
+:root {
+    --input-padding-x: .75rem;
+    --input-padding-y: .75rem;
+}
+
+.form-label-group {
+    position: relative;
+    margin-bottom: 1rem;
+}
+
+.form-label-group > input, .form-label-group > label {
+    padding: var(--input-padding-y) var(--input-padding-x);
+}
+
+.form-label-group > label {
+    position: absolute;
+    top: 0;
+    left: 0;
+    display: block;
+    width: 100%;
+    margin-bottom: 0; /* Override default `<label>` margin */
+    line-height: 1.5;
+    color: #495057;
+    border: 1px solid transparent;
+    border-radius: .25rem;
+    transition: all .1s ease-in-out;
+}
+
+.form-label-group input::-webkit-input-placeholder {
+    color: transparent;
+}
+
+.form-label-group input:-ms-input-placeholder {
+    color: transparent;
+}
+
+.form-label-group input::-ms-input-placeholder {
+    color: transparent;
+}
+
+.form-label-group input::-moz-placeholder {
+    color: transparent;
+}
+
+.form-label-group input::placeholder {
+    color: transparent;
+}
+
+.form-label-group input:not(:placeholder-shown) {
+    padding-top: calc(var(--input-padding-y) + var(--input-padding-y) * (2 / 3));
+    padding-bottom: calc(var(--input-padding-y) / 3);
+}
+
+.form-label-group input:not(:placeholder-shown) ~ label {
+    padding-top: calc(var(--input-padding-y) / 3);
+    padding-bottom: calc(var(--input-padding-y) / 3);
+    font-size: 12px;
+    color: #777;
+}

+ 67 - 0
src/main/resources/templates/fragments/footer.html

@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
+<head>
+    <meta charset="UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
+    <title></title>
+
+    <link rel="stylesheet" href="https://unpkg.com/bootstrap-material-design@4.1.1/dist/css/bootstrap-material-design.min.css"/>
+
+    <style>
+        footer a {
+            color: #EEEEEE;
+        }
+
+        footer a:hover {
+            color: lightgray;
+            text-decoration: none;
+        }
+
+        footer ul {
+            padding-left: 0;
+        }
+
+        footer ul li {
+            font-size: 14px;
+            list-style: none;
+        }
+
+        footer p {
+            font-size: 14px;
+        }
+    </style>
+</head>
+<body>
+<footer th:fragment="footer">
+    <div class="container-fluid text-white">
+        <div class="row" style="background-color: #444444; padding: 24px 48px;">
+            <div class="col col-xs-12 col-sm-12 col-md-4 col-lg-4">
+                <h4 class="white-text">页脚内容</h4>
+                <p>你可以用行和列来组织你的页脚内容。</p>
+            </div>
+            <div class="col col-xs-6 col-sm-6 col-md-3 col-lg-3 offset-md-1 offset-lg-1">
+                <h5>链接</h5>
+                <ul>
+                    <li><a class="#" href="#!">链接 1</a></li>
+                    <li><a class="#" href="#!">链接 2</a></li>
+                    <li><a class="#" href="#!">链接 3</a></li>
+                    <li><a class="#" href="#!">链接 4</a></li>
+                </ul>
+            </div>
+            <div class="col col-xs-6 col-sm-6 col-md-3 col-lg-3 offset-md-1 offset-lg-1">
+                <h5>链接</h5>
+                <ul>
+                    <li><a class="#" href="#!">链接 1</a></li>
+                    <li><a class="#" href="#!">链接 2</a></li>
+                    <li><a class="#" href="#!">链接 3</a></li>
+                    <li><a class="#" href="#!">链接 4</a></li>
+                </ul>
+            </div>
+        </div>
+        <div class="row" style="background-color: #111111; padding: 16px;">
+            <span class="text-muted">&copy; 2019 <a href="/"> 招聘</a></span>
+        </div>
+    </div>
+</footer>
+</body>
+</html>

+ 70 - 0
src/main/resources/templates/fragments/header.html

@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
+<head>
+    <meta charset="UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
+    <title></title>
+    <!-- Material Design for Bootstrap fonts and icons -->
+    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons"/>
+    <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 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">
+        <span class="navbar-toggler-icon"></span>
+    </button>
+
+    <div class="collapse navbar-collapse" id="navbar-supported-content">
+        <ul class="navbar-nav mr-auto">
+            <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="#!" data-toggle="tooltip" data-placement="bottom" data-html="true"
+                   title="<img src='https://files-1252373323.cos.ap-beijing.myqcloud.com/images/er_code.jpg' width='150' alt='下载二维码'/><p>Version: 1.0</p>">
+                    下载二维码
+                </a>
+            </li>
+        </ul>
+        <form class="form-inline my-2 my-lg-0">
+            <input class="form-control mr-sm-2" type="search" placeholder="搜索..." aria-label="Search"/>
+            <button class="btn btn-sm btn-outline-success my-2 my-sm-0" type="submit">搜索</button>
+        </form>
+        <ul class="navbar-nav ml-auto">
+            <li class="nav-item" th:if="${user == null}">
+                <a class="nav-link" href="/login">登录 / 注册</a>
+            </li>
+            <li class="nav-item dropdown" th:unless="${user == null}">
+                <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true"
+                   aria-expanded="false" th:text="${user.getName()}">
+                    用户名
+                </a>
+                <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
+                    <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="/logout">退出登录</a>
+                </div>
+            </li>
+        </ul>
+    </div>
+</nav>
+
+<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.js"></script>
+<script src="https://unpkg.com/popper.js@1.12.6/dist/umd/popper.js"></script>
+<script src="https://unpkg.com/bootstrap-material-design@4.1.1/dist/js/bootstrap-material-design.js"></script>
+<script>
+    $(document).ready(function () {
+    });
+</script>
+</body>
+</html>

+ 11 - 2
src/main/resources/templates/index.html

@@ -1,10 +1,19 @@
 <!DOCTYPE html>
-<html lang="zh-CN">
+<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>基于Java的校园智能求职招聘平台的设计与实现</title>
 </head>
 <body>
-<h5>基于Java的校园智能求职招聘平台的设计与实现</h5>
+<th:block layout:fragment="content">
+    <h5>基于Java的校园智能求职招聘平台的设计与实现</h5>
+
+    <div class="container" style="margin-top: 24px;">
+        123
+    </div>
+</th:block>
 </body>
 </html>

+ 45 - 0
src/main/resources/templates/layouts/layout.html

@@ -0,0 +1,45 @@
+<!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">
+<head>
+    <meta charset="UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
+    <title></title>
+
+    <!-- Material Design for Bootstrap fonts and icons -->
+    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons"/>
+    <link rel="stylesheet" href="https://unpkg.com/bootstrap-material-design@4.1.1/dist/css/bootstrap-material-design.min.css"/>
+    <style>
+        html {
+            position: relative;
+            min-height: 100%;
+        }
+
+        a:hover {
+            text-decoration: none;
+        }
+    </style>
+</head>
+<body style="margin-bottom: 60px;">
+<header th:replace="~{fragments/header :: header}"></header>
+
+<main style="margin-top: 57px; margin-bottom: 96px;">
+    <div layout:fragment="content">
+        <p>内容</p>
+    </div>
+</main>
+
+<footer th:replace="~{fragments/footer :: footer}"></footer>
+
+<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.js"></script>
+<script src="https://unpkg.com/popper.js@1.12.6/dist/umd/popper.js"></script>
+<script src="https://unpkg.com/bootstrap-material-design@4.1.1/dist/js/bootstrap-material-design.js"></script>
+<script>
+    $(document).ready(function () {
+        $('body').bootstrapMaterialDesign();
+        $('[data-toggle="tooltip"]').tooltip();
+    });
+</script>
+</body>
+</html>

+ 92 - 0
src/main/resources/templates/sign-in.html

@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html lang="zh-CN" xmlns="http://www.w3.org/1999/html"
+      xmlns:th="http://www.thymeleaf.org">
+<head>
+    <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>
+
+    <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}"/>
+</head>
+<body>
+<form class="form-login" method="post" action="/login">
+    <div class="text-center">
+        <a href="/"><img class="mb-4" src="../static/favicon.ico" alt="" width="72" height="72" th:src="@{/favicon.ico}"/></a>
+        <h1 class="h3 mb-3 font-weight-normal">用户登录</h1>
+        <small style="color: blue;" th:if="${param.logout}">退出登录成功,重新登录以开始您的会话</small>
+        <div th:unless="${param.logout}">
+            <small style="color: red;" th:if="${param.error != null && session.SPRING_SECURITY_LAST_EXCEPTION != null}"
+                   th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}"></small>
+            <small th:if="${param.error == null || session.SPRING_SECURITY_LAST_EXCEPTION == null}">登录以开始您的会话</small>
+        </div>
+    </div>
+
+    <div class="form-label-group">
+        <input class="form-control" id="username" name="username" type="text" placeholder="用户名" style="height: 44px;" required autofocus/>
+        <label for="username">用户名</label>
+    </div>
+
+    <div class="form-label-group">
+        <input class="form-control" id="password" name="password" type="password" placeholder="密码" style="height: 44px;" required/>
+        <label for="password">密码</label>
+    </div>
+
+    <div class="mb-3 text-center">
+        <a href="/register" style="margin-right: 36px;">注册账号</a>
+        <a href="#password-modal" data-toggle="modal">忘记密码</a>
+    </div>
+    <button class="btn btn-lg btn-success btn-block" type="submit">登录</button>
+    <p class="text-center mt-5 mb-3 text-muted">&copy; 2020 招聘</p>
+</form>
+
+<!-- Modal -->
+<div class="modal fade" id="password-modal" tabindex="-1" role="dialog" aria-labelledby="" aria-hidden="true">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">忘记密码</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                请联系系统管理员
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary" data-dismiss="modal">我知道了</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.js"></script>
+<script src="https://unpkg.com/popper.js@1.12.6/dist/umd/popper.js"></script>
+<script src="https://unpkg.com/bootstrap-material-design@4.1.1/dist/js/bootstrap-material-design.js"></script>
+<script>
+    $(document).ready(function () {
+    });
+
+    function login() {
+        $.ajax({
+            url: '/user/login?username=' + $('#username').val() + '&password=' + $('#password').val(),
+            type: 'POST',
+            data: null,
+            dataType: "text",
+            async: false,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function (data) {
+                alert(data)
+            },
+            error: function (data) {
+                console.log(data);
+            }
+        });
+    }
+</script>
+</body>
+</html>

+ 100 - 0
src/main/resources/templates/sign-up.html

@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html lang="zh-CN"
+      xmlns="http://www.w3.org/1999/html"
+      xmlns:th="http://www.thymeleaf.org">
+<head>
+    <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>
+
+    <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}"/>
+
+    <style>
+        .form-label-group {
+            margin-bottom: 4px;
+        }
+    </style>
+</head>
+<body>
+<div class="form-login">
+    <div class="text-center">
+        <a href="/"><img class="mb-4" src="/favicon.ico" alt="" width="72" height="72" th:src="@{/favicon.ico}"/></a>
+        <h1 class="h3 mb-3 font-weight-normal">用户注册</h1>
+    </div>
+
+    <div class="form-label-group">
+        <input class="form-control" id="name" type="text" placeholder="姓名" maxlength="16" style="height: 44px;" required autofocus/>
+        <label for="name">姓名</label>
+    </div>
+
+    <div class="form-label-group">
+        <input class="form-control" id="username" type="text" placeholder="用户名" maxlength="16" style="height: 44px; margin-top: 0;" required/>
+        <label for="username">用户名</label>
+    </div>
+
+    <div class="form-label-group">
+        <input class="form-control" id="password" type="password" placeholder="密码" minlength="6" maxlength="24" style="height: 44px; margin-bottom: 0;" required/>
+        <label for="password">密码</label>
+    </div>
+
+    <div class="form-label-group">
+        <input class="form-control" id="repeat-password" type="password" placeholder="重复密码" minlength="6" maxlength="24" style="height: 44px;" required/>
+        <label for="repeat-password">重复密码</label>
+    </div>
+
+    <div class="text-center mb-3"><a href="/login">我有账号? 去登录</a></div>
+    <button class="btn btn-lg btn-success btn-block" type="button" onclick="return submit();">注册</button>
+    <p class="text-center mt-5 mb-3 text-muted">&copy; 2019 招聘</p>
+</div>
+
+<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.js"></script>
+<script>
+    $(document).ready(function () {
+    });
+
+    function submit() {
+        let nameInput = $('#name'), usernameInput = $('#username'), passwordInput = $('#password'), passwordInput2 = $('#repeat-password');
+        let name = nameInput.val(), username = usernameInput.val(), password = passwordInput.val(), password2 = passwordInput2.val();
+        if (name === "") {
+            alert("姓名不能为空");
+            return;
+        } else if (username === "") {
+            alert("用户名不能为空");
+            return;
+        } else if (password === "") {
+            alert("密码不能为空");
+            return;
+        } else if (password.length < 6) {
+            alert("密码至少六位");
+            return;
+        } else if (password !== password2) {
+            alert("两次密码输入不一致");
+            passwordInput.val("");
+            passwordInput2.val("");
+            return;
+        }
+        $.ajax({
+            url: '/sign-up',
+            type: 'post',
+            data: JSON.stringify({"name": name, "username": username, "password": password}),
+            async: true,
+            dataType: 'json',
+            contentType: false,
+            success: function (result) {
+                if (result && result.success) {
+                    let user = result.data;
+                    alert("恭喜你 " + user.name + " ,注册成功,登录用户名为:" + user.username);
+                    window.location.href = "/sign-in";
+                } else {
+                    alert("注册失败:" + result.message);
+                    window.location.reload();
+                }
+            }
+        });
+        return false;
+    }
+</script>
+</body>
+</html>

+ 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>