瑞吉外卖第二天

问题分析

前面我们已经完成了后台系统的员工登录功能开发,但是目前还存在一个问题,接下来我们来说明一个这个问题, 以及如何处理。

1). 目前现状

用户如果不登录,直接访问系统首页面,照样可以正常访问。
在这里插入图片描述

2). 理想效果
上述这种设计并不合理,我们希望看到的效果应该 是,只有登录成功后才可以访问系统中的页面,如果没有登录, 访问系统中的任何界面都直接跳转到登录页面。
在这里插入图片描述
那么,具体应该怎么实现呢?

可以使用我们之前讲解过的过滤器、拦截器来实现,在过滤器、拦截器中拦截前端发起的请求,判断用户是否已经完成登录,如果没有登录则返回提示信息,跳转到登录页面。
1.2 思路分析
在这里插入图片描述
过滤器具体的处理逻辑如下:

A. 获取本次请求的URI

B. 判断本次请求, 是否需要登录, 才可以访问

C. 如果不需要,则直接放行

D. 判断登录状态,如果已登录,则直接放行

E. 如果未登录, 则返回未登录结果

如果未登录,我们需要给前端返回什么样的结果呢? 这个时候, 我们可以去看看前端是如何处理的 ?
在这里插入图片描述
1.3 代码实现
拦截器与过滤器的区别:

1 拦截器是属于springmvc体系的,只能拦截controller的请求

2 过滤器是属于servlet体系,可以拦截所有的请求。

1). 定义登录校验过滤器

自定义一个过滤器 LoginCheckFilter 并实现 Filter 接口, 在doFilter方法中完成校验的逻辑。 那么接下来, 我们就根据上述分析的步骤, 来完成具体的功能代码实现
所属包: com.mywork.reggie.filter

package com.mywork.reggie.filters;import com.alibaba.fastjson.JSON;
import com.mywork.reggie.common.R;
import com.sun.org.apache.bcel.internal.generic.RETURN;
import org.springframework.util.AntPathMatcher;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter{//该类专门用于匹配请求路径的。private AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {//1.  由于request对象需要使用到子类特有的方法,所以强制类型转换HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;//2. 获取本次请求的urlString requestURI = request.getRequestURI();  //http://localhost:8080/backend/index.html//3. 定义一个数组存储需要放行的url,判断本次请求是否需要登录权限的。String[] urls = {"/backend/**","/front/**","/employee/login"};boolean flag = checkUrl(urls,requestURI);if(flag){//直接放行filterChain.doFilter(request,response);return;}//4. 如果需要登录权限的,那么从session中取出登录成功标记检查HttpSession session = request.getSession();if(session.getAttribute("employee")!=null){//如果用户已经登录,也可以直接放行filterChain.doFilter(request,response);return;}//5. 如果没有登录,返回数据前端,让前端发生跳转/*目前你使用的是Filter,不是springmvc ,所有如果你需要返回json数据,需要自己转换,*/String json = JSON.toJSONString(R.error("NOTLOGIN"));response.getWriter().write(json);}/*检查本次请求路径是否属于直接放行的资源*/private boolean checkUrl(String[] urls, String requestURI) {//遍历所有放行的路径,是否与本次请求的路径匹配for (String url : urls) {if(antPathMatcher.match(url,requestURI)){return true;}}return false;}}

在这里插入图片描述
2). 开启组件扫描

需要在引导类上, 加上Servlet组件扫描的注解, 来扫描过滤器配置的@WebFilter注解, 扫描上之后, 过滤器在运行时就生效了

package com.mywork.reggie;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;@SpringBootApplication
@MapperScan(basePackages = "com.mywork.reggie.dao")
@ServletComponentScan(basePackages = "com.mywork.reggie.filters")
public class ReggieApplication {public static void main(String[] args) {SpringApplication.run(ReggieApplication.class, args);}
}

@ServletComponentScan 的作用:
在SpringBoot项目中, 在引导类/配置类上加了该注解后, 会自动扫描项目中(当前包及其子包下)的@WebServlet , @WebFilter , @WebListener 注解, 自动注册Servlet的相关组件 ;

功能测试

代码编写完毕之后,我们需要将工程重启一下,然后在浏览器地址栏直接输入系统管理后台首页,然后看看是否可以跳转到登录页面即可。我们也可以通过debug的形式来跟踪一下代码执行的过程。

在这里插入图片描述

对于前端的代码, 也可以进行debug调试。

F12打开浏览器的调试工具, 找到我们前面提到的request.js, 在request.js的响应拦截器位置打上断点

在这里插入图片描述
2. 新增员工
2.1 需求分析

后台系统中可以管理员工信息,通过新增员工来添加后台系统用户。点击[添加员工]按钮跳转到新增页面,如下:
在这里插入图片描述
当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。

2.2 数据模型

