Java-SpringBoot:员工管理系统

Java:SpringBoot-员工管理系统

参考的代码和资源链接:https://blog.csdn.net/qq_45173404/article/details/108934414?spm=1001.2014.3001.5501
学习视频:B站 狂神说Java – https://www.bilibili.com/video/BV1PE411i7CV

学习文档: 微信公众号 狂神说 –https://mp.weixin.qq.com/mp/homepage?__biz=Mzg2NTAzMTExNg==&hid=1&sn=3247dca1433a891523d9e4176c90c499&scene=18&uin=&key=&devicetype=Windows+10+x64&version=63020170&lang=zh_CN&ascene=7&fontgear=2

SpringBoot回顾

SpringBoot其实就是去整合了Spring、spring mvc框架,整合了里面的配置信息,“约定大于配置”。

  • xxxAutoConfiguration 完成自动配置文件,给容器中导入非常多的自动配置类。即给容器中导入配置好的组件 bean信息。

    使用时可以直接拿来使用,开箱即用。【不用再向原来mybatis、spring中那样写xml配置文件了】

  • 内嵌了服务器,比如Tomcat、Jetty。简化了web项目开发,不用再自己启动配置tomcat

  • 单体应用架构转化为微服务架构风格。

最重要的就是 SpringBoot自动装配原理了。

@SpringBootApplication
public class Springboot03WebApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot03WebApplication.class, args);
    }
}

开启SpringBoot应用的注解是 @SpringBootApplication,它里面包括:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
......
}
  • @ComponentScan 的作用是指定包加入Spring IOC的包扫描,自动扫描并加载符合条件的bean 或组件。将bean定义加载到 IOC容器中。
  • @SpringBootConfiguration 的作用是 表明这个类是springboot的配置类,里面保存的是配置信息,对应于spring中的xml配置文件
  • @EnableAutoConfiguration :开启自动配置功能。原来我们需要自己去添加配置的东西,在SpringBoot中可以直接自动地帮我们配置。

配置文件:

  • 自己定义 *.yaml 或者 *.properties 配置文件。然后可以去修改SpringBoot中默认的配置信息。

    yaml语法:key: 空格 value

    server:
      port: 8081
    #选择要激活那个环境块
    spring:
      profiles:
        active: prod
        
    ---
    server:
      port: 8082
    spring:
      profiles: dev # 设置环境的名称
      
    ---
    server:
      port: 8084
    spring:
      profiles: prod # 设置环境的名称
      
    person:
      name: alzn
      age: 18
      happy: true
      birth: 2002/01/01
      maps: {al: last name, zn: first name}
      lists:
        - learning
        - running
        - dancing
      dog:
        name: xiaohu
        age: 6
    
    • @ConfigurationProperties(加载配置文件的注解)对属性赋值
    • **@PropertySource :**加载指定的配置文件
  • 利用注解开发自定义配置文件,

    • 如mvc框架中的视图解析器:ViewResolver
    • 将自定义的配置文件信息 注入到Bean中,由SpringBoot帮我们去装配,组合所有的配置信息
    // 如果你想要 div 一些定制的功能,只要写这个组件,然后把它交给springboot,springboot就会帮我们装配
    // 扩展 springmvc  围绕着核心 dispatchservlet
    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
    
        // ViewResolver 实现了视图解析器接口的类。 我们就可以把这个当作视图解析器
        @Bean //放到bean中。通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
        public ViewResolver myViewResolver(){
            return new MyViewResolver();
        }
    
        //我们写一个静态内部类,视图解析器就需要实现ViewResolver接口
        private static class MyViewResolver implements ViewResolver{
            @Override
            public View resolveViewName(String s, Locale locale) throws Exception {
                return null;
            }
        }
    }
    

thymeleaf模板引擎:同样也是为了简化web开发,直接继承 thymeleaf模板引擎。

模板引擎的作用就是帮助我们写了一个页面模板,比如有些值是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。

在使用SpringBoot进行开发的时候,自己要解决的、参与的工作是:

  • 导入静态资源。配置资源文件:*.properties、 *.yaml
  • jsp、thymeleaf模板引擎。便于简化开发,动态的资源数据信息,代码去提供。thymeleaf模板引擎提供一个固定的页面
  • 装配和扩展 SpringMVC。自己定义的关于 WebMvcConfigurer 的mvcConfig配置信息;SpringBoot中包含的默认mvc配置信息。

@EnableWebMvc // 这个就是导入了一个类: DelegatingWebMvcConfiguration: 从容器中获取所有的 webmvcConfig

@EnableWebMvc 将 WebMvcConfigurationSupport 组件 导入进来了;而导入的 WebMvcConfigurationSupport 只是SpringMVC最基本的功能!此时我们全面接管SpringMVC。

SpringBoot:员工管理系统

SpringBoot web开发

jar:webapp

自动装配:

  1. 创建应用,选择模块

Springboot到底帮我们配置了什么? 我们能不能进行修改? 能修改哪些东西?能不能扩展?

  • xxxxAutoConfigurartion:自动配置类;给容器中添加组件

  • xxxxProperties: 封装配置文件中相关属性

其实SpringBoot的东西用起来非常简单,因为SpringBoot最大的特点就是自动装配。

使用SpringBoot的步骤:

1、创建一个SpringBoot应用,选择我们需要的模块,SpringBoot就会默认将我们的需要的模块自动配置好

2、手动在配置文件中配置部分配置项目就可以运行起来了

3、专注编写业务代码,不需要考虑以前那样一大堆的配置了。

我们在利用 Springboot的时候,能够利用和修改哪些配置,以及增加配置文件呢? 如下所示,我们最终是要通过这两个去进行 环境配置:

  • 向容器中自动配置组件 :xxxAutoconfiguration
  • 自动配置类,封装配置文件的内容:xxxProperties

我们在进行 springboot web 开发要解决的问题

  • 导入静态资源
  • 首页
  • jsp、模板引擎Thymeleaf
  • 装配扩展 SpringMVC
  • 增删查改
  • 拦截器
  • 国际化

参考的代码和资源链接:https://blog.csdn.net/qq_45173404/article/details/108934414?spm=1001.2014.3001.5501

1、环境搭建

新建一个SpringBoot项目:springboot-03-system。

image-20220421114225151

image-20220421114247795

选择pom依赖文件:SpringWeb 和Thymeleaf模板引擎

image-20220421114420737

导入静态资源

创建不存在的静态资源目录 public 和 resources,便于添加我们想要自定义的静态资源。

SpringBoot中的静态资源导入:【SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面】

  • 两种映射规则:“classpath:/META-INF/resources/”、“classpath:/resources/”、“classpath:/static/”、“classpath:/public/”
  • 自定义静态资源路径

