菜品管理业务开发 |
您所在的位置:网站首页 › 上传文件过程修改了文件名 › 菜品管理业务开发 |
1. 文件的上传下载 4-2 这里解释文件的上传时上传到我们后端指定的路径,下载时下到前端页面去 1.1 文件上传介绍 4-2文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。 文件上传时,对页面的form表单有如下要求: ●method="post" 采用post方式提交数据 ●enctype="multipart/ form-data" 采用multipart格式上传文件 ●type="file" 使用input的file控件.上传 举例: 服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件: ●commons-fileupload ● commons-io Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件,例如: 文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程。 通过浏览器进行文件下载,通常有两种表现形式: ● 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录 ●直接在浏览器中打开 通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。 2. 文件上传代码实现 4-3文件上传,页面端可以使用ElementUl提供的上传组件。 可以直接使用资料中提供的上传页面,位置 E:\java学习\瑞吉外卖\资料\1 瑞吉外卖项目\资料\文件上传下载页面 启动项目测试访问一下(这是没有写Controller类呢) http://localhost:8080/backend/page/demo/upload.html 完成功能 文件上传和下载的控制器类CommonController 4-3package com.itheima.reggie.controller; import com.itheima.reggie.common.R; import lombok.extern.slf4j.Slf4j; 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; //专门负责我们的文件上传和下载的控制器类 4-3 @RestController @RequestMapping("/common") @Slf4j public class CommonController { //文件上传 4-3 @PostMapping("/upload") public R upload(MultipartFile file){ //file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除 log.info(file.toString()); return null; } }这里测试要先登录,在去访问http://localhost:8080/backend/page/demo/upload.html 然后上传图片,才可使我们的file形参拿到图片资源(解释为什么,肯定有疑问不可以直接访问http://localhost:8080/backend/page/demo/upload.html吗,因为我们的demo/upload.html也是放在backend目录下的呀,而且backend目录还是被我们指定了静态资源路径映射的直接访问不就行了,其实这么理解是完全错误的,我们的目的是让我们的Controller类拿到图片资源(其实就是访问common/upload路径)而不是访问它(upload.html页面),应为我们设置过滤器的所以当我们访问上述路径添加图片时会访问common/upload路径(这个upload路径就是我们的Controller类upload方法上的,所以你要访问这个,就必须过过滤器这一关) 但是 这个路径在过滤器中会被拦截(因为只有过滤器中放行的路径可以通过,具体参考过滤器代码),所以我们就无法让我们的Controller类拿到图片资源 因为我们去添加图片每次都要先登录,在访问路径,在添加巴拉巴拉,很麻烦,所以重点我们直接在过滤器中设置放行common/upload路径(再次强调我们的目的是让Controller拿到图片资源,而不是访问它所以肯定要放行/common/**路径) //定义不需要处理的请求路径 2-3 String[] urls = new String[]{ "/employee/login", "/employee/logout", "/backend/**", "/front/**", "/common/**" };application.properties#配置文件下载的路径 4-4 reggie: path: E:\java\ruijiwaimai_download_picture_temp\测试随机生成文件名 在test中测试 UploadFileTest package com.itheima.test; import org.junit.jupiter.api.Test; public class UploadFileTest { //测试随机生成的文件名 4-4 @Test public void test1(){ String fileName = "ererewe.jpg"; String suffix = fileName.substring(fileName.lastIndexOf(".")); System.out.println(suffix); } }没问题 访问http://localhost:8080/backend/page/demo/upload.html添加图片 临时文件转存成功 温馨提示,输入流是读文件将一个文件读到一个数组中,输出流是写文件将文件从一个数组写到浏览器 后台系统中可以管理菜品信息,通过新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片,在移动端会按照菜品分类来展示对应的菜品信息。 新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_ flavor表插入数据。 所以在新增菜品时,涉及到两个表: ●dish 菜品表 ●dish_ flavor 菜品口味表 dish表 dish_ flavor表 在开发业务功能前,先将需要用到的类和接口基本结构创建好: ●实体类DishFlavor (直接从课程资料中导入即可,Dish实体前面课程中已经导入过了) ●Mapper接口 DishFlavorMapper 业务层接口DishFlavorService ●业务层实现类DishFlavorServicelmpl ●控制层DishController 菜品口味DishFlavor package com.itheima.reggie.entity; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import lombok.Data; import java.io.Serializable; import java.time.LocalDateTime; /** 菜品口味 4-7 */ @Data public class DishFlavor implements Serializable { private static final long serialVersionUID = 1L; private Long id; //菜品id private Long dishId; //口味名称 private String name; //口味数据list private String value; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser; //是否删除 private Integer isDeleted; }菜品口味 持久层接口 DishFlavorMapper package com.itheima.reggie.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.itheima.reggie.entity.DishFlavor; import org.apache.ibatis.annotations.Mapper; //菜品口味 持久层接口 4-7 @Mapper public interface DishFlavorMapper extends BaseMapper { }菜品口味 业务层接口DishFlavorService package com.itheima.reggie.service; import com.baomidou.mybatisplus.extension.service.IService; import com.itheima.reggie.entity.DishFlavor; //菜品口味 业务层接口 4-7 public interface DishFlavorService extends IService { }菜品口味 业务层接口实现类DishFlavorServiceImpl package com.itheima.reggie.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.itheima.reggie.entity.DishFlavor; import com.itheima.reggie.mapper.DishFlavorMapper; import com.itheima.reggie.service.DishFlavorService; import org.springframework.stereotype.Service; //菜品口味 业务层接口实现类 4-7 @Service public class DishFlavorServiceImpl extends ServiceImpl implements DishFlavorService { } 4.4 新增菜品功能开发 4-7代码开发梳理交互过程 在开发代码之前,需要梳理一下新增菜品时前端页面和服务端的交互过程: 1.页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中 2、页面发送请求进行图片上传,请求服务端将图片保存到服务器 3、页面发送请求进行图片下载,将上传的图片进行回显 4、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端 开发新增菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。 第一步 4-81.页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中 我们已经在上面实现了 第四步 4-104、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端因为上图可知前端传来的数据既有dish表的还有dish_flavors表的,所以我们不能简单的只用dish实体来接收,需要导入DTO 导入DTO 资料路径E:\java学习\瑞吉外卖\资料\1 瑞吉外卖项目\资料\dto debug,发现口味表中的dishId并没有赋值只是name和value赋值了,这个重点,在我们将数保存到数据库时要注意 我们在DishServiceImpl业务层中自己实现数据保存(因为涉及两张表操作框架无法为我们提供合适的方法) package com.itheima.reggie.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.itheima.reggie.dto.DishDto; import com.itheima.reggie.entity.Dish; import com.itheima.reggie.entity.DishFlavor; import com.itheima.reggie.mapper.DishMapper; import com.itheima.reggie.service.DishFlavorService; import com.itheima.reggie.service.DishService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.stream.Collectors; //菜品管理业务层接口实现类 3-8 @Service @Slf4j public class DishServiceImpl extends ServiceImpl implements DishService { @Autowired private DishFlavorService dishFlavorService; //新增菜品,同时保存对应的口味数据 4-10 @Transactional //控制事务 4-10 public void saveWithFlavor(DishDto dishDto) { //保存菜品的基本信息到菜品表dish this.save(dishDto); //解释为什么还要设置菜品id,因为我们在保存菜品口味表dish_flavor时,是没有保存菜品id的 //原因在于,我们用的是数据传输对象DTO而DishDto没有做保存dishId(在我们通过debug时 // 发现的)所以我们需要对DishDto中封装的Flavors做一些改动,首先遍历 // 我们通过dishDto.getFlavors()得到DishDto中封装的Flavors集合,然后取出集合中的 //元素(也就是Flavors对象),给他们的dishId赋值,结束后在在将其封装成list集合 // (因为我们改了原来集合,需要将改变了dishId属性的Flavors对象也添加进list集合) //获取菜品id Long dishId = dishDto.getId(); //得到集合 List flavors = dishDto.getFlavors(); //遍历集合 //.stream()是将集合转化为流,.map是将流中的元素计算或者转换(此处用map是为流中的 // dishId赋值) .collect是将流转化为集合 flavors = flavors.stream().map((item)->{ item.setDishId(dishId); return item; }).collect(Collectors.toList());//.collect(Collectors.toList())改成list集合 //保存菜品口味数据到菜品口味表dish_flavor dishFlavorService.saveBatch(flavors); } }是事务起作用 测试成功 4-11 菜品表dish 口味表dish_flavor 在开发代码之前,需要梳理一下菜品分页查询时前端页面和服务端的交互过程: 1、页面(backend/ page/food/list.html)发送ajax请求,将分页查询参数(page、pageSize、 name)提交到服务端,获取分页数据 2、页面发送请求,请求服务端进行图片下载,用于页面图片展示 开发菜品信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。 //菜品分页查询 4-13 @GetMapping("/page") public R page(int page,int pageSize,String name){ log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name); //构造分页构造器 //泛型只简单的使用Dish是不行的,因为没办法把菜品分类名称展示出来,所以还需要数据传输对象DTO 4-14 Page pageInfo = new Page(page, pageSize); //构造条件构造器 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); //添加过滤条件 queryWrapper.like(name!=null,Dish::getName,name); //添加排序条件 queryWrapper.orderByDesc(Dish::getUpdateTime); //执行分页查询 //这条语句会将我们查询到的数据进行封装 全部封装进Page的对象pageInfo dishService.page(pageInfo,queryWrapper); return R.success(dishDtoPage); }前端需要的数据 后端响应的数据 由结果可以看出,我们的菜品分类名称并没有展示出来,这是为什么呢?因为前端需要的是菜品分 类name属性,而我们后端给他传过去的是菜品分类的id,原因就在于我们用的是dish作为泛型来接收(Page dishDtoPage = new Page(); 重点的我们的目的就是得到一个泛型为DishDto的Page对象即dishDtoPage 图解 首先声明注入CategoryService属性 DishController //菜品分页查询 4-13 @GetMapping("/page") public R page(int page,int pageSize,String name){ log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name); //构造分页构造器 //泛型只简单的使用Dish是不行的,因为没办法把菜品分类名称展示出来,所以还需要数据传输对象DTO 4-14 Page pageInfo = new Page(page, pageSize); Page dishDtoPage = new Page(); //构造条件构造器 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); //添加过滤条件 queryWrapper.like(name!=null,Dish::getName,name); //添加排序条件 queryWrapper.orderByDesc(Dish::getUpdateTime); //执行分页查询 //这条语句会将我们查询到的数据进行封装 全部封装进Page的对象pageInfo dishService.page(pageInfo,queryWrapper); /* 解释 由结果可以看出,我们的菜品分类名称并没有展示出来,这是为什么呢?因为前端需要的是菜品分 类name属性,而我们后端给他传过去的是菜品分类的id,原因就在于我们用的是dish作为泛型来 接收(Page pageInfo = new Page(page, pageSize);),而dish里面没 有name属性,但是dishdto里面由categoryName属性,所以我们在 写一个Page dishDtoPage = new Page(); 重点的我们的目的就是得到一个泛型为DishDto的Page对象即dishDtoPage 需要将pageInfo拷贝给dishDtoPage但是但是不拷贝"records" 属性 因为这个records对 应的泛型是dish而我们需要的泛型是dishDto records集合 (集合里的每一个对象都是dish表的一条数据)在拿到records集合后 遍历 records集合—>stream流->map(取出集合数据) 将item对象(item就是records的一条记录)赋值给新new的dishDto对象,利用item的到分类id 根据id查询分类对象进而取出name属性值,将name设置给给dishDto的categoryName属性 最后再将流转换为DishDto泛型的list集合,再将带有categoryName属性的list集合设置 给dishDtoPage对象的records属性 */ //对象(都是Page对象)拷贝 但是不拷贝"records" 属性 因为这个records对应的泛型是dish // 而我们需要的泛型是dishDto 4-14 BeanUtils.copyProperties(pageInfo,dishDtoPage,"records"); //得到records集合 List records = pageInfo.getRecords(); //使用 流 遍历集合 List list = records.stream().map((item) -> { DishDto dishDto = new DishDto(); //因为上面new的DishDto的属性都是空值,需要将item对象赋值给dishDto BeanUtils.copyProperties(item,dishDto); Long categoryId = item.getCategoryId();//分类id //根据id查询分类对象 Category category = categoryService.getById(categoryId); if(category != null){ //得到分类名称 String categoryName = category.getName(); //给dishDto的categoryName属性设置值 dishDto.setCategoryName(categoryName); } return dishDto; }).collect(Collectors.toList());//将dishDto对象收集起来编程list集合 //经过上述操作,就得到了泛型为DishDto的list集合,而DishDto里还有categoryName属性 //再将带有categoryName属性的list集合设置给dishDtoPage对象的records属性 dishDtoPage.setRecords(list); return R.success(dishDtoPage); }代码开发-梳理交互过程 在开发代码之前,需要梳理一下修改菜品时前端页面(add.html) 和服务端的交互过程: 1、页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示 2、页面发送ajax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显 3、页面发送请求,请求服务端进行图片下载,用于页图片回显 4、点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端 开发修改菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。 2、页面发送ajax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显 菜品管理业务层接口DishService//根据菜品id来查询菜品信息和口味信息 4-17 public DishDto getByIdWithFlavor(Long id);业务层接口实现类DishServiceImpl/** * 根据菜品id查询菜品信息和对应的口味信息 4-17 * @param id * @return */ public DishDto getByIdWithFlavor(Long id) { //查询菜品基本信息,从dish表查询 Dish dish = this.getById(id); //对象拷贝,这里拷贝的是普通属性,不包括flavor DishDto dishDto = new DishDto(); BeanUtils.copyProperties(dish,dishDto); //DishFlavor byId = dishFlavorService.getById(id); //这里解释为什么不能直接用这行代码查口味表数据,因为这个id是菜品id不是口味表id,这样查查不到的 //所有要用下面的方法构造查询条件,查询条件就是口味对应的菜品id //查询当前菜品对应的口味信息,从dish_flavor表查询 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); queryWrapper.eq(DishFlavor::getDishId,dish.getId());//getDishId菜品id List flavors = dishFlavorService.list(queryWrapper); //这里将flavor设置进去 dishDto.setFlavors(flavors); return dishDto; } 菜品管理 DishController//修改菜品 之回显数据 4-17 //根据菜品id来查询菜品信息和口味信息 @GetMapping("/{id}") public R get(@PathVariable Long id){ //这里使用我们自己的方法,因为DishDto中包含了dish_flavor表 DishDto dishDto = dishService.getByIdWithFlavor(id); return R.success(dishDto); }测试回显成功 4、点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端 测试成功 停售 起售(起售传来的请求几乎和停售一样,只是 0-->1 也就是状态值的差异而已) 批量停售 批量起售 (批量起售传来的请求几乎和批量停售一样,只是 0-->1 也就是状态值的差异而已) 由上图分析可知,四种功能的请求路径一样,且都使用了restful风格,因此可以封装成一个代码实现 DishController//修改菜品售卖状态 ,自己实现的 可以批量可以单个 @PostMapping("/status/{status}") public R stop(@PathVariable int status,@RequestParam List ids){ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); queryWrapper.in(Dish::getId,ids);//先构造id(可能一个也可能多个,因为我们单独停售起售和批量写一起了) //在构造售卖状态 Dish dish = new Dish(); dish.setStatus(status); dishService.update(dish,queryWrapper); return R.success("菜品售卖状态修改成功"); }测试成功 删除一个 批量删除 由上图可知,删除和批量删除请求路径是一样的,所以可以封装成一个方法 我们设置处在售卖状态的菜品不能删除 菜品管理业务层接口DishService//删除菜品同时删除和菜品关联的口味表数据 自己实现 public void removeWithDish(List ids); 菜品管理业务层接口实现类DishServiceImpl//删除菜品同时删除和菜品关联的口味表数据 自己实现 @Override public void removeWithDish(List ids) { //查询菜品状态看看是否处于售卖状态,看看是否可以删除 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); queryWrapper.in(Dish::getId,ids); queryWrapper.eq(Dish::getStatus,1); //这里的思想是根据id和status查询,如果查询出来的数据大于0,就证明处于售卖状态,否则为停售状态 int count = this.count(queryWrapper); if(count > 0){ //如果不能删除,抛出一个业务异常 throw new CustomException("菜品正在售卖中,不能删除"); } //如果可以删除,先删除菜品表中的数据---dish this.removeByIds(ids); //接着删除口味表章关联的数据 LambdaQueryWrapper dishFlavorLambdaQueryWrapper = new LambdaQueryWrapper(); dishFlavorLambdaQueryWrapper.in(DishFlavor::getDishId,ids); dishFlavorService.remove(dishFlavorLambdaQueryWrapper); } DishController//删除菜品,自己实现 可以批量可以单个 @DeleteMapping public R delete(@RequestParam List ids){ //调用我们自己的方法,因为删除操作涉及了两张表菜品表和口味表 dishService.removeWithDish(ids); return R.success("删除菜品成功"); }测试成功 有菜品在售卖不能删除 |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |