# 目录
[TOC]
# 员工管理系统DEMO
## 一、安装lombok插件
>这里我们使用lombok帮助我们自动生成pojo包的getter和setter等函数结构
在IDEA中安装lombok插件

等待插件安装完成后,在项目pom中导入lombok
```xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
```
## 二、构建数据库(模拟)
> 在整合mybets之前,我们手动构建pojo和dao层进行模拟数据库
使用相应的注解来完善pojo结构
- `@Data` 注解用于生成属性的getter与setter
- `@AllArgsConstructor` 用于生成有参构造函数
- `@NoArgsConstructor` 用于生成无参构造函数
### 0x01 pojo层
Department.java
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
```
Employee.java
```java
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;
private Department department;
private Date birth;
}
```
### 0x02 dao层
## 二、登录页面实现
在自定义MVC配置中重写一个 `addViewControllers` ,用来添加一些基本的视图控制器
```java
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/index/").setViewName("index");
}
}
```
测试访问 `http://localhost:8080/index`

## 三、页面国际化配置(多语言)
### 0x01 添加配置文件
设置IDEA的文件编码

在 resources 目录下创建 `i18n` 目录,用来存放多语言的配置文件,并创建如下文件结构

`login.properties` 是我们的主配置文件, `login_en_XX.properties` 是我们的指定的各个语言的配置文件
点击可视化配置 `Resource Bundle` ,添加一个配置键值对,这里我们命名`login.tip`

在编辑器的右边输入对 `login.tip` 进行各个语言的配置,会分别映射到我们刚才创建好的几个语言的配置文件中

按照如上步骤,我们把页面的几个标签对应的也配置好
在配置文件 `application.properties` 中绑定多语言配置文件位置
```properties
spring.messages.basename=i18n.login
```
### 0x02 替换HTML中的标签
使用 thymeleaf 模板语法中的 `th:XX="#{}"` 对各个标签进行接管,例如
```html
<label class="sr-only" th:text="#{login.username}">Username</label>
```
`#{}` 中填写刚才我们在配置文件中添加的键
### 0x03 自定义一个转换器
在config中自定义一个转换器 `MyLocaleResolver` ,根据用户请求的参数进行设置页面的语言
```java
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的语言参数
String language = request.getParameter("lang");
Locale locale = Locale.getDefault(); //如果没有就使用默认的
//如果请求的链接携带了国际化的参数
if (!StringUtils.isEmpty(language)){
//分割字符串
String[] lang_split = language.split("_");
locale = new Locale(lang_split[0], lang_split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
```
在我们之前自定义的mvc类中,使用 `@Bean` 注解将我们的转换器 `MyLocaleResolver` 配置到spring容器内
```java
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/index/").setViewName("index");
}
//将自定义的国际化组件注册到spring bean中
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
```
接下来在HTML模板中使用thymeleaf模板语法中的 `th:href="@{xxx.html(key=value)}"`,设置跳转请求
```html
<a class="btn btn-sm" th:href="@{/index.html(lang='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(lang='en_US')}">English</a>
```
重新启动项目,查看效果

成功实现多语言的切换
## 四、登录功能实现
### 0x01 接口测试
创建一个Controller,我这里命名为 `LoginController` ,编写基本的结构,返回一个字符串进行测试
```java
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String Login(){
return "OK";
}
}
```
在HTML中提交的表单中修改请求url
```html
<form class="form-signin" action="/user/login" method="POST">
```
测试

### 0x02 编写登录逻辑
```java
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String Login(
@RequestParam("username") String username,
@RequestParam("password") String password,
Model model
){
if(("admin".equals(username) && "123123".equals(password))){
session.setAttribute("loginUser", username);
//重定向至main页面
return "redirect:/main";
}else{
model.addAttribute("msg", "用户名或密码错误"); //将错误信息渲染至页面
return "index";
}
}
}
```
这里暂时没有整合数据库,所以直接判断页面提交的值是否等于预定义的值,如果等于则重定向到main页面,否则返回登录页并渲染错误信息
前端页面新增一个p标签用于显示错误信息,使用 `thymeleaf` 模板引擎进行渲染
```html
<!--如果msg值为空则不显示错误信息-->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
```
添加一个main页面的路由,这里我在自定义的mvc配置类中重写了一个视图控制器,也可以自己新增一个controller进行路由
```java
registry.addViewController("/main").setViewName("dashboard");
```
### 0x03 添加登录拦截器
自定义一个拦截器`LoginHandlerInterceptor`,并实现 `HandlerInterceptor` 的`preHandle`方法,自定义拦截的逻辑
```java
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){
//设置提示信息
request.setAttribute("msg","没有权限,请先登录");
//跳转回到主页
request.getRequestDispatcher("/").forward(request, response);
return false;
}
return true;
}
}
```
在我们自定义的springmvc配置类中注册我们的拦截器 `LoginHandlerInterceptor`
```java
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//设置拦截的黑名单和白名单
//我们还要给静态资源设置白名单,不然显示不出来
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/", "/index*","/user/login", "/css/**", "/img/**", "/js/**");
}
```
登录成功后取session中的用户名显示到main页面
```html
<a>[[${session.loginUser}]]</a>
```
### 0x04 测试
测试拦截器、登录验证、主页显示

