1. 程式人生 > >二. fastDFS:springboot 整合fastDFS

二. fastDFS:springboot 整合fastDFS

一.引入官方客戶端依賴

<!--FastDFS-->
<dependency>
    <groupId>net.oschina.zcx7878</groupId>
    <artifactId>fastdfs-client-java</artifactId>
    <version>1.27.0.0</version>
</dependency>
<!--pool-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.4.2</version>
</dependency>

-- 以上只列舉相關的依賴,其它依賴自行新增

二 . 配置檔案

#######################FastDFS#######################
#檔案伺服器地址
file_server_addr=192.168.17.128:80
# 最大連線數 併發量較大的話可加大該連線數
max_storage_connection=10
#超時配置
fastdfs.connect_timeout_in_seconds=100000
fastdfs.network_timeout_in_seconds=300000
fastdfs.charset=UTF-8
#token 防盜鏈功能
fastdfs.http_anti_steal_token=true
#金鑰,預設為FASTDFS1234567890
fastdfs.http_secret_key=FASTDFS1234567890
# TrackerServer port
fastdfs.http_tracker_http_port=6666
## Tracker Server, if more than one, separate with ","
fastdfs.tracker_servers=192.168.17.128:22122
#######################FastDFS#######################

-- FastDFS的配置檔案只支援properties型別的,可以整合放入application.properties裡面,但是不能放入yml型別的裡面

三.服務端程式碼編寫

 0.啟動類配置

SpringbootFastdfsApplication.class
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@EnableAutoConfiguration
@MapperScan(value = "com.vesus.springbootfastdfs.mapper")
@Configuration
@PropertySource(value = "classpath:fastdfs.properties",encoding = "utf-8")
public class SpringbootFastdfsApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootFastdfsApplication.class, args);
    }

    /**
     * war 包部署
     * @param application
     * @return
     */
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
    {
        return application.sources(SpringbootFastdfsApplication.class);
    }
}

 -- 

 1.編寫排程tracker池

TrackerServerPool.class
public class TrackerServerPool {

    private static Logger logger= LoggerFactory.getLogger(TrackerServerPool.class);
    /***
     * 配置檔案路徑
     */
    private static final String FASTDFS_CONFIG_PATH = "fastdfs.properties";
    /***
     * 最大連線數
     */
    @Value("${max_storage_connection}")
    private static int maxStorageConnection ;
    /***
     * TrackerServer 物件池
     */
    private static GenericObjectPool<TrackerServer> pool ;
    /***
     * 無參建構函式
     */
    private TrackerServerPool(){}
    /***
     * 獲取TrackerServer連線池
     * @return
     * @throws Exception
     */
    public static synchronized GenericObjectPool<TrackerServer> getObjectPool() throws  Exception{
        if (pool ==null){
            //載入配置檔案
            ClientGlobal.initByProperties(FASTDFS_CONFIG_PATH);
            //pool配置,設定最大值和最小值
            GenericObjectPoolConfig config = new GenericObjectPoolConfig();
            config.setMinIdle(2);
            if (maxStorageConnection>0){
                config.setMaxIdle(maxStorageConnection);
            }

            pool = new GenericObjectPool<>(new TrackerServerFactory(),config) ;
        }
        return pool ;
    }
    /***
     * 獲取TrackerServer
     * @return
     * @throws Exception
     */
    public static TrackerServer borrowObject()throws Exception{
        TrackerServer trackerServer = null;
        trackerServer = getObjectPool().borrowObject() ;
        return  trackerServer ;
    }
    /***
     * 回收 TrackerServer
     * @param server
     * @throws Exception
     */
    public static void recycleObject(TrackerServer server) throws Exception{
        getObjectPool().returnObject(server);
    }
}

2.fastdfs排程Tracker工廠

TrackerServerFactory.class
public class TrackerServerFactory extends BasePooledObjectFactory<TrackerServer> {

    public TrackerServer create() throws Exception {
        //例項化TrackerClient
        TrackerClient client = new TrackerClient();
        //獲取TrackerServer
        TrackerServer trackerServer = client.getConnection();
        return trackerServer;
    }
    public PooledObject<TrackerServer> wrap(TrackerServer server) {
        return new DefaultPooledObject<TrackerServer>(server);
    }
}

3.編寫fastdfs操作客戶端

FastDFSClient.class
@Component
public class FastDFSClient {

