您的当前位置:首页正文

【苍穹外卖】项目实战Day04

2024-11-08 来源:个人技术集锦

新增套餐

需求分析和设计

产品原型:

业务规则:

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

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

  • 根据类型查询分类(已完成)

  • 图片上传(已完成)

  • 根据分类 id 查询菜品(这个接口在添加菜品页面用到,分类 id 比如素菜或者荤菜再加上输入框的条件一起查询符合的菜品)

  • 新增套餐

数据库设计:

  • 套餐表 setmeal,用于存储套餐的信息。具体表结构如下:

    字段名数据类型说明备注
    idbigint主键自增
    namevarchar(32)套餐名称唯一
    category_idbigint分类id逻辑外键
    pricedecimal(10,2)套餐价格
    imagevarchar(255)图片路径
    descriptionvarchar(255)套餐描述
    statusint售卖状态1起售 0停售
    create_timedatetime创建时间
    update_timedatetime最后修改时间
    create_userbigint创建人id
    update_userbigint最后修改人id
  • 套餐菜品关系表 setmeal_dish,用于存储套餐和菜品的关联关系。具体表结构如下:

    字段名数据类型说明备注
    idbigint主键自增
    setmeal_idbigint套餐id逻辑外键
    dish_idbigint菜品id逻辑外键
    namevarchar(32)菜品名称冗余字段
    pricedecimal(10,2)菜品单价冗余字段
    copiesint菜品份数

代码开发

根据分类 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);
}

DishMapper 中声明 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);

功能测试

可以进行接口测试,也可以进行前后端联调测试

Top