springboot项目:瑞吉外卖 前后端详细分析 part3

part 1
第二部分链接part 2
part 3 本文章
part 4

4 菜品管理

4.1 文件上传(后端为什么要返回文件名给前端、yml中自定义路径值并在类中取出、文件如何转存到指定位置、用UUID防止文件名称重复造成文件覆盖)
4.2 文件下载
4.3 新增菜品 (设计多表操作,事务保证一致性,DTO的使用,自己编写controller,值得学习)
4.4 菜品信息分页查询 (多表联合操作、Dto进一步使用)
4.5 修改菜品 ()

4.1 文件上传

4.1.1 整体思路分析

  • 知识点介绍

在这里插入图片描述在这里插入图片描述

  • 具体实现介绍
    在这里插入图片描述
    上面那段代码会动态地改变元素,生成下面这个input标签

在这里插入图片描述

  • 后面要做的就是写一个controller,接收前端发来的请求

4.1.2 前端分析

  • upload.html文件上传页面
<body>
   <div class="addBrand-container" id="food-add-app">
    <div class="container">
        <!--
            el-upload的upload组件:
            action:通过提交表单的形式来发送请求,和controller对应起来
            method:必须是post
            文件必须是form-data的形式,这个从浏览器F12就可以观察出来
        -->
        <el-upload class="avatar-uploader"
                action="/common/upload"
                :show-file-list="false"
                :on-success="handleAvatarSuccess"
                :before-upload="beforeUpload"
                ref="upload">
            <img v-if="imageUrl" :src="imageUrl" class="avatar"></img>
            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
        </el-upload>
    </div>
  </div>
   ...
    <script>
      new Vue({
        el: '#food-add-app',
        data() {
          return {
            imageUrl: ''
          }
        },
        methods: {
          handleAvatarSuccess (response, file, fileList) {
              this.imageUrl = `/common/download?name=${response.data}`
          },
          beforeUpload (file) {
            if(file){
              const suffix = file.name.split('.')[1]
              const size = file.size / 1024 / 1024 < 2
              if(['png','jpeg','jpg'].indexOf(suffix) < 0){
                this.$message.error('上传图片只支持 png、jpeg、jpg 格式!')
                this.$refs.upload.clearFiles()
                return false
              }
              if(!size){
                this.$message.error('上传文件大小不能超过 2MB!')
                return false
              }
              return file
            }
          }
        }
      })
    </script>

  • 上传文件后,404错误,因为controller没写。同时也注意拦截器的配置,当时配置的是只拦截controller(除了登入和登出的),其他均放行
// 是我们的conrtoller中的方法就拦截,如果不是的话,放行,给加载静态资源
if (!(handler instanceof HandlerMethod)) {
    log.info("是静态资源或非controller中的方法,放行");
    return true;
}

在这里插入图片描述
requestHeader中: Content-Type: multipart/form-data;

  • 注意观察写好后端成功上传后的form-data
    在这里插入图片描述
  • 在element中可以观察到,原来的<el-upload>标签动态生成了input 标签(原来的el-upload标签在页面上有样式,执行之后其实也是input标签)
    在这里插入图片描述

4.1.3 后端代码分析

  • MultipartFile下的transferTo()方法。注意下载到的地址,文件名的设置不要使用上传端的,否则重名之后会覆盖,思路是使用UUID
  1. 将路径配置到yml文件中,这里取出; 注意!!!不是lombok下的@Value,而是org.springframework.beans.factory.annotation.Value;解决一直报错问题
  2. 知识点二,资源转存,注意观看每一步需要的类型是什么;file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
  3. 知识点三、获取原文件的名字,并取出后缀,使用UUID修改文件名,防止覆盖
  4. 知识点四、判断目录是否存在,不存在就要创建
@Slf4j
@RestController
@RequestMapping("/backend/page/")
public class UploadDownloadController {

