随笔 - 24  文章 - 0  评论 - 1  阅读 - 1188 

出差期间,自己按照狂神视频搭建一个小型网站测试前后端交互

员工管理系统

前端使用BootStrap框架,Thymeleaf模板渲染引擎

后端使用SpringBoot

1、准备工作

1.1、需要的依赖

 
复制代码
<?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.5.5</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <groupId>com.zhou</groupId>
     <artifactId>SpringBoot-03-Web</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <name>SpringBoot-03-Web</name>
     <description>Demo project for Spring Boot</description>
     <properties>
         <java.version>1.8</java.version>
     </properties>
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </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><dependency>
             <groupId>org.webjars</groupId>
             <artifactId>jquery</artifactId>
             <version>3.4.1</version>
         </dependency><dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-thymeleaf</artifactId>
         </dependency>
     </dependencies><build>
         <plugins>
             <plugin>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
         </plugins>
     </build></project>
复制代码

 

前端资源在https://gitee.com/ENNRIAAA/springboot-learning/repository/archive/master.zip下下载。


后端部分,不使用数据库的情况下仿照数据库做一些操作

1.2、pojo层

实体层,设置部门和员工属性

复制代码
 package com.zhou.pojo;
 ​
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 ​
 /**
  * @Name Department
  * @Description 部门表
  * @Author 88534
  * @Date 2021/10/9 9:26
  */
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 public class Department {
     private Integer departmentId;
     private String departmentName;
 }
 package com.zhou.pojo;
 ​
 import lombok.Data;
 import lombok.NoArgsConstructor;
 ​
 import java.util.Date;
 ​
 /**
  * @Name Employee
  * @Description 员工表
  * @Author 88534
  * @Date 2021/10/9 9:28
  */
 @Data
 @NoArgsConstructor
 public class Employee {
     private Integer employeeId;
     private String lastName;
     private String email;
     /**
      * 0:女;1:男
      */
     private Integer gender;
     private Department department;
     private Date birth;
 ​
     public Employee(Integer employeeId, String lastName, String email, Integer gender, Department department) {
         this.employeeId = employeeId;
         this.lastName = lastName;
         this.email = email;
         this.gender = gender;
         this.department = department;
         this.birth = new Date();
     }
 }
复制代码

 

1.3、dao层

对数据进行处理,实现增删改查

部门dao

 
复制代码
package com.zhou.dao;
 ​
 import com.zhou.pojo.Department;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Repository;
 ​
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 ​
 /**
  * @Name DepartmentDao
  * @Description 部门dao,伪造数据库
  * @Author 88534
  * @Date 2021/10/9 9:30
  */
 @Repository
 public class DepartmentDao {
     /**
      * 模拟数据库中的数据
       */
     private static Map<Integer, Department> departments = null;
 ​
     static {
         // 创建一个部门表
         departments = new HashMap<Integer,Department>();
 ​
         departments.put(101,new Department(101,"教学部"));
         departments.put(102,new Department(102,"市场部"));
         departments.put(103,new Department(103,"教研部"));
         departments.put(104,new Department(104,"运营部"));
         departments.put(105,new Department(105,"后勤部"));
     }
 ​
     /**
      * 获得所有部门信息
      * @return
      */
     public Collection<Department> getDepartments(){
         return departments.values();
     }
 ​
     /**
      * 通过id得到部门
      * @param id
      * @return
      */
     public Department getDepartmentById(Integer id){
         return departments.get(id);
     }
 }
复制代码

 

员工dao

