一、前言

上一節將員工的CRUD做出來了,同時由於步驟幾乎相同,對於分類的Controller,我們直接導入,就不重複書寫了,接下來就要做菜品的CRUD了,這裏會使用到阿里雲OSS來存儲文件(圖片),同時菜品有不同的口味選擇,所以需要兩個表存儲。

二、通用接口—文件上傳

通用接口中將實現功能實現中公共的方法,這裏我們先只添加文件上傳的方法。

文件上傳的原理就是通過阿里雲OSS來實現雲存儲,這樣可以方便後續菜品的圖片上傳的存儲。

先看看文檔怎麼描述的,很顯然,是通過請求體傳入一個

外賣項目 - Day04_訂餐json數據下載_#spring boot

外賣項目 - Day04_訂餐json數據下載_#java_02

依舊從上往下書寫,先寫通用接口的Controller,裏面內含文件上傳的方法:

/**
 * 通用接口
 */
@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {

    //自動裝配的是OssConfiguration中創建的含參數的aliOssUtil對象
    @Autowired
    private AliOssUtil aliOssUtil;


    /**
     * 文件上傳
     * @param file
     * @return
     */
    @PostMapping("/upload")
    @ApiOperation("文件上傳")
    public Result<String> upload(MultipartFile file) {
        log.info("文件上傳:{}",file);
        try {
            //原始文件名
            String originalFilename = file.getOriginalFilename();
            //截取原始文件名的後綴  dfdfdf.pgn
            String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
            //構建新文件名稱
            String objectName = UUID.randomUUID().toString() + extension;

            //文件的請求路徑
            String filePath = aliOssUtil.upload(file.getBytes(), objectName);
            return Result.success(filePath);

        } catch (IOException e) {
            log.error("文件上傳失敗:{}",e);
        }

        return Result.error(MessageConstant.UPLOAD_FAILED);
    }
}

對於這個方法,我們的目的是通過工具類將指定文件上傳到阿里雲,我們從請求體中接收一個MultipartFile(二進制的文件類型參數),若上傳成功,最後將返回一個文件路徑的字符串到data,若上傳失敗,將返回報錯結果集到msg。

這裏對於文件名是進行了處理的,使用的是UUID來對文件進行隨機命名(結合多種元素如時間戳、隨機數等),但是由於我們依舊需要擴展名,所以要先將後綴分離出來,然後將文件名部分處理,最後拼接在一起。

最終得到類似的文件名:

外賣項目 - Day04_訂餐json數據下載_#spring boot_03

OSS的工具類如下,這是基於阿里雲官網給出的Java文檔進行封裝的,還是比較簡單的:

@Data
@AllArgsConstructor
@Slf4j
public class AliOssUtil {

    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;

    /**
     * 文件上傳
     *
     * @param bytes
     * @param objectName
     * @return
     */
    public String upload(byte[] bytes, String objectName) {

        // 創建OSSClient實例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 創建PutObject請求。
            ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        //文件訪問路徑規則 https://BucketName.Endpoint/ObjectName
        StringBuilder stringBuilder = new StringBuilder("https://");
        stringBuilder
                .append(bucketName)
                .append(".")
                .append(endpoint)
                .append("/")
                .append(objectName);

        log.info("文件上傳到:{}", stringBuilder.toString());

        return stringBuilder.toString();
    }
}

OSS的自動裝配的配置類如下,目的是創建aliOssUtil的bean,便於在接口中自動裝配:

/**
 * 用於創建AliOssUtil對象
 */
@Configuration
@Slf4j
public class OssConfiguration {

    @Bean
    @ConditionalOnMissingBean//只要沒有這個Bean就創建
    public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
        log.info("開始創建阿里雲文件上傳工具對象{}",aliOssProperties);
        return new AliOssUtil(
                aliOssProperties.getEndpoint(),
                aliOssProperties.getAccessKeyId(),
                aliOssProperties.getAccessKeySecret(),
                aliOssProperties.getBucketName());
    }
}

三、菜品的CRUD

1.新增菜品

(1)文檔

老樣子,先看文檔:

外賣項目 - Day04_訂餐json數據下載_分頁查詢_04

(2)Controller

可以看到這裏需要接收請求體中的參數,所以直接就能想到用DTO接收,並且用@RequestBody標記,所以很容易可以寫出:

/**
 * 菜品管理
 */
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相關接口")
@Slf4j
public class DishController {

    @Autowired
    private DishService dishService;

