1、准备工作
1)前端页面
- 将html页面放入templates目录
- 将css,js,img放入到static目录
2)实体类
- 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;
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();
}
}
3)Dao类
模拟数据库
- 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,"后勤部"));
}
/**
* 获取所有部门
* @return
*/
public Collection<Department> getAllDepartment(){
return departments.values();
}
/**
* 根据id得到部门
* @param id
* @return
*/
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,"AA","A123456@qq.com",1,new Department(101,"教学部")));
employees.put(1002,new Employee(1002,"BB","B123456@qq.com",0,new Department(102,"市场部")));
employees.put(1003,new Employee(1003,"CC","C123456@qq.com",1,new Department(103,"教研部")));
employees.put(1004,new Employee(1004,"DD","D123456@qq.com",0,new Department(104,"运营部")));
employees.put(1005,new Employee(1005,"EE","E123456@qq.com",1,new Department(105,"后勤部")));
}
//主键自增
private static Integer ininId = 1006;
/**
* 增加一个员工
*/
public void save(Employee employee) {
if (employee.getId() == null) {
employee.setId(ininId++);
}
employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
employees.put(employee.getId(),employee);
}
/**
* 查询全部员工信息
* @return
*/
public Collection<Employee> getAll() {
return employees.values();
}
/**
* 通过id查询员工
* @param id
* @return
*/
public Employee getEmployeeById(Integer id) {
return employees.get(id);
}
/**
* 删除员工通过id
* @param id
*/
public void delete(Integer id) {
employees.remove(id);
}
}
4)目录结构
2、首页实现
需要引入模板引擎
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
1)方式一
Controller(不建议使用)
@Controller
public class IndexController {
@RequestMapping({"/","/index.htm","/index.html"})
public String index(){
return "index";
}
}
2)方式二
自定义配置类
addViewControllers()
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.htm").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
3)加载静态资源
- 导入thymeleaf包
<html lang="en" xmlns:th="http://www.thymeleaf.org">
- 将所有页面的静态资源使用thymeleaf接管
<!-- css的导入 -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/css/signin.css}" rel="stylesheet">
<!-- 图片的导入 -->
<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<!-- js导入 -->
<script type="text/javascript" th:src="@{/js/jquery-3.2.1.slim.min.js}"></script>
<script type="text/javascript" th:src="@{/js/popper.min.js}"></script>
<script type="text/javascript" th:src="@{/js/bootstrap.min.js}"></script>
<script type="text/javascript" th:src="@{/js/feather.min.js}"></script>
<script type="text/javascript" th:src="@{/js/Chart.min.js}"></script>
3、页面国际化
1)准备工作
在IDEA中统一设置properties的编码问题!UTF-8
2)编写配置文件
- 在resources资源文件下新建一个i18n目录,存放国际化配置文件
- 建立一个login.properties文件,还有一个login_zh_CN.properties;IDEA会自动识别国际化操作;文件夹变了!统一在资源包下
3)文件配置生效探究
MessageSourceAutoConfiguration 自动配置类
@Configuration
// ...
public class MessageSourceAutoConfiguration {
@Bean
@ConfigurationProperties(
prefix = "spring.messages"
)
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
// 获取 properties 传递过来的值进行判断
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
// 设置国际化文件的基础名(去掉语言国家代码)
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
// ...
return messageSource;
}
// ...
protected static class ResourceBundleCondition extends SpringBootCondition {
// ...
public ConditionOutcome getMatchOutcome(...) {
String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
// ...
return outcome;
}
}
}
配置messages基础路径
spring:
# 国际化:省略 _en_US、_zh_CN
messages:
basename: i18n.login
4)配置页面国际化值
message取值操作为:
#{...}
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required="">
<input type="checkbox" value="remember-me" th:text="#{login.remember}">
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
5)配置国际化解析
根据按钮自动切换中文英文!
在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器!
@Bean
@ConditionalOnMissingBean(
name = {"localeResolver"}
)
public LocaleResolver localeResolver() {
if (this.webProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.WebProperties.LocaleResolver.FIXED) {
// 容器中没有就自己配,有的话就用用户配置的
return new FixedLocaleResolver(this.webProperties.getLocale());
} else {
// 接收头国际化分解
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.webProperties.getLocale());
return localeResolver;
}
}
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = this.getDefaultLocale();
// 默认的就是根据请求头带来的区域信息获取Locale进行国际化
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
} else {
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = this.getSupportedLocales();
if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
} else {
return defaultLocale != null ? defaultLocale : requestLocale;
}
} else {
return requestLocale;
}
}
}
- 自定义 LocaleResolver,在链接上携带区域信息!
前端html
<!-- 这里传入参数不需要使用 ?使用 (key=value)-->
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
MyLocaleResolver
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
// 获取请求参数
String lang = request.getParameter("l");
// 如果请求中没有区域值,就用默认的
Locale locale = Locale.getDefault();
if(StringUtils.hasText(lang)){
// 分割请求参数
String[] split = lang.split("_");
// 语言、地区
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
注册 MyLocaleResolver
在 MyMvcConfig 中添加 @Bean 方法
方法名必须为:localeResolver() ,不要乱写
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
// ...
// 注册bean
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
4、登录页面
1)表单提交地址
th:action="@{/user/login}"
<form class="form-signin" th:action="@{/user/login}">
2)LoginController
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model){
if(StringUtils.hasText(username) && "123456".equals(password)){
// 登录成功
return "dashboard";
}
model.addAttribute("msg", "用户名或密码错误");
return "index";
}
}
3)错误提示
登录页显示错误提示:
th:text="${msg}"
有消息时才提示:
th:if="${not #strings.isEmpty(msg)}"
<p style="color:red;" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
4)地址栏泄露密码
解决方案
(1)提交方式改为:post
<form class="form-signin" th:action="@{/user/login}" method="post">
(2)映射跳转
登录成功后
redirect
到/main.html
,再由main.html
映射到后台页面dashboard.html
main.html
形式上是页面,实际上是作为中间跳转的一个桥梁(请求),在地址栏展示给用户,替代原来的用户名密码信息,并非一个真正页面
- 登录成功后重定向到
main.html
修改 LoginController 的 login() 方法
// 登录成功
// return "dashboard";
return "redirect:/main.html";
- 由
main.html
到dashboard.html
方式一:在 LoginController 中添加 main() 方法
@RequestMapping("/main.html")
public String main(){
return "dashboard";
}
方式二:在 MyMvcConfig 中添加
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// ...
registry.addViewController("/main.html").setViewName("dashboard");
}
}
5、登录拦截器
1)Controller 中添加 Session
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model,
HttpSession session){
if(StringUtils.hasText(username) && "123456".equals(password)){
// 登录成功
session.setAttribute("loginUser", username);
return "redirect:/main.html";
}
model.addAttribute("msg", "用户名或密码错误");
return "index";
}
2)自定义登录拦截器
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 检查session
String loginUser = (String) request.getSession().getAttribute("loginUser");
if(!StringUtils.hasText(loginUser)){
// 无权限
request.setAttribute("msg", "没有权限,请登录");
// 请求转发到登录页
request.getRequestDispatcher("/index.html").forward(request, response);
// 拦截
return false;
}
return true;
}
}
3)添加拦截器
在 MyMvcConfig 中添加
addInterceptors()
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
/**
* 添加拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/index.html","/index.htm","/","/user/login","/css/**","/js/**","/img/**");
}
}
addPathPatterns()
拦截路径excludePathPatterns()
排除路径;静态资源的过滤,否则页面渲染效果会消失
4)前端显示登录用户
[[ ${session.loginUser} ]]
<!--顶部导航栏-->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[ ${session.loginUser} ]]</a>
<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" th:href="@{/user/logout}">注销</a>
</li>
</ul>
</nav>
6、员工列表展示
1)EmployeeController
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
@RequestMapping("/emps")
public String list(Model model){
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("emps",employees);
return "emp/list";
}
}
2)修改侧边栏链接
侧边栏员工管理链接
th:href="@{/emps}"
<li class="nav-item">
<a th:class="nav-lin" th:href="@{/emps}">
<svg ...>
...
</svg>
员工管理
</a>
</li>
3)提取公共页面
-
把 顶部导航栏 和 侧边栏 提取出来
在
templates
目录下面创建commons
目录,在commons
目录下面创建commons.html
放公共代码,以供其它页面调用th:fragment="topbar"
指定供其它页面调用的键
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<!--顶部导航栏-->
<nav class="..." th:fragment="topbar">
...
</nav>
<!--侧边栏-->
<nav class="..." th:fragment="sidebar">
...
</nav>
</html>
-
调用公共页面
th:insert="~{commons/commons::topbar}"
th:replace="~{commons/commons::sidebar}"
insert
和replace
效果一样,一个插入,一个是替换;insert
会在外面多加一层div
<!--顶部导航栏-->
<div th:insert="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<!--侧边栏-->
<div th:replace="~{commons/commons::sidebar}"></div>
...
</div>
</div>
4)侧边活动页高亮
-
引用页面传参
list.html
(active='list.html')
<!--侧边栏--> <div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>
dashboard.html
(active='main.html')
<!--侧边栏--> <!--传递参数给组件--> <div th:replace="~{commons/commons::sidebar(active='main.html')}"></div>
-
commons.html中接收参数并判断
<li class="nav-item"> <a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/main.html}"> ... 首页 </a> </li> <li class="nav-item"> <a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}"> ... 员工管理 </a> </li>
th:class="${active=='main.html'?'nav-link active':'nav-link'}"
三元运算符
如果接收到的
active
等于main.html
,那么class
的值就为nav-link active
,否则为nav-link
5)员工信息循环展示
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.getId()}"></td>
<td th:text="${emp.getLastName()}"></td>
<td th:text="${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>
</tr>
</tbody>
th:each="emp:${emps}"
遍历集合emps
,emp
为当前元素
7、添加员工
1)添加按钮
位于 list.html 页面的员工列表上面
<h2><a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a></h2>
2)跳转到添加页
EmployeeController
只接收 get 请求
@GetMapping
@GetMapping("/emp")
public String toAddPage(Model model){
// 部门信息传给添加页面
Collection<Department> departments = departmentDao.getAllDepartment();
model.addAttribute("departments",departments);
return "emp/add";
}
3)添加页
add.html
post 方式提交
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{/emp}" method="post">
<div class="form-group">
<label>LastName</label>
<input type="text" name="lastName" class="form-control" placeholder="姓名">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" class="form-control" placeholder="xxx@xxx.xxx">
</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">
<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>
<!-- 提交的是id,所以name为department.id,提交后可以自动绑定 -->
<select class="form-control" name="department.id">
<!-- 遍历部门信息:显示名称,值为id -->
<option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}">1</option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input type="text" name="birth" class="form-control" placeholder="2020-07-25">
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
</main>
注意:下拉框提交的时候应提交一个属性,因为其在controller接收的是一个Employee,否则不能自动绑定,会报错
4)添加员工
只接收 post 请求
参数为 employee 对象;要求提交表单的每一项name值与对象的字段对应
@PostMapping("/emp")
public String add(Employee employee){
// 添加员工
employeeDao.save(employee);
// 跳转到员工列表页
return "redirect:/emps";
}
5)自定义日期格式
系统默认日期格式
/**
* Date format to use, for example 'dd/MM/yyyy'.
*/
private String date;
/**
* Time format to use, for example 'HH:mm:ss'.
*/
private String time;
/**
* Date-time format to use, for example 'yyyy-MM-dd HH:mm:ss'.
*/
private String dateTime;
- 自定义格式
spring:
# 日期格式
mvc:
format:
date: yyyy-MM-dd
8、修改员工
1)修改按钮
<a class="btn btn-sm btn-primary" th:href="@{/update/}+${emp.getId()}">编辑</a>
@{/update/}+${emp.getId()}
=>/update/1003
Restful风格
2)跳转到修改页
@GetMapping("update/{id}")
public String toUpdate(
@PathVariable("id") Integer id,Model model){
// 员工信息
Employee employee = employeeDao.getEmployeeById(id);
model.addAttribute("emp", employee);
// 部门信息
Collection<Department> departments = departmentDao.getAllDepartment();
model.addAttribute("departments",departments);
return "emp/update";
}
3)修改页
update.html
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<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" name="lastName" class="form-control" placeholder="姓名" th:value="${emp.getLastName()}">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" class="form-control" placeholder="xxx@xxx.xxx" th:value="${emp.getEmail()}">
</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.getGender()==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.getGender()==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()}" th:selected="${dept.getId()==emp.getDepartment().getId()}">1</option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input type="text" name="birth" class="form-control" placeholder="2020-07-25" th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}">
</div>
<button type="submit" class="btn btn-primary">修改</button>
</form>
</main>
-
<input type="hidden" name="id" th:value="${emp.getId()}">
隐藏域(员工id) -
th:checked="${emp.getGender()==1}"
当员工性别值为1时checked
有效th:selected="${dept.getId()==emp.getDepartment().getId()}"
4)修改员工
@PostMapping("updateEmp")
public String update(Employee employee){
employeeDao.save(employee);
return "redirect:/emps";
}
9、删除员工
1)删除按钮
<a class="btn btn-sm btn-danger" th:href="@{/del/}+${emp.getId()}">删除</a>
2)删除员工
@GetMapping("/del/{id}")
public String del(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/emps";
}
10、404页
将
404.html
页面放入到templates
目录下面的error
目录中其它页面类似;如:500等
11、注销
把登录时添加的session值设为null
- 链接
<a class="nav-link" th:href="@{/user/logout}">注销</a>
- Controller
@RequestMapping("/user/logout")
public String logout(HttpSession session){
session.setAttribute("loginUser",null);
return "index";
}