目录

一 管理端员工的相关接口

接口1 实现员工登录

接口2 实现员工退出

接口3 实现新增员工

接口4 员工分页查询

接口5 启用禁用员工账号

接口6 根据id查询员工

接口7 编辑员工信息

二 菜品相关接口

接口1 新增菜品

接口2 修改菜品

接口3 删除菜品

接口4 根据分类的id查询菜品

接口5 菜品分页查询

接口6 根据id查询菜品和口味信息

三 套餐管理相关接口

接口1 新增套餐

接口2 修改套餐

接口3 套餐分页查询

接口4 套餐批量删除

接口5 根据id查询套餐(回显)

接口6 套餐的启售与停售


一 管理端员工的相关接口

三层架构模式(Controller控制层、Service业务层、Mapper持久层)

1 控制层EmployeeController 

package com.sky.controller.admin;

import com.sky.constant.JwtClaimsConstant;
import com.sky.dto.EmployeeDTO;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.dto.EmployeePageQueryDTO;
import com.sky.entity.Employee;
import com.sky.properties.JwtProperties;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.EmployeeService;
import com.sky.utils.JwtUtil;
import com.sky.vo.EmployeeLoginVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

/**
 * 员工管理
 */
@RestController
@Tag(name = "员工相关接口")
@RequestMapping("/admin/employee")
@Slf4j
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;
    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 登录
     *
     * @param employeeLoginDTO
     * @return
     */
    @PostMapping("/login")
    @Operation(summary = "员工登录")
    @Tag(name = "员工相关接口")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        log.info("员工登录:{}", employeeLoginDTO);

        Employee employee = employeeService.login(employeeLoginDTO);

        //登录成功后,生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);

        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();

        return Result.success(employeeLoginVO);
    }

    /**
     * 退出
     *
     * @return
     */
    @Operation(summary = "员工退出")
    @PostMapping("/logout")
    @Tag(name = "员工相关接口")
    public Result<String> logout() {
        return Result.success();
    }

    /**
     * 新增员工
     *
     * @param employeeDto
     * @return
     */

    @Operation(summary = "新增员工")
    @Tag(name = "员工相关接口")
    @PostMapping
    public Result save(@RequestBody EmployeeDTO employeeDto) {//Json格式的参数,用@RequestBody接收
        log.info("新增员工:{}", employeeDto);
        employeeService.save(employeeDto);
        return Result.success();
    }
//    问No mapping for POST /admin/employee怎么解决的,看下你的save方法上面的注解@PostMapping后面有没有("/save"),有的话要删了

    /**
     * 员工分页查询
     *
     * @param employeePageQueryDTO
     * @return
     */
    @Operation(summary = "员工分页查询")
    @Tag(name = "员工相关接口")
    @GetMapping("/page")
    public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO) {
        log.info("员工分页查询:{}", employeePageQueryDTO);
        PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
        return Result.success(pageResult);
    }

    /**
     * 启用禁用员工账号
     *
     * @param status
     * @param id
     * @return
     */

    @Tag(name = "员工相关接口")
    @Operation(summary = "启用禁用员工账号")
    @PostMapping("/status/{status}")
    public Result startOrStop(@PathVariable("status") Integer status, Long id) {
        log.info("启用禁用员工账号:{},{}", status, id);
        employeeService.startOrStop(status, id);
        return Result.success();
    }

    /**
     * 根据id查询员工
     *
     * @param id
     * @return
     */
    @Operation(summary = "根据id查询员工")
    @Tag(name = "员工相关接口")
    @GetMapping("/{id}")
    public Result<Employee> getById(@PathVariable Long id) {
        log.info("根据id查询员工:{}", id);
        Employee employee = employeeService.getById(id);
        return Result.success(employee);
    }

    /**
     * 编辑员工信息
     *
     * @param employeeDTO
     * @return
     */
    @Tag(name = "员工相关接口")
    @Operation(summary = "编辑员工信息")
    @PutMapping
    public Result update(@RequestBody EmployeeDTO employeeDTO) {
        log.info("编辑员工信息:{}", employeeDTO);
        employeeService.update(employeeDTO);
        return Result.success();
    }
}

职责:处理HTTP请求,协调请求与响应

分析:

接口1 实现员工登录

    /**
     * 登录
     *
     * @param employeeLoginDTO
     * @return
     */
    @PostMapping("/login")
    @Operation(summary = "员工登录")
    @Tag(name = "员工相关接口")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        log.info("员工登录:{}", employeeLoginDTO);

        Employee employee = employeeService.login(employeeLoginDTO);

        //登录成功后,生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);

        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();

        return Result.success(employeeLoginVO);
    }

1 @RequestBody EmployeeLoginDTO employeeLoginDTO 作用是借助@RequestBoby的作用将客户端发送的HTTP 请求体中的数据绑定到控制器方法的参数上,使其自动封装给指定的对象EmployeeLogin

package com.sky.dto;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;

@Data
@ApiModel(description = "员工登录时传递的数据模型")
public class EmployeeLoginDTO implements Serializable {

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("密码")
    private String password;

}

2 执行员工登录功能

Employee employee = employeeService.login(employeeLoginDTO);

进入service服务层

    /**
     * 员工登录
     *
     * @param employeeLoginDTO
     */
    Employee login(EmployeeLoginDTO employeeLoginDTO);