    /**
     * 新增菜品
     * @param dishDTO
     * @return
     */
    @PostMapping()
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO){
        log.info("新增菜品:{}",dishDTO);
        dishService.saveWithFlavor(dishDTO);
        return Result.success();
    }
}

DTO如下:

@Data
public class DishDTO implements Serializable {

    private Long id;
    //菜品名稱
    private String name;
    //菜品分類id
    private Long categoryId;
    //菜品價格
    private BigDecimal price;
    //圖片
    private String image;
    //描述信息
    private String description;
    //0 停售 1 起售
    private Integer status;
    //口味
    private List<DishFlavor> flavors = new ArrayList<>();

}

值得注意的是,這裏我們使用了一個數組集合來儲存口味選項,因為一個菜品的口味可能有很多個,所以自然的,我們需要一個新的表來存儲這個數據,這個表中由dish_id來作為邏輯外鍵,與dish表的主鍵進行關聯(這樣就知道哪幾個口味屬於哪一個菜品了):

外賣項目 - Day04_訂餐json數據下載_文件上傳_05

同時dish也需要一個表來存儲:

外賣項目 - Day04_訂餐json數據下載_#java_06

(3)Service層

接下來看Service層:

接口:

public interface DishService {

    /**
     * 新增菜品
     * @param dishDTO
     */
    public void saveWithFlavor(DishDTO dishDTO);
}

實現類:

@Service
@Slf4j
public class DishServiceImp implements DishService {

    @Autowired
    private DishMapper dishMapper;

    @Autowired
    private DishFlavorMapper dishFlavorMapper;

    @Override
    @Transactional
    public void saveWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();

        BeanUtils.copyProperties(dishDTO, dish);

        dishMapper.insert(dish);

        //獲取Insert語句生成的主鍵值
        Long dishId = dish.getId();

        //向口味表中插入n條數據
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && flavors.size() > 0){
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });
            //向口味表插入n條數據
            dishFlavorMapper.insertBatch(flavors);
        }
    }
}

這裏需要注意了,這個與員工的新增就不太一樣了,首先先插入dish到菜品表中,這一點是一樣的。

但是這裏我們需要額外處理口味表:由於是用一個數組集合存儲口味的,所以這裏需要遍歷口味表來將每個口味對應的dish_id設置為當前插入的菜品的id(邏輯外鍵),最終才將這些口味插入到口味表中去。

對應關係可以看看下圖:

外賣項目 - Day04_訂餐json數據下載_#後端_07

(4)持久層

兩個表的mapper如下:

@Mapper
public interface DishFlavorMapper {

    /**
     * 批量插入口味數據
     * @param flavors
     */
    void insertBatch(List<DishFlavor> flavors);
}
@Mapper
public interface DishMapper {


    /**
     * 根據分類id查詢菜品數量
     * @param categoryId
     * @return
     */
    @Select("select count(id) from dish where category_id = #{categoryId}")
    Integer countByCategoryId(Long categoryId);


    /**
     * 新增菜品和對應口味
     * @param dish
     */
    @AutoFill(value = OperationType.INSERT)
    void insert(Dish dish);
}

由於新增操作涉及插入的變量較多,我們就不使用註解了,這裏使用xml來配置:

