黑马苍穹外卖项目笔记02_员工管理、分类管理

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一.新增员工

1.需求分析和设计

新增员工的产品原型设计如下:
账号:每个人的账号必须唯一
员工姓名:没有要求
手机号:合法的11位手机号码
性别:男or女
身份证号:合法的18位身份证号码
密码为默认的值:123456(新增员工后员工可以登录进行修改密码)

2.接口设计:

前端提交数据采用post提交
传递给后端的参数信息由下图得知:
在这里插入图片描述
首先肯定传递的是json数据类型,id为非必须(数据库id设置为自增策略)
其他参数由需求分析与设计得知

后端返回给前端的数据:
在这里插入图片描述
code必须:返回参数0代表未成功,1代表成功
data:后端的总数据封装的data对象,包含了前端所需的数据,用于展示到页面
msg:异常或其他消息作为字符串返回给前端页面进行展示

项目约定:
客户端发出的请求:统一使用 /admin 作为前缀
用户端发出的请求:统一使用 /user 作为前缀

3.数据库设计(employee表)

在这里插入图片描述

一.代码开发

先了解下各种"O"的含义(DO,VO,DTO等等)https://zhuanlan.zhihu.com/p/296492029

简单来说就是:当前端提交的数据VO和实体类entity(不添加额外业务的DO)&DO中对应的属性差别比较大时,建议使用DTO来封装数据

1.在EmployeeController中创建新增员工方法,接收前端提交的参数

@PostMapping
    @ApiOperation("新增员工接口")
    public Result save(@RequestBody EmployeeDTO employeeDTO){
        log.info("新增员工:{}",employeeDTO);
        employeeService.save(employeeDTO);
        return Result.success();
    }

2.在EmployeeService接口中声明新增员工方法

/**
     * 新增员工
     * @param employeeDTO
     */
    void save(EmployeeDTO employeeDTO);

3.在EmployeeServiceImpl中实现新增员工方法

public void save(EmployeeDTO employeeDTO) {
        //实现DTO到entity的转换
        Employee employee=new Employee();
        //属性拷贝(使用了BeanUtils工具类)
        BeanUtils.copyProperties(employeeDTO,employee);

        //账号状态默认为1,正常状态
        employee.setStatus(StatusConstant.ENABLE);
        //默认密码为:123456
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
        //创建人,创建时间,修改人,修改时间
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        employee.setCreateUser(10L);
        employee.setUpdateUser(10L);

        employeeMapper.insert(employee);
    }

4.在EmployeeMapper中声明insert方法

@Insert("insert into employee"+
            "(name, username, password, phone, sex, id_number, status, create_time, update_time, create_user, update_user)"+
            "values"+
    "(#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{status}, #{createTime},#{updateTime},#{createUser}, #{updateUser})")
    void insert(Employee employee);

四段代码总体从逻辑上来看没有什么难的,很容易理解

二.功能测试

功能测试方式:
1.通过接口文档测试
2.通过前后端联调测试

刚开始进行接口文档测试时没有成功,原因是拦截器拦截到前端页面没有携带JWT令牌,故不能进行添加员工的操作,故只需要给请求头添加一个合法的JWT令牌即可:
在这里插入图片描述

三.代码完善

总体上来说,到此程序有以下问题没有得到解决:
1.录入的用户名已经存在,抛出异常后没有处理
2.新增员工时,创建人的id和修改人的id设置为了固定值

1.完善代码问题1

可以通过全局异常处理器来处理:

@ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        //用户名相同时会报错:Duplicate entry 'XXX' for key ...
        String message = ex.getMessage();
        if(message.contains("Duplicate entry")){
            String[] split= message.split(" ");
            String username=split[2];
            String msg=username+ MessageConstant.ALREADY_EXISTS;
            return Result.error(msg);
        }else{
            return Result.error(MessageConstant.UNKNOWN_ERROR);
        }
    }

测试显示:
在这里插入图片描述

2.完善代码问题2

前端每次进行操作的时候,后端拦截器就会检测携带的JWT令牌,在项目的代码中,如果已经携带有令牌,就会解析出当前的员工id,但解析出登录员工id后,如何传递给Service的save方法?

ThreadLocal

ThreadLocal并不是一个Thread,而是Thread的局部变量。
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。也就是说,前端每次进行的操作就是一个新的线程,根据这个线程对象,我们能取到拦截器里面的员工id并储存到线程存储空间中,同线程的service方法就能从线程存储空间拿到这个值。

