Bootstrap

瑞吉外卖学习笔记4

新增分类-需求分析&数据模型&代码开发&功能测试

需求分析

1、后台系统中可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类
2、当我们在后台系统中添加菜品时需要选择一个菜品分类
3、当我们在后台系统中添加一个套餐时需要选择一个套餐分类
4、在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐
可以在后台系统的分类管理页面分别添加菜品分类和套餐分类,如下:
新增菜品分类
在这里插入图片描述
新增套餐分类
在这里插入图片描述

数据模型

新增分类,其实就是将我们新增窗口录入的分类数据插入到category表,表结构如下:
在这里插入图片描述

代码分析

在开发业务功能前,先将需要用到的类和接口基本结构创建好:
在这里插入图片描述

1、实体类Category
2、Mapper接口CategoryMapper
4、业务层接口CategoryService
5、业务层实现类CategoryServicelmpl
6、控制层CategoryController

实体类Category

package com.peihj.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 分类
 */
@Data
public class Category implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;


    //类型 1 菜品分类 2 套餐分类
    private Integer type;


    //分类名称
    private String name;


    //顺序
    private Integer sort;


    //创建时间
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;


    //更新时间
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;


    //创建人
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;


    //修改人
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;


    //是否删除
//    private Integer isDeleted;

}

Mapper接口CategoryMapper

package com.peihj.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.peihj.reggie.entity.Category;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
}

业务层接口CategoryService

package com.peihj.reggie.service;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.peihj.reggie.entity.Category;

public interface CategoryService extends IService<Category> {


     // 添加自定义的service方法:(就是我们需要的业务mybatis没有提供,所以就需要自己另外在service创建新的方法,并且在相关的业务中实现)

    void remove(Long id);
}

业务层实现类CategoryServicelmpl

package com.itzq.reggie.service.Impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itzq.reggie.entity.Category;
import com.itzq.reggie.mapper.CategoryMapper;
import com.itzq.reggie.service.CategoryService;
import org.springframework.stereotype.Service;

@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
}


控制层CategoryController

package com.itzq.reggie.controller;

import com.itzq.reggie.common.R;
import com.itzq.reggie.entity.Category;
import com.itzq.reggie.service.CategoryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {

    @Autowired
    private CategoryService categoryService;

}


梳理一下整个程序的执行过程:
1、页面(backend/page/category/list.html)发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端
2、服务端Controller接收页面提交的数据并调用Service将数据进行保存
3、Service调用Mapper操作数据库,保存数据

可以看到新增菜品分类和新增套餐分类请求的服务端地址和提交的json数据结构相同,所以服务端只需要提供一个方法统一处理即可:

新增菜品分类:
在这里插入图片描述
新增套餐分类
在这里插入图片描述

在控制层CategoryController中添加逻辑代码

package com.itzq.reggie.controller;

import com.itzq.reggie.common.R;
import com.itzq.reggie.entity.Category;
import com.itzq.reggie.service.CategoryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {

    @Autowired
    private CategoryService categoryService;

    /**
     * 新增分类
     * @param category
     * @return
     */
    @PostMapping
    public R<String> save(@RequestBody Category category){
        log.info("category:{}",category);
        categoryService.save(category);
        return R.success("新增分类成功");
    }
}


前端传入的是菜品与分类,但是我们实体类使用了公共字段自动填充注解,于是公共字段会通过MyMetaObjecthandler自动填充,id是前端通过雪花算法自动生成的。于是整个实体类就都有了数据。

功能测试

自行查看一下,数据库与界面的情况。

启动项目,在新增菜品分类页面输入数据,点击保存,若提交相同分类名称(name字段),则会经过我们之前写的全局异常处理器类,报相关的错误

分类信息分页查询-需求分析&代码开发&功能测试

需求分析

系统中的分类很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。

代码开发

在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将分页查询参数(page、pageSize)提交到服务端
2、服务端Controller接收页面提交的数据并调用Service查询数据
3、Service调用Mapper操作数据库,查询分页数据
4、Controler将查询到的分页数据响应给页面
5、页面接收到分页数据并通过ElementUI的Table组件展示到页面上

前端页面分析如下:
在这里插入图片描述
在CategoryController类上添加page方法

