1. 程式人生 > 其它 >Java專案實戰——瑞吉外賣Day04

Java專案實戰——瑞吉外賣Day04

瑞吉外賣開發筆記 四

筆記內容為黑馬程式設計師視訊內容

菜品管理業務開發

檔案上傳下載

檔案上傳介紹

檔案上傳,也稱為upload,是指將本地圖片、視訊、音訊等檔案上傳到伺服器上,可以供其他使用者瀏覽或下載的過程。檔案上傳在專案中應用非常廣泛,我們經常發微博、發微信朋友圈都用到了檔案上傳功能。

檔案上傳時,對頁面的form表單有如下要求:

  • method="post"            採用post方式提交資料
  • enctype="multipart/form-data"     採用multipart格式上傳檔案
  • type="file"              使用input的file控制元件上傳

目前一些前端元件庫也提供了相應的上傳元件,但是底層原理還是基於form表單的檔案上傳。例如ElementUI中提供的upload上傳元件:

服務端要接收客戶端頁面上傳的檔案,通常都會使用Apache的兩個元件:

  • commons-fileupload
  • commons-io

Spring框架在spring-web包中對檔案上傳進行了封裝,大大簡化了服務端程式碼,我們只需要在Controller的方法中宣告一個MultipartFile型別的引數即可接收上傳的檔案。

檔案下載介紹

檔案下載,也稱為download,是指將檔案從伺服器傳輸到本地計算機的過程。
通過瀏覽器進行檔案下載,通常有兩種表現形式:

  • 以附件形式下載,彈出儲存對話方塊,將檔案儲存到指定磁碟目錄
  • 直接在瀏覽器中開啟

通過瀏覽器進行檔案下載,本質上就是服務端將檔案以流的形式寫回瀏覽器的過程。

檔案上傳程式碼實現

檔案上傳,頁面端可以使用ElementuI提供的上傳元件。
可以直接使用資料中提供的上傳頁面,位置:資料/檔案上傳下載頁面/upload.html

<!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" />
    <link rel="shortcut icon" href="../../favicon.ico">
</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>

新增CommonController,負責檔案上傳與下載

@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {

    //檔案上傳
    @PostMapping("/upload")
    public R<String> upload(MultipartFile file){
        //file 是一個臨時檔案,需要轉存到指定位置,否則請求完成後臨時檔案會刪除
        log.info("file:{}",file.toString());
        return null;
    }
}

MultipartFile定義的file變數必須與name保持一致

完整程式碼

@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {
    @Value("${reggie.path}")
    private String basePath;

    //檔案上傳
    @PostMapping("/upload")
    public R<String> upload(MultipartFile file){
        //file 是一個臨時檔案,需要轉存到指定位置,否則請求完成後臨時檔案會刪除
        //log.info("file:{}",file.toString());

        //原始檔名
        String originalFilename = file.getOriginalFilename();
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        //使用UUID隨機生成檔名,防止因為檔名相同造成檔案覆蓋
        String fileName = UUID.randomUUID().toString()+suffix;

        //建立一個目錄物件
        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);
    }
}

檔案下載程式碼實現

檔案下載,頁面端可以使用標籤展示下載的圖片

