• 瑞吉外卖——Day02


    目录

    完善登录功能

    问题分析

    代码实现

    新增员工

    需求分析

    ​编辑数据模型

    ​编辑代码开发

    编写全局异常处理器

    完善全局异常处理器并测试

    小结

    员工信息分页查询

    需求分析

    梳理程序执行过程&代码开发

    功能测试

    启用/禁用员工账号

    需求分析

    分析页面按钮动态显示效果

    ​编辑

    分析页面ajax请求发送过程

    代码开发和功能测试

    代码修复配置消息转换器

    编辑员工信息

    需求分析

    页面效果分析


    完善登录功能

    问题分析

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

    实现步骤:

    1. 创建自定义过滤器LoginCheckFilter
    2. 在启动类.上加入注解@ServletComponentScan(开启组件扫描,开启过滤器)
    3. 完善过滤器的处理逻辑

    过滤器的处理逻辑:

    代码实现

    • 创建过滤器
    1. package com.zqf.reggie.filter;
    2. import lombok.extern.slf4j.Slf4j;
    3. import javax.servlet.*;
    4. import javax.servlet.annotation.WebFilter;
    5. import javax.servlet.http.HttpServletRequest;
    6. import javax.servlet.http.HttpServletResponse;
    7. import java.io.IOException;
    8. @Slf4j
    9. //设置过滤器名称和拦截路径
    10. @WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
    11. public class LoginCheckFilter implements Filter {
    12. @Override
    13. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    14. HttpServletRequest request = (HttpServletRequest)servletRequest;
    15. HttpServletResponse response = (HttpServletResponse) servletResponse;
    16. log.info("拦截到请求:{}",request.getRequestURI());
    17. filterChain.doFilter(request,response);//放行
    18. }
    19. }
    • 添加扫描组件的注解
    1. package com.zqf.reggie;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.springframework.boot.SpringApplication;
    4. import org.springframework.boot.autoconfigure.SpringBootApplication;
    5. import org.springframework.boot.web.servlet.ServletComponentScan;
    6. @Slf4j
    7. @SpringBootApplication
    8. @ServletComponentScan//扫描Servlet组件
    9. public class ReggieApplication {
    10. public static void main(String[] args) {
    11. SpringApplication.run(ReggieApplication.class,args);
    12. log.info("项目启动成功...");
    13. }
    14. }

    •  实现过滤器的实现逻辑
    1. package com.zqf.reggie.filter;
    2. import com.alibaba.fastjson.JSON;
    3. import com.zqf.reggie.common.R;
    4. import lombok.extern.slf4j.Slf4j;
    5. import org.springframework.util.AntPathMatcher;
    6. import javax.servlet.*;
    7. import javax.servlet.annotation.WebFilter;
    8. import javax.servlet.http.HttpServletRequest;
    9. import javax.servlet.http.HttpServletResponse;
    10. import java.io.IOException;
    11. @Slf4j
    12. //设置过滤器名称和拦截路径
    13. @WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
    14. public class LoginCheckFilter implements Filter {
    15. //路径匹配器 支持通配符
    16. public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    17. @Override
    18. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    19. HttpServletRequest request = (HttpServletRequest)servletRequest;
    20. HttpServletResponse response = (HttpServletResponse) servletResponse;
    21. //1、获取本次请求的URI
    22. String requestURI = request.getRequestURI();
    23. log.info("拦截到请求:{}",requestURI);
    24. //不用处理的请求路径
    25. String[] urls = new String[]{
    26. "/employee/login",
    27. "/employee/login",
    28. "/backend/**", //静态资源
    29. "/front/**"
    30. };
    31. //2、判断本次请求是否需要处理
    32. boolean check = check(urls, requestURI);
    33. //3、如果不需要处理,则直接放行
    34. if (check){
    35. log.info("本次请求不需要处理:{}",requestURI);
    36. filterChain.doFilter(request,response);//放行
    37. return;
    38. }
    39. //4、判断登录状态,如果已登录,则直接放行
    40. Object employee = request.getSession().getAttribute("employee");
    41. if (employee != null){
    42. log.info("用户已登录,id为:{}",employee);
    43. filterChain.doFilter(request,response);//放行
    44. return;
    45. }
    46. /*
    47. // 响应拦截器
    48. service.interceptors.response.use(res => {
    49. if (res.data.code === 0 && res.data.msg === 'NOTLOGIN') {// 返回登录页面
    50. console.log('---/backend/page/login/login.html---')
    51. localStorage.removeItem('userInfo')
    52. window.top.location.href = '/backend/page/login/login.html'
    53. } else {
    54. return res.data
    55. }
    56. },如果重复跳转login,请在urls放行路径中,添加 /employee/page,因为进入index时候,会自动发起/employee/page请求
    57. */
    58. //5、如果未登录则返回未登录结果 通过输出流的方式向客户端页面响应数据
    59. log.info("用户未登录");
    60. response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
    61. return;
    62. }
    63. public boolean check(String[] urls,String requesrURI){
    64. for (String url : urls){
    65. boolean match = PATH_MATCHER.match(url, requesrURI);
    66. if (match){
    67. return true;
    68. }
    69. }
    70. return false;
    71. }
    72. }

    关于PATH_MATCHER的细节:Spring 概念模型 : PathMatcher 路径匹配器

    新增员工

    需求分析

            后台系统中可以管理员工信息,通过新增员工来添加后台系统用户。点击[添加员工]按钮跳转到新增页面。


    数据模型

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


    代码开发

    1、页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端
    2、服务端Controller接收页面提交的数据并调用Service将数据进行保存
    3、Service调 用Mapper操作数据库,保存数据

    1. @PostMapping//无需加路径了
    2. public R save(@RequestBody Employee employee){
    3. log.info("新增员工信息:{}",employee.toString());
    4. return null;
    5. }

    新增员工很多属性为空,可以设置MD5加密初始密码和其他属性

    补充:fill自动填充:8.使用fill完成字段自动填充

    1. @PostMapping//无需加路径了
    2. public R save(HttpServletRequest request,@RequestBody Employee employee){
    3. log.info("新增员工信息:{}",employee.toString());
    4. //设置初始密码123456, 需要进行md5加密处理
    5. employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
    6. //设置其他属性
    7. employee.setCreateTime(LocalDateTime.now());
    8. employee.setUpdateTime(LocalDateTime.now());
    9. Long empId = (Long)request.getSession().getAttribute("employee");
    10. //设置创建人,即先获取当前登录用户的id
    11. employee.setCreateUser(empId);
    12. employee.setUpdateUser(empId);
    13. employeeService.save(employee);
    14. return R.success("新增员工成功!");
    15. }

    功能测试

    添加成功:

    小问题:

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

    编写全局异常处理器

     当要处理的代码段很多很明显try catch不好用

    1. //拦截那些类上面加了@RestController和@Controller注解的controller
    2. @ControllerAdvice(annotations = {RestController.class, Controller.class})
    3. @ResponseBody //将结果封装为json
    4. @Slf4j
    5. public class GlobalExceptionHandler {
    6. @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    7. public R exceptionHandler(SQLIntegrityConstraintViolationException ex){
    8. log.error(ex.getMessage());
    9. return R.error("失败");
    10. }
    11. }

            现在@Controller出现异常就会被拦截到,如果是SQLIntegrityConstraintViolationException类异常就会被分到这个函数进行处理。

    结果

    完善全局异常处理器并测试

    现在就要取出001

    1. //拦截那些类上面加了@RestController和@Controller注解的controller
    2. @ControllerAdvice(annotations = {RestController.class, Controller.class})
    3. @ResponseBody //将结果封装为json
    4. @Slf4j
    5. public class GlobalExceptionHandler {
    6. @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    7. public R exceptionHandler(SQLIntegrityConstraintViolationException ex){
    8. log.error(ex.getMessage());
    9. //这样我们就知道这样违反了唯一约束
    10. if (ex.getMessage().contains("Duplicate entry")){
    11. String[] split = ex. getMessage().split( " ") ;
    12. String msg = split[2] + "已存在";
    13. return R.error(msg);
    14. }
    15. return R.error("失败");
    16. }
    17. }

    结果

    小结

    员工信息分页查询

    需求分析

    梳理程序执行过程&代码开发


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

    代码开发

    • 1.配置mybatis分页插件(通过拦截器的方式将插件配置进来)
    1. package com.zqf.reggie.config;
    2. import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
    3. import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
    4. import org.springframework.context.annotation.Bean;
    5. public class MyBatisPlusConfig {
    6. @Bean
    7. public MybatisPlusInterceptor mybatisPlusInterceptor() {
    8. MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
    9. mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
    10. return mybatisPlusInterceptor;
    11. }
    12. }
    • 2.创建controller接收查询参数(分页相关数据)

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

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

            现在再来考虑路径问题,前端采用get请求方式,并且路径为page,所以需要加上注解@GetMapping("/page")

    1. @GetMapping("/page")
    2. public R page(int page, int pageSize, String name){
    3. log.info("page = {},pageSize = {},name = {}",page,pageSize,name);
    4. return null;
    5. }

    结果

    •  3.构造分页构造器,调用service相关方法

    service中page方法已经在内部将各种条件封装到pageinfo里面了。

    1. @GetMapping("/page")
    2. public R page(int page,int pageSize,String name){
    3. log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
    4. //构造分页构造器
    5. Page pageInfo = new Page(page,pageSize);
    6. //构造条件构造器
    7. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
    8. //添加过滤条件
    9. queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
    10. //添加排序条件
    11. queryWrapper.orderByDesc(Employee::getUpdateTime);
    12. //执行查询
    13. employeeService.page(pageInfo,queryWrapper);
    14. return R.success(pageInfo);
    15. }

    功能测试

    没有limit应该是拦截器没写好,没加@Configuation注解

    加上之后:

    1. package com.zqf.reggie.config;
    2. import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
    3. import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
    4. import org.springframework.context.annotation.Bean;
    5. import org.springframework.context.annotation.Configuration;
    6. @Configuration
    7. public class MyBatisPlusConfig {
    8. @Bean
    9. public MybatisPlusInterceptor mybatisPlusInterceptor() {
    10. MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
    11. mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
    12. return mybatisPlusInterceptor;
    13. }
    14. }

     结果

    启用/禁用员工账号

    需求分析

    分析页面按钮动态显示效果

    页面中是怎么做到只有管理员admin能够看到启用、禁用按钮的?

    获取当前user,判断是否为admin,并动态显示启用/禁用

    分析页面ajax请求发送过程

    代码开发和功能测试

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

    确认路径和请求方式

    1. /**
    2. * 根据id修改员工信息
    3. * @param employee
    4. * @return
    5. */
    6. @PutMapping
    7. public R update(@RequestBody Employee employee){
    8. log.info(employee.toString());//查看参数是否能传进来
    9. return null;
    10. }

    说明客户端和服务端能够正常请求处理,参数是能够正常传递的

    设置参数属性之后

    1. /**
    2. * 根据id修改员工信息
    3. * @param employee
    4. * @return
    5. */
    6. @PutMapping
    7. public R update(HttpServletRequest request,@RequestBody Employee employee){
    8. log.info(employee.toString());//查看参数是否能传进来
    9. Long empId = (Long) request.getSession().getAttribute("employee");
    10. employee.setUpdateTime(LocalDateTime.now());
    11. employee.setUpdateUser(empId);
    12. /*
    13. status在前端传过来的参数里,即在employee里
    14. */
    15. employeeService.updateById(employee);
    16. return R.success("修改成功");
    17. }

    But出现了问题

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

    代码修复配置消息转换器

    1.在commns目录下添加类,这个类可以将java对象转换为json,也可以将json转换为 对象

    1. package com.zqf.reggie.common;
    2. import com.fasterxml.jackson.databind.DeserializationFeature;
    3. import com.fasterxml.jackson.databind.ObjectMapper;
    4. import com.fasterxml.jackson.databind.module.SimpleModule;
    5. import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
    6. import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
    7. import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
    8. import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
    9. import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
    10. import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
    11. import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
    12. import java.math.BigInteger;
    13. import java.time.LocalDate;
    14. import java.time.LocalDateTime;
    15. import java.time.LocalTime;
    16. import java.time.format.DateTimeFormatter;
    17. import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
    18. /**
    19. * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
    20. * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
    21. * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
    22. */
    23. public class JacksonObjectMapper extends ObjectMapper {
    24. public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    25. public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    26. public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
    27. public JacksonObjectMapper() {
    28. super();
    29. //收到未知属性时不报异常
    30. this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
    31. //反序列化时,属性不存在的兼容处理
    32. this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    33. SimpleModule simpleModule = new SimpleModule()
    34. .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
    35. .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
    36. .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
    37. .addSerializer(BigInteger.class, ToStringSerializer.instance)
    38. .addSerializer(Long.class, ToStringSerializer.instance)
    39. .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
    40. .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
    41. .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
    42. //注册功能模块 例如,可以添加自定义序列化器和反序列化器
    43. this.registerModule(simpleModule);
    44. }
    45. }

    2.扩展消息转换器

            将controller方法中的返回结果(R对象),转换为json,然后再通过输出流的方式响应给页面,所以页面上展示的都是json数据。

    1. /**
    2. * 扩展mvc框架的消息转换器
    3. * @param converters
    4. */
    5. @Override
    6. protected void extendMessageConverters(List> converters) {
    7. //创建消息转换器
    8. MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
    9. //设置对象转换器,底层使用Jackson将Java对象转为json
    10. messageConverter.setObjectMapper(new JacksonObjectMapper());
    11. //将上面的消息转换器对象追加到avc框架的转换器集合
    12. converters.add(0,messageConverter); //转换器是有顺序的,要让转换器优先使用我们自己设置的转换器,所以设置索引为0
    13. }

    当执行add方法之后,我们的转换器已添加,其余8个事mvc自带的消息转换器

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

    编辑员工信息

    需求分析

    执行流程 

    信息回显需要查询数据库

    页面效果分析

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

     

    获取参数用来查询回显

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

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

    controller添加查询信息 

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

    效果

  • 相关阅读:
    【python VS vba(系列2)】 python和vba读写EXCEL文件的方式比较 (建设ing)
    matplotlib显示图表图例
    组件自定义事件
    Node.js 前后端分离开发新思路
    数学中的基
    查词翻译类应用使用数据接口api总结
    AutoCAD2019开发配置
    数据结构与算法-第六章 图的最小生成树
    医院项目-预约挂号-第六部分(医院管理排班和网关)
    uniapp-图片压缩(适配H5,APP)
  • 原文地址:https://blog.csdn.net/m0_52601969/article/details/126033856