【開發技巧】-- 什麼你還在使用本地作爲文件保存服務器?本文將帶你瞭解,如何使用SpringBoot優雅的將文件上傳至阿里雲OSS、FastDFS(分佈式文件系統)

1.1 業務背景

當今互聯網項目,需求日漸增多,並且應用服務器的壓力也日漸增大,這時就引入了分佈式系統的概念,然後又有了動靜分離,即動態資源與靜態資源分開,使後端的應用服務器專注業務請求的處理,並降低因爲請求靜態資源而爲應用服務器帶來的壓力。

1.2 文件上傳的實現方式有哪些?

  1. 直接上傳到應用服務器(缺點:增加應用服務器的壓力)。
  2. 通過搭建私有云,比如通過FASTDFS搭建一個分佈式文件系統。
  3. 使用第三方雲存儲(阿里雲OSS、七牛雲等)。

1.3 文件上傳的實現

1.3.1 前置準備

1. 創建一個枚舉類FileSourceEnum(用於後期實例化指定文件上傳業務實現類)
package com.qingyun.farm.enums;

import lombok.Getter;

@Getter
public enum FileSourceEnum {

    LOCAL(1L,"LOCAL"),
    ALIYUN(2L,"ALIYUN"),
    FAST_DFS(3L,"FAST_DFS");

    private Long code;
    private String desc;

    FileSourceEnum(Long code, String desc) {
        this.code=code;
        this.desc=desc;
    }
}
2. 創建一個文件上傳業務實現類創建實例工廠類FileServiceFactory
package com.qingyun.farm.factory;

import com.qingyun.farm.enums.FileSourceEnum;
import com.qingyun.farm.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashMap;

/**
 * Created with IntelliJ IDEA.
 * User: 李敷斌.
 * Date: 2020-04-02
 * Time: 11:20
 * Explain: 文件業務工廠類
 */
@Component
public class FileServiceFactory {

    private HashMap<FileSourceEnum, FileService> fileServiceMap=new HashMap<>();

    @Autowired
    private FileService localFileServiceImpl;

    @Autowired
    private FileService aliyunFileServiceImpl;

    @Autowired
    private FileService fastdfsFileServiceImpl;

    @PostConstruct
    private void initFileService(){
        fileServiceMap.put(FileSourceEnum.LOCAL,localFileServiceImpl);
        fileServiceMap.put(FileSourceEnum.ALIYUN,aliyunFileServiceImpl);
        fileServiceMap.put(FileSourceEnum.FAST_DFS,fastdfsFileServiceImpl);
    }

    public FileService getFileService(Long fileSourceCode) {

        if (fileSourceCode.equals(FileSourceEnum.FAST_DFS.getCode())){
            return fileServiceMap.get(FileSourceEnum.FAST_DFS);
        }else if (fileSourceCode.equals(FileSourceEnum.ALIYUN.getCode())){
            return fileServiceMap.get(FileSourceEnum.ALIYUN);
        }

        return fileServiceMap.get(FileSourceEnum.LOCAL);
    }
}
3. 創建一個文件上傳業務實現接口類FileService
package com.qingyun.farm.service;

import java.io.IOException;

import com.qingyun.farm.model.FileInfo;
import org.springframework.web.multipart.MultipartFile;

public interface FileService {

	FileInfo upload(MultipartFile file) throws Exception;

	void delete(FileInfo fileInfo);

}
4. 創建一個文件上傳業務實現抽象父類AbstractFileService
package com.qingyun.farm.service.impl;

import com.qingyun.farm.dao.FileInfoDao;
import com.qingyun.farm.enums.FileSourceEnum;
import com.qingyun.farm.model.FileInfo;
import com.qingyun.farm.service.FileService;
import com.qingyun.farm.utils.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
public abstract class AbstractFileService implements FileService {

	protected abstract FileInfoDao getFileDao();