实现类

    /**
     * 员工登录
     *
     * @param employeeLoginDTO
     * @return
     */
    public Employee login(EmployeeLoginDTO employeeLoginDTO) {
        String username = employeeLoginDTO.getUsername();
        String password = employeeLoginDTO.getPassword();

        //1、根据用户名查询数据库中的数据
        Employee employee = employeeMapper.getByUsername(username);

        //2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)
        if (employee == null) {
            //账号不存在
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        //密码比对
        // TODO 后期需要进行md5加密,然后再进行比对
        password = DigestUtils.md5DigestAsHex(password.getBytes());
        if (!password.equals(employee.getPassword())) {
            //密码错误
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }

        if (employee.getStatus() == StatusConstant.DISABLE) {
            //账号被锁定
            throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
        }

        //3、返回实体对象
        return employee;
    }

这里也需要Mapper层对数据库的访问处理

    @Autowired
    private EmployeeMapper employeeMapper;

先将DTO中传输的数据获取出来,用于后续的判断

        String username = employeeLoginDTO.getUsername();
        String password = employeeLoginDTO.getPassword();

调用Mapper层的方法对用户名进行查询

Employee employee = employeeMapper.getByUsername(username);

Mapper层(这里的语句较为简单直接使用注解,就没有使用xml进行查询了)

    /**
     * 根据用户名查询员工
     * @param username
     * @return
     */
    @Select("select * from employee where username = #{username}")
    Employee getByUsername(String username);

获取之后进行判断(账号对象是否为空)

        if (employee == null) {
            //账号不存在
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

(密码是否正确,错误的话抛出异常)

        password = DigestUtils.md5DigestAsHex(password.getBytes());
        if (!password.equals(employee.getPassword())) {
            //密码错误
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }

(账号状态是否被锁定,锁定的话抛出异常)

        if (employee.getStatus() == StatusConstant.DISABLE) {
            //账号被锁定
            throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
        }

(如果前面都正常通过校验则登录成功返回实体对象)

return employee;

3 员工登陆成功,生成jwt令牌

生成jwt令牌的作用主要是便于后续进行操作的便携性,解决了登陆状态的持续验证问题。这里的实现是后端借助前端登录的信息,生成token,后续用户再访问客户端的其他功能,服务端只需要验证其token即可。验证过程:使用相同的密钥对Token进行签名验证,同时还会验证其是否过期

        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);

大致生成的token(键值对的先后顺序不影响)

{
  "emp_id": 12345,
  "exp": 1698765432, // 过期时间(由 jwtProperties.getAdminTtl() 决定)
  "iat": 1698761832   // 签发时间(通常由库自动生成)
}

token是借助密钥生成的,密钥也用于验证token。后端只存储密钥,token由前端存储。(这里是存储在配置文件当中的密钥)

  jwt:
    # 设置jwt签名加密时使用的秘钥
    admin-secret-key: itcast
    # 设置jwt过期时间
    admin-ttl: 7200000
    # 设置前端传递过来的令牌名称
    admin-token-name: token

将后端对象转换为前端需要的响应格式,同时利用构建器模式提升代码的可维护性和可读性。

        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();

将登录成功的员工信息(employeeLoginVO)封装到 Result 对象中,响应给前端

return Result.success(employeeLoginVO);

大致的响应样式

{
    "code": 200,
    "message": "成功",
    "data": {
        "id": 123,
        "userName": "john_doe",
        "name": "John Doe",
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
}

接口2 实现员工退出

    /**
     * 退出
     *
     * @return
     */
    @Operation(summary = "员工退出")
    @PostMapping("/logout")
    @Tag(name = "员工相关接口")
    public Result<String> logout() {
        return Result.success();
    }

接口3 实现新增员工

    /**
     * 新增员工
     *
     * @param employeeDto
     * @return
     */

    @Operation(summary = "新增员工")
    @Tag(name = "员工相关接口")
    @PostMapping
    public Result save(@RequestBody EmployeeDTO employeeDto) {//Json格式的参数,用@RequestBody接收
        log.info("新增员工:{}", employeeDto);
        employeeService.save(employeeDto);
        return Result.success();
    }

分析:

调用service接口的方法

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

实现类

    /**
     * 新增员工
     *
     * @param employeeDto
     */

    @Override
    public void save(EmployeeDTO employeeDto) {
        Employee employee = new Employee();
        //使用BeanUtils.copyProperties方法将employeeDto中的属性值复制到employee对象中
        BeanUtils.copyProperties(employeeDto, employee);
        //设置状态
        employee.setStatus(StatusConstant.ENABLE);
        //设置密码,使用默认密码(md5加密)
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
        
        employeeMapper.insert(employee);
    }

接收前端传递过来的员工信息

这里的DTO是前端传递过来的employee对象,里面的很多属性没有实现,我们需要对其先进行拷贝,再设置其状态(默认启用),再对其密码进行md5加密,调用Mapper层的insert将其存储再数据库的用户表当中。同时这里还使用了切面编程,将四个时间添加上。

    /**
     * 插入员工数据
     * @param employee
     */
    @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})")
    @AutoFill(OperationType.INSERT)
    void insert(Employee employee);

接口4 员工分页查询

    /**
     * 员工分页查询
     *
     * @param employeePageQueryDTO
     * @return
     */
    @Operation(summary = "员工分页查询")
    @Tag(name = "员工相关接口")
    @GetMapping("/page")
    public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO) {
        log.info("员工分页查询:{}", employeePageQueryDTO);
        PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
        return Result.success(pageResult);
    }

employeeDTO

@Data
public class EmployeePageQueryDTO implements Serializable {

    //员工姓名
    private String name;

    //页码
    private int page;

    //每页显示记录数
    private int pageSize;

}

调用Service业务层的接口

PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);

分页查询service接口

    /**
     * 分页查询
     *
     * @param employeePageQueryDTO
     * @return
     */

    PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

实现类

    /**
     * 员工分页查询
     * @param employeePageQueryDTO
     * @return
     */

    @Override
    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 启动分页,自动拦截后续的 SQL 查询并添加分页逻辑(LIMIT 子句)。

参数

employeePageQueryDTO.getPage():当前页码(从 第1页 开始)。

employeePageQueryDTO.getPageSize():每页记录数(如 10、20)。

底层原理
PageHelper 通过 ThreadLocal 保存分页参数,后续的 第一个 SQL 查询会被自动分页。

设置分页参数页码与尺寸大小,调用Mapper接口的查询方法

    /**
     * 分页查询
     * @param employeePageQueryDTO
     */
    Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

MapperXML文件

    <!--  信息的查询  -->
    <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>

查询结束后返回PageResult对象,参数是数据条数与数据组成的集合

接口5 启用禁用员工账号

    /**
     * 启用禁用员工账号
     *
     * @param status
     * @param id
     * @return
     */

    @Tag(name = "员工相关接口")
    @Operation(summary = "启用禁用员工账号")
    @PostMapping("/status/{status}")
    public Result startOrStop(@PathVariable("status") Integer status, Long id) {
        log.info("启用禁用员工账号:{},{}", status, id);
        employeeService.startOrStop(status, id);
        return Result.success();
    }

1 PathVariable注释的作用是从url路径当中获取参数

@PathVariable("status") Integer status, Long id

2 调用服务层的方法

employeeService.startOrStop(status, id);

service接口

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

接口的实现类构建一个对象将传递的状态值赋值给对象,调用Mapper层的数据库操作方法

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

        //使用构建器(创建对象设置属性)
        Employee employee = Employee.builder()
                .status(status)
                .id(id)
                .build();
        employeeMapper.update(employee);
    }

Mapper接口(这里的底层就是对数据库信息的更新,同时也使用了切面编程处理时间这些公共字段的更新操作

注解标记方法 + 切面拦截)

    /**
     * 修改员工信息
     * @param employee
     */
    @AutoFill(OperationType.UPDATE)
    void update(Employee employee);

MapperXML文件

    <!--  信息的更新  -->
    <update id="update">
        update employee
        <set>
            <if test="name != null">name = #{name},</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="status != null">status = #{status},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="updateUser != null">update_user = #{update}</if>
        </set>
        where id = #{id}
    </update>