复制代码
 package com.zhou.dao;
 ​
 import com.zhou.pojo.Department;
 import com.zhou.pojo.Employee;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Repository;
 ​
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 ​
 /**
  * @Name EmployeeDao
  * @Description 员工dao
  * @Author 88534
  * @Date 2021/10/9 13:26
  */
 @Repository
 public class EmployeeDao {
 ​
     /**
      * 模拟数据库中的数据
      */
     private static Map<Integer, Employee> employees = null;
 ​
     /**
      * 员工有所属的部门
      */
     @Autowired
     private DepartmentDao departmentDao;
 ​
     static {
         // 创建一个员工表
         employees = new HashMap<Integer,Employee>();
 ​
         employees.put(1001,new Employee(1001,"AA","A123456@qq.com",1,new Department(101,"教学部")));
         employees.put(1002,new Employee(1002,"BB","B123456@qq.com",0,new Department(102,"市场部")));
         employees.put(1003,new Employee(1003,"CC","C123456@qq.com",1,new Department(103,"教研部")));
         employees.put(1004,new Employee(1004,"DD","D123456@qq.com",0,new Department(104,"运营部")));
         employees.put(1005,new Employee(1005,"EE","E123456@qq.com",1,new Department(105,"后勤部")));
     }
 ​
     /**
      * 主键自增
      */
     private static Integer initId = 1006;
 ​
     /**
      * 增加一个员工
      * @param employee
      */
     public void save(Employee employee){
         if (employee.getEmployeeId() == null){
             employee.setEmployeeId(initId++);
         }
         employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getDepartmentId()));
         employees.put(employee.getEmployeeId(),employee);
     }
 ​
     /**
      * 查询所有部门信息
      * @return
      */
     public Collection<Employee> getAllEmployees(){
         return employees.values();
     }
 ​
     /**
      * 通过id查询员工
      * @param id
      * @return
      */
     public Employee getEmployeeById(Integer id){
         return employees.get(id);
     }
 ​
     /**
      * 通过id删除员工
      * @param id
      */
     public void delete(Integer id){
         employees.remove(id);
     }
 }
复制代码

 

系统配置application.properties

 # 自定义时间格式
 spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss
 spring.mvc.date-format=yyyy-MM-dd HH:mm:ss
 
 # 关闭模板引擎的缓存
 spring.thymeleaf.cache=false
 
 server.servlet.context-path=/zhou
 
 # 配置文件存放的真实路径
 spring.messages.basename=i18n.login

2、首页配置

使用BootStrap前端框架

所有页面的静态资源都需要使用Thymeleaf接管,以首页为例

复制代码
 <!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>Please sign in</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">请登录</h1>
     <label class="sr-only">账号</label>
     <input type="text" class="form-control" placeholder="账号" required="" autofocus="">
     <label class="sr-only">密码</label>
     <input type="password" class="form-control" placeholder="密码" required="">
     <div class="checkbox mb-3">
         <label>
             <input type="checkbox" value="remember-me"> 记住我
         </label>
     </div>
     <button class="btn btn-lg btn-primary btn-block" type="submit">提交</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>
复制代码

 

3、国际化

3.1、配置i18n文件

internationalization i和n之间隔着18个字母,简写为i18n

类似kubernetes:k8s

实现中英文切换

首先确保在配置file encoding中使用UTF-8支持中文

 

 

 

resource目录下添加i18n,添加配置文件

 

 

 

下载resource bundle插件,可以在下方选择中英文同步编辑

 

 

 

application.properties加上i18n路径

 # 配置文件存放的真实路径
 spring.messages.basename=i18n.login

3.2、LocaleResolver

需要在项目中进行按钮自动切换,需要自定义一个组件LocaleResolver

复制代码
 package com.zhou.config;
 ​
 import org.springframework.web.servlet.LocaleResolver;
 import org.thymeleaf.util.StringUtils;
 ​
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.util.Locale;
 ​
 /**
  * @Name MyLocalResolver
  * @Description 自定义语言转换组件
  * @Author 88534
  * @Date 2021/10/9 15:42
  */
 public class MyLocalResolver implements LocaleResolver {
     /**
      * 解析请求
      * @param request
      * @return
      */
     @Override
     public Locale resolveLocale(HttpServletRequest request) {
         // 获取请求中的语言参数
         String language = request.getParameter("l");
         // 如果没有就使用默认的
         Locale locale = Locale.getDefault();
         // 如果请求的链接携带了国际化的参数
         if (!StringUtils.isEmpty(language)){
             // zh_CN
             String[] split = language.split("_");
             // 语言_国家、地区
             locale = new Locale(split[0], split[1]);
         }
         return locale;
     }
 ​
     @Override
     public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
 ​
     }
 }
复制代码

 

3.3、@Bean

将组件配置到spring容器@Bean,交给Spring管理

复制代码
 package com.zhou.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;
 ​
 /**
  * @Name MyMvcConfig
  * @Description 添加自定义配置
  * @Author 88534
  * @Date 2021/10/5 21:21
  */
 @Configuration
 public class MyMvcConfig implements WebMvcConfigurer {
     @Override
     public void addViewControllers(ViewControllerRegistry registry) {
         registry.addViewController("/").setViewName("index");
         registry.addViewController("/index.html").setViewName("index");
     }
 ​
     /**
      * 自定义国际化组件
      * @return
      */
     @Bean
     public LocaleResolver localeResolver(){
         return new MyLocalResolver();
     }
 }
