springboot project: Detailed analysis of the front and back ends of St. Regis takeaway part3

part 1
second part link part 2
part 3 this article
part 4

4 dish management

4.1 File upload (why should the backend return the file name to the frontend, customize the path value in yml and take it out in the class, how to transfer the file to the specified location, use UUID to prevent file overwriting caused by repeated file names) 4.2 File download
4.3
Added Dishes (design multi-table operations, ensure consistency of transactions, use DTO, write your own controller, worth learning) 4.4
Paging query of dish information (multi-table joint operation, further use of Dto)
4.5 Modify dishes ()

4.1 File upload

4.1.1 Overall thought analysis

  • Introduction to knowledge points

insert image description hereinsert image description here

  • Specific implementation introduction
    insert image description here
    The above code will dynamically change the element and generate the following input tag

insert image description here

  • The next thing to do is to write a controller to receive the request from the front end

4.1.2 Front-end analysis

  • upload.html file upload page
<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>

  • After uploading the file, a 404 error occurs because the controller is not written. At the same time, pay attention to the configuration of the interceptor. At that time, it was configured to only intercept the controller (except for login and logout), and the others were released.
// 是我们的conrtoller中的方法就拦截,如果不是的话,放行,给加载静态资源
if (!(handler instanceof HandlerMethod)) {
    
    
    log.info("是静态资源或非controller中的方法,放行");
    return true;
}

insert image description here
In requestHeader:Content-Type: multipart/form-data;

  • Pay attention to observe the form-data after the backend is successfully uploaded
    insert image description here
  • It can be observed in the element that the original <el-upload>tag dynamically generates an input tag (the original el-upload tag has a style on the page, and it is actually an input tag after execution)
    insert image description here

4.1.3 Back-end code analysis

  • The transferTo() method under MultipartFile. Pay attention to the downloaded address and file name setting, do not use the uploader, otherwise it will be overwritten after the same name, the idea is to use UUID
  1. Configure the path into the yml file, take it out here; pay attention! ! ! Not @Value under lombok, but org.springframework.beans.factory.annotation.Value; solve the problem of always reporting errors
  2. Knowledge point 2, resource transfer, pay attention to what type is required for each step; file is a temporary file, which needs to be transferred to a specified location, otherwise the temporary file will be deleted after the request is completed
  3. Knowledge point 3. Obtain the name of the original file, take out the suffix, and use UUID to modify the file name to prevent overwriting
  4. Knowledge point 4. Determine whether the directory exists, and create it if it does not exist
@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 File download

4.2.1 Overall logic

  • Introduction
    insert image description here
  • Code Implementation Ideas
    There is an img tag in the upload component, which is used to specifically display a picture. The process is to upload the picture to the server first, send a request through: src="imageUrl", request our server, and then download the picture back
    insert image description here

4.2.2 Front-end code analysis

  • The key point is that there is an img tag in el-upload, which is used to display a picture.
    <img v-if="imageUrl" :src="imageUrl" class="avatar"/>
    The process is to upload the picture to the server first. :on-success="handleAvatarSuccess"After the upload is completed, it will call back imageUrlthe value filled in this method. By :src="imageUrl"sending a request, request our server, Then download the picture back and display it on this img tag.
<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() method.
  1. response.data is the previously uploaded file to the server, and the data attribute in RetObj is the file name. By file name, request file data
  2. The backend data can be obtained through the file name in response.data (the backend writes binary data back to the browser through the output stream), so the backend does not need to return a value
  3. The output stream is obtained through HttpServletResponse, which is a response to the browser; it also needs to receive the name
  4. Here is the callback function, assign a value to imageUrl,
  5. The previous one <img v-if="imageUrl" :src="imageUrl" class="avatar"/>will send the request. If the data is returned, the image will be displayed through the img tag