/**
     * 分页查询
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("/page")
    public R<Page> page(int page, int pageSize){
        //分页构造器
        Page<Category> pageInfo = new Page<>(page,pageSize);
        //条件构造器
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
        //添加排序条件
        queryWrapper.orderByAsc(Category::getSort);
        //分页查询
        categoryService.page(pageInfo,queryWrapper);
        return R.success(pageInfo);
    }

功能测试
点击分类管理,客户端发送请求给服务端,服务端接收数据,进行分页处理后,返回数据给页面,页面通过相应组件处理
在这里插入图片描述疑问:页面返回的数据type数据为int类型,为什么页面展示出来的是文字?
因为返回的数据在前端做了相应的判断处理,再回显到页面
在这里插入图片描述

删除分类-需求分析&代码开发&功能测试

需求分析

1、在分类管理列表页面,可以对某个分类进行删除操作
2、需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除

代码开发

在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将参数(id)提交到服务端
2、服务端Controller接收页面提交的数据并调用Service删除数据
3、Service调用Mapper操作数据库

在这里插入图片描述
需要注意教程文档中给出的url的id参数为ids,需要在category.js中将参数改为id,以便于与后端接收数据做好相应的映射处理,若还是不能正常访问,清除浏览器缓存

在CategoryController类上添加delete方法
在这里插入图片描述
在这里插入图片描述

功能测试

启动项目,点击删除按钮,弹出一个对话框,并点击确认,页面会重新发送分页请求,在页面将看不见该行数据,查看数据库也没有该行数据显示,删除数据成功。

删除分类-功能完善

功能完善

前面我们已经实现了根据id删除分类的功能,但是并没有检查删除的分类是否关联了菜品或者套餐,所以我们需要进行功能完善。
要完善分类删除功能,需要先准备基础的类和接口︰
1、实体类Dish和Setmeal
2、Mapper接口DishMapper和setmealMapper
3、Service接口DishService和SetmealService
4、Service实现类DishServicelmpl和SetmealServicelmpl

实体类Dish和Setmeal

package com.itzq.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 菜品
 */
@Data
public class Dish implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;


    //菜品名称
    private String name;


    //菜品分类id
    private Long categoryId;


    //菜品价格
    private BigDecimal price;


    //商品码
    private String code;


    //图片
    private String image;


    //描述信息
    private String description;


    //0 停售 1 起售
    private Integer status;


    //顺序
    private Integer sort;


    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;


    @TableField(fill = FieldFill.INSERT)
    private Long createUser;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;


    //是否删除
    private Integer isDeleted;

}

package com.itzq.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 套餐
 */
@Data
public class Setmeal implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;


    //分类id
    private Long categoryId;


    //套餐名称
    private String name;


    //套餐价格
    private BigDecimal price;


    //状态 0:停用 1:启用
    private Integer status;


    //编码
    private String code;


    //描述信息
    private String description;


    //图片
    private String image;


    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;


    @TableField(fill = FieldFill.INSERT)
    private Long createUser;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;


    //是否删除
    private Integer isDeleted;
}


Mapper接口DishMapper和SetmealMapper
DishMapper

package com.itzq.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itzq.reggie.entity.Dish;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface DishMapper extends BaseMapper<Dish> {
}


SetmealMapper

package com.itzq.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itzq.reggie.entity.Setmeal;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface SetmealMapper extends BaseMapper<Setmeal> {
}


Service接口DishService和SetmealService
DishService

package com.itzq.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itzq.reggie.entity.Dish;

public interface DishService extends IService<Dish> {
}


SetmealService

package com.itzq.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itzq.reggie.entity.Setmeal;

public interface SetmealService extends IService<Setmeal> {
}


Service实现类DishServicelmpl和SetmealServicelmpl

DishServicelmpl

package com.itzq.reggie.service.Impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itzq.reggie.entity.Dish;
import com.itzq.reggie.mapper.DishMapper;
import com.itzq.reggie.service.DishService;
import org.springframework.stereotype.Service;

@Service
public class DishServicelmpl extends ServiceImpl<DishMapper, Dish> implements DishService {
}


SetmealServicelmpl

package com.itzq.reggie.service.Impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itzq.reggie.entity.Setmeal;
import com.itzq.reggie.mapper.SetmealMapper;
import com.itzq.reggie.service.SetmealService;
import org.springframework.stereotype.Service;

@Service
public class SetmealServicelmpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {
}


添加自定义方法

添加自定义的service方法:(就是我们需要的业务mybatis没有提供,所以就需要自己另外在service创建新的方法,并且在相关的业务中实现),在CategoryService中定义自己需要的方法,直接写就行:

package com.peihj.reggie.service;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.peihj.reggie.entity.Category;

public interface CategoryService extends IService<Category> {


     // 添加自定义的service方法:(就是我们需要的业务mybatis没有提供,所以就需要自己另外在service创建新的方法,并且在相关的业务中实现)

    void remove(Long id);
}