接口6 根据id查询员工

    /**
     * 根据id查询员工
     *
     * @param id
     * @return
     */
    @Operation(summary = "根据id查询员工")
    @Tag(name = "员工相关接口")
    @GetMapping("/{id}")
    public Result<Employee> getById(@PathVariable Long id) {
        log.info("根据id查询员工:{}", id);
        Employee employee = employeeService.getById(id);
        return Result.success(employee);
    }

1 @PathVariable从url请求当中获取参数id

public Result<Employee> getById(@PathVariable Long id)

2 调用service的查询方法返回值为Employee对象

    /**
     * 根据id查询员工
     *
     * @param id
     * @return
     */

    Employee getById(Long id);

3 接口的实现类(出于对安全性的考虑,这里要对密码进行隐藏处理)

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

4 对应的Mapper接口类

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

5 MapperXML文件

    <!--  根据id查询员工  -->
    <select id="getById" resultType="com.sky.entity.Employee">
        select *
        from employee
        where id = #{id}
    </select>

接口7 编辑员工信息

    /**
     * 编辑员工信息
     *
     * @param employeeDTO
     * @return
     */
    @Tag(name = "员工相关接口")
    @Operation(summary = "编辑员工信息")
    @PutMapping
    public Result update(@RequestBody EmployeeDTO employeeDTO) {
        log.info("编辑员工信息:{}", employeeDTO);
        employeeService.update(employeeDTO);
        return Result.success();
    }

1 @RequestBoby的作用:将HTTP请求体当中的参数数据自动绑定到指定的java对象当中。

public Result update(@RequestBody EmployeeDTO employeeDTO)

2 调用Service服务层的方法

employeeService.update(employeeDTO);

3 service服务层

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

4 service的实现类

    /**
     * 编辑员工信息
     *
     * @param employeeDTO
     */
    @Override
    public void update(EmployeeDTO employeeDTO) {
        //使用BeanUtils.copyProperties方法将employeeDto中的属性值复制到employee对象中
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO, employee);
        employeeMapper.update(employee);
    }

使用BeanUtils实现对象拷贝

BeanUtils.copyProperties(employeeDTO, employee);

在使用Mapper接口当中的更新方法

employeeMapper.update(employee);

Mapper接口方法

    /**
     * 修改员工信息
     * @param employee
     */
    @AutoFill(OperationType.UPDATE)
    void update(Employee employee);

MapperXML文件SQL实现

    <!--  信息的更新  -->
    <update id="update">
        update employee
        <set>
            <if test="name != null">name = #{name},</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="status != null">status = #{status},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="updateUser != null">update_user = #{update}</if>
        </set>
        where id = #{id}
    </update>

二 菜品相关接口

三层架构模式(Controller控制层、Service业务层、Mapper持久层)

控制层DishController

package com.sky.controller.admin;

import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Slf4j
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
public class DishController {

    @Autowired
    private DishService dishService;

    /**
     * 新增菜品
     *
     * @param dishDTO
     * @return
     */

    @ApiOperation("新增菜品")
    @PostMapping
    public Result save(@RequestBody DishDTO dishDTO) {
        log.info("新增菜品:{}", dishDTO);
        dishService.saveWithFlavor(dishDTO);
        return Result.success();
    }

    /**
     * 菜品分页查询
     *
     * @param dishPageQueryDTO
     * @return
     */
    @ApiOperation("菜品分页查询")
    @GetMapping("/page")
    public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
        log.info("菜品分页查询:{}", dishPageQueryDTO);
        PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
        return Result.success(pageResult);
    }

    /**
     * 菜品删除
     *
     * @param ids
     * @return
     */
    @ApiOperation("菜品删除")
    @DeleteMapping
    public Result delete(@RequestParam List<Long> ids) {
        log.info("菜品删除:{}", ids);
        dishService.deleteBatch(ids);
        return Result.success();
    }

    /**
     * 根据id查询菜品和对应的口味信息
     * @param id
     * @return
     */

    @ApiOperation("根据id查询菜品和口味信息")
    @GetMapping("/{id}")
    public Result<DishVO> getById(@PathVariable Long id) {
        log.info("根据id查询菜品信息:{}", id);
        DishVO dishVO = dishService.getByIdWithFlavor(id);
        return Result.success(dishVO);
    }
    /**
     * 修改菜品
     * @param dishDTO
     * @return
     */

    @ApiOperation("修改菜品")
    @PutMapping
    public Result update(@RequestBody DishDTO dishDTO) {
        log.info("修改菜品:{}", dishDTO);
        dishService.updateWithFlavor(dishDTO);
        return Result.success();
    }

    /**
     * 根据分类id查询菜品
     * @param categoryId
     * @return
     */
    @ApiOperation("根据分类id查询菜品")
    @GetMapping("/list")
    public Result<List<Dish>> list(Long categoryId) {
        log.info("根据分类id查询菜品:{}", categoryId);
        // 获取这个分类下的菜品
        List<Dish> dishVOList = dishService.list(categoryId);
        return Result.success(dishVOList);
    }


}

接口1 新增菜品

    /**
     * 新增菜品
     *
     * @param dishDTO
     * @return
     */

    @ApiOperation("新增菜品")
    @PostMapping
    public Result save(@RequestBody DishDTO dishDTO) {
        log.info("新增菜品:{}", dishDTO);
        dishService.saveWithFlavor(dishDTO);
        return Result.success();
    }

1 @RequestBoby将请求体当中的参数自动封装到DishDTO

public Result save(@RequestBody DishDTO dishDTO)

2 调用Service层当中的新增菜品的方法

dishService.saveWithFlavor(dishDTO);

3 Service服务层

    /**
     * 新增菜品和对应的口味数据
     *
     * @param dishDTO
     */
    void saveWithFlavor(DishDTO dishDTO);

4 重写方法

    /**
     * 新增菜品和对应的口味数据
     *
     * @param dishDTO
     */
    @Override
    @Transactional
    public void saveWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);
        //向菜品表中插入1条数据
        dishMapper.insert(dish);
        //获取菜品id
        Long dishId = dish.getId();
        //向口味表插入n条数据
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && !flavors.isEmpty()) {
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });
            // 向口味表插入n条数据
            dishFlavorMapper.insertBatch(flavors);
            log.info("新增菜品:{},新增口味:{}", dish, flavors);
        }
    }

首先对象拷贝将传递过来的DishDTO菜品对象拷贝给Dish

        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);

调用Mapper接口的方法插入数据

dishMapper.insert(dish);

