1. 程式人生 > 實用技巧 >Day08_課程圖片管理與分散式檔案系統

Day08_課程圖片管理與分散式檔案系統

1 FastDFS研究

參考連結:https://www.cnblogs.com/artwalker/p/13502534.html

2 上傳圖片開發

1.1.1 需求分析

現在很多系統都有上傳圖片/上傳檔案的需求,比如:上傳課程圖片、上傳課程資料、上傳使用者頭像等,為了提供系統的可重用性專門設立檔案系統服務承擔圖片/檔案的管理,檔案系統服務實現對檔案的上傳、刪除、查詢等功能進行管理。

各各子系統不再開發上傳檔案的請求,各各子系統通過檔案系統服務進行檔案的上傳、刪除等操作。檔案系統服務 最終會將檔案儲存到FastDSF檔案系統中。

下圖是各各子系統與檔案系統服務之間的關係:

下圖是課程管理中上傳圖片處理流程:

執行流程如下:

1、管理員進入教學管理前端,點選上傳圖片
2、圖片上傳至檔案系統服務,檔案系統請求fastDFS上傳檔案
3、檔案系統將檔案入庫,儲存到檔案系統服務資料庫中。
4、檔案系統服務向前端返回檔案上傳結果,如果成功則包括檔案的Url路徑。
5、課程管理前端請求課程管理進行儲存課程圖片資訊到課程資料庫。
6、課程管理服務將課程圖片儲存在課程資料庫。

1.1.2 建立檔案系統服務工程

匯入xc-service-base-filesystem.zip工程。

1)工程目錄結構

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>xc-framework-parent</artifactId>
        <groupId>com.xuecheng</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../xc-framework-parent/pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>xc-service-base-filesystem</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.xuecheng</groupId>
            <artifactId>xc-service-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.xuecheng</groupId>
            <artifactId>xc-framework-model</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.xuecheng</groupId>
            <artifactId>xc-framework-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>net.oschina.zcx7878</groupId>
            <artifactId>fastdfs-client-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
    </dependencies>

</project>

2)配置檔案

原測試程式中fastdfs-client.properties的配置資訊統一放在application.yml

server:
  port: 22100
spring:
  application:
    name: xc-service-base-filesystem
  #mongo配置
  data:
    mongodb:
      database: xc_fs
      uri:  mongodb://localhost:27017
  #SpringMVC上傳檔案配置
  servlet:
    multipart:
      #預設支援檔案上傳.
      enabled: true
      #支援檔案寫入磁碟.
      file-size-threshold: 0
      # 上傳檔案的臨時目錄
      location:
      # 最大支援檔案大小
      max-file-size: 1MB
      # 最大支援請求大小
      max-request-size: 30MB
xuecheng:
  fastdfs:
    connect_timeout_in_seconds: 5
    network_timeout_in_seconds: 30
    charset: UTF-8
    tracker_servers: 10.211.55.13:22122 #多個 trackerServer中間以逗號分隔

1.1.3 API介面

1.1.3.1 模型類

系統的檔案資訊(圖片、文件等小檔案的資訊)在mongodb中儲存,下邊是檔案資訊的模型類。

  1. 模型如下:
package com.xuecheng.framework.domain.filesystem;

import lombok.Data;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.Map;

/**
 * Created by mrt on 2018/2/5.
 */
@Data
@ToString
@Document(collection = "filesystem")
public class FileSystem {

    @Id
    private String fileId;
    //檔案請求路徑
    private String filePath;
    //檔案大小
    private long fileSize;
    //檔名稱
    private String fileName;
    //檔案型別
    private String fileType;
    //圖片寬度
    private int fileWidth;
    //圖片高度
    private int fileHeight;
    //使用者id,用於授權
    private String userId;
    //業務key
    private String businesskey;
    //業務標籤
    private String filetag;
    //檔案元資訊
    private Map metadata;

}

說明:

fileId:FastDFS返回的檔案ID。

filePath:請求FastDFS瀏覽檔案URL。

filetag:檔案標籤,由於檔案系統服務是公共服務,檔案系統服務會為使用檔案系統服務的子系統分配檔案標籤, 用於標識此檔案來自哪個系統。

businesskey:檔案系統服務為其它子系統提供的一個業務標識欄位,各子系統根據自己的需求去使用,比如:課程管理會在此欄位中儲存課程id用於標識該圖片屬於哪個課程。

metadata:檔案相關的元資訊。
  1. collection

在mongodb建立資料庫xc_fs(檔案系統資料庫),並建立集合 filesystem。(資料庫中已經有了)

