前面已经完成了后台系统的员工登录功能开发,但是还存在一一个问题:用户如果不登录,直接访问系统首页面,照样可以正常访问。
这种设计并不合理,我们希望看到的效果应该是,只有登录成功后才可以访问系统中的页面,如果没有登录则跳转到登录页面。
那么,具体应该怎么实现呢?
答案就是使用过滤器或者拦截器,在过滤器或者拦截器中判断用户是否已经完成登录,如果没有登录则跳转到登录页面。
实现步骤:
过滤器的处理逻辑:

- package com.zqf.reggie.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;
-
- @Slf4j
- //设置过滤器名称和拦截路径
- @WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
- public class LoginCheckFilter implements Filter {
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- HttpServletRequest request = (HttpServletRequest)servletRequest;
- HttpServletResponse response = (HttpServletResponse) servletResponse;
- log.info("拦截到请求:{}",request.getRequestURI());
- filterChain.doFilter(request,response);//放行
- }
- }
- package com.zqf.reggie;
-
-
- 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//扫描Servlet组件
- public class ReggieApplication {
- public static void main(String[] args) {
- SpringApplication.run(ReggieApplication.class,args);
- log.info("项目启动成功...");
- }
- }

- package com.zqf.reggie.filter;
-
- import com.alibaba.fastjson.JSON;
- import com.zqf.reggie.common.R;
- import lombok.extern.slf4j.Slf4j;
- 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;
-
- @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("拦截到请求:{}",requestURI);
-
- //不用处理的请求路径
- String[] urls = new String[]{
- "/employee/login",
- "/employee/login",
- "/backend/**", //静态资源
- "/front/**"
- };
- //2、判断本次请求是否需要处理
- boolean check = check(urls, requestURI);
- //3、如果不需要处理,则直接放行
- if (check){
- log.info("本次请求不需要处理:{}",requestURI);
- filterChain.doFilter(request,response);//放行
- return;
- }
- //4、判断登录状态,如果已登录,则直接放行
- Object employee = request.getSession().getAttribute("employee");
- if (employee != null){
- log.info("用户已登录,id为:{}",employee);
- filterChain.doFilter(request,response);//放行
- return;
- }
-
- /*
- // 响应拦截器
- service.interceptors.response.use(res => {
- if (res.data.code === 0 && res.data.msg === 'NOTLOGIN') {// 返回登录页面
- console.log('---/backend/page/login/login.html---')
- localStorage.removeItem('userInfo')
- window.top.location.href = '/backend/page/login/login.html'
- } else {
- return res.data
- }
- },如果重复跳转login,请在urls放行路径中,添加 /employee/page,因为进入index时候,会自动发起/employee/page请求
- */
-
- //5、如果未登录则返回未登录结果 通过输出流的方式向客户端页面响应数据
- log.info("用户未登录");
- response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
- return;
- }
-
-
- public boolean check(String[] urls,String requesrURI){
- for (String url : urls){
- boolean match = PATH_MATCHER.match(url, requesrURI);
- if (match){
- return true;
- }
- }
- return false;
- }
- }
关于PATH_MATCHER的细节:Spring 概念模型 : PathMatcher 路径匹配器
后台系统中可以管理员工信息,通过新增员工来添加后台系统用户。点击[添加员工]按钮跳转到新增页面。

employee表中username唯一,status默认为1,表示状态正常

1、页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端
2、服务端Controller接收页面提交的数据并调用Service将数据进行保存
3、Service调 用Mapper操作数据库,保存数据
- @PostMapping//无需加路径了
- public R
save(@RequestBody Employee employee){ - log.info("新增员工信息:{}",employee.toString());
- return null;
- }

新增员工很多属性为空,可以设置MD5加密初始密码和其他属性
补充:fill自动填充:8.使用fill完成字段自动填充
- @PostMapping//无需加路径了
- public R
save(HttpServletRequest request,@RequestBody Employee employee){ - log.info("新增员工信息:{}",employee.toString());
- //设置初始密码123456, 需要进行md5加密处理
- employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
- //设置其他属性
- employee.setCreateTime(LocalDateTime.now());
- employee.setUpdateTime(LocalDateTime.now());
-
-
- Long empId = (Long)request.getSession().getAttribute("employee");
- //设置创建人,即先获取当前登录用户的id
- employee.setCreateUser(empId);
- employee.setUpdateUser(empId);
-
- employeeService.save(employee);
-
- return R.success("新增员工成功!");
- }
功能测试
添加成功:

小问题:
当再次输入相同用户名时, sql报错,因为username加了唯一约束

当要处理的代码段很多很明显try catch不好用
- //拦截那些类上面加了@RestController和@Controller注解的controller
- @ControllerAdvice(annotations = {RestController.class, Controller.class})
- @ResponseBody //将结果封装为json
- @Slf4j
- public class GlobalExceptionHandler {
-
- @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
- public R
exceptionHandler(SQLIntegrityConstraintViolationException ex){ - log.error(ex.getMessage());
- return R.error("失败");
- }
-
- }
现在@Controller出现异常就会被拦截到,如果是SQLIntegrityConstraintViolationException类异常就会被分到这个函数进行处理。
结果