    /**
     * 路徑分隔符
     */
    public static final String SEPARATOR = "/";
    /**
     * Point
     */
    public static final String POINT = ".";
    /**
     * 檔案型別
     */
    public static final Map<String, String> EXT_MAPS = new HashMap<>();

    /**
     * 日誌
     */
    private static Logger logger = LoggerFactory.getLogger(FastDFSClient.class);
    /**
     * 檔名稱
     */
    private static final String FILENAME = "filename";
    /**
     * 檔案最大的大小
     */
    private int maxFileSize = 500 * 1024 * 1024;

    public FastDFSClient(){
        initExt();
    }

    private void initExt(){
        //圖片
        EXT_MAPS.put("png", "image/png");
        EXT_MAPS.put("gif", "image/gif");
        EXT_MAPS.put("bmp", "image/bmp");
        EXT_MAPS.put("ico", "image/x-ico");
        EXT_MAPS.put("jpeg", "image/jpeg");
        EXT_MAPS.put("jpg", "image/jpeg");
        //壓縮檔案
        EXT_MAPS.put("zip", "application/zip");
        EXT_MAPS.put("rar", "application/x-rar");
        //word,excel,ppt
        EXT_MAPS.put("pdf", "application/pdf");
        EXT_MAPS.put("ppt", "application/vnd.ms-powerpoint");
        EXT_MAPS.put("xls", "application/vnd.ms-excel");
        EXT_MAPS.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        EXT_MAPS.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
        EXT_MAPS.put("doc", "application/msword");
        EXT_MAPS.put("doc", "application/wps-office.doc");
        EXT_MAPS.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        EXT_MAPS.put("txt", "text/plain");
        //視訊、音訊
        EXT_MAPS.put("mp4", "video/mp4");
        EXT_MAPS.put("flv", "video/x-flv");
    }

    /***
     * 獲取檔案最大值
     * @return
     */
    public int getMaxFileSize(){
        return maxFileSize ;
    }

    /***
     * 設定檔案的最大值
     * @param value
     */
    public void setMaxFileSize(int value){
        this.maxFileSize = value ;
    }

    /***
     * 獲取FastDFS檔案的名稱,如:M00/00/00/rBBNW1sJCvCAHgH7AAVGh5jPYok707.jpg
     * @param fileid fileId 包含組名和檔名,如:group1/M00/00/00/rBBNW1sJCvCAHgH7AAVGh5jPYok707.jpg
     * @return
     */
    public static String getFilename(String fileid){

        String[] results = new String[2];
        StorageClient1.split_file_id(fileid,results);
        return results[1] ;
    }

    /***
     * 獲取檔案描述資訊
     * @param filePath
     * @return
     * @throws Exception
     */
    public Map<String,Object> getFileDescriptions(String filePath)throws  Exception{
        //獲取trackerserver
        TrackerServer trackerServer = TrackerServerPool.borrowObject();
        StorageClient1 storageClient1 = new StorageClient1(trackerServer,null) ;
        NameValuePair[] pairs = null ;
        //獲取檔案元資料
        pairs = storageClient1.get_metadata1(filePath) ;
        //回收trackerServer
        TrackerServerPool.recycleObject(trackerServer);

        Map<String, Object> infoMap = null;
        if (pairs != null && pairs.length > 0) {
            infoMap = new HashMap<>(pairs.length);

            for (NameValuePair pair : pairs) {
                infoMap.put(pair.getName(), pair.getValue());
            }
        }

        return infoMap;
    }

    /***
     * 獲取原始檔的檔名稱
     * @param filePath
     * @return
     * @throws Exception
     */
    public String getOriginalFileName(String filePath) throws Exception{
        //獲取檔案的描述資訊
        Map<String,Object> descriptions = getFileDescriptions(filePath);
        //獲取檔名稱
        if (descriptions.get(FILENAME)!=null){
            return (String) descriptions.get(FILENAME);
        }
        return null ;
    }

    /***
     * 獲取檔名稱的字尾
     * @param fileName
     * @return
     */
    public static String getFIleNameSuffix(String fileName){
        String suffix = null ;
        if (fileName!=null&&!"".equals(fileName)){
            if (fileName.contains(SEPARATOR)){
                fileName = fileName.substring(fileName.lastIndexOf(SEPARATOR) + 1);
            }
            if (fileName.contains(POINT)){
                suffix = fileName.substring(fileName.lastIndexOf(POINT) + 1);
            } else {
                if (logger.isErrorEnabled()) {
                    logger.error("檔案字尾獲取失敗!");
                }
            }
        }

        return suffix ;
    }