<mapper namespace="com.sky.mapper.DishFlavorMapper">

    <insert id="insertBatch" >
        insert into dish_flavor(dish_id, name, value) VALUES
        <foreach collection="flavors" item="df" separator=",">
            (#{df.dishId},#{df.name},#{df.value})
        </foreach>
    </insert>
</mapper>

這裏是用了動態sql語句的,將flavors集合遍歷,插入表中(先前已經在Service層中處理了邏輯外鍵問題了)

<mapper namespace="com.sky.mapper.DishMapper">
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into dish(name, category_id, price, image, description, create_time, update_time, create_user, update_user,status)
            VALUES
                (#{name},#{categoryId},#{price},#{image},#{description},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})
    </insert>
</mapper>

這裏有兩個參數比較陌生:

1. useGeneratedKeys="true":

作用:告知 MyBatis,當前插入操作的主鍵是由數據庫自動生成的(例如 MySQL 的 AUTO_INCREMENT

2. keyProperty="id":

作用:指定將數據庫生成的主鍵值,賦值到 Java 對象的哪個屬性上。

2.分頁查詢菜品

分頁查詢依舊需要使用到PageHelper。

(1)文檔

先觀察文檔

外賣項目 - Day04_訂餐json數據下載_#java_08

很容易發現分頁查詢是用的Query參數,這就代表需要用到分頁插件了,返回值是查詢到的內容。

(2)Controller

有了員工的查詢經驗,這裏很容易寫出菜品的分頁查詢,這裏由於使用到了分頁插件,所以需要專門創建一個DTO來存儲數據,Controller內容很簡單,先日誌,然後調用Service層,最後返回結果集,由於我們需要分好了頁的結果,所以這裏傳到結果集中的是pageResult對象。

/**
     * 菜品分頁查詢
     * @param dishPageQueryDTO
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("菜品分頁查詢")
    public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
        log.info("菜品分頁查詢:{}", dishPageQueryDTO);
        PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);

        return Result.success(pageResult);
    }

DTO設計如下:

@Data
public class DishPageQueryDTO implements Serializable {

    private int page;

    private int pageSize;

    private String name;

    //分類id
    private Integer categoryId;

    //狀態 0表示禁用 1表示啓用
    private Integer status;

}

(3)Service層

接口:

/**
     * 菜品分頁查詢
     * @param dishPageQueryDTO
     * @return
     */
    PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO);

實現類:實現類中調用了PageHelper插件,傳入DTO,這個DTO其實是前端發送過來的請求體,這裏麪包含了很多參數,包括了頁碼和每頁記錄數(所以這個DTO也與普通的DTO不一樣),這將作為startPage方法傳入的參數用於開啓分頁。

然後我們需要調用持久層獲取查詢結果,結果是用VO封裝的(VO是前端展示數據,DTO是前端請求後端的),最終我們返回的VO結果集還需要封裝到我們自己創建的PageResult中去,然後傳給Controller。

其實這裏可以理解為當我們按下下一頁按鈕時,前端就會重新發出一個請求,這時新DTO的參數就會改變成當前頁的,於是開啓分頁時的參數也改變了,當然從持久層拿出的結果VO也會變,於是封裝VO的結果集也變了,我們自己封裝結果集的PageResult當然也會變,最後的結果就是在Controller中傳回的響應結果也變了(響應回去的data中的數據),於是響應回前端的數據就變成當前頁的了。

/**
     * 菜品分頁查詢
     *
     * @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());
    }

我們自己封裝的分頁查詢結果集如下:

/**
 * 封裝分頁查詢結果
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {

    private long total; //總記錄數

    private List records; //當前頁數據集合

}

(4)持久層

前面也提到了,持久層在分頁中的作用就是拿出VO結果,我們要清晰的知道目的,不然會被各種封裝繞暈。

Mapper:由於使用到動態語句,所以這裏還是使用xml文件配置。

/**
     * 菜品分頁查詢
     * @param dishPageQueryDTO
     * @return
     */
    Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);

映射文件:

這裏使用的語句就是外連接語句了。

這裏的sql語句很複雜,先要搞清楚目的,再看這條語句就會覺得豁然開朗了。

這裏的目的是:通過動態條件查詢 dish 表的菜品信息,並關聯(通過id關聯) category 表獲取菜品對應的分類名稱(通過外連接),最終將結果封裝到 DishVO 實體類中,支持按名稱模糊查詢、按分類 ID 篩選、按狀態篩選,並按創建時間倒序排列(最新創建的菜品在前)

<select id="pageQuery" resultType="com.sky.vo.DishVO">
        select d.* ,c.name as categoryName from dish d left outer join category c on d.category_id = c.id
        <where>
            <if test="name != null">
                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>

3.批量刪除菜品

(1)文檔

先看文檔:

外賣項目 - Day04_訂餐json數據下載_#後端_09

這裏請求的參數是ids,而且是String類型的,這樣我們很不好處理,好在Spring很智能,可以將這個請求參數自動轉化為一個集合,所以這裏我們必然會使用到@RequestParam註解,返回值沒有要求,就是返回個成功結果集就行了。

(2)Controller

/**
     * 菜品批量刪除
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation("菜品批量刪除")
    public Result delete(@RequestParam List<Long> ids){
        log.info("菜品批量刪除:{}",ids);
        dishService.deleteBatch(ids);
        return Result.success();
    }

沒什麼好説的,用了剛剛的註解,將請求參數轉換成了集合。

(3)Service層

接口:

/**
     * 菜品批量刪除
     * @param ids
     */
    void deleteBatch(List<Long> ids);

實現類:

這裏要説一下,我們的刪除和批量刪除是做到一起的,因為批量刪除完全可以完成刪除的功能,完全可以複用。

來看具體的要求:起售的菜品肯定不能刪,和套餐關聯的菜品肯定也不能刪,條件都滿足,就可以刪,所以我們採用了以下的邏輯:

遍歷前端傳來的集合(轉換後),每次循環都從持久層拿一個對應id的對象,判斷起售狀態,不滿足就拋異常,滿足就繼續判斷是否被套餐關聯。

然後去套餐表中查是否有這個id,如果有就拋異常,沒有就進行刪除操作。

刪除操作是需要循環的,確保每個id對應的菜品都被刪除。

/**
     * 菜品批量刪除
     *
     * @param ids
     */
    @Override
    @Transactional
    public void deleteBatch(List<Long> ids) {
        //判斷當前菜品是否能夠刪除---是否存在起售中的菜品
        for (Long id : ids) {
            Dish dish = dishMapper.getById(id);
            if (dish.getStatus() == StatusConstant.ENABLE) {
                //處於起售,不能刪除
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        }

        //判斷當前菜品是否能夠刪除---是否被套餐關聯了
        List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);
        if (setmealIds != null && setmealIds.size() > 0) {
            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
        }

        //刪除菜品表中的菜品數據
        for (Long id : ids) {
            dishMapper.deleteById(id);
            //刪除口味數據
            dishFlavorMapper.deleteByDishId(id);
        }  

    }

(4)持久層

下面兩個簡單,直接上註解,一個是用id查菜品,一個是用id刪菜品。

/**
     * 根據主鍵查詢菜品
     * @param id
     * @return
     */
    @Select("select * from dish where id = #{id}")
    Dish getById(Long id);
/**
     * 根據主鍵刪除菜品數據
     * @param id
     */
    @Delete("delete from dish where id = #{id}")
    void deleteById(Long id);

但是從套餐中查id就不能直接用註解了,因為需要動態sql語句,我們需要遍歷套餐表,去查找在中間表中,是否對應的菜品id有對應的套餐id。

/**
     * 根據id查詢對應套餐id
     * @param dishIds
     * @return
     */
    //select setmeal_id from setmeal dish where dish_id in (1,2,3,4)
    List<Long> getSetmealIdsByDishIds(List<Long> dishIds);

目的:通過傳入的多個菜品 ID(dishIds),查詢出所有包含這些菜品的套餐 ID(setmeal_id),即找出哪些套餐關聯了指定的菜品。

<mapper namespace="com.sky.mapper.SetmealDishMapper">

    <select id="getSetmealIdsByDishIds" resultType="java.lang.Long">
        select setmeal_id from setmeal_dish where dish_id in
        <foreach collection="dishIds" item="dishId" separator="," open="(" close=")">
            #{dishId}
        </foreach>
    </select>

</mapper>

這三個表關係如圖:

外賣項目 - Day04_訂餐json數據下載_文件上傳_10

(5)優化

最後一步刪除操作是可以優化的,將循環在數據庫中進行,節省從Java到數據庫中消耗的時間。

可以優化如下:

//優化
        //根據菜品id集合批量刪除菜品數據
        //sql: delete from dish where id in (?,?,?)
        dishMapper.deleteByIds(ids);

        //根據菜品id集合批量刪除關聯的口味數據
        //sql: delete from dish_flavor where dish_id in (?,?,?)
        dishFlavorMapper.deleteByDishIds(ids);

分別在菜品和口味的Mapper中添加優化的查找方式(在sql中動態循環):

/**
     * 根據菜品id集合批量刪除菜品
     * @param ids
     */
    void deleteByIds(List<Long> ids);
<delete id="deleteByIds">
        delete from dish where id in
        <foreach collection="ids" open="(" close=")" separator="," item="id">
            #{id}
        </foreach>
    </delete>
/**
     * 根據菜品id集合批量刪除關聯的口味數據
     * @param dishids
     */
    void deleteByDishIds(List<Long> dishids);
<delete id="deleteByDishIds" >
        delete from dish_flavor where dish_id
        <foreach collection="dishIds" open="(" close=")" separator="," item="dishId">
            #{dishId}
        </foreach>
    </delete>

4.修改菜品

沒有什麼要注意的,別忘記修改時選項要回顯就行(相當於又是一個接口,查詢接口)。

(1)文檔

外賣項目 - Day04_訂餐json數據下載_#後端_11

修改操作注意需要做到數據的回顯,所以可以看到請求體中向後端帶來了全部參數,最終只需要返回成功結果集即可。

(2)Controller

用DTO接收請求體中的數據,然後調用Service層。

/**
     * 修改菜品
     * @param dishDTO
     * @return
     */
    @PutMapping
    @ApiOperation("修改菜品")
    public Result update(@RequestBody DishDTO dishDTO){
        log.info("修改菜品");
        dishService.updateWithFlavor(dishDTO);
        return Result.success();
    }

(3)Service層

接口:

/**
     * 根據id修改菜品和口味信息
     * @param dishDTO
     */
    void updateWithFlavor(DishDTO dishDTO);

實現類:

/**
     * 修改信息
     * @param dishDTO
     */
    @Override
    public void updateWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO,dish);

        //修改菜品表信息
        dishMapper.update(dish);

        //刪除原有的口味數據
        dishFlavorMapper.deleteByDishId(dishDTO.getId());

        //重新插入口味信息
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && flavors.size() > 0) {
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishDTO.getId());
            });
            //向口味表插入n條數據
            dishFlavorMapper.insertBatch(flavors);
        }

    }

