菜品管理业务开发
一、文件上传下载
文件上传介绍
文件上传(upload),是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用广泛。文件上传时,对页面的form表单有如下要求:
method="post" 采用post方式提交数据
enctype="multipart/form-data" 采用multipart格式上传文件
type="file" 使用input的file控件上传
目前,一些前端组件也提供了相应的上传组件,但是底层原理还是基于form表单的文件上传。
例如:ElementUI提供的upload上传组件:
服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:
commons-fileupload
commons-io
Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,只需要在controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件,例如:
文件下载介绍
文件下载,是指将文件从服务器传输到本地计算机的过程。
通过浏览器进行文件下载,通常有两种表现形式:
以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录;
直接在浏览器中打开。
通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。
文件上传代码实现
文件上传,页面端可以使用ElementUI提供的上传组件。
前端页面upload.html
文件上传下载controller类CommonController
package com.huangzx.reggie.controller;
import com.huangzx.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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 java.io.File;
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")
public R<String> upload(MultipartFile file){ //与Form Data中的name必须一致
//file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
log.info(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);
}
}
文件下载代码实现
页面端使用<img>标签展示下载的图片
上传图片时返回文件名,下载时获取文件名
/**
* 文件下载
* @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.setContentType("image/jpeg"); //设置文件的类型为图片
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();
}
}
二、新增菜品
需求分析
后台系统中可以管理菜品信息,通过新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片,在移动端会按照菜品分类来展示对应的菜品信息。
数据模型
新增菜品时,其实就是将新增页面录入的菜品信息插入到Dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。所以在新增菜品时,涉及到两个表:菜品表dish,菜品口味表dish-flavor
所需结构:实体类DishFlavor,及Mapper接口、Service接口、Service接口实现类、控制层DishController。
代码开发
程序执行过程:
1.页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中;
2.页面发送请求进行图片上传,请求服务端将图片保存到服务器;
3.页面发送请求进行图片下载,将上传的图片进行回显;
4.点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端。
开发新增菜品功能,其实就是在服务端编写代码去处理前端页面发送的4次请求即可。
点击新增菜品按钮,发送请求http://localhost/category/list,获取菜品分类数据,默认type=1(菜品分类为1,套餐分类为2)
前端页面
在CategoryController类中编写菜品分类列表
/**
* 根据条件查询分类数据
* @param category
* @return
*/
@GetMapping("/list")
public R<List<Category>> list(Category category){
//条件构造器
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//添加条件 根据type来查询
queryWrapper.eq(category.getType() != null, Category::getType, category.getType());
//添加排序条件
queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);
List<Category> list = categoryService.list(queryWrapper);
return R.success(list);
}
文件上传与下载的请求
当填写完数据,点击保存后,发送的请求
服务端接收页面返回的数据时,因为有dish和dishflavor两张表中的属性内容,无法采用单个实体类封装,所以创建数据传输对象(DishDto)来封装页面提交的数据。
DTO,全称Data Transfer Object,即数据传输对象,一般用于展示层与服务层之间的数据传输。
创建dto包,加入DIshDto类
package com.huangzx.reggie.dto;
import com.huangzx.reggie.pojo.Dish;
import com.huangzx.reggie.pojo.DishFlavor;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class DishDto extends Dish {
private List<DishFlavor> flavors = new ArrayList<>(); //有多种口味类别选择,用list集合封装
private String categoryName;
private Integer copies;
}
DishController类中新增菜品方法(未写完)
DishService接口重写保存dish和dish-flavor的方法saveWithFlavor
接口实现 加上事务控制@Transactional,启动方法上加上@EnableTransactionManagement
import com.huangzx.reggie.pojo.DishFlavor;
import com.huangzx.reggie.service.DishFlavorService;
import com.huangzx.reggie.service.DishService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
@Autowired
private DishFlavorService dishFlavorService;
/**
* 新增菜品,同时保存菜品的口味数据
* @param dishDto
*/
@Transactional //加上事务控制,保证数据的一致性
public void saveWithFlavor(DishDto dishDto) {
//保存菜品的基本信息到菜品表Dish
this.save(dishDto);
//获取菜品的id,方便将菜品口味绑定到对应的菜品上
Long dishId = dishDto.getId();
//菜品口味
List<DishFlavor> flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item) ->{ //使用stream流给菜品口味附上菜品id
item.setDishId(dishId);
return item;
}).collect(Collectors.toList()); //返回一个新的集合
//保存菜品口味数据到口味表dish_flavor
dishFlavorService.saveBatch(flavors); //集合批量保存 saveBatch
}
}
DishController新增菜品方法
package com.huangzx.reggie.controller;
import com.huangzx.reggie.common.R;
import com.huangzx.reggie.dto.DishDto;
import com.huangzx.reggie.service.DishFlavorService;
import com.huangzx.reggie.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;
@RestController
@RequestMapping("/dish")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private DishFlavorService dishFlavorService;
/**
* 新增菜品
* @param dishDto
* @return
*/
@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
log.info(dishDto.toString());
dishService.saveWithFlavor(dishDto);
return R.success("新增菜品成功");
}
}
三、菜品信息分页查询
需求分析
系统中的菜品数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般系统中都会以分页的方式来展示列表数据。
代码开发
前端页面和服务端的交互过程:
1.页面(backend/page/food/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据;
2.页面发送请求,请求服务端进行图片下载,用于页面图片展示。
开发菜品信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的2次请求。
采用上面代码,不显示菜品分类名称,所以需要完善代码
采用Page泛型封装信息pageInfo,没有菜品名称categoryName属性,records装的菜品信息列表。所以采用DishDto泛型来封装数据dishDtoPage,将pageInfo中records列表信息拷贝到dishDtoPage中,并增加categoryName属性。
DishDto中的属性
/**
* 菜品分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
//条件构造器
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
//分页构造器对象
Page<Dish> pageInfo = new Page<>(page,pageSize);
Page<DishDto> dishDtoPage = new Page<>();
//添加过滤条件
queryWrapper.like(name != null,Dish::getName,name);
//添加排序条件
queryWrapper.orderByDesc(Dish::getUpdateTime);
//执行分页查询
dishService.page(pageInfo,queryWrapper);
//对象拷贝,将pageInfo中的内容拷贝到dishDtoPage中,并增加categoryName属性
BeanUtils.copyProperties(pageInfo,dishDtoPage,"records"); //暂不处理pageInfo中的records内容,后续单独处理
List<Dish> records = pageInfo.getRecords();
List<DishDto> list = records.stream().map((item) ->{
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item,dishDto); //将records 列表中的每列数据拷贝到dishDto中
Long categoryId = item.getCategoryId(); //获取每列数据的菜品id,后续通过菜品id查询菜品名称
Category category = categoryService.getById(categoryId); //通过菜品id获得菜品分类对象
if(category != null){ //判断菜品分类是否为空
String categoryName = category.getName(); //通过菜品分类对象查询菜品名称属性
dishDto.setCategoryName(categoryName);
}
return dishDto;
}).collect(Collectors.toList());
dishDtoPage.setRecords(list); //将DishDto列表数据装入dishDtoPage中
return R.success(dishDtoPage);
}
dishDtoPage封装的信息
四、修改菜品
需求分析
在菜品管理列表页面点击修改按钮,跳转到修改菜品页面,在修改页面回显菜品相关信息并进行修改,最后点击确定按钮完成修改操作。
代码开发
修改菜品时前端页面(add.html)和服务端的交互过程:
1.页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示;
2.页面发送ajax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显;
3.页面发送请求,请求服务端进行图片下载,用于图片回显;
4.点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端。
开发修改菜品功能,就是服务端编写代码去处理前端页面发送的这4次请求。
根据id查询菜品信息,用于菜品信息回显,代码:
在DishService实现类中重写方法,采用DishDto封装菜品和口味信息回显
DishController中:
/**
* 根据id查询菜品信息和对应的口味信息
* @param id
* @return
*/
@GetMapping("/{id}")
public R<DishDto> get(@PathVariable Long id){ //使用DishDto泛型来返回数据
DishDto dishDto = dishService.getByIdWithFlavor(id); //重写的方法
return R.success(dishDto);
}
修改后保存菜品信息,与使用添加菜品信息的前端页面相同 add.html
在DishService实现类中重写方法,修改菜品和口味
DishController中:
/**
* 修改菜品
* @param dishDto
* @return
*/
@PutMapping
public R<String> update(@RequestBody DishDto dishDto){
log.info(dishDto.toString());
dishService.updateWithFlavor(dishDto);
return R.success("修改菜品成功");
}
五、单个/批量修改起售/停售状态
代码开发
前端页面发送的请求
DishController方法
/**
* 对菜品进行批量/单个开启起售或者停售
* @param status
* @param ids
* @return
*/
@PostMapping("/status/{status}")
public R<String> status(@PathVariable Integer status,@RequestParam List<Long> ids){
log.info("status:{}",status);
log.info("ids:{}",ids);
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(ids != null,Dish::getId,ids);
List<Dish> list = dishService.list(queryWrapper);
for (Dish dish : list) {
if(dish != null){
dish.setStatus(status);
dishService.updateById(dish);
}
}
return R.success("售卖状态修改成功");
}
六、单个/批量删除菜品
代码开发
前端页面发送的请求
需要重写删除菜品的Service接口实现方法
/**
* 单个删除或者批量删除菜品
* @param ids
*/
@Transactional
public void removeWithFlavor(List<Long> ids) {
//查询菜品表信息 查询SQL语句:select count(*) from dish where id in (1,2,3) and ststus = 1
LambdaQueryWrapper<Dish> dishqueryWrapper = new LambdaQueryWrapper<>();
dishqueryWrapper.in(Dish::getId,ids);
dishqueryWrapper.eq(Dish::getStatus,1); //查询是否在起售状态
int count = this.count(dishqueryWrapper);
if(count > 0 ){
//如果不能删除,抛出业务异常
throw new CustomException("删除的菜品中有正在售卖的,无法删除");
}
//如果可以删除套餐,执行删除操作,删除菜品表中的信息
this.removeByIds(ids);
//删除菜品口味表关联信息 执行SQL语句:delete from dish_flavor where id in (1,2,3)
LambdaQueryWrapper<DishFlavor> dishflavorqueryWrapper = new LambdaQueryWrapper<>();
dishflavorqueryWrapper.in(DishFlavor::getDishId,ids);
dishFlavorService.remove(dishflavorqueryWrapper);
}
Controller方法