【从零开始分析项目实战】10-菜品管理业务开发(文件的上传与下载)
注:本文章基于黑马程序员相关视频及资料进行编写,代码简单,较容易理解,若有问题或者源码资料获取可以在评论区留言或者联系作者!
文章目录
- 开篇
- 一、文件的上传与下载
- (1)文件的上传
- (2)文件的下载
- 二、新增菜品
- (1)需求分析:
- (2)代码开发-准备工作
- (3)梳理交互过程
- (4)编码实现:
- 三、菜品信息分页查询
- (1)需求分析:
- (2)交互过程:
- (3)编码实现
- 四、修改菜品
- (1)需求分析:
- (2)交互过程:
- (3)编码实现:
- 总结:
开篇
菜品的起售、停售自行实现
一、文件的上传与下载
(1)文件的上传
也称为upload,是指将本地图片,视频,音频等文件上传到服务器,可以供其它用户浏览或下载的过程,文件上传在项目中应用非常广泛,我们经常发微博,朋友圈等都用到了文件上传功能;
服务端接受客户端页面上传的文件,通常会使用Apache的两个组件:
- commons-fileupload
- commons-io
spring框架在spring-web包中对文件上传作了封装处理,大大简化了服务端代码,我们只需要在controller方法中声明一个MultipartFile类型的参数即可接受上传的文件;
(1)文件上传的初步实现:
/* * 文件上传*/ @PostMapping("/upload") public R<String> upload(MultipartFile file) throws IOException {//注意这里的对象名必须和前端传过来的name相同; //file是一个临时文件,需要转存到指定位置,否则本次请求完成后,临时文件就会自动删除 log.info(file.toString()); //将临时文件转存到指定位置 file.transferTo(new File("F:\\springboot.jpg")); return null; }
(3)文件上传的功能完善
/* * 文件上传*/ @PostMapping("/upload") public R<String> upload(MultipartFile file) throws IOException {//注意这里的对象名必须和前端传过来的name相同; //file是一个临时文件,需要转存到指定位置,否则本次请求完成后,临时文件就会自动删除 log.info(file.toString()); //获取原始文件名 String originalFilename = file.getOriginalFilename(); String suffix=originalFilename.substring(originalFilename.lastIndexOf(".")); //创建一个目录对象 File dir=new File(basePath); //判断当前目录是否存在 if(!dir.exists()){ //如果不存在,则创建 dir.mkdirs(); } //使用uuid重新生成文件名,防止文件名重复造成文件覆盖 String filename = UUID.randomUUID().toString()+suffix;//xxxx.jpg //将临时文件转存到指定位置 file.transferTo(new File(basePath+filename)); return R.success(filename); }
(2)文件的下载
文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程。
通过浏览器进行文件下载,有两种表现形式:
- 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
- 直接在浏览器中进行打开
通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程;
/* * 文件下载 以流的方式进行返回,所以不需要数据返回*/ @GetMapping("/download") public void download(String name, HttpServletResponse response) throws IOException { //需要使用输入流,读取文件内容 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);//从第一个写,写len的长度 outputStream.flush(); } outputStream.close(); fileInputStream.close(); }
二、新增菜品
(1)需求分析:
后台系统可以管理菜品信息,通过新增功能来添加一个菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片,在移动端会按照菜品分类来展示对应的菜品信息;
新增菜品,实际上就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flsvor表插入数据,所以在添加新增菜品时,涉及到两个表dish(菜品)表和dish_falvor(菜品口味)表
(2)代码开发-准备工作
在开发代码之前,需要先将用到的类和接口基本结构创建好:
- 实体类DishFlavor(这里不再展示)
- Mapper接口Dishflavor
@Mapperpublic interface DishFlavorMapper extends BaseMapper<DishFlavor> {}
- 业务层接口DishflavorService
public interface DishService extends IService<Dish> {}
- 业务层实现类DishflavorServiceImpl
@Servicepublic class DishFlavorServiceImpl extends ServiceImpl implements DishFlavorService {}
- 控制层DishController
(3)梳理交互过程
- 页面add.html发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中
- 页面发送请求进行图片上传,请求服务器保存图片
- 页面发送请求进行图片下载,将上传的图片进行回显
- 点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交给服务端
(4)编码实现:
页面发送请求,服务器端返回菜品分类数据并展示到下拉框中;,在Categor中进行查询,由于页面会将查询的type属性值,也就是查询的是菜品还是套餐发送给后端服务器,所以用Category对象进行数据封装,然后进行查询返回;
@GetMapping("/list") public R<List<Category>> listR(Category category){ //条件构造器 LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>(); //添加条件 queryWrapper.eq(category.getType()!=null,Category::getType,category.getType()); //添加排序条件 queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime); List<Category> list = categoryService.list(queryWrapper); return R.success(list); }
由于前面已经对下载功能做过编码,所以这里直接进行第四步,服务器端接受前端传来的菜品信息,然后进行保存;
由于前端传来的信息具有Dish和Dishflavor两个对象,所以这里我们需要封装一个DishDto,用于封装页面提交的数据;
DTO:全称为Data TransferObject。及数据传输对象,一般用于展示层与服务层之间的数据传输
@Datapublic class DishDto extends Dish { private List<DishFlavor> flavors = new ArrayList<>(); private String categoryName; private Integer copies;}
首先在DishService中新增方法:
//新增菜品,同时插入菜品对应的口味数据,需要操作两张表,dish,dish_flavor public void saveWithFlavor(DishDto dishDto);
然后在DishServiceImpl中对方法进行实现:
//新增菜品,同时保存对应的口味 @Override @Transactional public void saveWithFlavor(DishDto dishDto) { //由于DishDto继承与Dish,所以可以直接进行保存菜品到菜品表中 this.save(dishDto); Long dishId=dishDto.getId();//菜品id //菜品口味 List<DishFlavor> flavors = dishDto.getFlavors(); flavors=flavors.stream().map((item)->{ item.setDishId(dishId); return item; }).collect(Collectors.toList()); dishFlavorService.saveBatch(flavors); }
运行程序:
三、菜品信息分页查询
(1)需求分析:
系统中的菜品数据很多的时候,如果在一个页面中展示出来会显得比较乱,不便于查看,所以采用分页的方式来展示列表的数据;
图片显示要用到之前的图片下载,菜品分类信息较为复杂
(2)交互过程:
- 页面(backend/page/food/list.html)发送ajax请求,将分页查询参数page,pageSize,name提交到服务器,获取分页数据;
- 页面发送请求,请求服务端进行图片下载,用于页面图片展示
开发菜品信息分页查询功能,其实就是在服务端编写代码处理前端页面的这两次请求
(3)编码实现
这里的分页查询和我们之前的分页查询基本实现方式一样,但唯一不同的是,这里进行分页查询的时候,页面显示的是菜品的分类名称,和菜品的其它信息,而在Dish表中只有菜品的分类id,要找到菜品的分类名称,也就是说还要到Category表中通过分类id查取分类名称;
具体实现可以使用DishDto类,里面刚好也封装了一个categoryName属性,首先通过page分页查询查出Dish表中的categoryid,然后将列表数据放在DishDto类中,再进行联合查询到categoryName,也放在DishDto类中,最后返回DishDto类型的分页查询对象;
@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); //对象拷贝,拷贝page,pageSize等属性,但是不包括records等获取到的属性 BeanUtils.copyProperties(pageinfo,dishDtoPage,"records"); //从原来的pageinfo中将一些属性值拿出来,列表形式 List<Dish> records = pageinfo.getRecords(); // List<DishDto> list=records.stream().map((item)->{//将records中的列表数据进行一条一条的迭代 //新建一个Dishdto类,因为里面有categoryName值 DishDto dishDto = new DishDto(); //将前端列表数据传给dishDto BeanUtils.copyProperties(item,dishDto); //从原来的数据获取菜品分类的id Long categoryId = item.getCategoryId(); //根据菜品分类id到分类列表中查询菜品这条数据 Category category = categoryService.getById(categoryId); if (category!=null){//获取菜品分类名String categoryName = category.getName();//在dishDto中对categoryName属性进行赋值dishDto.setCategoryName(categoryName); } return dishDto; }).collect(Collectors.toList());//转换为集合 //对dishDtoPage的Records属性进行封装 dishDtoPage.setRecords(list); return R.success(dishDtoPage); }
运行项目。页面能正常显示分页查询信息;
四、修改菜品
(1)需求分析:
在菜品管理列表页面点击修改按钮,跳转到修改菜品页面,在修改页面回显菜品相关信息并进行修改,最后点击确定按钮完成修改操作;
(2)交互过程:
- 页面发送AJAX请求,请求服务端获取分类数据,用于菜品分类下拉框中分类信息中的回显
- 页面发送AJAX请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显
- 页面发送请求,请求服务端进行图片下载,用于页面图片回显;
- 点击保存按钮,页面发送AJAX请求,将修改后的菜品信息以json数据格式提交 到服务端;
(3)编码实现:
首先页面发送AJAX请求,用于菜品分类下拉框中的分类信息的回显操作上面有实现过,所以这里可以直接使用;
然后页面发送AjAX请求,请求服务端,根据id查询当前菜品信息,由于设计到Dish表和Dish_flavor表的联合查询,然后对数据的组合,我们一样可以使用之前定义的DishDto类进行组合;
具体实现方法:
- 在DishService类中定义一个getByIdwithFlavor的方法;
//根据上传的信息查询Dish表和DishFlavor表 public DishDto getByIdWithFlavor(Long id);}
- 在DishServiceImpl类中对方法进行实现,分别从Dish表和Dish_Flavr表中查询数据,然后将数据重新组装到DishDto类中进行返回;
public DishDto getByIdWithFlavor(Long id) { DishDto dishDto = new DishDto(); //1.查询菜品的基本信息 从dish表查询 Dish dish=this.getById(id); BeanUtils.copyProperties(dish,dishDto); //2、查询当前菜品对应的口味信息,从dish_flavor表查询 LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(DishFlavor::getDishId,dish.getId()); List<DishFlavor> flavors = dishFlavorService.list(queryWrapper); dishDto.setFlavors(flavors); return dishDto; }
- 在DishController中编写具体接口方法:
/* 根据id查询菜品信息和对应的口味信息 要查两张表 */@GetMapping("/{id}") public R<DishDto> get(@PathVariable Long id){ DishDto byIdWithFlavor = dishService.getByIdWithFlavor(id); return R.success(byIdWithFlavor);}
对于页面发送请求,进行片的回显操作之前已经实现,所以也不需要再进行操作,所以最后的是将修改的信息进行提交给服务端进行保存;
具体实现步骤:
- 这里保存更新也涉及到两张表的操作,所以我们可以先在DishService类中新建一个updateByIdWithFlavor的方法;
//根据上传的信息更新Dish表和DishFlavor表 public void updateWithFlavor(DishDto dishDto);
- 在DishServiceImpl方法中进行实现,对上传的封装好的DishDto对象数据中的Dish和DIshFlavor数据提取出来,再分别进行操作;
/* * 更新菜品*/ @Override @Transactional public void updateWithFlavor(DishDto dishDto) { //更新dish表基本信息 this.updateById(dishDto); //清理当前菜品对应的口味数据--dish_flavor表的delete操作 LambdaQueryWrapper<DishFlavor> queryWrapper =new LambdaQueryWrapper<>(); queryWrapper.eq(DishFlavor::getDishId,dishDto.getId()); dishFlavorService.remove(queryWrapper); //添加当前提交过来的口味数据,--dish_flavor表的insert操作 List<DishFlavor> flavors = dishDto.getFlavors(); flavors=flavors.stream().map((item)->{ item.setDishId(dishDto.getId()); return item; }).collect(Collectors.toList()); dishFlavorService.saveBatch(flavors); }
3.在DishController中编写具体接口方法:
/*更新菜品*/ @PutMapping public R<String> update(@RequestBody DishDto dishDto){ log.info(dishDto.toString()); dishService.updateWithFlavor(dishDto); return R.success("新增菜品成功");
最后运行程序,能够在正常回显和修改菜品信息;
总结:
涉及到两张表的联合查询时:需要分别将两个表的数据查询出来,然后再通过一个Dto类进行封装组合(具体可以使用BeanUtils工具类的copyproperties方法) 涉及到两张表的联合新增时:需要将上传的数据(Dto类)分别获取出来,然后Dto的父类表可以直接进行插入保存,而另一个类表的数据进行公共字段的设置后,再进行插入 涉及到两个表的联合更新时,与新增类似
如果感觉内容写的还不错的话,一键三连不迷路!!!!
后面将会更新更多学习内容,一起学习吧!!!!!!