现在就要取出001
- //拦截那些类上面加了@RestController和@Controller注解的controller
- @ControllerAdvice(annotations = {RestController.class, Controller.class})
- @ResponseBody //将结果封装为json
- @Slf4j
- public class GlobalExceptionHandler {
-
- @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
- public R
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("失败");
- }
-
- }
结果




查看前端的请求路径及其参数

代码开发
- package com.zqf.reggie.config;
-
- import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
- import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
- import org.springframework.context.annotation.Bean;
-
- public class MyBatisPlusConfig {
-
- @Bean
- public MybatisPlusInterceptor mybatisPlusInterceptor() {
- MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
- mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
- return mybatisPlusInterceptor;
- }
- }

前端需要传入这两个数据,所以R的泛型需要填入Page,因为这个类型含有这两个参数


现在考虑该传入哪些参数?考虑前端会传来哪些数据那些参数:page,pageSize,name

现在再来考虑路径问题,前端采用get请求方式,并且路径为page,所以需要加上注解@GetMapping("/page")
- @GetMapping("/page")
- public R
page(int page, int pageSize, String name){ - log.info("page = {},pageSize = {},name = {}",page,pageSize,name);
- return null;
- }
结果

service中page方法已经在内部将各种条件封装到pageinfo里面了。
- @GetMapping("/page")
- public R
page(int page,int pageSize,String name){ - log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
-
- //构造分页构造器
- Page pageInfo = new Page(page,pageSize);
-
- //构造条件构造器
- LambdaQueryWrapper
queryWrapper = new LambdaQueryWrapper(); - //添加过滤条件
- queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
- //添加排序条件
- queryWrapper.orderByDesc(Employee::getUpdateTime);
-
- //执行查询
- employeeService.page(pageInfo,queryWrapper);
-
- return R.success(pageInfo);
- }


没有limit应该是拦截器没写好,没加@Configuation注解
加上之后:
- package com.zqf.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;
-
- @Configuration
- public class MyBatisPlusConfig {
-
- @Bean
- public MybatisPlusInterceptor mybatisPlusInterceptor() {
- MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
- mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
- return mybatisPlusInterceptor;
- }
- }

结果



页面中是怎么做到只有管理员admin能够看到启用、禁用按钮的?
获取当前user,判断是否为admin,并动态显示启用/禁用





启用、禁用员工账号,本质上就是一个更新操作,也就是对status状态字段进行操作
在Controller中创建update方法,此方法是一个通用的修改员工信息的方法
确认路径和请求方式

- /**
- * 根据id修改员工信息
- * @param employee
- * @return
- */
- @PutMapping
- public R
update(@RequestBody Employee employee){ - log.info(employee.toString());//查看参数是否能传进来
- return null;
- }


说明客户端和服务端能够正常请求处理,参数是能够正常传递的
设置参数属性之后
- /**
- * 根据id修改员工信息
- * @param employee
- * @return
- */
- @PutMapping
- public R
update(HttpServletRequest request,@RequestBody Employee employee){ - log.info(employee.toString());//查看参数是否能传进来
-
- Long empId = (Long) request.getSession().getAttribute("employee");
- employee.setUpdateTime(LocalDateTime.now());
- employee.setUpdateUser(empId);
-
- /*
- status在前端传过来的参数里,即在employee里
- */
-
- employeeService.updateById(employee);
- return R.success("修改成功");
- }
But出现了问题

原因:js对于long类型只能精确到前16位,后面则做了四舍五入,所以



1.在commns目录下添加类,这个类可以将java对象转换为json,也可以将json转换为 对象
- package com.zqf.reggie.common;
-
- 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);
- }
- }
2.扩展消息转换器
将controller方法中的返回结果(R对象),转换为json,然后再通过输出流的方式响应给页面,所以页面上展示的都是json数据。
- /**
- * 扩展mvc框架的消息转换器
- * @param converters
- */
- @Override
- protected void extendMessageConverters(List
> converters) { - //创建消息转换器
- MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
- //设置对象转换器,底层使用Jackson将Java对象转为json
- messageConverter.setObjectMapper(new JacksonObjectMapper());
- //将上面的消息转换器对象追加到avc框架的转换器集合
- converters.add(0,messageConverter); //转换器是有顺序的,要让转换器优先使用我们自己设置的转换器,所以设置索引为0
- }
当执行add方法之后,我们的转换器已添加,其余8个事mvc自带的消息转换器

可以看到此时的id已经加上双引号,时间的格式也有所改变


执行流程
信息回显需要查询数据库

钩子函数,渲染页面时自动执行


获取参数用来查询回显

如果获取的id有值得话那就是编辑选项而不是add,因为这两个功能都在同一个页面进去



当服务端响应过来之后,就会调用回调函数,如果res.code = 1(成功的状态码)就数据回显

controller添加查询信息
- @GetMapping("/{id}") //注意id是个路径变量 通过以下方式获取
- public R
getById(@PathVariable Long id){ - log.info("根据id查询信息");
- Employee byId = employeeService.getById(id);
- if (byId!=null){
- return R.success(byId);//确保一定是查出来的
- }
- return R.error("没有查询到员工信息");
- }
效果