所有的 /webjars/** , 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源
image-20220421161302785

静态资源文件:

image-20220421162232512

image-20220421162329494

把静态资源文件导入到对应文件夹中:

  • 将 html 静态资源放置 templates 目录下
  • 将 asserts 目录下的 css、img、js 等静态资源放置 static 目录下

image-20220421162456166

数据库的创建

创建一个伪数据库,进行数据库的模拟。

导入lombok,便于使用注解开发:

<dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
</dependency>

实体类:pojo

创建实体类:Department 部门表

package com.al.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

// 部门表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
    private Integer id;
    private String departmentName;
}

员工表:

  • 设置一对多和多对一。员工和部门的关系。
  • 性别没有使用char类型,使用Integr
package com.al.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

// 员工表
@Data
@NoArgsConstructor
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(Integer id, String lastName, String email, Integer gender, Department department, Date birth) {
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
        this.department = department;
        // 默认的创建日期
        //this.birth = birth;
        this.birth = new Date();
    }
}

dao层

创建dao层:dao层,负责底层数据的操作。

Bean的注解开发:

  • @Component 通常是通过类路径扫描来⾃动侦测以及⾃动装配到Spring容器中。@Component 表明会将这个类交给 Spring管理装配,它与@Bean的不同之处在于,它作用在类上。注册一个类的实例到Spring IOC 容器。需要的时候,从里面去获取。

  • 为了更好的进行分层,在 web 开发中,会按照 mvc 三层架构分层。Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

    • @Controller:web层
    • @Service:service层
    • @Repository:dao层

Bean的自动装配配置

  • @Autowired: 自动装配 通过类型,名字
    • 如果@Autowired 不能唯一自动装配属性,则还需要通过 @Qualifier(value = “xxx”)
  • @Nullable: 字段标注了这个注解,表示这个字段可以为 null
  • @Resource:自动装配 通过名字, 类型。先去匹配byName,后去进行byType。

部门dao类:

DepartmentDao:将此类注入到Spring IOC容器中,dao层使用注解 @Repository。

  • 模拟数据库中的部门数据。采用map表示。

  • Map<Integer, Department> departments; // Integer代替数据库表id,Department表示数据库表中部门的具体信息。

package com.al.dao;


import com.al.pojo.Department;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

// 部门dao
@Repository
public class DepartmentDao {

    // 模拟数据库表中的数据
    private static Map<Integer, Department> departments = null;
    static {
        // 创建一个部门表
        departments = new HashMap<>();
        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, "礼部"));
        departments.put(106, new Department(106, "工部"));
    }

    // 获取所有的部门信息
    public Collection<Department> getDepartments(){
        return departments.values();
    }

    // 通过id获取部门信息
    public Department getDepartmentById(Integer id){
        return departments.get(id);
    }
}

员工dao类:

EmployeeDao类:需要去继承DepartmentDao 这个部门dao,获取这样的一个类实例,所以

  • 使用注解@Autowired 获取DapartmentDao的一个实例。

  • 模拟数据库中的员工表数据。采用map表示:private static Map<Integer, Employee> employees = null;

package com.al.dao;

import com.al.pojo.Department;
import com.al.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

// 员工Dao
@Repository
public class EmployeeDao {
    // 模拟数据库表中的员工数据信息
    private static Map<Integer, Employee> employees = null;
    // 员工所属部门
    @Autowired
    private static DepartmentDao departmentDao;

    static {
        employees = new HashMap<>();
        employees.put(1001, new Employee(1001, "AAA", "1356207897@qq.com", 1, new Department(101, "户部"), new Date()));
        employees.put(1002, new Employee(1002, "BBB", "1356207897@qq.com", 0, new Department(102, "兵部"), new Date()));
        employees.put(1003, new Employee(1003, "CCC", "1356207897@qq.com", 1, new Department(103, "吏部"), new Date()));
        employees.put(1004, new Employee(1004, "DDD", "1356207897@qq.com", 0, new Department(104, "刑部"), new Date()));
        employees.put(1005, new Employee(1005, "EEE", "1356207897@qq.com", 1, new Department(105, "礼部"), new Date()));
    }

    // 主键自增
    private static Integer initId = 1006;

    // 增加一个员工
    public void addEmployee(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);
    }

    //通过id删除员工
    public void deleteEmployeeByID(int id) {
        employees.remove(id);
    }
}

2、首页实现

首页实现:自定义配置类 MyMvcConfig

首页控制,使导入的templates中的静态资源有效。利用该自定义的静态资源,需要提供静态资源配置文件。

  • 在 templates目录下的所有页面,只能通过 controller来跳转。这个需要模板引擎的支持。如 thymeleaf。

image-20220423174217358

在控制器 controller中,进行首页控制,IndexController:

package com.al.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {
    @RequestMapping({"/", "/index.html"}) //这是一个集合
    public String index(){
        return "index";
    }
}

不过最好我们采用这种方式去进行对根目录下的访问:【采用自定义配置 资源文件的方式】

在主程序同级目录下新建 config 包用来存放自己的配置类:

  • 新建自定义配置类 MyMvcConfig,进行视图跳转
package com.al.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
}

启动浏览器测试,观察界面:界面可以访问,但是不理想

image-20220423173924576

想要直接去访问 template 文件夹路径下的静态资源,需要引入模板引擎。这里采用 SpringBoot中的 thymeleaf 。

  • 导入thymeleaf的依赖
  • 在 index中就需要导入 thymeleaf
  • 语法要使用thymeleaf语法,URL路径要用@{…}

回顾:静态资源映射规则

创建有效的静态资源路径 文件夹:而其中的classpath:/META-INF/resource/ 这个就对应着上面的那个webjars 资源路径。

"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
  • classpath对应的文件夹等级就是 resources 资源文件夹。

  • 我们创建以下的几个文件夹,都能够存放我们的静态文件:【可以直接获取该路径下的静态资源文件】

image-20210408212018536

首页实现:thymeleaf模板引擎

上述测试的时候,发现页面中的图片没有加载出来,且css和js的样式并没有显示。 这是因为 想要直接去访问 template 文件夹路径下的静态资源,需要引入模板引擎。

在SpringBoot中,推荐使用Thymeleaf作为模板引擎,我们将其中的语法改为Thymeleaf,所有页面的静态资源都需要使用模板引擎接管,里面的动态数据由后端提供。前面页面需要符合 thymeleaf语法。

Selection Variable Expressions: *{…}:选择表达式:和${}在功能上是一样;
Message Expressions: #{…}:获取国际化内容
Link URL Expressions: @{…}:定义URL;
Fragment Expressions: ~{…}:片段引用表达式

在index.html页面中:

  • 引入thymeleaf:

    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    
  • 第一个/代表项目的classpath,也就是这里的resources目录。 直接获取 “classpath:/static/” 路径下的资源文件:

    image-20220423192313207

    image-20220423192155084

  • thymeleaf语法中 定义URL的方式:@{}

index.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">
		<title>Signin Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
		<!-- Custom styles for this template -->
		<link th:href="@{/css/signin.css}" rel="stylesheet">
	</head>

	<body class="text-center">
		<form class="form-signin" action="dashboard.html">
			<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
			<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
			<label class="sr-only">Username</label>
			<input type="text" class="form-control" placeholder="Username" required="" autofocus="">
			<label class="sr-only">Password</label>
			<input type="password" class="form-control" placeholder="Password" required="">
			<div class="checkbox mb-3">
				<label>
          <input type="checkbox" value="remember-me"> Remember me
        </label>
			</div>
			<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
			<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
			<a class="btn btn-sm">中文</a>
			<a class="btn btn-sm">English</a>
		</form>

	</body>

</html>

测试访问结果:

image-20220423191354609

其它页面进行同样的修改。

配置首页的时候:可以在这里自己自行定义 目录, 此时 localhost:8080失效,必须是 localhost:8080/al

  • application.properties 配置资源文件:
server.servlet.context-path=/al

此时的测试结果:ulr请求路径必须是:http://localhost:8080/al/
image-20220423195244892

3、页面国际化

IDEA中编码设置:UTF-8

首先在 setting -> FileEncodings中 必须要确保编码为 UTF-8, 不然写的东西会乱码。

image-20220423195912693

i18n国际化资源配置文件

在静态资源中创建一个 i18n文件,国际化:

在这其中分别创建两个配置文件:

  • 命名方式是下划线的组合:文件名_语言_国家.properties;

    以此方式命名,IDEA会帮我们识别这是个国际化配置包,自动绑定在一起转换成如下的模式:

  • login.properties 和 login_zh_CN.properties, 然后IDEA会自动帮你合并:

image-20220423200427431

如果再添加其它配置文件:选择Resource Bundle ‘login’,右键点击添加即可:

image-20220423200512109

此时只需要输入区域名即可创建成功,比如输入 en_US,就会自动识别

image-20220423200612791

此时的i18n文件下的配置资源文件:

image-20220423200647641

在编写login资源配置文件时:可以选择左下角的 Resource Bundle进行可视化编写

  • 进入到可视化编辑页面后,点击加号 + ,添加属性,首先新建一个 login.tip 代表首页中的提示

image-20220423201120032

然后对这种提示,去分别做语言配置:在输入框中写入相应的语言提示

image-20220423203613511

login.properties:

login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名

login_zh_CN.properties:

login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名

login_en_US.properties:

login.btn=login
login.password=password
login.remember=remember
login.tip=Please sigin in
login.username=suaername

那么如何让我们配置的这些生效呢?

  • 在Spring程序中,国际化主要是通过ResourceBundleMessageSource这个类来实现的

  • Spring Boot通过MessageSourceAutoConfiguration为我们自动配置好了管理国际化资源文件的组件

查看源码MessageSourceAutoConfiguration

  • 寻找Meaasge, 找到它的前缀。@ConfigurationProperties(prefix = “spring.messages”)
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {

	private static final Resource[] NO_RESOURCES = {};

	@Bean
	@ConfigurationProperties(prefix = "spring.messages")
	public MessageSourceProperties messageSourceProperties() {
		return new MessageSourceProperties();
	}

	@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;
	}
    ...
}

关于 messageSource 方法中的参数:MessageSourceProperties properties。

MessageSourceProperties 类:

public class MessageSourceProperties {
	/**
	 * Comma-separated list of basenames (essentially a fully-qualified classpath
	 * location), each following the ResourceBundle convention with relaxed support for
	 * slash based locations. If it doesn't contain a package qualifier (such as
	 * "org.mypackage"), it will be resolved from the classpath root.
	 */
	private String basename = "messages";
	/**
	 * Message bundles encoding.
	 */
	private Charset encoding = StandardCharsets.UTF_8;
    ...
}

注释的意思:

* 逗号分隔的基本名称列表(本质上是完全限定的类路径 location),每个都遵循 ResourceBundle 约定,并轻松支持
*基于斜线的位置。 如果它不包含包限定符(例如"org.mypackage"),它将从类路径根目录中解析。

意思是:如果你不在springboot配置文件中指定以.分隔开的国际化资源文件名称的话,它默认会去类路径下找messages.properties作为国际化资源文件。

自定义国际化资源文件,因此我们需要在SpringBoot配置文件application.properties中加入以下配置指定我们配置文件的名称

  • 我们对配置文件的真实位置(i18n.login),修改后:
#自定义配置:国际化资源文件
spring.messages.basename=i18n.login

Message Expressions: #{…}:获取国际化内容

对于国际化 message如何写:thymeleaf 中 表示message 的语法为 #{…}

  • <label>
              <input type="checkbox" value="remember-me" th:text="#{login.remember}">
        <input type="checkbox" value="remember-me"> [[#{login.remember}]]
            </label>
    
  • 修改的地方:【对应着前端页面显示的地方】

image-20220423222322894

此时的index.html 代码:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>Signin Template for Bootstrap</title>
    <!-- Bootstrap core CSS -->
    <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
    <!-- Custom styles for this template -->
    <link th:href="@{/css/signin.css}" rel="stylesheet">
</head>

<body class="text-center">
<form class="form-signin" action="dashboard.html">
    <img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
    <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
    <label class="sr-only">Username</label>
    <input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
    <label class="sr-only">Password</label>
    <input type="password" class="form-control" th:placeholder="#{login.password}" required="">
    <div class="checkbox mb-3">
        <label>
<!--            <input type="checkbox" value="remember-me" th:text="#{login.remember}"> Remember me-->
            <input type="checkbox" value="remember-me" th:text="#{login.remember}">
        </label>
    </div>
<!--    <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>-->
    <button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.btn}]]</button>
    <p class="mt-5 mb-3 text-muted">© 2017-2018</p>
    <a class="btn btn-sm">中文</a>
    <a class="btn btn-sm">English</a>
</form>

</body>

</html>

此时的测试:首页:

image-20220423222232836

国际化组件的中英文切换

切换方式

如何通过按钮 进行中英文的转换?在index.html页面中可以看到两个标签:

    <a class="btn btn-sm">中文</a>
    <a class="btn btn-sm">English</a>

在标签中加入跳转链接,点击后进行响应跳转,获取对应的信息资源参数:

<!--这里传入参数不需要使用?使用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>

自定义地区解析组件

Spring源码中关于国际化的类有:

  • Locale:代表地区,每一个Locale对象都代表了一个特定的地理、政治和文化地区
  • LocaleResolver:地区解析器

分析:

消息语言转换的类:AcceptHeaderLocaleResolver,实现接口 LocaleResolver

image-20220424093117184

LocaleResolver:

public interface LocaleResolver {
	Locale resolveLocale(HttpServletRequest request);

	void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);

}

在WebMvcAutoConfiguration 类中寻找 LocaleResolver:

image-20220424095759400

		@Override
		@Bean
		@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
		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;
		}

经过寻找后发现只要完成了 接口实现类 LocalResolver,就能完成 地区语言转换。该方法就是获取LocaleResolver地区对象解析器:

  • 如果用户配置了则使用用户配置的地区解析器;
  • 如果用户没有配置,则使用默认的地区解析器

在消息语言转换的类 AcceptHeaderLocaleResolver中:

public class AcceptHeaderLocaleResolver implements LocaleResolver {

	private final List<Locale> supportedLocales = new ArrayList<>(4);

	@Nullable
	private Locale defaultLocale;
    ......
    @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);
	}
    ......
    @Override
	public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
		throw new UnsupportedOperationException(
				"Cannot change HTTP accept header - use a different locale resolution strategy");
	}
    
}

我们自定义一个地区解析器,继承实现LocaleResolver 接口,使我们的自定义国际化资源生效。去实现,前端界面 Index.html中点击链接传递参数,使得按钮生效,完成中英文切换。

  • 定义自己的地区配置解析器LocalResolver
  • 前端页面的请求链接参数

index.html:

<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,完成实现接口LocalResolver。

  • 按照源码中那样编写, 我们要获取 index.html中的语言链接请求,然后进行对应的地区语言转换:
package com.al.config;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

// 地区解析器:获取链接上携带的参数信息
public class MyLocaleResolver implements LocaleResolver {
    // 解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        // 获取请求中的国家化参数
        String language = request.getParameter("l");
        // 默认的地区
        Locale locale = Locale.getDefault();
        //如果请求的链接参数不为空,携带了国际化参数
        if (!StringUtils.isEmpty(language)) {
            String[] split = language.split("_");//zh_CN(语言_地区)
            locale = new Locale(split[0], split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

我们的国际化 message 写完后, 为了使地区配置解析器的信息生效。该组件需要放入到 bean中。

  • 在配置类 MvcConofig 中添加bean:

    package com.al.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.LocaleResolver;
    import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @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 MyLocaleResolver();
        }
    }
    

重启项目进行访问测试。

  • 点击中文按钮,url路径为:http://localhost:8080/index.html?l=zh_CN

  • 点击英文按钮,url路径为:http://localhost:8080/index.html?l=en_US

    image-20220424111302852

4、登录功能的实现

前端页面 index.html

在登录页面,即首页去实现登录, 实现跳转到 dashboard.html 页面。

  • 在 index.html页面, 使用 thymeleaf 语法,改写提交动作 submit 跳转路径到 /user/login
  • 给定 username 名字和password密码时 添加 name 属性,便于传参
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>Signin Template for Bootstrap</title>
    <!-- Bootstrap core CSS -->
    <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
    <!-- Custom styles for this template -->
    <link th:href="@{/css/signin.css}" rel="stylesheet">
</head>

<body class="text-center">
<!--<form class="form-signin" action="dashboard.html">-->
<form class="form-signin" th:action="@{/user/login}">
    <img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
    <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
    <label class="sr-only">Username</label>
    <input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
    <label class="sr-only">Password</label>
    <input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required="">
    <p style="color: red" th:text="${msg}"></p>
    <div class="checkbox mb-3">
        <label>
<!--            <input type="checkbox" value="remember-me" th:text="#{login.remember}"> Remember me-->
            <input type="checkbox" value="remember-me" th:text="#{login.remember}">
        </label>
    </div>
<!--    <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>-->
    <button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.btn}]]</button>
    <p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<!--    <a class="btn btn-sm">中文</a>-->
