chenjunda
长期主义

苍穹外面day04

2025-07-29 javaWeb

苍穹外卖day04

image-20221018135930842

image-20221018140833345

业务规则:

  • 套餐名称唯一
  • 套餐必须属于某个分类
  • 套餐必须包含菜品
  • 名称、分类、价格、图片为必填项
  • 添加菜品窗口需要根据分类类型来展示菜品
  • 新增的套餐默认为停售状态

接口设计(共涉及到4个接口):

  • 根据类型查询分类(已完成)
  • 根据分类id查询菜品
  • 图片上传(已完成)
  • 新增套餐

image-20221018141521068image-20221018141606787

数据库设计:

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 菜品份数

1.2 代码实现

1.2.1 DishController

1
2
3
4
5
6
7
8
9
10
11
/**
* 根据分类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);
}

1.2.2 DishService

1
2
3
4
5
6
/**
* 根据分类id查询菜品
* @param categoryId
* @return
*/
List<Dish> list(Long categoryId);

1.2.3 DishServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 根据分类id查询菜品
* @param categoryId
* @return
*/
public List<Dish> list(Long categoryId) {
Dish dish = Dish.builder()
.categoryId(categoryId)
.status(StatusConstant.ENABLE)
.build();
return dishMapper.list(dish);
}

1.2.4 DishMapper

1
2
3
4
5
6
/**
* 动态条件查询菜品
* @param dish
* @return
*/
List<Dish> list(Dish dish);

1.2.5 DishMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="list" resultType="Dish" parameterType="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>

1.2.6 SetmealController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 套餐管理
*/
@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐相关接口")
@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();
}
}

1.2.7 SetmealService

1
2
3
4
5
6
7
8
public interface SetmealService {

/**
* 新增套餐,同时需要保存套餐和菜品的关联关系
* @param setmealDTO
*/
void saveWithDish(SetmealDTO setmealDTO);
}

1.2.8 SetmealServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 套餐业务实现
*/
@Service
@Slf4j
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) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO, setmeal);

//向套餐表插入数据
setmealMapper.insert(setmeal);

//获取生成的套餐id
Long setmealId = setmeal.getId();

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

//保存套餐和菜品的关联关系
setmealDishMapper.insertBatch(setmealDishes);
}
}

1.2.9 SetmealMapper

1
2
3
4
5
6
/**
* 新增套餐
* @param setmeal
*/
@AutoFill(OperationType.INSERT)
void insert(Setmeal setmeal);

1.2.10 SetmealMapper.xml

1
2
3
4
5
6
<insert id="insert" parameterType="Setmeal" 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>

1.2.11 SetmealDishMapper

1
2
3
4
5
/**
* 批量保存套餐和菜品的关联关系
* @param setmealDishes
*/
void insertBatch(List<SetmealDish> setmealDishes);

1.2.12 SetmealDishMapper.xml

1
2
3
4
5
6
7
8
<insert id="insertBatch" parameterType="list">
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>

1.3 功能测试

2. 套餐分页查询

2.1 需求分析和设计

产品原型:

image-20221018152429246

image-20221018152731141

业务规则:

  • 根据页码进行分页展示
  • 每页展示10条数据
  • 可以根据需要,按照套餐名称、分类、售卖状态进行查询

2.2 代码实现

2.2.1 SetmealController

1
2
3
4
5
6
7
8
9
10
11
/**
* 分页查询
* @param setmealPageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("分页查询")
public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO) {
PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);
return Result.success(pageResult);
}

2.2.2 SetmealService

1
2
3
4
5
6
/**
* 分页查询
* @param setmealPageQueryDTO
* @return
*/
PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);

2.2.3 SetmealServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 分页查询
* @param setmealPageQueryDTO
* @return
*/
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());
}

2.2.4 SetmealMapper

1
2
3
4
5
6
/**
* 分页查询
* @param setmealPageQueryDTO
* @return
*/
Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);

2.2.5 SetmealMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<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>

2.3 功能测试

3. 删除套餐

3.1 需求分析和设计

产品原型:image-20221018160214225

image-20221018160915177image-20221018160949864

3.2 代码实现

3.2.1 SetmealController

1
2
3
4
5
6
7
8
9
10
11
/**
* 批量删除套餐
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("批量删除套餐")
public Result delete(@RequestParam List<Long> ids){
setmealService.deleteBatch(ids);
return Result.success();
}

3.2.2 SetmealService

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

3.2.3 SetmealServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 批量删除套餐
* @param ids
*/
@Transactional
public void deleteBatch(List<Long> ids) {
ids.forEach(id -> {
Setmeal setmeal = setmealMapper.getById(id);
if(StatusConstant.ENABLE == setmeal.getStatus()){
//起售中的套餐不能删除
throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);
}
});

ids.forEach(setmealId -> {
//删除套餐表中的数据
setmealMapper.deleteById(setmealId);
//删除套餐菜品关系表中的数据
setmealDishMapper.deleteBySetmealId(setmealId);
});
}

3.2.4 SetmealMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 根据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);

3.2.5 SetmealDishMapper

1
2
3
4
5
6
/**
* 根据套餐id删除套餐和菜品的关联关系
* @param setmealId
*/
@Delete("delete from setmeal_dish where setmeal_id = #{setmealId}")
void deleteBySetmealId(Long setmealId);

3.3 功能测试

4. 修改套餐

4.1 需求分析和设计

产品原型:

image-20221018160214225

接口设计(共涉及到5个接口):

  • 根据id查询套餐
  • 根据类型查询分类(已完成)
  • 根据分类id查询菜品(已完成)
  • 图片上传(已完成)
  • 修改套餐