Mapper接口

    /**
     * 插入数据
     *
     * @return
     */
    // 公共字段自动填充
    @AutoFill(OperationType.INSERT)
    void insert(Dish dish);

MapperXML文件(这里需要写上useGenerateKeys="true" keyProperty="id"  可以高效地解决插入数据后获取自增主键的需求,避免了手动查询最新主键的繁琐操作。因为后面还有对这个菜品添加对应的口味数据。)

    <!--  useGenerateKeys获取主键值  -->
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into dish(name, category_id, price, image, description, status, create_time, update_time, create_user,
                         update_user)
        values (#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{status}, #{createTime}, #{updateTime},
                #{createUser}, #{updateUser})
    </insert>

向口味表当中添加数据(DTODish对象当中还有口味信息,在之前的拷贝过程当中已经将一些其他的信息拷贝完成,现在只需要将其中的口味进行存储即可)

        //获取菜品id
        Long dishId = dish.getId();
        //向口味表插入n条数据
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && !flavors.isEmpty()) {
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });
            // 向口味表插入n条数据
            dishFlavorMapper.insertBatch(flavors);
            log.info("新增菜品:{},新增口味:{}", dish, flavors);
        }

首先需要遍历口味将菜品的dishId给每种口味赋值上,然后执行插入操作

dishFlavorMapper.insertBatch(flavors);

调用口味的Mapper接口(传入的是口味的集合,实现批量插入)

    /**
     * 批量插入口味数据
     *
     * @param flavors
     */


    void insertBatch(List<DishFlavor> flavors);

口味的MapperXML文件

    <!--  实现口味的批量插入  -->
    <insert id="insertBatch">
        insert into dish_flavor (dish_id, name, value) values
        <foreach collection="flavors" item="flavor" separator=",">
            (#{flavor.dishId},#{flavor.name},#{flavor.value})
        </foreach>
    </insert>

接口2 修改菜品

    /**
     * 修改菜品
     * @param dishDTO
     * @return
     */

    @ApiOperation("修改菜品")
    @PutMapping
    public Result update(@RequestBody DishDTO dishDTO) {
        log.info("修改菜品:{}", dishDTO);
        dishService.updateWithFlavor(dishDTO);
        return Result.success();
    }

1 RequestBoby将HTTP请求当中的参数数据封装成DishDTO对象

public Result update(@RequestBody DishDTO dishDTO)

2 调用Service服务层的更新方法

dishService.updateWithFlavor(dishDTO);

3 Service服务层接口方法

    /**
     * 根据id修改菜品的基本信息和口味信息
     *
     * @param dishDTO
     */
    void updateWithFlavor(DishDTO dishDTO);

4 Service服务层实现类重写方法

    /**
     * 根据id修改菜品的基本信息和口味信息
     *
     * @param dishDTO
     */
    @Transactional
    @Override
    public void updateWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);//将dishDTO中的数据拷贝到dish中(这里知识修改菜品表的基本信息,不需要对口味进行修改)
        // 修改菜品表基本信息
        dishMapper.update(dish);

        // 删除原有口味信息
        dishFlavorMapper.deleteByDishId(dishDTO.getId());

        // 重新添加口味信息
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && !flavors.isEmpty()) {
            flavors.forEach(dishFlavor -> dishFlavor.setDishId(dishDTO.getId()));
            dishFlavorMapper.insertBatch(flavors);
        }
        log.info("修改菜品:{}", dishDTO);
    }

实现对菜品基础信息的拷贝(这里暂时不会对口味进行处理)

        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);//将dishDTO中的数据拷贝到dish中(这里知识修改菜品表的基本信息,不需要对口味进行修改)

修改菜品的基础信息

        // 修改菜品表基本信息
        dishMapper.update(dish);

Mapper层接口

    /**
     * 根据id修改菜品基本数据
     * @param dish
     */
    @AutoFill(OperationType.UPDATE)
    void update(Dish dish);

Mapper层XML文件

    <!--  根据id动态修改菜品数据  -->
    <update id="update">
        update dish
        <set>
            <if test="name != null">
                name = #{name},
            </if>
            <if test="categoryId != null">
                category_id = #{categoryId},
            </if>
            <if test="price != null">
                price = #{price},
            </if>
            <if test="image != null">
                image = #{image},
            </if>
            <if test="description != null">
                description = #{description},
            </if>
            <if test="status != null">
                status = #{status},
            </if>
            <if test="updateTime != null">
                update_time = #{updateTime},
            </if>
            <if test="updateUser != null">
                update_user = #{updateUser}
            </if>
        </set>
        where id = #{id}
    </update>

删除原有口味信息(根据菜品id)

        // 删除原有口味信息
        dishFlavorMapper.deleteByDishId(dishDTO.getId());

口味Mapper层接口

    /**
     * 根据菜品id删除对应的口味数据
     *
     * @param dishId
     */
    @Delete("delete from dish_flavor where dish_id = #{dishId}")
    void deleteByDishId(Long dishId);

口味的重新添加,先将口味的属性赋值上菜品的Id,便于匹配。然后再调用方法添加口味

        // 重新添加口味信息
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && !flavors.isEmpty()) {
            flavors.forEach(dishFlavor -> dishFlavor.setDishId(dishDTO.getId()));
            dishFlavorMapper.insertBatch(flavors);
        }
        log.info("修改菜品:{}", dishDTO);