<!--    <a class="btn btn-sm">English</a>-->
    <!--这里传入参数不需要使用?使用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>

</form>

</body>

</html>

image-20220424114139205

控制器Controller

我们创建一个控制器用于 响应这个请求 servlet,完成跳转。

LoginController 控制器:

package com.al.controller;


import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class LoginController {

    @RequestMapping("/user/login")
    public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model){
        if (!StringUtils.isEmpty(username) && "123456".equals(password)){
            return "dashboard";
        }else {
            model.addAttribute("msg", "用户名或密码错误");
            return "index";
        }
    }
}

然后我们在index.html首页中加一个标签用来显示controller返回的错误信息:

<p style="color: red" th:text="${msg}"></p>

测试结果:

image-20220424115727423

在密码不对的情况下:http://localhost:8080/user/login?username=ahoh&password=123

url路径里面显示了用户的名字和密码,这暴漏了用户的信息。我们编写一个映射 去解决这个问题。

  • 在自定义的配置类MyMvcConfig 中添加映射。在url=/main.html时,页面为 登录后的页面 dashboard.html

    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
    
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("/").setViewName("index");
            registry.addViewController("/index.html").setViewName("index");
            registry.addViewController("/main.html").setViewName("dashboard");
        }
    
        // 将自定义的国际化配置放入 Spring容器中,使组件生效
        @Bean
        public LocaleResolver localeResolver(){
            return new MyLocaleResolver();
        }
    }
    
  • 对应的,我们需要在 登录控制器 LoginController中进行重定向:redirect:/main.html

    @Controller
    public class LoginController {
    
        @RequestMapping("/user/login")
        public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model){
            if (!StringUtils.isEmpty(username) && "123456".equals(password)){
                return "redirect:/main.html";
            }else {
                model.addAttribute("msg", "用户名或密码错误");
                return "index";
            }
        }
    }
    

测试:输入正确的用户名和密码登陆成功后,浏览器不再泄露信息。

但此时的结果虽然可以在登录后的后台界面进去了,但是你直接在url路径输入 /main.html 也能进去, 这就需要 拦截器了。

登录拦截器

创建一个登录拦截器 LoginHandlerInterceptor。

拦截器去完成登录拦截的功能:【用户登录后,后台会得到用户的信息;如果没有登录,则不会有任何的用户信息】。

  • 重写拦截器接口 HandlerInterceptor的方法。有用户的 session 即用户信息()才让其允许通过。
  • 在登录时的控制器 LoginController 中去加入用户的 session

LoginController控制器

  • 添加登录用户的session 信息
@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 "dashboard";
            return "redirect:/main.html"; // 重定向
        }else {
            model.addAttribute("msg", "用户名或密码错误");
            return "index";
        }
    }
}

登录拦截器 LoginHandlerInterceptor:

编写一个自定义的登录拦截器 LoginHandlerInterceptor,继承HandlerInterceptor接口

  • 去获取用户信息 session:如果没有,让其跳转到首页 index.html,且提示消息 msg;
  • 如果不为空,放行
package com.al.config;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginHandlerInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 用户如果登录成功了,应该获取到用户的 session 信息
        Object session = request.getSession().getAttribute("LoginUser");
        if (session == null) {
            request.setAttribute("msg", "权限不够,请先登录");
            request.getRequestDispatcher("/index.html").forward(request, response);
            return false;
        } else {
            return true;
        }
    }
}

注册拦截器 LoginHandlerInterceptor 到spring 容器中,Srping Boot配置中添加自定义配置资源