ThreadLocal常用方法:
public void set(T value) 设置当前线程的线程局部变量的值
public T get()返回(取到) 当前线程所对应的线程局部变量的值
public void remove() 移除当前线程的线程局部变量

在初始工程中已经封装了 ThreadLocal 操作的工具类,可以直接使用

//2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:", empId);
            BaseContext.setCurrentId(empId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }

添加BaseContext.setCurrentId(empId)存储empid

employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        employee.setCreateUser(BaseContext.getCurrentId());
        employee.setUpdateUser(BaseContext.getCurrentId());

添加BaseContext.getCurrentId()取出empid

最后,别忘了将代码推送给git仓库

二.员工分页查询

需求分析和设计

业务规则:
1.根据页码展示员工信息
2.每页展示10条数据
3.分页查询时可以根据需要,输入员工姓名进行查询
接口设计&返回数据:
在这里插入图片描述

在这里插入图片描述

一.代码开发

根据分页查询接口设计对应的DTO:
在这里插入图片描述
对应的类为:EmployeePageQueryDTO

后面所有的分页查询,统一都封装成PageResult对象:

员工信息分页查询后端返回的对象类型为:Result

在这里插入图片描述
根据接口定义创建分页查询方法:

	@GetMapping("/page")
    @ApiOperation("员工分页查询")
    public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
        log.info("员工分页查询,参数为:{}",employeePageQueryDTO);
        PageResult pageResult=employeeService.pageQuery(employeePageQueryDTO);
        return Result.success(pageResult);
    }

在EmployeeService接口中声明pageQuery方法:

/**
     * 分页查询
     * @return
     */
    PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

在 EmployeeServiceImpl 中实现 pageQuery 方法:

public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
        PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());

        Page<Employee> page=employeeMapper.pageQuery(employeePageQueryDTO);

        long total = page.getTotal();
        List<Employee> result = page.getResult();

        return new PageResult(total,result);
    }

注意: 此处使用 mybatis 的分页插件 PageHelper 来简化分页代码的开发。底层基于 mybatis 的拦截器实现。

<dependency>
     <groupId>com.github.pagehelper</groupId>
     <artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>

在 EmployeeMapper 中声明 pageQuery 方法:

Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

在 EmployeeMapper.xml 中编写SQL:

<select id="pageQuery" resultType="com.sky.entity.Employee">
        select * from employee
            <where>
                <if test="name != null and name != ''">
                    and name like concat('%',{name},'%')
                </if>
            </where>
                 order by create_time desc
</select>

总结: 在前面学习mvc的时候,其实就已经编写过很多次的查询,对于这个项目来说,没有什么特别的技术难点,但比前面系统学习过的分页查询来说,多了以下几个方面:
1.采用二次封装(封装了一次PageResult,后又封装成了Result)
2.采用了PageHelper类来快速计算出应该展示的数据,为开发节省时间

二.功能测试&代码完善

可以通过接口文档进行测试,也可以进行前后端联调测试,发现操作时间字段展示有问题,如下:
在这里插入图片描述
所以需要将日期格式化,使其显示清晰明了

解决方法有两种:
在这里插入图片描述
这里推荐方式二的原因是方式一只能在某个属性上进行日期格式化,当后面还需要格式化某个属性就还需要加注解,显得代码冗余和繁琐

方式二实现代码:

/**
     * 拓展spring MVC框架的消息转换器
     * @param converters
     */
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("拓展消息转换器...");
        //创建一个消息转换器对象
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        //需要为消息转换器是指一个对象转换器,对象转换器可以将Java对象序列化为json数据
        converter.setObjectMapper(new JacksonObjectMapper());
        //将自己的消息转换器加入容器中
        converters.add(0,converter);
    }

再次进行前后端联调测试:
在这里插入图片描述
日期格式化成功!

最后,将员工分页查询代码提交并推送到git仓库

三.启用禁用员工账号

需求分析和设计

业务规则:
1.可以对状态为“启用” 的员工账号进行“禁用”操作
2.可以对状态为“禁用”的员工账号进行“启用”操作
3.状态为“禁用”的员工账号不能登录系统(EmployeeServiceImpl已经实现)

在这里插入图片描述

补充:

路径参数地址栏传参是两种不同的方式,用于将参数传递给URL。它们的主要区别在于参数的传递方式和用途:

1.路径参数(Path Parameters):