(4)持久層

Mapper:

/**
     * 根據id動態修改菜品
     * @param dish
     */
    @AutoFill(value = OperationType.UPDATE)
    void update(Dish dish);

映射文件:

<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>

(5)回顯

原理是點下修改按鈕,就會跳轉頁面,同時向後端發起請求參數id,後端根據id查詢菜品和口味,最後從後端返回VO集合(由於是要向前端展示的,所以用的VO)。

雖然也算一個完整接口,但是很簡單,所以給出代碼即可。

/**
     * 根據id查詢菜品
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Result<DishVO> getById(@PathVariable Long id){
        log.info("根據id查詢菜品:{}",id);
        DishVO dishVO = dishService.getByIdWithFlavor(id);
        return Result.success(dishVO);
    }
/**
     * 根據id查詢菜品和對應口味
     * @param id
     * @return
     */
    DishVO getByIdWithFlavor(Long id);
/**
     * 根據id查詢菜品和對應口味
     * @param id
     * @return
     */
    @Override
    public DishVO getByIdWithFlavor(Long id) {
        //根據id查詢菜品數據
        Dish dish = dishMapper.getById(id);
        //根據菜品id查詢口味數據
        List<DishFlavor> dishFlavors = dishFlavorMapper.getByDishId(id);
        //將查詢到的數據封裝到VO
        DishVO dishVO = new DishVO();
        BeanUtils.copyProperties(dish,dishVO);
        dishVO.setFlavors(dishFlavors);
        return dishVO;
    }
