苍穹外卖阶段性总结 (超详细版)
生成jwt令牌的作用主要是便于后续进行操作的便携性,解决了登陆状态的持续验证问题。这里的实现是后端借助前端登录的信息,生成token,后续用户再访问客户端的其他功能,服务端只需要验证其token即可。验证过程:使用相同的密钥对Token进行签名验证,同时还会验证其是否过期
目录
一 管理端员工的相关接口
三层架构模式(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 对象的属性(如 name、categoryId、status)
<!-- 动态条件查询菜品 -->
<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);
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)