瑞吉外卖-菜单模块开发

模块简介

本模块主要的开发任务分为以下几个方面

  • 文件的上传与下载
  • 新增菜品
  • 菜品信息分页查询
  • 修改菜品

模块内容大致与之前内容相似,只有一个新内容就是文件的上传与下载
那么什么是文件的上传与下载呢?

文件上传与下载相信生活中大家经常使用。在QQ、微信中,我们可以上传图片、音频等信息,而接收者可以对这些文件进行浏览和下载。这里我们就是要实现这个功能。

文件的上传与下载

文件上传

文件上传,也称为upload,是指将本地图片视频上传到服务器上,可以提供给其他用户浏览和下载的过程。

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

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

例如

<form method="post" action="/common/upload" enctype="mutipart/form-data">
    <input name="myFile" type="file"/>
    <input type="submit" value="提交"/>
</form>

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

  • commons-fileupload
  • commons-io

spring框架在spring-web包中对文件上传进行了封装,只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件

@PostMapping("/upload")
public Result upload(MultipartFile file){
    System.out.println(file);
    return null;
}

文件下载

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

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

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

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

代码实现

1.在yml文件中设置上传文件的储存地址,实际开发应该储存到服务器
2. 在controller包下创建CommonController
3. 在类中编写上传和下载的实现代码

yml文件(添加)

#文件路径可以自己选择
reggie:
  path: D:\img\

CommonController

package com.cjgn.contorller;

import com.cjgn.common.Code;
import com.cjgn.common.Result;
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;