	@Override
	public FileInfo upload(MultipartFile file) throws Exception {
		FileInfo fileInfo = FileUtil.getFileInfo(file);
		FileInfo oldFileInfo = getFileDao().getById(fileInfo.getId());

		if (oldFileInfo != null) {
			return oldFileInfo;
		}

		if (!fileInfo.getName().contains(".")) {
			throw new IllegalArgumentException("缺少後綴名");
		}

		uploadFile(file, fileInfo);

		fileInfo.setSource(fileSource().name());// 設置文件來源
		getFileDao().save(fileInfo);// 將文件信息保存到數據庫

		log.info("上傳文件:{}", fileInfo);

		return fileInfo;
	}

	/**
	 * 文件來源
	 * 
	 * @return
	 */
	protected abstract FileSourceEnum fileSource();

	/**
	 * 上傳文件
	 * 
	 * @param file
	 * @param fileInfo
	 */
	protected abstract void uploadFile(MultipartFile file, FileInfo fileInfo) throws Exception;

	@Override
	public void delete(FileInfo fileInfo) {
		deleteFile(fileInfo);
		getFileDao().delete(fileInfo.getId());
		log.info("刪除文件:{}", fileInfo);
	}

	/**
	 * 刪除文件資源
	 * 
	 * @param fileInfo
	 * @return
	 */
	protected abstract boolean deleteFile(FileInfo fileInfo);
}
5 .創建一個文件上傳請求控制器FileController
package com.qingyun.farm.controller;

import java.io.IOException;
import java.util.List;

import com.qingyun.farm.dao.FileInfoDao;
import com.qingyun.farm.dto.LayuiFile;
import com.qingyun.farm.factory.FileServiceFactory;
import com.qingyun.farm.model.FileInfo;
import com.qingyun.farm.page.table.PageTableHandler;
import com.qingyun.farm.page.table.PageTableRequest;
import com.qingyun.farm.page.table.PageTableResponse;
import com.qingyun.farm.service.FileService;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import com.qingyun.farm.annotation.LogAnnotation;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@Api(tags = "文件")
@RestController
@RequestMapping("/files")
public class FileController {

	@Autowired
	private FileServiceFactory fileServiceFactory;
	@Autowired
	private FileInfoDao fileInfoDao;