定义完拦截器后,我们需要进行在 bean中进行 注册配置:

  • 在MyMvcConfig配置类中,重写关于拦截器的方法,添加自定义的拦截器

  • 屏蔽:要拦截的以及不要拦截的 路径 资源,

  • 需要放行的:.excludePathPatterns(“/index.html”, “/”, “/user/login”, “/css/", "/js/”, “/img/**”)。才能将页面完全导入进来,页面加载出来

package com.al.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
        registry.addViewController("/main.html").setViewName("dashboard");
    }

    // 将自定义的国际化配置放入 Spring容器中,使组件生效
    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginHandlerInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/index.html", "/", "/user/login", "/css/**", "/js/**", "/img/**");
    }
}

如果不放行静态资源,页面无法完全导入:

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginHandlerInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/index.html", "/", "/user/login");
    }

image-20220425105914821

修改后测试:此时直接访问http://localhost:8080/main.html 显示权限不够:

image-20220425095810428

重新写入username 和password,进入到dashboard页面。

如果再重新直接访问 http://localhost:8080/main.html ,也可以直接进入。因为此时的 session 里面存入了用户的信息,拦截器会放行通过。

5、员工信息:增删改查

展示员工列表

实现Customers视图跳转

目标,功能:点击dashboard.html页面中的Customers 按钮跳转到 list.html列表,展示所有的员工信息。

image-20220425162706553

我们想要 点击员工管理,就去跳转到 员工管理的界面:

  • 那么,我们把这个对应的员工管理点击 动作 对应的超链接改为 url地址就可以了:
  • 员工管理展示信息列表的 url 请求响应后,对应的 controller 去控制这个请求 servlet: EmployeeController
  • 可能随着项目扩展,员工变多, 所以将 list页面移动到 创建的 emp文件中,或许以后还有其它页面。

dashboard.html页面的代码修改:

image-20220425163326807

修改后:

					<li class="nav-item">
								<a class="nav-link" th:href="@{/emps}">
									<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
										<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
										<circle cx="9" cy="7" r="4"></circle>
										<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
										<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
									</svg>
									员工管理
								</a>
							</li>

同样修改list.html对应该的代码为上述代码:

  • 我们在templates目录下新建一个包emp,用来放所有关于员工信息的页面,我们将list.html页面移入该包中

    image-20220425164141212

在这里插入图片描述
image-20220425163653903

员工管理控制器:EmployeeController:

  • 对于请求的controller,处理 /emps 这一url请求,在controller 包下,创建EmployeeController 类 去对 /emps 请求进行响应
package com.al.controller;

import com.al.dao.EmployeeDao;
import com.al.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Collection;

// 员工管理信息 controller
@Controller
public class EmployeeController {

    @Autowired
    private EmployeeDao employeeDao;

    @RequestMapping("/emps")
    public String list(Model model){
        Collection<Employee> employees = employeeDao.getAll();
        model.addAttribute("emps",employees);
        return "emps/list"; // 返回到 list界面
    }
}

测试:点击Customers,成功跳转到/emps

image-20220425170827413

改进的地方

  • 点击员工管理,我们想要实现高亮的效果
  • 需要对侧边栏进行修改, 对应着员工管理这些选项。类似的,我们也将顶部导航栏 进行插入, 实现代码的复用:
提取页面公共部分:侧边栏、顶部栏

对于这些公共的地方, 我们创建一个 commons文件夹去进行存放, commons.html 实现代码复用。

image-20220425202839075

commons.html

commons.html 公共部分的复用代码:顶部导航栏与侧边导航栏:

  • 顶部导航栏,利用th:fragment提取出来,命名为topbar【th:fragment=“topbar”】
  • 侧边栏,利用th:fragment提取出来,命名为sidebar【th:fragment=“siderbar”】
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<!--顶部导航栏,利用th:fragment提取出来,命名为topbar-->
<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/#">Company
        name</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" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
        </li>
    </ul>
</nav>

<!--侧边栏,利用th:fragment提取出来,命名为sidebar-->
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="siderbar">
    <div class="sidebar-sticky">
        <ul class="nav flex-column">
            <li class="nav-item">
                <a class="nav-link active" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                         stroke-linejoin="round" class="feather feather-home">
                        <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
                        <polyline points="9 22 9 12 15 12 15 22"></polyline>
                    </svg>
                    Dashboard <span class="sr-only">(current)</span>
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                         stroke-linejoin="round" class="feather feather-file">
                        <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
                        <polyline points="13 2 13 9 20 9"></polyline>
                    </svg>
                    Orders
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                         stroke-linejoin="round" class="feather feather-shopping-cart">
                        <circle cx="9" cy="21" r="1"></circle>
                        <circle cx="20" cy="21" r="1"></circle>
                        <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
                    </svg>
                    Products
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" th:href="@{/emps}">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                         stroke-linejoin="round" class="feather feather-users">
                        <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
                        <circle cx="9" cy="7" r="4"></circle>
                        <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
                        <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
                    </svg>
                    员工管理
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                         stroke-linejoin="round" class="feather feather-bar-chart-2">
                        <line x1="18" y1="20" x2="18" y2="10"></line>
                        <line x1="12" y1="20" x2="12" y2="4"></line>
                        <line x1="6" y1="20" x2="6" y2="14"></line>
                    </svg>
                    Reports
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                         stroke-linejoin="round" class="feather feather-layers">
                        <polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
                        <polyline points="2 17 12 22 22 17"></polyline>
                        <polyline points="2 12 12 17 22 12"></polyline>
                    </svg>
                    Integrations
                </a>
            </li>
        </ul>

        <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
            <span>Saved reports</span>
            <a class="d-flex align-items-center text-muted"
               href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
                     stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
                     class="feather feather-plus-circle">
                    <circle cx="12" cy="12" r="10"></circle>
                    <line x1="12" y1="8" x2="12" y2="16"></line>
                    <line x1="8" y1="12" x2="16" y2="12"></line>
                </svg>
            </a>
        </h6>
        <ul class="nav flex-column mb-2">
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                         stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Current month
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                         stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Last quarter
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                         stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Social engagement
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                         stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Year-end sale
                </a>
            </li>
        </ul>
    </div>
</nav>
</html>
list.html 和 dashboard.html

删除掉 list.html 和 dashboard.html 中的侧边栏和顶部导航栏的代码:

  • dashboard.html中删除后剩下的:

    image-20220425172750822

    <!DOCTYPE html>
    <!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    	<head>
    		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    		<meta name="description" content="">
    		<meta name="author" content="">
    
    		<title>Dashboard Template for Bootstrap</title>
    		<!-- Bootstrap core CSS -->
    		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
    
    		<!-- Custom styles for this template -->
    		<link th:href="@{/css/dashboard.css}" rel="stylesheet">
    		<style type="text/css">
    			/* Chart.js */
    			
    			@-webkit-keyframes chartjs-render-animation {
    				from {
    					opacity: 0.99
    				}
    				to {
    					opacity: 1
    				}
    			}
    			
    			@keyframes chartjs-render-animation {
    				from {
    					opacity: 0.99
    				}
    				to {
    					opacity: 1
    				}
    			}
    			
    			.chartjs-render-monitor {
    				-webkit-animation: chartjs-render-animation 0.001s;
    				animation: chartjs-render-animation 0.001s;
    			}
    		</style>
    	</head>
    
    	<body>
    
    		<div class="container-fluid">
    			<div class="row">
    
    				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
    					<div class="chartjs-size-monitor" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;">
    						<div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
    							<div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0"></div>
    						</div>
    						<div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
    							<div style="position:absolute;width:200%;height:200%;left:0; top:0"></div>
    						</div>
    					</div>
    					<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
    						<h1 class="h2">Dashboard</h1>
    						<div class="btn-toolbar mb-2 mb-md-0">
    							<div class="btn-group mr-2">
    								<button class="btn btn-sm btn-outline-secondary">Share</button>
    								<button class="btn btn-sm btn-outline-secondary">Export</button>
    							</div>
    							<button class="btn btn-sm btn-outline-secondary dropdown-toggle">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
                    This week
                  </button>
    						</div>
    					</div>
    
    					<canvas class="my-4 chartjs-render-monitor" id="myChart" width="1076" height="454" style="display: block; width: 1076px; height: 454px;"></canvas>
    
    
    				</main>
    			</div>
    		</div>
    
    		<!-- Bootstrap core JavaScript
        ================================================== -->
    		<!-- Placed at the end of the document so the pages load faster -->
    		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" ></script>
    		<script type="text/javascript" src="asserts/js/popper.min.js" ></script>
    		<script type="text/javascript" src="asserts/js/bootstrap.min.js" ></script>
    
    		<!-- Icons -->
    		<script type="text/javascript" src="asserts/js/feather.min.js" ></script>
    		<script>
    			feather.replace()
    		</script>
    
    		<!-- Graphs -->
    		<script type="text/javascript" src="asserts/js/Chart.min.js" ></script>
    		<script>
    			var ctx = document.getElementById("myChart");
    			var myChart = new Chart(ctx, {
    				type: 'line',
    				data: {
    					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
    					datasets: [{
    						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
    						lineTension: 0,
    						backgroundColor: 'transparent',
    						borderColor: '#007bff',
    						borderWidth: 4,
    						pointBackgroundColor: '#007bff'
    					}]
    				},
    				options: {
    					scales: {
    						yAxes: [{
    							ticks: {
    								beginAtZero: false
    							}
    						}]
    					},
    					legend: {
    						display: false,
    					}
    				}
    			});
    		</script>
    
    	</body>
    
    </html>
    
  • list.html 中删除后剩下的:

    image-20220425173129865

    <!DOCTYPE html>
    <!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    
    	<head>
    		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    		<meta name="description" content="">
    		<meta name="author" content="">
    
    		<title>Dashboard Template for Bootstrap</title>
    		<!-- Bootstrap core CSS -->
    		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
    
    		<!-- Custom styles for this template -->
    		<link th:href="@{/css/dashboard.css}" rel="stylesheet">
    		<style type="text/css">
    			/* Chart.js */
    			
    			@-webkit-keyframes chartjs-render-animation {
    				from {
    					opacity: 0.99
    				}
    				to {
    					opacity: 1
    				}
    			}
    			
    			@keyframes chartjs-render-animation {
    				from {
    					opacity: 0.99
    				}
    				to {
    					opacity: 1
    				}
    			}
    			
    			.chartjs-render-monitor {
    				-webkit-animation: chartjs-render-animation 0.001s;
    				animation: chartjs-render-animation 0.001s;
    			}
    		</style>
    	</head>
    
    	<body>
    
    
    		<div class="container-fluid">
    			<div class="row">
    				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
    					<h2>Section title</h2>
    					<div class="table-responsive">
    						<table class="table table-striped table-sm">
    							<thead>
    								<tr>
    									<th>#</th>
    									<th>Header</th>
    									<th>Header</th>
    									<th>Header</th>
    									<th>Header</th>
    								</tr>
    							</thead>
    							<tbody>
    								<tr>
    									<td>1,001</td>
    									<td>Lorem</td>
    									<td>ipsum</td>
    									<td>dolor</td>
    									<td>sit</td>
    								</tr>
    								<tr>
    									<td>1,002</td>
    									<td>amet</td>
    									<td>consectetur</td>
    									<td>adipiscing</td>
    									<td>elit</td>
    								</tr>
    								<tr>
    									<td>1,003</td>
    									<td>Integer</td>
    									<td>nec</td>
    									<td>odio</td>
    									<td>Praesent</td>
    								</tr>
    								<tr>
    									<td>1,003</td>
    									<td>libero</td>
    									<td>Sed</td>
    									<td>cursus</td>
    									<td>ante</td>
    								</tr>
    								<tr>
    									<td>1,004</td>
    									<td>dapibus</td>
    									<td>diam</td>
    									<td>Sed</td>
    									<td>nisi</td>
    								</tr>
    								<tr>
    									<td>1,005</td>
    									<td>Nulla</td>
    									<td>quis</td>
    									<td>sem</td>
    									<td>at</td>
    								</tr>
    								<tr>
    									<td>1,006</td>
    									<td>nibh</td>
    									<td>elementum</td>
    									<td>imperdiet</td>
    									<td>Duis</td>
    								</tr>
    								<tr>
    									<td>1,007</td>
    									<td>sagittis</td>
    									<td>ipsum</td>
    									<td>Praesent</td>
    									<td>mauris</td>
    								</tr>
    								<tr>
    									<td>1,008</td>
    									<td>Fusce</td>
    									<td>nec</td>
    									<td>tellus</td>
    									<td>sed</td>
    								</tr>
    								<tr>
    									<td>1,009</td>
    									<td>augue</td>
    									<td>semper</td>
    									<td>porta</td>
    									<td>Mauris</td>
    								</tr>
    								<tr>
    									<td>1,010</td>
    									<td>massa</td>
    									<td>Vestibulum</td>
    									<td>lacinia</td>
    									<td>arcu</td>
    								</tr>
    								<tr>
    									<td>1,011</td>
    									<td>eget</td>
    									<td>nulla</td>
    									<td>Class</td>
    									<td>aptent</td>
    								</tr>
    								<tr>
    									<td>1,012</td>
    									<td>taciti</td>
    									<td>sociosqu</td>
    									<td>ad</td>
    									<td>litora</td>
    								</tr>
    								<tr>
    									<td>1,013</td>
    									<td>torquent</td>
    									<td>per</td>
    									<td>conubia</td>
    									<td>nostra</td>
    								</tr>
    								<tr>
    									<td>1,014</td>
    									<td>per</td>
    									<td>inceptos</td>
    									<td>himenaeos</td>
    									<td>Curabitur</td>
    								</tr>
    								<tr>
    									<td>1,015</td>
    									<td>sodales</td>
    									<td>ligula</td>
    									<td>in</td>
    									<td>libero</td>
    								</tr>
    							</tbody>
    						</table>
    					</div>
    				</main>
    			</div>
    		</div>
    
    		<!-- Bootstrap core JavaScript
        ================================================== -->
    		<!-- Placed at the end of the document so the pages load faster -->
    		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"></script>
    		<script type="text/javascript" src="asserts/js/popper.min.js"></script>
    		<script type="text/javascript" src="asserts/js/bootstrap.min.js"></script>
    
    		<!-- Icons -->
    		<script type="text/javascript" src="asserts/js/feather.min.js"></script>
    		<script>
    			feather.replace()
    		</script>
    
    		<!-- Graphs -->
    		<script type="text/javascript" src="asserts/js/Chart.min.js"></script>
    		<script>
    			var ctx = document.getElementById("myChart");
    			var myChart = new Chart(ctx, {
    				type: 'line',
    				data: {
    					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
    					datasets: [{
    						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
    						lineTension: 0,
    						backgroundColor: 'transparent',
    						borderColor: '#007bff',
    						borderWidth: 4,
    						pointBackgroundColor: '#007bff'
    					}]
    				},
    				options: {
    					scales: {
    						yAxes: [{
    							ticks: {
    								beginAtZero: false
    							}
    						}]
    					},
    					legend: {
    						display: false,
    					}
    				}
    			});
    		</script>
    
    	</body>
    
    </html>
    

    测试,此时访问dashboard.html 或者 list.html(/emps)页面的结果:http://localhost:8080/emps

    没有了侧边栏和顶部导航栏

image-20220425174858729

插入侧边栏和导航栏:

  • 插入: 并注意使用 thymeleaf 语法 ~ 波浪号
<!--顶部导航栏-->
<div th:replace="~{commons/commons::topbar}"></div>

<!--侧边栏-->
<div th:replace="~{commons/commons::siderbar}"></div>

image-20220425175331768

测试:登陆后,成功导入侧边栏和导航栏。

image-20220425175456185

点击显示高亮

那么对于 高亮而言:就是 active 这里的动作:

commons页面:

  • 在公共页面commons.html相应标签部分利用thymeleaf接收参数active,利用三元运算符判断决定是否高亮
            <li class="nav-item">
                <a th:class="${active=='dashboard.html'?'nav-link active':'nav-link'}" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                         stroke-linejoin="round" class="feather feather-home">
                        <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
                        <polyline points="9 22 9 12 15 12 15 22"></polyline>
                    </svg>
                    Dashboard <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="@{/emps}">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                         stroke-linejoin="round" class="feather feather-users">
                        <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
                        <circle cx="9" cy="7" r="4"></circle>
                        <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
                        <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
                    </svg>
                    员工管理
                </a>
            </li>

image-20220425180918763

dashboard.html页面:

image-20220425180800589

				<!--侧边栏-->
				<div th:replace="~{commons/commons::siderbar(active='dashboard.html')}"></div>

同样在list.html的侧边栏标签传递参数activelist.html:

image-20220425180745215

		<!--侧边栏-->
				<div th:replace="~{commons/commons::siderbar(active='list.html')}"></div>

测试:

此时点击员工管理,员工管理高亮,成功。

image-20220425202706654

显示员工信息

修改 list.html 页面,展示员工的信息。

员工管理控制器 EmployeeController:

  • url请求 http://localhost:8080/emps 访问所有的员工信息。
  • 返回结果 集合 employees 封装在 emps中,利用 Model 将数据传递到前端
package com.al.controller;

import com.al.dao.EmployeeDao;
import com.al.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Collection;

// 员工管理信息 controller
@Controller
public class EmployeeController {

    @Autowired
    private EmployeeDao employeeDao;

    @RequestMapping("/emps")
    public String list(Model model){
        Collection<Employee> employees = employeeDao.getAll();
        model.addAttribute("emps",employees);
        return "emps/list"; // 返回到 list界面
    }
}

修改表格的展示信息:获取参数的信息表示:【从emps中获取,遍历表示】

  • <td th:text="${emp.getId()}"></td>
    
  • <td>[[${emp.getLastName()}]]</td>
    

image-20220426100858585

上述修改后,重新启动程序,可以展示员工的信息列表。不过此时的

  • gender 性别显示的是 0 与 1,而不是男/女。需要进行修改。使用三元运算符解决。
  • 且对于操作一栏,添加 编辑和删除的 按钮功能。
  • 日期一栏的格式,修改为 yyyy-MM-dd HH:mm:ss 的形式
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<h2>Section title</h2>
					<div class="table-responsive">
						<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.getId()}"></td>
									<td>[[${emp.getLastName()}]]</td>
									<td th:text="${emp.getEmail()}"></td>
									<td th:text="${emp.getGender()==0?'':''}"></td>
									<td th:text="${emp.getDepartment().getDepartmentName()}"></td>
									<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
									<td>
										<a class="btn btn-sm btn-primary">编辑</a>
										<a class="btn btn-sm btn-danger">删除</a>
									</td>
								</tr>
							</tbody>
						</table>
					</div>
				</main>

小结:

在登录过程中:

  • 用户名和密码的判断,决定是否成功登录。
  • 只有用户成功登录后,有了用户 session,才能进行访问特定界面。 拦截器【拦截请求,还需放行资源】

在展示员工列表的时候:

  • 提取页面公共部分,代码复用。

    <!--定义: 顶部导航栏,利用th:fragment提取出来,命名为topbar-->
    <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
        
        <!-- 插入:   顶部导航栏-->
    <div th:replace="~{commons/commons::topbar}"></div>
    
  • 员工信息在列表中循环/遍历获取 进行展示。

增加员工实现

员工管理的增加:

  • 在员工管理界面 添加 员工增加的请求url,按钮选项。
  • add 添加员工这一请求servlet 对应的控制器 controller。跳转到 add.html页面,进行添加
  • 创建 添加页面:add.html
