Java实现Fast DFS、服务器、OSS上传
支持Fast DFS、服务器、OSS等上传方式
介绍
在实际的业务中,可以根据客户的需求设置不同的文件上传需求,支持普通服务器上传+分布式上传(Fast DFS)+云服务上传OSS(OSS)
软件架构
为了方便演示使用,本项目使用的是前后端不分离的架构
前端:Jquery.uploadFile
后端:SpringBoot
前期准备:FastDFS、OSS(华为)、服务器
实现逻辑
通过 application 配置对上传文件进行一个自定义配置,从而部署在不同客户环境可以自定义选择方式。
优点:
- 一键切换;
- 支持当前主流方式;
缺点:
- 迁移数据难度增加:因为表示FileID在对象存储和服务器上传都是生成的UUID,而FastDFS是返回存取ID,当需要迁移的时候,通过脚本可以快速将FastDFS的数据迁移上云,因为存储ID可以共用。但是对象存储和服务器上传的UUID无法被FastDFS使用,增加迁移成本
核心代码
package com.example.file.util;
import com.example.file.common.ResultBean;
import com.github.tobato.fastdfs.domain.StorePath;
import com.github.tobato.fastdfs.proto.storage.DownloadByteArray;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.DeleteObjectRequest;
import com.obs.services.model.GetObjectRequest;
import com.obs.services.model.ObsObject;
import com.obs.services.model.PutObjectResult;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.Objects;
import java.util.UUID;
@Slf4j
public class FileUtil {
/**
*
* @param file 文件
* @param uploadFlag 标识
* @param uploadPath 上传路径
* @param endPoint 域名
* @param ak ak
* @param sk sk
* @param bucketName 桶名字
* @return fileId 用于下载
*/
public static ResultBean uploadFile(MultipartFile file, String uploadFlag, String uploadPath,
FastFileStorageClient fastFileStorageClient,
String endPoint, String ak, String sk,
String bucketName) {
if (StringUtils.isBlank(uploadFlag)){
ResultBean.error("uploadFlag is null");
}
switch (uploadFlag){
case "fastDFS":
return uploadFileByFastDFS(file,fastFileStorageClient);
case "huaweiOOS":
return uploadFileByHuaweiObject(file, endPoint, ak, ak, bucketName);
case "server":
default:
return uploadFileByOrigin(file,uploadPath);
}}
/**
* 上传文件fastDFS
* @param file 文件名
* @return
*/
private static ResultBean uploadFileByFastDFS(MultipartFile file,FastFileStorageClient fastFileStorageClient){
Long size=file.getSize();
String fileName=file.getOriginalFilename();
String extName=fileName.substring(fileName.lastIndexOf(".")+1);
InputStream inputStream=null;
try {
inputStream=file.getInputStream();
//1-上传的文件流 2-文件的大小 3-文件的后缀 4-可以不管他
StorePath storePath=fastFileStorageClient.uploadFile(inputStream,size,extName,null);
log.info("[uploadFileByFastDFS][FullPath]"+storePath.getFullPath());
return ResultBean.success(storePath.getPath());
}catch (Exception e){
log.info("[ERROR][uploadFileByFastDFS]"+e.getMessage());
return ResultBean.error(e.getMessage());
}finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 对象存储上传
* @param file 文件名
* @return
*/
private static ResultBean uploadFileByHuaweiObject(MultipartFile file, String huaweiEndPoint,String huaweiobsAk,String huaweiobsSk,
String bucketName){
String fileName = file.getOriginalFilename();
fileName = renameToUUID(fileName);
InputStream inputStream=null;
try {
inputStream=file.getInputStream();
// 创建ObsClient实例
ObsClient obsClient = new ObsClient(huaweiobsAk, huaweiobsSk, huaweiEndPoint);
PutObjectResult result = obsClient.putObject(bucketName, fileName, inputStream);
obsClient.close();
return ResultBean.success(fileName);
}catch (ObsException e){
log.info("[ERROR][uploadFileByHuaweiObject]"+e.getErrorMessage());
return ResultBean.error(e.getErrorMessage());
}catch (Exception e){
log.info("[ERROR][uploadFileByHuaweiObject]"+e.getMessage());
return ResultBean.error(e.getMessage());
}finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 上传文件原本方法
* @param file 文件名
* @return
*/
private static ResultBean uploadFileByOrigin(MultipartFile file,String uploadPath){
String fileName = file.getOriginalFilename();
fileName = renameToUUID(fileName);
File targetFile = new File(uploadPath);
if (!targetFile.exists()) {
targetFile.mkdirs();
}
FileOutputStream out = null;
try {
out = new FileOutputStream(uploadPath + fileName);
out.write(file.getBytes());
} catch (IOException e) {
log.info("[ERROR][uploadFileByOrigin]"+e.getMessage());
return ResultBean.error(e.getMessage());
}finally {
try {
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return ResultBean.success(fileName);
}
/**
* 下载
* @return
*/
public static byte[] downloadFile(String fileId,String uploadFlag,String uploadPath
,FastFileStorageClient fastFileStorageClient, String group,
String endPoint,String ak,String sk,
String bucketName) {
byte[] result=null;
switch (uploadFlag){
case "fastDFS":
result =downloadFileByFastDFS(fileId,fastFileStorageClient,group);
break;
case "huaweiOOS":
result =downloadFileByHuaweiObject(fileId, endPoint, ak, sk, bucketName);
break;
case "server":
default:
String path2 = uploadPath + fileId;
path2 = path2.replace("//", "/");
result=downloadFileByOrigin(path2);
break;
}
return result;
}
/**
* 下载文件fastDFS
* @param fileId 文件名
* @return
*/
private static byte[] downloadFileByFastDFS(String fileId,FastFileStorageClient fastFileStorageClient,
String group){
DownloadByteArray callback=new DownloadByteArray();
byte[] group1s=null;
try {
group1s = fastFileStorageClient.downloadFile(group, fileId, callback);
}catch (Exception e){
log.info("[ERROR][downloadFileByFastDFS]"+e.getMessage());
}
return group1s;
}
/**
* 下载文件对象存储
* @param fileId 文件名
* @return
*/
private static byte[] downloadFileByHuaweiObject(String fileId, String huaweiEndPoint,String huaweiobsAk,String huaweiobsSk,
String bucketName){
byte[] bytes =null;
try {
// 创建ObsClient实例
ObsClient obsClient = new ObsClient(huaweiobsAk, huaweiobsSk, huaweiEndPoint);
// 构造GetObjectRequest请求
GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, fileId);
// 执行下载操作
ObsObject obsObject = obsClient.getObject(getObjectRequest);
bytes = inputStreamToByteArray(obsObject.getObjectContent());
// 关闭OBS客户端
obsClient.close();
return bytes;
}catch (ObsException e){
log.info("[ERROR][downloadFileByHuaweiObject]"+e.getErrorMessage());
}catch (Exception e) {
log.info("[ERROR][downloadFileByHuaweiObject]"+e.getMessage());
}
return bytes;
}
/**
*
* @param input
* @return
* @throws IOException
*/
private static byte[] inputStreamToByteArray(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int n = 0;
while (-1 != (n = input.read(buffer))) {
output.write(buffer, 0, n);
}
return output.toByteArray();
}
/**
* 下载文件
* @param fileId 文件名
* @return
*/
private static byte[] downloadFileByOrigin(String fileId){
File file =new File(fileId);
InputStream inputStream=null;
byte[] buff = new byte[1024];
byte[] result=null;
BufferedInputStream bis = null;
ByteArrayOutputStream os = null;
try {
os=new ByteArrayOutputStream();
bis = new BufferedInputStream(new FileInputStream(file));
int i = bis.read(buff);
while (i != -1) {
os.write(buff, 0, buff.length);
i = bis.read(buff);
}
result=os.toByteArray();
os.flush();
} catch (Exception e) {
log.info("[ERROR][downloadFile]"+e.getMessage());
}finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
/**
* 删除文件
* @param fileId 文件ID
* @return 删除失败返回-1,否则返回0
*/
public static boolean deleteFile(String fileId,String fastDFSFlag,FastFileStorageClient fastFileStorageClient,
String group,String uploadPath) {
boolean result=false;
if (StringUtils.isNotBlank(fastDFSFlag)&&
fastDFSFlag.trim().equalsIgnoreCase("true")){
result =deleteFileByFastDFS(fileId,fastFileStorageClient,group);
}else {
String path2 = uploadPath + fileId;
path2 = path2.replace("//", "/");
result=deleteByOrigin(path2);
}
return result;
}
private static boolean deleteByOrigin(String fileName) {
File file = new File(fileName);
// 如果文件路径所对应的文件存在,并且是一个文件,则直接删除
if (file.exists() && file.isFile()) {
if (file.delete()) {
return true;
} else {
return false;
}
} else {
return false;
}
}
private static boolean deleteFileByFastDFS(String fileId,FastFileStorageClient fastFileStorageClient,
String group) {
try {
String groupFieId=group+"/"+fileId;
StorePath storePath = StorePath.praseFromUrl(groupFieId);
fastFileStorageClient.deleteFile(storePath.getGroup(), storePath.getPath());
} catch (Exception e) {
log.info("[ERROR][deleteFileByFastDFS]"+e.getMessage());
return false;
}
return true;
}
/**
* 生成fileId
* @param fileName
* @return
*/
private static String renameToUUID(String fileName) {
return UUID.randomUUID() + "." + fileName.substring(fileName.lastIndexOf(".") + 1);
}
/**
* 删除文件对象存储
* @param fileId 文件名
* @return
*/
private static boolean deleteFileByHuaweiObject(String fileId, String huaweiEndPoint,String huaweiobsAk,String huaweiobsSk,
String bucketName){
try {
// 创建ObsClient实例
ObsClient obsClient = new ObsClient(huaweiobsAk, huaweiobsSk, huaweiEndPoint);
// 构造GetObjectRequest请求
DeleteObjectRequest getObjectRequest = new DeleteObjectRequest(bucketName, fileId);
// 执行删除操作
obsClient.deleteObject(getObjectRequest);
// 关闭OBS客户端
obsClient.close();
}catch (ObsException e){
log.info("[ERROR][deleteFileByHuaweiObject]"+e.getErrorMessage());
}catch (Exception e) {
log.info("[ERROR][downloadFileByHuaweiObject]"+e.getMessage());
}
return true;
}
/**
* 文件数据输出(image)
* @param fileId
* @param bytes
* @param res
*/
public static void fileDataOut(String fileId, byte[] bytes, HttpServletResponse res){
String[] prefixArray = fileId.split("\\.");
String prefix=prefixArray[1];
File file =new File(fileId);
res.reset();
res.setCharacterEncoding("utf-8");
// res.setHeader("content-type", "");
res.addHeader("Content-Length", "" + bytes.length);
if(prefix.equals("svg"))
prefix ="svg+xml";
res.setContentType("image/"+prefix);
res.setHeader("Accept-Ranges","bytes");
OutputStream os = null;
try {
// os = res.getOutputStream();
// os.write(bytes);
// os.flush();
res.getOutputStream().write(bytes);
} catch (IOException e) {
System.out.println("not find img..");
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
参考资料
假设个人实战使用可以采用docker快速安装,但是未安装过FastDFS建议普通安装部署,了解一下tracker和storage的使用,以及部署搭建集群。
FastDFS普通安装部署:https://www.cnblogs.com/chenliugou/p/15322389.html
OpenFeign和FastDFS的类冲突:https://www.cnblogs.com/chenliugou/p/18113183
Gitee地址:https://gitee.com/chen-liugou/file/new/master?readme=true