學成在線筆記六:分佈式文件系統

FastDFS相關

FastDFS簡介

參考:http://www.xushuai.fun/2018/12/22/FastDFS簡介/

FastDFS安裝及配置

參考:http://www.xushuai.fun/2018/12/22/FastDFS安裝/

文件上傳微服務

需求分析

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

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

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

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

執行流程如下:

  1. 管理員進入教學管理前端,點擊上傳圖片。
  2. 圖片上傳至文件系統服務,文件系統請求fastDFS上傳文件。
  3. 文件系統將文件入庫,存儲到文件系統服務數據庫中。
  4. 文件系統服務向前端返回文件上傳結果,如果成功則包括文件的Url路徑。
  5. 課程管理前端請求課程管理進行保存課程圖片信息到課程數據庫。
  6. 課程管理服務將課程圖片保存在課程數據庫。

工程導入(省略)

我這裏用的FastDFS客戶端不是教程裏面的這個,我使用的是

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-D6wjyYEt-1592229881165)(https://images.xushuai.fun/18-12-24/42536809.jpg)]

依賴:

        <dependency>
            <groupId>com.github.tobato</groupId>
            <artifactId>fastdfs-client</artifactId>
            <version>1.26.1-RELEASE</version>
        </dependency>

配置:

fdfs:
  so-timeout: 1501
  connect-timeout: 601
  thumb-image:             #縮略圖生成參數
    width: 150
    height: 150
  tracker-list:            #TrackerList參數,支持多個
    - 192.168.136.110:22122 # 以實際你的機器爲準

Java配置類:配置了過後,會自動幫你注入用於操作文件的API類

package com.xuecheng.filesystem.config;

import com.github.tobato.fastdfs.FdfsClientConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.support.RegistrationPolicy;

/**
 * 導入FastDFS-Client組件
 * 
 * @author tobato
 *
 */
@Configuration
@Import(FdfsClientConfig.class)
// 解決jmx重複註冊bean的問題
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class ComponetImport {
    // 導入依賴組件
}

上傳實現

FileSystemControllerApi

package com.xuecheng.api.filesystem;

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

@Api(value = "文件管理服務", description = "文件管理,提供文件的上傳、下載等操作")
public interface FileSystemControllerApi {

    /**
     * 文件上傳
     *
     * @param file        文件
     * @param filetag     文件標籤
     * @param businesskey 業務key
     * @param metadata    元數據, JSON格式
     * @return UploadFileResult 上傳結果
     */
    UploadFileResult upload(MultipartFile file,
                            String filetag,
                            String businesskey,
                            String metadata);

}

FileSystemController

package com.xuecheng.filesystem.controller;

import com.xuecheng.api.filesystem.FileSystemControllerApi;
import com.xuecheng.filesystem.service.FileSystemService;
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.model.response.CommonCode;
import com.xuecheng.framework.web.BaseController;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("filesystem")
public class FileSystemController extends BaseController implements FileSystemControllerApi {

    @Autowired
    private FileSystemService fileSystemService;

    /**
     * 文件上傳
     *
     * @param file        文件
     * @param filetag     文件標籤
     * @param businesskey 業務key
     * @param metadata    元數據, JSON格式
     * @return UploadFileResult 上傳結果
     */
    @Override
    @PostMapping("upload")
    public UploadFileResult upload(@RequestParam("file") MultipartFile file,
                                   @RequestParam("filetag")String filetag,
                                   @RequestParam(value = "businesskey", required = false)String businesskey,
                                   @RequestParam(value = "metadata", required = false)String metadata) {
        FileSystem upload = fileSystemService.upload(file, filetag, businesskey, metadata);
        isNullOrEmpty(upload, FileSystemCode.FS_UPLOADFILE_SERVERFAIL);
        return new UploadFileResult(CommonCode.SUCCESS, upload);
    }
}

FileSystemService

package com.xuecheng.filesystem.service;

import com.alibaba.fastjson.JSON;
import com.github.tobato.fastdfs.domain.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
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.exception.ExceptionCast;
import com.xuecheng.framework.service.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Map;

@Slf4j
@Service
public class FileSystemService extends BaseService {

    @Autowired
    private FileSystemRepository fileSystemRepository;

    @Autowired
    private FastFileStorageClient fastFileStorageClient;

    /**
     * 文件上傳
     *
     * @param file        文件
     * @param filetag     文件標籤
     * @param businesskey 業務key
     * @param metadata    元數據, JSON格式
     * @return UploadFileResult 上傳結果
     */
    public FileSystem upload(MultipartFile file, String filetag, String businesskey, String metadata) {
        // 上傳文件
        String fileId = uploadFile(file);
        // 創建文件信息對象
        FileSystem fileSystem = new FileSystem();
        fileSystem.setFileId(fileId);
        fileSystem.setFilePath(fileId);
        fileSystem.setBusinesskey(businesskey);
        fileSystem.setFiletag(filetag);
        if (StringUtils.isNotBlank(metadata)) {
            try {
                Map metadataMap = JSON.parseObject(metadata, Map.class);
                fileSystem.setMetadata(metadataMap);
            } catch (Exception e) {
                log.error("[文件上傳] 從JSON獲取文件元數據失敗, metadata = {}", metadata);
                ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_METAERROR);
            }
        }
        fileSystem.setFileName(file.getOriginalFilename());
        fileSystem.setFileSize(file.getSize());
        fileSystem.setFileType(file.getContentType());
        // 保存到文件數據庫
        FileSystem save = fileSystemRepository.save(fileSystem);

        return save;
    }

    /**
     * 文件上傳到FastDFS
     *
     * @param file 文件
     * @return 文件ID
     */
    private String uploadFile(MultipartFile file) {
        try {
            if (file.isEmpty()) {
                ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_FILEISNULL);
            }
            // 獲取文件後綴名
            String filename = file.getOriginalFilename();
            assert filename != null;
            String suffix = filename.substring(filename.indexOf(".") + 1);

            // 上傳文件
            StorePath storePath = fastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(), suffix, null);
            // 返回文件ID
            return storePath.getFullPath();
        } catch (IOException e) {
            log.error("讀取文件內容發生IO異常.  e = {}", e);
            ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_SERVERFAIL);
            return null;
        }
    }
}