新增员工,其实就是将我们新增页面录入的员工数据插入到employee表。employee表中的status字段已经设置了默认值1,表示状态正常。
在这里插入图片描述

需要注意,employee表中对username字段加入了唯一约束,因为username是员工的登录账号,必须是唯一的。
在这里插入图片描述
2.3 程序执行流程

在开发代码之前,我们需要结合着前端页面发起的请求, 梳理一下整个程序的执行过程:
在这里插入图片描述

A. 点击"保存"按钮, 页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端, 请求方式POST, 请求路径 /employee

B. 服务端Controller接收页面提交的数据并调用Service将数据进行保存

C. Service调用Mapper操作数据库,保存数据

2.4 代码实现

在EmployeeController中增加save方法, 用于保存用户员工信息。

  1. EmployeeController 获取登陆者与创建修改时间
package com.mywork.reggie.controller;import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;@RestController
@RequestMapping("/employee")
public class EmployeeController {@Autowiredprivate EmployeeService employeeService;/*** 作用:员工添加*/@PostMappingpublic R save(@RequestBody  Employee employee, HttpSession session){//1. 先从session中取出当前登录的用户Long empId = (Long) session.getAttribute("employee");//2. 补全员工的创建人与修改人employee.setCreateUser(empId);employee.setUpdateUser(empId);//3. 调用service方法进行添加employeeService.save(employee);return R.success("添加成功");}}

EmployeeService

package com.mywork.reggie.service;import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;public interface EmployeeService {//添加员工void save(Employee employee);
}

EmployeeServiceImpl

package com.mywork.reggie.service.impl;import com.mywork.reggie.common.R;
import com.mywork.reggie.dao.EmployeeDao;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;import java.time.LocalDateTime;@Service
public class EmployeeServiceImpl implements EmployeeService{@Autowired(required = false)private EmployeeDao employeeDao;/*添加员工*/@Overridepublic void save(Employee employee) {//1.补全用户的数据employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));//2. 补全创建时间与修改时间、状态employee.setStatus(1);//默认是启用的状态employee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());employeeDao.save(employee);}
}

EmployeeMapper
用#号代表映射的是实体类里的属性名

package com.mywork.reggie.dao;import com.mywork.reggie.entity.Employee;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;public interface EmployeeDao {@Insert("insert into employee values(null,#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})")void save(Employee employee);
}

2.5 功能测试

代码编写完毕之后,我们需要将工程重启, 完毕之后直接访问管理系统首页, 点击 “员工管理” 页面中的 “添加员工” 按钮, 输入员工基本信息, 然后点击 “保存” 进行数据保存, 保存完毕后, 检查数据库中是否录入员工数据。

当我们在测试中,添加用户时, 输入了一个已存在的用户名时,前端界面出现错误提示信息:
在这里插入图片描述
而此时,服务端已经报错了, 报错信息如下:
在这里插入图片描述
出现上述的错误, 主要就是因为在 employee 表结构中,我们针对于username字段,建立了唯一索引,添加重复的username数据时,违背该约束,就会报错。但是此时前端提示的信息并不具体,用户并不知道是因为什么原因造成的该异常,我们需要给用户提示详细的错误信息

2.6 全局异常处理
2.6.1 思路分析

要想解决上述测试中存在的问题,我们需要对程序中可能出现的异常进行捕获,通常有两种处理方式:

A. 在Controller方法中加入 try…catch 进行异常捕获
在这里插入图片描述
如果采用这种方式,虽然可以解决,但是存在弊端,需要我们在保存其他业务数据时,也需要在Controller方法中加上try…catch进行处理,代码冗余,不通用。

B. 使用异常处理器进行全局异常捕获

采用这种方式来实现,我们只需要在项目中定义一个通用的全局异常处理器,就可以解决本项目的所有异常。
2.6.2 全局异常处理器
在这里插入图片描述

在项目中自定义一个全局异常处理器,在异常处理器上加上注解 @ControllerAdvice,可以通过属性annotations指定拦截哪一类的Controller方法。 并在异常处理器的方法上加上注解 @ExceptionHandler 来指定拦截的是那一类型的异常。

