web开发
RestfulCRUD
1)、默认访问首页
//情况一
@RequestMapping({"/","/index.html"})
public String index(){
return "login";
}
//情况二
// 所有的WebMvcConfigurer组件都会一起起作用
@Bean //将组件注册在容器
public WebMvcConfigurer webMvcConfigurer(){
WebMvcConfigurer adapter = new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController( "/" ).setViewName( "login" );
registry.addViewController( "/index.html" ).setViewName( "login" );
}
};
return adapter;
}
根据视图解析器知道路径是在
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
2)、国际化
1)、编写国际化配置文件;
2)、使用ResourceBundleMessageSource管理国际化资源文件
3)、在页面使用fmt:message取出国际化内容
步骤:
1)、编写好国际化配置文件,抽取页面需要显示的国际化消息
2)、SpringBoot自动配置好了管理国际化资源配置的组件;
public class MessageSourceAutoConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.messages")
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
/**
* Comma-separated list of basenames (essentially a fully-qualified classpath
* location), each following the ResourceBundle convention with relaxed support for
* slash based locations. If it doesn't contain a package qualifier (such as
* "org.mypackage"), it will be resolved from the classpath root.
*/
private String basename = "messages";
//我们的配置文件可以直接放在类路径下叫messages.properties
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
//设置国际化资源文件的基础名(去掉语言国家代码)
messageSource.setBasenames(StringUtils
.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
3)、去页面获取国际化的值;
<!doctype html>
<html lang="en" 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">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Jekyll v4.0.1">
<title>Signin Template · Bootstrap</title>
<link rel="canonical" href="https://getbootstrap.com/docs/4.5/examples/sign-in/">
<!-- Bootstrap core CSS -->
<link href="../assets/dist/css/bootstrap.css" th:href="@{/webjars/bootstrap/4.5.0/css/bootstrap.css}" rel="stylesheet">
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
</style>
<!-- Custom styles for this template -->
<link href="../assets/css/signin.css" th:href="@{assets/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin">
<img class="mb-4" th:src="@{/assets/brand/bootstrap-solid.svg}" src="../assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal"th:text="#{login.tip}">Please sign in</h1>
<label for="inputEmail" class="sr-only" th:text="#{login.email}">Email address</label>
<input type="email" id="inputEmail" class="form-control" placeholder="Email address" th:placeholder="#{login.email}" required autofocus>
<label for="inputPassword" class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" id="inputPassword" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required>
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2020</p>
</form>
</body>
</html>
效果:根据浏览器语言设置的信息切换了国际化;
原理:
国际化Locale(区域信息对象);LocaleResolver(获取区域信息对象);
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
//默认的就是根据请求头带来的区域信息获取Locale进行国际化
@ConditionalOnMissingBean
根据这个注解,自己往bean容器中添加LocaleResolver 组件;
4)、
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String l = request.getParameter( "l" );
Locale locale = Locale.getDefault();
if(!StringUtils.isEmpty( l )){
String[] split = l.split( "_" );
locale = new Locale( split[0], split[1] );
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
3)、登陆
开发期间模板引擎修改以后,要实时生效;
1)、禁用模板引擎的缓存;
#关闭thymeleaf缓存
spring.thymeleaf.cache=false
2)、页面修改以后Ctrl+F9
;重新编译;
登录信息的错误显示
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
4)、拦截器进行登录检查
* 登陆检查
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
// 目标方法执行之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute( "loginUser" );
if(user==null){
// 未登录,返回登录页面
request.setAttribute( "msg","没有权限,请先登录" );
request.getRequestDispatcher( "/index.html" ).forward( request,response );
return false;
}else{
// 已登录,放行请求:
return true;
}
}
}
// #######################
@Bean //将组件注册在容器
public WebMvcConfigurer webMvcConfigurer(){
WebMvcConfigurer adapter = new WebMvcConfigurer() {
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 静态资源: *.css *.js
// SpringBoot已经做好了静态资源映射
registry.addInterceptor( new LoginHandlerInterceptor()).addPathPatterns( "/**" )
.excludePathPatterns( "/index.html","/","/user/login" );
}
};
return adapter;
}
5)、CURD员工列表
实验要求:
1)、RestfulCURD:CRUD满足Rest分格;
URI:/资源名称/资源标识 HTTP请求方式区分资源CRUD操作
普通CRUD(uri来区分操作) | RestfulCRUD | |
---|---|---|
查询 | getEmp | emp—GET |
添加 | addEmp?xxx | emp—POST |
修改 | updateEmp?id=xxx&xxx=xx | emp/{id}—PUT |
删除 | deleteEmp?id | emp/{id}—DELETE |
2)、实验的请求架构;
请求URI | 请求方式 | |
---|---|---|
查询所有员工 | emps | GET |
查询某个员工(来到修改页面) | emp/1 | GET |
来到添加页面 | emp | GET |
添加员工 | emp | POST |
来到修改页面(查出员工进行信息回显) | emp/1 | GET |
修改员工 | emp | PUT |
删除员工 | emp/1 | DELETE |
5)、CRUE-员工列表
@GetMapping("/emps")
public String list(Model model){
// thymeleaf默认就会拼接
// classpath:/templates/xxxx.html
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute( "emps",employees );
return "emp/list";
}
<table class="table table-striped table-sm">
<thead>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>brith</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}" >
<td>[[${emp.id}]]</td>
<td>[[${emp.lastName}]]</td>
<td th:text="*{emp.email}">ipsum</td>
<td th:text="${emp.gender}==0?'male':'female'">ipsum</td>
<td th:text="${emp.department.getDepartmentName()}">ipsum</td>
<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}">ipsum</td>
<td>
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
<a class="btn btn-sm btn-danger" th:attr="del_uri=@{/emp/}+${emp.id}">删除</a>
</td>
</tr>
</tbody>
</table>
thymeleaf公共页面元素抽取
单独抽取出公共页面元素
方式一:
th:fragment="topbar"
,添加th:fragment标签指定公共元素
在通过th:replace
配合~{commons/bar::topbar}
添加公共元素
<div th:replace="~{commons/bar::topbar}"></div>
方式二:
给公共页面元素添加指定id="sidebarMenu"
在通过th:replace="commons/bar::#sidebarMenu(activeUri='emps')"
可以传参进行逻辑判断
th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}"
<div th:replace="commons/bar::#sidebarMenu(activeUri='emps')"></div>
公共页面元素demo:
<!--topbar -->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow" th:fragment="topbar" >
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#" th:text="${session.loginUser}">Company name</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="#">Sign out</a>
</li>
</ul>
</nav>
<!--sidebar-->
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse" >
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" href="#" th:href="@{/main.html}"
th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}">
<span data-feather="home"></span>
Dashboard <span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span data-feather="file"></span>
Orders
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span data-feather="shopping-cart"></span>
Products
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" th:href="@{/emps/}"
th:class="${activeUri=='emps'?'nav-link active':'nav-link'}">
<span data-feather="users"></span>
<!-- Customers-->
员工管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span data-feather="bar-chart-2"></span>
Reports
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span data-feather="layers"></span>
Integrations
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Saved reports</span>
<a class="d-flex align-items-center text-muted" href="#" aria-label="Add a new report">
<span data-feather="plus-circle"></span>
</a>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link" href="#">
<span data-feather="file-text"></span>
Current month
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span data-feather="file-text"></span>
Last quarter
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span data-feather="file-text"></span>
Social engagement
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span data-feather="file-text"></span>
Year-end sale
</a>
</li>
</ul>
</div>
</nav>
6)、CRUD-页面添加
1、通过Restful @GetMapping("/emp")
请求跳转到"emp/add"
thymeleaf默认就会拼接classpath:/templates/xxxx.html
在add.html页面,表单提交<form th:action="@{/emp}" method="post">
跳转到@PostMapping("/emp")
接口,对上传的数据employee进行保存(employee已经通过@RequestMapping将json转化为employee对象)保存完后,
// redirect:表示重定向到一个地址
// forword:表示转发到一个地址
return “redirect:/emps”;
重定向到 @GetMapping("/emps")接口,刷新list页面;
// 来到员工添加页面
@GetMapping("/emp")
public String toAddPage(Model model){
// 来到添加页面,查出所有的部门,在页面显示
Collection<Department> departments = DepartmentDao.getDepartments();
model.addAttribute( "depts",departments );
return "emp/add";
};
//员工添加
// springMVC自动将请求参数和入参对象的属性进行一一绑定;要求了请求参数的名字和Bean入参的对象里面的属性名是一样的;
@PostMapping("/emp")
public String addEmp(Employee employee){
log.info("保存的员工信息:"+employee);
employeeDao.save( employee );
// 来到模板列表页面
// redirect:表示重定向到一个地址
// forword:表示转发到一个地址
return "redirect:/emps";
}
<!doctype html>
<html lang="en" 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">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Jekyll v4.0.1">
<title>Dashboard Template · Bootstrap</title>
<link rel="canonical" href="https://getbootstrap.com/docs/4.5/examples/dashboard/">
<!-- Bootstrap core CSS -->
<link href="../assets/dist/css/bootstrap.css" th:href="@{/webjars/bootstrap/4.5.0/css/bootstrap.css}" rel="stylesheet">
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
</style>
<!-- Custom styles for this template -->
<link href="dashboard.css" th:href="@{/assets/css/dashboard.css}" rel="stylesheet">
</head>
<body>
<div th:replace="~{commons/bar::topbar}"></div>
<div class="container-fluid">
<div class="row">
<div th:replace="commons/bar::#sidebarMenu(activeUri='emps')"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<form th:action="@{/emp}" method="post">
<!-- 需要区分是员工修改还是添加;-->
<!-- 1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)-->
<!-- 2、页面创建一个post表单-->
<!-- 3、创建一个input项,name="_method";值就是我们请求的请求方式-->
<input type="hidden" name="_method" value="put" th:if="${emp!=null}">
<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
<div class="form-group">
<label>LastName</label>
<input name="lastName" th:value="${emp!=null}?${emp.lastName}" type="text" class="form-control" placeholder="zhangsan">
</div>
<div class="form-group">
<label>Email</label>
<input name="email" th:value="${emp!=null}?${emp.email}" type="email" class="form-control" placeholder="zhangsan@atguigu.com">
</div>
<div class="form-group">
<label>Gender</label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${emp.gender==1}">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp!=null}?${emp.gender==0}">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select class="form-control" name="department.id">
<option th:selected="${emp!=null}?${emp.department.id}==${dept.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input name="birth" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}" type="text" class="form-control" placeholder="zhangsan">
<!-- <input name="birth" type="text" class="form-control" placeholder="zhangsan">-->
<!-- [[${emp!=null}?${emp.birth}]]-->
</div>
<input type="hidden" value="" name="id">
<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'提交'">Submit</button>
</form>
</main>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="../assets/js/vendor/jquery.slim.min.js"><\/script>')</script><script src="../assets/dist/js/bootstrap.bundle.js" th:src="@{/assets/dist/js/bootstrap.bundle.js}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/feather-icons/4.9.0/feather.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js"></script>
<script src="dashboard.js" th:src="@{/assets/js/dashboard.js}"></script></body>
</html>
7)、CRUD-页面修改
首选在list页面中添加超链接(这里采用了thymeleaf模板th:href="@{/emp/}+${emp.id}
)拼接处URI路径
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
跳转到@GetMapping("/emp/{id}")
通过springframework的注解@PathVariable("id")
获取URI中传递的值通过传递的id值取dao层查询对应的数据,并且回显到页面(这里,复用了add页面,因为是复用页面,在添加接口中没有emp这个参数,所以在展示页面的时候我们需要做非空判断保证在添加接口调用时候不会报错。th:value="${emp!=null}?${emp.lastName}
")
在编辑完信息后我们需要保存编辑完后的信息,我们需要开启Restful的put请求,
SpringBoot 2.2.X默认不支持put,delete等请求方式的我们需求去自己配置开启,
spring.mvc.hiddenmethod.filter.enabled=true
在WebMvcAutoConfiguration中手动打开,配置,具体配置在HiddenHttpMethodFilter中,或者我们继承HiddenHttpMethodFilter主动注入到容器中。
具体的html页面操作:
<!-- 需要区分是员工修改还是添加;-->
<!-- 1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)-->
<!-- 2、页面创建一个post表单-->
<!-- 3、创建一个input项,name="_method";值就是我们请求的请求方式-->
<input type="hidden" name="_method" value="put" th:if="${emp!=null}">
然后submit提交之后,就进入到Restful的put接口,@PutMapping("/emp")
对上传的数据进行保存;
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
// 来到修改页面,查出当前员工,在页面回显
@GetMapping("/emp/{id}")
public String toEditPage(@PathVariable("id")Integer id,Model model){
Employee employee = employeeDao.get( id );
model.addAttribute( "emp",employee);
// 回到修改页面;(add是一个修改添加二合一的页面);
Collection<Department> departments = DepartmentDao.getDepartments();
model.addAttribute( "depts",departments );
return "emp/add";
}
@PutMapping("/emp")
public String updateEmployee(Employee employee){
log.info( "修改员工数据: "+employee );
employeeDao.save( employee );
return "redirect:/emps";
}
8)、CRUD-员工删除
员工删除,需要调用Restful的delete方式,SpringBoot 2.2.X默认不支持put,delete等请求方式的我们需求去自己配置开启,
我们拼接URI传递id值,@DeleteMapping("/emp/{id}")
我们通过@PathVariable("id")
获取URI中的id值,进行删除操作,操作完成后,进行重定向(return "redirect:/emps";
),刷新list页面;
@DeleteMapping("/emp/{id}")
public String deleteEmployee(@PathVariable("id") Integer id){
log.info( "Employee id:"+id );
employeeDao.delete( id );
return "redirect:/emps";
}
<!--方式一 -->
<form th:action="@{/emp/}+${emp.id}" method="post">-->
<input type="hidden" name="_method" value="delete">-->
<button type="submit" class="btn btn-sm btn-danger">删除</button>
</form>
<!--方式二 -->
<a class="btn btn-sm btn-danger" th:attr="del_uri=@{/emp/}+${emp.id}">删除</a>
<form id="deleteEmpFrom" method="post">
<input type="hidden" name="_method" value="delete">
</form>