文章目录


Spring Boot 构建后台管理系统

一、项目创建

使用Spring Initializr创建新项目,选中所需模块 thymeleaf、web-starter、devtools、lombok。

【Spring Boot】构建后台管理系统_导航栏

【Spring Boot】构建后台管理系统_spring boot_02


返回顶部


二、静态资源处理

自动配置好,我们只需要把所有静态资源放到 static 文件夹下

【Spring Boot】构建后台管理系统_spring boot_03

返回顶部


三、简单布局

1.项目结构

【Spring Boot】构建后台管理系统_spring boot_04


2.任务目标

  • 主要目标就是实现简单的登录界面、用户登陆跳转到主界面

3.具体实现

3.1 登录界面定制

登陆界面:用户名、密码

【Spring Boot】构建后台管理系统_mvc_05


3.2 构建用户对象类

package com.zyx.core.web.demo.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String username;
private String password;
}


3.3 Controller层

LoginPage() 方法给出登陆页面请求,最终转到login.html

Form() 方法实现页面表单提交请求处理:

  • User user封装登陆的用户对象信息
  • HttpSession session将登陆的用户信息暂存在session中,用于页面跳转信息传递
  • Model model将信息存贮在请求域中

主要的逻辑思路:

  • 发送登陆请求实现登陆界面的显示
  • 前端代码实现登陆表单的信息提交,判断用户输入信息(这里暂时不和数据库连接,就默认username不为空,密码为123456即可!)
  • 登陆到主页面后,如果Form()直接给出 /main 请求,那么每一次刷新主页面,都是一次表单的重提交。为了避免表单的重复提交,我们可以在转到main.html之前进行用户的信息判断,利用session中存储的用户信息,是否为登录状态;若为登录状态则直接转到主页面;否则回退到登录界面重新登陆。
  • 编写 MainPage() 进行用户的登陆判断(相当于拦截器), 在 Form() 中用户登陆后,就将信息存在session中,并且重定向至 MainPage() 进行用户登陆的判断。

package com.zyx.core.web.demo.controler;

import com.zyx.core.web.demo.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import javax.servlet.http.HttpSession;

@Controller
public class WebController {

/**
* 登陆
* @return
*/
@GetMapping(value = {"/","/login"})
public String LoginPage(){
return "login";
}

/**
* 表单提交
* @param user
* @param session
* @param model
* @return
*/
@PostMapping(value = "/login")
public String Form(User user, HttpSession session, Model model){
// 判断登陆信息的完整性
if ((StringUtils.hasLength(user.getUsername()))
&& (StringUtils.hasLength(user.getPassword()))
&& ("123456".equals(user.getPassword()))) {
// 把登陆成功的用户保存起来
session.setAttribute("loginUser", user);
// 登陆成功重定向到主页面
return "redirect:/main.html";
} else {
// 提示信息
model.addAttribute("msg", "账号或密码有误~");
// 回到登录页
return "login";
}
}

/**
* 主页面
* @param session
* @param model
* @return
*/
@GetMapping(value = "/main.html")
public String MainPage(HttpSession session,Model model){
// 判断是否登陆
Object loginUser = session.getAttribute("loginUser");
// 通过session存储的用户对象信息判断
if (loginUser != null){
return "main";
} else {
// 为空表示没有登陆,跳回登陆界面
model.addAttribute("msg","未登录!请先登录~");
return "login";
}
}

}


3.4 表单修改

【Spring Boot】构建后台管理系统_spring_06

返回顶部


四、模板抽取

4.1 Thymeleaf 的使用

  • th:insert/replace/include
  • th:insert is the simplest: it will simply insert the specified fragment as the body of its host tag.
  • th:replace actually replaces its host tag with the specified fragment.
  • th:include is similar to th:insert , but instead of inserting the fragment it only inserts the contents of this fragment.
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>

<body>
...
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
</body>


The Result is:

<body>
...
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery insert将整个带标签内容插入当前标签内部
</footer>
</div>

<footer>
© 2011 The Good Thymes Virtual Grocery replace使用引用的标签替换当前的外层标签
</footer>

<div>
© 2011 The Good Thymes Virtual Grocery include使用引用标签中的文本内容填充当前标签
</div>
</body>

4.2 网页基本布局

如下图所示,基本的管理系统界面主要包含三部分左边导航栏、头部导航栏、中间主题内容部分,并且在不同的导航跳转界面中,左侧导航栏、头部导航栏部分基本保持不变。