1.1.3.2 Api介面

在api工程下建立com.xuecheng.api.filesystem包,

package com.xuecheng.api.filesystem;

import com.xuecheng.framework.domain.filesystem.response.UploadFileResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.multipart.MultipartFile;

@Api(value="檔案管理介面",description = "檔案管理介面,提供檔案的增、刪、改、查")
public interface FileSystemControllerApi {
    /**
     * 上傳檔案
     * @param multipartFile 檔案
     * @param filetag   檔案標籤
     * @param businesskey 業務key
     * @param metadata 元資訊,json格式
     * @return
     */
    @ApiOperation("上傳檔案介面")
    public UploadFileResult upload(MultipartFile multipartFile,
                                   String filetag,
                                   String businesskey,
                                   String metadata);
}
1.1.2.4 Service
package com.xuecheng.filesystem.service;

import com.alibaba.fastjson.JSON;
import com.xuecheng.filesystem.dao.FileSystemRepository;
import com.xuecheng.framework.domain.filesystem.FileSystem;
import com.xuecheng.framework.domain.filesystem.response.FileSystemCode;
import com.xuecheng.framework.domain.filesystem.response.UploadFileResult;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.model.response.CommonCode;
import org.apache.commons.lang3.StringUtils;
import org.csource.fastdfs.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.util.Map;

/**
 * @author Administrator
 * @version 1.0
 **/
@Service
public class FileSystemService {

    @Value("${xuecheng.fastdfs.tracker_servers}")
    String tracker_servers;
    @Value("${xuecheng.fastdfs.connect_timeout_in_seconds}")
    int connect_timeout_in_seconds;
    @Value("${xuecheng.fastdfs.network_timeout_in_seconds}")
    int network_timeout_in_seconds;
    @Value("${xuecheng.fastdfs.charset}")
    String charset;

    @Autowired
    FileSystemRepository fileSystemRepository;

