菜品管理业务
1.文件上传下载
1.1 文件上传介绍
1.2 文件下载介绍
1.3 文件上传代码实现
前端页面直接使用现成的,源码这里:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
<!-- 引入样式 -->
<link rel="stylesheet" href="../../plugins/element-ui/index.css" />
<link rel="stylesheet" href="../../styles/common.css" />
<link rel="stylesheet" href="../../styles/page.css" />
</head>
<body>
<div class="addBrand-container" id="food-add-app">
<div class="container">
<el-upload class="avatar-uploader"
action="/common/upload"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeUpload"
ref="upload">
<img v-if="imageUrl" :src="imageUrl" class="avatar"></img>
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="../../plugins/vue/vue.js"></script>
<!-- 引入组件库 -->
<script src="../../plugins/element-ui/index.js"></script>
<!-- 引入axios -->
<script src="../../plugins/axios/axios.min.js"></script>
<script src="../../js/index.js"></script>
<script>
new Vue({
el: '#food-add-app',
data() {
return {
imageUrl: ''
}
},
methods: {
handleAvatarSuccess (response, file, fileList) {
this.imageUrl = `/common/download?name=${response.data}`
},
beforeUpload (file) {
if(file){
const suffix = file.name.split('.')[1]
const size = file.size / 1024 / 1024 < 2
if(['png','jpeg','jpg'].indexOf(suffix) < 0){
this.$message.error('上传图片只支持 png、jpeg、jpg 格式!')
this.$refs.upload.clearFiles()
return false
}
if(!size){
this.$message.error('上传文件大小不能超过 2MB!')
return false
}
return file
}
}
}
})
</script>
</body>
</html>
然后项目在terminal中输入
mvn clean compile
mvn install
清空浏览器缓存,访问http://localhost:8080/backend/page/demo/upload.html,就嫩实现图片上传。
但是看控制台信息就会发现,虽然显示结果是200,但是其实请求是被拦截的:
我们编写controller来正确处理这次请求:
新建CommonController处理文件上传下载。
其中我们文件本地转存的文件夹路径可以在applicaiton.yml中指定:
详细执行过程可以看代码注释,很多讲究
/**
*文件上传和下载
*/
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {
//使用value注解从application.yml配置文件中读取的路径
@Value("${reggie.path}")
private String pathValue;
/**
* 文件上传
* @param file 参数名要和前端页面请求的name参数的名称保持一致
* @return
*/
@PostMapping("/upload")
public R<String> upload(MultipartFile file){
//此时的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(pathValue);
//判断当前目录是否存在
if(!dir.exists()){
//如果目录不存在,则创建目录
dir.mkdir();
}
try{
//将我们的临时文件转存到指定位置
file.transferTo(new File(pathValue+filename));
}catch (IOException e){
e.printStackTrace();
}
return null;
}
}
上传图片,我们发图片成功转存在了本地指定目录下:
1.4 文件下载代码实现
img标签可以展示下载的图片
对应的请求在这里:
需要把文件传进去
我们编写controller代码
/**
* 文件下载
* @param name
* @param response
*/
@GetMapping("/download")
public void download(String name, HttpServletResponse response){
try {
//输入流,通过输入流读取文件内容
FileInputStream inputStream=new FileInputStream(new File(pathValue+name));
//输出流,将文件写回浏览器,从而可以展示图片
ServletOutputStream outputStream = response.getOutputStream();
response.setContentType("imag/jpeg");
int len=0;
byte[] bytes=new byte[1024];
while((len=inputStream.read(bytes))!=-1){
outputStream.write(bytes,0,len);
outputStream.flush();
}
//关闭资源
outputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
测试,运行:图片能成功回显了。
我们发现,整个过程浏览器一共提交了两个请求,处理第一个请求时将图片转存到本地,处理第二个请求将本地图片回显到浏览器上。
2.新增菜品
2.1需求分析
2.2 代码开发
其中图片上传下载功能之前已经开发好了,能直接使用。
首先是根据条件获取菜品分类数据(下拉框数据)。我们先看前端代码add.html:
直接看钩子函数created:
找到了发出的获取菜品分类的ajax请求:“/category/list”,get方法
然后我们在categoryController中编写按条件获取分类的请求代码
/**
* 根据条件查询分类数据
* @return
*/
@GetMapping("/list")
public R<List<Category>> getCatList(Category category){
//查询条件
LambdaQueryWrapper<Category> queryWrapper=new LambdaQueryWrapper<>();
//添加条件
queryWrapper.eq(category.getType()!=null,Category::getType,category.getType());
//排序条件
queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getCreateTime);
List<Category> list = categoryService.list(queryWrapper);
return R.success(list);
}
这里要强调一下为什么这个方法的参数没有加@RequestBody注解,是因为此方法是Get 请求,参数在请求头,而不是请求体。
测试:成功在下拉菜单中获取菜品分类。
然后我们试着新增一个菜,看看发出的dish请求:
看看前端的代码:点击保存调用submitForm方法
addDish方法:
我们要做的有两件事:一是保存菜品在dish表中,还有需要保存口味信息在dish_flavor表中。
那么我们请求体的参数类型要写Dish吗?这样是不行的,因为前端的请求参数和Dish的字段不完全一致,前端实际的请求参数多一个flavors。
我们的解决方法就是引入一个新的类DishDto(继承自Dish类),用来封装页面提交的数据。
我们在DishService接口中定义这样一个方法,用来同时保存菜品和口味:
public interface DishService extends IService<Dish> {
/**
* 新增菜品,同时保存菜品和口味数据
* @param dishDto
*/
public void saveWithFlavor(DishDto dishDto);
}
然后我们在DishServiceImpl中实现方法:
其中有两点需要注意:
1.flavors需要通过stream流的方式来设置dishId,
2. 因为涉及到操作多张表,所以需要事务,其注解为:@Transactional
3. 启动类上需要添加注解支持:@EnableTransactionManagement
@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
@Autowired
private DishFlavorService dishFlavorService;
/**
* 存口味和菜品
* @param dishDto
*/
//因为涉及到操作多张表,所以需要事务
@Transactional
public void saveWithFlavor(DishDto dishDto){
//存基本信息到菜品表
this.save(dishDto);
//菜品id
Long dishId = dishDto.getId();
List<DishFlavor> flavors = dishDto.getFlavors();
//使用stream流处理
flavors=flavors.stream().map((item)->{
item.setDishId(dishId);
return item; }).collect(Collectors.toList());
//保存口味信息到口味表
dishFlavorService.saveBatch(flavors);
}
}
完成后,我们编写dishController的方法:
/**
* 新增菜品,同时保存对应的口味数据
* @param dishDto
* @return
*/
@PostMapping
public R<String> addDish(@RequestBody DishDto dishDto){
log.info(dishDto.toString());
dishService.saveWithFlavor(dishDto);
return R.success("新增菜品成功");
}
测试,刷新数据库表,发现菜品和口味数据插入成功。
3.菜品信息分页查询
3.1需求分析
3.2 代码开发
ajax请求:
页面发送的请求:
于是我们编写controller方法:
/**
* 菜品信息分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> getDishPage(int page,int pageSize,String name){
//分页查询构造器
Page<Dish> pageInfo=new Page<>(page,pageSize);
//查询条件构造器
LambdaQueryWrapper<Dish> queryWrapper=new LambdaQueryWrapper<>();
//添加查询条件(模糊查询)
queryWrapper.like(StringUtils.isNotEmpty(name),Dish::getName,name);
//排序条件(根据更新时间降序排)
queryWrapper.orderByDesc(Dish::getUpdateTime);
//执行查询
dishService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
测试,发现问题
我们发现,菜品分类这一栏信息没有显示,是为什么呢?
我们浏览器调试,发现,我们响应回去的菜品数据,里面只有菜品分类的id,而没有菜品分类名称:
而人家前端需要的是categoryName,我们响应回去的数据没有这个字段。
怎么办呢?我们想到了我们的DishDto:
继承自Dish类,又扩展了categoryName属性,符合我们的需要。
于是我们对代码进行修改:
/**
* 菜品信息分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> getDishPage(int page,int pageSize,String name){
//分页查询构造器
Page<Dish> pageInfo=new Page<>(page,pageSize);
Page<DishDto> dishDtoPage=new Page<>();
//查询条件构造器
LambdaQueryWrapper<Dish> queryWrapper=new LambdaQueryWrapper<>();
//添加查询条件(模糊查询)
queryWrapper.like(StringUtils.isNotEmpty(name),Dish::getName,name);
//排序条件(根据更新时间降序排)
queryWrapper.orderByDesc(Dish::getUpdateTime);
//执行查询
dishService.page(pageInfo,queryWrapper);
//对象拷贝
BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
List<Dish> records = pageInfo.getRecords();
List<DishDto> dishDtoList;
//改变类型
dishDtoList=records.stream().map((item)->{
DishDto dishDto=new DishDto();
//对象(属性)拷贝
BeanUtils.copyProperties(item,dishDto);
//setCategoryName
Long categoryId = item.getCategoryId();
Category category = categoryService.getById(categoryId);
if(category!=null){
dishDto.setCategoryName(category.getName());
}
return dishDto;
}).collect(Collectors.toList());
dishDtoPage.setRecords(dishDtoList);
return R.success(dishDtoPage);
}
测试,发现能够正常查询了
4.修改菜品
4.1需求分析
4.2 代码开发
我们的第一个请求、第三个请求已经做好了。我们第二个请求(根据id查询当前菜品信息)在这里:
下面我们在DishService中定义好接口,然后在DishServiceImpl类中编写根据id查询菜品&口味信息的具体方法:
注意: BeanUtils.copyProperties此方法只能将父类对象的属性拷贝到子类对象中!其他的属性还是老老实实用set方法吧
/**
* 根据id查询菜品和口味信息
* @param id
* @return
*/
@Override
public DishDto queryDishById(Long id) {
//先查出对应id的菜品信息
Dish dishbyId = this.getById(id);
//属性拷贝到dishDto对象
DishDto dishDto=new DishDto();
BeanUtils.copyProperties(dishbyId,dishDto);// BeanUtils.copyProperties此方法只能将父类对象的属性拷贝到子类对象中
//然后根据菜品id查询口味List
//条件构造器
LambdaQueryWrapper<DishFlavor> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,dishbyId.getId());
List<DishFlavor> flavorList = dishFlavorService.list(queryWrapper);
dishDto.setFlavors(flavorList);
return dishDto;
}
controller代码
/**
* 根据id查询菜品信息和对应的口味信息
* @param id
* @return
*/
@GetMapping("/{id}")
public R<DishDto> getDishWhenUpdate(@PathVariable Long id){
DishDto dishDto = dishService.queryDishById(id);
return R.success(dishDto);
}
编写完成,测试:回显成功。
下一步就是修改信息后,点击保存,修改菜品和口味信息表。还是先在DishService中定义好接口,然后在DishServiceImpl中编写新增方法。因为涉及到多张表的操作,所以加上事务注解**@Transactional**
具体的逻辑是:先更新菜品表,然后先清理对应菜品的口味数据,最后在增加对应菜品的口味数据
/**
* 根据传进来的dishDto修改菜品和口味表
* @param dishDto
*/
@Override
@Transactional
public void updateWithFlavor(DishDto dishDto) {
//更新菜品表
this.updateById(dishDto);
//先清理对应菜品的口味数据
LambdaQueryWrapper<DishFlavor> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
dishFlavorService.remove(queryWrapper);
//再增加对应菜品的口味数据
List<DishFlavor> flavors = dishDto.getFlavors();
flavors=flavors.stream().map((item)->{
item.setDishId(dishDto.getId());
return item;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(flavors);
}
controller代码
/**
* 修改菜品提交
* @param dishDto
* @return
*/
@PutMapping
public R<String> updateDish(@RequestBody DishDto dishDto){
dishService.updateWithFlavor(dishDto);
return R.success("修改成功");
}
测试,修改成功。
5.停售/起售菜品(视频没有,自己写的,供参考)
5.1代码开发
停售/起售 controller,注意这里为了支持批量操作,传入的ids为long数组
/**
* (批量)停售/起售
* @param status
* @param ids
* @return
*/
@PostMapping("/status/{status}")
public R<String> beginEnd(@PathVariable Integer status,Long[] ids){
// log.info("id为{}的菜品要修改的状态为{}",ids,status);
for (Long oneids : ids) {
Dish dish = dishService.getById(oneids);
dish.setStatus(status);
dishService.updateById(dish);
}
return R.success("修改成功");
}
6.删除菜品(视频没有,自己写的,供参考)
6.1代码开发
/**
*(批量) 删除菜品,同时删除对应的口味信息
* @param ids
* @return
*/
@DeleteMapping
public R<String> deleteDish(Long[] ids){
for (Long id : ids) {
dishService.removeById(id);
LambdaQueryWrapper<DishFlavor> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,id);
dishFlavorService.remove(queryWrapper);
}
return R.success("删除成功");
}