模块简介
本模块主要的开发任务分为以下几个方面
- 文件的上传与下载
- 新增菜品
- 菜品信息分页查询
- 修改菜品
模块内容大致与之前内容相似,只有一个新内容就是文件的上传与下载
那么什么是文件的上传与下载呢?
文件上传与下载相信生活中大家经常使用。在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
调用DishMapper
和DishFlavorMapper
把信息保存到dish和dish_flavor表- 注意:因为在一个Controller中实现了向两个表中添加数据的操作,此时的形参是不能同时接受两个表的属性的。需要设置一个新类,同时包含两个表的属性。
- 因为是对两张表的操作,还需要添加事务
代码实现
本次代码涉及的模块较多,采用按包分类来展示
- 启动类(加入如下注解)
@EnableTransactionManagement
- 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;
}
- 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;
}
- 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,"添加成功");
}
}
- 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> {
}
- 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
这段代码比较复杂,大致的思路为:
- 将菜品的信息查询到Dish泛型的Page1中
- 将Page1的信息除了records以外的值复制到DishDto泛型的Page2中
- 将Page1的records进行复制给DishDto泛型的List,并添加categoryName的值到集合中
- 将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;
}
总结
至此菜单模块的开发就全部完成,本模块的重点如下
- 文件的上传和下载
- 多表的添加、查询和删除
总体的过程比较复杂,需要详细的去看一下代码,才能弄清楚