  1. 自定义一个异常类,名字已存在的异常类
package com.mywork.reggie.exception;public class NameExistsException extends  RuntimeException {public NameExistsException(String message) {super(message);}
}

修改EmployeeServiceImpl添加方法,添加之前先检查username是否已经存在
在这里插入图片描述

package com.mywork.reggie.service.impl;import com.mywork.reggie.common.R;
import com.mywork.reggie.dao.EmployeeDao;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.exception.NameExistsException;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;import java.time.LocalDateTime;@Service
public class EmployeeServiceImpl implements EmployeeService{@Autowired(required = false)private EmployeeDao employeeDao;/*添加员工*/@Overridepublic void save(Employee employee) {//查询员工的名字是否存在Employee dbEmp = employeeDao.findByName(employee);//如果不为空if(dbEmp!=null){throw  new NameExistsException(employee.getUsername());}//1.补全用户的数据employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));//2. 补全创建时间与修改时间、状态employee.setStatus(1);//默认是启用的状态employee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());employeeDao.save(employee);}
}

自定义 了全局异常处理器

package com.mywork.reggie.exception;import com.mywork.reggie.common.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;/*@RestControllerAdvice 表明该类是一个全局异常处理器。*/
@RestControllerAdvice
public class GlobalExceptionHandler {/*@ExceptionHandler : 用于指定这个方法处理那些类型的异常的。*/@ExceptionHandler(NameExistsException.class)public R exceptionHandler(NameExistsException e){e.printStackTrace(); //打印异常信息,否则控制台没有任何的内容return R.error(e.getMessage()+"已经存在");}/*@ExceptionHandler : 用于指定这个方法处理那些类型的异常的。*/@ExceptionHandler(Exception.class)public R exceptionHandler(Exception e){e.printStackTrace(); //打印异常信息,否则控制台没有任何的内容return R.error("目前访问人数过多,请稍后!");}
}

2.6.3 测试

全局异常处理器编写完毕之后,我们需要将项目重启, 完毕之后直接访问管理系统首页, 点击 “员工管理” 页面中的 “添加员工” 按钮。当我们在测试中,添加用户时, 输入了一个已存在的用户名时,前端界面出现如下错误提示信息:
在这里插入图片描述
3. 员工分页查询
3.1 需求分析

系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。而在我们的分页查询页面中, 除了分页条件以外,还有一个查询条件 “员工姓名”。
在这里插入图片描述

  • 请求参数
    • 搜索条件: 员工姓名(模糊查询)
    • 分页条件: 每页展示条数 , 页码
  • 响应数据
    • 总记录数
    • 结果列表

3.2 程序执行流程
3.2.1 页面流程分析

在开发代码之前,需要梳理一下整个程序的执行过程。

A. 点击菜单,打开员工管理页面时,执行查询:
在这里插入图片描述
B. 搜索栏输入员工姓名,回车,执行查询:

在这里插入图片描述
1). 页面发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端

2). 服务端Controller接收页面提交的数据, 并组装条件调用Service查询数据

3). Service调用Dao操作数据库,查询分页数据

4). Controller将查询到的分页数据, 响应给前端页面

5). 页面接收到分页数据, 并通过ElementUI的Table组件展示到页面上

3.2.2 前端代码介绍

1). 访问员工列表页面/member/list.html时, 会触发Vuejs中的钩子方法, 在页面初始化时调用created方法
在这里插入图片描述
从上述的前端代码中我们可以看到, 执行完分页查询, 我们需要给前端返回的信息中需要包含两项 : records 中封装结果列表, total中封装总记录数 。

而在组装请求参数时 , page、pageSize 都是前端分页插件渲染时的参数;
在这里插入图片描述

2). 在getMemberList方法中, 通过axios发起异步请求
在这里插入图片描述
最终发送给服务端的请求为 : GET请求 , 请求链接 /employee/page?page=1&pageSize=10&name=xxx

3.3 代码实现

pageHelper

介绍

PageHelper是国内非常优秀的一款开源的mybatis分页插件,它支持基本主流与常用的数据库, 例如mysql、 oracle、mariaDB、 DB2、 SQLite、Hsqldb等。

本项目在 github 的项目地址: https://github.com/pagehelper/Mybatis-PageHelper

本项目在 gitosc 的项目地址: http://git.oschina.net/free/Mybatis_PageHelper

自己实现分页的弊端:

  • (1)每一种数据库分页语句不同,我们实现分页需要针对每一种数据库定制符合其语法的SQL,如果更换数据库,需要修改分页SQL。limit top rownum
  • (2)除了这些以外,我们还要定义PageBean,封装分页参数,PageHelper提供了PageInfo对象给你们去使用,这里的PageInfo对象就是你们以前定义pageBean.

3.3.1 mybatis分页插件-pageHelper的使用
导入依赖(已完成)

<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.0</version>
</dependency>

在application.yml文件配置信息

pagehelper:#使用的数据库helper-dialect: mysql#合理化分页#如果查询页数小于实际页数则查询第一页,如果超出则查询最后一页reasonable: true

测试(pageHelper的基本使用)

EmployeeDao(添加一个findByName方法)

package com.mywork.reggie.dao;import com.mywork.reggie.entity.Employee;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;import java.util.List;public interface EmployeeDao {/*分页的时候name的参数是不一定传递的,所以我们需要使用动态sql,一旦使用动态sql,即使只有一个参数也需要使用参数的名字*/List<Employee>  findByPage(@Param("name") String name);}

EmployeeMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.mywork.reggie.dao.EmployeeDao"><select id="findByPage" resultType="employee">select * from employee<where><if test="name!=null and name!=''">name like concat('%',#{name},'%')</if></where></select>
  1. 测试类
  package com.mywork.reggie.test;import com.github.pagehelper.PageHelper;import com.github.pagehelper.PageInfo;import com.mywork.reggie.dao.EmployeeDao;import com.mywork.reggie.entity.Employee;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTestpublic class PageTest {@Autowiredprivate EmployeeDao employeeDao;/*pageHelper的用法步骤:1. 设置当前页与页面大小2. 查询页面的数据3. 创建PageInfo对象,PageINfo封装了页面的所有数据,相当于以前的PageBean对象*/@Testpublic void test01(){//        1. 设置当前页与页面大小PageHelper.startPage(1,2);//        2. 查询页面的数据   limit (curPgae-1)*pageSize , pagesizeList<Employee> empList = employeeDao.findByPage(null);//        3. 创建PageInfo对象,PageINfo封装了页面的所有数据,相当于以前的PageBean对象PageInfo<Employee> pageInfo = new PageInfo<>(empList);System.out.println("当前页:"+pageInfo.getPageNum());System.out.println("页面大小:"+pageInfo.getPageSize());System.out.println("总记录数:"+pageInfo.getTotal());System.out.println("总页数:"+pageInfo.getPages());System.out.println("页面的数据:"+pageInfo.getList());}}

pagehelp小结
1.设置当前页与页面大小
2.查询页面的数据
3.创建PageInfo对象,把List对象传入即可

3.3.2 分页查询实现

在上面我们已经分析了,页面在进行分页查询时, 具体的请求信息如下:

  请求  	说明                    请求方式	GET                   请求路径	/employee/page        请求参数	page , pageSize , name

那么查询完毕后我们需要给前端返回什么样的结果呢?

在上述我们也分析了, 查询返回的结果数据data中应该封装两项信息, 分别为: records 封装分页列表数据, total 中封装符合条件的总记录数。 那我们则需要自定义一个Page类必须包含records 与total两个属性的

Page实体类的编写

package com.mywork.reggie.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;@Data
@NoArgsConstructor
@AllArgsConstructor
public class Page<T>  {protected List<T> records;protected long total;protected long pageSize;protected long page;}

EmployeeController控制器

package com.mywork.reggie.controller;import com.github.pagehelper.PageInfo;
import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;@RestController
@RequestMapping("/employee")
public class EmployeeController {@Autowiredprivate EmployeeService employeeService;/*** 作用:员工列表分页*/@GetMapping("/page")public R  page(@RequestParam(defaultValue = "1") Integer page,@RequestParam(defaultValue = "10") Integer pageSize,String name){Page<Employee> pageBean =  employeeService.findByPage(page,pageSize,name);return R.success(pageBean);}}

EmployeeService接口

package com.mywork.reggie.service;import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;public interface EmployeeService {/*员工分页列表*/Page<Employee> findByPage(Integer page, Integer pageSize, String name);
}

EmployeeServiceImpl接口实现类
推荐安装使用GeneralAllSetter,在类中按alt+enter使用

package com.mywork.reggie.service.impl;import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mywork.reggie.common.R;
import com.mywork.reggie.dao.EmployeeDao;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;
import com.mywork.reggie.exception.NameExistsException;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;import java.time.LocalDateTime;
import java.util.List;@Service
public class EmployeeServiceImpl implements EmployeeService{@Autowired(required = false)private EmployeeDao employeeDao;/*员工分页列表*/@Overridepublic Page<Employee> findByPage(Integer page, Integer pageSize, String name) {//1.  设置当前页与页面大小PageHelper.startPage(page,pageSize);//2.  查询当前页的数据List<Employee> employeeList = employeeDao.findByPage(name);//3.  创建PageInfo对象,把List传入。PageInfo<Employee> pageInfo = new PageInfo<>(employeeList);//4.  创建Page对象,然后把PageInfo的数据封装到page对象。Page<Employee> pageResult = new Page<>(); //alt+enterpageResult.setRecords(pageInfo.getList()) ;//页面的数据pageResult.setTotal(pageInfo.getTotal()); //总记录数pageResult.setPageSize(pageInfo.getPageSize()); //页面大小pageResult.setPage(pageInfo.getPageNum()); //当前页return pageResult;}
}

EmployeeMapper文件

package com.mywork.reggie.dao;import com.mywork.reggie.entity.Employee;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;import java.util.List;public interface EmployeeDao {/*分页的时候name的参数是不一定传递的,所以我们需要使用动态sql,一旦使用动态sql,即使只有一个参数也需要使用参数的名字*/List<Employee>  findByPage(@Param("name") String name);}

EmployeeMapper.xml映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.mywork.reggie.dao.EmployeeDao"><select id="findByPage" resultType="employee">select * from employee<where><if test="name!=null and name!=''">name like concat('%',#{name},'%')</if></where></select></mapper>

3.4 功能测试

代码编写完毕之后,我们需要将工程重启, 完毕之后直接访问管理系统首页, 默认就会打开员工管理的列表页面, 我们可以查看列表数据是否可以正常展示, 也可以通过分页插件来测试分页功能, 及员工姓名的模糊查询功能。

在进行测试时,可以使用浏览器的监控工具查看页面和服务端的数据交互细节。 并借助于debug的形式, 根据服务端参数接收及逻辑执行情况。
在这里插入图片描述

测试过程中可以发现,对于员工状态字段(status)服务端返回的是状态码(1或者0),但是页面上显示的则是“正常”或者“已禁用”,这是因为页面中在展示数据时进行了处理。
在这里插入图片描述
4. 启用/禁用员工账号
4.1 需求分析

在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。如果某个员工账号状态为正常,则按钮显示为 “禁用”,如果员工账号状态为已禁用,则按钮显示为"启用"。

需要注意,只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示。

A. admin 管理员登录
在这里插入图片描述

B. 普通用户登录
在这里插入图片描述
4.2 程序执行流程
4.2.1 页面按钮动态展示

在上述的需求中,我们提到需要实现的效果是 : 只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示 , 页面中是怎么做到只有管理员admin能够看到启用、禁用按钮的?

1). 在列表页面(list.html)加载时, 触发钩子函数created, 在钩子函数中, 会从localStorage中获取到用户登录信息, 然后获取到用户名
在这里插入图片描述
2). 在页面中, 通过Vue指令v-if进行判断,如果登录用户为admin将展示 启用/禁用 按钮, 否则不展示
在这里插入图片描述
4.2.2 执行流程分析

1). 当管理员admin点击 “启用” 或 “禁用” 按钮时, 调用方法statusHandle
在这里插入图片描述
2). statusHandle方法中进行二次确认, 然后发起ajax请求, 传递id、status参数
在这里插入图片描述