    /***
     * 獲取檔案資訊
     * @param filePath
     * @return
     * @throws Exception
     */
    public Map<String,Object> getFileInfo(String filePath) throws Exception{
        //獲取trackerserver
        TrackerServer trackerServer = TrackerServerPool.borrowObject();
        StorageClient1 storageClient1 = new StorageClient1(trackerServer,null) ;
        FileInfo fileInfo = null ;
        //獲取檔案資料
        fileInfo = storageClient1.get_file_info1(filePath);

        // 返還物件
        TrackerServerPool.recycleObject(trackerServer);

        Map<String, Object> infoMap = new HashMap<>(4);
        //源IP
        infoMap.put("SourceIpAddr", fileInfo.getSourceIpAddr());
        //檔案大小
        infoMap.put("FileSize", fileInfo.getFileSize());
        //建立時間
        infoMap.put("CreateTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(fileInfo.getCreateTimestamp()));
        //簽名
        infoMap.put("CRC32", fileInfo.getCrc32());

        return infoMap;
    }

    /***
     * 轉換路徑中的 '\' 為 '/' ,並把檔案字尾轉為小寫
     * @param path
     * @return
     */
    public static String toLocal(String path) {
        if (path!=null&&!"".equals(path)) {
            path = path.replaceAll("\\\\", SEPARATOR);

            if (path.contains(POINT)) {
                String pre = path.substring(0, path.lastIndexOf(POINT) + 1);
                String suffix = path.substring(path.lastIndexOf(POINT) + 1).toLowerCase();
                path = pre + suffix;
            }
        }
        return path;
    }

    /**
     * MultipartFile 上傳檔案
     *
     * @param file
     * @return 返回檔案路徑
     */
    public String uploadFileWithMultipart(MultipartFile file) throws Exception {
        return upload(file, null);
    }

    /**
     * MultipartFile 上傳檔案
     *
     * @param file
     * @param descriptions
     * @return 返回檔案路徑
     */
    public String uploadFileWithMultipart(MultipartFile file, Map<String, String> descriptions) throws Exception {
        return upload(file, descriptions);
    }

    /**
     * 根據指定的路徑上傳檔案
     *
     * @param filepath
     * @return 返回檔案路徑
     */
    public String uploadFileWithFilepath(String filepath) throws Exception {
        return upload(filepath, null);
    }

    /**
     * 根據指定的路徑上傳檔案
     *
     * @param filepath
     * @param descriptions
     * @return 返回檔案路徑
     */
    public String uploadFileWithFilepath(String filepath, Map<String, String> descriptions) throws Exception {
        return upload(filepath, descriptions);
    }

    /**
     * 上傳base64檔案
     *
     * @param base64
     * @return 返回檔案路徑
     */
    public String uploadFileWithBase64(String base64) throws Exception {
        return upload(base64, null, null);
    }

    /**
     * 上傳base64檔案
     *
     * @param base64
     * @param filename
     * @return 返回檔案路徑
     */
    public String uploadFileWithBase64(String base64, String filename) throws Exception {
        return upload(base64, filename, null);
    }

    /**
     * 上傳base64檔案
     *
     * @param base64
     * @param filename
     * @param descriptions
     * @return 返回檔案路徑
     */
    public String uploadFileWithBase64(String base64, String filename, Map<String, String> descriptions) throws Exception {
        return upload(base64, filename, descriptions);
    }

    /***
     * 上傳通用方法
     * @param is 檔案流
     * @param fileName 檔名稱
     * @param descriptions
     * @return 組名+檔案路徑
     * @throws Exception
     */
    public String upload(InputStream is, String fileName, Map<String, String> descriptions) throws Exception {
        if(is == null){
            throw new Exception("檔案為空!");
        }

        if(is.available() > maxFileSize){
            throw new Exception("檔案太大!");
        }

        fileName = toLocal(fileName);
        // 返回路徑
        String path = null;
        // 檔案描述
        NameValuePair[] nvps = null;
        List<NameValuePair> nvpsList = new ArrayList<>();
        // 檔名字尾
        String suffix = getFIleNameSuffix(fileName);

        // 檔名
        if (fileName!=null&&!"".equals(fileName)) {
            nvpsList.add(new NameValuePair(FILENAME, fileName));
        }
        // 描述資訊
        if (descriptions != null && descriptions.size() > 0) {
            descriptions.forEach((key, value) -> {
                nvpsList.add(new NameValuePair(key, value));
            });
        }
        if (nvpsList.size() > 0) {
            nvps = new NameValuePair[nvpsList.size()];
            nvpsList.toArray(nvps);
        }

        TrackerServer trackerServer = TrackerServerPool.borrowObject();
        StorageClient1 storageClient = new StorageClient1(trackerServer, null);
        try {
            // 讀取流
            byte[] fileBuff = new byte[is.available()];
            is.read(fileBuff, 0, fileBuff.length);

            // 上傳
            path = storageClient.upload_file1(fileBuff, suffix, nvps);

            if(path==null||"".equals(path)) {
                throw new Exception("上傳失敗!");
            }

            if (logger.isDebugEnabled()) {
                logger.debug("上傳成功!", path);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        } finally {
            // 關閉流
            if(is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        // 回收物件
        TrackerServerPool.recycleObject(trackerServer);

        return path;
    }

    /****
     * 根據指定的路徑上傳
     * @param filePath
     * @param descriptions
     * @return
     * @throws Exception
     */
    public String upload(String filePath ,Map<String,String> descriptions) throws Exception{
        if (filePath==null||"".equals(filePath)){
            throw  new Exception("檔案路徑為空!") ;
        }
        File file = new File(filePath);
        String path = null ;
        //獲取檔案流
        InputStream is = new FileInputStream(file);
        // 獲取檔名
        filePath = toLocal(filePath);
        String filename = filePath.substring(filePath.lastIndexOf("/") + 1);
        //上傳
        path = upload(is, filename, descriptions);

        return path ;
    }

    /***
     * 使用 MultipartFile 上傳
     * @param file
     * @param descriptions
     * @return 檔案路徑
     * @throws Exception
     */
    public String upload(MultipartFile file, Map<String, String> descriptions) throws Exception {
        if(file == null || file.isEmpty()){
            throw new Exception("檔案為空!");
        }
        String path = null;
        try {
            path = upload(file.getInputStream(), file.getOriginalFilename(), descriptions);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return path;
    }

    /***
     * 上傳base64檔案
     * @param base64
     * @param filename
     * @param descriptions
     * @return
     * @throws Exception
     */
    public String upload(String base64, String filename, Map<String, String> descriptions) throws Exception {
        if(base64==null||"".equals(base64)){
            throw new Exception("檔案為空!");
        }
        return upload(new ByteArrayInputStream(Base64.decodeBase64(base64)), filename, descriptions);
    }

    /***
     * 獲取訪問伺服器的token
     * @param filePath group1/M00/00/00/rBBNW1sJCvCAHgH7AAVGh5jPYok707.jpg
     * @param secret 金鑰
     * @return 返回token
     */
    public static String getToken(String filePath,String secret) throws Exception{
        //獲取當前時間毫秒數
        int ts = (int) Instant.now().getEpochSecond();
        String token ="" ;
        try {
            token = ProtoCommon.getToken(getFilename(filePath),ts,secret) ;
        }catch (Exception e){
            e.printStackTrace();
        }

        return token ;
    }

    /**
     * 以附件形式下載檔案
     *
     * @param filepath
     * @param response
     */
    public void downloadFile(String filepath, HttpServletResponse response) throws Exception {
        download(filepath, null, null, response);
    }
    /**
     * 下載檔案 輸出檔案
     *
     * @param filepath
     * @param os 輸出流
     */
    public void downloadFile(String filepath, OutputStream os) throws Exception {
        download(filepath, null, os, null);
    }
    /**
     * 以附件形式下載檔案 可以指定檔名稱.
     *
     * @param filepath
     * @param filename
     * @param response
     */
    public void downloadFile(String filepath, String filename, HttpServletResponse response) throws Exception {
        download(filepath, filename, null, response);
    }
    /**
     * 下載檔案
     *
     * @param filepath
     * @param fileName
     * @param os 輸出流
     * @param response
     */
    public void download(String filepath, String fileName, OutputStream os, HttpServletResponse response) throws Exception {
        if(fileName!=null&&!"".equals(fileName)){
            throw new Exception("檔案路徑為空!");
        }
        filepath = toLocal(filepath);
        // 檔名
        if (fileName==null||"".equals(fileName)) {
            fileName = getOriginalFileName(filepath);
        }
        //獲取檔案型別
        String contentType = EXT_MAPS.get(getFIleNameSuffix(fileName));
        //獲取trackerServer
        TrackerServer trackerServer = TrackerServerPool.borrowObject();
        StorageClient1 storageClient = new StorageClient1(trackerServer, null);
        InputStream is = null;
        try {
            // 下載
            byte[] fileByte = storageClient.download_file1(filepath);

            if(fileByte == null){
                throw new Exception("下載失敗!");
            }
            if (response != null) {
                os = response.getOutputStream();

                // 設定響應頭
                if (contentType!=null||!"".equals(contentType)) {
                    // 檔案編碼
                    String encoderName = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20").replace("%2B", "+");
                    response.setHeader("Content-Disposition", "attachment;filename=\"" + encoderName + "\"");
                    response.setContentType(contentType + ";charset=UTF-8");
                    response.setHeader("Accept-Ranges", "bytes");
                }
            }

            is = new ByteArrayInputStream(fileByte);
            byte[] buffer = new byte[1024 * 5];
            int len = 0;
            while ((len = is.read(buffer)) > 0) {
                os.write(buffer, 0, len);
            }
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
            throw new Exception("下載失敗!");
        } finally {
            // 關閉流
            try {
                if(is != null){
                    is.close();
                }
                if(os != null){
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 回收物件
        TrackerServerPool.recycleObject(trackerServer);
    }
    /**
     * 刪除檔案
     *
     * @param filepath
     * @return 刪除成功返回 0, 失敗返回其它
     */
    public int deleteFile(String filepath) throws Exception {
        if(filepath==null||"".equals(filepath)){
            throw new Exception("檔案路徑為空!");
        }

        TrackerServer trackerServer = TrackerServerPool.borrowObject();
        StorageClient1 storageClient = new StorageClient1(trackerServer, null);
        int success = 0;
        try {
            success = storageClient.delete_file1(filepath);
            if(success != 0){
                throw new Exception("刪除失敗!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
            throw new Exception("刪除失敗!");
        }
        // 回收物件
        TrackerServerPool.recycleObject(trackerServer);
        return success;
    }
}

4. 測試controller

FileController.class
@Controller
public class FileController {

    @Autowired
    FastDFSClient fastDFSClient ;

    @Autowired
    private IItemService itemService;

    @Value("${fastdfs.http_secret_key}")
    public  String httpSecretKey;

    @Value("${file_server_addr}")
    public  String fileServerAddr;

    @RequestMapping("/index")
    public String index(){
        return "index" ;
    }

    /**
     * 上傳到伺服器
     *
     * @param file
     * @return
     */
    @RequestMapping("/upload/file")
    public String upload(MultipartFile file){
        ResultData responseData = new ResultData();
        try {
            // 上傳到伺服器
            String filepath = fastDFSClient.uploadFileWithMultipart(file);
            // 設定訪檔案的Http地址. 有時效性.
            String token = FastDFSClient.getToken(filepath, httpSecretKey);
            System.out.println("上傳token:"+token);
            System.out.println("上傳返回路徑path:"+filepath);
            itemService.insertItem(new Item(file.getOriginalFilename(),filepath));
            return "redirect:http://"+fileServerAddr+"/"+filepath+"?token="+token ;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null ;
    }

    /**
     * 下載檔案
     *
     * @param filePath
     * @param response
     */
    @RequestMapping("/download/file")
    public void downloadFile(String filePath, HttpServletResponse response) throws Exception {
        try {
            fastDFSClient.downloadFile(filePath, response);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 刪除伺服器檔案
     *
     * @param filePath
     */
    @RequestMapping("/delete/file")
    public ResultData deleteFile(String filePath) throws Exception{
        ResultData responseData = new ResultData();
        Item item = new Item();
        item.setUrl(filePath);
        List<Item> items = itemService.selectItemList(item);
        if(item!=null && items.size()>0){
            itemService.selectItemById(items.get(0).getId());
        }
        try {
            fastDFSClient.deleteFile(filePath);
        } catch (Exception e) {
            e.printStackTrace();
            responseData.setSuccess(false);
            responseData.setCode("下載檔案失敗!");
            responseData.setMessage(e.getMessage());
        }
        return responseData;
    }

    /**
     * 獲取訪問檔案的token
     * @param filePath
     * @return
     */
    @RequestMapping("/get/token")
    @ResponseBody
    public ResultData getToken(String filePath)throws Exception{
        ResultData responseData = new ResultData();
        // 設定訪檔案的Http地址. 有時效性.
        String token = FastDFSClient.getToken(filePath, httpSecretKey);
        responseData.setToken(token);
        responseData.setHttpUrl(fileServerAddr+"/"+filePath+"?"+token);
        return responseData;
    }
}