基于Springboot外卖系统08:员工账号状态管理功能+对象转换器+扩展Spring mvc的消息转换器

简介: 在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。如果某个员工账号状态为正常,则按钮显示为 "禁用",如果员工账号状态为已禁用,则按钮显示为"启用"。

1. 员工账号状态管理


1.1 需求分析


在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。如果某个员工账号状态为正常,则按钮显示为 "禁用",如果员工账号状态为已禁用,则按钮显示为"启用"。


只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,普通用户登录系统后启用、禁用按钮不显示。


A. admin 管理员登录


29705e01324345ed80443dd4eaf2bbfc.png


B. 普通用户登录


ee0867672a07436695a4cc7dfa6c648c.png


1.2 程序执行流程


1.2.1 页面按钮动态展示


只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示:


1). 在列表页面(list.html)加载时, 触发钩子函数created, 在钩子函数中, 会从localStorage中获取到用户登录信息, 然后获取到用户名


5edd1ccc6de449f7b95d090907689b6b.png


2). 在页面中, 通过Vue指令v-if进行判断,如果登录用户为admin将展示 启用/禁用 按钮, 否则不展示


d99300665932433f943838d3aef622e3.png


1.2.2 执行流程分析


1). 当管理员admin点击 "启用" 或 "禁用" 按钮时, 调用方法statusHandle


11dd45a5dbb14f769d2ac30d23e83ca2.png


scope.row : 获取到的是这一行的数据信息 ;


2). statusHandle方法中进行二次确认, 然后发起ajax请求, 传递id、status参数


53131075785c45908b2ffd0f1ead15eb.png


c6845dafb25f4155a4d8239e41209764.png


最终发起异步请求, 请求服务端, 请求信息如下:


请求 说明
请求方式 PUT
请求路径 /employee
请求参数 {"id":xxx,"status":xxx}


{...params} : 三点是ES6中出现的扩展运算符。作用是遍历当前使用的对象能够访问到的所有属性,并将属性放入当前对象中。


1.3 员工账号状态管理程序流程


A.页面发送ajax请求,将参数(id、status)提交到服务端


B. 服务端Controller接收页面提交的数据并调用Service更新数据


C. Service调用Mapper操作数据库


1.4 员工账号状态管理代码实现


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


package com.itheima.reggie.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
    @Autowired
    private EmployeeService employeeService;
    @PostMapping("/login")
    public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){
    /**@Description: 员工登陆
     * @param @RequestBody 传入的是json 故需要将其转化为实体类,json中的类名与实体类名对应才可以封装
     *          A. 由于需求分析时, 我们看到前端发起的请求为post请求, 所以服务端需要使用注解 @PostMapping
     *          B. 由于前端传递的请求参数为json格式的数据, 这里使用Employee对象接收, 但是将json格式数据封装到实体类中, 在形参前需要加注解@RequestBody
     * @return com.itheima.reggie.common.R<com.itheima.reggie.entity.Employee>
     * @version v1.0
     * @author LiBiGo
     * @date 2022/8/12 11:32
     */
        //  ①. 将页面提交的密码password进行md5加密处理, 得到加密后的字符串
        String password = employee.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());
        //  ②. 根据页面提交的用户名username查询数据库中员工数据信息
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername,employee.getUsername());
        Employee emp = employeeService.getOne(queryWrapper); //在数据库中用户名是唯一的,所以可以用getone()
        //  ③. 如果没有查询到, 则返回登录失败结果
        if (emp == null){
            return R.error("用户名不存在");
        }
        //  ④. 密码比对,如果不一致, 则返回登录失败结果
        if(!emp.getPassword().equals(password)){
            return R.error("密码失败");
        }
        //  ⑤. 查看员工状态,如果为已禁用状态,则返回员工已禁用结果
        if(emp.getStatus() == 0){
            return R.error("账户已禁用");
        }
        //  ⑥. 登录成功,将员工id存入Session, 并返回登录成功结果
        request.getSession().setAttribute("employee",emp.getId());
        return R.success(emp);
    }
    @PostMapping("/logout")
    public R<String> logout(HttpServletRequest request){
    /**@Description:  /employee/logout 员工退出
     * @author LiBiGo
     * @date 2022/8/12 12:09
     */
        //  清理Session中保存的当前登录员工的id
        request.getSession().removeAttribute("employee");
        //  返回结果
        return R.success("退出成功");
    }
    @PostMapping
    public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
        /**@Description: 用于保存用户员工信息。
         *
         * B. 在组装员工信息时, 还需要封装创建时间、修改时间,创建人、修改人信息(从session中获取当前登录用户)。
         * @author LiBiGo
         * @date 2022/8/12 17:25
         */
        log.info("新增员工,员工信息:{}",employee.toString());
        // A. 在新增员工时,设置初始密码,需要进行MD5加密处理
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
        // B. 在组装员工信息时, 需封装创建时间、修改时间,创建人、修改人信息(从session中获取当前登录用户)。
        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); // 因为employeeService继承了,所以不用写,直接用
        log.info("新增员工成功");
        return R.success("新增员工成功");
    }
    @GetMapping("page")
    public R<Page> page(int page,int pageSize,String name){
        /**@Description: 员工分页查询
         * @param page 当前查询页码
         * @param pageSize 每页展示记录数
         * @param name 员工姓名 - 可选参数
         * @author LiBiGo
         * @date 2022/8/13 9:51
         */
        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); // 当name不为null,则进行添加过滤
        //排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);
        //执行查询
        employeeService.page(pageInfo,queryWrapper); //结果自动放入 pageInfo,不需要传参
        return R.success(pageInfo);
    }
    @PutMapping
    public R<String> update(HttpServletRequest request,@RequestBody  Employee employee){
        /**@Description: 根据id修改员工信息,传入员工的所有信息,再根据员工id进行数据库的替换
         * @author LiBiGo
         * @date 2022/8/13 10:53
         */
        log.info("更新的用户信息{}",employee.toString()); // 传入的
        Long empId = (Long)request.getSession().getAttribute("employee"); //获取emp的id
        employee.setUpdateTime(LocalDateTime.now()); //设置更新时间
        employee.setUpdateUser(empId); // 设置更新人
        employeeService.updateById(employee);
        return R.success("员工信息修改成功");
    }
}