所以我们可以将这两部分看做是公共部分,进行模板的抽取,当需要利用的时候我们就进行提取,主体部分可以根据具体的几面具体设置。

【Spring Boot】构建后台管理系统_mvc_07

新建common.html存储公共界面信息。注意抽取的同时提取对应的 js、css资源文件。

【Spring Boot】构建后台管理系统_拦截器_08

抽取完后,接下来就需要引用了。

【Spring Boot】构建后台管理系统_mvc_09

返回顶部


五、页面跳转

【Spring Boot】构建后台管理系统_spring boot_10

假设导航栏中的上面一部分,我们需要实现页面的跳转;如果按照正常写法,每个页面的跳转都需要重新编写导航栏部分,代码量就会显得很大,实现了模板的抽取后,只需要引用需要即可。并且需要修改只需要修改公共抽取页面一个地方的信息即可,接下来我们实现页面的跳转。

针对于表格页面,我们单独定义一个Controller,给出请求路径,实现跳转:

package com.zyx.core.web.demo.controler;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class TableController {

/**
* basic_table
* @return
*/
@GetMapping(value = "/basic_table")
public String basic_table(){
return "table/basic_table";
}

/**
* dynamic_table
* @return
*/
@GetMapping(value = "/dynamic_table")
public String dynamic_table(){
return "table/dynamic_table";
}

/**
* editable_table
* @return
*/
@GetMapping(value = "/editable_table")
public String editable_table(){
return "table/editable_table";
}

/**
* pricing_table
* @return
*/
@GetMapping(value = "/pricing_table")
public String pricing_table(){
return "table/pricing_table";
}

/**
* responsive_table
* @return
*/
@GetMapping(value = "/responsive_table")
public String responsive_table(){
return "table/responsive_table";
}
}

给出请求后,我们只需要在页面中的对应部分指定连接跳转请求即可,首先我们将 basic_table.html 实现抽取填充:

【Spring Boot】构建后台管理系统_spring_11

页面的跳转只需要在公共抽取部分修改一次即可(以basic_table为例):

【Spring Boot】构建后台管理系统_拦截器_12

效果展示:

【Spring Boot】构建后台管理系统_spring_13

返回顶部


六、数据渲染

6.1 表格内容的遍历

@GetMapping("/dynamic_table")
public String dynamic_table(Model model){
// 表格内容的遍历
List<User> users = Arrays.asList(new User("zhangsan", "123456"),
new User("lisi", "123444"),
new User("haha", "aaaaa"),
new User("hehe ", "aaddd"));
// 将user信息存储在请求域中
model.addAttribute("users",users);
return "table/dynamic_table";
}

#

用户名

密码

Trident

Internet

[[${user.password}]]

<table class="display table table-bordered" id="hidden-table-info">
<thead>
<tr>
<th>#</th>
<th>用户名</th>
<th>密码</th>
</tr>
</thead>
<tbody>
<tr class="gradeX" th:each="user,stats:${users}">
<td th:text="${stats.count}">Trident</td>
<td th:text="${user.userName}">Internet</td>
<td >[[${user.password}]]</td>
</tr>
</tbody>
</table>

【Spring Boot】构建后台管理系统_导航栏_14

返回顶部


七、拦截器

7.1 HandlerInterceptor 接口

拦截器是在面向切面编程中应用的,就是在你的service或者一个方法前调用一个方法,或者在方法后调用一个方法比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。

定义拦截器要实现HandlerInterceptor接口,并实现该接口中提供的三个方法:

  • preHandle方法:进入Handler方法之前执行。可以用于身份认证、身份授权。比如如果认证没有通过表示用户没有登陆,需要此方法拦截不再往下执行(return false),否则就放行(return true)。
  • postHandle方法:进入Handler方法之后,返回ModelAndView之前执行。可以看到该方法中有个modelAndView的形参。应用场景:从modelAndView出发:将公用的模型数据(比如菜单导航之类的)在这里传到视图,也可以在这里同一指定视图。
  • afterCompletion方法:执行Handler完成之后执行。应用场景:统一异常处理,统一日志处理等。

package com.zyx.core.web.demo.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

