我们先创建一个SpringBoot的 web 项目,并导入thymeleaf
、lombok
坐标。
1、环境搭建
首先我们先将静态资源及页面拷入到项目中。
然后我们编写 pojo、dao类
这里我们没有连接数据库 ,Dao层的数据是使用Map临时伪造的;等我们后面讲到数据库集成的时候在回来修改此项目。
Department类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
Employee 类
@Data
@NoArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
//1 male, 0 female
private Integer gender;
private Department department;
private Date birth;
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.department = department;
this.birth = new Date();// 默认创建日期
}
}
DepartmentDao 类
@Repository
public class DepartmentDao {
// 模拟数据库中的数据
private static Map<Integer,Department> departments = null;
static {
departments = new HashMap<Integer, Department>();
departments.put(101,new Department(101,"科研部"));
departments.put(102,new Department(102,"市场部"));
departments.put(103,new Department(103,"教育部"));
departments.put(104,new Department(104,"运营部"));
departments.put(105,new Department(105,"后勤部"));
}
// 获得所有部门信息
public Collection<Department> getDepartments(){
return departments.values();
}
//根据 id 获得部
public Department getDepartmentById(Integer id){
return departments.get(id);
}
}
EmployeeDao 类
@Repository
public class EmployeeDao {
// 模拟数据库中的数据
private static Map<Integer,Employee> employees = null;
// 员工有所属的部门
@Autowired
private DepartmentDao departmentDao;
static {
employees = new HashMap<Integer, Employee>();
employees.put(1001, new Employee(1001, "E-AA", "[email protected]", 1, new Department(101, "后勤部")));
employees.put(1002, new Employee(1002, "E-BB", "[email protected]", 1, new Department(102, "市场部")));
employees.put(1003, new Employee(1003, "E-CC", "[email protected]", 0, new Department(103, "教育部")));
employees.put(1004, new Employee(1004, "E-DD", "[email protected]", 0, new Department(104, "运营部")));
employees.put(1005, new Employee(1005, "E-EE", "[email protected]", 1, new Department(105, "科研部")));
}
// 主键自增
private static Integer initId = 1006;
// 添加一个员工
public void save(Employee employee){
if(employee.getId() == null){
employee.setId(initId++);
}
employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
employees.put(employee.getId(), employee);
}
// 查询全部员工
public Collection<Employee> getAll(){
return employees.values();
}
// 通过id 查询员工
public Employee get(Integer id){
return employees.get(id);
}
// 根据id 删除员工
public void delete(Integer id){
employees.remove(id);
}
}
2、默认访问首页
因为 页面我们放在了 templates
文件夹中,需要用Controller 跳转访问;
这我们使用通用的配置类实现;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
效果图:
这里我们只是看到了页面,并没有显示出样式;此时我们需要释放静态资源。
3、释放静态资源
- 我们需要先导入thymeleaf 坐标
- 在application.properties 文件中加入 关闭模板引擎缓存
spring.thymeleaf.cache=false
- 在 html 页面需要引入头文件
xmlns:th="http://www.thymeleaf.org"
- 要在每一个 css、js、img等位置使用 thymeleaf 语法显示,如:
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
4、国际化
详细步骤说明:https://blog.csdn.net/weixin_45606067/article/details/108098673
5、实现登录功能
1、在index.html 中更改请求,并增加每个输入框的 name 属性
2、编写Controller
@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) && "123456".equals(password)){
session.setAttribute("loginUser",username);
return "redirect:/main.html";
}else{
model.addAttribute("msg","用户或者密码错误");
return "index";
}
}
}
注意:这里我们要在 MyMvcConfig 类中的 addViewControllers 方法中加入 main 的跳转。
3、运行测试发现,我们登录失败之后页面没有对应的信息提示,不够人性化;此时我们想在页面上回显我们放在session的错误信息。
这里我们查看 thymeleaf 官网文档说明:#19
- #strings : utility methods for String objects:
/*
* ======================================================================
* See javadoc API for class org.thymeleaf.expression.Strings
* ======================================================================
*/
/*
* Null-safe toString()
*/
${#strings.toString(obj)} // also array*, list* and set*
/*
* Check whether a String is empty (or null). Performs a trim() operation before check
* Also works with arrays, lists or sets
*/
${#strings.isEmpty(name)}
${#strings.arrayIsEmpty(nameArr)}
${#strings.listIsEmpty(nameList)}
${#strings.setIsEmpty(nameSet)}
/*
* Perform an 'isEmpty()' check on a string and return it if false, defaulting to
* another specified string if true.
* Also works with arrays, lists or sets
*/
${#strings.defaultString(text,default)}
${#strings.arrayDefaultString(textArr,default)}
${#strings.listDefaultString(textList,default)}
${#strings.setDefaultString(textSet,default)}
/*
* Check whether a fragment is contained in a String
* Also works with arrays, lists or sets
*/
${#strings.contains(name,'ez')} // also array*, list* and set*
${#strings.containsIgnoreCase(name,'ez')} // also array*, list* and set*
/*
* Check whether a String starts or ends with a fragment
* Also works with arrays, lists or sets
*/
${#strings.startsWith(name,'Don')} // also array*, list* and set*
${#strings.endsWith(name,endingFragment)} // also array*, list* and set*
/*
* Substring-related operations
* Also works with arrays, lists or sets
*/
${#strings.indexOf(name,frag)} // also array*, list* and set*
${#strings.substring(name,3,5)} // also array*, list* and set*
${#strings.substringAfter(name,prefix)} // also array*, list* and set*
${#strings.substringBefore(name,suffix)} // also array*, list* and set*
${#strings.replace(name,'las','ler')} // also array*, list* and set*
/*
* Append and prepend
* Also works with arrays, lists or sets
*/
${#strings.prepend(str,prefix)} // also array*, list* and set*
${#strings.append(str,suffix)} // also array*, list* and set*
/*
* Change case
* Also works with arrays, lists or sets
*/
${#strings.toUpperCase(name)} // also array*, list* and set*
${#strings.toLowerCase(name)} // also array*, list* and set*
/*
* Split and join
*/
${#strings.arrayJoin(namesArray,',')}
${#strings.listJoin(namesList,',')}
${#strings.setJoin(namesSet,',')}
${#strings.arraySplit(namesStr,',')} // returns String[]
${#strings.listSplit(namesStr,',')} // returns List<String>
${#strings.setSplit(namesStr,',')} // returns Set<String>
/*
* Trim
* Also works with arrays, lists or sets
*/
${#strings.trim(str)} // also array*, list* and set*
/*
* Compute length
* Also works with arrays, lists or sets
*/
${#strings.length(str)} // also array*, list* and set*
/*
* Abbreviate text making it have a maximum size of n. If text is bigger, it
* will be clipped and finished in "..."
* Also works with arrays, lists or sets
*/
${#strings.abbreviate(str,10)} // also array*, list* and set*
/*
* Convert the first character to upper-case (and vice-versa)
*/
${#strings.capitalize(str)} // also array*, list* and set*
${#strings.unCapitalize(str)} // also array*, list* and set*
/*
* Convert the first character of every word to upper-case
*/
${#strings.capitalizeWords(str)} // also array*, list* and set*
${#strings.capitalizeWords(str,delimiters)} // also array*, list* and set*
/*
* Escape the string
*/
${#strings.escapeXml(str)} // also array*, list* and set*
${#strings.escapeJava(str)} // also array*, list* and set*
${#strings.escapeJavaScript(str)} // also array*, list* and set*
${#strings.escapeJavaScript(str)} // also array*, list* and set*
${#strings.unescapeJava(str)} // also array*, list* and set*
${#strings.unescapeJavaScript(str)} // also array*, list* and set*
/*
* Null-safe comparison and concatenation
*/
${#strings.equals(first, second)}
${#strings.equalsIgnoreCase(first, second)}
${#strings.concat(values...)}
${#strings.concatReplaceNulls(nullValue, values...)}
/*
* Random
*/
${#strings.randomAlphanumeric(count)}
通过查看文档第15行
在登录页面的适合位置填上以下代码:
<!--如果msg 值为空,则不显示消息-->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
测试我们重新启动服务再次运行就 OK 了!
6、登录拦截器
我们现在不登录,直接访问http://localhost:8080/main.html
地址也可以进入,这样是不够安全的。
这里我们编写登录拦截器类。
public class LoginHandlerInterceptor implements HandlerInterceptor {
// 登拦截器
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 登录成功得到用户session
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser ==null){
request.setAttribute("msg","没有权限,请先登录!");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else{
return true;
}
}
}
并且我们要将自定义的拦截器注入到 Ioc容器中。
// 注入登录拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**").
excludePathPatterns("/index.html","/","/user/login","/css/**","/js/**","/img/**");
}
注意:除了登录请求、登录成功后、静态资源等,其他的请求都需要被拦截。如果不过滤资源的话,会造成死循环。
此时我们测试即可。
7、展示员工列表
1、RestfulCRUD
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=1 | emp/{id}—DELETE |
实验的请求架构
实验功能 | 请求URI | 请求方式 |
---|---|---|
查询所有员工 | emps | GET |
查询某个员工(来到修改页面) | emp/1 | GET |
来到添加页面 | emp | GET |
添加员工 | emp | POST |
来到修改页面(查出员工进行信息回显) | emp/1 | GET |
修改员工 | emp | PUT |
删除员工 | emp/1 | DELETE |
2、抽取公共页面
我们把顶部导航栏、侧边栏每个页面都有的、可以复用的,抽取一个公共的供其他页面使用的。
- 需要在抽取代码的位置加上
th:fragment="topbar"
2. 在需要的页面引入抽取公共的资源即可<div th:replace="~{commons/commons::topbar}"></div>
这里我们可以用th:replace
或者th:insert
都可以实现代码的复用。
3、导航栏高亮
这里我们点击侧边栏的某个功能时,它既会跳转又会高亮我们所点击的。
如果要传递参数,可以直接使用 ()
传参,接收判断即可。
这里我们使用 thymeleaf 提供的三元运算符。
4、实现页面展示信息
<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>
<td th:text="${emp.getLastName()}"></td>
<td>[[${emp.getEmail()}]]</td>
<td th:text="${emp.getGender()==0?'女':'男'}"></td>
<td th:text="${emp.department.getDepartmentName()}"></td>
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
<td>
<button class="btn btn-sm btn-primary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</td>
</tr>
</tbody>
</table>
效果图:
8、增加员工信息
1、准备添加页面
首先我们要在展示页list.html
中增加 添加员工按钮。
<h2>
<a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a>
</h2>
跳转后的添加页面。
<form th:action="@{/emp}" method="post">
<div class="form-group">
<label>LastName</label>
<input type="text" class="form-control" name="lastName" placeholder="StarSea99">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" class="form-control" name="email" placeholder="[email protected]">
</div>
<div class="form-group">
<label>Gender</label><br/>
<div class="form-check form-check-inline">
<input class="form-check-label" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-label" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>Department</label>
<!--我们在controller中接收的是一个Employee,所以我们需要提交的是其中的一个属性 -->
<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="1990-01-01">
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
2、实现页面跳转及部门信息的回显
@GetMapping("/emp")
public String toAddEmp(Model model){
// 查出所有部门信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "emp/add";
}
3、实现Controller类
@PostMapping("/emp")
public String addEmp(Model model,Employee employee){
// 添加员工
employeeDao.save(employee);
return "redirect:/employee/list";
}
4、启动服务测试即可
9、修改员工信息
1、准备修改页面
首先我们要在展示页list.html
中增加 编辑按钮。
<td>
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.getId()}">编辑</a>
</td>
跳转后的修改页面。
<form th:action="@{/updateEmp}" method="post">
<input type="hidden" name="id" th:value="${emp.getId()}">
<div class="form-group">
<label>LastName</label>
<input type="text" class="form-control" th:value="${emp.getLastName()}" name="lastName" placeholder="StarSea99">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" class="form-control" th:value="${emp.getEmail()}" name="email" placeholder="[email protected]">
</div>
<div class="form-group">
<label>Gender</label><br/>
<div class="form-check form-check-inline">
<input class="form-check-label" th:checked="${emp.getGender()==1}" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-label" th:checked="${emp.getGender()==0}" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>Department</label>
<!--我们在controller中接收的是一个Employee,所以我们需要提交的是其中的一个属性 -->
<select class="form-control" name="department.id">
<option th:each="dept:${departments}" th:selected="${dept.getId()==emp.department.getId()}" th:text="${dept.getDepartmentName()}"
th:value="${dept.getId()}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input type="text" name="birth" th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm')}" class="form-control" placeholder="1990-01-01">
</div>
<button type="submit" class="btn btn-primary">更新</button>
</form>
2、跳转更新页面,并回显页面信息
// 去员工的修改页面
@GetMapping("/emp/{id}")
public String toUpdate(@PathVariable("id") Integer id ,Model model){
// 查询原来数据
Employee employee = employeeDao.get(id);
model.addAttribute("emp",employee);
// 查出所有部门信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "emp/update";
}
3、编写Controller 类
//更新员工
@PostMapping("/updateEmp")
public String updateEmp(Employee employee){
employeeDao.save(employee);
return "redirect:/employee/list";
}
4、启动服务测试即可
10、删除员工以404处理
删除功能
1、首先我们要在展示页list.html
中增加 删除按钮。
<td>
<a class="btn btn-sm btn-danger" th:href="@{/delemp/}+${emp.getId()}">删除</a>
</td>
2、编写Controller类
// 删除员工
@GetMapping("/delemp/{id}")
public String deleteEmp(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/employee/list";
}
3、启动服务测试即可
404处理
我们只需要在templates 下创建error文件夹,并将页面放到里面。
注意:命名为404.html
,SpringBoot会自动识别,不需要我们有多余的配置即可。
11、退出登录
1、修改退出按钮的请求地址
<li class="nav-item text-nowrap">
<a class="nav-link" th:href="@{/user/logout}">退出</a>
</li>
2、编写Controller 类
// 退出功能
@RequestMapping("/user/logout")
public String logout(HttpSession session){
session.invalidate();
return "redirect:/index.html";
}
3、启动服务测试即可
以上就是 RestfulCRUD 的全部功能!!!
如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客