传递方式:路径参数是通过将参数编码到URL的路径中而传递的,通常出现在URL的一部分,被包含在花括号 {} 中。例如,/example/{paramValue} 中的 {paramValue} 是一个路径参数。
用途:路径参数通常用于创建 RESTful 风格的URL,其中参数通常表示资源标识符或资源的属性。它们通常用于HTTP的GET请求,以请求特定资源或资源属性。
例如,一个使用路径参数的URL可以是:http://example.com/api/resource/123,其中 123 就是路径参数,用于请求标识为123的资源。

2.地址栏传参(Query Parameters):

传递方式:地址栏传参是通过将参数编码到URL的查询部分(通常以问号 ? 开头)而传递的。参数通常以键值对的形式出现,如 ?param1=value1&param2=value2。
用途:地址栏传参通常用于向服务器提交请求参数,这些参数可以用于过滤、排序、分页或任何其他与请求相关的参数。它们通常用于HTTP的GET请求,但也可用于POST请求。
例如,一个使用地址栏传参的URL可以是:http://example.com/api/resource?param1=value1&param2=value2,其中 param1 和 param2 是地址栏参数,用于请求服务器过滤或排序资源。

总之,路径参数主要用于标识资源或资源属性,而地址栏传参用于向服务器传递请求参数,用于过滤或控制请求的行为。选择使用哪种方式取决于您的应用程序需求和RESTful设计的原则。

一.代码开发&功能测试

1.根据接口设计中的请求参数形式对应的在 EmployeeController 中创建启用禁用员工账号的方法:

/**
     * 启用禁用员工账号
     * @param status
     * @param id
     * @return
     */
    @PostMapping("/status/{status}")
    @ApiOperation("启用禁用员工账号")
    public Result startOrStop(@PathVariable Integer status,Long id){
        log.info("启用禁用员工账号: {},{}",status,id);
        employeeService.startOrStop(status,id);
        return Result.success();
    }

2.在 EmployeeService 接口中声明启用禁用员工账号的业务方法:

/**
     * 启用禁用员工账号
     * @param status
     * @param id
     */
    void startOrStop(Integer status, Long id);

3.在 EmployeeServiceImpl 中实现启用禁用员工账号的业务方法:

/**
     * 启用禁用员工账号
     * @param status
     * @param id
     */
    public void startOrStop(Integer status, Long id) {

        Employee employee=Employee.builder()
                        .status(status)
                        .id(id)
                        .build();

        employeeMapper.update(employee);
    }

4.在 EmployeeMapper 接口中声明 update 方法:

/**
     * 根据主键动态修改属性
     */
    void update(Employee employee);

5.在 EmployeeMapper.xml 中编写SQL:

<update id="update" parameterType="employee">
        update employee
        <set>
            <if test="username != null">username = #{username},</if>
            <if test="name != null">name = #{name},</if>
            <if test="password != null">password = #{password},</if>
            <if test="phone != null">phone = #{phone},</if>
            <if test="sex != null">sex = #{sex},</if>
            <if test="idNumber != null">id_Number = #{idNumber},</if>
            <if test="updateTime != null">update_Time = #{updateTime},</if>
            <if test="updateUser != null">update_User = #{updateUser},</if>
            <if test="status != null">status = #{status},</if>
        </set>
        where id = #{id}
    </update>

功能测试:可以通过接口文档进行测试,最后完成前后端联调测试即可

总结:代码没有什么难度,最后在Mapper.xml中编写的是任意参数的更新语句

四.编辑员工

一.需求分析和设计

在这里插入图片描述
返回数据即回显数据(根据id查询员工信息)
在这里插入图片描述
返回数据即更新操作返回的信息

1.回显数据

需求分析和设计在 EmployeeController 中创建 getById 方法:

/**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询员工信息")
    public Result<Employee> getById(@PathVariable Long id){
        Employee employee=employeeService.getById(id);
        return Result.success(employee);
    }

在 EmployeeService 接口中声明 getById 方法:

/**
     * 根据id查询员工
     * @param id
     * @return
     */
    Employee getById(Long id);

在 EmployeeServiceImpl 中实现 getById 方法:

/**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    public Employee getById(Long id) {
        Employee employee=employeeMapper.getById(id);
        employee.setPassword("****");
        return employee;
    }

在 EmployeeMapper 接口中声明 getById 方法:

@Select("select * from employee where id = #{id}")
    Employee getById(Long id);

2.修改员工数据

在 EmployeeController 中创建 update 方法:

/**
     * 编辑员工信息
     * @param employeeDTO
     * @return
     */
    @PutMapping
    @ApiOperation("编辑员工信息")
    public Result update(@RequestBody EmployeeDTO employeeDTO){
        log.info("编辑员工信息:{}",employeeDTO);
        employeeService.update(employeeDTO);
        return Result.success();
    }

