继续上一节的内容,本节是作业课程,要求独立完成套餐管理模块所有业务功能,包括:新增套餐、套餐分页查询、删除套餐、修改套餐、起售停售套餐。
页面原型:
其中的文件上传、和套餐分类查询已经完成。套餐分类查询的接口地址为/admin/category/list,根据请求参数的type值区分查询的分类类型:1为菜品分类,2为套餐分类,这里前端查询的是套餐分类。这些功能前面已经完成不再赘述。
在新增套餐时需要选择套餐里包含的菜品,所以需要先完成根据分类id查询菜品功能。
根据分类id查询菜品接口:
先来完成根据分类id查询菜品:
Controller层
DishController.java
/**
* 根据分类id查询菜品
*
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<Dish>> getByCategory(Long categoryId){
log.info("根据分类id查询菜品:{}", categoryId);
List<Dish> list = dishService.getByCategoryID(categoryId);
return Result.success(list);
}
Service层实现类
DishServiceImpl.java
/**
* 根据分类id查询菜品信息
*
* @param categoryId
*/
public List<Dish> getByCategoryID(Long categoryId) {
return dishMapper.getByCategoryID(categoryId);
}
Mapper层
/**
* 根据分类id查询菜品
*
* @param categoryId
*/
@Select("select * from dish where status = 1 and category_id = #{categoryId} order by create_time desc")
List<Dish> getByCategoryID(Long categoryId);
完成之后测试新增套餐时往套餐中加入菜品:
按道理来说,这里前端写了输入菜品名称进行搜索的,后端应该开发出动态条件查询菜品的功能,输入数据是分类id还有菜品名等,但是这里因为前后端的接口文档里这个接口只传入了一个分类id,所以楼主就没有写动态查询的功能,因此前端的添加菜品里的按名称进行搜索也是无法使用的,有兴趣的可以自己修改前端源码修改接口。
新增套餐接口:
下面来完成新增套餐:
Controller层
SetmealController
/**
* 套餐管理
*/
@RestController
@RequestMapping("/admin/setmeal")
@Slf4j
@Api(tags= "套餐相关接口") //描述类的作用
public class SetmealController {
@Autowired
private SetmealService setmealServices;
/**
* 新增套餐
*
* @param setmealDTO
* @return
*/
@PostMapping
@ApiOperation(value = "新增套餐")
public Result save(@RequestBody SetmealDTO setmealDTO){
log.info("新增套餐:{}",setmealDTO);
setmealServices.save(setmealDTO);
return Result.success();
}
}
Service层实现类
SetmealServiceImpl
@Service
public class SetmealServiceImpl implements SetmealService {
@Autowired
private SetmealMapper setmealMapper;
@Autowired
private SetmealDishMapper setmealDishMapper;
/**
* 新增套餐,同时需要保存套餐和菜品的关联关系
*
* @param setmealDTO
* @return
*/
@Transactional // 事务
public void save(SetmealDTO setmealDTO) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO, setmeal); //套餐
List<SetmealDish> setmealDishlist = setmealDTO.getSetmealDishes(); // 套餐菜品关系
setmealMapper.insert(setmeal);
long setmealId = setmeal.getId(); //返回自动生成的套餐菜品表主键id
if (setmealDishlist != null && setmealDishlist.size() > 0) {
setmealDishlist.forEach(setmealDish -> {
setmealDish.setSetmealId(setmealId);
});
//向套餐菜品关系表插入n条数据
setmealDishMapper.insertBatch(setmealDishlist);
}
}
}
Mapper层
SetmealMapper
/**
* 新增套餐数据
*
* @param setmeal
*/
@AutoFill(OperationType.INSERT) // 公共字段填充
void insert(Setmeal setmeal);
SetmealMapper.xml
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.SetmealMapper">
<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>
mapper>
SetmealDishMapper
/**
* 向套餐菜品关系表批量插入数据
*
* @param setmealDishlist
* @return
*/
void insertBatch(List<SetmealDish> setmealDishlist);
SetmealDishMapper.xml
<insert id="insertBatch">
insert into setmeal_dish (setmeal_id, dish_id, name, price, copies) VALUES
<foreach collection="setmealDishlist" item="sd" separator=",">
(#{sd.setmealId},#{sd.dishId},#{sd.name},#{sd.price},#{sd.copies})
foreach>
insert>
前后端联调测试新增套餐功能,由于未开发套餐分页查询功能,固在数据库中查看数据是否插入成功:
测试通过,提交代码。
接口信息:
Controller层
SetmealController
/**
* 套餐分页查询
* @param setmealPageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("套餐分页查询")
public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO){
log.info("套餐分页查询,参数为:{}", setmealPageQueryDTO);
PageResult pageResult = setmealServices.pageQuery(setmealPageQueryDTO);
return Result.success(pageResult);
}
Service层实现类
SetmealServiceImpl
/**
* 套餐分页查询
*
* @param setmealPageQueryDTO
* @return
*/
public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO){
// select * from setmeal limit 0,10
//开始分页查询
PageHelper.startPage(setmealPageQueryDTO.getPage(), setmealPageQueryDTO.getPageSize());
Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);
long total = page.getTotal();
List<SetmealVO> records = page.getResult();
return new PageResult(total, records);
}
Mapper层
SetmealMapper
/**
* 套餐分页查询
* @param setmealPageQueryDTO
* @return
*/
Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
SetmealMapper.xml
<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>
测试:
测试通过,提交代码。
接口传入的套餐ids包含多个id,所以需要开发批量删除功能。
删除套餐的时候需要判断当前套餐是否在起售状态,否则不能删除,需要使用到根据id查询套餐功能里的查询套餐功能,根据id查询套餐功能里还包括了根据套餐id查询对应菜品的功能,所以干脆先把根据id查询套餐的功能开发了。
接口如下:
Controller层
SetmealController
@GetMapping("/{id}")
@ApiOperation("根据id查询套餐")
public Result<SetmealVO> getById(@PathVariable Long id){
log.info("根据id查询套餐:{}", id);
SetmealVO setmealVO = setmealServices.getByIdWithDishes(id);
return Result.success(setmealVO);
}
Service层实现类
SetmealServiceImpl
/**
* 根据套餐id查询套餐和其对应菜品
* @param id
* @return
*/
@Override
public SetmealVO getByIdWithDishes(Long id) {
//根据id查询套餐数据
Setmeal setmeal = setmealMapper.getById(id);
//根据套餐id查询菜品数据
List<SetmealDish> setmealDishes = setmealDishMapper.getBySetmealId(id);
//将查询到的数据封装到VO
SetmealVO setmealVO = new SetmealVO();
BeanUtils.copyProperties(setmeal, setmealVO);
setmealVO.setSetmealDishes(setmealDishes);
return setmealVO;
}
Mapper层
SetmealMapper
/**
* 根据套餐id查询套餐数据
* @param id
* @return
*/
@Select("select * from setmeal where id=#{id}")
Setmeal getById(Long id);
SetmealDishMapper
/**
* 根据套餐id查询菜品数据
* @param setmealId
* @return
*/
@Select("select * from setmeal_dish where setmeal_id=#{setmealId}")
List<SetmealDish> getBySetmealId(Long setmealId);
接口如下:
Controller层
SetmealController
/**
* 套餐批量删除
*
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("套餐批量删除")
public Result delete(@RequestParam List<Long> ids) {
log.info("套餐批量删除:{}", ids);
setmealServices.deleteBatch(ids);
return Result.success();
}
Service层实现类
SetmealServiceImpl
/**
* 套餐批量删除
* @param ids
*/
@Transactional //事务
public void deleteBatch(List<Long> ids) {
//判断当前套餐是否能够删除---是否存在起售中的套餐??
for (Long id : ids) {
Setmeal setmeal = setmealMapper.getById(id); //在这里需要使用到根据id查询套餐功能的查询套餐
if (setmeal.getStatus() == StatusConstant.ENABLE) {
//当前菜品处于起售中,不能删除
throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);
}
}
for (Long id : ids) {
setmealMapper.deleteById(id);//删除套餐数据
setmealDishMapper.deleteBySetmealId(id);//删除套餐菜品关系表中套餐关联的菜品数据
}
}
Mapper层
SetmealMapper
/**
* 根据套餐id删除套餐数据
* @param id
*/
@Delete("delete from setmeal where id = #{id}")
void deleteById(Long id);
SetmealDishMapper
/**
* 根据套餐id删除套餐菜品关系表中的菜品数据
* @param setmealId
*/
@Delete("delete from setmeal_dish where setmeal_id = #{setmealId}")
void deleteBySetmealId(Long setmealId);
测试略,由于还未开发套餐起售状态修改功能,固可以去数据库直接修改套餐的销售状态,然后去swagger或者前端测试删除起售中的套餐,同时测试批量删除功能。
测试通过后提交代码到git。
共涉及到5个接口:根据id查询套餐(在删除套餐里已完成)、根据类型查询分类(已完成)、根据分类id查询菜品(已完成)、图片上传(已完成)、修改套餐
修改套餐接口信息如下,传入的参数和新增套餐是一致的,只不过从insert改成了update。
Controller层
SetmealController
/**
* 修改套餐
*
* @param setmealVO
* @return
*/
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody SetmealVO setmealVO) {
log.info("修改菜品:{}", setmealVO);
setmealServices.updateWithDishes(setmealVO);
return Result.success();
}
Service层实现类
SetmealServiceImpl
/**
* 根据id修改套餐基本信息和对应的菜品信息
* @param setmealVO
*/
@Transactional //事务
public void updateWithDishes(SetmealVO setmealVO) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealVO, setmeal);
//修改套餐表基本信息
setmealMapper.update(setmeal);
//删除原有的套餐菜品关系表数据
setmealDishMapper.deleteBySetmealId(setmealVO.getId());
//重新插入套餐菜品关系数据
List<SetmealDish> setmealDishes = setmealVO.getSetmealDishes();
if (setmealDishes != null && setmealDishes.size() > 0) {
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmealVO.getId());
});
//向套餐菜品关系表插入n条数据
setmealDishMapper.insertBatch(setmealDishes);
}
}
Mapper层
SetmealMapper
/**
* 根据id动态修改套餐数据
* @param setmeal
*/
@AutoFill(OperationType.UPDATE) // 公共字段填充
void update(Setmeal setmeal);
SetmealMapper.xml
<update id="update">
update setmeal
<set>
<if test="categoryId != null">category_id = #{categoryId},if>
<if test="name != null">name = #{name},if>
<if test="price != null">price = #{price},if>
<if test="status != null">status = #{status},if>
<if test="description != null">description = #{description},if>
<if test="image != null">image = #{image},if>
<if test="updateTime != null">update_time = #{updateTime},if>
<if test="updateUser != null">update_user = #{updateUser},if>
set>
where id = #{id}
update>
测试:
数据库:
测试通过,提交代码到github。
写这一块的时候发现菜品管理里面的起售停售菜品好像老师也没有带我们写,所以干脆一块写了吧。
Controller层
DishController
@PostMapping("/status/{status}")
@ApiOperation("修改菜品销售状态")
public Result updateStatus(@PathVariable Integer status,Long id){
log.info("根据分类id修改菜品销售状态:{}", status);
dishService.updateStatusById(status,id);
return Result.success();
}
Service层实现类
DishServiceImpl
/**
* 根据菜品id修改菜品销售状态
* @param status
* @param id
*/
public void updateStatusById(Integer status, Long id) {
Dish dish = Dish.builder()
.id(id)
.status(status)
.build();
dishMapper.update(dish);//直接调用以前的根据id动态修改菜品数据接口就行
}
Mapper层
这里直接调用以前的菜品修改的Mapper层接口。
DishMapper
/**
* 根据id动态修改菜品数据
*
* @param dish
*/
@AutoFill(value = OperationType.UPDATE) // 公共字段填充
void update(Dish dish);
DishMapper.xml
<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>
接口:
Controller层
SetmealController
@PostMapping("/status/{status}")
@ApiOperation("修改套餐销售状态")
public Result updateStatus(@PathVariable Integer status,Long id){
log.info("根据套餐id修改套餐销售状态:{}", status);
setmealServices.updateStatusById(status,id);
return Result.success();
}
Service层实现类
SetmealServiceImpl
@Autowired
private DishMapper dishMapper;
/**
* 根据套餐id修改套餐销售状态
*
* @param status
* @param id
*/
public void updateStatusById(Integer status, Long id) {
//如果是将套餐的销售状态修改为起售,则需要保证套餐里所有的菜品的销售状态也是起售 否则抛出异常
if(status==StatusConstant.ENABLE){
List<SetmealDish> setmealDishes = setmealDishMapper.getBySetmealId(id);//根据套餐id去套餐菜品关系表里找到所有的菜品
if(setmealDishes == null || setmealDishes.size()==0) return; //这里其实可以抛出一个异常给前端 套餐里没有菜品
setmealDishes.forEach(setmealDish -> {//再根据菜品id去菜品表里找到菜品的销售状态
if(dishMapper.getById(setmealDish.getDishId()).getStatus()==StatusConstant.DISABLE){
throw new SetmealEnableFailedException(MessageConstant.SETMEAL_ENABLE_FAILED);
}
});
}
Setmeal setmeal=Setmeal.builder()
.id(id)
.status(status)
.build();
setmealMapper.update(setmeal);//直接调用之前写好的根据id动态修改套餐数据接口
}
Mapper层
这里直接调用之前写好的根据id动态修改套餐数据接口就行
SetmealMapper
/**
* 根据id动态修改套餐数据
* @param setmeal
*/
@AutoFill(OperationType.UPDATE) // 公共字段填充
void update(Setmeal setmeal);
<update id="update">
update setmeal
<set>
<if test="categoryId != null">category_id = #{categoryId},if>
<if test="name != null">name = #{name},if>
<if test="price != null">price = #{price},if>
<if test="status != null">status = #{status},if>
<if test="description != null">description = #{description},if>
<if test="image != null">image = #{image},if>
<if test="updateTime != null">update_time = #{updateTime},if>
<if test="updateUser != null">update_user = #{updateUser},if>
set>
where id = #{id}
update>
测试:
测试通过,提交代码。
这里再分享另外一种写法,上面这种写法其实比较偷懒,因为调用的都是现成的mapper接口,得先通过套餐id去套餐菜品关系表里找到套餐菜品关联数据,然后拿到所有的菜品id去菜品表里拿到菜品的销售状态。
其实可以直接通过套餐id、套餐表联合菜品表直接拿到菜品数据,把逻辑写在sql语句就行:
SetmealServiceImpl
@Autowired
private DishMapper dishMapper;
/**
* 套餐起售、停售
* @param status
* @param id
*/
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.size() > 0){
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);
}
DishMapper
/**
* 根据套餐id查询菜品
* @param setmealId
* @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 setmealId);