添加员工选项

在员工信息展示列表 list.html 界面中添加 员工增加(add)选项,和对应的url请求路径:

<h2><a class="btn btn-sm btn-success" th:href="@{/add}">添加员工</a></h2>

image-20220426220955115

效果:

image-20220426220409062

EmployeeController 控制器

编写 add 添加员工这一请求的 controller。

  • 通过Get方式提交,返回add.html页面(在该页面完成员工信息添加)
    @GetMapping("/add")
    public String toAddPage(Model model) {
        //查出所有的部门信息,添加到departments中,用于前端接收
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments", departments);
        return "emps/add";//返回到添加员工页面
    }

在这里,toAddPage函数传递 部门信息给前端,便于在添加员工信息时 去选择所在部门信息。

  • model.addAttribute(“departments”, departments);
增加员工信息界面:add.html

此时的department 信息 传递给前端, 在前端显示出来所有部门的信息。 add.thml 页面的修改:

add.html :

  • 复制list.html中的内容,把员工表格信息展示的部分修改为:
<!--顶部导航栏-->
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid">
    <div class="row">
        <!--侧边栏-->
        <div th:replace="~{commons/commons::siderbar(active='list.html')}"></div>

        <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
            <div class="form-group">
                <label>LastName</label>
                <input type="text" name="lastName" class="form-control" placeholder="lastname:zsr">
            </div>
            <div class="form-group">
                <label>Email</label>
                <input type="email" name="email" class="form-control" placeholder="email: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">
                    <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>
                <!--注意这里的name是department.id,因为传入的参数为id-->
                <select class="form-control" name="department.id">
                    <option th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option>
                </select>
            </div>
            <div class="form-group">
                <label>Birth</label>
                <!--springboot默认的日期格式为yy/MM/dd-->
                <input type="text" name="date" class="form-control" placeholder="birth:yyyy/MM/dd">
            </div>
            <button type="submit" class="btn btn-primary">添加</button>
        </main>
    </div>
</div>

点击添加员工,成功跳转到add.html页面:

image-20220426222007053

add.html页面完成员工信息添加功能

在前面增加员工实现的功能,已经完成了:

  • 在list.html这一员工信息展示列表 添加了 增加员工 选项,以及响应 增加员工请求(/add) 的控制器 controller
  • 员工控制器EmployeeController 中添加 toAddPage 方法,前往增加员工界面 add.html,且传递 部门信息
  • 增加员工界面 add.html 的代码编写,展示员工的 lastName、email、department、gender的信息,且添加 ‘name’ 属性,便于将相应的属性值由前端传递给后端,添加数据到底层。这里是添加到 模拟数据库表中的员工数据信息的 Map<Integer, Employee> employees 中。

那么增加员工实现 功能完成后,跳转页面到 list.html中,展示所有的员工信息。

  • 增加选项动作提交时的url响应。这里也同样使用 /add 请求路径,不过请求方式改为 Post。【使用Resful风格】
  • 编写对应的 controller,并在该控制器中 获取此时的员工map,传递给 list.html界面,展示添加员工后的 员工信息列表。

add.html:

image-20220427161111403

<!--顶部导航栏-->
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid">
    <div class="row">
        <!--侧边栏-->
        <div th:replace="~{commons/commons::siderbar(active='list.html')}"></div>

        <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
            <form th:action="@{/add}" method="post">
            <div class="form-group">
                <label>LastName</label>
                <input type="text" name="lastName" class="form-control" placeholder="lastname:zsr">
            </div>
            <div class="form-group">
                <label>Email</label>
                <input type="email" name="email" class="form-control" placeholder="email: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">
                    <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>
                <!--注意这里的name是department.id,因为传入的参数为id-->
                <select class="form-control" name="department.id">
                    <option th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option>
                </select>
            </div>
            <div class="form-group">
                <label>Birth</label>
                <!--springboot默认的日期格式为yy/MM/dd-->
                <input type="text" name="date" class="form-control" placeholder="birth:yyyy/MM/dd">
            </div>
            <button type="submit" class="btn btn-primary">添加</button>
            </form>
        </main>
    </div>
</div>

编写对应的controller,同样在EmployeeController中添加一个方法addEmp用来处理点击添加按钮的操作

package com.al.controller;

import com.al.dao.DepartmentDao;
import com.al.dao.EmployeeDao;
import com.al.pojo.Department;
import com.al.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Collection;

// 员工管理信息 controller
@Controller
public class EmployeeController {

    @Autowired
    private EmployeeDao employeeDao;
    @Autowired
    private DepartmentDao departmentDao;

    @RequestMapping("/emps")
    public String list(Model model){
        Collection<Employee> employees = employeeDao.getAll();
        model.addAttribute("emps",employees);
        return "emps/list"; // 返回到 list界面
    }

    @GetMapping("/add")
    public String toAddPage(Model model) {
        //查出所有的部门信息,添加到departments中,用于前端接收
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments", departments);
        return "emps/add";//返回到添加员工页面
    }
    
    @PostMapping("/add")
    public String addEmp(Employee employee){
        System.out.println("save=>" +employee);
        employeeDao.addEmployee(employee); //添加一个员工
        return "redirect:/emps";//重定向到/emps,刷新列表,返回到list页面
    }
}

重启程序,进行测试,选择添加员工进入到 add.html界面:【注意:此时的日期是yyyy/MM/dd 的形式】

image-20220427161927229

点击添加选项,url请求 /add,resful风格的 Post请求提交方式,响应的控制器是 addEmp,完成员工信息添加,跳转到list.html界面。展示的所有员工信息为:这时候,发现错误,无法跳转到 list.html界面。错误的原因:

image-20220427174356120

在dao层的 EmployeeDao 发生了错误:

  • 增加员工时的:employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId())); 不正确
    // 增加一个员工
    public void addEmployee(Employee employee){
        if (employee.getId() == null){
            employee.setId(initId++);
        }
        employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
        employees.put(employee.getId(), employee);
        System.out.println(employee);
    }

经过测试发现:

  • employee.getDepartment().getId() 没有问题,获取的是 department部门的id 102
  • departmentDao.getDepartmentById(102) 这里出错了

关于 departmentDao 中的方法无法调用。???错误还没解决掉

但是如果有问题的话,在EmployeeController 控制器 中的 toAddPage里面的

Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments", departments);

这两行代码应该生效才对。 还是不对劲。…

修改员工信息

界面中,关于员工信息列表展示的时候,操作栏添加了 编辑 和 删除 的选项。同样的,对于编辑选项:【和添加员工信息的步骤类似】

  • 对于编辑选项,添加请求路径,url: /emp/{id} ,请求的时候携带员工id,便于查询对应的员工信息,进行展示 和修改
  • /emp/{id} 这一请求 servlet 对应的控制器 controller,跳转到更新界面 update.html
  • 在 update.html 点击 修改 按钮的一瞬间,我们需要返回到list页面,更新员工信息,因此我们需要添加 href 属性,实现点击按钮时发起一个请求 /edit
list.html界面中的编辑选项

点击编辑选项,

  • 增加 url 请求路径。
  • 且前端需要传递给后端的参数:携带员工id
<a class="btn btn-sm btn-primary" th:href="@{/emp/{id}(id=${emp.getId()})}">编辑</a>
EmployeeController 控制器

响应 url请求,获取前端传递过来的员工 id 信息,跳转到 update.html 页面,展示员工 目前的信息:

    @GetMapping("/emp/{id}")
    public String toUpdatePage(@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 "emps/update";
    }
updat.html 页面

这里的信息展示 和 add.html页面类似,复制进行修改:

  • 不能每次修改一次员工信息,就让id+1, 需要id隐藏域标签

    <input type="hidden" name="id" th:value="${emp.getId()}">
    
  • 显示员工的原有部门信息,以及所有的部门信息

                        <select class="form-control" name="department.id">
                            <option th:selected="${emp.getDepartment().getId() == department.getId()}" th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option>
                        </select>
    
  • 规定一下显示的日期格式

    <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}" type="text" name="birth" class="form-control" placeholder="birth:yyyy/MM/dd">
    

update.html:

<!--顶部导航栏-->
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid">
    <div class="row">
        <!--侧边栏-->
        <div th:replace="~{commons/commons::siderbar(active='list.html')}"></div>

        <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 th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="lastname:zsr">
                </div>
                <div class="form-group">
                    <label>Email</label>
                    <input th:value="${emp.getEmail()}" type="email" name="email" class="form-control" placeholder="email:xxxxx@qq.com">
                </div>
                <div class="form-group">
                    <label>Gender</label><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 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>
                <div class="form-group">
                    <label>department</label>
                    <!--注意这里的name是department.id,因为传入的参数为id-->
                    <select class="form-control" name="department.id">
                        <option th:selected="${emp.getDepartment().getId() == department.getId()}" th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option>
                    </select>
                </div>
                <div class="form-group">
                    <label>Birth</label>
                    <!--springboot默认的日期格式为yy/MM/dd-->
<!--                    <input th:value="${emp.getBirth()}" type="text" name="birth" class="form-control" placeholder="birth:yyyy/MM/dd"> -->
<!--                    <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm')}" type="text" name="birth" class="form-control" placeholder="birth:yyyy/MM/dd">-->
                    <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}" type="text" name="birth" class="form-control" placeholder="birth:yyyy/MM/dd">
                </div>
                <button type="submit" class="btn btn-primary">修改</button>
            </form>
        </main>
    </div>
</div>
update.html页面完成员工信息修改功能

员工信息修改后, 需要重新跳转 或者重定向到 员工列表的页面。

在 update.html 这个修改员工信息页面完成后, 提交动作需要执行 发送 更新页面完成这个请求, 然后才能重定向到员工列表页面:

update.html:

<form th:action="@{/updateEmp}" method="post">

对应的控制器 Controller:

  • 处理点击修改按钮的请求。同样在EmployeeController中添加一个方法EditEmp用来处理edit页面点击添加的操作
    @PostMapping("/updateEmp")
    public String updataEmp(Employee employee){
        employeeDao.addEmployee(employee);//添加一个员工
        return "redirect:/emps";//添加完成重定向到/emps,刷新列表
    }

进行测试,进入添加页面,填写相关信息

image-20220428132617117

点击修改,返回list.html页面:【我取消掉关于employeeDao中增加员工中的departmentDao 才有如下结果】

image-20220428132712878

同样的错误:?????????????????????????????

  • employee.getDepartment().getId() 没有问题,获取的是 department部门的id 102
  • departmentDao.getDepartmentById(102) 这里出错了

关于 departmentDao 中的方法无法调用。???错误还没解决掉

但是如果有问题的话,在EmployeeController 控制器 中的 toAddPage里面的

Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments", departments);

这两行代码应该生效才对。 还是不对劲。…

删除员工信息

对于操作栏的删除,响应删除请求,url请求对应的controller,直接返回list.html界面。

删除选项增加超链接:【和编辑选项类似,超链接属性 href,且传递参数应携带员工id】

<a class="btn btn-sm btn-danger" th:href="@{/delemp/{id}(id=${emp.getId()})}">删除</a>

EmployeeController 控制器:

响应url请求 /delemp/{id},移除员工信息,返回重定向到 list.html 页面。

    @GetMapping("/delemp/{id}")
    public String deleteEmp(@PathVariable("id")Integer id, Model model){
        employeeDao.deleteEmployeeByID(id);
        return "redirect:/emps";//添加完成重定向到/emps,刷新列表
    }

测试,成功删除。

6、404页面定制和注销操作

404页面定制

只需要在templates目录下新建一个error包,然后将404.html放入其中,报错SpringBoot就会自动找到这个页面。
image-20220428190151951

注销操作