FileSystemRepository

package com.xuecheng.filesystem.dao;

import com.xuecheng.framework.domain.filesystem.FileSystem;
import org.springframework.data.mongodb.repository.MongoRepository;

public interface FileSystemRepository extends MongoRepository<FileSystem, String> {
}

測試

訪問圖片地址:http://192.168.136.110/group1/M00/00/00/wKiIbl1yH6SARrBnAAB03ZQYayU081.jpg

OK!成功上傳和訪問到圖片。

課程圖片管理

完成課程圖片的增刪改查。

後端

CoursePicControllerApi

package com.xuecheng.api.course;

import com.xuecheng.framework.domain.course.CoursePic;
import com.xuecheng.framework.model.response.ResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@Api(value = "課程圖片管理接口", description = "課程圖片管理接口,提供圖片的增刪改查")
public interface CoursePicControllerApi {

    @ApiOperation("新增課程圖片")
    CoursePic saveCoursePic(String courseId, String pic);

    @ApiOperation("查詢課程圖片")
    CoursePic findById(String courseId);

    @ApiOperation("刪除課程圖片")
    ResponseResult deleteById(String courseId);

}

CoursePicController

package com.xuecheng.manage_course.controller;

import com.xuecheng.api.course.CoursePicControllerApi;
import com.xuecheng.framework.domain.course.CoursePic;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.framework.web.BaseController;
import com.xuecheng.manage_course.service.CoursePicService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("course/coursepic")
public class CoursePicController extends BaseController implements CoursePicControllerApi {

    @Autowired
    private CoursePicService coursePicService;

    /**
     * 新增/更新課程圖片
     *
     * @param courseId 課程ID
     * @param pic      圖片ID
     * @return CoursePic
     */
    @Override
    @PostMapping("add")
    public CoursePic saveCoursePic(@RequestParam String courseId,
                                   @RequestParam String pic) {
        isNullOrEmpty(courseId, CommonCode.PARAMS_ERROR);
        isNullOrEmpty(pic, CommonCode.PARAMS_ERROR);
        return coursePicService.save(courseId, pic);
    }

    /**
     * 查詢課程圖片
     *
     * @param courseId 課程ID
     * @return CoursePic
     */
    @Override
    @GetMapping("list/{courseId}")
    public CoursePic findById(@PathVariable String courseId) {
        isNullOrEmpty(courseId, CommonCode.PARAMS_ERROR);
        return coursePicService.findById(courseId);
    }

    /**
     * 查詢課程圖片
     *
     * @param courseId 課程ID
     * @return CoursePic
     */
    @Override
    @DeleteMapping("delete")
    public ResponseResult deleteById(@RequestParam String courseId) {
        isNullOrEmpty(courseId, CommonCode.PARAMS_ERROR);
        coursePicService.deleteById(courseId);
        return ResponseResult.SUCCESS();
    }
}

CoursePicService

package com.xuecheng.manage_course.service;

import com.xuecheng.framework.domain.course.CoursePic;
import com.xuecheng.framework.domain.course.response.CourseCode;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.service.BaseService;
import com.xuecheng.manage_course.dao.CoursePicRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Slf4j
@Service
public class CoursePicService extends BaseService {

    @Autowired
    private CoursePicRepository coursePicRepository;

    /**
     * 新增/更新課程圖片
     *
     * @param courseId 課程ID
     * @param pic      圖片ID
     * @return CoursePic
     */
    public CoursePic save(String courseId, String pic) {
        CoursePic result = null;
        // 查詢課程圖片
        Optional<CoursePic> optionalCoursePic = coursePicRepository.findById(courseId);
        if (optionalCoursePic.isPresent()) {
            // 更新課程圖片
            result = optionalCoursePic.get();
            result.setPic(pic);
        } else {
            // 新增課程圖片
            result = new CoursePic();
            result.setCourseid(courseId);
            result.setPic(pic);
        }
        return coursePicRepository.save(result);
    }

    /**
     * 查詢課程圖片
     *
     * @param courseId 課程ID
     * @return CoursePic
     */
    public CoursePic findById(String courseId) {
        // 查詢課程圖片
        Optional<CoursePic> optionalCoursePic = coursePicRepository.findById(courseId);
        if (!optionalCoursePic.isPresent()) {
            ExceptionCast.cast(CourseCode.COURSE_NOT_EXIST);
        }
        return optionalCoursePic.get();
    }

    /**
     * 刪除課程圖片
     *
     * @param courseId 課程ID
     * @return
     */
    public void deleteById(String courseId) {
        coursePicRepository.deleteById(courseId);
    }
}

CoursePicRepository

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;

}

前端

前端沒有什麼需要修改的地方。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章