苍穹外卖04 新增菜品菜品分页查询删除菜品修改菜品
2-6 新增菜品
02 05-新增菜品_需求分析和设计
03 06-新增菜品_代码开发_1
文件上传接口开发:
在这一部分我们主要在于对阿里云oss的代码开发和实现
1.配置阿里云oss:
alioss:endpoint: oss-cn-beijing-internal.aliyuncs.comaccess-key-id: access-key-secret: bucket-name:
填你自己的。
2.创建公共接口管理类CommonController:
Slf4j
@RestController
@ApiOperation("通用接口")
@RequestMapping("/admin/common")
public class CommonController {/*** 文件上传接口* @param file* @return*/@ApiOperation("文件上传接口")@PostMapping("/upload")public Result<String> upload(MultipartFile file){//接受文件的参数需要和请求参数保持一致。所以这里采用filereturn null;}
}
3.熟悉我们已经有的阿里云工具类:
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;@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/ObjectNameStringBuilder stringBuilder = new StringBuilder("https://");stringBuilder.append(bucketName).append(".").append(endpoint).append("/").append(objectName);log.info("文件上传到:{}", stringBuilder.toString());return stringBuilder.toString();}
}
可以看到除了基础的阿里云oss的配置代码之后我们还自己创建了一个stringbuilder对象用于存放一个经过字符串拼接的url。这个url就是用于我们前端来回显我们上传内容的。
4.创建一个配置类用于生成aliOssUtil对象:
package com.sky.config;import com.sky.properties.AliOssProperties;
import com.sky.utils.AliOssUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;@Component
@Slf4j
public class AliOssConfiguaration {// 配置类,用来创建aliyunossUtil对象@Beanpublic AliOssUtil aliOssUtil(AliOssProperties aliOssProperties) {log.info("开始创建阿里云文件上传工具类对象:{}", aliOssProperties);return new AliOssUtil(aliOssProperties.getEndpoint(),aliOssProperties.getAccessKeyId(),aliOssProperties.getAccessKeySecret(),aliOssProperties.getBucketName());}
}
注意这个@Bean注解
04 07-新增菜品_代码开发_2
在这里我们主要实现文件上传的具体代码。
package com.sky.controller.admin;import com.sky.result.Result;
import com.sky.utils.AliOssUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.UUID;@Slf4j
@RestController
@ApiOperation("通用接口")
@RequestMapping("/admin/common")
public class CommonController {@Autowiredprivate AliOssUtil aliOssUtil;/*** 文件上传接口* @param file* @return*/@ApiOperation("文件上传接口")@PostMapping("/upload")public Result<String> upload(MultipartFile file){//接受文件的参数需要和请求参数保持一致。所以这里采用filelog.info("文件上传:{}",file);try {// 获取文件原始名称String originalFilename = file.getOriginalFilename();// 截取文件类型String type = originalFilename.substring(originalFilename.lastIndexOf("."));//使用UUid生成文件名String objectName = UUID.randomUUID().toString() + type;// 调用工具类进行文件上传,获取文件上传路径String filePath=aliOssUtil.upload(file.getBytes(),objectName);return Result.success(filePath);} catch (IOException e) {log.error("文件上传失败:{}",e);}return null;}
}
通过前后端联调我们发现可以实现数据的上传
05 08-新增菜品_代码开发_3
完成了菜品上传的具体业务代码:
1,创建DishController:
package com.sky.controller.admin;import com.sky.dto.DishDTO;
import com.sky.result.Result;
import com.sky.service.DishService;
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;@RequestMapping("/dish")
@RestController
@Api(tags = "菜品管理接口")
@Slf4j
public class DishController {@Autowiredprivate DishService dishService;/*** 新增菜品* @param dishDTO* @return*/@ApiOperation("新增菜品")@PostMappingpublic Result save(@RequestBody DishDTO dishDTO) {log.info("新增菜品:{}", dishDTO);dishService.saveWithFlavor(dishDTO);return Result.success();}
}
创建接口和接口实现类:
@Service
@Slf4j
public class DishServiceImpl implements DishService {@Autowiredprivate DishMapper dishMapper;@Autowiredprivate DishFlavorMapper dishFlavorMapper;/*** 新增菜品和口味* @param dishDTO* @return*/@Override@Transactionalpublic void saveWithFlavor(DishDTO dishDTO) {log.info("新增菜品:{}", dishDTO);//向菜品表插入数据//属性拷贝:Dish dish = new Dish();BeanUtils.copyProperties(dishDTO, dish);dishMapper.insert(dish);//获取插入后的菜品idLong dishId = dish.getId();//向口味表插入数据List<DishFlavor> flavors = dishDTO.getFlavors();if (flavors != null && flavors.size() > 0) {//遍历/* for (DishFlavor flavor : flavors) {flavor.setDishId(dishId);}*/flavors.forEach(flavor -> flavor.setDishId(dishId));//向表中插入n条数据dishFlavorMapper.insertBatch(flavors);}}
}
根据接口文档我们发现,这里的方法需要对两个表进行修改操作,需要同时修改菜品表和口味表。于是我们需要在实现方法前添加@Transactional注解来保证二者同步进行。
3,创建对应的Mapper层并提供Sql方法:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishMapper"><insert id="insert" useGeneratedKeys="true" keyProperty="id">INSERT INTO dish (name, category_id, price, image, description, status, create_time, update_time, create_user, update_user)VALUES (#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})</insert>
</mapper><?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishFlavorMapper"><insert id="insertBatch">INSERT INTO dish_flavor (dish_id, name, value) VALUES<foreach collection="flavors" item="flavor" separator=",">(#{flavor.dishId}, #{flavor.name}, #{flavor.value})</foreach></insert>
</mapper>
由于我们添加口味的方法需要菜品的id,我们会在新增菜品的mapper.xml中添加这一段:
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
表明需要返回值且返回内容是id。
然后再实现类中就可以获取到对应的dishId了:
//获取插入后的菜品idLong dishId = dish.getId();
最后使用foreach的形式遍历,将dishId存进去:
//遍历/* for (DishFlavor flavor : flavors) {flavor.setDishId(dishId);}*/flavors.forEach(flavor -> flavor.setDishId(dishId));
这样就可以将dishId获取并存入dishFlavor表中了。
06 09-新增菜品_功能测试
测试范本如下:
结果如下:
新增菜品业务完成!
作业:完成菜品管理模块中的 **菜品起售停售** 功能
1. 根据产品原型进行需求分析,分析出业务规则
大概一个业务规则口述下来就是:
点击起售按钮,修改数据库dish表中的status,0是停售,1是起售
起售按钮点击后完成业务会变成停售按钮(不知道这点业务是前端还是后端完成的,是后端的话可能会需要一个返回值来帮助前端完成改变)
2. 设计 菜品起售停售 功能的接口
由图片可知是一个post请求
会有一个路径参数status,一个id,不需要返回值
3. 根据接口设计进行代码实现
/*** 控制起售停售* @param status* @param id* @return*/@ApiOperation("控制起售停售")@PostMapping("/status/{status}")public Result<String> startOrStop(@PathVariable("status") Integer status, Long id) {log.info("控制起售停售:{},{}", status, id);dishService.startOrStop(status, id);return Result.success();}/*** 控制起售停售* @param status* @param id* @return*/@Overridepublic void startOrStop(Integer status, Long id) {log.info("控制起售停售:{},{}", status, id);//有现成的id判断,直接用update方法修改得了说是Dish dish = Dish.builder().status(status).id(id).build();dishMapper.update(dish);}
写这个作业的时间节点是写完2.7之后了。索性直接调用修改方法,一样可以实现这个需求。毕竟是动态sql且id喂嘴里了。
然后发现自己傻波一了,想东西又没想全。
如果是商品停售的操作,那么商品所关联的套餐也需要停售!
于是灰溜溜的修改实现类代码:
@Overridepublic void startOrStop(Integer status, Long id) {log.info("控制起售停售:{},{}", status, id);//有现成的id判断,直接用update方法修改得了说是Dish dish = Dish.builder().status(status).id(id).build();dishMapper.update(dish);if (status == StatusConstant.DISABLE) {// 如果是停售操作,还需要将包含当前菜品的套餐也停售List<Long> dishIds = new ArrayList<>();dishIds.add(id);// select setmeal_id from setmeal_dish where dish_id in (?,?,?)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);}}}}
代码逻辑是:先正常修改菜品,后续做一个判断,如果是停售操作:
先用一个dishId来存储id,并且用这个idshid获取到对应的套餐id。
然后判断:如果获取到的id不为空且大于0,说明有套餐关联。
这时候选择遍历获取id,依次用一个setmeal来存储数据,然后作为形参交给setalMapper的修改方法去修改套餐。之所以用集合的方式来存储是因为存在一个商品关联多个套餐的情况。
update方法就是常规的动态sql,不贴代码了。
2-7 菜品分页查询&删除菜品&修改菜品
01 10-菜品分页查询_需求分析和设计
你会发现返回数据当中存在一个CategoryName和其他返回数据并不在一个表
02 11-菜品分页查询_代码开发和功能测试
1.需要使用dishPageQueryDTO来接收数据,用DishVO来返回数据
代码开发如下:
/*** 菜品分页查询* @param dishPageQueryDTO* @return*/@ApiOperation("菜品分页查询")@GetMapping("/page")public Result<Object> page(DishPageQueryDTO dishPageQueryDTO) {log.info("菜品分页查询:{}", dishPageQueryDTO);PageResult pageResult =dishService.pageQuery(dishPageQueryDTO);return Result.success(pageResult);}/*** 菜品分页查询* @param dishPageQueryDTO* @return*/@Overridepublic PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {log.info("菜品分页查询:{}", dishPageQueryDTO);//1.获取分页参数和总数PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());//2,根据条件查询Page<DishVO> page= dishMapper.pageQuery(dishPageQueryDTO);//3.对返回对象进行处理Long total = page.getTotal();List<DishVO> records = page.getResult();return new PageResult(total, records);}<select id="pageQuery" resultType="com.sky.vo.DishVO">SLECT d.*, c.name as categoryName from dish d LEFT JOIN category c ON d.category_id = c.id<where><if test="name != null and name != ''">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.update_time DESC</select>
和以往的分页查询没什么大区别。值得注意一下的就是这里的sql语法实现了一个左外连接的多表查询。
SLECT d.*, c.name as categoryName from dish d LEFT JOIN category c ON d.category_id = c.id
理解这一句的别名,和逻辑就好了。
03 12-删除菜品_需求分析和设计
具体要操作三个表:
04 13-删除菜品_代码实现
Controller:
/*** 批量删除菜品* @param ids* @return*/@ApiOperation("批量删除菜品")@DeleteMappingpublic Result delete(@RequestParam List<Long> ids) {log.info("批量删除菜品:{}", ids);dishService.deleteBatch(ids);return Result.success();}
impl:
/*** 批量删除菜品* @param ids* @return*/@Transactional@Overridepublic void deleteBatch(List<Long> ids) {//1,判断当前菜品是否在售for (Long id : ids) {Dish dish = dishMapper.getByIds(id);if (dish.getStatus() == StatusConstant.ENABLE) {throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);}}//2,判断是否被套餐关联,关联的菜品不能删除,没有关联的可以删除List<Long> setmealIds = setmealDishMapper.getSetmealDishIdByDishId(ids);if (setmealIds != null && setmealIds.size() > 0) {throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);}//3.可以删除了for (Long id : ids) {//3,删除菜品表中的数据dishMapper.deleteById(id);//4,删除口味表中的数据dishFlavorMapper.deleteByDishId(id);}}
关联到的Sql语法:
<--查询有无关联套餐-->
<select id="getSetmealDishIdByDishId" resultType="java.lang.Long">select * from setmeal_dish where dish_id in<foreach collection="ids" item="dishId" separator="," open="(" close=")">#{dishId}</foreach></select>/*** 根据id查询菜品* @param id* @return*/@Select("select * from dish where id = #{id}")Dish getByIds(Long id);/*** 根据id删除菜品* @param id*/@Delete("delete from dish where id = #{id}")void deleteById(Long id);/*** 根据菜品id删除对应的口味数据* @param id*/@Delete("delete from dish_flavor where dish_id = #{id}")void deleteByDishId(Long id);
关键点其实不多,主要是三个表的查询外加条件判断会比较复杂,需要熟练度的支撑。
多表查询,@Transactional注解别忘了。
此外需要注意foreach的格式:
collection="ids" 这里的内容要和mapper层方法的形参相同 item="dishId" 这里的内容要和下方foreach#{}内的内容相同
where后调用in 的时候注意一定要有括号:open="(" close=")"
05 14-删除菜品_功能测试
创建了几个,自己删了下,没啥毛病。
06 15-修改菜品_需求分析和设计
需要回显的东西还是比较多比较复杂的
存在flavors数组。可能有多个口味。
07 16-修改菜品_代码开发_1
根据id查询菜品接口代码实现如下:
/*** 根据id查询菜品* @param id* @return*/@ApiOperation("根据id查询菜品")@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*/@Overridepublic DishVO getByIdWithFlavor(Long id) {log.info("根据id查询菜品:{}", id);Dish dish = dishMapper.getById(id);//获取口味数据List<DishFlavor> flavors= dishFlavorMapper.getByDishId(id);DishVO dishVO = new DishVO();BeanUtils.copyProperties(dish, dishVO);dishVO.setFlavors(flavors);return dishVO;}
只贴出了核心业务部分。我发现自己编写实现类的时候操作不太规范,我直接new了一个dishVO对象去存储dishMapper.getById(id);的值。这并不合规。但是代码逻辑没问题。
以后需要刻意性质的用实体类来调用mapper层方法获取返回值,想要标准化以VO的形式返回给前端就做数据拷贝
08 17-修改菜品_代码开发_2
修改菜品代码实现如下:
/*** 修改菜品* @param dishDTO* @return*/@ApiOperation("修改菜品")@PutMappingpublic Result update(@RequestBody DishDTO dishDTO) {log.info("修改菜品:{}", dishDTO);dishService.update(dishDTO);return Result.success();}/*** 修改菜品* @param dishDTO* @return*/@Overridepublic void update(DishDTO dishDTO) {log.info("修改菜品:{}", dishDTO);Dish dish = new Dish();BeanUtils.copyProperties(dishDTO, dish);//修改菜品表数据dishMapper.update(dish);//修改口味表数据:先删除,再插入log.info("修改口味表数据:先删除,再插入");dishFlavorMapper.deleteByDishId(dishDTO.getId());//插入新数据List<DishFlavor> flavors = dishDTO.getFlavors();if (flavors != null && flavors.size() > 0) {flavors.forEach(flavor -> flavor.setDishId(dishDTO.getId()));dishFlavorMapper.insertBatch(flavors);}log.info("修改菜品成功");}
这个业务里面比较有意思的有两点:
1,获取到的dishDTO里包含了flavors口味集合。所以一方面为了规范,一方面为了查询,我们创建了一个dish对象拷贝DTO中的内容后,来对dish数据库表进行修改操作。
2,由于口味传过来的是一个集合,直接修改会比较复杂。我们这里采用先删除口味信息,再新增口味信息的方式来实现对口味更改的操作。
09 18-修改菜品_功能测试
测试成功没毛病
作业:完成套餐管理模块所有业务功能
包括:
- 新增套餐 ok-套餐分页查询 ok -删除套餐 ok - 修改套餐 - 起售停售套餐
要求:
1. 根据产品原型进行需求分析,分析出业务规则
2. 设计接口
3. 梳理表之间的关系(分类表、菜品表、套餐表、口味表、套餐菜品关系表)
4. 根据接口设计进行代码实现
5. 分别通过swagger接口文档和前后端联调进行功能测试
分析:
- 套餐名称唯一
- 套餐必须属于某个分类
- 套餐必须包含菜品
- 名称、分类、价格、图片为必填项
- 添加菜品窗口需要根据分类类型来展示菜品
- 新增的套餐默认为停售状态
接口设计:
- 根据类型查询分类(已完成)
- 根据分类id查询菜品 搞定
- 图片上传(已完成)
- 新增套餐 搞定
套餐分页查询代码实现:
/*** 套餐分页查询* @param* @return*/@ApiOperation("套餐分页查询")@GetMapping("/page")public Result<Object> page(SetmealPageQueryDTO setmealPageQueryDTO) {log.info("菜品分页查询:{}", setmealPageQueryDTO);PageResult pageResult =setmealService.pageQuery(setmealPageQueryDTO);return Result.success(pageResult);}/*** 套餐分页查询* @param setmealPageQueryDTO* @return*/@Overridepublic PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {log.info("套餐分页查询:{}", setmealPageQueryDTO);//1.获取分页参数和总数PageHelper.startPage(setmealPageQueryDTO.getPage(), setmealPageQueryDTO.getPageSize());//2,根据条件查询Page<SetmealVO> page= setmealMapper.pageQuery(setmealPageQueryDTO);//3.对返回对象进行处理Long total = page.getTotal();List<SetmealVO> records = page.getResult();return new PageResult(total, records);}<select id="pageQuery" resultType="com.sky.vo.SetmealVO">SELECT s.*, c.name as categoryName from setmeal s LEFT JOIN category c ON s.category_id = c.id<where><if test="name != null and name != ''">and s.name like concat('%',#{name},'%')</if><if test="categoryId != null">and s.category_id = #{categoryId}</if><if test="status !=null">and s.status = #{status}</if></where>order by s.update_time desc</select>
新增套餐代码实现:
/*** 新增套餐* @param setmealDTO* @return*/@PostMapping@ApiOperation("新增套餐")public Result save(@RequestBody SetmealDTO setmealDTO) {setmealService.saveWithDish(setmealDTO);return Result.success();}/*** 新增套餐,同时需要保存套餐和菜品的关联关系* @param setmealDTO*/@Transactionalpublic void saveWithDish(SetmealDTO setmealDTO) {Setmeal setmeal = new Setmeal();BeanUtils.copyProperties(setmealDTO, setmeal);//向套餐表插入数据setmealMapper.insert(setmeal);//获取生成的套餐idLong setmealId = setmeal.getId();List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();setmealDishes.forEach(setmealDish -> {setmealDish.setSetmealId(setmealId);});//保存套餐和菜品的关联关系setmealDishMapper.insertBatch(setmealDishes);}
删除套餐代码实现:
/*** 批量删除套餐* @param ids* @return*/@ApiOperation("批量删除套餐")@DeleteMappingpublic Result delete(@RequestParam List<Long> ids) {log.info("批量删除套餐,ids:{}", ids);setmealService.deleteBatch(ids);return Result.success();}/*** 批量删除套餐** @param ids* @return*/@Overridepublic void deleteBatch(List<Long> ids) {//1,判断当前套餐是否在售for (Long id : ids) {Setmeal setmeal = setmealMapper.getByIds(id);if (setmeal.getStatus() == StatusConstant.ENABLE) {throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);}}//3.可以删除了for (Long id : ids) {//3,删除套餐表中的数据setmealMapper.deleteById(id);}}/*** 批量删除套餐* @param id*/@Delete("delete from setmeal where id = #{id}")void deleteById(Long id);
修改套餐代码实现:
/*** 根据id查询套餐,应对回显* @param id* @return*/@ApiOperation("根据id查询套餐")@GetMapping("/{id}")public Result<SetmealVO> getById(@PathVariable Long id) {log.info("根据id查询套餐:{}", id);SetmealVO setmealVO = setmealService.getByIdWithDish(id);return Result.success(setmealVO);}/*** 修改套餐* @param setmealDTO* @return*/@ApiOperation("修改套餐")@PutMappingpublic Result update(@RequestBody SetmealDTO setmealDTO) {log.info("修改套餐:{}", setmealDTO);setmealService.update(setmealDTO);return Result.success();}/*** 修改套餐* @param setmealDTO* @return*/@Overridepublic void update(SetmealDTO setmealDTO) {Setmeal setmeal = new Setmeal();BeanUtils.copyProperties(setmealDTO, setmeal);//修改套餐表数据setmealMapper.update(setmeal);}/*** 根据id查询套餐* @param id* @return*/@Overridepublic SetmealVO getByIdWithDish(Long id) {log.info("根据id查询套餐:{}", id);Setmeal setmeal = setmealMapper.getByIds(id);SetmealVO setmealVO = new SetmealVO();BeanUtils.copyProperties(setmeal, setmealVO);return setmealVO;}/*** 根据分类id查询套餐的数量* @param id* @return*/@Select("select count(id) from setmeal where category_id = #{categoryId}")Integer countByCategoryId(Long id);/*** 修改套餐* @param setmeal*/@AutoFill(OperationType.UPDATE)void update(Setmeal setmeal);<update id="update">update setmeal<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="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>
套餐起售停售:
/*** 套餐起售、停售* @param status* @param id* @return*/@Overridepublic void startOrStop(Integer status, Long id) {//public static final String SETMEAL_ENABLE_FAILED = "套餐内包含未启售菜品,无法启售";//如果当前套餐中如果包含未起售的菜品,则无法起售套餐//1.根据套餐id查询套餐中菜品的数据,并装在一个集合中List<Dish> dish =setmealDishMapper.getDishIdBySetmealId(id);//创建集合List<Long> dishIds = new ArrayList<>();//遍历集合,将菜品id添加到集合中for (Dish dish1 : dish) {dishIds.add(dish1.getId());}//2.根据菜品id查询菜品信息,判断菜品是否在售,如果存在停售菜品,则抛出异常,无法起售套餐for (Long dishId : dishIds) {Dish dish1 = dishMapper.getByIds(dishId);if (dish1.getStatus() == StatusConstant.DISABLE) {throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ENABLE_FAILED);}}//3.如果套餐中菜品都起售,则允许起售套餐Setmeal setmeal = new Setmeal();setmeal.setId(id);setmeal.setStatus(status);setmealMapper.update(setmeal);}
相关文章:
苍穹外卖04 新增菜品菜品分页查询删除菜品修改菜品
2-6 新增菜品 02 05-新增菜品_需求分析和设计 03 06-新增菜品_代码开发_1 文件上传接口开发: 在这一部分我们主要在于对阿里云oss的代码开发和实现 1.配置阿里云oss: alioss:endpoint: oss-cn-beijing-internal.aliyuncs.comaccess-key-id: access-ke…...
C++ 读取英伟达显卡名称、架构及算力
C++ 读取英伟达显卡名称、架构及算力 通过CUDA Runtime API获取计算能力(推荐)CUDA计算能力(Compute Capability)的版本号直接对应显卡架构(如8.6=Ampere,9.0=Hopper)。实现步骤: 1.安装依赖: 安装 NVIDIA CUDA Toolkit。确保显卡驱动支持CUDA。2. C…...
VitePress 中以中文字符结尾的字体加粗 Markdown 格式无法解析
背景 在编写vitepress项目过程中,发现了一个markdown格式解析的问题。 md文件中,以中文句号结尾的字体加粗,无法正确解析: 不只是中文句号,只要是加粗语句中以中文字符结尾,都无法被正确解析 需要将中文…...
2.前端汇总
框架 html5 html语法 css css3 css语法 框架 tailwind css 官网 JavaScript JavaScript语法 typescript 语法 nodejs 语法 vue3 官网 组件 vite 打包 vue router -路由 pinia - 状态管理 ui element plus axios - ajax 后台管理系统前端快速开发框架 …...
外部因素导致的 ADC误差来源分析
前面分享了ADC自身因素带来的误差,现在再分享一波由于外部因素导致的ADC采样误差。 一、模拟信号源输入减少带来的误差 看一个STM32的ADC转换器的示意图: 从图中可以看到,输入源与采样引脚之间存在阻抗RAIN,流入引脚的电压可能因…...
集成运算放大器知识汇总
一、集成运放的组成 集成运算放大器,就是通过内部元器件的电参量关系将电参量进行运算,达到放大的目的。我们拆解来看: 集成:将电路封装,留出接口,使其模块化,便于移植。运算:这里…...
HBCPC2025 补题 (F、I)
HBCPC2025 补题 补题连接:Codeforces I 感染 做法1:std做法:树上dp统计贡献找最大 #include <bits/stdc.h> using namespace std; typedef long long ll; #define endl \n #define int long long #define pb push_back #define pii pair<int,…...
针对 CSDN高质量博文发布 的详细指南
结合技术写作规范与平台特性,分为 内容规划、写作技巧、排版优化、发布策略 四部分,确保专业性与传播效果: 一、内容规划:精准定位与深度挖掘 选题策略 热点结合:追踪技术趋势(如2025年AIGC、量子计算&am…...
python读写bin文件
import numpy as np# 创建二进制数据 data np.array([0x33, 0x34, 0x35, 0x36], dtypenp.uint8)# 写入bin文件 with open(example.bin, wb) as f:data.tofile(f)print("bin文件生成成功")data np.fromfile(example.bin, dtypenp.uint8) print("numpy读取结果:…...
矩阵的秩(Rank)
矩阵的秩(Rank)是线性代数中的核心概念,表示矩阵中线性无关的行(或列)的最大数量,反映了矩阵所包含的“独立信息”的多少。以下是其核心要点: 1. 秩的定义 行秩:矩阵中线性无关的行…...
Vue响应式系统演进与实现解析
一、Vue 2 响应式实现详解 1. 核心代码实现 // 依赖收集器(观察者模式) class Dep {constructor() {this.subscribers new Set();}depend() {if (activeEffect) {this.subscribers.add(activeEffect);}}notify() {this.subscribers.forEach(effect &g…...
【SPIN】高级时序规范(SPIN学习系列--6)
时序操作符[](总是)和 <>(最终)可应用于任何LTL公式,因此 []<><>A 和 <>[]<>(A ∧ []B) 在语法上是正确的。本书不涉及LTL的演绎理论(如公理、推理规则及公式的结合律、交换…...
C语言学习之内存函数
今天我们来学习一下C语言中内存函数 以下内存函数的使用均需要包含头文件<string.h> 目录 memcpy函数的使用及其模拟实现 memcpy函数的模拟实现 memmove函数的使用和模拟实现 memmove函数的模拟实现 memset函数的使用 memcmp函数的使用 memcpy函数的使用及其模拟实现…...
Python 数据库编程
一、数据库连接基础 1. 标准流程 import database_module # 如mysql.connector, sqlite3等 # 1. 建立连接 connection database_module.connect( host"localhost", user"username", password"password", database"dbnam…...
软考软件评测师——软件工程之开发模型与方法
目录 一、核心概念 二、主流模型详解 (一)经典瀑布模型 (二)螺旋演进模型 (三)增量交付模型 (四)原型验证模型 (五)敏捷开发实践 三、模型选择指南 四…...
机器学习入门
机器学习入门 1 . 机器学习是什么? 机器学习(Machine Learning, ML)是一种用数据经验替代显式规则编程来完成任务的方法──模型从样本 (X, y) 中学习 映射函数 f: X → Y,并在新样本上做出预测。和传统“if … else”程序相比&…...
git学习与使用(远程仓库、分支、工作流)
文章目录 前言简介git的工作流程git的安装配置git环境:git config --globalgit的基本使用新建目录初始化仓库(repository)添加到暂存区新增/修改/删除 文件状态会改变 提交到仓库查看提交(commit)的历史记录git其他命令…...
制造业或跨境电商相关行业三种模式:OEM、ODM、OBM
一、基础概念对比 模式定义核心能力利润来源控制权OEM代工生产(贴牌生产)纯生产制造能力加工费(薄利)品牌方掌控一切ODM设计生产(自主设计代工)设计研发能力设计溢价生产利润制造商掌握设计OBM自主品牌&am…...
APPtrace 智能参数系统:重构 App 用户增长与运营逻辑
一、免填时代:APPtrace 颠覆传统参数传递模式 传统 App 依赖「邀请码 / 手动绑定」实现用户关联,流程繁琐导致 20%-30% 的用户流失。APPtrace 通过 **「链接参数自动传递 安装后智能识别」** 技术,让用户在无感知状态下完成关系绑定、场景还…...
在 Excel 中使用 C# .NET 用户定义函数 操作步骤
点开选项 点击加载项 点击跳转 点击浏览 选择仙盟excel...
PyTest
一、基本用法: 1.测试框架做了什么: (1).测试发现 a.创建test_开头的文件 b.创建Test开头的类 c.创建test_开头的函数或方法 pytest中以每一个函数或方法作为一个用例 pytest主要以名字区分普通函数(方法)、用例 pytest的启动方式:在给定的项目中执行pytest命令即可 p…...
Python Day27 学习
今天学习讲义Day17的内容:无监督算法中的聚类浙大疏锦行 Q1. 什么是聚类? 本质上就是一种分组分类 关于聚类的准备工作: 代码实现 # 先运行之前预处理好的代码 import pandas as pd import pandas as pd #用于数据处理和分析ÿ…...
在 Win 10 上,Tcl/Tk 脚本2个示例
set PATH 新增 D:\Git\mingw64\bin where tclsh D:\Git\mingw64\bin\tclsh.exe where wish D:\Git\mingw64\bin\wish.exe 编写 test_tk.tcl 如下 #!/usr/bin/tclsh # test 文件对话框 package require Tk# 弹出文件选择对话框,限制选择.txt文件 set filePath […...
渐开线少齿差传动学习笔记
之前看到了一个渐开线一齿差的视频,觉得比较有意思,想自己动手做一个看看,下面是最开始尝试的一个失败的结果,不知道小伙伴们发现问题了没? 本来就是想凑一凑看看,但是发现不是凑起来不是件容易的事。那么…...
基于CATIA参数化圆锥建模的自动化插件开发实践——NX建模之圆锥体命令的参考与移植(二)
引言 在CATIA二次开发领域,参数化建模技术可提升复杂几何体的创建效率达60%。本文基于PySide6 GUI框架与pycatia接口库,深度解析锥体自动化建模工具的开发实践。该工具创新性地融合了NX的交互逻辑与CATIA的混合建模技术,实现双模式输入(高度/锥角)的智能参数转换,较传统…...
Java集合框架详解:单列集合与双列集合
目录 1. 引言:为什么需要集合框架 2. 基础概念:集合框架概述 2.1 集合框架的结构 编辑 编辑 2.2 集合与数组的比较 3. 前置知识:理解集合框架背后的基础数据结构 3.1 数组 3.2 链表 3.3 哈希表 3.4 二叉树与二叉查找树 3.5 红…...
leetcode 33. Search in Rotated Sorted Array
题目描述 可以发现的是,将数组从中间分开成左右两部分的时候,一定至少有一部分的数组是有序的。左部分[left,mid-1],右部分[mid1,right]。 第一种情况:左右两部分都是有序的,说明nums[mid]就是整个数组的最大值。此时…...
OpenCV 图像色彩空间转换
一、知识点: 1、色彩空间转换函数 (1)、void cvtColor( InputArray src, OutputArray dst, int code, int dstCn 0, AlgorithmHint hint cv::ALGO_HINT_DEFAULT ); (2)、将图像从一种颜色空间转换为另一种。 (3)、参数说明: src: 输入图像,即要进行颜…...
python-leetcode 69.最小栈
题目: 设计一个支持push,pop,top,操作,并能在常数时间内检索到最小元素的栈。 辅助栈法: 1:使用两个栈,一个主栈用于存储所有元素,另一个辅助栈用于存储当前元素的最小值 2: 每次push时,将元…...
C#基础:yield return关键字的特点
一、特点 1.最终返回的结果是IEnumerable<T> 2.使用yield return时,返回的是单个元素(即T) 3.好处:延迟加载,需要时才计算 二、验证 通过打断点可知,只有当listB遍历的时候,才会进入Get…...
机器学习 集成学习方法之随机森林
集成学习方法之随机森林 1 集成学习2 随机森林的算法原理2.1 Sklearn API2.2 示例 1 集成学习 机器学习中有一种大类叫集成学习(Ensemble Learning),集成学习的基本思想就是将多个分类器组合,从而实现一个预测效果更好的集成分类…...
【Vue篇】组件的武林绝学:状态风暴下的乾坤挪移术
引言 🔍 Vue组件迷雾重重? ✧ Scoped如何实现样式隔离? ✧ Data为何必须是函数? ✧ 父子组件如何跨域通信? ✧ Props单向数据流如何破局? 🚀 本文直击组件化七大核心: ▸ 样式隔离原…...
使用亮数据代理IP+Python爬虫批量爬取招聘信息训练面试类AI智能体(手把手教学版)
文章目录 一、为什么要用代理IP?(重要!!!)二、环境准备(三件套走起)2.1 安装必备库(pip大法好)2.2 获取亮数据代理(官网注册送试用) 三、编写爬虫代码&#x…...
【MySQL】第七弹——复习总结 视图
文章目录 🌏客户端和数据库操作🌏表操作🌏CRUD 增删改查总结🌏数据库约束🌏表的设计🌏分组查询🌏聚合函数🌏联合查询🌏SQL语句中各部分的执行顺序🪐视图 &…...
基于Springboot + vue3实现的工商局商家管理系统
项目描述 本系统包含管理员、商家两个角色。 管理员角色: 用户管理:管理系统中所有用户的信息,包括添加、删除和修改用户。 许可证申请管理:管理商家的许可证申请,包括搜索、修改或删除许可证申请。 许可证审批管理…...
前端开发——前端样式BUG调试全指南2025终极版
前端开发——前端样式BUG调试全指南2025终极版 前端样式BUG调试指南(2025终极版)一、调试方法论与工具链1. 问题定位三板斧(1) 现象复现与特征提取(2) 现代调试工具组合拳(3) 三维问题定位法 2. 深度排查六步法步骤1:样式覆盖检测步骤2&#…...
【DeepSeek】为什么需要linux-header
编译Linux驱动程序时,通常需要 Linux内核头文件(linux-headers),而不是完整的源代码(linux-source)。以下是详细解释: 1. 为什么需要内核头文件? 头文件的作用: 内核头文…...
c语言刷题之实际问题
小乐乐定闹钟 代码如下: 小乐乐排电梯 代码如下: 小乐乐与欧几里得 代码如下: 小乐乐改数字 代码如下: 小乐乐走台阶 代码如下: 台阶为1,2时走法分别1,2种 1:(1) 2:(1,1),(2) 要走完n阶时,即我…...
【图像大模型】Kolors:基于自监督学习的通用视觉色彩增强系统深度解析
Kolors:基于自监督学习的通用视觉色彩增强系统深度解析 一、项目架构与技术原理1.1 系统定位与核心能力1.2 核心算法突破1.2.1 色彩感知表征学习1.2.2 动态色彩变换矩阵 二、系统实现与训练策略2.1 训练框架设计2.1.1 自监督预训练 2.2 损失函数设计 三、实战部署指…...
生产消费者模型 读写者模型
概念 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据…...
每日Prompt:双重曝光
提示词 双重曝光,Midjourney 风格,融合、混合、叠加的双重曝光图像,双重曝光风格。一幅由 Yukisakura 创作的杰出杰作,展现了一个奇妙的双重曝光构图,将阿拉贡阿拉松之子的剪影与生机勃勃春季里中土世界视觉上引人注目…...
基于springboot3 VUE3 火车订票系统前后端分离项目适合新手学习的项目包含 智能客服 换乘算法
博主介绍:专注于Java(springboot ssm 等开发框架) vue .net php phython node.js uniapp 微信小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设,从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆…...
DAY29 超大力王爱学Python
知识点回顾 类的装饰器装饰器思想的进一步理解:外部修改、动态类方法的定义:内部定义和外部定义 作业:复习类和函数的知识点,写下自己过去29天的学习心得,如对函数和类的理解,对python这门工具的理解等&…...
jvm对象压缩
最近在看一些文章,知道64位jvm在启动时指定-XX:UseCompressedOops后就会开启压缩,但是怎么压缩的,以及什么情况下压缩失效,没有一篇文章讲的特别透彻,在这里记录一下,后面抽时间进行更新。 参考文档 https…...
TripGenie:畅游济南旅行规划助手:个人工作纪实(十八)
本周,我增加了网页搜索并在右侧显示搜索链接与标题的功能,通过这个功能,可以帮助用户搜索网络上关于济南旅游的信息,提高用户检索信息的速度,用户可以通过点击网页链接了解更多关于济南的信息。 首先,我设计…...
Brooks Polycold快速循环水蒸气冷冻泵客户使用手含电路图,适用于真空室应用
Brooks Polycold快速循环水蒸气冷冻泵客户使用手含电路图,适用于真空室应用...
Rofin PowerLine E Air维护和集成手侧激光Maintenance and Integration Manual
Rofin PowerLine E Air维护和集成手侧激光Maintenance and Integration Manual...
C++中String类
1.String学习前的了解 1.1范围for和auto关键字 范围 for 循环 范围 for 循环是 C11 引入的一种语法糖,用于简化遍历容器或序列的代码。其基本语法如下: 迭代器版本: 范围for主要用来遍历一个容器(数据结构).范围for的(…...
LSTM语言模型验证代码
#任务:基于已有文本数据,建立LSTM模型,预测序列文字 1 完成数据预处理,将文字序列数据转化为可用于LSTM输入的数据。 2 查看文字数据预处理后的数据结构,并进行数据分离操作 3 针对字符串输入(“In the hea…...
Nextjs App Router 开发指南
Next.js是一个用于构建全栈web应用的React框架。App Router 是 nextjs 的基于文件系统的路由器,它使用了React的最新特性,比如 Server Components, Suspense, 和 Server Functions。 术语 树(Tree): 一种用于可视化的层次结构。例如,包含父…...