瑞吉外卖(四)

菜品管理业务开发

文件上传下载
文件上传介绍

文件上传,也称为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">

注意这里的文件是临时文件,请求结束将消失,所以需要保存下来:

image-20230308162616170

文件下载代码实现
@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);
}

前端新增菜品请求的参数:

image-20230309154219360

前端请求接口:

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 }
  })
}

image-20230310131550049

代码开发
@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);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值