目录
一、文件上传和下载
1.1 文件上传和下载介绍
1.2 代码实现
yml配置文件:配置上传图片的存储位置
reggie: path: D:\img\
package com.learn.reggie.controller; import com.learn.reggie.common.R; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; 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 javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.UUID; /** * 文件上传和下载 */ @Slf4j @RestController @RequestMapping("/common") public class CommonController { @Value("${reggie.path}") private String basePath; /** * 文件上传 * @param file * @return */ @PostMapping("/upload") public R<String> upload(MultipartFile file){ //file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除 log.info(file.toString()); //原始文件名 String originalFilename = file.getOriginalFilename(); //abc.jpg String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); //使用UUID重新生成文件名,防止文件名称重复造成文件覆盖 String fileName = UUID.randomUUID().toString() + suffix; //创建一个目录对象 File dir = new File(basePath); //判断当前目录是否存在 if (!dir.exists()){ //目录不存在,需要创建 dir.mkdirs(); } try { //将临时文件转存到指定位置 file.transferTo(new File(basePath + fileName)); } catch (IOException e) { e.printStackTrace(); } return R.success(fileName); } /** * 文件下载 * @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.flush(); } //关闭资源 outputStream.close(); fileInputStream.close(); } catch (Exception e) { e.printStackTrace(); } } }
注意:这里上传的文件的文件名要和这个地方的一样,接收文件的参数的名不能随便定义,要和下面的name的值一致;
二、新增菜品
2.1 需求分析
2.2 数据模型
2.3 代码开发
创建相关的mapper和service层:
package com.learn.reggie.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.learn.reggie.entity.DishFlavor; import org.apache.ibatis.annotations.Mapper; /** * @author 咕咕猫 * @version 1.0 */ @Mapper public interface DishFlavorMapper extends BaseMapper<DishFlavor> { }
package com.learn.reggie.service; import com.baomidou.mybatisplus.extension.service.IService; import com.learn.reggie.entity.DishFlavor; /** * @author 咕咕猫 * @version 1.0 */ public interface DishFlavorService extends IService<DishFlavor> { }
package com.learn.reggie.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.learn.reggie.entity.DishFlavor; import com.learn.reggie.mapper.DishFlavorMapper; import com.learn.reggie.service.DishFlavorService; import com.learn.reggie.service.DishService; import org.springframework.stereotype.Service; /** * @author 咕咕猫 * @version 1.0 */ @Service public class DishFlavorServiceImpl extends ServiceImpl<DishFlavorMapper, DishFlavor> implements DishFlavorService { }
编写controller:
先获取和返回菜品分类列表; 前端主要的代码;
// 获取菜品分类列表 const getCategoryList = (params) => { return $axios({ url: '/category/list', method: 'get', params }) } if (res.code === 1) { this.dishList = res.data //这里就相当于把所有的category对象的数据赋值给dishList } 这是菜品分类和数据双向绑定的前端代码: 我们返回的是一个集合, </el-form-item> <el-form-item label="菜品分类:" prop="categoryId" > <el-select v-model="ruleForm.categoryId" placeholder="请选择菜品分类" > <el-option v-for="(item,index) in dishList" :key="index" :label="item.name" :value="item.id" /> </el-select> </el-form-item>
在CategoryController书写查询代码,不过这里的返回值和参数接收值可能和自己想的有点不一样。。。这个的返回值和参数值 值得多思考一下; 这里之所以返回list集合,是因为这个要展示的数据是引用类型的数据集,集合可以存放任意类型的数据;
/** * 根据条件查询分类数据 * @param category * @return */ @GetMapping("/list") //这个接口接收到参数其实就是一个前端传过来的type,这里之所以使用Category这个类来接受前端的数据,是为了以后方便 //因为这个Category类里面包含了type这个数据,返回的数据多了,你自己用啥取啥就行 public R<List<Category>> list(Category category){ //条件构造器 LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>(); //添加条件 queryWrapper.eq(category.getType() != null, Category::getType,category.getType()); //添加排序条件 使用两个排序条件,如果sort相同的情况下就使用更新时间进行排序 queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime); List<Category> list = categoryService.list(queryWrapper); return R.success(list); }
测试的返回数据:
接收页面提交的数据(涉及两张表)
点击保存按钮的时候,把前端的json数据提交到后台,后台接收数据,对数据进行处理;要与两张表打交道,一个是dish一个是dish_flavor表;
先用前端页面向后端发一次请求,看看前端具体的请求是什么,我们好写controller;然后再看前端提交携带的参数是什么,我们好选择用什么类型的数据来接收!!!
看下图:这是前端传过来的具体参数,我们需要什么参数类型来接收这些数据就大概知道了;因为这里传过来的参数比较复杂,所以这里有两种方式进行封装,第一:创建与这些数据对应的实体类(dto) ,第二使用map来接收;
这里我们选择使用第一种方式;
package com.learn.reggie.dto; import com.learn.reggie.entity.Dish; import com.learn.reggie.entity.DishFlavor; import lombok.Data; import java.util.ArrayList; import java.util.List; @Data public class DishDto extends Dish { private List<DishFlavor> flavors = new ArrayList<>(); private String categoryName; private Integer copies; }
前端关键代码:
<el-button type="primary" @click="submitForm('ruleForm')" > 保存 </el-button> let params = {...this.ruleForm} // params.flavors = this.dishFlavors params.status = this.ruleForm ? 1 : 0 params.price *= 100 //存到数据库的时候是以分为单位,所以这里x100 params.categoryId = this.ruleForm.categoryId params.flavors = this.dishFlavors.map(obj => ({ ...obj, value: JSON.stringify(obj.value) })) if (this.actionType == 'add') { delete params.id addDish(params).then(res => { if (res.code === 1) { this.$message.success('菜品添加成功!') if (!st) { this.goBack() } else { .... // 新增接口 const addDish = (params) => { return $axios({ url: '/dish', method: 'post', data: { ...params } }) }
后端代码:
在DishService中新增一个方法:
//新增菜品,同时插入菜品对应的口味数据,需要同时操作两张表:dish dish_flavor void saveWithFlavor(DishDto dishDto);
相关的实现:
package com.learn.reggie.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.learn.reggie.dto.DishDto; import com.learn.reggie.entity.Dish; import com.learn.reggie.entity.DishFlavor; import com.learn.reggie.mapper.DishMapper; import com.learn.reggie.service.DishFlavorService; import com.learn.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; /** * @author 咕咕猫 * @version 1.0 */ @Service @Slf4j public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService { @Autowired private DishFlavorService dishFlavorService; /** * 新增菜品,同时保存对应的口味数据 * @param dishDto */ @Transactional public void saveWithFlavor(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()); //保存菜品的口味数据到菜品口味表dish_flavor dishFlavorService.saveBatch(flavors); } }
在启动类开启事务: 加上这个注解就行 @EnableTransactionManagement
controller 层的代码:
package com.learn.reggie.controller; import com.learn.reggie.common.R; import com.learn.reggie.dto.DishDto; import com.learn.reggie.entity.Dish; import com.learn.reggie.service.DishFlavorService; import com.learn.reggie.service.DishService; 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; /** * 菜品管理 */ @RestController @RequestMapping("/dish") @Slf4j public class DishController { @Autowired private DishService dishService; @Autowired private DishFlavorService dishFlavorService; /** * 新增菜品 * @param dishDto * @return */ @PostMapping public R<String> save(@RequestBody DishDto dishDto){ //前端提交的是json数据的话,我们在后端就要使用这个注解来接收参数,否则接收到的数据全是null log.info(dishDto.toString()); dishService.saveWithFlavor(dishDto); return R.success("新增菜品成功"); } }
测试功能
三、菜品信息分页查询
3.1 需求分析
3.2 代码开发
图片下载的请求前面已经写好了,前端也写好了相关的请求,所以第二步的图片下载和展示就不需要管了;
controller层的代码(DishController):不过这里是有bug的,后面会改善;
/** * 菜品信息分页查询 * * @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); //对象拷贝 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); Long categoryId = item.getCategoryId();//分类id //根据id查询分类对象 Category category = categoryService.getById(categoryId); if (category != null) { String categoryName = category.getName(); dishDto.setCategoryName(categoryName); } return dishDto; }).collect(Collectors.toList()); dishDtoPage.setRecords(list); return R.success(dishDtoPage); }
功能完善:引入了DishDto
package com.learn.reggie.dto; import com.learn.reggie.entity.Dish; import com.learn.reggie.entity.DishFlavor; import lombok.Data; import java.util.ArrayList; import java.util.List; @Data public class DishDto extends Dish { private List<DishFlavor> flavors = new ArrayList<>(); private String categoryName; private Integer copies; }
功能测试
这边输入流error没关系,是io异常,图片查找的是位置在xml文件里配置,默认是D:/img
四、修改菜品
4.1 需求分析
4.2 代码开发
第一次交互的后端代码已经完成了;菜品分类的信息前面做新增菜品的时候就已经完成了,这里前端发一个相关接口的请求就行;
第三次交互,图片的下载前面也已经写了,所以前端直接发生请求就行;
菜品信息的回显
在service添加自己要实现的方法
//根据id来查询菜品信息和对应的口味信息 DishDto getByIdWithFlavor(Long id);
方法的实现:
/** * 根据id查询菜品信息和对应的口味信息 * @param id * @return */ public DishDto getByIdWithFlavor(Long id) { //查询菜品基本信息,从dish表查询 Dish dish = this.getById(id); DishDto dishDto = new DishDto(); BeanUtils.copyProperties(dish,dishDto); //查询当前菜品对应的口味信息,从dish_flavor LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(DishFlavor::getDishId,dish.getId()); List<DishFlavor> flavors = dishFlavorService.list(queryWrapper); dishDto.setFlavors(flavors); return dishDto; }
controller 层的编写:
/** * 根据id来查询菜品信息和对应的口味信息 * @param id * @return */ @GetMapping("/{id}") public R<DishDto> get(@PathVariable Long id){ //这里返回什么数据是要看前端需要什么数据,不能直接想当然的就返回Dish对象 DishDto dishDto = dishService.getByIdWithFlavor(id); return R.success(dishDto); }
相关的实现:
@Override @Transactional public void updateWithFlavor(DishDto dishDto) { //更新dish表的基本信息 因为这里的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(); //下面这段流的代码我注释,然后测试,发现一次是报dishId没有默认值(先测),两次可以得到结果(后测,重新编译过,清除缓存过),相隔半个小时 //因为这里拿到的flavorsz只有name和value(这是在设计数据封装的问题),不过debug测试的时候发现有时候可以拿到全部数据,有时候又不可以... 所以还是加上吧。。。。。 flavors = flavors.stream().map((item) -> { item.setDishId(dishDto.getId()); return item; }).collect(Collectors.toList()); dishFlavorService.saveBatch(flavors); }