在 EmployeeService 接口中声明 update 方法:

/**
     * 编辑员工信息
     * @param employeeDTO
     */
    void update(EmployeeDTO employeeDTO);

在 EmployeeServiceImpl 中实现 update 方法:

 /**
     * 编辑员工信息
     * @param employeeDTO
     */
    public void update(EmployeeDTO employeeDTO) {
        Employee employee=new Employee();
        BeanUtils.copyProperties(employeeDTO,employee);

        employee.setUpdateTime(LocalDateTime.now());
        employee.setUpdateUser(BaseContext.getCurrentId());

        employeeMapper.update(employee);
    }

二.功能测试

通过Swagger接口文档进行测试,通过后再前后端联调测试即可

回忆:

1.resultType 属性用于指定一个 SQL 查询语句的执行结果应该映射为的 Java 对象类型。

2.parameterType 是用于指定 SQL 语句(例如insert, update, delete, select) 中的参数类型的属性。它定义了传递给 SQL 语句的参数类型,以便 MyBatis 在执行 SQL 语句时正确映射参数。
(1).指定参数类型:它指定了传递给 SQL 语句的参数的 Java 类型。这可以是一个完全限定的 Java 类型,例如 com.example.model.User,也可以是一个简单的 Java 类型,例如 int、String 等。
(2).传递参数:在 SQL 语句中,你可以使用 #{parameterName} 的语法引用参数,其中 parameterName 是你在 parameterType 中指定的参数类型中的属性。例如,如果 parameterType 是 com.example.model.User,你可以在 SQL 语句中使用 #{id}、#{name} 等引用 User 类的属性。

3.配置 type-aliases-package: com.sky.entity 表示想要使用 MyBatis 的自动扫描功能,将指定包中的所有类作为类型别名注册,这样在映射文件中就可以使用简化的别名引用这些类。

如果 com.sky.entity 包下包含了所有数据库对应的实体类,这个配置将会非常方便,因为你不需要为每个实体类手动定义别名。MyBatis 将自动为这个包下的每个类创建一个别名,通常是类名的首字母小写形式。

例如,如果有一个名为 com.sky.entity.User 的实体类,MyBatis 会自动将其别名设置为 user,这意味着可以在映射文件中使用 resultType=“user” 来引用 User 类,而不必使用完整的类名。

这种配置方式可以大大简化 MyBatis 映射文件的编写,减少了需要手动定义别名的工作,提高了可维护性。

五.导入分类模块功能代码

一.需求分析和设计

在这里插入图片描述
业务规则:
1.分类名称必须是唯一的
2.分类按照类型可以分为菜品分类和套餐分类
3.新添加的分类状态默认为“禁用”

在这里插入图片描述

二.代码导入

导入代码即可

三.功能测试

直接进行前后端联调测试即可

四.Q&A:

Q1:分类管理的mapper有三个:CatgoryMapper,DishMapper,SetmealMapper,为什么要引入后面两个mapper?

A:为了解答这个问题,可以用下面的关系图来解释:
在这里插入图片描述
在这张图里面,我们可以看到,其实分类管理关联了两张表,一张是菜品表,还有一张是套餐表,也就是说在进行分类管理删除某个分类,例如图上的酒水饮料,后端代码必须检查是否有关联了菜品(王老吉,北冰洋,雪花啤酒),如果关联了,那么就不能进行删除这项分类,其一是因为在后续的添加菜品的功能时,要选择加入的分类,其二是如果删除了分类,那么菜品无法进行管理(特别是像数据库里面的数据变得十分庞大的时候,误删除了一个分类后续操作想要菜品添加回分类后显得十分困难),所以说必须关联两张表判断分类是否为空,为空则可以进行删除.

Q2:我并没有在CategoryController看到EmployeeController类下面类似的按照id查询的方法,那进行修改分类的时候,数据是怎么样回显到修改页面上的呢?

A:虽然没有的回显操作,但回显通常在前端完成,而不是在后端控制器中进行。后端主要负责接收请求,处理请求,然后返回响应数据。回显通常涉及前端代码,例如前端页面的JavaScript代码或前端框架(如React、Angular、Vue.js)来显示已保存的数据。

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值