目录
12、菜品管理业务开发--文件上传下载
12.1、需求分析
12.2、代码开发(上传、下载)
13、新增菜品
13.1、需求分析
13.2、代码开发
14、菜品信息分页查询
14.1、需求分析
14.2、代码开发
15、修改菜品
15.1、问题分析
15.2、代码开发
16、停售/起售菜品,删除菜品
16.1、需求分析
16.2、代码开发
12、菜品管理业务开发--文件上传下载
12.1、需求分析
--在我们进行添加菜品时,需要提供图片,此时就涉及到了文件上传。从客户端上传文件至服务器。
- 发QQ、微博、微信朋友圈都用到了文件上传功能,将用户数据上传到服务器保存
--在我们进行分页查询菜品数据时,会在页面显示图片资源,此时就涉及到了文件下载。从服务器下载文件至客户端。通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。
- 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
- 直接在浏览器中打开(也是下载的一种)
12.2、代码开发(上传、下载)
--上传:Spring框架在spring-web包中对文件上传进行了封装,在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件。
--根据前端检查信息:此name值即为MultipartFile声明的参数名称
--操作如下:
-上传时,需要指定磁盘路径。我们在yml文件中配置
reggie:
path: E:\BaiduNetdiskDownload\7、黑马程序员瑞吉外卖平台实战开发\瑞吉外卖-资料\1 瑞吉外卖项目\资料\图片资源\
-上传业务代码:
@Value("${reggie.path}")
private String basePath;
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
public R<String>upload(MultipartFile file){ //MultipartFile将客户端的文件传送到服务器
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); //截取后缀
String fileName = UUID.randomUUID().toString() + suffix;
//创建包路径的目录对象
File dir = new File(basePath);
//如果包路径不存在,创建
if (!dir.exists()){
dir.mkdir();
}
//将临时文件转存到指定位置
try {
file.transferTo(new File(basePath + fileName));
} catch (IOException e) {
e.printStackTrace();
}
return R.success(fileName);
}
-下载:流的读写操作
@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){
//只要len里面有内容(由输入流读取的)就一直用输出流读
outputStream.write(bytes,0,len);
outputStream.flush();
}
fileInputStream.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
13、新增菜品
13.1、需求分析
--通过新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片,在移动端会按照菜品分类来展示对应的菜品信息。
--新增菜品时包含口味属性,所以我们要添加新的数据模型dish_flavor表。所以在新增菜品时,涉及到两个表:dish表(菜品表),dish_flavor表(菜品口味表)。多表操作
13.2、代码开发
--配置dish_flavor的相关配置:实体类、mapper接口、service接口和实现类
新增菜品时前端页面和服务端的交互过程:
1、页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中
2、页面发送请求进行图片上传,请求服务端将图片保存到服务器
3、页面发送请求进行图片下载,将上传的图片进行回显
4、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端
开发新增菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。
--1、我们在添加菜品时,需要选择当前菜品所属的菜品分类,所以我们要回显菜品分类到下拉框。
-从前端检查请求路径得知,需要在CategoryController中操作
/**
* 根据条件查询菜品的分类数据
* @param category
* @return
*/
@GetMapping("/list") //返回的为菜品的分类集合
public R<List<Category>>list(Category category){
LambdaQueryWrapper<Category> qw = new LambdaQueryWrapper<>();
//若菜品没有分类,则不在此页面显示,此页面只显示菜品的分类
qw.eq(category.getType()!=null,Category::getType,category.getType());
//根据sort和修改时间排序
qw.orderByAsc(Category::getSort).orderByAsc(Category::getUpdateTime);
List<Category> list = categoryService.list();
return R.success(list);
}
--2和3我们在做过公用的上传和下载
--4、涉及到两个表:dish表(菜品表),dish_flavor表(菜品口味表)。多表操作,我们需要中级体(Dto)来协助。MP提供的添加操作无法满足,需自定义
DTO,全称为Data Transfer object,即数据传输对象,一般用于展示层与服务层之间的数据传输。
-操作如下:我们在DishService中编写添加方法saveWithFlavor,在实现类中重写
********此处涉及到Stream流操作***********
// 菜品新增----因新增的内容含有口味等属性,此属性在菜品口味表中DishFlavor
// * MP提供的方法只能操作Dish表中属性,所以要自定义方法DishServiceImpl--利用Dto进行多表操作
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
@Autowired
private DishFlavorService dishFlavorService;
@Override
@Transactional
public void saveWithFlavor(DishDto dishDto) {
//客户端传的数据dishDto中有部分内容在dish实体类(数据库dish表),先将dish中有的内容添加进去
this.save(dishDto);
/**
* "flavors":[{"name":"甜味","value":"[\"无糖\",\"少糖\",\"半糖\",\"多糖\",\"全糖\"]","showOption":false}
* //因前端传送的flavors里并没有dishId,所以需要利用stream联合map进行映射,将dishId手动添加至集合中的每一条数据中
*/
//先在dishDto中手动获取dishId
Long dishId = dishDto.getId();
//创建flavors集合
List<DishFlavor> flavors = dishDto.getFlavors();
//flavors.stream()创建flavors集合的stream流
//stream().map将集合里面的每一条数据映射出来
flavors.stream().map((item) -> {
//对集合中的每条数据进行操作-----加入dishId
item.setDishId(dishId);
return item;
}).collect(Collectors.toList()); //将刚刚单独取出来的每条数据在转化为集合
// for (DishFlavor item:flavors){ //增强for
// item.setDishId(dishId);
// }
dishFlavorService.saveBatch(flavors);
}
}
-在Controller中调用
/**
* 菜品新增----因新增的内容含有口味等属性,此属性在菜品口味表中DishFlavor
* MP提供的方法只能操作Dish表中属性,所以要自定义方法--利用Dto进行多表操作
* @param dishDto
* @return
*/
@PostMapping
public R<String>save(@RequestBody DishDto dishDto ){
// dishService.save(dish);
dishService.saveWithFlavor(dishDto );
return R.success("菜品新增成功");
}
14、菜品信息分页查询
14.1、需求分析
--分页显示数据,涉及到客户端返回类型中有个别属性在前端页面无法显示,需要后端进行操作
* 前端菜品分类需要显示名称,但是前端查询时,只能查到菜品的id, * 我们需要在后端根据传来的id来查找对应的名称然后返回给前端
14.2、代码开发
--1、页面(backend/page/food/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据。2、页面发送请求,请求服务端进行图片下载,用于页面图片展示
************涉及到Stream流和对象拷贝**************
/**
* 前端菜品分类需要显示名称,但是前端查询时,只能查到菜品的id,
* 我们需要在后端根据传来的id来查找对应的名称然后返回给前端
* @param page
* @param pageSize
* @param name
* @return
*/
@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> qw = new LambdaQueryWrapper<>();
//添加查询条件
qw.like(StringUtils.isNotEmpty(name),Dish::getName,name);
qw.orderByDesc(Dish::getSort);
//开始查询分页
dishService.page(pageInfo,qw);
//将得到的分页数据进行处理
//对象对象拷贝----其中records为页面集合数据因缺少categoryName不需要复制
BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
List<Dish> records = pageInfo.getRecords();
//stream().map将集合里面的每一条数据映射出来
// List<DishDto> nowrecords = records.stream().map((item)->{
//对集合中的每条数据进行操作
//获取dishDto用于存放属性
// DishDto dishDto = new DishDto();
//将集合中每条数据已有属性拷贝到dishDto
// BeanUtils.copyProperties(item,dishDto);
//获取菜品的id
// Long categoryId = item.getCategoryId();
//根据id查对应的菜品
// Category category = categoryService.getById(categoryId);
//获取对应菜品的名称
// String categoryName = category.getName();
//将得到的名称set到dishDto中
// dishDto.setCategoryName(categoryName);
// return dishDto;
// }).collect(Collectors.toList());
ArrayList<DishDto> list = new ArrayList<>();
for (Dish newRecords:records){
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(newRecords,dishDto);
Long categoryId = newRecords.getCategoryId();
Category category = categoryService.getById(categoryId);
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
list.add(dishDto);
}
// dishDtoPage.setRecords(nowrecords);
dishDtoPage.setRecords(list);
return R.success(dishDtoPage);
}
15、修改菜品
15.1、问题分析
--在菜品管理列表页面点击修改按钮,跳转到修改菜品页面,在修改页面回显菜品相关信息并进行修改,最后点击确定按钮完成修改操作
-所以,我们需要做的事分两步,先回显,再修改
15.2、代码开发
--1、在修改页面回显数据:
根据检查消息得:回显数据为根据id进行get请求查询信息
查询内容分两块:基本信息Dish表和口味信息DishFlavor表
--2、点击保存提交数据:
点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端
--操作如下(多表操作,自定义service方法):
DishController:
/**
* 修改菜品--1回显数据
* @param ids
* @return
*/
@GetMapping("/{ids}")
public R<DishDto> update(@PathVariable Long ids){
DishDto dishDto = dishService.getWithFlavor(ids);
return R.success(dishDto);
}
/**
* 修改菜品--2保存数据
* @param dishDto
* @return
*/
@PutMapping
public R<String>put(@RequestBody DishDto dishDto){
dishService.updateWithFlavor(dishDto);
return R.success("修改成功");
}
DishService
//修改菜品--1回显数据
DishDto getWithFlavor (Long ids);
//修改菜品--2保存数据
void updateWithFlavor (DishDto dishDto);
DishServiceImpl
/**
* //修改菜品--1回显数据
* @param ids
* @return
*/
@Override
public DishDto getWithFlavor(Long ids) {
//查询菜品的基本信息
Dish dish = this.getById(ids);
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(dish,dishDto);
//查询口味信息
LambdaQueryWrapper<DishFlavor> qw = new LambdaQueryWrapper<>();
qw.eq(DishFlavor::getDishId,dish.getId());
List<DishFlavor> list = dishFlavorService.list(qw);
dishDto.setFlavors(list);
return dishDto;
}
/**
* //修改菜品--2保存数据
* @param dishDto
*/
@Override
public void updateWithFlavor(DishDto dishDto) {
//更改基本信息
this.updateById(dishDto);
//删除原有口味
LambdaQueryWrapper<DishFlavor> qw = new LambdaQueryWrapper<>();
qw.eq(DishFlavor::getDishId,dishDto.getId());
dishFlavorService.remove(qw);
//添加更改之后的口味
List<DishFlavor> flavors = dishDto.getFlavors();
flavors.stream().map((item) -> {
item.setDishId(dishDto.getId());
return item;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(flavors);
}
16、停售/起售菜品,删除菜品
16.1、需求分析
--批量进行商品停售,起售以及删除方法
16.2、代码开发
因批量操作,我们客户端提交的数据为数组类型的ids
--1、商品停售,起售:
-从检查得知请求路径为"/status/{status}",客户端提交给服务器的数据有status和ids的数组
--操作如下:
/**
* 批量起售/停售
* @param status
* @param ids
* @return
*/
@PostMapping("/status/{status}")
public R<String>stop(@PathVariable int status, String[] ids){
for (String id:ids) {
Dish dish = dishService.getById(id);
dish.setStatus(status);
dishService.updateById(dish);
}return R.success("菜品状态更改成功");
}
}
--2、删除操作:
-删除菜品同时要删除口味,多表操作
DishController:调用自定义的deleteWithFlavor
/**
* 删除菜品-连带口味一起删除
* @param ids
* @return
*/
@DeleteMapping
public R<String>delete(String[] ids){
dishService.deleteWithFlavor(ids);
return R.success("菜品删除成功");
}
DishService:
//删除菜品-连带口味一起删除
void deleteWithFlavor (String[]ids);
DishServiceImpl
/**
* 删除菜品-连带口味一起删除
* @param ids
*/
@Override
public void deleteWithFlavor(String[] ids) {
for (String id:ids) {
this.removeById(id);
LambdaQueryWrapper<DishFlavor> qw = new LambdaQueryWrapper<>();
qw.eq(DishFlavor::getDishId,id);
dishFlavorService.remove(qw);
}
}