Mapper

    /**
     * 批量插入口味数据
     *
     * @param flavors
     */


    void insertBatch(List<DishFlavor> flavors);
    <!--  实现口味的批量插入  -->
    <insert id="insertBatch">
        insert into dish_flavor (dish_id, name, value) values
        <foreach collection="flavors" item="flavor" separator=",">
            (#{flavor.dishId},#{flavor.name},#{flavor.value})
        </foreach>
    </insert>

接口3 删除菜品

    /**
     * 菜品删除
     *
     * @param ids
     * @return
     */
    @ApiOperation("菜品删除")
    @DeleteMapping
    public Result delete(@RequestParam List<Long> ids) {
        log.info("菜品删除:{}", ids);
        dishService.deleteBatch(ids);
        return Result.success();
    }

1 @RequestParam将url路径的查询参数获取

public Result delete(@RequestParam List<Long> ids)

2 调用Service层的删除方法

dishService.deleteBatch(ids);

3 Service接口

    /**
     * 菜品的批量删除功能
     *
     * @param ids
     */

    void deleteBatch(List<Long> ids);

4 Service实现类

    /**
     * 批量删除菜品
     *
     * @param ids
     */
    @Transactional
    @Override
    public void deleteBatch(List<Long> ids) {
        //判断当前菜品是否在售
        for (Long id : ids) {
            Dish dish = dishMapper.getById(id);//根据id查询菜品数据(添加方法)
            if (dish.getStatus() == 1) {
                //如果菜品处于启售状态,则不能删除
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        }
        //判断当前菜品是否被套餐关联
        List<Long> dishIds = setMealDishMapper.getSetMealIdsByDishIds(ids);//根据菜品id查询套餐id(添加方法)
        if (dishIds != null && !dishIds.isEmpty()) {
            //如果存在关联的套餐,则不能删除
            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
        }

        //批量删除菜品
        dishMapper.deleteByIds(ids);
        //批量删除菜品对应的口味数据
        dishFlavorMapper.deleteByDishIds(ids);
        
    }

根据菜品的id进行遍历,获取其状态值判断是否在售,在售卖则不能删除

        //判断当前菜品是否在售
        for (Long id : ids) {
            Dish dish = dishMapper.getById(id);//根据id查询菜品数据(添加方法)
            if (dish.getStatus() == 1) {
                //如果菜品处于启售状态,则不能删除
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        }

Mapper接口的方法

    /**
     * 根据主键获取菜品信息
     *
     * @param id
     * @return
     */

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

根据菜品的id查询套餐的id,如果存在关联的套餐则不能删除

        //判断当前菜品是否被套餐关联
        List<Long> dishIds = setMealDishMapper.getSetMealIdsByDishIds(ids);//根据菜品id查询套餐id(添加方法)
        if (dishIds != null && !dishIds.isEmpty()) {
            //如果存在关联的套餐,则不能删除
            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
        }

Mapper接口

    /**
     * 根据菜品id查询套餐id
     *
     * @param dishIds
     * @return
     */
    List<Long> getSetMealIdsByDishIds(List<Long> dishIds);

MapperXML文件

    <!--  根据菜品id查询对应的套餐id  -->
    <select id="getSetMealIdsByDishIds" resultType="java.lang.Long">
        select setmeal_id from setmeal_dish where dish_id in
        <foreach collection="dishIds" item="dishId" open="(" close=")" separator=",">
            #{dishId}
        </foreach>
    </select>

判断结束,成功校验则对菜品进行删除

        //批量删除菜品
        dishMapper.deleteByIds(ids);
        //批量删除菜品对应的口味数据
        dishFlavorMapper.deleteByDishIds(ids);

Mapper接口

    /**
     * 根据菜品id集合批量删除菜品数据
     * @param ids
     */

    void deleteByIds(List<Long> ids);

MapperXML文件

    <!--  根据菜品id批量删除  -->
    <delete id="deleteByIds">
        delete from dish where id in
        <foreach collection="ids" open="(" close=")" separator="," item="id">
            #{id}
        </foreach>
    </delete>

Mapper接口

    /**
     * 根据菜品id批量删除口味数据
     *
     * @param ids
     */

    void deleteByDishIds(List<Long> ids);

MapperXML文件

    <!--  实现口味的批量删除  -->
    <delete id="deleteByDishIds">
        delete from dish_flavor where dish_id in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </delete>

接口4 根据分类的id查询菜品

每个菜品都有一个类id

    /**
     * 根据分类id查询菜品
     * @param categoryId
     * @return
     */
    @ApiOperation("根据分类id查询菜品")
    @GetMapping("/list")
    public Result<List<Dish>> list(Long categoryId) {
        log.info("根据分类id查询菜品:{}", categoryId);
        // 获取这个分类下的菜品
        List<Dish> dishVOList = dishService.list(categoryId);
        return Result.success(dishVOList);
    }

1 方法传递的是这个分类的id返回的菜品构成的集合

public Result<List<Dish>> list(Long categoryId)

2 调用Service层的方法

        // 获取这个分类下的菜品
        List<Dish> dishVOList = dishService.list(categoryId);

3 Service层

    /**
     * 根据分类id查询菜品返回菜品的集合
     * @param categoryId
     * @return
     */

    List<Dish> list(Long categoryId);

4 Service层实现类

    /**
     * 根据分类id查询菜品获取菜品的集合
     *
     * @param categoryId
     * @return
     */

    @Override
    public List<Dish> list(Long categoryId) {
        Dish dish = Dish.builder().categoryId(categoryId).status(StatusConstant.ENABLE).build();
        return dishMapper.list(dish);
    }

将菜品分类id和起售状态传递给Dish

Dish dish = Dish.builder().categoryId(categoryId).status(StatusConstant.ENABLE).build();

Mapper接口

    /**
     * 动态查询菜品
     * @param dish
     * @return
     */

    List<Dish> list(Dish dish);

MapperXML文件

MyBatis 通过 反射 或 Get 方法 访问 Dish 对象的属性(如 namecategoryIdstatus

    <!--  动态条件查询菜品  -->
    <select id="list" resultType="com.sky.entity.Dish" parameterType="Dish">
        select * from dish
        <where>
            <if test="name != null and name != ''">
                and name like concat('%', #{name}, '%')
            </if>
            <if test="categoryId != null">
                and category_id = #{categoryId}
            </if>
            <if test="status != null">
                and status = #{status}
            </if>
        </where>
        order by create_time desc

    </select>

接口5 菜品分页查询

    /**
     * 菜品分页查询
     *
     * @param dishPageQueryDTO
     * @return
     */
    @ApiOperation("菜品分页查询")
    @GetMapping("/page")
    public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
        log.info("菜品分页查询:{}", dishPageQueryDTO);
        PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
        return Result.success(pageResult);
    }

1 传递DishPageQueryDTO对象

public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO)
@Data
public class DishPageQueryDTO implements Serializable {

    private int page;

    private int pageSize;

    private String name;

    //分类id
    private Integer categoryId;

    //状态 0表示禁用 1表示启用
    private Integer status;

}

2 调用Service接口的方法

PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);

3 Service接口

    /**
     * 菜品分页查询
     *
     * @param dishPageQueryDTO
     * @return
     */

    PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO);

4 Service实现类

    /**
     * 菜品分页查询
     *
     * @param dishPageQueryDTO
     * @return
     */
    @Override
    public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
        PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
        Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
        return new PageResult(page.getTotal(), page.getResult());
    }

作用
使用 MyBatis 分页插件 PageHelper 启动分页,自动拦截后续的 SQL 查询并添加分页逻辑(LIMIT 子句)。

参数

dishPageQueryDTO.getPage():当前页码(从 第1页 开始)。

dishPageQueryDTO.getPageSize():每页记录数(如 10、20)。

底层原理
PageHelper 通过 ThreadLocal 保存分页参数,后续的 第一个 SQL 查询会被自动分页。

PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());

调用Mapper持久层接口的分页查询

Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);

Mapper持久层接口

    /**
     * 菜品分页查询
     *
     * @param dishPageQueryDTO
     * @return
     */

    Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);

