SpringBoot:Demo
2021-02-12 11:22
标签:哈哈 throws extra 页面 mvc 直接 cal header pat 跟着狂神的SpringBoot网课做了一个小demo,功能有:登录、注销、拦截器、Restful CRUD、错误页面。不过完善了下put、delete的请求方式。 使用Lombok插件 pom.xml pojo层 dao层 此demo没有使用数据库,使用Map模拟数据库 BootStrap 后台模板下载地址 css、js等放在 static 文件夹下 html 放在 templates 文件夹下 templates下的页面只能通过Controller跳转实现,而META-INF/resources、resources、static、public下的页面是能直接被外界访问的 我们在登录的表单提交地址上写一个Controller:/user/login 去写对应的Controller 这里说一下为什么不能使用转发,而是用重定向: 因为如果是转发的话,提交完表单(即使表单提交方式为post)后,再经由Controller转发,也会将表单中的参数携带过去,而此时的URL地址又不会发生变化,如果用户此时刷新的话,会造成表单的重复提交! 如图: 而重定向不同,重定向地址栏会发生变化。当表单提交到Controller后,经由Controller重定向后,表单的参数不会显示再地址栏,因为重定向是两次请求,可以防止表单的重复提交! 而重定向不同,重定向地址栏会发生变化,经由Controller重定向后,不会将表单的参数携带过去,可以防止表单的重复提交! 其实问题的本质还是转发和重定向的区别。 设置重定向后的视图:在 页面存在缓存,所以我们需要禁用模板引擎的缓存 模板引擎修改后,想要实时生效!页面修改完毕后,IDEA小技巧 : Ctrl + F9? 重新编译! OK ,测试登录成功!如果模板出现500错误,参考处理连接: https://blog.csdn.net/fengzyf/article/details/83341479 下面该是拦截器了! 如果不设置拦截器的话,用户可以不用登录进行身份验证,直接跳到后台! 然后将拦截器注册到我们的SpringMVC配置类(MyMvcConfig)当中! 我们可以在后台的导航栏显示用户的登录信息 下面进行正式的增删改查,也就是Restful 风格的CRUD 概念:REST指的是一组架构约束条件和原则,如果一个架构符合REST的约束条件和原则,就称之为RESTful架构。 restful不是一个专门的技术,他是一个规范。规范就是写写代码给类命名,给属性命名,创建包结构 等等都最好按一定的规则来进行。这样的话以后代码看起来更规范,更容易理解。好比以前写增删改查请求的路径。 优点: 可以方便的实现程序的前后台代码的分离 resutful要求每个请求都是无状态的 可以使请求的路径更规范 要求 : 我们需要使用 Restful风格实现我们的crud操作! 普通CRUD和Restful风格的CRUD对别,明显的区别在URL地址栏上! 看看一些具体的要求,就是我们小实验的架构; 我们先来实现第一个功能: 给 a 连接添加请求 编写处理请求的Controller ok,跳转没有问题!我们只需将数据渲染进去即可。 但是发现一个问题,侧边栏和顶部都相同,我们是不是应该将它抽取出来呢? Thymeleaf 公共页面元素抽取 1.抽取公共片段 2.引入公共片段? 我们来抽取一下,使用list列表做演示!我们要抽取头部导航栏和侧边栏 为了重用更清晰,我们建立一个 然后我们在list页面中去引入,可以删掉原来的 模板名:会使用thymeleaf的前后缀配置规则进行解析 引入方式: 除了使用replace插入,还可以使用insert替换,或者include包含,三种方式会有一些小区别,可以见名知义; 我们使用replace替换,可以解决div多余的问题,可以查看thymeleaf的文档学习 还有一个问题:侧边栏激活的问题,它总是激活第一个;按理来说,这应该是动态的才对! 解决: 去修改对应的请求: 现在我们来遍历我们的员工信息!顺便美化一些页面,增加添加,修改,删除的按钮 1. 将添加员工信息改为超链接 2. 编写对应的controller 3. 修改前端页面 先测试是否能够跳转和页面的展示情况 1. 设置表单的提交地址 2. 编写对应的Controller 原理探究 : ThymeleafViewResolver 从源码中得知:如果是转发和重定向的话,不会走视图解析器,会走到我们的请求中! 在使用SpringBoot的时候,前端填写数据,注意时间格式问题! SpringMVC会将页面提交的值转换为指定的类型,默认日期是按照 / 的方式提交 ; 比如将2019/01/01 转换为一个date对象。 那思考一个问题?我们能不能修改这个默认的格式呢? 我们去看 点进: application.properties 现在它可以支持 - 格式了,但是又不能支持 / 格式了。 我们要实现员工修改功能,需要实现两步; 1. 点击修改按钮,去到编辑页面,我们可以直接使用添加员工的页面实现 2. 显示原数据,修改完毕后跳回列表页面! 我们去实现一下:首先修改跳转链接的位置; 编写对应的Controller 我们需要在这里将add页面复制一份,改为update页面;需要修改页面,将我们后台查询数据回显 数据回显OK,我们继续完成数据修改问题! 修改表单提交的地址: 编写对应的Controller 这里我们需要注意一点:修改时,使用隐藏域提交该员工的id 重启,修改信息测试OK! 在做Restful的删除操作之前,我们先来看一个类!,这个类可以将HTTP方法进行转换 当作删除操作的时候,不再通过超链接提交,通过 但在 在 点进 解决思路: 借鉴出处 通过JS获取删除员工的 我们还需要保证两件事: form的请求必须是POST form中必须带一个_method的参数,代表要把POST请求转换为什么请求。 Come on! 编写删除的a链接 声明一个form,用来提交delete请求: 需要给删除超链接增加单击事件,而且还要阻止原来的超链接事件: 编写Controller SpringBoot默认是开启的,如果我们关闭,就会405报错! 做完删除操作之后,想起来修改操作put也应该这样做!哈哈 只需要在 update.html 的 form表单中加上隐藏域即可 我们只需要在模板目录下添加一个error文件夹,文件夹中存放我们相应的错误页面,比如404.html? 或者 4xx.html 等等,SpringBoot就会帮我们自动使用了! a链接 对应的Controller 学到这里,SpringBoot的基本开发就以及没有问题了,我们后面去学习一下SpringBoot如何操作数据库以及配置Mybatis; SpringBoot:Demo 标签:哈哈 throws extra 页面 mvc 直接 cal header pat 原文地址:https://www.cnblogs.com/rainszj/p/12730736.html
Demo
准备工作
package com.rainszj.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
// 员工表
@Data
@NoArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
/**
* 0:女 1: 男
*/
private Integer gender;
private Date birth;
private Department department;
public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
// 日期自动设置
this.birth = new Date();
this.department = department;
}
}
package com.rainszj.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
// 部门表
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
package com.rainszj.dao;
import com.rainszj.pojo.Department;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class DepartmentDao {
// 模拟数据库中的数据
private static Map
package com.rainszj.dao;
import com.rainszj.pojo.Department;
import com.rainszj.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class EmployeeDao {
@Autowired
private DepartmentDao departmentDao;
// 模拟数据库中的数据
private static Map
登录+拦截器
package com.rainszj.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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.context.annotation.SessionScope;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.HttpSession;
@Controller
public class LoginController {
/**
* 登录
*/
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model, HttpSession session) {
if (!StringUtils.isEmpty(username) && "123".equals(password)) {
// 登录成功
session.setAttribute("loginUser", username);
// 这里使用重定向,防止表单重复提交
return "redirect:/main.html";
} else {
// 登录失败
model.addAttribute("msg", "用户名或密码错误");
return "index";
}
}
}
MyMvcConfig
中addViewControllers()
添加// 跳转到 dashboard?
registry.addViewController("/main.html").setViewName("dashboard");
#禁用模板缓存
spring.thymeleaf.cache=false
package com.rainszj.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义拦截器
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
/**
* return true:放行,false:不放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser == null) {
// 登录失败
request.setAttribute("msg", "没有权限,请先登录");
request.getRequestDispatcher("/index.html").forward(request, response);
return false;
} else {
// 登录成功
return true;
}
}
}
/**
* 添加拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册拦截器,及拦截请求和要剔除哪些请求!
//我们还需要过滤静态资源文件,否则样式显示不出来
registry.addInterceptor(new LoginHandlerInterceptor())
// 对所有请求的所有路径进去拦截
.addPathPatterns("/**")
// 放行哪些路径
.excludePathPatterns("/index.html", "/", "/user/login", "/css/*", "/js/*", "/img/*");
}
th:text="${session.loginUser}"
或者
[[${session.loginUser}]]
Restful CRUD
Restful架构
查询所有员工
员工管理
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
//查询所有员工,返回列表页面
@GetMapping("/emps")
public String list(Model model){
Collection
th:fragment
? 定义模板名th:insert
? 插入模板名commons
文件夹,专门存放公共页面;~{模板::标签名}
添加员工
ID
姓名
邮箱
性别
生日
部门
操作
修改
删除
添加员工
添加员工
/**
?* 跳转到添加员工页面
?*/
@GetMapping("/emp")
public String toAddPage(Model model) {
? ? Collection
/**
* 添加员工页面
* 接收前端传递的参数,自动封装成为对象
* [要求前端传递的参数名,和属性名一致]
*/
@PostMapping("/emp")
public String addEmp(Employee employee) {
System.out.println("[addEmp]=>" + employee);
employeeDao.addEmployee(employee);
return "redirect:/emps";
}
@Override
protected View createView(final String viewName, final Locale locale) throws Exception {
// First possible call to check "viewNames": before processing redirects and forwards
if (!this.alwaysProcessRedirectAndForward && !canHandle(viewName, locale)) {
vrlogger.trace("[THYMELEAF] View \"{}\" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
return null;
}
// Process redirects (HTTP redirects)
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
vrlogger.trace("[THYMELEAF] View \"{}\" is a redirect, and will not be handled directly by ThymeleafViewResolver.", viewName);
final String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length(), viewName.length());
final RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
}
// Process forwards (to JSP resources)
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
// The "forward:" prefix will actually create a Servlet/JSP view, and that‘s precisely its aim per the Spring
// documentation. See http://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/html/mvc.html#mvc-redirecting-forward-prefix
vrlogger.trace("[THYMELEAF] View \"{}\" is a forward, and will not be handled directly by ThymeleafViewResolver.", viewName);
final String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length(), viewName.length());
return new InternalResourceView(forwardUrl);
}
// Second possible call to check "viewNames": after processing redirects and forwards
if (this.alwaysProcessRedirectAndForward && !canHandle(viewName, locale)) {
vrlogger.trace("[THYMELEAF] View \"{}\" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
return null;
}
vrlogger.trace("[THYMELEAF] View {} will be handled by ThymeleafViewResolver and a " +
"{} instance will be created for it", viewName, getViewClass().getSimpleName());
return loadView(viewName, locale);
}
WebMvcAutoConfiguration
;找到一个日期格式化的方法 mvcConversionService()
,我们可以看一下@Bean
@Override
public FormattingConversionService mvcConversionService() {
WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
getDateFormat()
,这表示我们可以在配置文件中修改它默认的日期格式!# 自定义日期格式化
spring.mvc.date-format=yyyy-MM-dd
员工修改功能
编辑
/**
* 跳转到修改页面
*/
@GetMapping("/emp/{id}")
public String toUpdatePage(@PathVariable("id") Integer id, Model model) {
Employee employee = employeeDao.getEmployeeById(id);
model.addAttribute("emp", employee);
Collection
/**
* 修改员工
*/
@PutMapping("/emp")
public String updateEmp(Employee employee) {
employeeDao.addEmployee(employee);
return "redirect:/emps";
}
HiddenHttpMethodFilter
form
表单提交,为了将请求方法从post
转成 DELETE
,否则可能会走到修改请求或者是删除请求,具体走那个看这两个请求定义的先后顺序了!spring
针对这个问题为我们一个解决访问,我们只需要在web.xml
中配置一个转换请求方式的过滤器SpringBoot
中已经我们自动装配了!mvc
的自动配置类中,有个hiddenHttpMethodFilter
类@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = true)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
? ?return new OrderedHiddenHttpMethodFilter();
}
OrderedHiddenHttpMethodFilter
类,发现他继承了HiddenHttpMethodFilter
类/**
* 由于浏览器目前仅支持GET和POST,无法使用其他方法发送请求。
* 但该过滤器可以将发布的方法参数装换为相应的HTTP方法。
* 例如由Prototype库(JS的一个库)提供的,使用带有附加隐藏表单字段(_method),的普通POST方法来传递"真实" HTTP方法。
* 该过滤器读取该参数,隐藏域的name="_method",value仅允许使用delete、put、patch的HTTP方法
* 注意:如果是多部分POST请求,此过滤器需要在多部分处理之后运行,
* 因为它本身需要检查POST主体参数。
* 因此,通常,放置一个Spring的MultipartFilter}在您的web.xml过滤器链中的此HiddenHttpMethodFilter之前。
*/
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
// 允许的方法
private static final List
删除员工
a
标签的href
的值,将其赋值给form表单的action,form表单中通过隐藏域提交请求的方法,由JS来提交表单。
删除
/**
?* 删除用户
?*/
@DeleteMapping("/emp/{id}")
public String deleteEmp(@PathVariable("id") Integer id) {
? ? employeeDao.removeEmployeeById(id);
? ? return "redirect:/emps";
}
spring.mvc.hiddenmethod.filter.enabled=false
定制错误页面
注销功能
Sign out
/**
* 注销
*/
@GetMapping("/user/logout")
public String logout(HttpSession session) {
// 将 Session 删除
session.invalidate();
return "redirect:/index.html";
}
上一篇:Python实现队列
下一篇:Css 表格边框