    //知识点1、将路径配置到yml文件中,这里取出; 注意!!!不是lombok下的@Value,而是org.springframework.beans.factory.annotation.Value;解决一直报错问题
    @Value("${custom.download-path}")
    private String basePath;
    /**
     * 文件上传
     * @param file 注意形参是MultipartFile类型
     * @return
     */
    @PostMapping("upload/upload.do")
    public RetObj<String> upload(MultipartFile file){
        log.info("MultipartFile的值 = {}",file.toString());

        //知识点三、获取原文件的名字,并取出后缀,使用UUID修改文件名,防止覆盖
        String originalFilename = file.getOriginalFilename();// asd.jsp
        String suffix = originalFilename.substring(originalFilename.indexOf(".")); // .jpg
        //使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
        String newFileName = UUID.randomUUID().toString() + suffix; // UUID.jpg

        //知识点四、判断目录是否存在,不存在就要创建
        File dir = new File(basePath);//理解为把当前文件读进来
        if (!dir.exists()){ //如果之前读的文件不存在
            dir.mkdirs();
        }
        try {
            //知识点二,资源转存,注意观看每一步需要的类型是什么
            //file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
            file.transferTo(new File(basePath + newFileName));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return RetObj.success(newFileName);
    }
}

4.2 文件下载

4.2.1 整体逻辑

  • 简介
    在这里插入图片描述
  • 代码实现思路
    upload组件中有img标签,用于专门展示一个图片。过程是先把图片上传到服务端,通过:src=“imageUrl” 发送一个请求,请求我们的服务端,再把图片下载回来
    在这里插入图片描述

4.2.2 前端代码分析

