boot实战-菜品管理
菜品管理
文件上传下载
因为菜品中要上传对应的图片所以要用到java中的io流,这里复习下文件的上传下载
以下是前端代码对文件上传的要求
目前这种文件上传一般直接使用开源的组件库(element-ui)
而我们服务端就是使用开源的基于io流的组件
只是目前Spring给我做了简化
文件上传
这里要注意我们写的方法参数要和表单对应的名字一样才能接收
/* * 文件上传和下载 * */ @RestController @RequestMapping("/common") public class CommonController { @Value("${reggie.path}") private String basePath; /** * 文件上传 * @param file * @return */ @PostMapping("/upload") // 注意这里的参数要和表单中对应的名字一样 public R<String> upload(MultipartFile file){ // 获取原始文件名 String originalFilename=file.getOriginalFilename(); // 截取文件后缀 String houzui = originalFilename.substring(originalFilename.lastIndexOf(".")); System.out.println(houzui); // 使用uuid随机生成文件名称:防止文件名重复照成文件覆盖 String fileName = UUID.randomUUID().toString()+houzui; //file是一个临时文件需要转存到指定位置,否者请求完成则删除 try { file.transferTo(new File(basePath+fileName)); } catch (IOException e) { e.printStackTrace(); } return R.success(fileName); } }
因为这里要存到指定的位置所以我们要在springboot配置文件中配置自定义配置
reggie: path: D:\image\
那如果我们指定的目录不存在呢?我们可以在转存之前判断目录是否存在,不存在就创建该目录
// 判断指定目录是否存在,不存在则创建目录 File dir=new File(basePath); // 判断目录是否存在 if(!dir.exists()){ // 目录不存在,创建 dir.mkdirs(); }
文件下载
在我们上传完之后,页面还会发起请求用于下载刚刚的图片使其展示出来
/** * 文件下载 * @param name * @param response */ @GetMapping("/download") public void download(String name, HttpServletResponse response){ // 通过输入流读取文件内容 try { FileInputStream fileInputStream=new FileInputStream(new File(basePath+name)); // 通过输出流,将文件写回浏览器 ServletOutputStream outputStream = response.getOutputStream(); // 定义字节流数组将我们的文件类容读入一次写入一次 response.setContentType("image/jpeg"); int len=0; byte[] bytes=new byte[1024]; while ((len=fileInputStream.read(bytes)) !=-1){ outputStream.write(bytes,0,len); } // 手动关闭流 outputStream.close(); fileInputStream.close(); } catch (Exception e) { e.printStackTrace(); } }
测试
新增菜品
需求分析
数据模型
及数据库中对应的表
框架搭建
搭建实体类,mapper,service,serviceimpl,(与前面的主要代码类似)controller
/** * 菜品管理 */ @RestController @RequestMapping("/dish") public class DishController { @Autowired private DishService dishService; @Autowired private DishFlavorService dishFlavorService; }
查询分类数据
就是个条件查询
/** * 根据条件查询分类数据 * @param category * @return */ @GetMapping("/list") public R<List<Category>> list(Category category){ // 条件构造器(SELECT * FROM category WHERE type=?) LambdaQueryWrapper<Category> queryWrapper=new LambdaQueryWrapper<>(); // 添加条件 queryWrapper.eq(category.getType()!=null,Category::getType,category.getType()); // 添加排序条件(优先使用sort字段排序,相同情况再使用updatetime排序) queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime); List<Category> list = categoryService.list(queryWrapper); return R.success(list); }
菜品图片上传
其实就是我们之前写好了的文件上传下载的代码
接收页面提交数据
根据分析我们就是要接受数据保存到dish(菜品表)和dish-flavor(口味表)
这里要注意因为里面有两个实体类的数据,所以我们选哪个实体类都不好,直接创造个新的实体类,使其能够封装所有参数(继承原实体类补充新的属性用于接收)
因为我们在dish的基础上要插入对菜品口味的保存,所以我们要在DishServic中添加下自定义的保存方法,随后完成实现类,这里因为是同时操作两张表的方法所以我们要开启事务 @Transactional(注意要到运行类中开启事务功能)
//开启事务支持 @EnableTransactionManagement
同时通过前端提交的数据你会发现flavor中没有对应的id所以我们要用for循环或者stream为flavor添加id
public interface DishService extends IService<Dish> { //新增菜品,同时插入菜品对应的口味数据,需要操作两张表:dish dish_flavor public void saveWithFlavor(DishDto dishDto); }
@Service @Slf4j public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService { @Autowired private DishFlavorService dishFlavorService; /** * 新增菜品,同时保存口味数据 * @param dishDto */ // 因为要同时操作多表,开启事务支持 @Transactional @Override public void saveWithFlavor(DishDto dishDto) { // 保存菜品基本信息到菜品表 this.save(dishDto); // 获取菜品id Long id = dishDto.getId(); //保存菜品口味数据到口味表 List<DishFlavor> flavors = dishDto.getFlavors(); for(Object flavor:flavors){ DishFlavor dishFlavor=(DishFlavor) flavor; dishFlavor.setDishId(id); } dishFlavorService.saveBatch(flavors); } }
//可用此代替上面的for flavors=flavors.stream().map((item)->{ item.setDishId(id); return item; }).collect(Collectors.toList());
最后完成controller进行测试
菜品信息分页查询
需求分析
有两个难点一是要显示图片(用到之前的图片下载),二是要显示菜品分类,要通过id从分类表中查询数据
代码开发
显示已有的菜品,分页查询和之前的基本一样
我们来模仿之前的完成分页查询
@GetMapping("/page") public R<Page> page(int page,int pageSize,String name){ // 构造分页构造器 Page<Dish> pageInfo=new Page<>(page,pageSize); // 条件构造器 LambdaQueryWrapper<Dish> queryWrapper=new LambdaQueryWrapper<>(); // 添加条件 queryWrapper.like(name!=null,Dish::getName,name); queryWrapper.orderByDesc(Dish::getUpdateTime); dishService.page(pageInfo,queryWrapper); return R.success(pageInfo); }
可是我们直接返回pageInfo可以发现分类的列没有数据
其实这是因为我们返回的直接是分类表中对应的id而没有查到字段,我们需要返回的是categoryName
这就是page里面的泛型Dish不含有这个属性
最后处理完成代码
/** * 分页查询菜品 * @param page * @param pageSize * @param name * @return */ @GetMapping("/page") public R<Page> page(int page,int pageSize,String name){ // 构造分页构造器 Page<Dish> pageInfo=new Page<>(page,pageSize); Page<DishDto> dishDtoPage=new Page<>(); // 条件构造器 LambdaQueryWrapper<Dish> queryWrapper=new LambdaQueryWrapper<>(); // 添加条件 queryWrapper.like(name!=null,Dish::getName,name); queryWrapper.orderByDesc(Dish::getUpdateTime); dishService.page(pageInfo,queryWrapper); // 对象拷贝,排除records属性 BeanUtils.copyProperties(pageInfo,dishDtoPage,"records"); List<Dish> records = pageInfo.getRecords(); List<DishDto> list= records.stream().map((item)->{ DishDto dishDto=new DishDto(); BeanUtils.copyProperties(item,dishDto); // 获取每一项的分类id Long categoryId = item.getCategoryId(); // 根据id查询分类对象 Category category = categoryService.getById(categoryId); // 获取每一项对应分类名称 String categoryName = category.getName(); dishDto.setCategoryName(categoryName); return dishDto; }).collect(Collectors.toList()); dishDtoPage.setRecords(list); return R.success(dishDtoPage); }
修改菜品
需求分析
交互过程
代码开发
这里是result风格但是要注意,因为需要返回的信息不是只有dish一张表的所以我们要联合口味表查,不能简单封装成Dish对象
DishDto和Dish不同的就是多了几个参数,我们正好需要List来存对应菜品的口味
我们不妨模仿之前新增菜品在DishService中添加一个自定义的查询方法,实现类中根据dishId查出DishFlavor的list集合,新定义一个DishDto对象,将查出的dish对象复制给dishdto,同时将dishflavors赋值给disdto
service&serviceImpl
// 根据id查询,查询同时查出对应的口味 public List<DishFlavor> getWithFlavor(Long dishId); ---------------------- /** * 根据dish_id查出dish&List<dishflavor>将其封装成dishdto * @param dishId * @return */ @Override public List<DishFlavor> getWithFlavor(Long dishId) { DishDto dishDto=new DishDto(); LambdaQueryWrapper<DishFlavor> queryWrapper=new LambdaQueryWrapper<>(); queryWrapper.eq(DishFlavor::getDishId,dishId); List<DishFlavor> dishFlavor = dishFlavorService.list(queryWrapper); System.out.println(dishFlavor); return dishFlavor; }
controller
@GetMapping("/{id}") public R<DishDto> GetById(@PathVariable Long id){ DishDto dishDto=new DishDto(); List<DishFlavor> withFlavor = dishService.getWithFlavor(id); Dish dish = dishService.getById(id); BeanUtils.copyProperties(dish,dishDto); dishDto.setFlavors(withFlavor); return R.success(dishDto); }
这里只是完成了菜品信息的显示
以下来完成菜品信息的更新,因为是多表操作,所以别忘了使用事务
可见前端直接返回的是DishDto对象,我们更新口味的表有个思路是先删除对应的dish_id对应的口味数据,随后在存入,因为我们修改可能是增加或者删除操作,所以我们直接更新其口味,可以把现有口味清空在存入信息中的口味。
/** * 根据提供的DishDto对象同时更新菜品和口味信息 * @param dishDto * @return */ @Transactional @PutMapping public R<String> Update(@RequestBody DishDto dishDto){ //直接更新菜品信息 dishService.updateById(dishDto); //查询出菜品对应的口味,清空数据 LambdaQueryWrapper<DishFlavor> queryWrapper=new LambdaQueryWrapper(); queryWrapper.eq(DishFlavor::getDishId,dishDto.getId()); dishFlavorService.remove(queryWrapper); //获取更改后的口味信息全部保存 List<DishFlavor> flavors = dishDto.getFlavors(); for (Object item:flavors){ DishFlavor dishFlavor=(DishFlavor) item; dishFlavor.setDishId(dishDto.getId()); dishFlavorService.save(dishFlavor); } return R.success("更新菜品成功!"); }
删除菜品(批量)
需求分析
点击删除按钮,删除对应的菜品以及其口味信息(添加事务),可以同时选择多个id传入完成批量删除
这里代码和修改菜品里面的部分代码类似
代码开发
/** * 批量删除菜品及对应口味 * @param ids * @return */ @Transactional @DeleteMapping public R<String> deleteByIds(Long[] ids){ List<Long> idList= Arrays.asList(ids); dishService.removeByIds(idList); for (Long id:idList){ LambdaQueryWrapper<DishFlavor> queryWrapper=new LambdaQueryWrapper<>(); queryWrapper.eq(DishFlavor::getDishId,id); dishFlavorService.remove(queryWrapper); } return R.success("删除菜品成功!"); }
停售菜品
需求分析
可以单击菜品完成停售/启售,也可以批量停售和批量起售
请求路径都是一样的
代码开发
这里注意看我们传入的status就是我们要改的值,这里我要禁用菜品,他传入的是0,正好我们需要将1改成0!!
那代码巨简单了
/** * 批量启售禁售菜品 * @param ids * @param status * @return */ @PostMapping("/status/{status}") public R<String> updateStatus(Long[] ids,@PathVariable int status){ for (Long id:ids){ Dish dish = dishService.getById(id); dish.setStatus(status); dishService.updateById(dish); } return R.success("更新状态成功"); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现