1.5 功能测试


重启工程,访问前端页面, 进行 "启用" 或 "禁用" 的测试。


6a441816da4a4e4a89eb1a4234c42f47.png


测试过程中没有报错,但是功能并没有实现,查看数据库中的数据也没有变化。但是从控制台输出的日志, 可以看出确实没有更新成功。


56a1e554f92b4c3a9fc725207d9f6e48.png


而在我们的数据库表结构中, 并不存在该ID, 数据库的ID为1420038345634918401


2 对象转换器+扩展Spring mvc的消息转换器


2.1 原因分析


2.1.1 现象


a3048dd168db407a85d6626194fa91de.png


通过观察控制台输出的SQL发现页面传递过来的员工id的值和数据库中的id值不一致


在分页查询时,服务端会将返回的R对象进行json序列化,转换为json格式的数据,而员工的ID是一个Long类型的数据,而且是一个长度为 19 位的长整型数据, 该数据返回给前端是没有问题的。


f9c8e5412f7f4f37bad71dab52649645.png


2.1.2 具体的问题原因


前端JS对长度较长的长整型数据进行处理时,会损失精度,从而导致提交的id和数据库中的id不一致。


2.1.3 解决方案


解决方案:将js处理的ID数据类型转为字符串类型, 这样不损失精度。


在业务中, 让分页查询返回的json格式数据库中long类型的属性, 不直接转换为数字类型, 而转换为字符串类型就可以解决这个问题 , 最终返回的结果为 :


79ece48c7a264c3392ef37f5b8362ee3.png


2.2 代码修复


由于在SpringMVC中, 将Controller方法返回值转换为json对象, 是通过jackson来实现的, 涉及到SpringMVC中的一个消息转换器MappingJackson2HttpMessageConverter, 因此需要对该消息转换器的功能进行拓展。


2.2.1 具体实现步骤:


1). 提供对象转换器JacksonObjectMapper,基于Jackson进行Java对象到json数据的转换


2). 在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到json数据的转换


1). 引入JacksonObjectMapper


package com.itheima.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]
 * 
 * 该自定义的对象转换器, 主要指定在进行json数据序列化及反序列化时, LocalDateTime、LocalDate、LocalTime的处理方式, 
 *                                    以及BigInteger及Long类型数据,直接转换为字符串。
 */
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) // 将long转化为string,可以解决前端页面不能正确显示超过固定尾位数的数字
                .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). 在WebMvcConfig中重写方法extendMessageConverters


package com.itheima.reggie.config;
import com.itheima.reggie.common.JacksonObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.util.List;
//配置类
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    /**@Description: 设置静态资源的映射
     * @param 
     * @return
     * @version v1.0
     * @author LiBiGo
     * @date 2022/8/11 23:41
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("静态资源开始映射......");
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
        log.info("静态资源映射成功");
    }
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        /**@Description: 扩展mvc框架的消息转换器,项目启动时即调用,
         * @author LiBiGo
         * @date 2022/8/13 11:16
         */
        log.info("扩展消息转换器...");
        // 创建消息转换器
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        // 设置对象转换器,底层使用将jackson将java对象转化为json
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        // 将上面的消息转化器对象追加到mvc的转化器集合中
        converters.add(0,messageConverter); // 将我们的转化器放在最前面,实现优先使用
    }
}


2.2.4 测试成功


1c8dd3ba1f6e45fe8d8f04a0c7f3251b.png