复制代码

 

3.4、前端

前端页面加上#{}和对应的配置

链接上加入传递给后端的参数l


输出效果:可以点击下方链接实现中英文跳转

中文:

 

 

 

英文:

 

 

4、登录+拦截器

不符合条件的拦截在登录页面

 package com.zhou.interceptor;
 
复制代码
 <!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 th:text="#{login.tip}">Please sign in</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"> [[#{login.remember}]]
         </label>
     </div>
     <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
     <p class="mt-5 mb-3 text-muted">© 2021-2022</p>
     <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>
复制代码

 

 
复制代码
 import org.springframework.web.servlet.HandlerInterceptor;
 import org.springframework.web.servlet.ModelAndView;
 ​
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 ​
 /**
  * @Name LoginHandlerInterceptor
  * @Description
  * @Author 88534
  * @Date 2021/10/9 17:20
  */
 public class LoginHandlerInterceptor implements HandlerInterceptor {
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         // 登录成功之后,应该有用户的session
         Object loginUser = request.getSession().getAttribute("loginUser");
         if (loginUser==null){
             // 没有登录
             request.setAttribute("msg","没有权限,请先登录");
             request.getRequestDispatcher("/index.html").forward(request,response);
             return false;
         } else {
             return true;
         }
     }
 ​
     @Override
     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
         HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
     }
 ​
     @Override
     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
         HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
     }
 }
复制代码

 

 

前端加入msg的加红告警

 <!--    如果msg不为空则显示消息msg-->
     <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

输出结果:

没有输入时

 

 

输入密码错误时:

 

 

5、员工列表展示

5.1、提取公共页面

顶部栏和侧边栏是各个界面都重复的,可以收入一个commons/commons.html作为公共组件

可以使用th:fragment和th:insert/replace实现打包和插入组件

 th:fragment="topBar"
 <div th:replace="~{commons/commons::topBar}"></div>
 th:fragment="sideBar"
 <div th:insert="~{commons/commons::sideBar(active='main.html')}

 

5.2、高亮转移

高亮部分由一个nav-link active的class标签属性确定,可以通过th:class传递active参数,规定到了哪个页面是哪一个部分高亮

如果要传递参数,可以直接使用()传参,接收判断

 <a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/main.html}">
     <div th:insert="~{commons/commons::sideBar(active='main.html')}"></div>
 <a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">
     <div th:insert="~{commons/commons::sideBar(active='list.html')}"></div>

 

5.3、员工界面

使用th:each进行遍历

复制代码
 <div class="table-responsive">
    <table class="table table-striped table-sm">
       <thead>
          <tr>
             <th>序号</th>
             <th>名字</th>
             <th>电子邮箱</th>
             <th>性别</th>
             <th>部门</th>
             <th>生日</th>
             <th>操作</th>
          </tr>
       </thead>
       <tbody>
          <tr th:each="emp:${emps}">
             <td th:text="${emp.getEmployeeId()}"></td>
             <td>[[${emp.getLastName()}]]</td>
             <td th:text="${emp.getEmail()}"></td>
             <td th:text="${emp.getGender()==0?'女':'男'}"></td>
             <td th:text="${emp.department.getDepartmentName()}"></td>
             <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
             <td>
                <button class="btn btn-sm btn-primary">编辑</button>
                <button class="btn btn-sm btn-danger">删除</button>
             </td>
          </tr>
       </tbody>
    </table>
 </div>
复制代码

 

展示效果:

 

 

6、添加员工

1、按钮提交

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

2、前端页面

使用post提交表单

复制代码
 <form th:action="@{/emp}" method="post">
     <div class="form-group">
         <label>用户名</label>
         <input type="text" name="lastName" class="form-control" placeholder="admin">
     </div>
     <div class="form-group">
         <label>电子邮箱</label>
         <input type="text" name="email" class="form-control" placeholder="123456@qq.com">
     </div>
     <div class="form-group">
         <label>性别</label>
         <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>部门</label>
         <select class="form-control" name="department.departmentId">
             <!--controller层接收的是一个对象Employee,前端无法提交,所以需要提交的是其中的一个属性-->
             <option th:each="department:${departments}"
                     th:text="${department.getDepartmentName()}"
                     th:value="${department.getDepartmentId()}"></option>
         </select>
     </div>
     <div class="form-group">
         <label>生日</label>
         <!--                     日期格式默认为yyyy/MM/dd-->
         <input type="text" name="birth" class="form-control" placeholder="2001-01-01 00:00:00">
     </div>
     <button type="submit" class="btn btn-primary">添加</button>
 </form>