/**
     * 根據主鍵查詢菜品
     * @param id
     * @return
     */
    @Select("select * from dish where id = #{id}")
    Dish getById(Long id);

四、菜品的起售與禁售

1.文檔

外賣項目 - Day04_訂餐json數據下載_#spring boot_12

請求參數傳來的是一個Path參數和一個Query,Path參數是一個URL中的動態參數,所以我們需要通過@PathVariable來綁定參數,而Query參數直接接收即可,不需要註解來接收參數。

2.Controller

/**
     * 菜品啓用禁用
     * @param status
     * @param id
     * @return
     */
    @PostMapping("/status/{status}")
    @ApiOperation("菜品啓用禁用")
    public Result startOrStop(@PathVariable Integer status,Long id){
        log.info("菜品啓用禁用:{},{}",id,status);
        dishService.startOrStop(status,id);
        return Result.success();
    }

3.Service層

接口:

/**
     * 菜品啓用禁用
     * @param status
     * @param id
     */
    void startOrStop(Integer status, Long id);

實現類:

實現類這裏比較複雜,如果是停售操作,還需要將包含當前菜品的套餐也停售 。

但是注意,如果我們寫套餐的啓用禁用就不用考慮這麼多了,只需要if上面那一部分即可。

/**
     * 菜品禁用啓用
     * @param status
     * @param id
     */
    @Override
    @Transactional
    public void startOrStop(Integer status, Long id) {
        Dish dish = Dish.builder().
                status(status).
                id(id).
                build();
        dishMapper.update(dish);

        if (status == StatusConstant.DISABLE) {
            // 如果是停售操作,還需要將包含當前菜品的套餐也停售
            List<Long> dishIds = new ArrayList<>();
            dishIds.add(id);
            List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(dishIds);
            if (setmealIds != null && setmealIds.size() > 0) {
                for (Long setmealId : setmealIds) {
                    Setmeal setmeal = Setmeal.builder()
                            .id(setmealId)
                            .status(StatusConstant.DISABLE)
                            .build();
                    setmealMapper.update(setmeal);
                }
            }
        }
    }

4.持久層

Mapper:

/**
     * 根據id動態修改菜品
     * @param dish
     */
    @AutoFill(value = OperationType.UPDATE)
    void update(Dish dish);

映射文件:

<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>