在CategoryService实现类中重写该方法,同时自定义异常类,因为这里需要抛异常了:
自定义业务异常类:
在这里插入图片描述

package com.peihj.reggie.common;

// 自定义业务异常类
public class CustomException extends RuntimeException{
    public CustomException(String message){
        super(message);
    }
}

然后在外面前面写的GlobalExceptionHandler全局异常捕获器中添加该异常,这样就可以把相关的异常信息显示给前端操作的人员看见,并处理自定义的异常,为了让前端展示我们的异常信息,这里需要把异常进行全局捕获,然后返回给前端

package com.peihj.reggie.common;


import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.sql.SQLIntegrityConstraintViolationException;

/*
* 全局异常处理
* */
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody  // 将结果通过jsion形式输出
@Slf4j
public class GlobalExceptionHandler {
    /*
    * 异常处理方法
    * */
    @ExceptionHandler(value = SQLIntegrityConstraintViolationException.class)
    public R<String> 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("未知错误");
    }

    // 然后在外面前面写的GlobalExceptionHandler全局异常捕获器中添加该异常,这样就可以把相关的异常信息显示给前端操作的人员看见
    // 处理自定义的异常,为了让前端展示我们的异常信息,这里需要把异常进行全局捕获,然后返回给前端
    @ExceptionHandler(CustomException.class)
    public R<String> exceptionHandler(CustomException ex){
        log.error(ex.getMessage());

        return R.error(ex.getMessage());
    }
}

逻辑:查看当前要删除的分类id是否与菜品或套餐相关联,若与其中一个关联,则抛出异常

package com.peihj.reggie.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.peihj.reggie.common.CustomException;
import com.peihj.reggie.entity.Category;
import com.peihj.reggie.entity.Dish;
import com.peihj.reggie.entity.Setmeal;
import com.peihj.reggie.mapper.CategoryMapper;
import com.peihj.reggie.service.CategoryService;
import com.peihj.reggie.service.DishService;
import com.peihj.reggie.service.SetmealService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CategoryServicelmpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
    @Autowired
    DishService dishService;

    @Autowired
    SetmealService setmealService;


    @Override
    public void remove(Long id) {
        /*
        * 逻辑:查看当前要删除的分类id是否与菜品或套餐相关联,若与其中一个关联,则抛出异常
        * */

        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        // 添加dish查询条件
        queryWrapper.eq(Dish::getCategoryId,id);
        //注意:这里使用count方法的时候一定要传入条件查询的对象,否则计数会出现问题,计算出来的是全部的数据的条数
        int count = dishService.count(queryWrapper);

        // 查//查询当前分类是否关联了菜品,如果已经管理,直接抛出一个业务异常
        if (count > 0){
            // 已关联了菜品,抛出异常
            throw new CustomException("当前分类下关联了菜品,不能删除");
        }

        // 查询当前分类是否关联了套餐,如果已经管理,直接抛出一个业务异常
        LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
        int count1 = setmealService.count(setmealLambdaQueryWrapper);

        if (count1 > 1){
            throw new CustomException("当前分类下关联了套餐,不能删除");
        }
        // 正常删除
        super.removeById(id);


    }
}

因为dish表和setnmeal表关联有属性category_id,如何判断表与表之间是否关联属性主要就是靠,这个属性。因为运行过程中会再控制台执行输出:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

测试

删除川菜
在这里插入图片描述

同理,若当前分类关联了套餐,也会有相应的提示,可以自己操作检验

修改分类-需求分析&分析页面回显效果&代码开发&功能测试

需求分析

在分类管理列表页面点击修改按钮,弹出修改窗口,在修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改操作

分析页面回显效果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
v-model实现数据的双向绑定
在这里插入图片描述

代码开发

在CategoryController类中添加update方法,请求方式为put请求
在这里插入图片描述

/**
     * 修改分类信息
     * @param category
     * @return
     */
    @PutMapping
    public R<String> update(@RequestBody Category category){
        log.info("修改分类信息为:{}",category);
        categoryService.updateById(category);
        return R.success("修改分类信息成功");
    }

功能测试

点击修改按钮,填入想要修改的信息后,点击确定,页面显示,数据库中内容,如果都变了,那么证明修改成功!

参考

https://blog.csdn.net/eadzsdad/article/details/124231440
https://blog.csdn.net/weixin_53142722/article/details/124356412?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165968621016781432979539%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=165968621016781432979539&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-124356412-null-null.142v39new_blog_pos_by_title,185v2control&utm_term=%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96%E9%A1%B9%E7%9B%AE%E9%BB%91%E9%A9%AC&spm=1018.2226.3001.4187

;