今天主要完善以下功能:
- 拦截页面(页面拦截功能,在这里用的是过滤器实现的)
- 添加员工功能
项目结构
1.页面拦截功能
filter——LoginCheckFilter类
@Slf4j
@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*")
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();
log.info("拦截到URI------> {}",requestURI);
//定义不需要处理的请求路径
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
//静态资源可以放行,我们过滤的是这些页面上的动态数据
"/backend/**",
"/front/**"
};
//如果我们请求的路径是/backend/index.html这类的,他和上面放行的路径/backend/**不匹配
//这时就要用到路径匹配器
//2.判断本次请求是否需要处理
boolean check = check(requestURI, urls);
//3.如果不需要处理,则直接放行
if (check){
log.info("------本次请求{}不需要处理------",requestURI);
filterChain.doFilter(request,response);
return;
}
//4.判断登陆状态,如果已登录,则直接放行
if (request.getSession().getAttribute("selectEmployee") != null){
log.info("用户已登录,id为------>{}",request.getSession().getAttribute("selectEmployee"));
filterChain.doFilter(request,response);
return;
}
//5.如果没有登陆,则返回未登录结果
//这里的页面跳转,前端已经做了(响应拦截器),后端只需要给前端响应数据,前端就能自己做判断,并执行页面跳转(request.js)
//注意这里,为什么之前controller层中直接return数据,而这里要用流的方式输出?因为这个方法的返回值为void
log.info("用户未登陆");
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
}
/**
* 路径匹配
* @param requestURI
* @param urls
* @return
*/
public boolean check(String requestURI,String[] urls){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if (match){
return true;
}
}
return false;
}
}
介绍下这个类中的重点:
@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*") 表明这个类是个filter,并且设置filter的名称和过滤路径- 有了上面的注解之后,我们还需要在springboot的启动类上添上如下注解:
@ServletComponentScan 有了这个注解之后,在filter,servlet,listener上使用@WebFilter这个注解之后,可以自动完成注册- 在这个类中有一个叫check的方法,用来进行路径匹配,就是用从前端获取的URI和自己设置的放行的路径进行比较,若成功匹配,就返回true,否则返回false
单独讲解下,用户没有登录的情况的程序,这个要结合前端代码来看,如下:
在前后端分离的项目里,后端只管数据Model,前端来管控制视图跳转View,上面的代码可以看出,要想进行试图跳转,必须满足两个条件,一个是code===0,这个在我们通用类R中封装的有这种情况,若返回R.error,里面的code默认是0,另一个是msg==='NOTLOGIN',这个需要后端自己传值,但要注意的是,doFilter这个方法的返回值是void,因此我们要用getWrite()来给前端传值
2.添加员工功能
前端分析
在前后端分离的项目中,后端一定要学会看前端的代码,请求,返回值等等,然后照着前端的接口来写程序,下面以添加员工为例来说明
进入添加员工的页面,我们输入了员工数据之后,network会显示相关的请求,从这里我们就能获取到该功能的请求url和method,有了这两个东西,我们才能规范的编写controller层的代码
看了请求携带的参数之后,我们才能规范的编写SQL语句(知道了前端参数和数据库字段的对应关系)
看了js代码,我们得知了,当添加员工成功时,要给前端返回code===1,所有controller层的返回值要用通用类中的R.success(这个方法中设置了code为1)
controller——EmployeeController类
//添加员工功能
@PostMapping
public R<String> addEmployee(@RequestBody Employee employee){
System.out.println("准备执行");
//设置初始密码123456,需要进行md5加密
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
//设置用户创建和修改的时间
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//设置创建和修改人(因为视频里的这两个属性是用的mybatis-plus,这里我自己做了修改)
employee.setCreateUser(1L);
employee.setUpdateUser(1L);
//设置id
Random random = new Random();
Long i = (long) random.nextInt(1000);
employee.setId(i);
employeeService.addEmployee(employee);
log.info("员工信息:{}",employee.toString());
return R.success("添加成功");
}
common——GlobalExceptionHandler类
数据库里的username字段设置的是唯一unique,当我们在添加员工的时候,如果出现用户名重复的情况,数据库就会抛出异常SQLIntegrityConstraintViolationException(其实id也设置了唯一,在视频里采用的是mybatis-plus,人家在设置id时用了雪花算法,而我用的是mybatis,所以上面的代码,采用的是随机数,而不是给它写死的),所以我们要来处理这个数据库抛出来的异常
在common包下添加一个处理异常的通用类
着重介绍下面的程序
@ControllerAdvice 用来拦截Controller的注解,它默认不写参数,表示拦截所有的controller,我们也可以给他添加某个包名,让其扫描指定包下的所有controller,也可以像下面的程序这样,给他指定扫描controller(下面的代码意思是,让它扫描所有的controller和所有的RestController) @ResponseBody 用来返回Json格式的数据 @ExceptionHandler 处理指定的异常
@ControllerAdvice(annotations = {RestController.class,Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException exception){
log.info(exception.getMessage());
//抛出的异常为Duplicate entry 'zhangsan' for key 'employee.idx_username'
//如果抛的异常里有"Duplicate entry"就进入if
if (exception.getMessage().contains("Duplicate entry")){
//将我们获取的异常按空格为分隔符封装成数组
//比如这个数组的0号下标对应的字符串是Duplicate,1号下标对应的是entry
String[] split = exception.getMessage().split(" ");
String msg = split[2] + "已存在";
return R.error(msg);
}
return R.error("未知错误");
}
}
除了上面的注解和方法要学习之外,我们也要学习这样的处理方法,如果不这样除了,我可能会将输入的用户名和遍历数据库后得到的username做对比,如果一样再返回给前端对应的信息,这样的方式和上面的处理方式一对比后,就显得很烂了
3.分页查询功能(Mybatis)
- 原本一直在用mybatis进行项目开发,但是在这个功能完成的时候,因为controller层的返回数据的类型是人家mybatisplus封装好的,这下不得不用mybatisplus去开发了,但是基于mybatis的代码我还是放下面了,可以参考参考>_<
前端代码分析
当我进入到员工列表页面时,前端就会发送给一个请求,里面携带了两个路径参数page和pageSize,并且告知了后端请求的url,还有请求方式为GET
当我在搜索框里输入员工姓名并点击查询时,前端又会传来一个叫做name的路径参数
后端就需要根据这些来完善相关代码,如下:
mapper——EmployeeMapper接口
//查询所有员工
List<Employee> selectAllEmployee(int page,int pageSize,String name);
mapper——EmployeeMapper.xml映射文件
可以看看这里的SQL怎么来写的,由于刚才的前端分析可知name这个参数前端传了才有,没传就没有,所以SQL编写的时候就要加一个if判断,来动态添加name属性
<select id="selectAllEmployee" resultType="employee">
select * from employee
<if test="name != null">
where name like concat('%',#{name},'%')
</if>
limit #{page},#{pageSize}
</select>
这里Service层就省略吧,有点懒了说实话,啊哈哈哈哈>_<
controller——EmployeeController类
//分页查询功能
@GetMapping("/page")
public R<Employee> page(int page,int pageSize,String name){
log.info("page = {},pageSize = {},name = {}",page,pageSize,name);
page = (page - 1) * pageSize;
List<Employee> employees = employeeService.selectAllEmployee(page, pageSize,name);
for (Employee employee : employees) {
System.out.println(employee);
}
return null;
}
有以下几点需要学习:
- 通过GET请求的方式,后端可以直接获得路径参数(注意:如果是restful风格的GET请求,则要在参数前加上@PathVariable注解),具体可以看看这个作者写的文章:
- https://blog.csdn.net/CJPSR/article/details/131094717
- 这里我们把前端传的路径参数里的三个值取出来了,并且做了分页查询的逻辑处理,再让其作为参数传到分页查询的方法里
4.分页查询功能(MybatisPlus)
前端代码已经分析过了,接下来只是用mybatisplus来实现这个功能
准备工作:
- 导入依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency>
- mapper层接口继承BaseMapper<>
@Mapper @Repository public interface EmployeePlusMapper extends BaseMapper<Employee> { }
- service层接口继承IService<>,实现类继承ServiceImpl<>
public interface EmployeePlusService extends IService<Employee> { }
@Service public class EmployeePlusServiceImpl extends ServiceImpl<EmployeePlusMapper, Employee> implements EmployeePlusService { }
PS:之前用的是mybatis,现在换成了mybatisplus之后,配置文件里的格式要改一改
#mybatis.configuration.map-underscore-to-camel-case=true
#
#mybatis.type-aliases-package=com.itheima.entity
#
#mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.type-aliases-package=com.itheima.entity
mybatis-plus.mapper-locations=classpath:mybatis/mapper/*.xml
controller——EmployeeController类
@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> wrapper = new LambdaQueryWrapper<>();
if (name != null){
wrapper.like(Employee::getName,name);
}
//添加排序条件
wrapper.orderByDesc(Employee::getUpdateTime);
//执行查询
employeePlusService.page(pageInfo,wrapper);
return R.success(pageInfo);
}
config——MybatisPlusConfig类
这个类的作用是导入mybatisplus分页查询的插件
/**
* 导入MybatisPlus分页查询的插件
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
5.启用和禁用员工账号
首先,只有admin管理员才能有启用和禁用员工的按钮,普通员工没有这个功能按钮,这是怎么实现的呢?
在list.html文件里有这样的程序:
如果user为admin,那么就展现下面的禁用和启用的按钮,否则就不展现,那么这个user是哪来的呢?
同样的,在list.html中有create的钩子函数:
将key值为userInfo的键值对中的username转换成Json格式并且给user,这个userInfo就是我们在实现登录功能时,如果登陆成功,就将这个用户信息存在userInfo这个键中,并将这个键值对存放在localStorage中,如下:
了解一点前端代码,总没有坏处>_<
下面来正式开发启用和禁用功能
mapper——EmployeeMapper接口
//修改员工的status
int updateEmployeeStatus(int status, Long id, LocalDateTime updateTime);
mapper——EmployeeMapper.xml映射文件
<update id="updateEmployeeStatus">
update employee set status = #{status},update_time = #{updateTime} where id = #{id}
</update>
service层略
controller——EmployeeController类
//禁用和启用功能
@PutMapping
public R<String> updateEmployeeStatus(@RequestBody Employee employee){
//获取前端传来的json格式的参数
Integer status = employee.getStatus();
Long id = employee.getId();
//修改时间
employee.setUpdateTime(LocalDateTime.now());
log.info("id = {},status = {}",id,status);
//执行SQL语句
employeeService.updateEmployeeStatus(status,id,employee.getUpdateTime());
return R.success("执行成功");
}
6.编辑员工功能
6.1.回显用户数据
老规矩,先分析前端代码,当点击编辑按钮时,前端就会发送如下请求
进入add.html来分析分析前端代码 ,从这里可以看出添加员工和修改员工的页面是同一个,当从前端的url中获取id,那么就编辑页面,并且执行init方法
init方法的具体内容是,当后端传来的code为1,则就在编辑页面回显员工的信息。
mapper——EmployeeMapper接口
//编辑用户——回显用户数据
Employee selectEmployeeById(long id);
EmployeeMapper.xml映射文件
<select id="selectEmployeeById" resultType="employee">
select * from employee where id = #{id}
</select>
controller——EmployeeController类
//编辑用户——回显用户数据
@GetMapping("/{id}")
public R<Employee> selectEmployeeById(@PathVariable Long id){
//根据id查询用户
Employee employee = employeeService.selectEmployeeById(id);
return R.success(employee);
}
获取restful风格的路径参数,需要添加@PathVariable注解
6.2.编辑用户
- 分析了前端请求可知,这个功能的请求路径和请求方式和之前禁用和启用员工功能的是一样的,所以我们只需要在之前编写好的基础上做一点修改即可。
- 回顾下之前的禁用和启用功能,无非就是修改一下员工的status属性,这个编辑功能的本质也是修改,所以只需要在xml映射文件里添一个if判断即可,当传的员工信息的参数不为空,那么就把那些属性添加到SQL语句中
EmployeeMapper.xml映射文件
<update id="updateEmployee" parameterType="employee">
update employee set username = #{username} ,
name = #{name} ,
phone = #{phone} ,
sex = #{sex} ,
id_number = #{idNumber} ,
update_time = #{updateTime}
where id = #{id}
</update>
还有一个小修改是,将对应的方法的参数修改成Employee类型
- 别学着学懵了,SQL语句里#{}中的属性,是我们entity里编写的属性,当controller层获取到前端传来的用户信息的参数之后,就会把其封装到Employee类型的参数中(因为前端是这样写好的,通过把用户数据封装到Employee对象中,再用Json格式传给后端)如此,这个employee的对象中的属性就会随之改变,不需要让方法的参数具体到某一个字段
前后修改对比
//修改员工的status
//int updateEmployeeStatus(int status, Long id, LocalDateTime updateTime);
int updateEmployeeStatus(Employee employee);