methods: {
    
    
    handleAvatarSuccess (response, file, fileList) {
    
    
    	//这里直接一个url就是请求,之前用的都是ajax,这里直接请求,
        this.imageUrl = `/common/download?name=${
    
    response.data}`
    },

4.2.3 Back-end code analysis

  1. The receiving parameter name is to receive the name of the file to be downloaded
  2. After receiving the name, use the name to find the picture in the corresponding path in the server. Through the input stream, read the file content through the input stream new FileInputStream(new File(path))Cooperate with reading:fileInputStream.read(bytes)
  3. After finding it, call it to the front end in binary form through the output stream. response.getOutputStream();Cooperate with writing: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 New dishes

4.3.1 Overall Analysis

  • demand analysis

  • data model
    insert image description here

  • For code development, first use mybatis_plus to get it done, and then understand the interactive process
    insert image description here

4.3.2 Analysis of front-end ideas

4.3.2.1 Select the drop-down box corresponding to the dish category

  • dishList, defined in vue, two-way binding, the backend queries the data and encapsulates it into RetObj.success (in the data attribute), and then uses the vue component in the html page to cycle out the data in the dishList and display it on the page.

 <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>
  • Ajax in vue interacts with the backend, checks out all type=1 data in the category table (checks out all dishes), and saves it as a list (defined as a list in vue, and then uses v-for to traverse this list is displayed in the drop-down box), there is RetObj.sucess(List data)
    insert image description here
// 获取菜品分类
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 Flavor Management

insert image description here

// 按钮 - 添加口味
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 Image upload and download

  • Here directly use the previous image upload and download function.
<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 Backend code

  • Get category list
/**
 * 通过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);
}
  • To insert data, the form submitted by the front end is as follows. Operations involving two tables. Each data is submitted in the form of json, pay attention to using the annotation @RequestBody where the data in flavors is an array one by one, and json0 is stored in each array. It cannot be directly encapsulated into the Dish entity class, because the Dish entity class does not contain the fields corresponding to favors.

insert image description here

  • Use a DTO to build a class that protects the fields of both tables.

@Data in Lombok: Note on the class, provide the get, set, equals, hashCode, canEqual, toString methods of the class

  • To fully understand the DishFlavor table, each piece of data is the corresponding name, such as sweetness, and the value corresponds to "sugar-free...", so the idea is to use the backend to List<DishFlavor>encapsulate each piece of data into a DishFlavor object, and finally form a List gather.
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;

}

  • After the backend receives the frontend data and encapsulates it into the DishDto class, it should write a service and use the DishDto object to operate two tables at the same time. dish, dish_flavor. Among them, the field dish_id in the dish_f table is inserted using the dish_id in DishDto (the taste must describe the taste of a certain dish, which is equivalent to the primary key and secondary key). In addition, the front end transmits multiple pieces of data at one time, that is, multiple dish_flavors (the number of rows in the table), corresponding to a dish with multiple flavor options.
public interface DishService extends IService<Dish> {
    
    

    //新增菜品,同时插入菜品对应的口味数据,需要操作两张表:dish、dish_flavor
    public void saveWithFlavor(DishDto dishDto);
	...
  • Add transaction control to ensure the consistency of the two tables
@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 Paging query of dish information

4.4.1 Organize ideas

insert image description here

  • More than one table is involved. It is mainly a dish table, which should display the dish name, price, sales status, last operation time, operation, etc. In addition, it is also necessary to display pictures (which have been written before and will be called by itself. After sending the ajax request, it will automatically send a request to download the picture, pay attention to changing the download request address), and the name of the dish category.
  • Solve the problem of the name of the dish category: here the name of the dish category is passed in the dish table, there is only one category_id, and there is no name of the dish corresponding to this id), when the backend returns, this pageInfo is actually a type that does not encapsulate the property of the return RetObj.success(pageInfo);dish Page<Dish>name , which means that this generic Dish does not meet the requirements. The idea is to use DishDto to cover: dish (inherited from it), flavor. Now adding an attribute categoryName to this DishDto is enough. (Of course, writing sql multi-table (dish table and catogory table) joint query can also directly solve the problem, because mybatis_plus only has a single table)
  • interactive process. Where name is the condition of fuzzy query
    insert image description here

4.4.2 Front-end analysis

The front-end code is unremarkable, very similar to the previous ones. The difficulty of this part lies in the processing of the back-end.

  • The page display is similar. Take image download as an example, mainly using vue components
<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>
  • The Vue code that sends the request, both page and pageSize have initial values. The records under the return value pageInfo are each encapsulated data
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 Back-end code analysis

  • Solve the problem of the name of the dish category: here the name of the dish category is passed in the dish table, there is only one category_id, and there is no name of the dish corresponding to this id), when the backend returns, this pageInfo is actually a type that does not encapsulate the property of the return RetObj.success(pageInfo);dish Page<Dish>name , which means that this generic Dish does not meet the requirements. The idea is to use DishDto to cover: dish (inherited from it), flavor. Now adding an attribute categoryName to this DishDto is enough.
  • Write sql, service, etc. by yourself: You can write sql by yourself to realize multi-table joint query. The returned data must be packaged in strict accordance with the requirements of the front end. For example, it represents each piece of data, and the name List<T> recordsneeds to be record and total.
  • If MP is used, since MP cannot perform multi-table union queries, ideas:
  1. First use dishService to perform paging query. Note that since it is dishService, the results can only be encapsulated in the Dish entity class in pageInfo, and cannot be directly encapsulated in the DishDto class.dishService.page(pageInfo,queryWrapper);
  2. After creating it Page<DishDto> dishDtoPage = new Page<>(); , copy the original pageInfo information to dishDtoPage, but exclude the record, because the record is: List<Dish> records, we hope that the element type in the List in this record is DishDto.
  3. Traverse List<Dish> records, in which new DishDto, copy each piece of Dish data to DishDto, and use the category table according to categoryId toquery categoryName, assigned to DishDto, so that the field we need most is assigned categoryName.
  4. Put each of the above objects dishDto into the list, and then: dishDtoPage.setRecords(list);, the encapsulation of dishDtoPage can be completed.
@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 Modify dish information

4.5.1 Overall Analysis

  • It is similar to the previous modification. Add.html is still used, and it still needs to be echoed. The trouble with echoing is that the taste must also be echoed, and DishDto must be used.

insert image description here

4.5.2 Front-end analysis

Take out the id in the url, only 0 and 1, for editing and adding
(the interface of add is multiplexed, and this page is also used when modifying, and the data is echoed) the
id description is based on the id query data Echo, not add, but modify.


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 || '操作失败')
    }
  })
  • Note that one bar and one value, remember to use path variable annotations in the back end, and use brackets {name}
// 查询详情
const queryDishById = (id) => {
    
    
  return $axios({
    
    
    url: `/backend/page/food/add/getInfo.do/${
    
    id}`,
    method: 'get'
  })
}

4.5.3 Backend analysis

  • Echo: The id is sent from the front end, and we need to query the following information based on the id
    insert image description here
  1. Obviously, there is no flavor-related information in the dish table. It is necessary to find out the information in dish and dish_flavor, encapsulate it in dishDto, and send it to the front end for echo.
  2. After the user modifies the information on the front-end, DishDto is returned to the back-end, which also needs to be operated step by step: first follow the new dish table, for the convenience of clearing the number of records of all dishFlavor corresponding to dishId, and then insert according to the new dish_flavor of DishDto .
  • An error in the echo Note! A space error will cause 404, don't add more spaces here
    insert image description here

  • Pay attention to the strong transfer problem, you can use the copy of the class instead of the forced type conversion
    insert image description here

  • echo code

    /**
     * 工具前端传过来的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);
    }

  • Modify the code and pay attention to the transaction. During the test, it was found that the flavor was deleted, and other modifications were not successful. It is best to put it in the service and add transaction control!
@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 package management (see part4)

Guess you like

Origin blog.csdn.net/YiGeiGiaoGiao/article/details/129855051