黑马程序员Java项目实战《瑞吉外卖》 笔记Day2:视频P15-P25
本课程以当前热门的外卖点餐为业务基础,业务真实、实用、广泛。基于流行的Spring Boot、mybatis plus等技术框架进行开发,带领学员体验真实项目开发流程、需求分析过程和代码实现过程。学完本课程能够收获:锻炼需求分析能力、编码能力、bug调试能力,增长开发经验。
- 链接:https://www.bilibili.com/video/BV13a411q753
20230319 Day2:视频P15-25
- 完善登录功能
- 新增员工
- 员工信息分页查询
- 启用/禁用员工账号
- 编辑员工信息
完善登录功能:
问题分析:
前面我们已经完成了后台系统的员工登录功能开发,但是还存在一个问题:用户如果不登录,直接访问系统首页面,照样可以正常访问。
这种设计并不合理,我们希望看到的效果应该是,只有登录成功后才可以访问系统中的页面,如果没有登录则跳转到登录页面。
那么,具体应该怎么实现呢?
答案就是使用过滤器或者拦截器,在过滤器或者拦截器中判断用户是否已经完成登录,如果没有登录则跳转到登录页面。
实现步骤:
1.创建自定义过滤器LoginCheckFilter
2.在启动类上加入注解@ServletComponentScan
3.完善过滤器的处理逻辑
创建filter包,在filter内创建LoginCheckFilter过滤器类,添加@WebFilter注解,设定过滤器名filterName = “LoginCheckFilter”,拦截路径urlPatterns = “/*”(全部拦截 ),继承Filter接口,并实现doFilter方法
package com.raggie.filter;
import lombok.extern.slf4j.Slf4j;
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 {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
}
通过强转,将servletRequest,servletResponse转型为HttpServletRequest和HttpServletResponse,并通过日志输出拦截信息,添加放行指令
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
log.info("拦截到请求:{}",request);
filterChain.doFilter(request,response);
在启动类中添加@ServletComponentScan注解(用于扫描filter)
package com.raggie;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@Slf4j
@SpringBootApplication
@ServletComponentScan
public class ReggieApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieApplication.class, args);
log.info("Spring项目启动成功...");
}
}
运行程序,控制台返回一下信息,说明拦截成功
[nio-8080-exec-1] com.raggie.filter.LoginCheckFilter : 拦截到请求:org.apache.catalina.connector.RequestFacade@286ca83e
[nio-8080-exec-2] com.raggie.filter.LoginCheckFilter : 拦截到请求:org.apache.catalina.connector.RequestFacade@286ca83e
过滤器具体的处理逻辑如下:
1.获取本次请求的URI
String requestURI = request.getRequestURI();
2.判断本次请求是否需要处理
定义一个urls数组,存储不需要处理的访问路径
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"backend/**",
"front/**"
};
定义一个路径匹配器
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
新建一个boolean型的方法check,将定义的urls数组和requestURI传值
public boolean check(String[] urls,String requestURI){
for(String url:urls){
boolean match = PATH_MATCHER.match(url, requestURI);
if(match==true)
return true;
}
return false;
}
3.如果不需要处理,则直接放行
boolean check = check(urls,requestURI);
if(check){
filterChain.doFilter(request,response);
}
4.判断登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("employee")!=null){
filterChain.doFilter(request,response);
return;
}
5.如果未登录则返回未登录结果,通过输出流的方式,向页面响应数据
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
LoginCheckFilter完整代码:
package com.raggie.filter;
import com.alibaba.fastjson.JSON;
import com.raggie.common.R;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
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();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1.获取本次请求的URI
String requestURI = request.getRequestURI();
//定义不需要处理的路径
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
log.info("拦截到请求:{}",request);
//2.判断本次请求是否需要处理
boolean check = check(urls,requestURI);
if(check){
// 3.如果不需要处理,则直接放行
filterChain.doFilter(request,response);
return;
}
//4.判断登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("employee")!=null){
filterChain.doFilter(request,response);
return;
}
//5.如果未登录则返回未登录结果,通过输出流的方式,向页面响应数据
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
}
/**
* 进行路径匹配,检查本次请求是否需要放行
* @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==true)
return true;
}
return false;
}
}
新增员工功能:
- 需求分析
- 数据模型
- 代码开发
- 功能测试
在开发代码之前,需要梳理一下整个程序的执行过程:
1.页面发送ajax请求,将新增员工页面中输入的数据以)json的形式提交到服务端
2.服务端Controller接收页面提交的数据并调用Service将数据进行保存
3.Service调用Mapper操作数据库,保存数据
创建返回值为R的方法save,并将接收到的json数据通过@注解转换类型
@PostMapping
public R<String> save(@RequestBody Employee employee){
return null;
}
设置员工的初始密码为123456的md5加密结果
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
设置员工更新和创建时间为系统时间
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
设置员工创建和更新者为session中存储的employee(需要在参数中添加HttpServletRequest request)
Long empID = (Long) request.getSession().getAttribute("employee");
employee.setCreateUser(empID);
employee.setUpdateUser(empID);
调用employeeService的save方法,并返回R.success
employeeService.save(employee);
return R.success("新增员工成功!");
完整的save代码如下:
@PostMapping
public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
//设置初始密码为123456,进行md5加密处理
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
Long empID = (Long) request.getSession().getAttribute("employee");
employee.setCreateUser(empID);
employee.setUpdateUser(empID);
employeeService.save(employee);
log.info("新增员工,员工信息:{}",employee.toString());
return R.success("新增员工成功!");
}
创建全局异常处理器:
在common包中新建一个GlobalExceptionHandler类,用于捕获全局的异常信息
package com.raggie.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.RestControllerAdvice;
import java.sql.SQLIntegrityConstraintViolationException;
@ControllerAdvice(annotations = {
RestControllerAdvice.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.error(ex.getMessage());
return R.error("操作失败!");
}
}
在exceptionHandler中判断,若错误信息内包含Duplicate entry,则返回xxx已存在
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.error(ex.getMessage());
if(ex.getMessage().contains("Duplicate entry")){
String[] split = ex.getMessage().split(" ");
String msg=split[2]+"已存在!";
return R.error(msg);
}
return R.error("未知错误!");
}
员工信息分页查询:
需求分析:
系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会
以分页的方式来展示列表数据。
代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
1.页面发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端
2.服务端Controller接收页面提交的数据并调用Service查询数据
3.Service调用Mapper操作数据库,查询分页数据
4.Controller将查询到的分页数据响应给页面
5.页面接收到分页数据并通过ElementUl的Table:组件展示到页面上
在config包中新建MyBatisPlusConfig,用于配置MyBatisPlus分页插件
package com.raggie.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;
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
在EmployeeController中新建Page方法,泛型为mybatis提供的实体类Page,通过分析前端请求,需要传入page/pageSize/name三个参数,并添加@GetMapping(“/page”)注解
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
return null;
}
}
构造分页构造器
Page pageinfo=new Page(page,pageSize);
构造条件构造器
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
添加过滤条件
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
添加排序条件
queryWrapper.orderByDesc(Employee::getUpdateTime);
执行查询,并返回结果
employeeService.page(pageinfo,queryWrapper);
return R.success(pageinfo);
page方法完整代码如下:
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
log.info("接收到查询请求,page={},pagesize={},name={}",page,pageSize,name);
//构造分页构造器
Page pageinfo=new Page(page,pageSize);
//构造条件构造器
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
//添加过滤条件
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
//添加排序条件
queryWrapper.orderByDesc(Employee::getUpdateTime);
//执行查询
employeeService.page(pageinfo,queryWrapper);
return R.success(pageinfo);
}
启用/禁用员工账号:
需求分析
在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工 可以正常登录。
需要注意,只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、 禁用按钮不显示。
在EmployeeController中新建update方法,并添加@PutMapping注解
设定employee的UpdateTime和UpdateUser
Long empID = (Long) request.getSession().getAttribute("employee");
employee.setUpdateUser(empID);
employee.setUpdateTime(LocalDateTime.now());
调用employeeService的updateById方法,更新员工信息,并返回成功。
update方法完整代码如下:
@PutMapping
public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
log.info(employee.toString());
Long empID = (Long) request.getSession().getAttribute("employee");
employee.setUpdateUser(empID);
employee.setUpdateTime(LocalDateTime.now());
employeeService.updateById(employee);
log.info("员工信息修改为:{}",employee.toString());
return R.success("员工信息修改成功!");
}
编辑员工信息:
需求分析:
在员工管理列表页面点击编辑按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击保存按
钮完成编辑操作
在开发代码之前需要梳理一下操作过程和对应的程序的执行流程:
1、点击编辑按钮时,页面跳转到add.html,并在url中携带参数[员工id]
2、在add.html页面获取url中的参数[员工id]
3、发送ajax请求,请求服务端,同时提交员工id参数
4、服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
5、页面接收服务端响应的jso数据,通过VUE的数据绑定进行员工信息回显
6、点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端
7、服务端接收员工信息,并进行处理,完成后给页面响应
8、页面接收到服务端响应信息后进行相应处理
在EmployeeController中新建getById方法,传入@PathVariable Long id变量,并添加@GetMapping(“/{id}”)注解
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
return null;
}
调用employeeService的getById方法,查询员工信息,判断其是否为空,并调用R.success返回信息给前端
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
log.info("根据id查询员工信息...");
Employee employee = employeeService.getById(id);
if(employee!=null){
log.info("查询到员工信息为:{}",employee.toString());
return R.success(employee);
}
log.info("未查询到员工信息!");
return R.error("未查询到对应员工信息!");
}