    //上傳檔案
    public UploadFileResult upload( MultipartFile multipartFile,
                                   String filetag,
                                   String businesskey,
                                   String metadata){
        if(multipartFile ==null){
            ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_FILEISNULL);
        }
        //第一步:將檔案上傳到fastDFS中,得到一個檔案id
        String fileId = fdfs_upload(multipartFile);
        if(StringUtils.isEmpty(fileId)){
            ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_SERVERFAIL);
        }
        //第二步:將檔案id及其它檔案資訊儲存到mongodb中。
        FileSystem fileSystem = new FileSystem();
        fileSystem.setFileId(fileId);
        fileSystem.setFilePath(fileId);
        fileSystem.setFiletag(filetag);
        fileSystem.setBusinesskey(businesskey);
        fileSystem.setFileName(multipartFile.getOriginalFilename());
        fileSystem.setFileType(multipartFile.getContentType());
        if(StringUtils.isNotEmpty(metadata)){
            try {
                Map map = JSON.parseObject(metadata, Map.class);
                fileSystem.setMetadata(map);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        fileSystemRepository.save(fileSystem);
        return new UploadFileResult(CommonCode.SUCCESS,fileSystem);
    }

    //上傳檔案到fastDFS

    /**
     *
     * @param multipartFile 檔案
     * @return 檔案id
     */
    private String fdfs_upload(MultipartFile multipartFile){
         //初始化fastDFS的環境
         initFdfsConfig();
         //建立trackerClient
        TrackerClient trackerClient = new TrackerClient();
        try {
            TrackerServer trackerServer = trackerClient.getConnection();
            //得到storage伺服器

            StorageServer storeStorage = trackerClient.getStoreStorage(trackerServer);
            //建立storageClient來上傳檔案
            StorageClient1 storageClient1 = new StorageClient1(trackerServer,storeStorage);
            //上傳檔案
            //得到檔案位元組
            byte[] bytes = multipartFile.getBytes();
            //得到檔案的原始名稱
            String originalFilename = multipartFile.getOriginalFilename();
            //得到副檔名
            String ext = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
            String fileId = storageClient1.upload_file1(bytes, ext, null);
            return fileId;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    //初始化fastDFS環境
    private void initFdfsConfig(){
        //初始化tracker服務地址(多個tracker中間以半形逗號分隔)
        try {
            ClientGlobal.initByTrackers(tracker_servers);
            ClientGlobal.setG_charset(charset);
            ClientGlobal.setG_network_timeout(network_timeout_in_seconds);
            ClientGlobal.setG_connect_timeout(connect_timeout_in_seconds);
        } catch (Exception e) {
            e.printStackTrace();
            //丟擲異常
            ExceptionCast.cast(FileSystemCode.FS_INITFDFSERROR);
        }
    }
}
1.1.2.5 Controller
package com.xuecheng.filesystem.controller;

import com.xuecheng.api.filesystem.FileSystemControllerApi;
import com.xuecheng.filesystem.service.FileSystemService;
import com.xuecheng.framework.domain.filesystem.response.UploadFileResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author Administrator
 * @version 1.0
 **/
@RestController
@RequestMapping("/filesystem")
public class FileSystemController implements FileSystemControllerApi {
    @Autowired
    FileSystemService fileSystemService;


    @Override
    @PostMapping("/upload")
    public UploadFileResult upload(MultipartFile multipartFile, String filetag, String businesskey, String metadata) {

        return fileSystemService.upload(multipartFile, filetag, businesskey, metadata);
    }
}
1.1.2.6 測試

使用swagger-ui或postman進行測試。

下圖是使用swagger-ui進行測試的介面:

1.1.3 上傳課程圖片前端

1.1.3.1 需求

上傳圖片介面如下圖:

點選“加號”上傳圖片,圖片上傳成功自動顯示;點選“刪除”將刪除圖片。

1.1.3.2 頁面

使用Element-UI的Upload上傳元件實現上邊的效果。

  1. template (course_picture.vue)
<template>
  <div>
    <el-upload
      action="/api/filesystem/upload"
      list-type="picture-card"
      :before-upload="setuploaddata"
      :on-success="handleSuccess"
      :file-list="fileList"
      :limit="picmax"
      :on-exceed="rejectupload"
      :before-remove="handleRemove"
      :data="uploadval">
      <i class="el-icon-plus"></i>
    </el-upload>
  </div>
</template>

el-upload引數說明:

action:必選引數,上傳的地址
list-type:檔案列表的型別(text/picture/picture-card)
before-upload:上傳前執行鉤子方法 ,function(file)
on-success:上傳成功 執行的鉤子方法 ,function(response, file, fileList)
on-error:上傳失敗的鉤子方法,function(err, file, fileList)
on-remove:檔案刪除的鉤子方法,function(file, fileList)
file-list:檔案列表,此列表為上傳成功的檔案
limit:最大允許上傳個數
on-exceed:檔案超出個數限制時的鉤子,方法為:function(files, fileList)
data:提交上傳的額外引數,需要封裝為json物件,最終提交給服務端為key/value串

2)資料模型

<script>
  import * as sysConfig from '@/../config/sysConfig';
  import * as courseApi from '../../api/course';
  import utilApi from '../../../../common/utils';
  import * as systemApi from '../../../../base/api/system';
  export default {
    data() {
      return {
        picmax:1,//最大上傳檔案的數量
        courseid:'',
        dialogImageUrl: '',
        dialogVisible: false,
        fileList:[],
        uploadval:{filetag:"course"},//上傳提交的額外的資料,將uploadval轉成key/value提交給伺服器
        imgUrl:sysConfig.imgUrl
      }
    },
    methods: {
      //超出檔案上傳個數提示資訊
      rejectupload(){
        this.$message.error("最多上傳"+this.picmax+"個圖片");
      },
      //在上傳前設定上傳請求的資料
      setuploaddata(){

      },
      //刪除圖片
      handleRemove(file, fileList) {
        console.log(file)
        //呼叫服務端去刪除課程圖片資訊,如果返回false,前端停止刪除
        //非同步呼叫
        return new Promise((resolve,rejct)=>{
          courseApi.deleteCoursePic(this.courseid).then(res=>{
            if(res.success){

                //成功
              resolve()
            }else{
              this.$message.error("刪除失敗");
                //失敗
              rejct()
            }

          })
        })

      },
      //上傳成功的鉤子方法
      handleSuccess(response, file, fileList){
        console.log(response)
//        alert('上傳成功')
        //呼叫課程管理的儲存圖片介面,將圖片資訊儲存到課程管理資料庫course_pic中
        //從response得到新的圖片檔案的地址
        if(response.success){
          let fileId = response.fileSystem.fileId;
          courseApi.addCoursePic(this.courseid,fileId).then(res=>{
              if(res.success){
                  this.$message.success("上傳圖片")
              }else{
                this.$message.error(res.message)
              }

          })
        }

      },
      //上傳失敗執行的鉤子方法
      handleError(err, file, fileList){
        this.$message.error('上傳失敗');
        //清空檔案佇列
        this.fileList = []
      },
      //promise 有三種狀態:
      //進行中pending
      //執行成功 resolve
      //執行失敗 reject
      testPromise(i){

          return new Promise((resolve,reject)=>{
              if(i<2){
                  //成功了
                resolve('成功了');
              }else{
                  //失敗了
                reject('失敗了');
              }

          })
      }
    },
    mounted(){
      //課程id
      this.courseid = this.$route.params.courseid;
      //查詢課程
      courseApi.findCoursePicList(this.courseid).then(res=>{
          if(res && res.pic){
              let imgUrl = this.imgUrl+res.pic;
              //將圖片地址設定到
            this.fileList.push({name:'pic',url:imgUrl,fileId:res.pic})
          }

      })
    }
  }
</script>
1.1.3.3 測試

點選“加號”測試上傳圖片。

3 儲存課程圖片

1.2.1 需求分析

圖片上傳到檔案系統後,其它子系統如果想使用圖片可以引用圖片的地址,課程管理模組使用圖片的方式是將圖片 地址儲存到課程資料庫中。

業務流程如下:

1、上傳圖片到檔案系統服務
2、儲存圖片地址到課程管理服務
3、在course_pic儲存圖片成功後方可查詢課程圖片資訊

在課程管理服務建立儲存課程與圖片對應關係的表 course_pic。

通過查詢course_pic表資料則查詢到某課程的圖片資訊。

1.2.2 課程管理服務端開發(在xc-service-manage-course下)

1.2.2.1 API

課程管理需要使用圖片,則在課程管理服務中提供儲存課程圖片的api。

@Api(value = "課程管理介面", description = "課程管理介面,提供課程的增、刪、改、查")
public interface CourseControllerApi {
    @ApiOperation("新增課程圖片")
    public ResponseResult addCoursePic(String courseId, String pic);
}
1.2.2.2 Dao

模型:

package com.xuecheng.framework.domain.course;

import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import java.io.Serializable;

/**
 * Created by admin on 2018/2/10.
 */
@Data
@ToString
@Entity
@Table(name="course_pic")
@GenericGenerator(name = "jpa-assigned", strategy = "assigned")
public class CoursePic implements Serializable {
    private static final long serialVersionUID = -916357110051689486L;

    @Id
    @GeneratedValue(generator = "jpa-assigned")
    private String courseid;
    private String pic;

}

Dao如下:

package com.xuecheng.filesystem.dao;

import com.xuecheng.framework.domain.course.CoursePic;

/**
 * @author HackerStar
 * @create 2020-08-14 16:43
 */
public interface CoursePicRepository extends JpaRepository<CoursePic, String> {
}

1.2.3.3 Service
//新增課程圖片
    @Transactional
    public ResponseResult saveCoursePic(String courseId, String pic) {
        //查詢課程圖片
        Optional<CoursePic> picOptional = coursePicRepository.findById(courseId);
        CoursePic coursePic = null;
        if (picOptional.isPresent()) {
            coursePic = picOptional.get();
        }
        //沒有課程圖片則新建物件
        if (coursePic == null) {
            coursePic = new CoursePic();
        }
        coursePic.setCourseid(courseId);
        coursePic.setPic(pic);
        //儲存課程圖片
        coursePicRepository.save(coursePic);
        return new ResponseResult(CommonCode.SUCCESS);
    }
1.2.3.4 Controller
		@Override
    public ResponseResult addCoursePic(String courseId, String pic) {
        //儲存課程圖片
        return courseService.saveCoursePic(courseId,pic);
    }

1.2.4 前端開發

前端需要在上傳圖片成功後儲存課程圖片資訊。

1.2.4.1 Api方法 (course.js)
//新增課程圖片 
export const addCoursePic = (courseId, pic) => {
  return http.requestPost(apiUrl + '/course/coursepic/add?courseId=' + courseId + "&pic=" + pic)
}
1.2.4.2 頁面

1)新增上傳成功的鉤子 :on-success="handleSuccess"