Mapper持久层XML文件

MyBatis 通过 反射 或 Get 方法 访问 DishVO 对象的属性

    <!--  菜品分页查询  -->
    <select id="pageQuery" resultType="com.sky.vo.DishVO">
        select d.*,
        c.name as categoryName
        from dish d
        left join category c on d.category_id = c.id
        <where>
            <if test="name != null and name != ''">
                and d.name like concat('%', #{name}, '%')
            </if>
            <if test="categoryId != null">
                and d.category_id = #{categoryId}
            </if>
            <if test="status != null">
                and d.status = #{status}
            </if>
        </where>
        order by d.create_time desc
    </select>

接口6 根据id查询菜品和口味信息

    /**
     * 根据id查询菜品和对应的口味信息
     *
     * @param id
     * @return
     */

    @ApiOperation("根据id查询菜品和口味信息")
    @GetMapping("/{id}")
    public Result<DishVO> getById(@PathVariable Long id) {
        log.info("根据id查询菜品信息:{}", id);
        DishVO dishVO = dishService.getByIdWithFlavor(id);
        return Result.success(dishVO);
    }

1 @PathVariable 获取url路径上的参数id

public Result<DishVO> getById(@PathVariable Long id)

2 调用Service业务层的方法

DishVO dishVO = dishService.getByIdWithFlavor(id);

3 Service业务层接口

    /**
     * 根据id查询菜品的基本信息和口味信息
     *
     * @param id
     * @return
     */
    DishVO getByIdWithFlavor(Long id);

4 Service业务层实现类

    /**
     * 根据id查询菜品和对应的口味数据
     *
     * @param id
     * @return
     */

    @Override
    public DishVO getByIdWithFlavor(Long id) {
        // 根据id查询菜品数据
        Dish dish = dishMapper.getById(id);
        // 根据id查询口味数据
        List<DishFlavor> dishFlavorList = dishFlavorMapper.getByDishId(id);
        // 将查询结果封装到VO中
        DishVO dishVO = new DishVO();
        BeanUtils.copyProperties(dish, dishVO);//将dish中的数据拷贝到dishVO中
        dishVO.setFlavors(dishFlavorList);
        return dishVO;
    }

根据id查询菜品/根据id查询口味

        // 根据id查询菜品数据
        Dish dish = dishMapper.getById(id);
        // 根据id查询口味数据
        List<DishFlavor> dishFlavorList = dishFlavorMapper.getByDishId(id);

Mapper持久层

    /**
     * 根据主键id获取菜品信息
     *
     * @param id
     * @return
     */

    @Select("select * from dish where id = #{id}")
    Dish getById(Long id);
    /**
     * 根据菜品id查询对应的口味数据
     *
     * @param dishId
     * @return
     */
    @Select("select * from dish_flavor where dish_id = #{dishId}")
    List<DishFlavor> getByDishId(Long dishId);

将数据全部封装到DishVO当中(该类包含菜品的基础信息和口味信息)

        // 将查询结果封装到VO中
        DishVO dishVO = new DishVO();
        BeanUtils.copyProperties(dish, dishVO);//将dish中的数据拷贝到dishVO中
        dishVO.setFlavors(dishFlavorList);

最后将其返回

return dishVO;

三 套餐管理相关接口

三层架构模式(Controller控制层、Service业务层、Mapper持久层)

控制层SetMealController

接口1 新增套餐

    /**
     * 新增套餐
     *
     * @param setmealDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增套餐")
    public Result save(@RequestBody SetmealDTO setmealDTO) {
        log.info("新增套餐:{}", setmealDTO);
        setMealService.saveWithDish(setmealDTO);
        return Result.success();
    }

1 RequestBoby获取HTTP请求请求体当中的参数将其封装为Set MealDTO对象

public Result save(@RequestBody SetmealDTO setmealDTO)

2 调用Service层的方法

setMealService.saveWithDish(setmealDTO);

3 Service接口

    /**
     * 新增套餐
     *
     * @param setmealDTO
     */

    void saveWithDish(SetmealDTO setmealDTO);

4 Service接口实现类

    /**
     * 新增套餐,同时需要保存套餐和菜品的关联关系
     *
     * @param setmealDTO
     */

    @Transactional
    @Override
    public void saveWithDish(SetmealDTO setmealDTO) {
        log.info("新增套餐:{}", setmealDTO);
        Setmeal setmeal = new Setmeal();
        BeanUtils.copyProperties(setmealDTO, setmeal);
        // 向套餐表插入数据
        setmealMapper.insert(setmeal);
        // 获取生成的套餐id
        Long setmealId = setmeal.getId();
        log.info("套餐id:{}", setmealId);

        List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
        setmealDishes.forEach(setmealDish -> {
            setmealDish.setSetmealId(setmealId);
            log.info("套餐id:{}", setmealId);
        });
        //保存套餐和菜品的关联关系
        setMealDishMapper.insertBatch(setmealDishes);

    }

将DTO的对象参数拷贝到实体类Setmeal当中

        Setmeal setmeal = new Setmeal();
        BeanUtils.copyProperties(setmealDTO, setmeal);

再调用Mapper接口的方法将数据插入(这里处理的是套餐的信息)

setmealMapper.insert(setmeal);

Mapper持久层(这里使用切面实现对时间公共字段的处理)

    /**
     * 新增套餐
     * @param setmeal
     */
    @AutoFill(OperationType.INSERT)
    void insert(Setmeal setmeal);

MapperXML文件(这里需要写上useGenerateKeys="true" keyProperty="id"  可以高效地解决插入数据后获取自增主键的需求,避免了手动查询最新主键的繁琐操作。)

    <!--  新增套餐  -->
    <insert id="insert" parameterType="setmeal" useGeneratedKeys="true" keyProperty="id">

        insert into setmeal (id, category_id, name, price, status, description, image, create_time, update_time,
                             create_user, update_user)
        values (#{id}, #{categoryId}, #{name}, #{price}, #{status}, #{description}, #{image}, #{createTime},
                #{updateTime}, #{createUser}, #{updateUser})
    </insert>

获取生成的套餐id

Long setmealId = setmeal.getId();

通过 foreach 循环为每个 SetmealDish 对象设置套餐 ID

        List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
        setmealDishes.forEach(setmealDish -> {
            setmealDish.setSetmealId(setmealId);
            log.info("套餐id:{}", setmealId);
        });

setMealDishMapper.insertBatch(setmealDishes);

Mapper持久层接口

    /**
     * 批量插入套餐菜品关系
     * @param setmealDishes
     */
    void insertBatch(List<SetmealDish> setmealDishes);

MapperXML文件

    <!--   批量保存套餐和菜品的关联关系   -->
    <insert id="insertBatch" parameterType="list">

        insert into setmeal_dish (setmeal_id, dish_id,name,price,copies)
        values
        <foreach collection="setmealDishes" item="setmealDish" separator=",">
            (#{setmealDish.setmealId},#{setmealDish.dishId},#{setmealDish.name},#{setmealDish.price},#{setmealDish.copies})
        </foreach>
    </insert>

接口2 修改套餐

    /**
     * 修改套餐
     *
     * @param setmealDTO
     * @return
     */
    @PutMapping
    @ApiOperation("修改套餐")
    public Result update(@RequestBody SetmealDTO setmealDTO) {
        log.info("修改套餐:{}", setmealDTO);
        setMealService.update(setmealDTO);
        return Result.success();
    }

1 @RequestBoby获取HTTP请求当中的参数将其封装到SetmealDTO当中

public Result update(@RequestBody SetmealDTO setmealDTO)

2 调用Service层的方法

setMealService.update(setmealDTO);

3 Service业务层接口

    /**
     * 修改套餐
     *
     * @param setmealDTO
     */
    void update(SetmealDTO setmealDTO);

4 Service接口实现类

    /**
     * 修改套餐
     *
     * @param setmealDTO
     */
    @Override
    public void update(SetmealDTO setmealDTO) {
        Setmeal setmeal = new Setmeal();
        BeanUtils.copyProperties(setmealDTO, setmeal);
        // 1.修改套餐表,执行更新操作update
        setmealMapper.update(setmeal);
        //2.获取套餐id
        Long setmealId = setmealDTO.getId();
        // 3.删除套餐和菜品的关联关系delete,操作setmeal_dish表
        setMealDishMapper.deleteBySetmealId(setmealId);
        List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
        setmealDishes.forEach(setmealDish -> {
            setmealDish.setSetmealId(setmealId);
        });
        // 4.重新插入套餐和菜品的关联关系insert,操作setmeal_dish表
        setMealDishMapper.insertBatch(setmealDishes);
    }

将传递过来的DTO对象拷贝给实体对象

        Setmeal setmeal = new Setmeal();
        BeanUtils.copyProperties(setmealDTO, setmeal);

将套餐表上的数据修改

setmealMapper.update(setmeal);

Mapper层接口

    /**
     * 修改套餐表
     *
     * @param setmeal
     */
    @AutoFill(OperationType.UPDATE)
    void update(Setmeal setmeal);

MapperXML文件(这里的根据id是通过反射从传递的对象当中获取的)

    <!--  修改套餐表  -->
    <update id="update">
        update setmeal
        <set>
        <if test="name != null">
            name = #{name},
        </if>
        <if test="categoryId != null">
            category_id = #{categoryId},
        </if>
        <if test="price != null">
            price = #{price},
        </if>
        <if test="image != null">
            image = #{image},
        </if>
        <if test="description != null">
            description = #{description},
        </if>
        <if test="status != null">
            status = #{status},
        </if>
        <if test="updateTime != null">
            update_time = #{updateTime},
        </if>
        <if test="updateUser != null">
            update_user = #{updateUser}
        </if>
        </set>
        where id = #{id}
    </update>

从DTO对象当中获取套餐的id值

Long setmealId = setmealDTO.getId();

删除原有关联数据:根据套餐ID删除原有菜品关联记录。

setMealDishMapper.deleteBySetmealId(setmealId);

设置新关联数据:将新提交的菜品对象与当前套餐ID绑定。

        List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
        setmealDishes.forEach(setmealDish -> {
            setmealDish.setSetmealId(setmealId);
        });

批量插入新关联数据:将绑定后的菜品数据插入数据库。

setMealDishMapper.insertBatch(setmealDishes);

调用Mapper持久层接口

    /**
     * 批量插入套餐菜品关系
     * @param setmealDishes
     */
    void insertBatch(List<SetmealDish> setmealDishes);

MapperXML文件

    <!--   批量保存套餐和菜品的关联关系   -->
    <insert id="insertBatch" parameterType="list">

        insert into setmeal_dish (setmeal_id, dish_id,name,price,copies)
        values
        <foreach collection="setmealDishes" item="setmealDish" separator=",">
            (#{setmealDish.setmealId},#{setmealDish.dishId},#{setmealDish.name},#{setmealDish.price},#{setmealDish.copies})
        </foreach>
    </insert>

接口3 套餐分页查询


 

    /**
     * 分页查询
     *
     * @param setmealPageQueryDTO
     * @return
     */
    @ApiOperation("套餐分页查询")
    @GetMapping("/page")
    public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO) {
        log.info("套餐分页查询:{}", setmealPageQueryDTO);
        PageResult pageResult = setMealService.pageQuery(setmealPageQueryDTO);
        return Result.success(pageResult);
    }

1 调用Service方法

PageResult pageResult = setMealService.pageQuery(setmealPageQueryDTO);

2 Service业务层

    /**
     * 分页查询
     *
     * @param setmealPageQueryDTO
     * @return
     */
    PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);

3 接口实现类

    /**
     * 分页查询套餐
     *
     * @param setmealPageQueryDTO
     * @return
     */
    @Override
    public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {
        int pageNum = setmealPageQueryDTO.getPage();
        int pageSize = setmealPageQueryDTO.getPageSize();

        PageHelper.startPage(pageNum, pageSize);
        Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);
        return new PageResult(page.getTotal(), page.getResult());
    }

作用
使用 MyBatis 分页插件 PageHelper 启动分页,自动拦截后续的 SQL 查询并添加分页逻辑(LIMIT 子句)。

参数

setmealPageQueryDTO.getPage():当前页码(从 第1页 开始)。

setmealPageQueryDTO.getPageSize():每页记录数(如 10、20)。

底层原理
PageHelper 通过 ThreadLocal 保存分页参数,后续的 第一个 SQL 查询会被自动分页。

Mapper接口

    /**
     * 分页查询
     *
     * @param setmealPageQueryDTO
     * @return
     */
    Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);