最终发起异步请求, 请求服务端, 请求信息如下:

请求 说明
请求方式 PUT
请求路径 /employee
请求参数 {“id”:xxx,“status”:xxx}

{…params} : 三点是ES6中出现的扩展运算符。作用是遍历当前使用的对象能够访问到的所有属性,并将属性放入当前对象中。

4.3 代码实现

在开发代码之前,需要梳理一下整个程序的执行过程:

1). 页面发送ajax请求,将参数(id、status)提交到服务端

2). 服务端Controller接收页面提交的数据并调用Service更新数据

3). Service调用Dao操作数据库

启用、禁用员工账号,本质上就是一个更新操作,也就是对status状态字段进行操作。在Controller中创建update方法,此方法是一个通用的修改员工信息的方法。
代码实现

  1. EmployeeController
package com.mywork.reggie.controller;import com.github.pagehelper.PageInfo;
import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;@RestController
@RequestMapping("/employee")
public class EmployeeController {@Autowiredprivate EmployeeService employeeService;/*** 作用:修改员工信息*/@PutMappingpublic R  updateEmp(@RequestBody Employee employee,HttpSession session){//补全修改人//1.先从session中取出当前登录的用户Long empId = (Long) session.getAttribute("employee");//2. 补全员工的创建人与修改人employee.setUpdateUser(empId);employeeService.updateEmp(employee);return R.success("修改成功");}}

EmployeeService

package com.mywork.reggie.service;import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;public interface EmployeeService {/*修改员工信息*/void updateEmp(Employee employee);
}

EmployeeServiceImp

package com.mywork.reggie.service.impl;import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mywork.reggie.common.R;
import com.mywork.reggie.dao.EmployeeDao;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;
import com.mywork.reggie.exception.NameExistsException;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;import java.time.LocalDateTime;
import java.util.List;@Service
public class EmployeeServiceImpl implements EmployeeService{@Autowired(required = false)private EmployeeDao employeeDao;@Overridepublic void updateEmp(Employee employee) {employee.setUpdateTime(LocalDateTime.now());employeeDao.updateEmp(employee);}
}

EmployeeMapper

package com.mywork.reggie.dao;import com.mywork.reggie.entity.Employee;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;import java.util.List;public interface EmployeeDao {/*修改员工信息 ,不管你是修改员工的状态还是修改员工的基本信息都可以使用这个方法,由于修改不同信息传递的字段不一样,所以这里使用动态sql*/void updateEmp(Employee employee);
}

EmployeeMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.mywork.reggie.dao.EmployeeDao"><!--set标签的作用: 1.帮你添加set的关键字,2.去除最后一个逗号--><update id="updateEmp">update employee<set><if test="username!=null and username!=''">username=#{username},</if><if test="name!=null and name!=''">name=#{name},</if><if test="password!=null and password!=''">password=#{password},</if><if test="phone!=null and phone!=''">phone=#{phone},</if><if test="sex!=null and sex!=''">sex=#{sex},</if><if test="idNumber!=null and idNumber!=''">id_number=#{idNumber},</if><if test="status!=null">status=#{status},</if><if test="updateTime!=null">update_time=#{updateTime},</if><if test="updateUser!=null">update_user=#{updateUser},</if></set>where id=#{id}</update>
</mapper>

4.4 功能测试

代码编写完毕之后,我们需要将工程重启。 然后访问前端页面, 进行 “启用” 或 “禁用” 的测试
在这里插入图片描述

测试过程中没有报错,但是功能并没有实现,查看数据库中的数据也没有变化。但是从控制台输出的日志, 可以看出确实没有更新成功。
在这里插入图片描述
而在我们的数据库表结构中, 并不存在该ID, 数据库中 风清扬 对应的ID为 1420038345634918401
在这里插入图片描述
4.5 代码修复
4.5.1 原因分析

通过观察控制台输出的SQL发现页面传递过来的员工id的值和数据库中的id值不一致,这是怎么回事呢?

在这里插入图片描述

在分页查询时,服务端会将返回的R对象进行json序列化,转换为json格式的数据,而员工的ID是一个Long类型的数据,而且是一个长度为 19 位的长整型数据, 该数据返回给前端是没有问题的。
在这里插入图片描述
那么具体的问题出现在哪儿呢?

问题实际上, 就出现在前端JS中, js在对长度较长的长整型数据进行处理时, 会损失精度, 从而导致提交的id和数据库中的id不一致。 这里,我们也可以做一个简单的测试,代码如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script>alert(1420038345634918401);</script>
</head>
<body>
</body>
</html>

4.5.2 解决方案

要想解决这个问题,也很简单,我们只需要让js处理的ID数据类型为字符串类型即可, 这样就不会损失精度了。同样, 大家也可以做一个测试:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script>alert("1420038345634918401");</script>
</head>
<body>
</body>
</html>

那么在我们的业务中, 我们只需要让分页查询返回的json格式数据库中, long类型的属性, 不直接转换为数字类型, 转换为字符串类型就可以解决这个问题了 , 最终返回的结果为 :
在这里插入图片描述
4.5.3 代码修复

由于在SpringMVC中, 将Controller方法返回值转换为json对象, 是通过jackson来实现的, 涉及到SpringMVC中的一个消息转换器MappingJackson2HttpMessageConverter, 所以我们要解决这个问题, 就需要对该消息转换器的功能进行拓展。

具体实现步骤:

1). 提供对象转换器JacksonObjectMapper,基于Jackson进行Java对象到json数据的转换(资料中已经提供,直接复制到项目中使用)

2). 在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到json数据的转换