<el-upload
      action="/api/filesystem/upload"
      list-type="picture-card"
      :before-upload="setuploaddata"
      :on-success="handleSuccess"
      :file-list="fileList"
      :limit="picmax"
      :on-exceed="rejectupload"
      :before-remove="handleRemove"
      :data="uploadval">
      <i class="el-icon-plus"></i>
</el-upload>

2)在鉤子方法 中儲存課程圖片資訊

如果儲存圖片失敗則上傳失敗,清除檔案列表。

//上傳成功的鉤子方法
      handleSuccess(response, file, fileList){
        console.log(response)
//        alert('上傳成功')
        //呼叫課程管理的儲存圖片介面,將圖片資訊儲存到課程管理資料庫course_pic中
        //從response得到新的圖片檔案的地址
        if(response.success){
          let fileId = response.fileSystem.fileId;
          courseApi.addCoursePic(this.courseid,fileId).then(res=>{
              if(res.success){
                  this.$message.success("上傳圖片成功")
              }else{
                this.$message.error(res.message)
              }

          })
        }
      },
//上傳失敗執行的鉤子方法
      handleError(err, file, fileList){
        this.$message.error('上傳失敗');
        //清空檔案佇列
        this.fileList = []
      },

4 圖片查詢

1.3.1 需求分析