复制代码

 

3、后端处理

前往add.html页面

使用post方法接收emp

复制代码
 @GetMapping("/emp")
 public String toAddPage(Model model){
     // 查出所有部门的信息
     Collection<Department> departments = departmentDao.getDepartments();
     model.addAttribute("departments",departments);
     return "/emp/add";
 }
 ​
 @PostMapping("/emp")
 public String addEmp(Employee employee){
     System.out.println("save=>"+employee);
     // 调用底层业务方法,保存员工信息
     employeeDao.save(employee);
     return "redirect:/emps";
 }
复制代码

 

显示效果:

其中显示的为默认值,可修改

 

 

7、修改、删除员工

1、按钮提交

 <a class="btn btn-sm btn-primary" th:href="@{/emp/{id}(id=${emp.getEmployeeId()})}">编辑</a>
 <a class="btn btn-sm btn-danger" th:href="@{/deleteEmp/{id}(id=${emp.getEmployeeId()})}">删除</a>

 

2、前端页面

修改员工,提交表单,需要获取对应修改id的信息并展示

复制代码
 <form th:action="@{/updateEmp}" method="post">
 <!--                  防止自增,使用隐藏域添加id进入修改-->
                   <input type="hidden" name="employeeId" th:value="${emp.getEmployeeId()}">
                   <div class="form-group">
                      <label>用户名</label>
                      <input type="text" th:value="${emp.getLastName()}" name="lastName" class="form-control">
                   </div>
                   <div class="form-group">
                      <label>电子邮箱</label>
                      <input type="text" th:value="${emp.getEmail()}" name="email" class="form-control">
                   </div>
                   <div class="form-group">
                      <label>性别</label>
                      <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>部门</label>
                      <select class="form-control" name="department.departmentId">
 <!--                        controller层接收的是一个对象Employee,前端无法提交,所以需要提交的是其中的一个属性-->
                         <option th:selected="${department.getDepartmentId()
                                              ==emp.getDepartment().getDepartmentId()}"
                               th:each="department:${departments}"
                               th:text="${department.getDepartmentName()}"
                               th:value="${department.getDepartmentId()}"></option>
                      </select>
                   </div>
                   <div class="form-group">
                      <label>生日</label>
 <!--                     日期格式默认为yyyy/MM/dd-->
                      <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}" 
                             type="text" name="birth" class="form-control">
                   </div>
                   <button type="submit" class="btn btn-primary">修改</button>
                </form>
复制代码

 

3、后端处理

复制代码
 /**
  * 去员工的修改页面
  * 符合RestFUL风格
  * @param id
  * @param model
  * @return
  */
 @GetMapping("/emp/{id}")
 public String toUpdateEmp(@PathVariable("id")Integer id, Model model){
     // 查出原来的数据
     Employee employeeById = employeeDao.getEmployeeById(id);
     model.addAttribute("emp",employeeById);
     // 查出所有部门的信息
     Collection<Department> departments = departmentDao.getDepartments();
     model.addAttribute("departments",departments);
     return "/emp/update";
 }
 ​
 @PostMapping("/updateEmp")
 public String updateEmp(Employee employee){
     employeeDao.save(employee);
     return "redirect:/emps";
 }
 ​
 /**
  * 删除员工
  * @param id
  * @return
  */
 @GetMapping("/deleteEmp/{id}")
 public String deleteEmp(@PathVariable("id")Integer id){
     employeeDao.delete(id);
     return "redirect:/emps";
 }
复制代码

 

输出效果:

 

 

删除,直接在原页面上直接去掉

8、404处理及注销处理

spring能智能识别resources/templates/error目录(需自行创建),根据各种不同的错误类型,放入对应的404.html、500.html等页面即可自动完成错误页面跳转。

注销只需要销毁session

 <a class="nav-link" th:href="@{/user/logout}">注销</a>

LoginController添加

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

 

注销后会自动返回登录界面

posted on   zrm0612  阅读(164)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示