【瑞吉外卖】-项目复盘(四)

【瑞吉外卖】-项目复盘(四)

课程内容

  • 文件上传下载
  • 菜品新增(同时操作两张表需要添加事务,引入DTO对象)
  • 菜品分页查询
  • 菜品修改

1、文件上传下载

文件上传前端:

文件上传时,对页面的form表单有如下要求:

表单属性取值说明
methodpost必须选择post方式提交
enctypemultipart/form-data采用multipart格式上传文件
typefile使用input的file控件上传

简单提交html代码如下:

<form method="post" action="/common/upload" enctype="multipart/form-data">
    <input name="myFile" type="file"  />
    <input type="submit" value="提交" /> 
</form>

文件上传服务端

服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:

  • commons-fileupload
  • commons-io

而Spring框架在spring-web包下对文件上传进行了封装,大大简化了服务端代码,只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件,例如:

上传代码实现

1)需要在application.yml中定义文件存储路径

reggie: 
	path: D:\img\

2)CommonController

文件上传的方法,通过MultipartFile类型的参数即可接收上传的文件,方法形参的名称需要与页面的file域的name属性一致

上传逻辑:

  1. 获取文件的原始文件名,通过原始文件名获取文件后缀
  2. 通过UUID重新声明文件名,以免文件名称重复造成文件覆盖
  3. 创建文件存放目录
  4. 将上传的临时文件转存到指定位置
import com.itheima.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
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){
        //file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
        log.info(file.toString());
		
        //原始文件名
        String originalFilename = file.getOriginalFilename();//abc.jpg
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));

        //使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
        String fileName = UUID.randomUUID().toString() + suffix;//dfsdfdfd.jpg
		
        //创建一个目录对象
        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);
    }
}    

下载代码实现

前端通过标签展示数据,src为/common/download?name=xxxx.jpg

请添加图片描述

因此,服务端需要接收页面的name参数,然后读取图片文件的数据,以流的形式写回浏览器

具体实现逻辑:

1)定义输入流,通过输入流读取文件内容

2)通过response对象,获取到输出流

3)通过response对象设置响应数据格式(image/jpeg)

4)通过输入流读取文件数据,然后通过上述的输出流写回浏览器

5)关闭资源

代码实现:

/**
 * 文件下载
 * @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();
    }
}

2、菜品新增

新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。所以在新增菜品时,涉及到两个表:dishdish_flavor

菜品的新增的逻辑

1). 点击新建菜品按钮, 访问页面(backend/page/food/add.html), 页面加载时发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中

2). 页面发送请求进行图片上传,请求服务端将图片保存到服务器(上传功能已实现)

3). 页面发送请求进行图片下载,将上传的图片进行回显(下载功能已实现)

4). 点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端

其中图片上传和下载的功能之前已经实现,因此,我们需要额外实现两个功能:

  1. 菜品分类数据列表查询
  2. 保存菜品信息

DishDto实体类

保存菜品信息的功能,前端页面传过来的数据除了,Dish的基本信息外,还有flavor等口味信息。而Dish实体类中是没有这些flavor属性的。因此,需要自定义一个实体类dto(data transfer object,数据传输对象),继承自Dish,并对Dish的属性进行拓展,增加flavors集合属性。

由于,我们不仅需要保存菜品的基本信息,还需要保存菜品的口味信息,需要操作两张表,所以我们需要在DishService中定义接口方法,在这个方法中需要保存上述的两部分数据:

/**
 * 新增菜品
 * @param dishDto
 * @return
 */
@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
    log.info(dishDto.toString());

    dishService.saveWithFlavor(dishDto);

    return R.success("新增菜品成功");
}

dishService.saveWithFlavor方法的具体逻辑如下:

  1. 保存菜品基本信息;
  2. 获取保存的菜品ID;
  3. 获取菜品口味列表,遍历列表,为菜品口味对象属性dishId赋值
  4. 批量保存菜品口味列表
@Autowired
private DishFlavorService dishFlavorService;
/**
* 新增菜品,同时保存对应的口味数据
* @param dishDto
*/
@Transactional
public void saveWithFlavor(DishDto dishDto) {
    //保存菜品的基本信息到菜品表dish
    this.save(dishDto);
	
    Long dishId = dishDto.getId();//菜品id
    //菜品口味
    List<DishFlavor> flavors = dishDto.getFlavors();
    flavors = flavors.stream().map((item) -> {
        item.setDishId(dishId);
        return item;
    }).collect(Collectors.toList());

    //保存菜品口味数据到菜品口味表dish_flavor
    dishFlavorService.saveBatch(flavors);
}

由于在 saveWithFlavor 方法中,进行了两次数据库的保存操作,操作了两张表,那么为了保证数据的一致性,我们需要在方法上加上注解@Transactional来控制事务。

在引导类上加注解@EnableTransactionManagement

Service层方法上加的注解@Transactional要想生效,需要在引导类上加上注解@EnableTransactionManagement,开启对事物的支持

3、菜品分页查询

在菜品列表展示,除了菜品的基本信息,还有两个字段略微特殊,图片字段菜品分类

在实体类Dish中,仅仅包含categoryId,不包含categoryName,因此在这里我们可以返回DishDto对象,在该对象中扩展一个属性categoryName,来封装菜品分类名称。

@Data
public class DishDto extends Dish {
    private List<DishFlavor> flavors = new ArrayList<>();
    private String categoryName; //菜品分类名称
    private Integer copies;
}

具体逻辑为:

1). 构造分页条件对象

2). 构建查询及排序条件

3). 执行分页条件查询

4). 遍历分页查询列表数据,根据分类ID查询分类信息,从而获取该菜品的分类名称

5). 封装数据并返回

4、菜品修改

分析步骤:

1)点击修改,携带菜品id参数跳转到add.html

2)进入add.html,页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示(已实现)

3)页面发送请求,获取菜品基本信息以及口味信息,用于回显

4)页面请求图片数据,用于菜品图片回显(已实现)

5)页面发送请求,将修改后菜品基本信息以及口味信息以json提交到服务端

遇到的注解

  • @Value:获取yml中的参数,例如@Value(${reggie.path})
  • @Transactional:如果同时操作了两张表,或者多次修改操作,并且这些操作涉及到事务的一致性问题。
  • @EnableTransactionManagement:修饰引导类,开启对事务的支持
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值