templates
文件夹static
文件夹public class Department {
private Integer id;
private String departmentName;
public Department() {}
public Department(Integer id, String departmentName) {
this.id = id;
this.departmentName = departmentName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
@Override
public String toString() {
return "Department{" +
"id=" + id +
", departmentName='" + departmentName + '\'' +
'}';
}
}
import java.util.Date;
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender; //0女 1男
private Department department;
private Date birth;
public Employee() {
}
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();
}
...
}
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);
}
}
public class EmployeeDao {
//模拟数据库中的数据
private static Map<Integer, Employee> employees = null;
//员工所属部门
private DepartmentDao departmentDao;
static {
//创建一个员工表
employees = new HashMap<Integer, Employee>();
employees.put(1001, new Employee(1001, "zzz", "[email protected]", 1, new Department(101, "后勤部")));
employees.put(1002, new Employee(1002, "mmm", "[email protected]", 0, new Department(102, "市场部")));
employees.put(1003, new Employee(1003, "ttt", "[email protected]", 1, new Department(103, "教研部")));
employees.put(1004, new Employee(1004, "aaa", "[email protected]", 0, new Department(101, "后勤部")));
employees.put(1005, new Employee(1005, "bbb", "[email protected]", 1, new Department(102, "市场部")));
employees.put(1006, new Employee(1006, "ccc", "[email protected]", 0, new Department(101, "后勤部")));
}
//增加一个员工,主键自增
private static Integer initId = 1007;
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 getEmployeeById(Integer id){
return employees.get(id);
}
//删除员工
public void delete(Integer id){
employees.remove(id);
}
}
themeleaf
的依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.4version>
<relativePath/>
parent>
<groupId>com.zmtgroupId>
<artifactId>springboot-03-web02artifactId>
<version>0.0.1-SNAPSHOTversion>
<name>springboot-03-web02name>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
IndexController
,不建议使用@Controller
public class IndexController {
@RequestMapping({"/","/index.html"})
public String index(){
return "index";
}
}
config
,新建MyMvcConfig
类,重写addViewControllers()
方法@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<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">
<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>
properties
的编码问题在resources
文件夹下新建一个i18n
文件夹,用于存放国际化配置文件
i18n:internationalization
,从开头字母i
到结尾字母n
中间有18个字母
新建一个login.properties
文件和login_zh_CN.properties
。发现IDEA自动世界了我们要做国际化的操作,文件夹发生了变化,自动生成了一个文件夹
Resource Bundle
,点击可以进行可视化配置,即配置该登录页面所有方式的多语言模式login.properties
文件中出现login.tip=请登录
login.properties
,其中为打开页面的默认值login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
login_en_US.properties
login.btn=Sign in
login.password=password
login.remember=remember me
login.tip=please sign in
login.username=username
login_zh_CN.properties
login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
查看Spring Boot源码中对国际化的自动配置,查看类MessageSourceAutoConfiguration
其中的messageSource()
方法,获取了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;
}
application.properties
文件中配置对应的message路径spring.messages.basename=i18n.login
message
取值操作,修改为#{...}
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign inh1>
WebMvcAutoConfig
类,找到localeResolver()
方法public LocaleResolver localeResolver() {
if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.webProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.webProperties.getLocale());
return localeResolver;
}
AcceptHeaderLocaleResolver
类中,其中有一个resolveLocale()
方法@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = getDefaultLocale();
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
}
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = getSupportedLocales();
if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
return requestLocale;
}
Locale supportedLocale = findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
}
return (defaultLocale != null ? defaultLocale : requestLocale);
}
<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')}">Englisha>
MyLocaleResolver
组件类public class MyLocaleResolver 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) {
}
}
MyMvcConfig
中添加bean,使这个组件生效//把自定义的国际化组件放进来
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
@thymeleaf
接管,url:@{}LocaleResolver
@Bean
LoginController
登录验证@Controller
public class LoginController {
@RequestMapping("/user/login")
@ResponseBody
public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model){
//具体业务(判断用户名是否为空等)
//用户名不为空 且 密码为123456
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
return "dashboard";
}else{
//告诉用户你登录失败了
model.addAttribute("msg","用户名或密码错误");
return "index";
}
}
}
<form class="form-signin" th:action="@{/user/login}">
......
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}">p>
<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="">
......
form>
此时,页面并不友好,在地址栏可以看到你的账号和密码,解决密码泄露问题
在MyMvcConfig
类中,添加main
映射
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
LoginController
的跳转页面(redirect跳转)@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username, @RequestParam("password") String password,
Model model){
//具体业务(判断用户名是否为空等)
//用户名不为空 且 密码为123456
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
return "redirect:/main.html";
}else{
//告诉用户你登录失败了
model.addAttribute("msg","用户名或密码错误");
return "index";
}
}
}
localhost:8080/main.html
就可以直接进入页面了,需要使用拦截器阻止直接访问LoginController
中添加一个session
进行判断@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.setAttribute("loginUser",username);
return "redirect:/main.html";
}else{
//告诉用户你登录失败了
model.addAttribute("msg","用户名或密码错误");
return "index";
}
}
}
config
文件夹下新建一个LoginHandlerInterceptor
拦截器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;
}
}
}
MyMvcConfig
中重写拦截器方法,记得过滤静态资源,否则渲染效果会消失@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login","/css/**","/js/**","/img/**");
}
dashboard.html
页面中修改登录信息为[[ ${session.loginUser} ]]
,这样登录后会显示用户名<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[ ${session.loginUser} ]]a>
前端页面中,有一部分是多个页面公共使用的,可以抽取公共代码方便调用,在list.html
和dashboard.html
中抽取公共部分代码
发现页面公共部分为顶部导航栏和侧边栏
<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>
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
...
nav>
templates
目录下创建commons
目录,在该目录下创建commons.html
用于存放公共页面代码
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<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>
html>
dashboard.html
和list.html
可以直接调用公共部分代码显示页面
<div th:replace="~{commons/commons::topbar}">div>
<div th:replace="~{commons/commons::sidebar}">div>
dashboard.html
和list.html
页面中侧边栏传参
<div th:replace="~{commons/commons::sidebar(active='list.html')}">div>
commons.html
中接收参数并判断
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar(active)">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/main.html}">
.............
首页 <span class="sr-only">(current)span>
a>
li>
.............
<li class="nav-item">
<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/employees}">
.............
员工管理
a>
li>
.............
ul>
.............
div>
nav>
html>
EmployController
员工管理后台@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";
}
}
注意:如果employeeDao
爆红,去EmployeeDao
类中,加上注解@Repository
修改前端公共页面页面中的链接@{/emps
}`
<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">
list.html
添加,自己的数据循环
<div th:replace="~{commons/commons::sidebar(active='list.html')}">div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2>Section titleh2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>idth>
<th>lastNameth>
<th>emailth>
<th>genderth>
<th>departmentth>
<th>birthth>
<th>操作th>
tr>
thead>
<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>
<td>
<button class="btn btn-sm btn-primary">编辑button>
<button class="btn btn-sm btn-danger">删除button>
td>
tr>
tbody>
table>
div>
main>
list.html
页面添加提交按钮<h2><a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工a>h2>
点击添加按钮,跳转到添加信息页面
add.html
页面编写,其余部分代码和list.html
相同,只需修改main
标签中的代码
<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>LastNamelabel>
<input type="text" name="lastName" class="form-control" placeholder="姓名">
div>
<div class="form-group">
<label>Emaillabel>
<input type="email" name="email" class="form-control" placeholder="[email protected]">
div>
<div class="form-group">
<label>Genderlabel><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>
<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 class="form-group">
<label>departmentlabel>
<select class="form-control" name="department.id">
<option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}">1option>
select>
div>
<div class="form-group">
<label>Birthlabel>
<input type="text" name="birth" class="form-control" placeholder="2020/07/25 18:00:00">
div>
<button type="submit" class="btn btn-primary">添加button>
form>
main>
EmployeeController
中编写跳转到添加页的toAddPage()
方法@GetMapping("/emp")
public String toAddPage(Model model){
//查出所有部门的信息
Collection<Department> department = departmentDao.getDepartments();
model.addAttribute("departments",department);
return "emp/add";
}
EmployeeController
中编写addEmp()
方法//添加员工
@PostMapping("/emp")
public String addEmp(Employee employee){
//添加的操作
employeeDao.save(employee);
return "redirect:/emps";
}
list.html
中添加编辑按钮<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.getId()}">编辑a>
update.html
编写,只需修改main
标签中的代码,其他和list.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>LastNamelabel>
<input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="海绵宝宝">
div>
<div class="form-group">
<label>Emaillabel>
<input th:value="${emp.getEmail()}" type="email" name="email" class="form-control" placeholder="[email protected]">
div>
<div class="form-group">
<label>Genderlabel><br>
<div class="form-check form-check-inline">
<input th:checked="${emp.getGender()==1}" class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男label>
div>
div>
<div class="form-check form-check-inline">
<input th:checked="${emp.getGender()==0}" class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女label>
div>
<div class="form-group">
<label>departmentlabel>
<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>Birthlabel>
<input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}" type="text" name="birth" class="form-control" placeholder="2020-07-25 00:00:00">
div>
<button type="submit" class="btn btn-primary">修改button>
form>
main>
update.html
页面,EmployeeController
中编写toUpdateEmp()
方法//跳转到修改页面
@GetMapping("/emp/{id}")
public String toUpdateEmp(@PathVariable("id") Integer id, Model model){
//查出修改前数据
Employee employee = employeeDao.getEmployeeById(id);
model.addAttribute("emp",employee);
//查出所有部门的信息
Collection<Department> department = departmentDao.getDepartments();
model.addAttribute("departments",department);
return "emp/update";
}
EmployeeController
中编写updateEmp()
方法//修改方法
@RequestMapping("/updateEmp")
public String updateEmp(Employee employee){
//把修改后的信息再次保存
employeeDao.save(employee);
return "redirect:/emps";
}
list.html
中添加删除按钮<a class="btn btn-sm btn-danger" th:href="@{/deleteEmp/}+${emp.getId()}">删除a>
EmployeeController
中编写deleteEmp()
方法//删除方法
@GetMapping("/delemp/{id}")
public String deleteEmp(@PathVariable("id")int id){
employeeDao.delete(id);
return "redirect:/emps";
}
将404.html
页面放入到templates
目录中的error
目录中
目录结构
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";
}