MapperXML文件

    <!--  套餐分页查询  -->
    <select id="pageQuery" resultType="com.sky.vo.SetmealVO">
        select
        s.*,c.name categoryName
        from
        setmeal s
        left join
        category c
        on
        s.category_id = c.id
        <where>
            <if test="name != null">
                and s.name like concat('%',#{name},'%')
            </if>
            <if test="status != null">
                and s.status = #{status}
            </if>
            <if test="categoryId != null">
                and s.category_id = #{categoryId}
            </if>
        </where>
        order by s.create_time desc
    </select>

接口4 套餐批量删除

    /**
     * 批量删除套餐
     *
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation("批量删除套餐")
    public Result delete(@RequestParam List<Long> ids) {
        log.info("批量删除套餐:{}", ids);
        setMealService.deleteBatch(ids);
        return Result.success();
    }

1 RequestParam从HTTP请求当中获取数据封装到集合当中

public Result delete(@RequestParam List<Long> ids)

2 调用Service业务层当中的方法

setMealService.deleteBatch(ids);

3 Service业务层接口

    /**
     * 批量删除套餐
     *
     * @param ids
     */
    void deleteBatch(List<Long> ids);

4 Service业务层接口实现类

    /**
     * 批量删除套餐
     *
     * @param ids
     */
    @Transactional
    @Override
    public void deleteBatch(List<Long> ids) {
        ids.forEach(id -> {
            Setmeal setmeal = setmealMapper.getById(id);//(新增方法)
            if (setmeal.getStatus() == 1) {
                //如果菜品处于启售状态,则不能删除
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        });
        ids.forEach(setmealId -> {
            //删除套餐数据
            setmealMapper.deleteById(setmealId);//(新增方法)
            //删除套餐和菜品的关联数据
            setMealDishMapper.deleteBySetmealId(setmealId);//(新增方法)
        });
    }

遍历套餐当中的菜品是否处于售卖状态,处于售卖状态则不能删除并抛出异常

        ids.forEach(id -> {
            Setmeal setmeal = setmealMapper.getById(id);//(新增方法)
            if (setmeal.getStatus() == 1) {
                //如果菜品处于启售状态,则不能删除
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        });

满足需求则将其数据删除

        ids.forEach(setmealId -> {
            //删除套餐数据
            setmealMapper.deleteById(setmealId);//(新增方法)
            //删除套餐和菜品的关联数据
            setMealDishMapper.deleteBySetmealId(setmealId);//(新增方法)
        });

根据套餐id删除套餐数据

setmealMapper.deleteById(setmealId);

Mapper持久层

    /**
     * 根据id删除套餐
     *
     * @param id
     */
    @Select("delete from setmeal where id = #{id}")
    void deleteById(Long id);

删除套餐与菜品的关联数据

setMealDishMapper.deleteBySetmealId(setmealId);

Mapper持久层

    /**
     * 根据套餐id删除套餐菜品关系
     *
     * @param setmealId
     */
    @Delete("delete from setmeal_dish where setmeal_id = #{setmealId}")
    void deleteBySetmealId(Long setmealId);

接口5 根据id查询套餐(回显)

    /**
     * 根据id查询套餐-用于修改界面回显数据
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询套餐")
    public Result<SetmealVO> getById(@PathVariable Long id) {
        log.info("根据id查询套餐:{}", id);
        SetmealVO setmealVO = setMealService.getByIdWithDish(id);
        return Result.success(setmealVO);
    }

1 @PathVariable获取请求路径当中的id信息

public Result<SetmealVO> getById(@PathVariable Long id)

2 调用Service业务层接口将查询数据接收封装

 SetmealVO setmealVO = setMealService.getByIdWithDish(id);

3 Service业务层接口

    /**
     * 根据id查询套餐和关联的菜品数据
     *
     * @param id
     * @return
     */
    SetmealVO getByIdWithDish(Long id);

4 Service业务层实现类

    /**
     * 根据id查询套餐和套餐菜品的关联关系
     *
     * @param id
     * @return
     */
    @Override
    public SetmealVO getByIdWithDish(Long id) {
        Setmeal setmeal = setmealMapper.getById(id);
        List<SetmealDish> setmealDishes = setMealDishMapper.getBySetmealId(id);//(新增方法)
        SetmealVO setmealVO = new SetmealVO();
        BeanUtils.copyProperties(setmeal, setmealVO);
        setmealVO.setSetmealDishes(setmealDishes);
        return setmealVO;
    }

先根据套餐id查询套餐

Setmeal setmeal = setmealMapper.getById(id);

Mapper层

    /**
     * 根据id查询套餐
     *
     * @param id
     * @return
     */
    @Select("select * from setmeal where id = #{id}")
    Setmeal getById(Long id);

根据套餐id查询套餐中菜品

List<SetmealDish> setmealDishes = setMealDishMapper.getBySetmealId(id);

Mapper层

    /**
     * 根据套餐id查询套餐菜品关系
     *
     * @param id
     * @return
     */

    @Select("select * from setmeal_dish where setmeal_id = #{id}")
    List<SetmealDish> getBySetmealId(Long id);

将查询到的数据封装到setmealVO当中给前端返回

        SetmealVO setmealVO = new SetmealVO();
        BeanUtils.copyProperties(setmeal, setmealVO);
        setmealVO.setSetmealDishes(setmealDishes);

接口6 套餐的启售与停售

    /**
     * 套餐启售停售
     *
     * @param status
     * @param id
     * @return
     */
    @PostMapping("/status/{status}")
    @ApiOperation("套餐起售停售")
    public Result startOrStop(@PathVariable Integer status, Long id) {
        log.info("套餐起售停售:{},{}", status, id);
        setMealService.startOrStop(status, id);
        return Result.success();
    }

1 @PathVariable从请求url路径当中获取需要修改状态,id是通过请求的参数自动绑定

public Result startOrStop(@PathVariable Integer status, Long id)

2 调用Service业务层方法

setMealService.startOrStop(status, id);

3 Service接口

    /**
     * 套餐启售停售
     *
     * @param status
     * @param id
     */
    void startOrStop(Integer status, Long id);

4 Service实现类

    /**
     * 启售与停售
     *
     * @param status
     * @param id
     */

    @Override
    public void startOrStop(Integer status, Long id) {
        //起售套餐时,判断套餐内是否有停售菜品,有停售菜品提示"套餐内包含未启售菜品,无法启售"
        if (status == StatusConstant.ENABLE) {
            //select a.* from dish a left join setmeal_dish b on a.id = b.dish_id where b.setmeal_id = ?
            List<Dish> dishList = dishMapper.getBySetmealId(id);
            if (dishList != null && !dishList.isEmpty()) {
                dishList.forEach(dish -> {
                    if (StatusConstant.DISABLE == dish.getStatus()) {
                        throw new SetmealEnableFailedException(MessageConstant.SETMEAL_ENABLE_FAILED);
                    }
                });
            }
        }

        Setmeal setmeal = Setmeal.builder()
                .id(id)
                .status(status)
                .build();
        setmealMapper.update(setmeal);
    }

Mapper接口查询菜品

    /**
     * 根据套餐id查询菜品
     *
     * @param id
     * @return
     */
    @Select("select a.* from dish a left join setmeal_dish b on a.id = b.dish_id where b.setmeal_id = #{setmealId}")
    List<Dish> getBySetmealId(Long id);

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