//檔案下載
@GetMapping("/download")
public void download(String name, HttpServletResponse response){
    try {
        //輸入流,通過輸入流讀取檔案內容
        FileInputStream fileInputStream=new FileInputStream(new File(basePath+name));
        //輸出流,通過輸出流將檔案寫回瀏覽器,在瀏覽器中展示圖片
        ServletOutputStream outputStream = response.getOutputStream();

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

新增菜品

需求分析

後臺系統中可以管理菜品資訊,通過新增功能來新增一個新的菜品,在新增菜品時需要選擇當前菜品所屬的菜品分類,並且需要上傳菜品圖片,在移動端會按照菜品分類來展示對應的菜品資訊。

資料模型

新增菜品,其實就是將新增頁面錄入的菜品資訊插入到dish表,如果添加了口味做法,還需要向dish_flavor表插入資料。所以在新增菜品時,涉及到兩個表:

  • dish(菜品表)

  • dish_flavor(菜品口味表)

程式碼開發-準備工作

在開發業務功能前,先將需要用到的類和介面基本結構建立好:

  • 實體類DishFlavor(直接從課程資料中匯入即可,Dish實體前面課程中已經匯入過了)
@Data
public class DishFlavor implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;


    //菜品id
    private Long dishId;


    //口味名稱
    private String name;


    //口味資料list
    private String value;


    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;


    @TableField(fill = FieldFill.INSERT)
    private Long createUser;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;


    //是否刪除
    private Integer isDeleted;
}
  • Mapper介面DishFlavorMapper
@Mapper
public interface DishFlavorMapper extends BaseMapper<DishFlavor> {
}
  • 業務層介面DishFlavorService
public interface DishFlavorService extends IService<DishFlavor> {
}
  • 業務層實現類 DishFlavorServicelmpl
@Service
public class DishFlavorServiceImpl extends ServiceImpl<DishFlavorMapper, DishFlavor>implements DishFlavorService {
}
  • 控制層 DishController
@RestController
@RequestMapping("/dish")
public class DishController {
    @Autowired
    private DishService dishService;
    @Autowired
    private DishFlavorService dishFlavorService;
}

程式碼開發-梳理互動過程

在開發程式碼之前,需要梳理一下新增菜品時前端頁面和服務端的互動過程:

1、頁面(backend/page/food/add.html)傳送ajax請求,請求服務端獲取菜品分類資料並展示到下拉框中

2、頁面傳送請求進行圖片上傳,請求服務端將圖片儲存到伺服器

3、頁面傳送請求進行圖片下載,將上傳的圖片進行回顯

4、點選儲存按鈕,傳送ajax請求,將菜品相關資料以json形式提交到服務端

開發新增菜品功能,其實就是在服務端編寫程式碼去處理前端頁面傳送的這4次請求即可。

菜品分類下拉框:在CategoryController新增

//根據條件查詢分類資料
@GetMapping("/list")
public R<List<Category>> list(Category category){
    //條件構造器
    LambdaQueryWrapper<Category> lambdaQueryWrapper=new LambdaQueryWrapper<>();
    //新增條件
    lambdaQueryWrapper.eq(category.getType()!=null,Category::getType,category.getType());
    //新增排序條件
    lambdaQueryWrapper.orderByAsc(Category::getSort).orderByAsc(Category::getUpdateTime);
    List<Category> list = categoryService.list(lambdaQueryWrapper);
    return R.success(list);
}

匯入DishDto(位置:資料/dto),用於封裝頁面提交的資料

@Data
public class DishDto extends Dish {

    private List<DishFlavor> flavors = new ArrayList<>();

    private String categoryName;

    private Integer copies;
}

注意:DTO,全稱為Data Transfer object,即資料傳輸物件,一般用於展示層與服務層之間的資料傳輸。

新增菜品同時插入菜品對應的口味資料,需要操作兩張表:dish、dishflavor

在DishService介面中新增方法saveWithFlavor,在DishServiceImpl實現

@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
    @Autowired
    private DishFlavorService dishFlavorService;

    @Override
    @Transactional
    public void saveWithFlavor(DishDto dishDto) {
        //儲存菜品基本資訊到菜品表dish
        this.save(dishDto);

        Long dishid = dishDto.getId();
        //菜品口味
        List<DishFlavor> flavors = dishDto.getFlavors();
        flavors = flavors.stream().map((item) -> {
            item.setDishId(dishid);
            return item;
        }).collect(Collectors.toList());
        //dishFlavorService.saveBatch(dishDto.getFlavors());
        //儲存菜品口味到菜品資料表dish_flavor
        dishFlavorService.saveBatch(flavors);
    }
}

由於以上程式碼涉及多表操作,在啟動類上開啟事務支援新增@EnableTransactionManagement註解,但是本人新增該註解會報錯,專案啟動會失敗,並且springboot該註解應該是預設開啟的,故沒有新增

新增菜品

@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
    dishService.saveWithFlavor(dishDto);
    return R.success("新增菜品成功");
}

菜品資訊分頁查詢

需求分析

系統中的菜品資料很多的時候,如果在一個頁面中全部展示出來會顯得比較亂,不便於檢視,所以一般的系統中都會以分頁的方式來展示列表資料。

程式碼開發-梳理互動過程

在開發程式碼之前,需要梳理一下菜品分頁查詢時前端頁面和服務端的互動過程:

1、頁面(backend/page/food/list.html)傳送ajax請求,將分頁查詢引數(page、pageSize、name)提交到服務端,獲取分頁資料

2、頁面傳送請求,請求服務端進行圖片下載,用於頁面圖片展示

開發菜品資訊分頁查詢功能,其實就是在服務端編寫程式碼去處理前端頁面傳送的這2次請求即可。