	@LogAnnotation
	@PostMapping
	@ApiOperation(value = "文件上傳")
	public FileInfo uploadFile(MultipartFile file,@RequestParam(value = "sourceCode",defaultValue = "1") Long sourceCode) throws IOException {
		try {
			return fileServiceFactory.getFileService(sourceCode).upload(file);
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

	@LogAnnotation
	@DeleteMapping("/{id}")
	@ApiOperation(value = "文件刪除")
	@RequiresPermissions("sys:file:del")
	public void delete(@PathVariable String id,Long sourceCode) {
		FileInfo fileInfo = fileInfoDao.getById(id);

		fileServiceFactory.getFileService(sourceCode).delete(fileInfo);
	}

}
6. 創建一個文件上傳工具類FileUtil,用於獲取上傳文件的信息,以及具體實現本地文件上傳。
package com.qingyun.farm.utils;

import com.qingyun.farm.model.FileInfo;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.time.LocalDate;
import java.util.Date;

public class FileUtil {

	public static FileInfo getFileInfo(MultipartFile file) throws Exception {
		String md5 = fileMd5(file.getInputStream());

		FileInfo fileInfo = new FileInfo();
		fileInfo.setId(md5);// 將文件的md5設置爲文件表的id
		fileInfo.setName(file.getOriginalFilename());
		fileInfo.setContentType(file.getContentType());
		fileInfo.setIsImg(fileInfo.getContentType().startsWith("image/")?1L:0L);
		fileInfo.setSize(file.getSize());
		fileInfo.setCreateTime(new Date());

		return fileInfo;
	}

	/**
	 * 文件的md5
	 * 
	 * @param inputStream
	 * @return
	 */
	public static String fileMd5(InputStream inputStream) {
		try {
			return DigestUtils.md5Hex(inputStream);
		} catch (IOException e) {
			e.printStackTrace();
		}

		return null;
	}

	public static String saveFile(MultipartFile file, String path) {
		try {
			File targetFile = new File(path);
			if (targetFile.exists()) {
				return path;
			}

			if (!targetFile.getParentFile().exists()) {
				targetFile.getParentFile().mkdirs();
			}
			file.transferTo(targetFile);

			return path;
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

	public static boolean deleteFile(String pathname) {
		File file = new File(pathname);
		if (file.exists()) {
			boolean flag = file.delete();

			if (flag) {
				File[] files = file.getParentFile().listFiles();
				if (files == null || files.length == 0) {
					file.getParentFile().delete();
				}
			}

			return flag;
		}

		return false;
	}








    public static String getPath() {
        return "/" + LocalDate.now().toString().replace("-", "/") + "/";
    }

    /**
     * 將文本寫入文件
     *
     * @param value
     * @param path
     */
    public static void saveTextFile(String value, String path) {
        FileWriter writer = null;
        try {
            File file = new File(path);
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }

            writer = new FileWriter(file);
            writer.write(value);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static String getText(String path) {
        File file = new File(path);
        if (!file.exists()) {
            return null;
        }

        try {
            return getText(new FileInputStream(file));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        return null;
    }

    public static String getText(InputStream inputStream) {
        InputStreamReader isr = null;
        BufferedReader bufferedReader = null;
        try {
            isr = new InputStreamReader(inputStream, "utf-8");
            bufferedReader = new BufferedReader(isr);
            StringBuilder builder = new StringBuilder();
            String string;
            while ((string = bufferedReader.readLine()) != null) {
                string = string + "\n";
                builder.append(string);
            }

            return builder.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (isr != null) {
                try {
                    isr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return null;
    }


}

以上就是文件上傳業務實現的準備工作,可以根據具體業務修改相關代碼實現。

1.3.2 通過SpringMVC直接將文件上傳至應用服務器

1. 創建本地文件上傳業務實現類
package com.qingyun.farm.service.impl;

import java.io.IOException;
import java.time.LocalDate;

import com.qingyun.farm.enums.FileSourceEnum;
import com.qingyun.farm.model.FileInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 com.qingyun.farm.dao.FileInfoDao;
import com.qingyun.farm.service.FileService;
import com.qingyun.farm.utils.FileUtil;

@Service
public class LocalFileServiceImpl extends AbstractFileService {


	@Autowired
	private FileInfoDao fileInfoDao;

	@Override
	protected FileInfoDao getFileDao() {
		return fileInfoDao;
	}

	@Value("${file.local.urlPrefix}")
	private String urlPrefix;
	/**
	 * 上傳文件存儲在本地的根路徑
	 */
	@Value("${file.local.path}")
	private String localFilePath;

	@Override
	protected FileSourceEnum fileSource() {
		return FileSourceEnum.LOCAL;
	}

	@Override
	protected void uploadFile(MultipartFile file, FileInfo fileInfo) throws Exception {
		int index = fileInfo.getName().lastIndexOf(".");
		// 文件擴展名
		String fileSuffix = fileInfo.getName().substring(index);

		String suffix = "/" + LocalDate.now().toString().replace("-", "/") + "/" + fileInfo.getId() + fileSuffix;

		String path = localFilePath + suffix;
		String url = urlPrefix + suffix;
		fileInfo.setPath(path);
		fileInfo.setUrl(url);

		FileUtil.saveFile(file, path);
	}

	@Override
	protected boolean deleteFile(FileInfo fileInfo) {
		return FileUtil.deleteFile(fileInfo.getPath());
	}
}
2. 配置上傳文件的保存路徑
file:
  local:
    path: /Users/qingyun/IdeaProjects/SmartAgriculture/src/main/resources/static/upload
    prefix: /upload
    urlPrefix: http://localhost:8088/${file.local.prefix}

1.3.3 使用FASTDFS搭建私有云實現文件上傳。

1. 下載fastdfs-client,並將它安裝到maven倉庫中【fastdfs-client-java:點擊鏈接即可下載, 密碼: ol7r】
2. 引入fastdfs-client所需maven依賴
<dependency>
	<groupId>org.csource</groupId>
	<artifactId>fastdfs-client-java</artifactId>
	<version>1.29-SNAPSHOT</version>
</dependency>
3. 創建一個fastdfs配置文件,tracker.conf

在這裏插入圖片描述

tracker_server=192.168.69.139:22122

# 連接超時時間,針對socket套接字函數connect,默認爲30秒
connect_timeout=30000

# 網絡通訊超時時間,默認是60秒
network_timeout=60000
4. 創建一個Fastdfs文件上傳客戶端配置類
package com.qingyun.farm.config;

import org.csource.fastdfs.ClientGlobal;
import org.csource.fastdfs.StorageClient;
import org.csource.fastdfs.TrackerClient;
import org.csource.fastdfs.TrackerServer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created with IntelliJ IDEA.
 * User: 李敷斌.
 * Date: 2020-02-26
 * Time: 17:57
 * Explain: 文件上傳配置類
 */
@Configuration
@ConditionalOnProperty(prefix = "file.fastdfs.tracker.config",name = "path",havingValue = "/tracker.conf")
public class FastDFSUploadConfig {

    @Value(value = "${file.fastdfs.tracker.config.path}")
    private String trackerConfigPath;

    @Bean
    public StorageClient storageClient(){

        TrackerClient trackerClient=null;
        TrackerServer trackerServer=null;

        try {
            String tracker = FastDFSUploadConfig.class.getResource(trackerConfigPath).getPath();

            //初始化
            ClientGlobal.init(tracker);

            //創建trackerClient
            trackerClient=new TrackerClient();
            //通過client獲取service
            trackerServer=trackerClient.getTrackerServer();
        }catch (Exception e){

        }

        //以trackerservice爲參數 構建storageclient
        return new StorageClient(trackerServer,null);
    }
}
5. 創建fastdfs文件上傳業務實現類
package com.qingyun.farm.service.impl;

import com.qingyun.farm.dao.FileInfoDao;
import com.qingyun.farm.enums.FileSourceEnum;
import com.qingyun.farm.model.FileInfo;
import lombok.extern.slf4j.Slf4j;
import org.csource.common.MyException;
import org.csource.fastdfs.StorageClient;
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.io.IOException;

/**
 * Created with IntelliJ IDEA.
 * User: 李敷斌.
 * Date: 2020-04-02
 * Time: 11:58
 * Explain: fastdfs文件上傳
 */
@Service
@Slf4j
public class FastdfsFileServiceImpl extends AbstractFileService {

    @Autowired
    private StorageClient storageClient;

    @Autowired
    private FileInfoDao fileInfoDao;

    @Value("${file.fastdfs.domain}")
    private String fastdfs_domain;

    @Override
    protected FileInfoDao getFileDao() {
        return fileInfoDao;
    }

    /**
     * 文件來源
     *
     * @return
     */
    @Override
    protected FileSourceEnum fileSource() {
        return FileSourceEnum.FAST_DFS;
    }

    /**
     * 上傳文件
     *
     * @param multipartFile
     * @param fileInfo
     */
    @Override
    protected void uploadFile(MultipartFile multipartFile, FileInfo fileInfo) throws Exception {
        //獲取文件後綴名
        String sourceFileName = multipartFile.getOriginalFilename();

        //獲取後綴名
        String suffixName = sourceFileName.substring(sourceFileName.lastIndexOf(".")+1);

        String[] uploadFileResult=null;

        try {
            //上傳文件
            uploadFileResult = storageClient.upload_file(multipartFile.getBytes(), suffixName, null);
        } catch (Exception e){
            log.error("【文件上傳】獲取二進制數據失敗,ex.msg={}",e.getMessage());
        }

        String url="http://"+fastdfs_domain+getUrl(uploadFileResult);
        fileInfo.setUrl(url);

        log.debug("【文件上傳】文件訪問路徑,imageUrl={}",url);
    }

    private String getUrl(String[] uploadFileResult){

        StringBuffer sbf=new StringBuffer();

        for (String item : uploadFileResult) {
            sbf.append("/"+item);
        }

        return sbf.toString();
    }

    /**
     * 刪除文件資源
     *
     * @param fileInfo
     * @return
     */
    @Override
    protected boolean deleteFile(FileInfo fileInfo) {
        try {
            return storageClient.delete_file("group1",fileInfo.getName())>0;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        }

        return false;
    }
}
6. 在配置文件中,配置好fastdfs文件上傳業務有關配置信息
file:
  fastdfs:
    tracker:
      config:
        path: /tracker.conf
    domain: 192.168.69.139

fastdfs文件上傳至此以及完整地實現了。

1.3.4 使用阿里雲OSS實現文件上傳

1. 引入阿里雲OSS實現文件上傳所需maven依賴
<dependency>
	<groupId>com.aliyun.oss</groupId>
	<artifactId>aliyun-sdk-oss</artifactId>
	<version>${aliyun-sdk-oss.version}</version>
</dependency>
<dependency>
	<groupId>com.aliyun</groupId>
	<artifactId>aliyun-java-sdk-core</artifactId>
	<version>${aliyun-sdk-core.version}</version>
</dependency>
<dependency>
	<groupId>com.aliyun</groupId>
	<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
	<version>${aliyun-sdk-dysmsapi.version}</version>
</dependency>

對應版本信息:

<aliyun-sdk-oss.version>2.8.2</aliyun-sdk-oss.version>
<aliyun-sdk-core.version>3.2.8</aliyun-sdk-core.version>
<aliyun-sdk-dysmsapi.version>1.1.0</aliyun-sdk-dysmsapi.version>
2. 在配置文件中,配置阿里雲OSS文件上傳相關配置
file:
  aliyun:
    endpoint: xxx
    accessKeyId: xxx
    accessKeySecret: xxx
    bucketName: xxx
    domain: xxx
3. 寫一個阿里雲OSS文件上傳配置類
package com.qingyun.farm.config;

import com.aliyun.oss.OSSClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;

@Configuration
public class AliyunConfig {

	@Value("${file.aliyun.endpoint}")
	private String endpoint;
	@Value("${file.aliyun.accessKeyId}")
	private String accessKeyId;
	@Value("${file.aliyun.accessKeySecret}")
	private String accessKeySecret;

	/**
	 * 阿里雲文件存儲client
	 * 
	 */
	@Bean
	public OSSClient ossClient() {
		OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
		return ossClient;
	}


	public static void main(String[] args) throws FileNotFoundException {

		OSSClient ossClient = new OSSClient("oss-cn-beijing.aliyuncs.com", "LTAI3jTQMjLamd0v", "aOR1ZFUoJCKmiSUUQopZcwZDu0uei6");
		InputStream inputStream = new FileInputStream("D://ssfw.sql");

		ossClient.putObject("topwulian", "upload/" + "ss11fw.sql", inputStream);

		Date expiration = new Date(new Date().getTime() + 3600l * 1000 * 24 * 365 * 10);
		// 生成URL
		URL url = ossClient.generatePresignedUrl("topwulian", "upload/" + "ss11fw.sql", expiration);

		System.out.println(url);


	}
}
4. 創建阿里雲OSS文件上傳業務實現類
package com.qingyun.farm.service.impl;

import com.aliyun.oss.OSSClient;
import com.qingyun.farm.dao.FileInfoDao;
import com.qingyun.farm.enums.FileSourceEnum;
import com.qingyun.farm.model.FileInfo;
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.io.IOException;

/**
 * 阿里雲存儲文件
 * 
 * @author 小威老師
 *
 */
@Service("aliyunFileServiceImpl")
public class AliyunFileServiceImpl extends AbstractFileService {

	@Autowired
	private FileInfoDao fileInfoDao;

	@Override
	protected FileInfoDao getFileDao() {
		return fileInfoDao;
	}

	@Override
	protected FileSourceEnum fileSource() {
		return FileSourceEnum.ALIYUN;
	}

	@Autowired
	private OSSClient ossClient;

	@Value("${file.aliyun.bucketName}")
	private String bucketName;
	@Value("${file.aliyun.domain}")
	private String domain;

	@Override
	protected void uploadFile(MultipartFile file, FileInfo fileInfo) throws Exception {
		ossClient.putObject(bucketName, fileInfo.getName(), file.getInputStream());
		fileInfo.setUrl(domain + "/" + fileInfo.getName());
	}

	@Override
	protected boolean deleteFile(FileInfo fileInfo) {
		ossClient.deleteObject(bucketName, fileInfo.getName());
		return true;
	}
}

至此三種文件上傳方式整合完畢,如果文章對你有幫助的話請給我點個贊哦,也可以關注博主我將持續更新一些組件使用教程❤️

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