菜品管理业务开发
文件上传下载
文件上传介绍
文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能
文件上传时,对页面的form表单有如下要求:
- method=“post” 采用post方式提交数据
- enctype=“multipart/form-data” 采用multipart格式上传文件
- type=“file” 使用input的file控件上传
服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:
- commons-fileupload
- commons-io
Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件
文件下载介绍
文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程。
通过浏览器进行文件下载,通常有两种表现形式:
- 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
- 直接在浏览器中打开
通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程
文件上传代码实现
@RestController
@Slf4j
@RequestMapping("/common")
public class CommonController {
@PostMapping("/upload")
public Result<String> upload(MultipartFile file){
log.info(file.toString());
return null;
}
}
MultipartFile参数对象的名字不能随便取,需要跟表单的name属性一致:
<input type="file" name="file" class="el-upload__input">
注意这里的文件是临时文件,请求结束将消失,所以需要保存下来:
文件下载代码实现
@GetMapping("/download")
public void download(String name, HttpServletResponse response) throws IOException {
File file = new File(basePath + name);
if (!file.exists()) {
return;
}
InputStream is = null;
OutputStream os = null;
try {
is = new FileInputStream(file);
os=response.getOutputStream();
byte[] bytes = new byte[1024];
int len = 0;
while ((len = is.read(bytes)) != -1) {
os.write(bytes,0,len);
os.flush();
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
if (is!=null) is.close();
if (os!=null) os.close();
}
}
新增菜品
需求分析
首先我们可以了解到前端请求的参数和响应数据的类型
1、前端首先会向服务器请求菜品分类的信息
created() {
this.getDishList()
// 口味临时数据
this.getFlavorListHand()
this.id = requestUrlParam('id')
}
// 获取菜品分类
getDishList() {
getCategoryList({'type': 1}).then(res => {
if (res.code === 1) {
this.dishList = res.data
} else {
this.$message.error(res.msg || '操作失败')
}
})
}
const getCategoryList = (params) => {
return $axios({
url: '/category/list',
method: 'get',
params
})
}
2、页面发送请求进行图片上传,请求服务端将图片保存到服务器
3、页面发送请求进行图片下载,将上传的图片进行回显
4、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端
代码开发
获取菜单分类列表
@GetMapping("/list")
public Result<List<Category>> list(Category category){
LambdaQueryWrapper<Category> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(category.getName()!=null,Category::getName,category.getName());
wrapper.eq(category.getType()!=null,Category::getType,category.getType());
wrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);
List<Category> list = categoryService.list(wrapper);
return Result.success(list);
}
前端新增菜品请求的参数:
前端请求接口:
const addDish = (params) => {
return $axios({
url: '/dish',
method: 'post',
data: { ...params }
})
}
导入DishDto,用于封装页面提交的数据
DTO,全称为Data Transfer object,即数据传输对象,一般用于展示层与服务层之间的数据传输
@Data
public class DishDto extends Dish {
private List<DishFlavor> flavors = new ArrayList<>();
private String categoryName;
private Integer copies;
}
接收前端请求数据,因为数据格式为json,注意要在参数加上@RequestBody注解
@PostMapping
public Result<String> addDish(@RequestBody DishDto dishDto){
log.info(dishDto.toString());
dishService.saveDishAndFlavor(dishDto);
return Result.success("新增菜品成功");
}
saveDishAndFlavor方法:
@Override
@Transactional
public void saveDishAndFlavor(DishDto dishDto) {
this.save(dishDto);
Long dishId = dishDto.getId();//获取生成的菜品ID
List<DishFlavor> flavors = dishDto.getFlavors();
flavors.forEach(flavor->flavor.setDishId(dishId));//为每个口味赋菜品ID
dishFlavorService.saveBatch(flavors);
}
为方法加上事务管理,即加上@Transactional注解,并在springboot的启动类上加上@EnableTransactionManagement注解
菜品分页查询
需求分析
前端接口
const params = {
page: this.page,
pageSize: this.pageSize,
name: this.input ? this.input : undefined
}
const getDishPage = (params) => {
return $axios({
url: '/dish/page',
method: 'get',
params
})
}
代码开发
@GetMapping("/page")
public Result<Page> page(@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
String name) {
Page<Dish> dishPage = new Page<>(page, pageSize);
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.hasLength(name), Dish::getName, name);
queryWrapper.orderByAsc(Dish::getSort);
dishService.page(dishPage, queryWrapper);//根据名字和顺序查找
Page<DishDto> dishDtoPage = new Page<>();
//将dishPage的内容拷贝到dishDtoPage,除了records之外
BeanUtils.copyProperties(dishPage,dishDtoPage,"records");
List<Dish> dishes = dishPage.getRecords();
LinkedList<DishDto> dishDtos = new LinkedList<>();
dishes.forEach(dish -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(dish,dishDto);
Category category = categoryService.getById(dish.getCategoryId());
dishDto.setCategoryName(category.getName());
dishDtos.add(dishDto);
});
dishDtoPage.setRecords(dishDtos);
return Result.success(dishDtoPage);
}
修改菜品
代码开发
在开发代码之前,需要梳理一下修改菜品时前端页面( add.html)和服务端的交互过程:
1、页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示
2、页面发送ajax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显
前端请求:
const queryDishById = (id) => {
return $axios({
url: `/dish/${id}`,
method: 'get'
})
}
代码实现:
@Override
public DishDto getDishDtoById(Long id) {
Dish dish = this.getById(id);
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(dish,dishDto);
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,id);
List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);
dishDto.setFlavors(flavors);
return dishDto;
}
3、页面发送请求,请求服务端进行图片下载,用于页图片回显
4、点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端
@PutMapping
public Result<String> updateDish(@RequestBody DishDto dishDto){
log.info(dishDto.toString());
dishService.updateDishAndFlavor(dishDto);
return Result.success("修改菜品成功");
}
@Override
@Transactional
public void updateDishAndFlavor(DishDto dishDto) {
this.updateById(dishDto);
Long dishId = dishDto.getId();//将菜品关联的口味信息删除
LambdaQueryWrapper<DishFlavor> flavorLambdaQueryWrapper = new LambdaQueryWrapper<>();
flavorLambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);
dishFlavorService.remove(flavorLambdaQueryWrapper);
//添加新的口味信息
List<DishFlavor> flavors = dishDto.getFlavors();
flavors.forEach(flavor -> flavor.setDishId(dishId));
dishFlavorService.saveBatch(flavors);
}
时间转换问题
到这里我发现了一个问题,就是前端页面展示时间的时候格式有问题:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qePFzsGE-1678556069650)(C:\Users\note\AppData\Roaming\Typora\typora-user-images\image-20230309224322728.png)]
因为我更新时间和创建时间的类型都是Date,而不是视频中的LocalDate,所以我觉得是JacksonObjectMapper不起作用,我的解决方法是在构造方法中继续加上以下语句:
//DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"
setDateFormat(new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT));
删除菜品
需求开发
删除菜品有两种方式,一种是删除一个,一种是批量删除。要注意起售状态的菜品不能删除,并且删除菜品时对应的口味信息和图片也要删除。
两种删除方式所使用的接口都是同一个,应该用列表或者数组的类型接收参数。
const deleteDish = (ids) => {
return $axios({
url: '/dish',
method: 'delete',
params: { ids }
})
}
代码开发
@Override
@Transactional
public void deleteDishAndFlavorWithBatch(List<Long> ids) {
List<Dish> dishList = this.listByIds(ids);
for (Dish dish : dishList) {
if (dish.getStatus()!=0){
throw new DishException("启售中的菜品不能删除");
}
LambdaQueryWrapper<DishFlavor> dishFlavorLambdaQueryWrapper = new LambdaQueryWrapper<>();
dishFlavorLambdaQueryWrapper.eq(DishFlavor::getDishId,dish.getId());
dishFlavorService.remove(dishFlavorLambdaQueryWrapper);
}
this.removeByIds(ids);
//删除菜品对应的图片
for (Dish dish : dishList) {
String imagePath = basePath+dish.getImage();
File file = new File(imagePath);
file.delete();
}
}
批量启售停售菜品
需求开发
const dishStatusByStatus = (params) => {
return $axios({
url: `/dish/status/${params.status}`,
method: 'post',
params: { ids: params.id }
})
}
代码开发
@Override
@Transactional
public void updateStatusByBatch(Integer status, List<Long> ids) {
if (status>1&&status<0){
throw new DishException("修改失败");
}
List<Dish> dishes = new LinkedList<>();
ids.forEach(id->{
Dish dish = new Dish();
dish.setId(id);
dish.setStatus(status);
dishes.add(dish);
});
this.updateBatchById(dishes);
}
@Override
@Transactional
public void updateStatusByBatch(Integer status, List ids) {
if (status>1&&status<0){
throw new DishException(“修改失败”);
}
List dishes = new LinkedList<>();
ids.forEach(id->{
Dish dish = new Dish();
dish.setId(id);
dish.setStatus(status);
dishes.add(dish);
});
this.updateBatchById(dishes);
}