image-20221018160915177 image-20221018160949864 image-20221018161046352 image-20221018161117780 image-20221018161139861

4.2 代码实现

4.2.1 SetmealController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 根据id查询套餐,用于修改页面回显数据
*
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询套餐")
public Result<SetmealVO> getById(@PathVariable Long id) {
SetmealVO setmealVO = setmealService.getByIdWithDish(id);
return Result.success(setmealVO);
}

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

4.2.2 SetmealService

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 根据id查询套餐和关联的菜品数据
* @param id
* @return
*/
SetmealVO getByIdWithDish(Long id);

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

4.2.3 SetmealServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 根据id查询套餐和套餐菜品关系
*
* @param id
* @return
*/
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;
}

/**
* 修改套餐
*
* @param setmealDTO
*/
@Transactional
public void update(SetmealDTO setmealDTO) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO, setmeal);

//1、修改套餐表,执行update
setmealMapper.update(setmeal);

//套餐id
Long setmealId = setmealDTO.getId();

//2、删除套餐和菜品的关联关系,操作setmeal_dish表,执行delete
setmealDishMapper.deleteBySetmealId(setmealId);

List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmealId);
});
//3、重新插入套餐和菜品的关联关系,操作setmeal_dish表,执行insert
setmealDishMapper.insertBatch(setmealDishes);
}

4.2.4 SetmealDishMapper

1
2
3
4
5
6
7
/**
* 根据套餐id查询套餐和菜品的关联关系
* @param setmealId
* @return
*/
@Select("select * from setmeal_dish where setmeal_id = #{setmealId}")
List<SetmealDish> getBySetmealId(Long setmealId);

4.3 功能测试

5. 起售停售套餐

5.1 需求分析和设计

产品原型:

image-20221018163720881

image-20221018165055208

5.2 代码实现

5.2.1 SetmealController

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 套餐起售停售
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("套餐起售停售")
public Result startOrStop(@PathVariable Integer status, Long id) {
setmealService.startOrStop(status, id);
return Result.success();
}

5.2.2 SetmealService

1
2
3
4
5
6
/**
* 套餐起售、停售
* @param status
* @param id
*/
void startOrStop(Integer status, Long id);

5.2.3 SetmealServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 套餐起售、停售
* @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);
}

5.2.4 DishMapper

1
2
3
4
5
6
7
/**
* 根据套餐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);

5.3 功能测试

Author: chenjunda

Link: http://example.com/2025/07/29/%E8%8B%8D%E7%A9%B9%E5%A4%96%E9%9D%A2day04/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
diary2025-7-29
NextPost >
diary2025-7-27
CATALOG
  1. 1. 苍穹外卖day04
    1. 1.0.1. 1.2 代码实现
      1. 1.0.1.1. 1.2.1 DishController
      2. 1.0.1.2. 1.2.2 DishService
      3. 1.0.1.3. 1.2.3 DishServiceImpl
      4. 1.0.1.4. 1.2.4 DishMapper
      5. 1.0.1.5. 1.2.5 DishMapper.xml
      6. 1.0.1.6. 1.2.6 SetmealController
      7. 1.0.1.7. 1.2.7 SetmealService
      8. 1.0.1.8. 1.2.8 SetmealServiceImpl
      9. 1.0.1.9. 1.2.9 SetmealMapper
      10. 1.0.1.10. 1.2.10 SetmealMapper.xml
      11. 1.0.1.11. 1.2.11 SetmealDishMapper
      12. 1.0.1.12. 1.2.12 SetmealDishMapper.xml
    2. 1.0.2. 1.3 功能测试
  2. 1.1. 2. 套餐分页查询
    1. 1.1.1. 2.1 需求分析和设计
    2. 1.1.2. 2.2 代码实现
      1. 1.1.2.1. 2.2.1 SetmealController
      2. 1.1.2.2. 2.2.2 SetmealService
      3. 1.1.2.3. 2.2.3 SetmealServiceImpl
      4. 1.1.2.4. 2.2.4 SetmealMapper
      5. 1.1.2.5. 2.2.5 SetmealMapper.xml
    3. 1.1.3. 2.3 功能测试
  3. 1.2. 3. 删除套餐
    1. 1.2.1. 3.1 需求分析和设计
    2. 1.2.2. 3.2 代码实现
      1. 1.2.2.1. 3.2.1 SetmealController
      2. 1.2.2.2. 3.2.2 SetmealService
      3. 1.2.2.3. 3.2.3 SetmealServiceImpl
      4. 1.2.2.4. 3.2.4 SetmealMapper
      5. 1.2.2.5. 3.2.5 SetmealDishMapper
    3. 1.2.3. 3.3 功能测试
  4. 1.3. 4. 修改套餐
    1. 1.3.1. 4.1 需求分析和设计
    2. 1.3.2. 4.2 代码实现
      1. 1.3.2.1. 4.2.1 SetmealController
      2. 1.3.2.2. 4.2.2 SetmealService
      3. 1.3.2.3. 4.2.3 SetmealServiceImpl
      4. 1.3.2.4. 4.2.4 SetmealDishMapper
    3. 1.3.3. 4.3 功能测试
  5. 1.4. 5. 起售停售套餐
    1. 1.4.1. 5.1 需求分析和设计
    2. 1.4.2. 5.2 代码实现
      1. 1.4.2.1. 5.2.1 SetmealController
      2. 1.4.2.2. 5.2.2 SetmealService
      3. 1.4.2.3. 5.2.3 SetmealServiceImpl
      4. 1.4.2.4. 5.2.4 DishMapper
    3. 1.4.3. 5.3 功能测试