1). 引入JacksonObjectMapper

package com.mywork.reggie.config;import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;/*** 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]*/
public class JacksonObjectMapper extends ObjectMapper {public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";public JacksonObjectMapper() {super();//收到未知属性时不报异常this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);//反序列化时,属性不存在的兼容处理this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);SimpleModule simpleModule = new SimpleModule().addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))).addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))).addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))).addSerializer(BigInteger.class, ToStringSerializer.instance).addSerializer(Long.class, ToStringSerializer.instance).addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))).addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))).addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));//注册功能模块 例如,可以添加自定义序列化器和反序列化器this.registerModule(simpleModule);}
}

该自定义的对象转换器, 主要指定了, 在进行json数据序列化及反序列化时, LocalDateTime、LocalDate、LocalTime的处理方式, 以及BigInteger及Long类型数据,直接转换为字符串。
2). 在WebMvcConfig中重写方法extendMessageConverters

/*** 扩展mvc框架的消息转换器* @param converters*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {log.info("扩展消息转换器...");//创建消息转换器对象MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();//设置对象转换器,底层使用Jackson将Java对象转为jsonmessageConverter.setObjectMapper(new JacksonObjectMapper());//将上面的消息转换器对象追加到mvc框架的转换器集合中converters.add(0,messageConverter);
}

5. 编辑员工信息
5.1 需求分析

在员工管理列表页面点击 “编辑” 按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击 “保存” 按钮完成编辑操作。
在这里插入图片描述

那么从上述的分析中,我们可以看出当前实现的编辑功能,我们需要实现两个方法:

A. 根据ID查询, 用于页面数据回显

B. 保存修改

5.2 程序执行流程

在开发代码之前需要梳理一下操作过程和对应的程序的执行流程:

1). 点击编辑按钮时,页面跳转到add.html,并在url中携带参数[员工id]
在这里插入图片描述
2). 在add.html页面获取url中的参数[员工id]

3). 发送ajax请求,请求服务端,同时提交员工id参数

4). 服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
在这里插入图片描述
5.3 代码实现
5.3.1 根据ID查询

经过上述的分析,我们看到,在根据ID查询员工信息时,请求信息如下:
在这里插入图片描述
代码实现:

  1. 在EmployeeController中增加方法, 根据ID查询员工信息。
package com.mywork.reggie.controller;import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;@RestController
@RequestMapping("/employee")
public class EmployeeController {@Autowiredprivate EmployeeService employeeService;/*** 作用:根据id查询员工的信息*/@GetMapping("/{id}")public R  findById(@PathVariable  Long id){Employee employee =  employeeService.findById(id);return R.success(employee);}}