/**
* 目标方法执行之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("拦截的请求路劲是:"+requestURI);

// 登陆检查
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if (loginUser!=null){
// 放行
return true;
}
// 拦截
request.setAttribute("msg","请先登陆!!!");
// 重定向至登陆界面
request.getRequestDispatcher("/").forward(request,response);
return false;
}

/**
* 目标方法执行完成后
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle方法执行了"+modelAndView);
}

/**
* 页面渲染完成后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion方法执行了,异常是:"+ex);
}
}


7.2 配置容器注册拦截器

package com.zyx.core.web.demo.config;

import com.zyx.core.web.demo.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 1.编写一个拦截器实现HandlerInterceptor接口
* 2.将拦截器注册到容器中 (实现WebMvcConfigurer接口的addInterceptors方法)
* 3.指定拦截规则 ---- /** 会拦截所有包括静态资源文件
*/
@Configuration // 配置容器
public class AdminWebConfig implements WebMvcConfigurer {
/**
* 添加拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(new LoginInterceptor()) // 添加拦截器
.addPathPatterns("/**") // 拦截所有路径 --- 静态资源也会被拦截;不加下面一行页面显示没有样式!
.excludePathPatterns("/", "/login","/css/**","/fonts/**","/images/**","/js/**"); // 放行的路径
}
}

当配置了拦截器后我们可以将之前的用戶登陆检验注释,并添加日志測試:

【Spring Boot】构建后台管理系统_spring_15


【Spring Boot】构建后台管理系统_拦截器_16


可以看出,在注释了==/main.html==中的测试后,再次运行直接登陆主页面,拦截器起到了作用,对登陆用户进行了判断,实施拦截!

然后我们进行登陆测试:

【Spring Boot】构建后台管理系统_导航栏_17

【Spring Boot】构建后台管理系统_spring boot_18


7.3 拦截器原理

1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】

【Spring Boot】构建后台管理系统_mvc_19

2、先来顺序执行 所有拦截器的 preHandle方法

  • 如果当前拦截器prehandler返回为true,则执行下一个拦截器的preHandle

  • 如果当前拦截器返回为false,直接倒序执行所有已经执行了的拦截器的 afterCompletion;
  • 【Spring Boot】构建后台管理系统_mvc_20

3、如果任何一个拦截器返回false,直接跳出不执行目标方法(如上图)

4、所有拦截器都返回True,执行目标方法

【Spring Boot】构建后台管理系统_mvc_21

5、倒序执行所有拦截器的postHandle方法。

【Spring Boot】构建后台管理系统_导航栏_22

6、前面的步骤有任何异常都会直接倒序触发 afterCompletion

7、页面成功渲染完成以后,也会倒序触发 afterCompletion

【Spring Boot】构建后台管理系统_导航栏_23

8.示意图

【Spring Boot】构建后台管理系统_spring boot_24


返回顶部


八、文件上传

8.1 文件提交表单

<div class="panel-body">
<!-- 文件上传表单的固定写法 -->
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1"
placeholder="Enter email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">姓名</label>
<input type="text" name="username" class="form-control" id="exampleInputPassword1"
placeholder="Password">
</div>
<!-- 单文件上传 -->
<div class="form-group">
<label for="exampleInputFile">头像</label>
<input type="file" name="headerImage" id="exampleInputFile">
</div>
<!-- 多文件上传 -->
<div class="form-group">
<label for="exampleInputFile">自拍</label>
<input type="file" name="photos" multiple>
</div>
<div class="checkbox">
<label>
<input type="checkbox"> Check me out
</label>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>

【Spring Boot】构建后台管理系统_mvc_25


8.2 提交信息测试

package com.zyx.core.admin.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@Controller
public class FileUploadController {

/**
* MultipartFile 会自动封装
*
* @param email
* @param username
* @param headerImage
* @param photos
* @return
*/
@PostMapping(value = "/upload")
public String FileUpload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImage") MultipartFile headerImage,
@RequestPart("photos") MultipartFile[] photos) {
// 控制台输出 测试上传的信息
log.info("上传的信息:email={},username={},headerImage_size={},photos={}",
email, username, headerImage.getSize(), photos);

return "index";
}

}

【Spring Boot】构建后台管理系统_拦截器_26


8.3 表单文件存储

package com.zyx.core.admin.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;