目录
相关文章
|
6月前
|
安全 Java Apache
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 身份和权限认证
本文介绍了 Apache Shiro 的身份认证与权限认证机制。在身份认证部分,分析了 Shiro 的认证流程,包括应用程序调用 `Subject.login(token)` 方法、SecurityManager 接管认证以及通过 Realm 进行具体的安全验证。权限认证部分阐述了权限(permission)、角色(role)和用户(user)三者的关系,其中用户可拥有多个角色,角色则对应不同的权限组合,例如普通用户仅能查看或添加信息,而管理员可执行所有操作。
309 0
|
6月前
|
安全 Java 数据安全/隐私保护
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 三大核心组件
本课程介绍如何在Spring Boot中集成Shiro框架,主要讲解Shiro的认证与授权功能。Shiro是一个简单易用的Java安全框架,用于认证、授权、加密和会话管理等。其核心组件包括Subject(认证主体)、SecurityManager(安全管理员)和Realm(域)。Subject负责身份认证,包含Principals(身份)和Credentials(凭证);SecurityManager是架构核心,协调内部组件运作;Realm则是连接Shiro与应用数据的桥梁,用于访问用户账户及权限信息。通过学习,您将掌握Shiro的基本原理及其在项目中的应用。
237 0
|
6月前
|
NoSQL Java 关系型数据库
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
本文介绍在 Spring Boot 中集成 Redis 的方法。Redis 是一种支持多种数据结构的非关系型数据库(NoSQL),具备高并发、高性能和灵活扩展的特点,适用于缓存、实时数据分析等场景。其数据以键值对形式存储,支持字符串、哈希、列表、集合等类型。通过将 Redis 与 Mysql 集群结合使用,可实现数据同步,提升系统稳定性。例如,在网站架构中优先从 Redis 获取数据,故障时回退至 Mysql,确保服务不中断。
262 0
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
|
5月前
|
存储 Java 数据库
Spring Boot 注册登录系统:问题总结与优化实践
在Spring Boot开发中,注册登录模块常面临数据库设计、密码加密、权限配置及用户体验等问题。本文以便利店销售系统为例,详细解析四大类问题:数据库字段约束(如默认值缺失)、密码加密(明文存储风险)、Spring Security配置(路径权限不当)以及表单交互(数据丢失与提示不足)。通过优化数据库结构、引入BCrypt加密、完善安全配置和改进用户交互,提供了一套全面的解决方案,助力开发者构建更 robust 的系统。
162 0
|
6月前
|
Java 数据安全/隐私保护 微服务
微服务——SpringBoot使用归纳——Spring Boot中使用监听器——Spring Boot中自定义事件监听
本文介绍了在Spring Boot中实现自定义事件监听的完整流程。首先通过继承`ApplicationEvent`创建自定义事件,例如包含用户数据的`MyEvent`。接着,实现`ApplicationListener`接口构建监听器,用于捕获并处理事件。最后,在服务层通过`ApplicationContext`发布事件,触发监听器执行相应逻辑。文章结合微服务场景,展示了如何在微服务A处理完逻辑后通知微服务B,具有很强的实战意义。
376 0
|
6月前
|
缓存 Java 数据库
微服务——SpringBoot使用归纳——Spring Boot中使用监听器——监听器介绍和使用
本文介绍了在Spring Boot中使用监听器的方法。首先讲解了Web监听器的概念,即通过监听特定事件(如ServletContext、HttpSession和ServletRequest的创建与销毁)实现监控和处理逻辑。接着详细说明了三种实际应用场景:1) 监听Servlet上下文对象以初始化缓存数据;2) 监听HTTP会话Session对象统计在线用户数;3) 监听客户端请求的Servlet Request对象获取访问信息。每种场景均配有代码示例,帮助开发者理解并应用监听器功能。
401 0
|
6月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
134 0
|
6月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
878 0
|
2月前
|
存储 人工智能 自然语言处理
用Spring AI搭建本地RAG系统:让AI成为你的私人文档助手
想让AI帮你读懂PDF文档吗?本文教你用Spring AI和Ollama搭建一个本地RAG系统,让AI成为你的私人文档助手。无需GPU,无需云端API,只需几行代码,你的文档就能开口说话了!
|
消息中间件 存储 Java
📨 Spring Boot 3 整合 MQ 构建聊天消息存储系统
本文详细介绍了如何使用Spring Boot 3结合RabbitMQ构建高效可靠的聊天消息存储系统。通过引入消息队列,实现了聊天功能与消息存储的解耦,解决了高并发场景下直接写入数据库带来的性能瓶颈问题。文章首先分析了不同MQ产品的特点及适用场景,最终选择RabbitMQ作为解决方案,因其成熟稳定、灵活路由和易于集成等优势。接着,通过Docker快速部署RabbitMQ,并完成Spring Boot项目的配置与代码实现,包括生产者发送消息、消费者接收并处理消息等功能。最后,通过异步存储机制,既保证了消息的即时性,又实现了可靠持久化。
361 0
📨 Spring Boot 3 整合 MQ 构建聊天消息存储系统