  • 重点在 el-upload 中中有img标签,用于专门展示一个图片
    <img v-if="imageUrl" :src="imageUrl" class="avatar"/>
    过程是先把图片上传到服务端,:on-success="handleAvatarSuccess" 上传完成后就会回调这个方法填写imageUrl的值,通过:src="imageUrl" 发送一个请求,请求我们的服务端,再把图片下载回来,在这个img标签上展示。
<el-upload class="avatar-uploader"
           action="/backend/page/upload/upload.do"
           :show-file-list="false"
           :on-success="handleAvatarSuccess"
           :before-upload="beforeUpload"
           ref="upload">
    <img v-if="imageUrl" :src="imageUrl" class="avatar"/>
    <i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
  • handleAvatarSuccess()方法。
  1. response.data 是前面上传文件到服务器端,RetObj中的data属性,就是文件名。通过文件名,请求文件数据
  2. 可以通过response.data中的文件名拿到后端的数据(后端通过输出流向浏览器写回二进制数据)所以后端就不用返回值了
  3. 输出流通过HttpServletResponse获得,是一个对浏览器的响应;同时还要接收name
  4. 这里是回调函数,给imageUrl赋值,
  5. 在前面的<img v-if="imageUrl" :src="imageUrl" class="avatar"/>就会发送请求。如果返回数据就会通过img标签来展示图片
methods: {
    handleAvatarSuccess (response, file, fileList) {
    	//这里直接一个url就是请求,之前用的都是ajax,这里直接请求,
        this.imageUrl = `/common/download?name=${response.data}`
    },

4.2.3 后端代码分析

  1. 接收参数 name,就是接收要下载的文件名
  2. 接收到名字之后,在服务器中对应的路径用名字去找这个图片。通过输入流,通过输入流读取文件内容 new FileInputStream(new File(path)) 配合读: fileInputStream.read(bytes)
  3. 找到后通过输出流以二进制的形式打给前端。response.getOutputStream(); 配合写:outputStream.write(bytes,0,len);
    @GetMapping("upload/download.do")
    public void downloadController(String name, HttpServletResponse response){
        FileInputStream fis = null;
        ServletOutputStream outputStream = null;
        try {
            //1、输入流,通过文件输入流,把目标图片先读到
            fis = new FileInputStream(new File(basePath + name));
            //2、输出流,把图片以流的形式展示到前端
            outputStream = response.getOutputStream();

            byte[] bytes = new byte[1024];
            int len = 0;
            while((len = fis.read(bytes)) != -1){
                outputStream.write(bytes,0,len);
                outputStream.flush();//输出流记得刷新
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                outputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                fis.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

4.3 新增菜品

4.3.1 整体分析

  • 需求分析

  • 数据模型
    在这里插入图片描述

  • 代码开发 ,先用mybatis_plus弄好,之后交互过程弄明白
    在这里插入图片描述

4.3.2 前端思路分析

4.3.2.1 选择菜品分类对应的下拉框
  • dishList,在vue中定义,双向绑定,后端查询数据后封装到RetObj.success(data属性中),然后在html页面中使用vue组件把dishList中的数据循环出来,展示到页面上。

 <el-form-item
   label="菜品分类:"
   prop="categoryId"
 >
   <el-select
     v-model="ruleForm.categoryId"
     placeholder="请选择菜品分类"
   >
     <!--注意这里的dishList,在vue中定义,双向绑定,后端查询数据后封装到RetObj.success(data属性中)-->
     <el-option v-for="(item,index) in dishList" :key="index" :label="item.name" :value="item.id" />
   </el-select>
 </el-form-item>
  • vue 中的ajax与后端交互,将category表中所有的type=1的数据查出来(把所有菜品查出来),并存为一个list(vue中定义为一个list,然后使用v-for去遍历这个list显示到下拉框中),存在RetObj.sucess(List data)
    在这里插入图片描述
// 获取菜品分类
getDishList () {
  //下面这个方法封装到js文件中,在表中,1是菜品分类,2是套餐分类。
  //把type传给后端来查询(直接用一个对象接住,就会自动为这个对象的某些属性赋值),后端可以用一个对象来接收,因为以后可能不止一个type,还有其他参数
  getCategoryList({ 'type': 1 }).then(res => {
    if (res.code === 1) {
      this.dishList = res.data
    } else {
      this.$message.error(res.msg || '操作失败')
    }
  })
},
  • getCategoryList()
// 获取菜品分类列表
const getCategoryList = (params) => {
  return $axios({
    url: '/category/list',
    method: 'get',
    params
  })
}
4.3.2.2 口味管理

在这里插入图片描述

// 按钮 - 添加口味
addFlavore () {
  this.dishFlavors.push({'name': '', 'value': [], showOption: false}) // JSON.parse(JSON.stringify(this.dishFlavorsData))
},

// 按钮 - 删除口味
delFlavor (ind) {
  this.dishFlavors.splice(ind, 1)
},

// 按钮 - 删除口味标签
delFlavorLabel (index, ind) {
  this.dishFlavors[index].value.splice(ind, 1)
},

//口味位置记录
flavorPosition (index) {
  this.index = index
},

// 添加口味标签
keyDownHandle (val,index) {
  console.log('keyDownHandle----val',val)
  console.log('keyDownHandle----index',index)
  console.log('keyDownHandle----this.dishFlavors',this.dishFlavors)
  if (event) {
    event.cancelBubble = true
    event.preventDefault()
    event.stopPropagation()
  }

  if (val.target.innerText.trim() != '') {
    this.dishFlavors[index].value.push(val.target.innerText)
    val.target.innerText = ''
  }
},
<!--
	 再次复习,placeholder就是默认显示的灰色字,点击就没了
	 @focus="selectFlavor(true,index)"
	 @blur="outSelect(false,index)"失去焦点就触发  这两个大多都是记录日志,可以看对应的函数,在vue下面那里
-->
<el-input
   v-model="item.name"
   type="text"
   style="width: 100%"
   placeholder="请输入口味"
   @focus="selectFlavor(true,index)"
   @blur="outSelect(false,index)"
   @input="inputHandle(index)"
 />
// 获取口味列表
getFlavorListHand () {
  // flavor flavorData
  this.dishFlavorsData = [
    {'name':'甜味','value':['无糖','少糖','半糖','多糖','全糖']},
    {'name':'温度','value':['热饮','常温','去冰','少冰','多冰']},
    {'name':'忌口','value':['不要葱','不要蒜','不要香菜','不要辣']},
    {'name':'辣度','value':['不辣','微辣','中辣','重辣']}
  ]
},
4.3.2.3 图片上传下载
  • 这里直接使用之前的图片上传下载功能。
<el-form-item
   label="菜品图片:"
   prop="region"
   class="uploadImg"
 >
   <el-upload
     class="avatar-uploader"
     action="/backend/page/upload/upload.do"
     :show-file-list="false"
     :on-success="handleAvatarSuccess"
     :on-change="onChange"
     ref="upload"
   >

4.3.3 后端代码

  • 获取分类列表
/**
 * 通过category中的type = 1/0 查出对于的菜品分类或是套餐分类,显示到前端的下拉框中
 *
 * 需求:查出所有菜品分类,并以优先级sort排序、再以updatetime排序
 *
 * 注意,lambdaQueryWrapper.eq(R column, Object val);这里是两个参数,一个是字段,一个是要匹配的值
 *      lambdaQueryWrapper.orderByDesc(R column) ,只要指定那个列就行了!因为不和上面那样,需要比较
 */
@GetMapping("/food/list/getCategory.do")
public RetObj getCategoryList(Category category){
    LambdaQueryWrapper<Category> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(Category::getType,category.getType())
            .orderByAsc(Category::getSort)
            .orderByDesc(Category::getUpdateTime);
    List<Category> categoryList = categoryService.list(lambdaQueryWrapper);
    log.info("查询出菜品:{}",categoryList);
    return RetObj.success(categoryList);
}
  • 对数据进行插入,前端提交的表单如下。涉及到两张表的操作。每个数据都是以json的形式提交的,注意使用注解@RequestBody 其中flavors里面的数据是一个一个的数组,每个数组中存的是json0。不可以直接封装到Dish实体类中,因为Dish实体类不包含favors对应的字段。

在这里插入图片描述

  • 使用DTO构建一个类,这个类保护两个表的字段。

Lombok中的@Data : 注在类上,提供类的get、set、equals、hashCode、canEqual、toString方法

  • 要充分理解DishFlavor这个表,每一条数据就是对应的name比如甜味,然后value对应“无糖…”,因此思路就是后端使用List<DishFlavor> 来将每一条数据封装到一个DishFlavor对象中去,最后构成一个List集合。
package cn.edu.uestc.ruijitakeout.backend.dto;

import cn.edu.uestc.ruijitakeout.backend.domain.Dish;
import cn.edu.uestc.ruijitakeout.backend.domain.DishFlavor;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
public class DishDto extends Dish {
    /*
    flavors中的所有信息都是通过一个数组传给后端,数组中每个元素是json类型
    name、value 就是DishFlavor中最主要的信息,当然,还要绑定上对应的分类id,
    后面使用注解,就可以将数组中json解析到DishFlavor对象中,因此,
    [
      {
        "name": "甜味",
        "value": "[\"无糖\",\"少糖\",\"半糖\",\"多糖\",\"全糖\"]",
        "showOption": false
      },
      {
        "name": "温度",
        "value": "[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]",
        "showOption": false
      }
    ]
 */
     //错误的点:这里属性名要和前端的保持一致,否者无法注入!
    //private List<DishFlavor> flavorsList = new ArrayList<>();
    private List<DishFlavor> flavors = new ArrayList<>();

    private String categoryName;
    private Integer copies;

}

  • 后端接收到前端数据并封装到DishDto类中后,应该编写service,使用DishDto对象同时操作两张表。dish、dish_flavor。其中dish_f表中的字段dish_id,使用DishDto中的dish_id来插入(口味肯定是描述某道菜的口味,相当于主键副键)。另外,前端一次性传过来多条数据,就是多个dish_flavor (表中行数),对应的是一道菜,多个口味选项。
public interface DishService extends IService<Dish> {

    //新增菜品,同时插入菜品对应的口味数据,需要操作两张表:dish、dish_flavor
    public void saveWithFlavor(DishDto dishDto);
	...
  • 加入事务控制,保证两张表的一致性
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish>
    implements DishService{

    @Resource
    private DishFlavorService dishFlavorService;
    @Override
    //这个方法对多张表进行了操作,需要保证数据一致性!,在Application中也要加上对应的注解    @EnableTransactionManagement
    @Transactional
    public void saveWithFlavor(DishDto dishDto) {
        //为dish表添加数据
        this.save(dishDto);

        Long dishId = dishDto.getId();
        List<DishFlavor> flavorsList = dishDto.getFlavorsList();
        flavorsList.forEach(data -> data.setDish_id(dishId));
        //为dishFlavor表添加数据
        dishFlavorService.saveBatch(flavorsList);
    }
}

4.4 菜品信息分页查询

4.4.1 思路整理

在这里插入图片描述

  • 不止涉及一张表。主要是菜品表,要显示菜品名称、售价、售卖状态、最后操作时间、操作等。除此之外,还需要显示图片(之前已经写好了,会自己调用,发送完ajax请求后会自动在发一个请求进行图片下载,注意改下载的请求地址)、菜品分类的名称。
  • 解决菜品分类的名称问题:这里菜品分类名称通过dish表中,只有一个category_id,而没有这个id对应的菜品名字),在后端返回的时候return RetObj.success(pageInfo); 这个pageInfo实际上是Page<Dish>类型里面并没有封装菜品名称这个属性,也就是说这个泛型Dish不满足要求。思路是之前就使用DishDto涵括了:dish(继承了它)、flavor。现在在这个DishDto中再加一个属性categoryName,就满足了。(当然自己写sql多表(dish表和catogory表)联合查询也能直接解决问题,因为mybatis_plus只有单表的 )
  • 交互过程。其中name是模糊查询的条件
    在这里插入图片描述

4.4.2 前端分析

前端代码平平无奇,和之前的很相似,这一部分难的在后端的处理上。

  • 页面展示都是类似的。以图片下载为例,主要就是使用vue的组件
<el-table-column prop="image" label="图片" align="center">
  <template slot-scope="{ row }">
    <el-image style="width: auto; height: 40px; border:none;cursor: pointer;" 
    :src="getImage(row.image)" 
    :preview-src-list="[ `/backend/page/upload/download.do?name=${row.image}` ]" >
    <div slot="error" class="image-slot">
      <img src="./../../images/noImg.png"  style="width: auto; height: 40px; border:none;" >
    </div>  
  </el-image>
  </template>
</el-table-column>
  • 发送请求的vue代码,page和pageSize都有了初始值。返回值pageInfo下的records,就是封装好的每一条数据
methods: {
  async init () {
    //先构造一个对象
    const params = {
      page: this.page,
      pageSize: this.pageSize,
      name: this.input ? this.input : undefined
    }
    await getDishPage(params).then(res => {
      if (String(res.code) === '1') {
        this.tableData = res.data.records || []
        this.counts = res.data.total
      }
    }).catch(err => {
      this.$message.error('请求出错了:' + err)
    })
  },
  getImage (image) {
    return `/backend/page/upload/download.do?name=${image}`
  },
  handleQuery() {
    this.page = 1;
    this.init();
  },
// 查询列表接口
const getDishPage = (params) => {
  return $axios({
    url: 'backend/page/food/list/page.do',
    method: 'get',
    params
  })
}

4.2.3 后端代码分析

  • 解决菜品分类的名称问题:这里菜品分类名称通过dish表中,只有一个category_id,而没有这个id对应的菜品名字),在后端返回的时候return RetObj.success(pageInfo); 这个pageInfo实际上是Page<Dish>类型里面并没有封装菜品名称这个属性,也就是说这个泛型Dish不满足要求。思路是之前就使用DishDto涵括了:dish(继承了它)、flavor。现在在这个DishDto中再加一个属性categoryName,就满足了。
  • 自己写sql、service等:可以自己写sql实现多表联合查询,返回的数据需要严格按照前端要求分装好,比如List<T> records代表的就是每一条数据,名字需要是record,还有total。
  • 如果使用MP,由于MP无法进行多表联合的查询,思路:
  1. 先使用 dishService执行分页查询,注意,既然是dishService,在pageInfo中就只能把结果封装到Dish实体类中,不能够直接封装给DishDto类。dishService.page(pageInfo,queryWrapper);
  2. 创建出 Page<DishDto> dishDtoPage = new Page<>(); ,之后将原来的pageInfo的信息复制给dishDtoPage,但是要排除record,因为record是:List<Dish> records ,我们希望这个record中的List中的元素类型是DishDto。
  3. 遍历List<Dish> records ,在其中new DishDto,把每一条Dish数据复制给DishDto,同时根据categoryId使用category表去查询categoryName,赋值给DishDto,这样我们最需要的字段得到赋值categoryName。
  4. 把上面的一个个对象dishDto,放入到list中,之后:dishDtoPage.setRecords(list);,就可以完成dishDtoPage的封装。
@GetMapping("list/page.do")
public RetObj pageController(int page, int pageSize, String name){
    Page<Dish> pageInfo = new Page<>(page,pageSize);
    Page<DishDto> dishDtoPage = new Page<>(page,pageSize);

    LambdaQueryWrapper<Dish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.like(StringUtils.isNotBlank(name),Dish::getName, name)
            .orderByDesc(Dish::getUpdateTime);
    dishService.page(pageInfo,lambdaQueryWrapper);

    //pageInfo中的record,里面的list存的类型是Dish,我们要DishDto,所以整个record就不拷贝了
    BeanUtils.copyProperties(pageInfo,dishDtoPage,"record");
    List<Dish> records = pageInfo.getRecords();
    List<DishDto> list = records.stream().map(item ->{
        DishDto dishDto = new DishDto();
        BeanUtils.copyProperties(item,dishDto);
        Category category = categoryService.getById(item.getCategoryId());
        //dishDto.setCategoryName(category.getName());
        if (category != null){ //很重要
            dishDto.setCategoryName(category.getName());
        }
        return dishDto;
    }).collect(Collectors.toList());
    dishDtoPage.setRecords(list);
    return RetObj.success(dishDtoPage);
}

4.5 修改菜品信息

4.5.1 整体分析

  • 和之前的修改是类似的,使用的还是add.html,还是需要回显,回显麻烦的是口味也要回显,又要使用DishDto

在这里插入图片描述

4.5.2 前端分析

把url中的id取出来,只有0和1,对于编辑和添加
(add这个界面是复用的,在修改的时候也是用这个页面,而且进行数据回显)
有id说明是根据id查询数据进行回显,不是添加,而是修改。


this.id = requestUrlParam('id')
this.actionType = this.id ? 'edit' : 'add'
if (this.id) {
  this.init() //是修改页面,才执行这个init方法,主要就是去回显用的,看queryDishById方法
}
},
mounted() {
},
methods: {
async init () {
  queryDishById(this.id).then(res => {
    console.log(res)
    if (String(res.code) === '1') {
      this.ruleForm = { ...res.data }
      this.ruleForm.price = String(res.data.price/100)
      this.ruleForm.status = res.data.status == '1'
      this.dishFlavors = res.data.flavors && res.data.flavors.map(obj => ({ ...obj, value: JSON.parse(obj.value),showOption: false }))
      console.log('this.dishFlavors',this.dishFlavors)
      // this.ruleForm.id = res.data.data.categoryId
      // this.imageUrl = res.data.data.image
      this.imageUrl = `/common/download?name=${res.data.image}`
    } else {
      this.$message.error(res.msg || '操作失败')
    }
  })
  • 注意是一杠一值,后端记得要使用路径变量注解,并且使用括号{name}
// 查询详情
const queryDishById = (id) => {
  return $axios({
    url: `/backend/page/food/add/getInfo.do/${id}`,
    method: 'get'
  })
}

4.5.3 后端分析

  • 回显:前端传来id,我们要根据id查询到以下这么多信息
    在这里插入图片描述
  1. 显然,dish表中是没有口味相关信息的,需要把dish和dish_flavor中的信息都查出来,封装到dishDto中,传给前端进行回显
  2. 用户在前端修改好信息后,返回给后端的也是DishDto,也是要分步来操作:先跟新dish表,为了方便清空所有dishId对应的dishFlavor的记录条数,之后再根据DishDto新的dish_flavor进行插入。
  • 回显的一个错误注意! 一个空格的错误会导致404,不要多空格这个地方
    在这里插入图片描述

  • 注意强转问题,可以使用类的复制来代替强制类型转换
    在这里插入图片描述

  • 回显代码

    /**
     * 工具前端传过来的id,查询信息进行回显,当然,还要同时查询口味表,把口味表的数据也进行回显
     * @param id ,注意这里的id就是dish的id,不是categoryId,也不是flavor的id,而flavor中有dish——id
     * @return
     */
    @GetMapping("/add/getInfo.do/{id}")
    public RetObj showBack(@PathVariable Long id){
        //查到的信息直接向下转型----为啥不行,而要进行复制?
        //编译器:拒绝了父类强转为子类
        //DishDto dishDto = (DishDto) dishService.getById(id);
        Dish dish = dishService.getById(id);
        DishDto dishDto = new DishDto();
        BeanUtils.copyProperties(dish,dishDto);
        //现在还需要把口味信息封装到DishDto中:List<DishFlavor>,前端自动展示
        LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper();
        lambdaQueryWrapper.eq(DishFlavor::getDishId,id);
        List<DishFlavor> list = dishFlavorService.list(lambdaQueryWrapper);
        dishDto.setFlavors(list);

        return RetObj.success(dishDto);
    }

  • 修改代码,注意考虑事务,测试的时候就发现,口味删除了,其他没修改成功,最好放在service中,加入事务控制!
@Transactional
@PutMapping("add/edit.do")
public RetObj editController(@RequestBody DishDto dishDto){
    //Dish dish = (Dish)dishDto;
    dishService.updateById(dishDto); //多态,dishDto也是dish!!!

    //对于flavor,思考,还是把原来的全部删掉,再插入新的数据比较好处理。
    LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
    dishFlavorService.remove(lambdaQueryWrapper);

    //dishService.saveWithFlavor(dishDto);不能用这个的原因是这个是新增,新增是插入数据,不是更新
    List<DishFlavor> flavors = dishDto.getFlavors();
    flavors = flavors.stream().map(item -> {
        item.setDishId(dishDto.getId());
        return item;
    }).collect(Collectors.toList());
    dishFlavorService.saveBatch(flavors);

    return RetObj.success("成功修改菜品!");
}

5 套餐管理(见part4)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值