@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> queryWrapper = new LambdaQueryWrapper<>();

  //新增過濾條件
  queryWrapper.like(!StringUtils.isEmpty(name), Dish::getName, name);

  //新增排序條件
  queryWrapper.orderByDesc(Dish::getUpdateTime);

  //進行分頁查詢
  dishService.page(pageInfo, queryWrapper);

  //物件拷貝
  BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");

  List<Dish> records = pageInfo.getRecords();
  List<DishDto> list=records.stream().map((item)->{
    DishDto dishDto=new DishDto();

    BeanUtils.copyProperties(item,dishDto);
    Long categoryId = item.getCategoryId();
    //根據id查分類物件
    Category category = categoryService.getById(categoryId);
    if(category!=null){
      String categoryName = category.getName();
      dishDto.setCategoryName(categoryName);
    }
    return dishDto;
  }).collect(Collectors.toList());

  dishDtoPage.setRecords(list);

  return R.success(dishDtoPage);
}

修改菜品

需求分析

在菜品管理列表頁面點選修改按鈕,跳轉到修改菜品頁面,在修改頁面回顯菜品相關資訊並進行修改,最後點選確定按鈕完成修改操作

程式碼開發-梳理互動過程

在開發程式碼之前,需要梳理一下修改菜品時前端頁面( add.html)和服務端的互動過程:

1、頁面傳送ajax請求,請求服務端獲取分類資料,用於菜品分類下拉框中資料展示

2、頁面傳送ajax請求,請求服務端,根據id查詢當前菜品資訊,用於菜品資訊回顯

  • DishController處理Get請求
//根據Id查詢菜品資訊與對應的口味資訊
@GetMapping("/{id}")
public R<DishDto> getById(@PathVariable Long id){
    DishDto dishDto = dishService.getByIdWithFlavor(id);
    return R.success(dishDto);
}
  • 在DishServiceImpl新增getByIdWithFlavor方法
@Override
@Transactional
public DishDto getByIdWithFlavor(Long id) {
    //查詢菜品基本資訊
    Dish dish = this.getById(id);

    DishDto dishDto=new DishDto();
    BeanUtils.copyProperties(dish,dishDto);

    //查詢菜品口味資訊
    LambdaQueryWrapper<DishFlavor> queryWrapper=new LambdaQueryWrapper<>();
    queryWrapper.eq(DishFlavor::getDishId,dish.getId());
    List<DishFlavor> list = dishFlavorService.list(queryWrapper);

    dishDto.setFlavors(list);

    return dishDto;
}

3、頁面傳送請求,請求服務端進行圖片下載,用於頁圖片回顯

4、點選儲存按鈕,頁面傳送ajax請求,將修改後的菜品相關資料以json形式提交到服務端

  • 在DishController新增put方法
//修改菜品
@PutMapping
public R<String> update(@RequestBody DishDto dishDto){
    dishService.updateWithFlavor(dishDto);
    return R.success("修改菜品成功");
}
  • 在DishServiceImpl新增updateWithFlavor方法
@Override
public void updateWithFlavor(DishDto dishDto) {
    //更新dish表基本資訊
    this.updateById(dishDto);

    //更新dish_flavor表資訊delete操作
    LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(DishFlavor::getDishId, dishDto.getId());
    dishFlavorService.remove(queryWrapper);

    //更新dish_flavor表資訊insert操作
    List<DishFlavor> flavors = dishDto.getFlavors();

    flavors = flavors.stream().map((item) -> {
        item.setDishId(dishDto.getId());
        return item;
    }).collect(Collectors.toList());

    dishFlavorService.saveBatch(flavors);
}

開發修改菜品功能,其實就是在服務端編寫程式碼去處理前端頁面傳送的這4次請求即可。

停售/起售菜品,刪除菜品

需求分析

在商品買賣過程中,商品停售,起售可以更加方便的讓使用者知道店家還有什麼型別的商品在賣。刪除方法也更方便的管理菜品

程式碼實現

在DishController新增sale方法與delete方法,通過陣列儲存ids,批量起售停售、刪除都能生效

//停售起售菜品
@PostMapping("/status/{status}")
public R<String> sale(@PathVariable int status,
                      String[] ids){
    for(String id: ids){
        Dish dish = dishService.getById(id);
        dish.setStatus(status);
        dishService.updateById(dish);
    }
    return R.success("修改成功");
}
//刪除菜品
@DeleteMapping
public R<String> delete(String[] ids){
    for (String id:ids) {
        dishService.removeById(id);
    }
    return R.success("刪除成功");
}