課程圖片上傳成功,再次進入課程上傳頁面應該顯示出來已上傳的圖片。

1.3.2 API

在課程管理服務定義查詢方法

@Api(value = "課程管理介面", description = "課程管理介面,提供課程的增、刪、改、查")
public interface CourseControllerApi {
    @ApiOperation("獲取課程基礎資訊")
    public CoursePic findCoursePic(String courseId);
}

1.3.2 課程管理服務開發

1.3.2.1 Dao

使用CoursePicRepository即可,無需再開發。

1.3.2.2 Service

根據課程id查詢課程圖片

		//查詢課程圖片
    public CoursePic findCoursePic(String courseId) {
        //查詢課程圖片
        Optional<CoursePic> picOptional = coursePicRepository.findById(courseId);
        if(picOptional.isPresent()){
            CoursePic coursePic = picOptional.get();
            return coursePic;
        }
        return null;
    }
1.3.2.3 Controller
		@Override
    @GetMapping("/coursepic/list/{courseId}")
    public CoursePic findCoursePic(@PathVariable("courseId") String courseId) {
        return courseService.findCoursePic(courseId);
    }

1.3.3 前端開發

1.3.3.1 API方法
//查詢課程圖片
export const findCoursePicList = courseId => {
  return http.requestQuickGet(apiUrl + '/course/coursepic/list/' + courseId)
}
1.3.3.2 頁面

在課程圖片頁面的mounted鉤子方法中查詢課程圖片資訊,並將圖片地址賦值給資料物件。

1、定義圖片查詢方法

//查詢圖片
      list() {
        courseApi.findCoursePicList(this.courseid).then((res) => {
          console.log(res);
          if (res.pic) {
            let name = '圖片';
            let url = this.imgUrl + res.pic;
            let fileId = res.courseid; //先清空檔案列表,再將圖片放入檔案列表
            this.fileList = [];
            this.fileList.push({name: name, url: url, fileId: fileId});
          }
          console.log(this.fileList);
        });
      },

2)mounted鉤子方法

在mounted鉤子方法中呼叫服務端查詢檔案列表並繫結到資料物件。

mounted() {
      //課程id
      this.courseid = this.$route.params.courseid;
      //查詢圖片 
      this.list();
      //查詢課程
      courseApi.findCoursePicList(this.courseid).then(res => {
        if (res && res.pic) {
          let imgUrl = this.imgUrl + res.pic;
          //將圖片地址設定到
          this.fileList.push({name: 'pic', url: imgUrl, fileId: res.pic})
        }
      })
    }
1.3.3.3 測試

測試流程:

1、上傳圖片成功
2、進入上傳圖片頁面,點到其他資訊,再返回,觀察圖片是否顯示

資料給的程式碼前端程式碼有問題,上傳圖片時,總是顯示沒有上傳圖片,匯入課上程式碼,是有用的。

5 課程圖片刪除

1.4.1 需求分析

課程圖片上傳成功後,可以重新上傳,方法是先刪除現有圖片再上傳新圖片。

注意:此刪除只刪除課程資料庫的課程圖片資訊,不去刪除檔案資料庫的檔案資訊及檔案系統伺服器上的檔案,由於課程圖片來源於該使用者的檔案庫,所以此圖片可能存在多個地方共用的情況,所以要刪除檔案系統中的檔案,需要到圖片庫由使用者確認後再刪除。

1.4.2 課程管理服務端開發

1.4.2.1 API
@Api(value = "課程管理介面", description = "課程管理介面,提供課程的增、刪、改、查")
public interface CourseControllerApi {
    @ApiOperation("刪除課程圖片")
    public ResponseResult deleteCoursePic(String courseId);
}
1.4.2.2 Dao