EmployeeService

package com.mywork.reggie.service;import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;public interface EmployeeService {/*** 根据id查找员工* @param id* @return*/Employee findById(Long id);
}

EmployeeServiceImpl

package com.mywork.reggie.service.impl;import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mywork.reggie.common.R;
import com.mywork.reggie.dao.EmployeeDao;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;
import com.mywork.reggie.exception.NameExistsException;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;import java.time.LocalDateTime;
import java.util.List;@Service
public class EmployeeServiceImpl implements EmployeeService{@Autowired(required = false)private EmployeeDao employeeDao;/*** 根据id查找员工* @param id* @return*/@Overridepublic Employee findById(Long id) {Employee employee = employeeDao.findById(id);return employee;}
}

EmployeeMapper

package com.mywork.reggie.dao;import com.mywork.reggie.entity.Employee;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;import java.util.List;public interface EmployeeDao {@Select("select * from employee where id = #{id}")Employee findById(Long id);
}

5.3.2 修改员工(已完成不需要再写)

经过上述的分析,我们看到,在修改员工信息时,请求信息如下:
在这里插入图片描述

5.4 功能测试

代码编写完毕之后,我们需要将工程重启。 然后访问前端页面, 按照前面分析的操作流程进行测试,查看数据是否正常修改即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/127850.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redux中间件源码解析与实现

基本介绍 本文中涉及到的关键npm包的版本信息如下&#xff1a; react 的版本为18.2.0 redux的版本为4.1.2 redux-thunk版本为2.4.2 redux-promise版本为0.6.0 redux-logger版本为3.0.6 在Redux源码解析与实现&#xff08;一&#xff09;Redux源码解析与实现&#xff08;二&…

基于任务队列的机器学习服务实现

将机器模型部署到生产环境的方法有很多。 常见的方法之一是将其实现为 Web 服务。 最流行的类型是 REST API。 它的作用是全天候&#xff08;24/7&#xff09;部署和运行&#xff0c;等待接收来自客户端的 JSON 请求&#xff0c;提取输入&#xff0c;并将其发送到 ML 模型以预测…

词!自然语言处理之词全解和Python实战!

目录 一、为什么我们需要了解“词”的各个方面词是语言的基础单位词的多维特性词在NLP应用中的关键作用 二、词的基础什么是词&#xff1f;定义分类 词的形态词根、词干和词缀形态生成 词的词性 三、词语处理技术词语规范化定义方法 词语切分&#xff08;Tokenization&#xff…

网络安全合规-DSMM

DSMM&#xff08;Data Security Management Model&#xff09;是一种数据安全管理模型。该模型以数据为中心&#xff0c;从数据的生命周期入手&#xff0c;从数据发布、使用、共享、存储、删除等几个方面来管理数据安全。 DSMM提供了一些有效的数据安全管理原则和策略&#xf…

用通俗易懂的方式讲解大模型分布式训练并行技术:数据并行

近年来&#xff0c;随着Transformer、MOE架构的提出&#xff0c;使得深度学习模型轻松突破上万亿规模参数&#xff0c;传统的单机单卡模式已经无法满足超大模型进行训练的要求。因此&#xff0c;我们需要基于单机多卡、甚至是多机多卡进行分布式大模型的训练。 而利用AI集群&a…

文生图模型进化简史和生成能力比较——艺术肖像篇

很久没有更新文章&#xff0c;最近真的太忙啦&#xff0c;在T2I领域&#xff0c;学习速度真的赶不上进化速度&#xff01;每天都有无数新模型、新插件、新玩法涌现。玩得太上瘾啦。 上月初我去参加我硕士专业的夏季烧烤大趴&#xff0c;跟我的论文导师重逢&#xff08;好多年没…

医院信息化、数字医学影像、DICOM、PACS源码

PACS系统适合卫生院、民营医院、二甲或以下公立医院的放射科、超声科使用。功能强大且简洁&#xff0c;性能优异&#xff0c;具备MPR&#xff08;三维重建&#xff09;、VR&#xff08;容积重建&#xff09;、胶片打印功能&#xff0c;能够快速部署。 PACS系统支持DR、CT、磁共…

Kafka入门,这一篇就够了(安装,topic,生产者,消费者)