## 五、展示员工信息
### 0x01 定义页面模板
我们从main页面种可以看出,页面的顶部栏和侧边栏的样式的固定的,所以我们可以将这两个部分独立成模块,在创建新的页面时我们可以直接复用预定义好的模块,减少代码量
我们新建一个 `base.html` 的页面,用于定义我们模块的代码,使用 `th:fragment` 标签定义模块的名称,在其他页面使用 `th:insert` 或 `th:replace` 引用模板
- `th:insert` :保留自己的主标签,保留th:fragment的主标签。
- `th:replace` :不要自己的主标签,保留th:fragment的主标签。
定义模板
```html
<!--顶部栏-->
<nav th:fragment="topBar" class="navbar"></nav>
```
```html
<!--侧边栏-->
<nav th:fragment="leftBar" class="col-md-2"></nav>
```
引用模板
```html
<!--顶部栏-->
<div th:replace="~{commons/dashboard_base :: topBar}"></div>
```
```html
<!--侧边栏-->
<div th:replace="~{commons/dashboard_base :: leftBar}"></div>
```
### 0x02 侧边栏高亮
#### 需求
在点击侧边栏的功能时候,需要在跳转页面后将指定的项高亮显示,例如下图

#### 具体实现
在引用模板时候增加一个页面变量作为标识,如下代码
```html
<div th:replace="~{commons/dashboard_base :: leftBar(barType='emps')}"></div>
```
在上面的代码当中,我们定义了一个`barType`的变量,作为页面类型的标识
接下来我们在base模板中对页面传递过来的变量进行识别
```html
<li class="nav-item">
<a th:class="${barType == 'main'? 'nav-link active': 'nav-link'}" th:href="@{/main}"></a>
</li>
<li class="nav-item">
<a th:class="${barType == 'emps'? 'nav-link active': 'nav-link'}" th:href="@{/emps/info}"></a>
</li>
```
在上面的代码当中,我们使用了`thymeleaf` 的三元运算符进行渲染,如果传递过来的`barType` 符合预期设定的值,则在标签的class中增加`active`实现高亮
### 0x03 控制器
定义一个Controller,使用`Autowired` 标识将`EmployeeDao`注入到当前Controller中,用于实现对employee的pojo的一些操作,调用了`getEmpsInfo`函数获取所有员工信息的值,并将所有员工信息作为model传递到页面中,代码如下
> EmployeeController.java
```java
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao; //注入
@GetMapping("/emps/info")
public String showInfo(Model model){
Collection<Employee> empsInfo = employeeDao.getEmpsInfo();
model.addAttribute("empsInfo", empsInfo);
return "employee/info";
}
}
```
在info页面中定义一个table标签,使用 `th:each` 对controller传递到页面的员工信息进行遍历,并渲染到页面,代码实现如下
> emps/info.html
```html
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>性别</th>
<th>部门</th>
<th>生日</th>
<th>操作</th>
</tr>
</thead>
<tr th:each="emp:${empsInfo}">
<th th:text="${emp.id}"></th>
<td th:text="${emp.lastName}">2</td>
<td th:text="${emp.email}">3</td>
<td th:text="${emp.gender}">4</td>
<td th:text="${emp.department.departmentName}">5</td>
<!--格式化日期-->
<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"></td>
<td>
<button type="button" class="btn btn-primary btn-sm">编辑</button>
<button type="button" class="btn btn-danger btn-sm">删除</button>
</td>
</tr>
</table>
```
### 0x05 实现效果