员工管理系统进行注销,即移除掉 session。

注销的功能在 commons.html文件中:

  • 顶部导航栏处中的标签添加href属性,实现点击发起请求/user/logout
<li class="nav-item text-nowrap">
    <a class="nav-link" th:href="@{/user/logout}">Sign out</a>
</li>

控制器controller,响应 /user/logout 请求,在LoginController中编写对应的方法,清除session,并重定向到首页。

    @RequestMapping("/user/logout")
    public String logout(HttpSession session) {
        session.invalidate();
        return "redirect:/index.html";
    }

测试,重启程序,成功注销。

如何写一个网站呢?

  • 前端

    • 模板:利用别人写好的,进行改写成自己想要的
    • 框架:去进行拼接。Bootstrap,layui,semantic-ui。 完成 栅格系统,导航栏,侧边栏,表单
  • 设计数据库【数据库表的设计、持久性、保护、备份】

  • 后端操作数据库。【事务、设计模式,提高开发效率的】

  • 后端和前端的数据交互。数据接口对接:json,对象 all in one

SpringBoot: 整合Mybatis-员工管理系统

对于这个员工管理系统,整合mybatis去完成对数据库操作。

创建数据库表

image-20220501094630816

数据库排序规则:

  • utf8_general_ci 不区分大小写
  • utf8_general_cs 区分大小写

建立的数据库表:

department 部门表:

