• 【外卖项目实战开发四】



    菜品管理业务开发

    文件上传下载

    文件上传介绍

    文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。

    文件上传时,对页面的form表单有如下要求:

    • method=“post”            采用post方式提交数据
    • enctype=“multipart/form-data”     采用multipart格式上传文件
    • type=“file”              使用input的file控件上传
      image

    目前一些前端组件库也提供了相应的上传组件,但是底层原理还是基于form表单的文件上传。例如ElementUI中提供的upload上传组件:
    image
    在这里插入图片描述

    服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:

    • commons-fileupload
    • commons-io

    Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件。

    文件下载介绍

    文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程。

    通过浏览器进行文件下载,通常有两种表现形式:

    • 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
    • 直接在浏览器中打开

    通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。

    文件上传代码实现

    文件上传,页面端可以使用ElementuI提供的上传组件。

    
    DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>文件上传title>
      
      <link rel="stylesheet" href="../../plugins/element-ui/index.css" />
      <link rel="stylesheet" href="../../styles/common.css" />
      <link rel="stylesheet" href="../../styles/page.css" />
        <link rel="shortcut icon" href="../../favicon.ico">
    head>
    <body>
       <div class="addBrand-container" id="food-add-app">
        <div class="container">
            <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 src="../../plugins/vue/vue.js">script>
        
        <script src="../../plugins/element-ui/index.js">script>
        
        <script src="../../plugins/axios/axios.min.js">script>
        <script src="../../js/index.js">script>
        <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>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    添加CommonController,负责文件上传与下载

    @Slf4j
    @RestController
    @RequestMapping("/common")
    public class CommonController {
    
        //文件上传
        @PostMapping("/upload")
        public R<String> upload(MultipartFile file){
            //file 是一个临时文件,需要转存到指定位置,否则请求完成后临时文件会删除
            log.info("file:{}",file.toString());
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    MultipartFile定义的file变量必须与name保持一致
    image

    完整代码

    @Slf4j
    @RestController
    @RequestMapping("/common")
    public class CommonController {
        @Value("${reggie.path}")
        private String basePath;
    
        //文件上传
        @PostMapping("/upload")
        public R<String> upload(MultipartFile file){
            //file 是一个临时文件,需要转存到指定位置,否则请求完成后临时文件会删除
            //log.info("file:{}",file.toString());
    
            //原始文件名
            String originalFilename = file.getOriginalFilename();
            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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    文件下载代码实现

    文件下载,页面端可以使用标签展示下载的图片
    在这里插入图片描述

    //文件下载
    @GetMapping("/download")
    public void download(String name, HttpServletResponse response){
        try {
            //输入流,通过输入流读取文件内容
            FileInputStream fileInputStream=new FileInputStream(new File(basePath+name));
            //输出流,通过输出流将文件写回浏览器,在浏览器中展示图片
            ServletOutputStream outputStream = response.getOutputStream();
    
            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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    新增菜品

    需求分析

    后台系统中可以管理菜品信息,通过新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片,在移动端会按照菜品分类来展示对应的菜品信息。
    image

    数据模型

    新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。所以在新增菜品时,涉及到两个表:

    • dish(菜品表)
      image

    • dish_flavor(菜品口味表)
      image

    代码开发-准备工作

    在开发业务功能前,先将需要用到的类和接口基本结构创建好:

    • 实体类DishFlavor
    @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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    Mapper接口DishFlavorMapper

    @Mapper
    public interface DishFlavorMapper extends BaseMapper<DishFlavor> {
    }
    
    • 1
    • 2
    • 3

    业务层接口DishFlavorService

    public interface DishFlavorService extends IService<DishFlavor> {
    }
    
    • 1
    • 2

    业务层实现类 DishFlavorServicelmpl

    @Service
    public class DishFlavorServiceImpl extends ServiceImpl<DishFlavorMapper, DishFlavor>implements DishFlavorService {
    }
    
    • 1
    • 2
    • 3

    控制层 DishController

    @RestController
    @RequestMapping("/dish")
    public class DishController {
        @Autowired
        private DishService dishService;
        @Autowired
        private DishFlavorService dishFlavorService;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    代码开发-梳理交互过程

    在开发代码之前,需要梳理一下新增菜品时前端页面和服务端的交互过程:

    • 1、页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中

    • 2、页面发送请求进行图片上传,请求服务端将图片保存到服务器

    • 3、页面发送请求进行图片下载,将上传的图片进行回显

    • 4、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端

    开发新增菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。

    菜品分类下拉框:在CategoryController添加

    //根据条件查询分类数据
    @GetMapping("/list")
    public R<List<Category>> list(Category category){
        //条件构造器
        LambdaQueryWrapper<Category> lambdaQueryWrapper=new LambdaQueryWrapper<>();
        //添加条件
        lambdaQueryWrapper.eq(category.getType()!=null,Category::getType,category.getType());
        //添加排序条件
        lambdaQueryWrapper.orderByAsc(Category::getSort).orderByAsc(Category::getUpdateTime);
        List<Category> list = categoryService.list(lambdaQueryWrapper);
        return R.success(list);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    导入DishDto(位置:资料/dto),用于封装页面提交的数据

    @Data
    public class DishDto extends Dish {
    
        private List<DishFlavor> flavors = new ArrayList<>();
    
        private String categoryName;
    
        private Integer copies;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意:DTO,全称为Data Transfer object,即数据传输对象,一般用于展示层与服务层之间的数据传输。

    新增菜品同时插入菜品对应的口味数据,需要操作两张表:dish、dishflavor

    在DishService接口中添加方法saveWithFlavor,在DishServiceImpl实现

    @Service
    public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
        @Autowired
        private DishFlavorService dishFlavorService;
    
        @Override
        @Transactional
        public void saveWithFlavor(DishDto dishDto) {
            //保存菜品基本信息到菜品表dish
            this.save(dishDto);
    
            Long dishid = dishDto.getId();
            //菜品口味
            List<DishFlavor> flavors = dishDto.getFlavors();
            flavors = flavors.stream().map((item) -> {
                item.setDishId(dishid);
                return item;
            }).collect(Collectors.toList());
            //dishFlavorService.saveBatch(dishDto.getFlavors());
            //保存菜品口味到菜品数据表dish_flavor
            dishFlavorService.saveBatch(flavors);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    由于以上代码涉及多表操作,在启动类上开启事务支持添加@EnableTransactionManagement注解,但是本人添加该注解会报错,项目启动会失败,并且springboot该注解应该是默认开启的,故没有添加

    新增菜品

    @PostMapping
    public R<String> save(@RequestBody DishDto dishDto){
        dishService.saveWithFlavor(dishDto);
        return R.success("新增菜品成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    菜品信息分页查询

    需求分析

    系统中的菜品数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
    image

    代码开发-梳理交互过程

    在开发代码之前,需要梳理一下菜品分页查询时前端页面和服务端的交互过程:

    • 1、页面(backend/page/food/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据

    • 2、页面发送请求,请求服务端进行图片下载,用于页面图片展示

    开发菜品信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

    @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(!StringUtils.isEmpty(name), 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查分类对象
        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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    修改菜品

    需求分析

    在菜品管理列表页面点击修改按钮,跳转到修改菜品页面,在修改页面回显菜品相关信息并进行修改,最后点击确定按钮完成修改操作
    image

    代码开发-梳理交互过程

    在开发代码之前,需要梳理一下修改菜品时前端页面( add.html)和服务端的交互过程:

    • 1、页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示

    • 2、页面发送ajax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显

    DishController处理Get请求

    //根据Id查询菜品信息与对应的口味信息
    @GetMapping("/{id}")
    public R<DishDto> getById(@PathVariable Long id){
        DishDto dishDto = dishService.getByIdWithFlavor(id);
        return R.success(dishDto);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在DishServiceImpl添加getByIdWithFlavor方法

    @Override
    @Transactional
    public DishDto getByIdWithFlavor(Long id) {
        //查询菜品基本信息
        Dish dish = this.getById(id);
    
        DishDto dishDto=new DishDto();
        BeanUtils.copyProperties(dish,dishDto);
    
        //查询菜品口味信息
        LambdaQueryWrapper<DishFlavor> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(DishFlavor::getDishId,dish.getId());
        List<DishFlavor> list = dishFlavorService.list(queryWrapper);
    
        dishDto.setFlavors(list);
    
        return dishDto;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    image

    3、页面发送请求,请求服务端进行图片下载,用于页图片回显

    4、点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端

    • 在DishController添加put方法
    //修改菜品
    @PutMapping
    public R<String> update(@RequestBody DishDto dishDto){
        dishService.updateWithFlavor(dishDto);
        return R.success("修改菜品成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 在DishServiceImpl添加updateWithFlavor方法
    @Override
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    开发修改菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。

    停售/起售菜品,删除菜品

    需求分析

    在商品买卖过程中,商品停售,起售可以更加方便的让用户知道店家还有什么类型的商品在卖。删除方法也更方便的管理菜品

    代码实现

    在DishController添加sale方法与delete方法,通过数组保存ids,批量起售停售、删除都能生效

    //停售起售菜品
    @PostMapping("/status/{status}")
    public R<String> sale(@PathVariable int status,
                          String[] ids){
        for(String id: ids){
            Dish dish = dishService.getById(id);
            dish.setStatus(status);
            dishService.updateById(dish);
        }
        return R.success("修改成功");
    }
    //删除菜品
    @DeleteMapping
    public R<String> delete(String[] ids){
        for (String id:ids) {
            dishService.removeById(id);
        }
        return R.success("删除成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    Unity容器构造函数参数循环引用问题及解决
    智能洗地机哪个牌子好用?智能洗地机品牌排行榜
    风很大的PMP证书真有这么厉害?这是被腾讯/华为招聘时所提到过的证书
    Linux进程基础(一)
    [附源码]java毕业设计高校教师教学工作量核算系统
    【VScode】VScode+如何从git上面拉取代码?
    获取虎牙直播源
    睿趣科技:现在开抖音小店到底要多少钱
    Unity丨自动巡航丨自动寻路丨NPC丨
    Spring 如何使用JdbcTemplate类进行数据库操作呢?
  • 原文地址:https://blog.csdn.net/m0_52896752/article/details/127878189