## 六、添加员工信息
### 0x01 实现思路
> 1. 点击添加员工按钮,提交GET请求至控制器,并携带部门信息渲染至add页面
> 2. 用户填写信息,提交POST请求至controller
> 3. 控制器将表单提交的employee对象保存至对象数据中
### 0x02 提交GET请求
> /emps/info.html
```html
<h2><a style="color: white;" class="btn btn-sm btn-info" th:href="@{/emps/add}">添加员工</a></h2>
```
控制器返回add页面,并携带部门名称信息
```java
@Autowired
EmployeeDao employeeDao; //注入Dao
@Autowired
DepartmentDao departmentDao;
@GetMapping("/emps/add")
public String addEmp(Model model){
Collection<Department> departments = departmentDao.getDepartments(); //传递部门信息到前端
model.addAttribute("departments", departments);
return "employee/add";
}
```
add.html页面主体数据
```html
<!--添加员工表单-->
<form th:action="@{/emps/add}" th:method="POST">
<div class="form-group">
<label>LastName</label>
<input name="lastName" type="text" class="form-control" placeholder="b5ck">
</div>
<div class="form-group">
<label>Email</label>
<input name="email" type="email" class="form-control" placeholder="XXXXX@qq.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" checked="true">
<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">
<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="dep:${departments}" th:value="${dep.getId()}" th:text="${dep.getDepartmentName()}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input NAME="birth" type="text" class="form-control date-cell" placeholder="2020-01-01">
</div>
<button type="submit" class="btn btn-primary">添加</button>
```
页面效果

### 0x03 提交POST请求
携带用户填写的数据,控制器接收请求
```java
@PostMapping("/emps/add")
public String addEmpPost(Employee employee, RedirectAttributes model){
employeeDao.save(employee);
model.addFlashAttribute("add",true);
return "redirect:/emps/info";
// return "employee/info";
}
```
在上述代码中,接收到了用户提交表单的数据,转换为`Employee`对象,并调用前面代码中注入的`employeeDao`中的save函数将对象写入现有的数据中。
写入成功后,我们传递一个名称为`add`的model值至页面,用于标识添加成功,在页面添加相应的提示,因为这里使用的是`redirect`进行重定向页面,所以需要使用RedirectAttributes对象来传递`model`值至页面。
在info页面中添加一个提示框标签
```HTML
<!--添加成功提示框-->
<div th:if="${add == true}" class="alert alert-success alert-dismissable">
<button type="button" class="close" data-dismiss="alert"
aria-hidden="true">
×
</button>
员工信息添加成功!
</div>
```
该标签中使用`th:if`语句对model对象`add`进行判断,值为true时才对该div标签进行渲染,页面效果如下

controller全部代码
```java
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao; //注入Dao
@Autowired
DepartmentDao departmentDao;
@GetMapping("/emps/info")
public String showInfo(Model model){
Collection<Employee> empsInfo = employeeDao.getEmpsInfo();
model.addAttribute("empsInfo", empsInfo);
return "employee/info";
}
@GetMapping("/emps/add")
public String addEmp(Model model){
Collection<Department> departments = departmentDao.getDepartments(); //传递部门信息到前端
model.addAttribute("departments", departments);
return "employee/add";
}
@PostMapping("/emps/add")
public String addEmpPost(Employee employee, RedirectAttributes model){
employeeDao.save(employee);
model.addFlashAttribute("add",true);
return "redirect:/emps/info";
}
}
```
## 七、删除员工信息
### 0x01 实现思路
> 1. 用户点击删除按钮,触发模态框再次确认
> 2. 点击删除,发送删除请求,请求路径中包含员工ID值
### 0x02 实现过程
确认框
```html
<!-- 模态框(Modal) -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="myModalLabel">确定要删除该员工的信息吗?</h4>
</div>
<div class="modal-body">
<label>员工ID</label>
<input th:value="${emp.id}" disabled="true" class="form-control" type="text" name="" >
<label style="margin-top: 10px">员工姓名</label>
<input th:value="${emp.lastName}" disabled="true" class="form-control" type="text" name="">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
<a class="btn btn-danger btn-default" th:href="@{/emps/delete/}+${emp.id}">确定删除</a>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal -->
</div>
```
点击删除按钮,触发模态框
```html
<button class="btn btn-danger btn-sm" data-target="#myModal" data-toggle="modal">删除</button>
```
控制器
```java
//删除员工信息
@GetMapping("/emps/delete/{id}")
public String toDelete(@PathVariable("id") Integer id, RedirectAttributes model) {
String lastName = employeeDao.getEmployeeById(id).getLastName();
employeeDao.delete(id);
model.addFlashAttribute("msg", "员工 " + lastName + " 信息已被删除");
return "redirect:/emps/info";
}
```
测试删除

