一、完善登录功能
1.1 问题分析
1.2 代码实现
package com.itheima.reggie.filter;
//这是一个过滤器类
//登录检查过滤器
import com.alibaba.fastjson.JSON;
import com.itheima.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.AntPathMatcher;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 检查用户是否已经完成登录
*/
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
//路径匹配器,支持通配符写法(专门用来路径比较的)
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
/**
* 过滤的方法
* @param servletRequest
* @param servletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
/**
* 1、获取本次请求的URI
* 2、判断本次请求是否需要处理(是否需要检查用户已经登录了)【检查登录状态】
* 3、如果不需要处理,则直接放行
* 4、判断登录状态,如果已登录,则直接放行
* 5、如果未登录则返回未登录结果
*/
//1、获取本次请求的URI
String requestURI = request.getRequestURI();
//日志:拦截到的请求
log.info("拦截到的请求:{}", requestURI);
//2、判断本次请求是否需要处理(是否需要检查用户已经登录了)【检查登录状态】
//定义一些不需要处理的请求路径(直接放行),只拦截针对Controller的请求
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
//判断是否需要处理
boolean check = check(urls, requestURI);
//3、如果不需要处理,则直接放行
//check = true时不需要处理
if (check) {
log.info("本次请求{}不需要处理", requestURI);
//放行
filterChain.doFilter(request, response);
return;
}
//4、判断登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("employee") != null) {
log.info("用户已登录,用户id为{}", request.getSession().getAttribute("employee"));
//放行
filterChain.doFilter(request, response);
return;
}
log.info("用户未登录");
//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return;
}
/**
* 路径匹配,检查本次请求是否需要放行
* @param urls
* @param requestURI
* @return
*/
//封装方法
public boolean check(String[] urls,String requestURI) {
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if (match) {
return true;
}
}
//整个for循环都遍历完了都没有匹配上,就返回false
return false;
}
}
1.3 功能测试
二、新增员工
2.1 需求分析
2.2 数据模型
2.3 代码开发
package com.itheima.reggie.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.itheima.reggie.common.R;
import com.itheima.reggie.entity.Employee;
import com.itheima.reggie.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
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.HttpServletRequest;
import java.time.LocalDateTime;
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
//自动装配
@Autowired
private EmployeeService employeeService;
/**
* 员工登录
* @param request
* @param employee
* @return
*/
//前端发送的请求是 post 请求
@PostMapping("/login")
//接收json数据
//requset对象可以get
public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){
/**
* 1、将页面提交的密码password进行md5的加密处理
* 2、根据页面提交的用户名username查询数据库
* 3、如果没有查询到则返回登录失败的结果
* 4、密码比对,如果不一致则返回登录失败结果
* 5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
* 6、登录成功,将员工id存入Session并返回登录成功结果
*/
// 1、将页面提交的密码password进行md5的加密处理
//从employee中把password拿到
String password = employee.getPassword();
//调用工具类中的md5加密的方法
password = DigestUtils.md5DigestAsHex(password.getBytes());
//2、根据页面提交的用户名username查询数据库
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
//添加查询条件
queryWrapper.eq(Employee::getUsername, employee.getUsername());
//数据库已经对user_name做了唯一约束
Employee emp = employeeService.getOne(queryWrapper);
//3、如果没有查询到则返回登录失败的结果
if(emp == null){
return R.error("登录失败");
}
//4、密码比对,如果不一致则返回登录失败结果
if(!password.equals(emp.getPassword())){
//密码匹配不成功
return R.error("登录失败");
}
//登录成功
//5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
if (emp.getStatus() == 0){
return R.error("账号已经被禁用");
}
//6、登录成功,将员工id存入Session并返回登录成功结果
request.getSession().setAttribute("employee", emp.getId());
//这是我们从数据库中查出来的对象
return R.success(emp);
}
/**
* 退出方法
*/
/**
* 员工退出
* @param request
* @return
*/
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){
//清理Session中保存的当前登录员工的id
request.getSession().removeAttribute("employee");
return R.success("退出成功");
}
/**
* 新增员工
* @param employee
* @return
*/
@PostMapping
public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
log.info("新增员工,员工信息:{}",employee.toString());
//设置初始密码:123456,需要进行md5加密处理。getBytes():设置成getBytes()数组
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
//登录时间和更新时间
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//获得当前登录用户的id
Long empId = (Long) request.getSession().getAttribute("employee");
employee.setCreateUser(empId);
employee.setUpdateUser(empId);
//保存对象
employeeService.save(employee);
//新增员工成功
return R.success("新增员工成功");
}
}
package com.itheima.reggie.common;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.sql.SQLIntegrityConstraintViolationException;
/**
* 全局异常捕获处理
* RestController.class, Controller.class:只有有这两个注解的类都会被我们这个类来处理
*/
@ControllerAdvice(annotations = {RestController.class, Controller.class}) //通知
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex) {
log.error(ex.getMessage());
//判断异常获取信息中是否有:Duplicate entry(重复条目)
if(ex.getMessage().contains("Duplicate entry")){
//根据空格进行分割,把异常信息存储到 split数组中
String[] split = ex.getMessage().split(" ");
//获取数组中已经用户名信息(唯一约束)
String msg = split[2] + "已存在";
//输出错误信息(账户已存在的信息)
//return 把错误信息输出到页面上
return R.error(msg);
}
//显示到页面的信息
return R.error("未知错误");
}
}
2.4 功能测试
2.5 总结
1、根据产品原型明确业务需求
2、重点分析数据的流转过程和数据格式
3、通过debug断点调试跟踪程序执行过程
三、员工信息分页查询
3.1 需求分析
在后台显示界面,一页显示出所有员工信息不利于查看。
解决方法:将员工信息进行分页展示
- 输入框:可以添加过滤条件,在添加过滤条件的同时进行分页处理
- 页码展示、可以跳转到相应的页码、也可直接点击相应的页码
3.2 代码开发
3.2.1 梳理程序执行流程
- 页面发送 ajax 请求,将分页查询参数(page、pageSize、name)提交到服务器
- 服务端 Controller 接收页面提交的数据并调用 Service 查询数据
- Service 调用 Mapper 操作数据库,查询分页数据
- Controller 将查询到的分页数据转成 JSON 响应给页面
- 页面接收到分页数据并通过 ElementUI 的 Table 组件展示到页面上
分页插件的使用:
MyBatisPlus 给我们提供了一个分页插件。
package com.itheima.reggie.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置MybatisPlus 的分页插件,配置类要加 @Configuration 注解
*/
@Configuration
public class MyBatisPlusConfig {
//拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
服务端 Controller 接收页面提交的数据并调用 Service 查询数据
//返回泛型Page,这个是MyBatisPlus 封装的类
//方法中的形参指的是:前端页面传递给我们的值
/**
* 员工信息的分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){
log.info("page = {},pageSize = {},name = {}",page,pageSize,name);
return null;
}
分页查询设置
//返回泛型Page,这个是MyBatisPlus 封装的类
//方法中的形参指的是:前端页面传递给我们的值
/**
* 员工信息的分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){
log.info("page = {},pageSize = {},name = {}",page,pageSize,name);
//底层是基于MyBatisPlus提供的分页插件进行分页
//1、构建分页构造器(分页条件:告诉MyBatisPlus我要查第几页,第几条)
Page pageInfo = new Page(page, pageSize);
//2、构造条件构造器(封装过滤分页条件)
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
//添加过滤条件,like查询
//判断name是否为null,然后再来添加条件
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
//添加排序条件(就是相当于在SQL语句中加一个OrderBy)
queryWrapper.orderByDesc(Employee::getUpdateTime);
//3、执行查询
employeeService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
3.3 功能测试
四、启动 / 禁用员工账号
4.1 需求分析
- 只有管理员(admin 用户)可以对其他普通用户进行启用、禁用操作
- 普通用户登录系统后启用、禁用按钮不显示
- 账户禁用的员工不能登录系统
- 账户启用的员工可以正常登录
- 如果某个员工账户状态为正常,则按钮显示为 “禁用”
- 如果员工账户状态为已禁用,则按钮显示为 “启用”
4.2 代码开发
在开发代码之前,需要梳理一下整个程序的执行流程:
- 页面发送 ajax 请求,将参数(id、status)提交到服务端
- 服务端 Controller 接收页面提交的数据并调用 Service 更新数据
- Service 调用 Mapper 操作数据库
本质:是一个更新操作(Update),修改状态码
启用、禁用(或者是编辑)员工账号,本质上就是一个更新操作,也就是对 status 状态字段进行操作。
在 Controller 中创建 update 方法,此方法是一个通用的修改员工信息的方法。
4.3 功能测试
4.4 代码修复
五、编辑员工信息
5.1 需求分析
在员工管理列表页面点击编辑按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击保存按钮完成编辑操作。
5.2 代码开发
在开发代码之前需要梳理一下操作过程喝对应的程序的执行流程:
- 点击编辑按钮时,页面跳转到 add.html ,并在 url 中携带参数【员工 id】
- 在 add.html 页面获取 url 中的参数【员工 id】
- 发送 ajax 请求,请求服务端,同时提交员工 id 参数
- 服务端接收请求,根据员工 id 查询员工信息,将员工信息以 json 形式响应给页面
- 页面接收服务端响应的 json 数据,通过 VUE 的数据绑定进行员工信息回显
- 点击保存按钮,发送 ajax 请求,将页面中的员工信息以 json 方式提交给服务端
- 服务端接收员工信息,并进行处理,完成后给页面响应
- 页面接收到服务端响应信息后进行相应处理