CoursePicRepository父類提供的delete方法沒有返回值,無法知道是否刪除成功,這裡我們在CoursePicRepository下邊自定義方法:

//刪除成功返回1否則返回0 
long deleteByCourseid(String courseid);
1.4.2.3 Service
//刪除課程圖片
    @Transactional
    public ResponseResult deleteCoursePic(String courseId) {
        //執行刪除,返回1表示刪除成功,返回0表示刪除失敗
        long result = coursePicRepository.deleteByCourseid(courseId);
        if (result > 0) {
            return new ResponseResult(CommonCode.SUCCESS);
        }
        return new ResponseResult(CommonCode.FAIL);
    }
1.4.2.4 Controller
		@Override
    @DeleteMapping("/coursepic/delete")
    public ResponseResult deleteCoursePic(@RequestParam("courseId") String courseId) {
        return courseService.deleteCoursePic(courseId);
    }

1.4.3 前端開發

1.4.3.1 API 呼叫
//刪除課程圖片
export const deleteCoursePic = courseId => {
  return http.requestDelete(apiUrl + '/course/coursepic/delete?courseId=' + courseId)
}
1.4.3.2 頁面測試

1)before-remove鉤子方法

在upload元件的before-remove鉤子方法中實現刪除動作。

before-remove說明:刪除檔案之前的鉤子,引數為上傳的檔案和檔案列表,若返回 false 或者返回 Promise 且被 reject,則停止刪除。

定義handleRemove方法進行測試:

handleRemove 返回true則刪除頁面的圖片,返回false則停止刪除頁面的圖片。

//刪除圖片
      handleRemove(file, fileList) {
        console.log(file)
        alert('刪除成功')
				return true;
      },
1.4.3.3 promise非同步呼叫

在handleRemove方法呼叫刪除圖片的api方法,刪除成功時return true,刪除失敗時return false;

//刪除圖片
      handleRemove(file, fileList) {
        console.log(file)
         courseApi.deleteCoursePic('1').then((res) => {
            if(res.success){
              this.$message.success('刪除成功');
              return true;
            }else{
              this.$message.error(res.message);
         	    return false;
            }
          });
      },

在上邊程式碼中將提交的課程id故意寫錯,按照我們預期應該是刪除失敗,而測試結果卻是圖片在頁面上刪除成功。

問題原因:

通過查詢deleteCoursePic方法的底層程式碼,deleteCoursePic最終返回一個promise物件。
Promise是ES6提供的用於非同步處理的物件,因為axios提交是非同步提交,這裡使用promise作為返回值。

Promise的使用方法如下:

Promise物件在處理過程中有三種狀態:
pending:進行中
resolved:操作成功
rejected: 操作失敗

Promise的構建方法如下:

const promise = new Promise(function(resolve,reject){
		//...TODO...
		if(操作成功){
			resolve(value);
		}else{
			reject(error);
		}
})

上邊的構造方法function(resolve,reject)執行流程如下:

1)方法執行一些業務邏輯。
2)如果操作成功將Promise的狀態由pending變為resolved,並將操作結果傳出去
3)如果操作失敗會將promise的狀態由pending變為rejected,並將失敗結果傳出去。

上邊說的操作成功將操作結果傳給誰了呢?操作失敗將失敗結果傳給誰了呢?

通過promise的then、catch來指定

promise.then(function (result) { 
	console.log('操作成功:' + result); 
});
promise.catch(function (reason) {
	console.log('操作失敗:' + reason);
});

例子如下:

1、定義一個方法,返回promise物件

testpromise(i){
	return new Promise((resolve,reject)=>{
	if(i % 2==0){
		resolve('成功了')
	}else{
		reject('拒絕了')
	})
}

2、呼叫此方法

向方法傳入偶數、奇數進行測試。

this.testpromise(3).then(res=>{//在then中對成功結果進行處理
	alert(res)
}).catch(res=>{//在catch中對操作失敗結果進行處理
	alert(res)
})

3、最終將handleRemove方法修改如下

handleRemove方法返回promise物件,當刪除成功則resolve,刪除失敗則reject。

//刪除圖片
      handleRemove(file, fileList) {
        console.log(file)
        return new Promise((resolve,reject)=>{
          courseApi.deleteCoursePic(this.courseid).then(res=>{
            if(res.success){
              //成功
							this.$message.success("刪除成功");
              resolve();
            }else{
              this.$message.error("刪除失敗");
              //失敗
             reject();
            }
          })
        })
      },
1.4.3.4 測試