SHOW CREATE DATABASE school;
create table `department`(
`id` int(4) not null auto_increment comment'部门id',
`departmentName` VARCHAR(30) NOT NULL DEFAULT '机密部门' COMMENT '姓名',
primary key(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

insert into `department`(`id`,`departmentName`)
values(101,'户部'),(102,'兵部'),(103,'吏部'),(104,'刑部'),(105,'礼部'),(106,'工部')

外键

概念:如果公共关键字在一个关系中是主关键字,那么这个公共关键字被称为另一个关系的外键。由此可见,外键表示了两个关系之间的相关联系。以另一个关系的外键作主关键字的表被称为主表,具有此外键的表被称为主表的从表

在实际操作中,将一个表的值放入第二个表来表示关联,所使用的值是第一个表的主键值(在必要时可包括复合主键值)。此时,第二个表中保存这些值的属性称为外键(foreign key)。

作用:保持数据一致性完整性,主要目的是控制存储在外键表中的数据,约束。使两张表形成关联,外键只能引用外表中的列的值或使用空值。

创建表的时候指定外键的约束

方式一:在创建表的时候,增加约束(麻烦,复杂)

CREATE TABLE IF NOT EXISTS `school`.`grade` (
`gradeid` INT(10) NOT NULL auto_increment COMMENT '年级编号',
`gradename` VARCHAR(10) not null COMMENT '年级名字',
PRIMARY KEY(`gradeid`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

CREATE TABLE if not EXISTS`student` (
`id` int(4) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '学号',
`name` varchar(20) NOT NULL DEFAULT '匿名' COMMENT '姓名',
`pwd` varchar(20) NOT NULL DEFAULT '123456' COMMENT '密码',
`sex` varchar(2) NOT NULL DEFAULT '男' COMMENT '性别',
`gradeid` INT(10) NOT NULL auto_increment COMMENT '年级编号',
`birthday` datetime DEFAULT NULL COMMENT '出生日期',
`address` varchar(60) DEFAULT NULL COMMENT '住址',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`),
  KEY `FK_gradeid` (`gradeid`),
  CONSTRAINT `FK_gradeid` FOREIGN KEY (`gradeid`) REFERENCES `grade` (`gradeid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

创建表之后再去修改

方式二:创建表之后再创建外键关系

alter table `student`
add constraint `FK_gradeid` foreign key(`gradeid`) references `grade` (`gradeid`);
-- alter table `表名`
-- add constraint `约束名` foreign key(作为外键的列) references 哪个表(哪个字段);

删除有外键关系的表的时候,必须要先删除引用别人的表(从表),再删除自己的表()。即先删除子表,再删除主表。

以上的操作都是物理外键,数据库级别的外键,,我们不建议使用,避免数据库过多造成困扰。

最佳实践

  • 数据库就是单纯的表,只用来存数据,只有行和列
  • 我们想使用多张表的数据,想使用外键,程序去实现

员工表:employee

create table `employee`(
`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT'员工id',
`lastName` VARCHAR(30) NOT NULL COMMENT '姓名',
`email` VARCHAR(30) NOT NULL DEFAULT '*.com' COMMENT '邮箱',
`gender` INT(4) NOT NULL DEFAULT '0' COMMENT '性别',
`department` INT(4) NOT NULL COMMENT '部门id',
`birth`DATETIME DEFAULT NULL COMMENT '生日',
primary key(`id`),
key `FK_id`(`department`),
constraint `FK_id` foreign key(`department`) references `department`(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

insert into `employee`(`id`,`lastName`,`email`,`gender`,`department`,`birth`)
values(1001,'AAA','1356207897@qq.com',0,101,'2020-04-05'),(1002,'BBB','1356207897@qq.com',0,102,'2020-04-06'),
(1003,'CCC','1356207897@qq.com',1,103,'2020-05-05'),(1004,'DDD','1356207897@qq.com',1,104,'2020-10-05'),
(1005,'EEE','1356207897@qq.com',1,103,'2020-11-05'),(1006,'FFF','1356207897@qq.com',0,106,'2021-04-05')

数据库表成功创建后的结果:
image-20220501111419863
image-20220501111401905

创建SpringBoot项目

新建一个SpringBoot项目:springboot-employee-management-system-mybatis。

image-20220501192125912

选择的springBoot配置有 web、JDBC、MySQL的:

image-20220501192218369

注意:我们需要导入 thymeleaf 模板引擎的maven依赖:

       <!--thymeleaf. 我们都是基于 3.x 开发的-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

项目建立之后,将原来的静态资源文件,导入到现在的这个项目中。

建立数据库连接

对数据库配置文件进行连接的时候,导入 JDBC、mysql、Mybatis的maven依赖。

  • mybatis 的依赖
  • 静态资源导出的依赖

pom.xml :

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.AL</groupId>
    <artifactId>springboot-employee-management-system-mybatis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-employee-management-system-mybatis</name>
    <description>Employee Management System Mybatis for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--thymeleaf. 我们都是基于 3.x 开发的-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--mybatis的maven依赖-->
        <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.2</version>
        </dependency>
        <!-- Druid数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>
        <!-- log4j日志-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <!--静态资源导出问题-->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

</project>

数据库配置资源文件:

我使用 application.properties 去进行数据信息配置 。

application.properties 资源配置文件:

# 自定义ul请求目录
#server.servlet.context-path=/al

#自定义配置:国际化资源文件
spring.messages.basename=i18n.login

# 数据库配置信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

进行测试,建立数据库连接成功。

测试类文件:

package com.AL;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class SpringbootEmployeeManagementSystemMybatisApplicationTests {

    // DI 注入数据源。从spring IOC 容器中获取数据配置信息
    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        // 查看默认的数据源: com.zaxxer.hikari.HikariDataSource
        System.out.println(dataSource.getClass());
        // 获得数据库连接:
        Connection connection = dataSource.getConnection()
        System.out.println(connection); // HikariProxyConnection@522082506 wrapping com.mysql.cj.jdbc.ConnectionImpl@57f847af

        //DruidDataSource druidDataSource = (DruidDataSource) dataSource;
        //System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
        //System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());

        connection.close(); // 关闭连接
    }

}

显示出了默认的数据源:

image-20220501114255012

pojo层

创建实体类:Department 和 Employee 类。

Department:

package com.AL.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

// 部门表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
    private Integer id;
    private String departmentName;
}

Employee:

package com.AL.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

// 员工表
@Data
@NoArgsConstructor
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(Integer id, String lastName, String email, Integer gender, Department department, Date birth) {
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
        this.department = department;
        // 默认的创建日期
        //this.birth = birth;
        this.birth = new Date();
    }
}

dao层

在这里,为了和上面的区分开来,创建Mapper目录以及对应Mapper接口、Mapper接口映射文件,完成对数据库的操作。

DepartmentMapper.java

package com.AL.mapper;

import com.AL.pojo.Department;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

//@Mapper : 表示本类是一个 MyBatis 的 Mapper
@Mapper
@Repository
public interface DepartmentMapper {

    // 获取所有部门信息
    List<Department> getDepartments();

    // 通过id获得部门
    Department getDepartment(Integer id);

}

DepartmentMapper.xml:关于DepartmentMapper 接口的映射文件,实现类。 完成对数据库的操作。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace= 绑定一个对应的Dao/Mapper接口
<mapper namespace="org.mybatis.example.BlogMapper">
    <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
-->
<mapper namespace="com.AL.mapper.DepartmentMapper">

    <select id="getDepartments" resultType="com.al.pojo.Department">
       select * from department;
    </select>

    <select id="getDepartment" resultType="Department" parameterType="int">
       select * from department where id = #{id};
    </select>

</mapper>

整合Myabtis

在配置文件中,整合myabtis。绑定关于dao层的接口和接口实现类 映射文件:

application.properties:

# 数据库配置信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

# 整合mybatis
mybatis.type-aliases-package=com.AL.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

Controller层

关于DepartmentController 的代码:

package com.AL.controller;

import com.AL.mapper.DepartmentMapper;
import com.AL.pojo.Department;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class DepartmentController {
    @Autowired
    DepartmentMapper departmentMapper;

    // 查询全部部门
    @GetMapping("/getDepartments")
    public List<Department> getDepartments(){
        return departmentMapper.getDepartments();
    }

    // 查询全部部门
    @GetMapping("/getDepartment/{id}")
    public Department getDepartment(@PathVariable("id") Integer id){
        return departmentMapper.getDepartment(id);
    }
}

启动主程序进行测试,发现错误:

org.thymeleaf.exceptions.TemplateInputException: Error resolving template [index], template might not exist or might not be accessible by any of the configured Template Resolvers

检查后,错误的原因是 thymeleaf 的依赖重复了,写了两遍。

在新建项目之后,去进行关于 controller 层的测试,发现了错误

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'departmentController': Unsatisfied dependency expressed through field 'departmentMapper'; 

错误的意思是:创建名为“departmentController”的bean时出错:通过字段“departmentMapper”表示的依赖关系不满足;

检查发现,是我的DepartmentMapper.xml中的代码写错了,修改后:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace= 绑定一个对应的Dao/Mapper接口
<mapper namespace="org.mybatis.example.BlogMapper">
    <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
-->
<mapper namespace="com.AL.mapper.DepartmentMapper">

    <select id="getDepartments" resultType="com.AL.pojo.Department">
       select * from department;
    </select>

    <select id="getDepartment" resultType="com.AL.pojo.Department" parameterType="int">
       select * from department where id = #{id};
    </select>

</mapper>

启动主程序,进行测试:http://localhost:8080/getDepartments

image-20220501195553235

对于根据 id 访问部门信息:http://localhost:8080/getDepartment/102

image-20220501195839188

员工 Employee

那么对于员工信息Employee 的CRUD操作,同样和部门信息类似,去建立dao层、controller进行测试,完成相关的操作。

Mapper层

EmployeeMapper:

package com.AL.mapper;


import com.AL.pojo.Employee;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

//@Mapper : 表示本类是一个 MyBatis 的 Mapper
@Mapper
@Repository
public interface EmployeeMapper {

    // 获取所有员工信息
    List<Employee> getEmployees();

    // 新增一个员工
    int addEmployee(Employee employee);

    // 通过id获得员工信息
    Employee getEmployeeById(Integer id);

    // 通过id删除员工
    int deleteEmployeeByID(Integer id);

}

EmployeeMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace= 绑定一个对应的Dao/Mapper接口
<mapper namespace="org.mybatis.example.BlogMapper">
    <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
-->
<mapper namespace="com.AL.mapper.EmployeeMapper">
    <select id="getEmployees" resultType="com.AL.pojo.Employee">
        select * from employee
    </select>
    <delete id="deleteEmployeeByID" parameterType="int">
        delete from employee where id=#{id}
    </delete>

</mapper>

Controller层

package com.AL.controller;


import com.AL.mapper.EmployeeMapper;
import com.AL.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class EmployeeController {

    @Autowired
    EmployeeMapper employeeMapper;

    // 获取所有员工信息
    @GetMapping("/getEmployees")
    public List<Employee> getEmployees(){
        return employeeMapper.getEmployees();
    }

    // 通过id删除员工
    @GetMapping("/delete/{id}")
    public int delete(@PathVariable("id") Integer id){
        return employeeMapper.deleteEmployeeByID(id);
    }

}

测试,访问 http://localhost:8080/getEmployees ,获取的所有 员工信息列表为:

image-20220501203942607

[{"id":1001,"lastName":"AAA","email":"1356207897@qq.com","gender":0,"department":101,"birth":"2020-04-05T00:00:00.000+00:00"},{"id":1002,"lastName":"BBB","email":"1356207897@qq.com","gender":0,"department":102,"birth":"2020-04-06T00:00:00.000+00:00"},{"id":1003,"lastName":"CCC","email":"1356207897@qq.com","gender":1,"department":103,"birth":"2020-05-05T00:00:00.000+00:00"},{"id":1004,"lastName":"DDD","email":"1356207897@qq.com","gender":1,"department":104,"birth":"2020-10-05T00:00:00.000+00:00"},{"id":1005,"lastName":"EEE","email":"1356207897@qq.com","gender":1,"department":103,"birth":"2020-11-05T00:00:00.000+00:00"},{"id":1006,"lastName":"FFF","email":"1356207897@qq.com","gender":0,"department":106,"birth":"2021-04-05T00:00:00.000+00:00"}]

可以发现,能够成功的显示员工所有的信息。不过 我们会想要的是关于部门的具体信息,而不仅仅是部门id。对于这种的解决方法,可以采用resultMap,映射的方法去解决。

resultMap

在这里修改的时候发现,为了更好的 方便的展示信息,需要在实体类 Employee 中绑定部门Department 类的信息。如下所示:

Employee:

package com.AL.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

// 员工表
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender; // 0:女  1:男。 用数字表示性别男女
    private Integer department; //原有的属性,对应数据库的 department id, 但对应的想要的是一个表
    private Date birth;
    private Department eDepartment; // 冗余设计

}
按照查询嵌套处理

多对一: 关联。 一对多:集合。

如何让员工表格和 部门表格去建立连接, 关联 起来。 需要用到map 以及 association

思路:
1.查询所有的员工信息
2.根据查询出来的员工信息的 department(即部门id),寻找对应的部门信息

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace= 绑定一个对应的Dao/Mapper接口
<mapper namespace="org.mybatis.example.BlogMapper">
    <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
-->
<mapper namespace="com.AL.mapper.EmployeeMapper">
    <!--查询员工信息: 单纯的 department id-->
<!--    <select id="getEmployees" resultType="com.AL.pojo.Employee">-->
<!--        select * from employee-->
<!--    </select>-->
<resultMap id="EmployeeDepartment" type="Employee">
    <result property="id" column="id"/>
    <result property="lastName" column="LastName"/>
    <result property="email" column="email"/>
    <result property="gender" column="gender"/>
    <result property="birth" column="birth"/>
    <!--复杂的属性:我们需要单独处理 对象:association    集合:collection-->
    <!--复杂的属性:我们需要单独处理
对象 关联:association    集合:collection
association : 关联,多对一
column : 数据库对应的列名
property : 对应属性名
javaType : 多对一 字段对应的Java类型
select : 关联一个语句
-->
    <association property="eDepartment" column="department" javaType="Department" select="getDepartment"/>
</resultMap>
    <select id="getDepartment" resultType="Department">
        select * from department where id = #{id}
    </select>
        <select id="getEmployees" resultMap="EmployeeDepartment">
        select * from employee
    </select>

    <delete id="deleteEmployeeByID" parameterType="int">
        delete from employee where id=#{id}
    </delete>

</mapper>

这时,重新启动程序进行测试,成功显示 department的所有信息。

image-20220501211708403

分析:

association property=“eDepartment” column=“department” javaType=“Department” select=“getDepartment” 在这里面的 property 对应实体类中的属性,column对应数据库表中的字段名,javaType表示多对一(该字段对应的Java类型)。从这里去绑定部门 Department 的信息。

按照结果嵌套处理

将查询到的结果进行封装到对象的属性中,重新进行展示。

    <!--    &lt;!&ndash;结果封装,将查询出来的列封装到对象属性中&ndash;&gt;-->
    <resultMap id="EmployeeDepartment2" type="Employee">
        <result property="id" column="eid"/>
        <result property="lastName" column="lastName"/>
        <result property="email" column="email"/>
        <result property="gender" column="gender"/>
        <result property="birth" column="birth"/>
        <association property="eDepartment" javaType="Department">
            <result property="id" column="did"/>
            <result property="departmentName" column="dname"/>
        </association>
    </resultMap>
    <select id="getEmployees" resultMap="EmployeeDepartment2">
        select e.id as eid,lastName,email,gender,birth,d.id as did,d.departmentName as dname
        from department d,employee e
        where d.id = e.department
    </select>

启动程序,进行测试,可以成功的查询到所有员工的信息以及对应的部门id和部门名称。

[{"id":1001,"lastName":"AAA","email":"1356207897@qq.com","gender":0,"birth":"2020-04-05T00:00:00.000+00:00","edepartment":{"id":101,"departmentName":"户部"}},{"id":1002,"lastName":"BBB","email":"1356207897@qq.com","gender":0,"birth":"2020-04-06T00:00:00.000+00:00","edepartment":{"id":102,"departmentName":"兵部"}},{"id":1003,"lastName":"CCC","email":"1356207897@qq.com","gender":1,"birth":"2020-05-05T00:00:00.000+00:00","edepartment":{"id":103,"departmentName":"吏部"}},{"id":1004,"lastName":"DDD","email":"1356207897@qq.com","gender":1,"birth":"2020-10-05T00:00:00.000+00:00","edepartment":{"id":104,"departmentName":"刑部"}},{"id":1005,"lastName":"EEE","email":"1356207897@qq.com","gender":1,"birth":"2020-11-05T00:00:00.000+00:00","edepartment":{"id":103,"departmentName":"吏部"}},{"id":1006,"lastName":"FFF","email":"1356207897@qq.com","gender":0,"birth":"2021-04-05T00:00:00.000+00:00","edepartment":{"id":106,"departmentName":"工部"}}]

分析:【自己的理解】

在这里的

    <association property="eDepartment" javaType="Department">
        <result property="id" column="did"/>
        <result property="departmentName" column="dname"/>
    </association>

里面的 eDepartment 是Employee实体类的一个属性,这个属性对应的其实是多个字段(一个类Department)。 那么在这种 resulMap的map映射里面,将这些查询到的结果 字段值 重新封装到一个对应的表里面,对应着 想要的对象属性,然后去进行展示。

Employee的完善

对于剩下的CRUD操作代码的完善。

EmployeeMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace= 绑定一个对应的Dao/Mapper接口
<mapper namespace="org.mybatis.example.BlogMapper">
    <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
-->
<mapper namespace="com.AL.mapper.EmployeeMapper">
    <!--查询员工信息: 单纯的 department id-->
<!--    <select id="getEmployees" resultType="com.AL.pojo.Employee">-->
<!--        select * from employee-->
<!--    </select>-->

<!--按照查询嵌套处理-->
<!--<resultMap id="EmployeeDepartment" type="Employee">-->
<!--    <result property="id" column="id"/>-->
<!--    <result property="lastName" column="LastName"/>-->
<!--    <result property="email" column="email"/>-->
<!--    <result property="gender" column="gender"/>-->
<!--    <result property="birth" column="birth"/>-->
<!--    &lt;!&ndash;复杂的属性:我们需要单独处理 对象:association    集合:collection&ndash;&gt;-->
<!--    &lt;!&ndash;复杂的属性:我们需要单独处理-->
<!--对象 关联:association    集合:collection-->
<!--association : 关联,多对一-->
<!--column : 数据库对应的列名-->
<!--property : 对应属性名-->
<!--javaType : 多对一 字段对应的Java类型-->
<!--select : 关联一个语句-->
<!--&ndash;&gt;-->
<!--    <association property="eDepartment" column="department" javaType="Department" select="getDepartment"/>-->
<!--</resultMap>-->
<!--    <select id="getDepartment" resultType="Department">-->
<!--        select * from department where id = #{id}-->
<!--    </select>-->
<!--        <select id="getEmployees" resultMap="EmployeeDepartment">-->
<!--        select * from employee-->
<!--    </select>-->

    <!--    &lt;!&ndash;结果封装,将查询出来的列封装到对象属性中&ndash;&gt;-->
    <resultMap id="EmployeeDepartment2" type="Employee">
        <result property="id" column="eid"/>
        <result property="lastName" column="lastName"/>
        <result property="email" column="email"/>
        <result property="gender" column="gender"/>
        <result property="birth" column="birth"/>
        <association property="eDepartment" javaType="Department">
            <result property="id" column="did"/>
            <result property="departmentName" column="dname"/>
        </association>
    </resultMap>
    <select id="getEmployees" resultMap="EmployeeDepartment2">
        select e.id as eid,lastName,email,gender,birth,d.id as did,d.departmentName as dname
        from department d,employee e
        where d.id = e.department
    </select>

    <insert id="addEmployee" parameterType="Employee">
        insert into employee (lastName,email,gender,department,birth)
        values (#{lastName},#{email},#{gender},#{department},#{birth});
    </insert>

    <!--select * from employee where id = #{id}-->
    <select id="getEmployeeById" parameterType="int" resultMap="EmployeeDepartment2">
        select e.id as eid,lastName,email,gender,birth,d.id as did,d.departmentName as dname
        from department d,employee e
        where d.id = e.department and e.id = #{id}
    </select>

    <delete id="deleteEmployeeByID" parameterType="int">
        delete from employee where id=#{id}
    </delete>

</mapper>

EmployeeController:

package com.AL.controller;


import com.AL.mapper.DepartmentMapper;
import com.AL.mapper.EmployeeMapper;
import com.AL.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.List;

@RestController
public class EmployeeController {

    @Autowired
    EmployeeMapper employeeMapper;

    @Autowired
    DepartmentMapper departmentMapper;

    // 获取所有员工信息
    @GetMapping("/getEmployees")
    public List<Employee> getEmployees(){
        return employeeMapper.getEmployees();
    }

    @GetMapping("/add")
    public int save(){
        Employee employee = new Employee();
        employee.setLastName("alzn");
        employee.setEmail("hhhhhh@qq.com");
        employee.setGender(1);
        employee.setEDepartment(departmentMapper.getDepartment(101));
        employee.setBirth(new Date());
        return employeeMapper.addEmployee(employee);
    }

    // 通过id获得员工信息
    @GetMapping("/get/{id}")
    public Employee getById(@PathVariable("id") Integer id){
        return employeeMapper.getEmployeeById(id);
    }

    // 通过id删除员工
    @GetMapping("/delete/{id}")
    public int delete(@PathVariable("id") Integer id){
        return employeeMapper.deleteEmployeeByID(id);
    }

}

重新启动程序,进行测试,成功展示员工的信息以及对应的部门信息。

整合前端界面

将前面的项目里面的资源配置文件复制到此时项目里面:

image-20220502093740134

关于url请求拦截、静态资源放行的配置文件和用户登录验证的控制器、首页显示控制器:

image-20220502094502950

登录首页实现

首页显示控制器:

  • 所有的访问界面需要从 index.html 界面进行登录。直接跳转到该界面
@Controller
public class IndexController {
    @RequestMapping({"/", "/index.html"}) //这是一个集合
    public String index(){
        return "index";
    }
}

去测试,看能否访问到该页面信息,去让用户进行登录。

错误信息:

 Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
     
     org.thymeleaf.exceptions.TemplateInputException: Error resolving template [index], template might not exist or might not be accessible by any of the configured Template Resolvers

可以发现编译后的 targets里面没有我们的静态资源配置文件 templates:

image-20220502103312237

这属于静态资源导出时的问题,我们查找后,对pom.xml 配置资源文件进行修改:

  • 加入**/*.html 配置和其它的
  • src/main/resources **/.yml **/.properties **/.xml **/.html false
        <!--静态资源导出问题-->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.yaml</include>
                    <include>**/*.xml</include>
                    <include>**/*.html</include>
                    <include>**/*.js</include>
                    <include>**/*.css</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>

我们使静态资源能够导出,此时再进行测试的结果:

image-20220502110111923

image-20220502110136618

登录功能的实现

用户登录控制器和拦截器配置:

  • LoginController:
package com.Al.controller;


import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpSession;

@Controller
//@RestController
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 "dashboard";
            return "redirect:/main.html"; // 重定向
        }else {
            model.addAttribute("msg", "用户名或密码错误");
            return "index";
        }
    }

    @RequestMapping("/user/logout")
    public String logout(HttpSession session) {
        session.invalidate();
        return "redirect:/index.html";
    }
}
  • 登录时的拦截器:LoginHandlerInterceptor
package com.AL.config;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginHandlerInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 用户如果登录成功了,应该获取到用户的 session 信息
        Object session = request.getSession().getAttribute("LoginUser");
        if (session == null) {
            request.setAttribute("msg", "权限不够,请先登录");
            request.getRequestDispatcher("/index.html").forward(request, response);
            return false;
        } else {
            return true;
        }
    }
}

地域解析器配置:

package com.AL.config;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

// 地区解析器:获取链接上携带的参数信息
public class MyLocaleResolver implements LocaleResolver {
    // 解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        // 获取请求中的国家化参数
        String language = request.getParameter("l");
        // 默认的地区
        Locale locale = Locale.getDefault();
        //如果请求的链接参数不为空,携带了国际化参数
        if (!StringUtils.isEmpty(language)) {
            String[] split = language.split("_");//zh_CN(语言_地区)
            locale = new Locale(split[0], split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

MVC配置信息:

package com.AL.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
        registry.addViewController("/main.html").setViewName("dashboard");
    }

    // 将自定义的国际化配置放入 Spring容器中,使组件生效
    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginHandlerInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/index.html", "/", "/user/login", "/css/**", "/js/**", "/img/**");
    }
}

启动程序,进行测试,发现能够成功登录,且也可以进行拦截没有用户session的url请求。

image-20220502111548747

控制器修改

在整合mybatis时测试的控制器,直接返回json形式的数据。只管展示。

在这里,为了和原先模拟数据库显示前端界面,需要对控制器中后端传递给前端数据 的代码进行修改。

查询:员工信息列表展示

在controller包下创建 EmployeeControllerDemo02 控制器。查询员工所有的信息,后端程序将结果封装在 Model 中的 emps 中,传递给前端。前端的emps 进行foreach 进行展示员工的信息。

EmployeeControllerDemo02:

package com.AL.controller;

import com.AL.mapper.DepartmentMapper;
import com.AL.mapper.EmployeeMapper;
import com.AL.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

// 员工管理信息
@Controller
public class EmployeeControllerDemo02 {
    @Autowired
    EmployeeMapper employeeMapper;

    @Autowired
    DepartmentMapper departmentMapper;

    @RequestMapping("/emps")
    public String list(Model model){
        List<Employee> employeeList = employeeMapper.getEmployees();
        model.addAttribute("emps",employeeList);
        return "emps/list"; // 返回到 list界面
    }
}

前端界面显示 员工的信息。

  • 需要注意的是,在这里,我们的实体类pojo中的 Employee 的属性为
public class Employee {
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender; // 0:女  1:男。 用数字表示性别男女
    private Integer department; //原有的属性,对应数据库的 department id, 但对应的想要的是一个表
    private Date birth;
    private Department eDepartment; // 冗余设计
}
  • 对应的数据库表 employee 的字段为:
create table `employee`(
`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT'员工id',
`lastName` VARCHAR(30) NOT NULL COMMENT '姓名',
`email` VARCHAR(30) NOT NULL DEFAULT '*.com' COMMENT '邮箱',
`gender` INT(4) NOT NULL DEFAULT '0' COMMENT '性别',
`department` INT(4) NOT NULL COMMENT '部门id',
`birth`DATETIME DEFAULT NULL COMMENT '生日',
primary key(`id`),
key `FK_id`(`department`),
constraint `FK_id` foreign key(`department`) references `department`(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

image-20220502194530171

而前面的,我们编写的dao层中的 EmployeeMapper.xml中查询所有的员工信息,返回的结果为:

<resultMap id="EmployeeDepartment2" type="Employee">
    <result property="id" column="eid"/>
    <result property="lastName" column="lastName"/>
    <result property="email" column="email"/>
    <result property="gender" column="gender"/>
    <result property="birth" column="birth"/>
    <association property="eDepartment" javaType="Department">
        <result property="id" column="did"/>
        <result property="departmentName" column="dname"/>
    </association>
</resultMap>
<select id="getEmployees" resultMap="EmployeeDepartment2">
    select e.id as eid,lastName,email,gender,birth,d.id as did,d.departmentName as dname
    from department d,employee e
    where d.id = e.department
</select>

image-20220502195131803

我们需要在前端中修改的部分就是关于 获取部门信息 重新进行展示的部分。如下所示:

list.html :

<tr th:each="emp:${emps}">
<!--									<td th:text="${emp.getId()}"></td>-->
    <td th:text="${emp.getId()}"></td>
    <td>[[${emp.getLastName()}]]</td>
    <td th:text="${emp.getEmail()}"></td>
    <td th:text="${emp.getGender()==0?'':''}"></td>
<!--									<td th:text="${emp.getDepartment().getDepartmentName()}"></td>-->
    <td th:text="${emp.getEDepartment().getDepartmentName()}"></td>
    <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
    <td>
        <a class="btn btn-sm btn-primary" th:href="@{/emp/{id}(id=${emp.getId()})}">编辑</a>
        <a class="btn btn-sm btn-danger" th:href="@{/delemp/{id}(id=${emp.getId()})}">删除</a>
    </td>
</tr>

image-20220502195346683

测试,访问员工管理界面,查看是否成功显示所有的 员工信息。

image-20220502195609123

增加员工

增加员工实现的步骤:

  • 在list.html前端界面添加 员工增加的 选项及其超链接,响应url请求,跳转到toAddPage页面
  • toAdd控制器,响应 /add 这一url请求。后端需要传递前端一个部门信息(部门id、name名称),注意add.html显示部门选项 option
  • add.html 员工信息添加页面展示,尤其注意部门选项option这一部分的东西
  • 员工的信息添加完成后,重定向到 list.html页面,显示所有的员工信息。

EmployeeControllerDemo02:控制器中的 toadd 和add 方法,去响应对应着请求url

    @GetMapping("/add")
    public String toAddPage(Model model) {
        //查出所有的部门信息,添加到departments中,用于前端接收
        Collection<Department> departments = departmentMapper.getDepartments();
        model.addAttribute("departments", departments);
        return "emps/add";//返回到添加员工页面
    }
    @PostMapping("/add")
    public String addEmp(Employee employee){
        System.out.println("save=>" +employee);
        employeeMapper.addEmployee(employee); //添加一个员工
        return "redirect:/emps";//重定向到/emps,刷新列表,返回到list页面
    }

add.html:员工信息添加,重点注意的是 部门信息展示

  • 注意: 我将这里的属性 name 值设置成了department,这样才和 employee中的属性对应:select class=“form-control” name=“department”
  • option中显示的信息是 部门的名称,所以 th:text=“${department.getDepartmentName()}”
  • 传递的是部门id,所以是 th:value=“${department.getId()}” 对应着name=“department” 传递给前端

image-20220502203014662

            <form th:action="@{/add}" method="post">
            <div class="form-group">
                <label>LastName</label>
                <input type="text" name="lastName" class="form-control" placeholder="lastname:zsr">
            </div>
            <div class="form-group">
                <label>Email</label>
                <input type="email" name="email" class="form-control" placeholder="email: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">
                    <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>
                <!--注意这里的name是department.id,因为传入的参数为id-->
<!--                <select class="form-control" name="department.id">-->
                <!--注意: 我将这里的属性 name 值设置成了department,这样才和 employee中的属性对应-->
                <select class="form-control" name="department">
                    <option th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option>
                </select>
            </div>
            <div class="form-group">
                <label>Birth</label>
                <!--springboot默认的日期格式为yy/MM/dd-->
                <input type="text" name="birth" class="form-control" placeholder="birth:yyyy/MM/dd">
            </div>
            <button type="submit" class="btn btn-primary">添加</button>
            </form>

测试,查看跳转到 添加员工界面:

image-20220502203550588

点击添加选项,成功重定向到 list.html页面,展示所有员工信息:

image-20220502203341213

解决了这里的之后,那么在前面的那里,员工添加 信息 出错的原因,是不是也是这个呢?

我需要对springboot-03-system 项目中的add.html 的前端传递部门信息进行修改。查看这个问题

修改员工

修改员工的信息:和增加员工信息的步骤类似。

EmployeeControllerDemo02

    @GetMapping("/emp/{id}")
    public String toUpdatePage(@PathVariable("id")Integer id, Model model){
        //查出原来的数据
        Employee employee = employeeMapper.getEmployeeById(id);
        model.addAttribute("emp", employee);
        //查出所有部门的信息
        Collection<Department> departments = departmentMapper.getDepartments();
        model.addAttribute("departments", departments);
        return "emps/update";
    }
    @PostMapping("/updateEmp")
    public String updataEmp(Employee employee){
        employeeMapper.addEmployee(employee);//添加一个员工
        return "redirect:/emps";//添加完成重定向到/emps,刷新列表
    }

update.html

  • 重要的同样是 部门信息展示的部分。我自己修改的部门信息
<div class="form-group">
    <label>department</label>
    <!--注意这里的name是department.id,因为传入的参数为id-->
<!--                    <select class="form-control" name="department.id">-->
    <select class="form-control" name="department">
<!--                        <option th:selected="${emp.getDepartment().getId() == department.getId()}" th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option>-->
        <option th:selected="${emp.getEDepartment().getId() == department.getId()}" th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option>
    </select>
</div>

image-20220502210456988

进行测试,查看是否能够修改员工信息:

image-20220502205518670

修改后重定向到 list.html 页面:

  • 发现有两个该员工。检查后,发现自己在这里使用的方法里面是 addEmployee,所以导致增加了一个员工信息。

image-20220502205603239

删除员工

直接删除员工信息,重定向到 list.html页面。

EmployeeControllerDemo02

    @GetMapping("/delemp/{id}")
    public String deleteEmp(@PathVariable("id")Integer id, Model model){
        employeeMapper.deleteEmployeeByID(id);
        return "redirect:/emps";//添加完成重定向到/emps,刷新列表
    }

测试,删除员工。成功完成。

image-20220502210301345

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
课程简介:历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring BootSpring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值