🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:苍穹外卖项目实战
🌠 首发时间:2024年5月5日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾
目录
- 新增套餐
- 需求分析和设计
- 代码开发
- 功能测试
- 套餐分页查询
- 需求分析和设计
- 代码开发
- 功能测试
- 删除套餐
- 需求分析和设计
- 代码开发
- 功能测试
- 修改套餐
- 需求分析和设计
- 代码开发
- 功能测试
- 套餐启售停售
- 需求分析和设计
- 代码开发
- 功能测试
新增套餐
需求分析和设计
产品原型:
业务规则:
- 套餐名称唯一
- 套餐必须属于某个分类
- 套餐必须包含菜品
- 名称、分类、价格、图片为必填项
- 添加菜品窗口需要根据分类类型来展示菜品
- 新增的套餐默认为停售状态
接口设计(共涉及到4个接口):
-
根据类型查询分类(已完成)
-
图片上传(已完成)
-
根据分类 id 查询菜品(这个接口在添加菜品页面用到,分类 id 比如素菜或者荤菜再加上输入框的条件一起查询符合的菜品)
-
新增套餐
数据库设计:
-
套餐表 setmeal,用于存储套餐的信息。具体表结构如下:
字段名 数据类型 说明 备注 id bigint 主键 自增 name varchar(32) 套餐名称 唯一 category_id bigint 分类id 逻辑外键 price decimal(10,2) 套餐价格 image varchar(255) 图片路径 description varchar(255) 套餐描述 status int 售卖状态 1起售 0停售 create_time datetime 创建时间 update_time datetime 最后修改时间 create_user bigint 创建人id update_user bigint 最后修改人id -
套餐菜品关系表 setmeal_dish,用于存储套餐和菜品的关联关系。具体表结构如下:
字段名 数据类型 说明 备注 id bigint 主键 自增 setmeal_id bigint 套餐id 逻辑外键 dish_id bigint 菜品id 逻辑外键 name varchar(32) 菜品名称 冗余字段 price decimal(10,2) 菜品单价 冗余字段 copies int 菜品份数
代码开发
根据分类 id 查询菜品接口实现
在 DishController 中创建 list 方法:
/**
* 根据分类id查询菜品
*
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<Dish>> list(Long categoryId) {
List<Dish> list = dishService.list(categoryId);
return Result.success(list);
}
在 DishService 接口中声明 list 方法:
/**
* 根据分类id查询菜品
*
* @param categoryId
* @return
*/
List<Dish> list(Long categoryId);
在 DishServiceImpl 接口中实现 list 方法:
/**
* 根据分类id查询菜品
*
* @param categoryId
* @return
*/
public List<Dish> list(Long categoryId) {
Dish dish = Dish.builder()
.categoryId(categoryId)
.status(StatusConstant.ENABLE)
.build();
return dishMapper.list(dish);
}
在 DishMapper 中声明 list 方法:
/**
* 动态条件查询菜品
*
* @param dish
* @return
*/
List<Dish> list(Dish dish);
在 DishMapper.xml 中配置对应的 SQL:
<!-- 根据动态条件查询菜品-->
<select id="list" resultType="com.sky.entity.Dish">
select * from dish
<where>
<if test="name != null">
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>
新增套餐接口实现
新建 SetmealController,在其中创建 list 方法:
import com.sky.dto.SetmealDTO;
import com.sky.result.Result;
import com.sky.service.SetmealService;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/admin/setmeal")
@Api("套餐相关接口")
@Slf4j
public class SetmealController {
@Autowired
private SetmealService setmealService;
/**
* 新增套餐
*
* @param setmealDTO
* @return
*/
@PostMapping
@ApiOperation("新增套餐")
public Result save(@RequestBody SetmealDTO setmealDTO) {
setmealService.saveWithDish(setmealDTO);
return Result.success();
}
}
新建 SetmealService 接口,在其中声明 saveWithDish 方法:
import com.sky.dto.SetmealDTO;
public interface SetmealService {
/**
* 新增套餐, 同时需要保存套餐和菜品的关联关系
*
* @param setmealDTO
*/
void saveWithDish(SetmealDTO setmealDTO);
}
新建 SetmealServiceImpl ,实现 saveWithDish 方法:
import com.sky.dto.SetmealDTO;
import com.sky.entity.Setmeal;
import com.sky.entity.SetmealDish;
import com.sky.mapper.DishMapper;
import com.sky.mapper.SetmealDishMapper;
import com.sky.mapper.SetmealMapper;
import com.sky.service.SetmealService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class SetmealServiceImpl implements SetmealService {
@Autowired
private SetmealMapper setmealMapper;
@Autowired
private SetmealDishMapper setmealDishMapper;
@Autowired
private DishMapper dishMapper;
/**
* 新增套餐, 同时需要保存套餐和菜品的关联关系
*
* @param setmealDTO
*/
@Transactional
public void saveWithDish(SetmealDTO setmealDTO) {
//1. 保存套餐数据
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO, setmeal);
setmealMapper.insert(setmeal);
//2. 保存套餐菜品关联信息
//获取套餐插入后生成的id
Long setmealId = setmeal.getId();
//获取前端传过来的套餐菜品关系
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
//设置套餐id
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmealId);
});
setmealDishMapper.insertBatch(setmealDishes);
}
}
在 SetmealMapper 中声明 insert 方法:
**
* 插入套餐数据
* @param setmeal
*/
@AutoFill(OperationType.INSERT)
void insert(Setmeal setmeal);
在 SetmealMapper.xml 中配置对应的 SQL:
<!--插入套餐数据-->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into setmeal
(category_id, name, price, status, description, image, create_time, update_time, create_user, update_user)
values (#{categoryId}, #{name}, #{price}, #{status}, #{description}, #{image}, #{createTime}, #{updateTime},
#{createUser}, #{updateUser})
</insert>
在 SetmealDishMapper 中声明 insertBatch 方法:
/**
* 批量保存套餐和菜品的关联关系
*
* @param setmealDishes
*/
void insertBatch(List<SetmealDish> setmealDishes);
在 SetmealDishMapper.xml 中配置对应的 SQL:
<!-- 批量保存套餐和菜品的关联关系-->
<insert id="insertBatch">
insert into setmeal_dish
(setmeal_id, dish_id, name, price, copies)
values
<foreach collection="setmealDishes" item="sd" separator=",">
(#{sd.setmealId}, #{sd.dishId}, #{sd.name}, #{sd.price}, #{sd.copies})
</foreach>
</insert>
功能测试
可以进行接口测试,也可以进行前后端联调测试
随便添加一个套餐,因为分页查询还没写,套餐数据显示不出来,所以我们可以看看对应的表中是否有数据:
套餐分页查询
需求分析和设计
产品原型:
业务规则:
- 根据页码进行分页展示
- 每页展示 10 条数据
- 可以根据需要,按照套餐名称、分类、售卖状态进行查询
接口设计:
代码开发
在 SetmealController 中创建 page 方法:
/**
* 套餐分页查询
*
* @param setmealPageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("套餐分页查询")
public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO) {
PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);
return Result.success(pageResult);
}
在 SetmealService 接口中声明 pageQuery 方法:
/**
* 分页查询
*
* @param setmealPageQueryDTO
* @return
*/
PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
在 SetmealServiceImpl 中实现 pageQuery 方法:
/**
* 分页查询
*
* @param setmealPageQueryDTO
* @return
*/
public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {
//开始分页查询
PageHelper.startPage(setmealPageQueryDTO.getPage(), setmealPageQueryDTO.getPageSize());
Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);
return new PageResult(page.getTotal(), page.getResult());
}
在 SetmealMapper 中声明 pageQuery 方法:
/**
* 分页查询
*
* @param setmealPageQueryDTO
* @return
*/
Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
在 SetmealMapper.xml 中配置对应的 SQL:
<!--分页查询-->
<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>
功能测试
可以进行接口测试,也可以进行前后端联调测试
删除套餐
需求分析和设计
产品原型:
业务规则:
- 可以一次删除一个套餐,也可以批量删除套餐
- 起售中的套餐不能删除
接口设计:
代码开发
在 SetmealController 中创建 delete 方法:
/**
* 批量删除套餐
*
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("批量删除套餐")
public Result delete(@RequestParam List<Long> ids) {
setmealService.deleteBatch(ids);
return Result.success();
}
在 SetmealService 接口中声明 deleteBatch 方法:
/**
* 批量删除套餐
*
* @param ids
*/
void deleteBatch(List<Long> ids);
在 SetmealServiceImpl 中实现 deleteBatch 方法:
/**
* 批量删除套餐
*
* @param ids
*/
@Transactional
public void deleteBatch(List<Long> ids) {
//判断套餐集合中是否存在启售状态的套餐
ids.forEach(id -> {
Setmeal setmeal = setmealMapper.getById(id);
if (setmeal.getStatus().equals(StatusConstant.ENABLE)) {
throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);
}
});
//删除套餐
ids.forEach(setmealId -> {
//删除套餐表中的数据
setmealMapper.deleteById(setmealId);
//删除套餐菜品关系表中的数据
setmealDishMapper.deleteBySetmealId(setmealId);
});
}
在 SetmealMapper 中声明 getById 方法和 deleteById 方法,并配置 SQL:
/**
* 根据id查询套餐
*
* @param id
* @return
*/
@Select("select * from setmeal where id = #{id}")
Setmeal getById(Long id);
/**
* 根据id删除套餐
*
* @param setmealId
*/
@Delete("delete from setmeal where id = #{id}")
void deleteById(Long setmealId);
在 SetmealDishMapper 中声明 deleteBySetmealId 方法并配置 SQL:
/**
* 根据套餐id删除套餐和菜品的关联关系
*
* @param setmealId
*/
@Delete("delete from setmeal_dish where setmeal_id = #{setmealId}")
void deleteBySetmealId(Long setmealId);
功能测试
可以进行接口测试,也可以进行前后端联调测试
先随便添加一个套餐:
将其删除:
修改套餐
需求分析和设计
产品原型:
接口设计(共涉及到5个接口):
-
根据 id 查询套餐
-
根据类型查询分类(已完成)
-
根据分类 id 查询菜品(已完成)
-
图片上传(已完成)
-
修改套餐
代码开发
根据 id 查询套餐接口开发
在 SetmealController 中创建 getById 方法:
/**
* 根据id查询套餐
*
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询套餐")
public Result<SetmealVO> getById(@PathVariable Long id) {
SetmealVO setmealVO = setmealService.getByIdWithDish(id);
return Result.success(setmealVO);
}
在 SetmealService 接口中声明 getByIdWithDish 方法:
/**
* 根据id查询套餐和关联的菜品数据
*
* @param id
* @return
*/
SetmealVO getByIdWithDish(Long id);
在 SetmealServiceImpl 中实现 getByIdWithDish 方法:
/**
* 根据id查询套餐和关联的菜品数据
*
* @param id
* @return
*/
@Override
public SetmealVO getByIdWithDish(Long id) {
//获取套餐和套餐关联的菜品数据
Setmeal setmeal = setmealMapper.getById(id);
List<SetmealDish> setmealDishes = setmealDishMapper.getDishesBySetmealId(id);
//封装数据
SetmealVO setmealVO = new SetmealVO();
BeanUtils.copyProperties(setmeal, setmealVO);
setmealVO.setSetmealDishes(setmealDishes);
return setmealVO;
}
在 SetmealDishMapper 中声明 getDishesBySetmealId 方法并配置 SQL:
/**
* 根据套餐id查询套餐和菜品的关联关系
*
* @param id
* @return
*/
@Select("select * from setmeal_dish where setmeal_id = #{setmealId}")
List<SetmealDish> getDishesBySetmealId(Long id);
修改套餐接口开发
在 SetmealController 中创建 update 方法:
/**
* 修改套餐
*
* @param setmealDTO
* @return
*/
@PutMapping
@ApiOperation("修改套餐")
public Result update(@RequestBody SetmealDTO setmealDTO) {
setmealService.update(setmealDTO);
return Result.success();
}
在 SetmealService 接口中声明 update 方法:
/**
* 修改套餐
*
* @param setmealDTO
*/
void update(SetmealDTO setmealDTO);
在 SetmealServiceImpl 中实现 update 方法:
/**
* 修改套餐
*
* @param setmealDTO
*/
@Transactional
public void update(SetmealDTO setmealDTO) {
//拷贝套餐数据
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO, setmeal);
//修改套餐表
setmealMapper.update(setmeal);
//获取套餐id
Long setmealId = setmealDTO.getId();
//删除原套餐和菜品的关联关系
setmealDishMapper.deleteBySetmealId(setmealId);
//给新的关联菜品添加套餐id
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmealId);
});
//重新插入套餐和菜品的关联关系
setmealDishMapper.insertBatch(setmealDishes);
}
功能测试
可以进行接口测试,也可以进行前后端联调测试
套餐启售停售
需求分析和设计
产品原型:
业务规则:
- 可以对状态为启售的套餐进行停售操作,可以对状态为停售的套餐进行启售操作
- 启售的套餐可以展示在用户端,停售的套餐不能展示在用户端
- 启售套餐时,如果套餐内包含停售的菜品,则不能启售
接口设计:
代码开发
在 SetmealController 中创建 startOrStop 方法:
/**
* 套餐启售停售
*
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("套餐启售停售")
public Result startOrStop(@PathVariable Integer status, Long id) {
setmealService.startOrStop(status, id);
return Result.success();
}
在 SetmealService 接口中声明 startOrStop 方法:
/**
* 套餐启售停售
*
* @param status
* @param id
*/
void startOrStop(Integer status, Long id);
在 SetmealServiceImpl 中实现 startOrStop 方法:
/**
* 套餐启售停售
*
* @param status
* @param id
*/
@Transactional
public void startOrStop(Integer status, Long id) {
//启售套餐时, 需要判断套餐内是否有停售的菜品, 有则不能启售
if (status.equals(StatusConstant.ENABLE)) {
//获取套餐内所有菜品
List<Dish> dishes = dishMapper.getBySetmealId(id);
if (dishes != null && dishes.size() > 0) {
dishes.forEach(dish -> {
//判断菜品的状态
if (dish.getStatus().equals(StatusConstant.DISABLE)) {
throw new SetmealEnableFailedException(MessageConstant.SETMEAL_ENABLE_FAILED);
}
});
}
}
//更新套餐信息
Setmeal setmeal = Setmeal.builder()
.id(id)
.status(status)
.build();
setmealMapper.update(setmeal);
}
在 SetmealMapper 中声明 getBySetmealId 方法并配置 SQL:
/**
* 根据套餐id查询关联的菜品
*
* @param id
* @return
*/
@Select("select * from dish d left join setmeal_dish sd on d.id = sd.dish_id where sd.setmeal_id = #{setmealId}")
List<Dish> getBySetmealId(Long id);
功能测试
可以进行接口测试,也可以进行前后端联调测试