目录 Kafka的安装文件与配置目录binconfig 配置文件server.propertiesproducer.propertiesconsumer.properties 命令行简单使用kafka-topics.sh新增查看列表查看详情修改删除 kafka-console-producer.shkafka-console-consumer.sh 概念集群代理broker主题topic分区partition偏移…

android 车载widget小部件部分详细源码实战开发-千里马车载车机framework开发实战课程

官网参考链接&#xff1a;https://developer.android.google.cn/develop/ui/views/appwidgets/overview 1、什么是小部件 App widgets are miniature application views that can be embedded in other applications (such as the home screen) and receive periodic updates…

什么是Linux

什么是Linux&#xff1f; 不知道大家是什么时候开始接触Linux&#xff0c;我记得我是大三的时候&#xff0c;那时候通过国嵌、韦东山的教学视频&#xff0c;跟着搭bootloader&#xff0c;修改内核&#xff0c;制作根文件系统&#xff0c;一步步&#xff0c;视频真的很简单&…

GRU门控循环单元

GRU 视频链接 https://www.bilibili.com/video/BV1Pk4y177Xg?p23&spm_id_frompageDriver&vd_source3b42b36e44d271f58e90f86679d77db7Zt—更新门 Rt—重置门 控制保存之前一层信息多&#xff0c;还是保留当前神经元得到的隐藏层的信息多。 Bi-GRU GRU比LSTM参数少 …

大数据Flink(七十四):SQL的滑动窗口(HOP)

文章目录 SQL的滑动窗口(HOP) SQL的滑动窗口(HOP) 滑动窗口定义:滑动窗口也是将元素指定给固定长度的窗口。与滚动窗口功能一样,也有窗口大小的概念。不一样的地方在于,滑动窗口有另一个参数控制窗口计算的频率(滑动窗口滑动的步长)。因此,如果滑动的步长小于窗口大…

10.Xaml ListBox控件

1.运行界面 2.运行源码 a.Xaml 源码 <Grid Name="Grid1"><!--IsSelected="True" 表示选中--><ListBox x:Name="listBo

0003号因子测试结果、代码和数据

这篇文章共分为四个部分:第一个部分是因子测试结果,第二个部分是因子逻辑,第三个部分是因子代码,第四个部分是整个因子测试用的数据、代码、分析结果的下载地址。 因子测试结果: 因子描述 因子属性-量价因子因子构建:计算成交量的变化率和日振幅率,计算两者在过去一定…

LASSO回归

LASSO回归 LASSO(Least Absolute Shrinkage and Selection Operator&#xff0c;最小绝对值收敛和选择算子算法)是一种回归分析技术&#xff0c;用于变量选择和正则化。它由Robert Tibshirani于1996年提出&#xff0c;作为传统最小二乘回归方法的替代品。 损失函数 1.线性回…

MySQL学习5:事务、存储引擎

事务 简介 事务是一组数据库操作的执行单元&#xff0c;它要么完全执行&#xff0c;要么完全不执行。事务是确保数据库中的数据一致性和完整性的重要机制之一。 事务具有以下四个特性&#xff08;称为ACID特性&#xff09;&#xff1a; 原子性&#xff08;Atomicity&#xf…

将 ordinals 与 比特币智能合约集成 : 第 1 部分

将序数与比特币智能合约集成&#xff1a;第 1 部分 最近&#xff0c;比特币序数在区块链领域引起了广泛关注。 据称&#xff0c;与以太坊 ERC-721 等其他代币标准相比&#xff0c;Ordinals 的一个主要缺点是缺乏对智能合约的支持。 我们展示了如何向 Ordinals 添加智能合约功…

Spring Boot 中使用 Poi-tl 渲染数据并生成 Word 文档

本文 Demo 已收录到 demo-for-all-in-java 项目中&#xff0c;欢迎大家 star 支持&#xff01;后续将持续更新&#xff01; 前言 产品经理急冲冲地走了过来。「现在需要将按这些数据生成一个 Word 报告文档&#xff0c;你来安排下」 项目中有这么一个需求&#xff0c;需要将用户…

MySQL——主从复制

简介 在实际的生产中&#xff0c;为了解决Mysql的单点故障已经提高MySQL的整体服务性能&#xff0c;一般都会采用「主从复制」。 主从复制开始前有个前提条件&#xff1a;两边的数据要一样&#xff0c;主必须开启二进制日志 dump thread 线程 基于位置点从是否需要开启二进…

Codeforces Round 895 (Div. 3)

Codeforces Round 895 (Div. 3) A. Two Vessels 思路&#xff1a; 我们可以发现当在 a 拿 c 到 b 其实可以让他们差值减少 2c&#xff0c;所以对a和b的差值除以2c向上取整即可 #include<bits/stdc.h> using namespace std; #define int long long #define rep(i,a,n) …