/**
 * 进行文件的上传和下载
 */
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {

    //自定义的配置文件中的路径
    @Value("${reggie.path}")
    private String basePath;

    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping("/upload")
    //file要和前端传回文件的name属性相同,例如前端传回文件name = "file"
    //file是一个临时文件,需要转存,否则本次请求完成后文件就会删除
    public Result upload(MultipartFile file){
        //获得原始文件名
        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 new Result(fileName, Code.OK,"上传成功");
    }

    /**
     * 下载文件
     * @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的返回格式为图片
            response.setContentType("image/jpeg");
            //使用bytes数组存数据
            int lens = 0;
            byte[] bytes = new byte[1024];
            while ((lens = fileInputStream.read(bytes))!=-1){
                outputStream.write(bytes,0,lens);
                //刷新
                outputStream.flush();
            }
            //关闭流
            fileInputStream.close();
            outputStream.close();

        }
        catch (Exception e) {
            e.printStackTrace();
        }

    }
}

新增菜品

需求分析

  • 通过新增功能来添加一个菜品,添加菜品时要选择菜品分类
  • 需要上传菜品的图片,移动端会根据分类展示菜品信息

代码分析

  • 点击新增菜品后,向CategoryController发送请求,查询菜品分类
  • 选择完分类,填完信息,上传图片后,点击新增菜品
  • 新增菜品后,发送ajax请求,调用DishController并传输数据,图片的数据传输为图片的名字
  • DishController调用DishService,处理数据信息,返回一个布尔值
  • DishService调用DishMapperDishFlavorMapper把信息保存到dish和dish_flavor表
  • 注意:因为在一个Controller中实现了向两个表中添加数据的操作,此时的形参是不能同时接受两个表的属性的。需要设置一个新类,同时包含两个表的属性。
  • 因为是对两张表的操作,还需要添加事务

代码实现

本次代码涉及的模块较多,采用按包分类来展示

  1. 启动类(加入如下注解)
@EnableTransactionManagement
  1. entity包

DishFlavor.java

package com.cjgn.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 菜品口味
 */
@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. dto包 (一个新的包,用来放新的实体类,包含两个实体类属性)

DishDto.java

package com.cjgn.dto;

import com.cjgn.entity.Dish;
import com.cjgn.entity.DishFlavor;
import lombok.Data;

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

@Data
public class DishDto extends Dish {

    //DishFlavor的集合
    private List<DishFlavor> flavors = new ArrayList<>();

    private String categoryName;
    private Integer copies;




}

  1. controller包

CategoryController.java (新增)

 /**
     * 获取分类的列表,用于在添加页面展示
     * @param category
     * @return
     */
    @GetMapping("/list")
    public Result getList(Category category){
        List<Category> categories = categoryService.getList(category);
        Integer code = categories!=null? Code.OK:Code.ERR;
        String msg = categories!=null?"查询成功":"查询失败";
        return new Result(categories,code,msg);
    }

DishContorller.java

package com.cjgn.contorller;


import com.cjgn.common.Code;
import com.cjgn.common.Result;
import com.cjgn.dto.DishDto;
import com.cjgn.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;

/**
 * 用来处理对dish和dish_flavor表的操作
 */
@RestController
@RequestMapping("/dish")
@Slf4j
public class DishContorller {
    @Autowired
    private DishService dishService;
    /**
     * 新增菜品
     * @param dishDto
     * @return
     */
    @PostMapping
    public Result insert(@RequestBody DishDto dishDto){
        log.info(dishDto.toString());
        dishService.insertWithFlavor(dishDto);
        return new Result(Code.OK,"添加成功");
    }
}
  1. mapper包

DishFlavorMapper.java

package com.cjgn.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cjgn.entity.DishFlavor;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface DishFlavorMapper extends BaseMapper<DishFlavor> {
}
  1. service包

CategoryService.java (新增)

 /**
     * 获取分类的列表,用于在添加页面展示
     * @param category
     * @return
     */
    public List<Category> getList(Category category);

CategoryServiceImpl.java(新增)

 /**
     * 获取分类的列表,用于在添加页面展示
     * @param category
     * @return
     */
    @Override
    public List<Category> getList(Category category) {
        LambdaQueryWrapper<Category> wrapper = new LambdaQueryWrapper<Category>();
        //获取type值
        Integer type = category.getType();
        //设置相等条件
        wrapper.eq(type!=null,Category::getType,type);
        //设置排序条件
        wrapper.orderByAsc(Category::getSort).orderByDesc(Category::getCreateTime);
        //查询数据
        List<Category> categories = categoryMapper.selectList(wrapper);
        return categories;
    }

DishService.java

package com.cjgn.service;

import com.cjgn.dto.DishDto;
import org.springframework.transaction.annotation.Transactional;

public interface DishService {

    /**
     * 新增菜品,同时插入菜品对应的口味数据,需要操作两张表dish和dish_flavor
     * 开启事务
     */
    @Transactional
    public void insertWithFlavor(DishDto dishDto);
}

DishServiceImpl.java

package com.cjgn.service.impl;

import com.cjgn.dto.DishDto;
import com.cjgn.entity.DishFlavor;
import com.cjgn.mapper.DishFlavorMapper;
import com.cjgn.mapper.DishMapper;
import com.cjgn.service.DishService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

/**
 * 菜品的业务层
 */
@Service
public class DishServiceImpl implements DishService {
    @Autowired
    private CategoryMapper categoryMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private DishFlavorMapper dishFlavorMapper;
    /**
     * 新增菜品,同时插入菜品对应的口味数据,需要操作两张表dish和dish_flavor
     * @param dishDto
     */
    @Override
    public void insertWithFlavor(DishDto dishDto) {
        //保存菜品的基本信息到菜品表
        dishMapper.insert(dishDto);
        //获取雪花算法自动生成的dish表的id
        Long id = dishDto.getId();
        //保存菜品口味信息到dish_flavor
        List<DishFlavor> flavors = dishDto.getFlavors();
        //向集合中注入id的值
        flavors.stream().map((item) -> {
        	//注入id
            item.setDishId(id);
            //保存到dish_flavor
            dishFlavorMapper.insert(item);
            return item;
        }).collect(Collectors.toList());

    }
}

DishFlavorService.java

package com.cjgn.service;
public interface DishFlavorService {
}

DishFlavorServiceImpl.java

package com.cjgn.service.impl;


import com.cjgn.service.DishFlavorService;
import org.springframework.stereotype.Service;

@Service
public class DishFlavorServiceImpl implements DishFlavorService {
}

菜品信息分页查询

在查询之前,先把资料中的图片信息,复制到你设置的yml文件中保存图片的位置

需求分析

  • 分页查询已经实现很多次,本次需求主要提出一些特别的需求
  • 本次的分页查询,需要通过文件下载来展示图片
  • 要通过菜品表中的分类id,去查询此分类id对应的分类名称,并显示到前端

代码分析

  • 前端传输查询页面,页面大小等数据给服务端
  • 页面发送请求,请求服务端进行图片的下载
  • 服务端接收请求后,调用service向dish和dish_flavor查询数据
  • 按照菜品分类的id找出查询菜品分类的名称
  • 把查询的数据和图片展示到客户端

代码实现

DishController.java(添加)

 /**
     *菜品信息分页查询
     * @param page 第几页
     * @param pageSize 页面大小
     * @param name 菜品的名称
     * @return
     */
    @GetMapping("/page")
    public Result getByPage(int page,int pageSize,String name){
        //调用service进行查询
        Page byPage = dishService.getByPage(page, pageSize, name);
        return new Result(byPage,Code.OK,"查询成功");
    }

DishService.java

 /**
     * 分页查询菜品信息
     * @param page 第几页
     * @param pageSize 页面大小
     * @param name 菜品名称
     */
    public Page getByPage(int page, int pageSize, String name);

DishServiceImpl.java

这段代码比较复杂,大致的思路为:

  1. 将菜品的信息查询到Dish泛型的Page1中
  2. 将Page1的信息除了records以外的值复制到DishDto泛型的Page2中
  3. 将Page1的records进行复制给DishDto泛型的List,并添加categoryName的值到集合中
  4. 将List注入到Page2中,将Page2返回给DishController
 /**
     * 分页查询菜品信息
     * @param page 第几页
     * @param pageSize 页面大小
     * @param name 菜品名称
     */
    @Override
    public Page getByPage(int page, int pageSize, String name) {
        //用来查询
        Page<Dish> iPage = new Page(page,pageSize);
        //用来输出
        Page<DishDto> dtoPage = new Page<>(page,pageSize);
        //设置菜品的分页查询的条件
        LambdaQueryWrapper<Dish> wrapper = new LambdaQueryWrapper<Dish>();
        //设置模糊查询
        wrapper.like(name!=null,Dish::getName,name);
        //按照更新时间降序排序
        wrapper.orderByDesc(Dish::getUpdateTime);
        //查询
        dishMapper.selectPage(iPage,wrapper);

        //对象拷贝,忽视records
        BeanUtils.copyProperties(iPage,dtoPage,"records");
        List<Dish> records = iPage.getRecords();
         //注入dishdto的list集合的值
        List<DishDto> dishDtos = records.stream().map((item)->{
            //新建dto对象
            DishDto dishDto = new DishDto();
            //拷贝对象
            BeanUtils.copyProperties(item,dishDto);
            //获取菜品的id
            Long categoryId = item.getCategoryId();
            //获取菜品的name
            Category category = categoryMapper.selectById(categoryId);
            //判断是否有这个分类
            if(category!=null){
                //注入菜品name
                dishDto.setCategoryName(category.getName());
            }
            return dishDto;
        }).collect(Collectors.toList());
        //注入records
        dtoPage.setRecords(dishDtos);
        return dtoPage;
    }

修改菜品

需求分析

  • 点击修改按钮,可以修改菜品的信息,包括名字,口味,图片,价格,分类,描述等。

代码分析

  • 页面发送ajax请求,在服务端查询分类信息数据,在前端显示
  • 页面发送ajax请求,根据id查询当前菜品和口味信息,用于信息的回显
  • 页面请求服务端进行图片的下载
  • 点击保存后,将json数据传到服务端进行保存

代码开发

DishController.java(新增)

  /**
     * 修改菜品和口味的信息
     * @param dishDto 传回来的菜品和口味数据
     * @return
     */
    @PutMapping
    public Result updateDishDto(@RequestBody DishDto dishDto){
        dishService.updateDishDto(dishDto);
        return new Result(Code.OK,"更新成功");
    }

DishService.java

 /**
     * 更新菜品和口味的信息
     * @param dishDto
     * @return
     */
    @Transactional
    public boolean updateDishDto(DishDto dishDto);

DishServiceImpl.java
菜品信息可以直接修改,口味因为有的删除有的修改,所以采取先全部删除,再重新添加的方式

  /**
     * 更新菜品和口味的信息
     * @param dishDto
     * @return
     */
    @Override
    public boolean updateDishDto(DishDto dishDto) {
        //更新菜品信息
        dishMapper.updateById(dishDto);
        //清理当前的口味数据
        LambdaQueryWrapper<DishFlavor> wrapper1 = new LambdaQueryWrapper<>();
        //获取菜品id
        Long id = dishDto.getId();
        //设置条件
        wrapper1.eq(DishFlavor::getDishId,id);
        //全部删除
        dishFlavorMapper.delete(wrapper1);
        //再次新增数据
        List<DishFlavor> flavors = dishDto.getFlavors();
        //给数据注入id
        flavors.stream().map((item)->{
            item.setDishId(id);
            //把口味添加进数据库
            dishFlavorMapper.insert(item);
            return item;
        }).collect(Collectors.toList());
        return true;
    }

总结

至此菜单模块的开发就全部完成,本模块的重点如下

  • 文件的上传和下载
  • 多表的添加、查询和删除

总体的过程比较复杂,需要详细的去看一下代码,才能弄清楚

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值