@Slf4j
@Controller
public class FileUploadController {

/**
* MultipartFile 会自动封装
*
* @param email
* @param username
* @param headerImage
* @param photos
* @return
*/
@PostMapping(value = "/upload")
public String FileUpload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImage") MultipartFile headerImage,
@RequestPart("photos") MultipartFile[] photos) {
// 测试上传的信息
log.info("上传的信息:email={},username={},headerImage_size={},photos={}",
email, username, headerImage.getSize(), photos);

// 单文件存储
if (headerImage != null) {
try {
// 获取文件名
String originalFilename = headerImage.getOriginalFilename();
// 存储文件
headerImage.transferTo(new File("G:\\Projects\\IdeaProjects\\MyWeb\\src\\main\\resources\\upload\\"
+ originalFilename));
} catch (Exception e) {
e.printStackTrace();
}
}
// 多文件存储
if (photos.length>0){
for (MultipartFile photo:photos){
if (photo!=null){
try {
// 获取文件名
String originalFilename = photo.getOriginalFilename();
// 存储文件
photo.transferTo(new File("G:\\Projects\\IdeaProjects\\MyWeb\\src\\main\\resources\\upload\\"
+ originalFilename));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

return "index";
}
}

【Spring Boot】构建后台管理系统_拦截器_27

【Spring Boot】构建后台管理系统_导航栏_28


8.4 文件上传自动配置原理

文件上传自动配置类-MultipartAutoConfiguration - MultipartProperties

  • 自动配置好了StandardServletMultipartResolver 【文件上传解析器】
  • 原理步骤
  • 1、请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
  • 2、参数解析器来解析请求中的文件内容封装成MultipartFile
  • 3、将request中文件信息封装为一个Map;MultiValueMap<String, MultipartFile>

FileCopyUtils => 实现文件流的拷贝

@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos)

返回顶部


九.异常处理

9.1 错误处理

1、默认规则

  • 默认情况下,Spring Boot提供​​/error​​处理所有错误的映射
  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
  • 【Spring Boot】构建后台管理系统_导航栏_29


  • 【Spring Boot】构建后台管理系统_spring_30

  • **要对其进行自定义,添加 ** ​View解析为error
  • 要完全替换默认行为,可以实现​​ErrorController ​​​并注册该类型的Bean定义,或添加​​ErrorAttributes类型的组件​​以使用现有机制但替换其内容。
  • error/下的4xx,5xx页面会被自动解析;

2、定制错误处理逻辑

  • 自定义错误页
  • error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
  • @ControllerAdvice+@ExceptionHandler处理全局异常;底层是ExceptionHandlerExceptionResolver 支持的
  • @ResponseStatus+自定义异常 ;底层是ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用response.sendError(statusCode, resolvedReason);tomcat发送的/error
  • Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
  • response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
  • 【Spring Boot】构建后台管理系统_mvc_31

  • 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则
  • ErrorViewResolver实现自定义处理异常;
  • response.sendError 。error请求就会转给controller
  • 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
  • basicErrorController 要去的页面地址是ErrorViewResolver

3、异常处理自动配置原理

  • ErrorMvcAutoConfiguration 自动配置异常处理规则
  • 容器中的组件:类型:DefaultErrorAttributes ->id:errorAttributes
  • public classDefaultErrorAttributesimplementsErrorAttributes,HandlerExceptionResolver
  • DefaultErrorAttributes:定义错误页面中可以包含哪些数据。
  • 【Spring Boot】构建后台管理系统_spring boot_32

  • 【Spring Boot】构建后台管理系统_mvc_33

  • **容器中的组件:类型:**BasicErrorController --> id:basicErrorController(json+白页 适配响应)
  • 处理默认/error 路径的请求;页面响应newModelAndView(“error”, model);
  • 容器中有组件 View->id是error;(响应默认错误页)
  • 容器中放组件BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
  • **容器中的组件:**类型:**DefaultErrorViewResolver -> id:**conventionErrorViewResolver
  • 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面
  • error/404、5xx.html

如果想要返回页面;就会找error视图【StaticView】。(默认是一个白页)

【Spring Boot】构建后台管理系统_spring_34

写出去json

【Spring Boot】构建后台管理系统_导航栏_35


4、异常处理步骤流程

1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException

2、进入视图解析流程(页面渲染)

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;

  • 1、遍历所有的handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器
  • 【Spring Boot】构建后台管理系统_mvc_36

  • 2、系统默认的 异常解析器;
  • 【Spring Boot】构建后台管理系统_spring_37

  • 1、DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;
  • 2、默认没有任何人能处理异常,所以异常会被抛出
  • 1、如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理
  • 2、解析错误视图;遍历所有的ErrorViewResolver 看谁能解析。
  • 【Spring Boot】构建后台管理系统_mvc_38

  • 3、默认的DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html
  • 4、模板引擎最终响应这个页面error/500.html

返回顶部