## 八、更新员工信息
### 0x01 实现思路
> 1. 修改编辑按钮:INFO页面在渲染时修改编辑按钮的a标签链接,链接包含该行数据的ID值
> 2. 渲染update页面:页面包含该id对应的员工信息数据
> 3. 提交update请求
### 0x02 实现过程
#### 修改编辑按钮
使用`th:herf`渲染链接,并携带当前行的员工的id号
```html
<a class="btn btn-primary btn-sm" th:href="@{/emps/update/}+${emp.id}">编辑</a>
```
#### 渲染update页面
控制器代码如下
```java
@GetMapping("/emps/update/{id}")
public String updateEmpInfo(@PathVariable("id") Integer id, Model model){
Employee employeeById = employeeDao.getEmployeeById(id);
Collection<Department> departments = departmentDao.getDepartments(); //获取所有部门信息
System.out.println(employeeById);
model.addAttribute("emp", employeeById);
model.addAttribute("deps", departments);
return "employee/update";
}
```
使用`{}`在请求路径中取id值,通过`@PathVariable` 将id值赋值给变量
根据员工id获取该员工的所有信息,获取所有部门的信息,并渲染至页面中;
update.html页面主体代码如下
```html
<!--更新员工表单-->
<form th:action="@{/emps/update/} + ${emp.id}" th:method="POST">
<div class="form-group">
<label>LastName</label>
<input th:value="${emp.lastName}" name="lastName" type="text" class="form-control" placeholder="b5ck">
</div>
<div class="form-group">
<label>Email</label>
<input th:value="${emp.email}" name="email" type="email" class="form-control" placeholder="123@qq.com">
</div>
<div class="form-group">
<label>Gender</label><br/>
<div class="form-check form-check-inline">
<input th:checked="${emp.gender == 1}" class="form-check-input " type="radio" name="gender" value="1" checked="true">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<!-- 自动选中lable -->
<input th:checked="${emp.gender == 0}" class="form-check-input" type="radio" name="gender" value="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:each="dep:${deps}" th:selected="${emp.department.id == dep.getId()}" th:value="${dep.getId()}" th:text="${dep.getDepartmentName()}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input th:value="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm:ss')}" NAME="birth" type="text" class="form-control date-cell" placeholder="2020-01-01">
</div>
<button type="submit" class="btn btn-primary">更新</button>
</form>
```
前端代码与add.html页面相似,不同的是表单中的值使用`th:value`从控制器中传递的model取出赋值搭到对应的位置供用户修改;
性别lable中使用`th:checked="${emp.gender == 1}"`对`gender`值进行判断,使得lable标签能自动选中,而部门`select`下拉框使用`th:selected`对当前emp对象的部门ID值与dep值相比较,使得`option`标签能被自动选择。
实现效果

定义一个用于接收修改请求的控制器
```java
@PostMapping("/emps/update/{id}")
public String toUpdateEmpInfo(@PathVariable("id") Integer id, Employee employee, RedirectAttributes model){
//由于前端传递过来的只有部门ID号,需要重新将部门名称赋值
employee.setDepartment(departmentDao.getDepartmentsById(employee.getDepartment().getId()));
employeeDao.updateInfo(id, employee);
model.addFlashAttribute("msg", employee.getLastName() +" 信息更新成功");
return "redirect:/emps/info";
}
```
同样的从URL中取ID值,调用`employeeDao`中的`updateInfo`方法进行员工信息的更新。
修改用户`AA`的邮箱为123@qq.com,部门为后勤部,点击更新。

更新成功!
## 九、注销
### 0x01 实现思路
> 1. 定义控制器,获取用户session并清空
> 2. 在模板页面内添加一个注销按钮
> 3. 重定向页面至index
### 0x02 实现过程
添加控制器
> Controller/LoginController.java
```java
@RequestMapping("/user/logout")
public String Logout(HttpSession session){
session.invalidate(); //清除该session会话信息
return "redirect:/index";
}
```
在模板页面 `dashboard_base.html` 中添加注销按钮样式,并提交请求

```html
<a th:href="@{/user/logout}" class="btn btn-warning">Sign out</a>
```
## 十、错误页面配置
在`templates`目录下创建一个`error`目录,创建一个`404.html`页面,当请求发生404状态时,springboot会自动重定向到`404.html`页面中

测试404
其他错误状态也是如此,例如`500.html`、`403.html`

Spring Boot 实现员工信息管理demo