3. SpringBoot Web 开发
3.1 导入静态资源
(1) webjars
-
导入 jquery 依赖
<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.6.0</version> </dependency>
-
访问 jquery.js 文件
http://localhost:8080/webjars/jquery/3.6.0/jquery.js
(2) WebProperties ( SpringBoot 3.5.4 )
-
静态资源默认路径(访问优先级由高到低排序)
classpath:/META-INF/resources/ classpath:/resources/ classpath:/static/ classpath:/public/
一般 public 目录存放公共资源,如各模块需要调用的 js;static 目录存放静态资源,如图片;
resources 目录存放上传的文件。
3.2 配置首页
- 将 index.html 文件放在静态资源默认路径下,一般放在 static 目录下。
- 标签页图标 favicon.ico 放在 static 目录下。
3.3 模板引擎
3.3.1 配置模板引擎 Thymeleaf
(1) 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
(2) 将 html 文件放在对应目录下
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
(3) html 文件引入约束
<html lang="en" xmlns:th="http://www.thymeleaf.org/">
3.3.2 Thymeleaf 语法
- Tutorial: Using Thymeleaf
3.4 扩展 SpringMVC
-
config 目录下自定义配置类
// 扩展 SpringMvc,不能加 @EnableWebMvc @Configuration public class MyMvcConfig implements WebMvcConfigurer { @Bean public ViewResolver myViewResolver() { return new MyViewResolver(); } // 自定义一个视图解析器 public static class MyViewResolver implements ViewResolver { @Override public View resolveViewName(String viewName, Locale locale) throws Exception { return null; } } }
3.5 国际化
(1) 配置 i18n 文件
-
编写配置文件
-
绑定到 html 文件
(2) 自定义组件
-
添加 html 国际化按钮
-
编写组件
public class MyLocaleResover implements LocaleResolver { // 解析请求 @Override public Locale resolveLocale(HttpServletRequest request) { // 获取请求中的语言参数 String language = request.getParameter("l"); // 获取默认设置 Locale locale = Locale.getDefault(); if (!(StringUtils.isEmpty(language))) { // zh_CN String[] split = language.split("_"); locale = new Locale(split[0], split[1]); } return locale; } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { } }
(3) 将组件注册到 Spring 中
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
// 国际化组件注册到Spring
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResover();
}
}
(4) 添加配置
# application.properties
# 配置文件位置
spring.messages.basename=i18n.login
3.6 登录实现
(1) 设置 html 输入框 name
<input type="text" class="form-control" name="username" th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" class="form-control" name="password" th:placeholder="#{login.password}" required="">
(2) 设置表单提交路径
<form class="form-signin" th:action="@{/user/login}">
(3) 编写登录控制器
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model, HttpSession session) {
// 用户名不为空,密码为 123456
if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
// 向 session 传入登录标识
session.setAttribute("loginUser", username);
// 重定向到面板
return "redirect:/main.html";
} else {
// 向 msg 传入信息
model.addAttribute("msg", "用户名或密码错误");
return "index";
}
}
}
(4) 编写登录拦截器
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取登录标识
Object loginUser = request.getSession().getAttribute("loginUser");
// 没有登录
if (loginUser == null) {
// 向 msg 传入信息
request.setAttribute("msg", "没有权限");
// 跳转到 index
request.getRequestDispatcher("/index.html").forward(request, response);
return false;
} else {
return true;
}
}
}
(5) 添加登录拦截器
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// / 可访问到 index.html
registry.addViewController("/").setViewName("index");
// index.html 可访问到 index
registry.addViewController("/index.html").setViewName("index");
// /main.html 可访问到 dashboard
registry.addViewController("/main.html").setViewName("dashboard");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加登录拦截器
registry.addInterceptor(new LoginHandlerInterceptor())
// 设置拦截对象
.addPathPatterns("/**")
// 排除拦截对象
.excludePathPatterns("/index.html", "/", "/user/login","/css/**", "/js/**", "/img/**");
}
}
3.7 列表展示
3.7.1 提取公共页面
(1) 提取代码
<!--resources/templates/commons/commons.html-->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
...
</nav>
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
...
</nav>
(2) 插入代码
<!--resources/templates/dashboard.html-->
<div th:replace="~{commons/commons::topbar}"></div>
<!--传递参数-->
<div th:replace="~{commons/commons::sidebar(active='main.html')}"></div>
<!--resources/templates/emp/list.html-->
<div th:insert="~{commons/commons::topbar}"></div>
<!--传递参数-->
<div th:insert="~{commons/commons::sidebar(active='list.html')}"></div>
(3) 接收参数
<a th:class="${active == 'main.html'?'nav-link active':'nav-link'}" th:href="@{/index.html}">
...
</a>
<a th:class="${active == 'list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">
...
</a>
3.7.3 渲染列表
(1) 控制器获取数据
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
@Autowired
DepartmentDao departmentDao;
@RequestMapping("/emps")
public String list(Model model) {
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("emps", employees);
return "emp/list";
}
}
(2) 渲染列表
<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>birth</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.getId()}"/>
<td th:text="${emp.getLastName()}"/>
<td th:text="${emp.getEmail()}"/>
<td th:text="${emp.getGender()==0?'女':'男'}"/>
<td th:text="${emp.getDepartment().departmentName}"/>
<td th:text="${#dates.format(emp.getBirth(),'YYYY-MM-DD HH:mm:ss')}"/>
<td>
<button class="btn btn-sm btn-primary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</td>
</tr>
</tbody>
</table>
3.8 添加记录
(1) 添加界面
<form th:action="@{/emp}" method="post">
<div class="form-group">
<label>LastName</label>
<input type="text" name="lastName" class="form-control" placeholder="why">
</div>
<div class="form-group">
<label>Email</label>
<input type="text" name="email" class="form-control" placeholder="123456@qq.com">
</div>
<div class="form-group">
<label>Gender</label>
<div class="form-check form-check-inline">
<input type="radio" class="form-check-input" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" class="form-check-input" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
</div>
<div class="form-group">
<label>Department</label>
<select class="form-control" name="department.id">
<option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input type="text" name="birth" class="form-control" placeholder="2021-01-01">
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
</form>
- 注意 HTML 元素名称和实体属性名称的对应
(2) 编写控制器
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
@Autowired
DepartmentDao departmentDao;
@GetMapping("/emp")
public String toAddPage(Model model) {
// 获取部门数据
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments", departments);
return "emp/add";
}
@PostMapping("/emp")
public String addEmp(Employee employee) {
System.out.println("receive_emp ==>" + employee);
// 保存员工数据
employeeDao.save(employee);
return "redirect:/emps";
}
}
3.9 修改记录
(1) 编写请求链接
<!--list.html-->
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.getId()}">编辑</a>
(2) 编写页面跳转控制器
// EmployeeController.java
@GetMapping("/emp/{id}")
public String toUpdateEmp(@PathVariable("id") Integer id, Model model) {
// 获取员工数据
Employee employee = employeeDao.getEmployeeById(id);
model.addAttribute("emp", employee);
// 获取部门数据
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments", departments);
return "emp/update";
}
(3) 添加修改界面
<!--update.html-->
<form th:action="@{/updateEmp}" method="post">
<!--携带 id 的隐藏域-->
<input type="hidden" name="id" th:value="${emp.getId()}">
<div class="form-group">
<label>LastName</label>
<input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="why">
</div>
<div class="form-group">
<label>Email</label>
<input th:value="${emp.getEmail()}" type="text" name="email" class="form-control" placeholder="123456@qq.com">
</div>
<div class="form-group">
<label>Gender </label>
<div class="form-check form-check-inline">
<input th:checked="${emp.getGender() == 0}" type="radio" class="form-check-input" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
<div class="form-check form-check-inline">
<input th:checked="${emp.getGender() == 1}" type="radio" class="form-check-input" name="gender" value="1">
<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="${dept.getId() == emp.getDepartment().getId()}" th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<!--日期格式化-->
<input th:value="${#dates.format(emp.getBirth(), 'yyyy-MM-dd HH:mm')}" type="text" name="birth" class="form-control" placeholder="2021-01-01">
</div>
<button type="submit" class="btn btn-primary">修改</button>
</form>
(4) 编写修改员工控制器
@PostMapping("/updateEmp")
public String updateEmp(Employee employee) {
employeeDao.save(employee);
return "redirect:/emps";
}
3.10 删除记录
(1) 编写请求链接
<!--list.html-->
<a class="btn btn-sm btn-danger" th:href="@{/deleteEmp/}+${emp.getId()}">删除</a>
(2) 编写删除控制器
@GetMapping("/deleteEmp/{id}")
public String deleteEmp(@PathVariable("id") Integer id) {
employeeDao.deleteEmp(id);
return "redirect:/emps";
}
3.11 错误页面和注销
(1) 错误页面
-
templates 下添加 error 路径
-
将错误页面以错误代码命名放入 error 目录下
(1) 注销
● 编写链接
<!--commons.html-->
<a class="nav-link" th:href="@{/user/logout}">注销</a>
● 编写控制器
// LoginController.java
@RequestMapping("/user/logout")
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/index.html";
}