前言
前幾天自己搭建了一個小的練手項目,其中文件存儲使用了 FastDFS 文件服務器 ,但是在實際部署時出現了問題,由于項目是部署在阿里云服務器上,FastDFS服務器部署在本地局域網通過端口路由的方式進行文件操作 ,兩臺服務器不在同一個局域網,導致項目連接Fdfs服務器時獲取到的Storage存儲節點為該局域網ip 上傳下載文件都timeout。
后面查了下fastdfs-client的 源碼 發現 里面獲取Storage存儲節點時是從 tracker里獲取的 代碼如下:
/**
* 獲取存儲Group
*
* @param groupName
* @return
*/
private StorageNode getStorageNode(String groupName) {
if (null == groupName) {
return trackerClient.getStoreStorage();
} else {
return trackerClient.getStoreStorage(groupName);
}
}
既然找到了問題的所在,那就好解決了,話不多說 上代碼
添加配置:
fdfs.storage.node.flag=true
fdfs.storage.node.server.ip=192.168.8.18
fdfs.storage.node.server.port=23000
重寫 Fdfs獲取StorageClient節點的實現
package com.hm.www.admin.config;
import com.github.tobato.fastdfs.FdfsClientConstants;
import com.github.tobato.fastdfs.domain.fdfs.MetaData;
import com.github.tobato.fastdfs.domain.fdfs.StorageNode;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.fdfs.ThumbImageConfig;
import com.github.tobato.fastdfs.domain.proto.storage.StorageSetMetadataCommand;
import com.github.tobato.fastdfs.domain.proto.storage.StorageUploadFileCommand;
import com.github.tobato.fastdfs.domain.proto.storage.StorageUploadSlaveFileCommand;
import com.github.tobato.fastdfs.domain.proto.storage.enums.StorageMetadataSetType;
import com.github.tobato.fastdfs.domain.upload.FastFile;
import com.github.tobato.fastdfs.domain.upload.FastImageFile;
import com.github.tobato.fastdfs.domain.upload.ThumbImage;
import com.github.tobato.fastdfs.exception.FdfsUnsupportImageTypeException;
import com.github.tobato.fastdfs.exception.FdfsUploadImageException;
import com.github.tobato.fastdfs.service.DefaultGenerateStorageClient;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* @author yangxiaohui
* @Date: Create by 2019-03-08 13:26
* @Description: FastDFS獲取的節點數據配置為正常的
*/
@Primary
@Component("myFastFileStorageClient")
public class MyFastFileStorageClient extends DefaultGenerateStorageClient implements FastFileStorageClient {
/**
* 支持的圖片類型
*/
private static final List<String> SUPPORT_IMAGE_LIST = Arrays.asList(FdfsClientConstants.SUPPORT_IMAGE_TYPE);
/**
* 縮略圖生成配置
*/
@Autowired
private ThumbImageConfig thumbImageConfig;
@Value("${fdfs.storage.node.flag}")
private boolean defaultStorageFlag;
@Value("${fdfs.storage.node.server.ip}")
private String defaultStorageIp;
@Value("${fdfs.storage.node.server.port}")
private int defaultStoragePort;
/**
* 上傳文件
*/
@Override
public StorePath uploadFile(InputStream inputStream, long fileSize,
String fileExtName, Set<MetaData> metaDataSet) {
FastFile fastFile;
if (null == metaDataSet) {
fastFile = new FastFile.Builder()
.withFile(inputStream, fileSize, fileExtName)
.build();
} else {
fastFile = new FastFile.Builder()
.withFile(inputStream, fileSize, fileExtName)
.withMetaData(metaDataSet)
.build();
}
return uploadFile(fastFile);
}
/**
* 上傳圖片并且生成縮略圖
*/
@Override
public StorePath uploadImageAndCrtThumbImage(InputStream inputStream,
long fileSize,
String fileExtName,
Set<MetaData> metaDataSet) {
FastImageFile fastImageFile;
if (null == metaDataSet) {
fastImageFile = new FastImageFile.Builder()
.withFile(inputStream, fileSize, fileExtName)
.withThumbImage()
.build();
} else {
fastImageFile = new FastImageFile.Builder()
.withFile(inputStream, fileSize, fileExtName)
.withMetaData(metaDataSet)
.withThumbImage()
.build();
}
return uploadImage(fastImageFile);
}
/**
* 上傳文件
* <pre>
* 可通過fastFile對象配置
* 1. 上傳圖像分組
* 2. 上傳元數據metaDataSet
* <pre/>
* @param fastFile
* @return
*/
@Override
public StorePath uploadFile(FastFile fastFile) {
Validate.notNull(fastFile.getInputStream(), "上傳文件流不能為空");
Validate.notBlank(fastFile.getFileExtName(), "文件擴展名不能為空");
// 獲取存儲節點
StorageNode client = getStorageNode(fastFile.getGroupName());
// 上傳文件
return uploadFileAndMetaData(client, fastFile.getInputStream(),
fastFile.getFileSize(), fastFile.getFileExtName(),
fastFile.getMetaDataSet());
}
/**
* 上傳圖片
* <pre>
* 可通過fastImageFile對象配置
* 1. 上傳圖像分組
* 2. 上傳元數據metaDataSet
* 3. 是否生成縮略圖
* 3.1 根據默認配置生成縮略圖
* 3.2 根據指定尺寸生成縮略圖
* 3.3 根據指定比例生成縮略圖
* <pre/>
* @param fastImageFile
* @return
*/
@Override
public StorePath uploadImage(FastImageFile fastImageFile) {
String fileExtName = fastImageFile.getFileExtName();
Validate.notNull(fastImageFile.getInputStream(), "上傳文件流不能為空");
Validate.notBlank(fileExtName, "文件擴展名不能為空");
// 檢查是否能處理此類圖片
if (!isSupportImage(fileExtName)) {
throw new FdfsUnsupportImageTypeException("不支持的圖片格式" + fileExtName);
}
// 獲取存儲節點
StorageNode client = getStorageNode(fastImageFile.getGroupName());
byte[] bytes = inputStreamToByte(fastImageFile.getInputStream());
// 上傳文件和metaDataSet
StorePath path = uploadFileAndMetaData(client, new ByteArrayInputStream(bytes),
fastImageFile.getFileSize(), fileExtName,
fastImageFile.getMetaDataSet());
//如果設置了需要上傳縮略圖
if (null != fastImageFile.getThumbImage()) {
// 上傳縮略圖
uploadThumbImage(client, new ByteArrayInputStream(bytes), path.getPath(), fastImageFile);
}
bytes = null;
return path;
}
/**
* 獲取存儲Group
*
* @param groupName
* @return
*/
private StorageNode getStorageNode(String groupName) {
StorageNode storageNode = null;
if (null == groupName) {
storageNode = trackerClient.getStoreStorage();
} else {
storageNode = trackerClient.getStoreStorage(groupName);
}
if (defaultStorageFlag) {
if (!StringUtils.isEmpty(defaultStorageIp)) {
storageNode.setIp(defaultStorageIp);
}
if (defaultStoragePort > 0) {
storageNode.setPort(defaultStoragePort);
}
}
return storageNode;
}
/**
* 獲取byte流
*
* @param inputStream
* @return
*/
private byte[] inputStreamToByte(InputStream inputStream) {
try {
return IOUtils.toByteArray(inputStream);
} catch (IOException e) {
LOGGER.error("image inputStream to byte error", e);
throw new FdfsUploadImageException("upload ThumbImage error", e.getCause());
}
}
/**
* 檢查是否有MetaData
*
* @param metaDataSet
* @return
*/
private boolean hasMetaData(Set<MetaData> metaDataSet) {
return null != metaDataSet && !metaDataSet.isEmpty();
}
/**
* 是否是支持的圖片文件
*
* @param fileExtName
* @return
*/
private boolean isSupportImage(String fileExtName) {
return SUPPORT_IMAGE_LIST.contains(fileExtName.toUpperCase());
}
/**
* 上傳文件和元數據
*
* @param client
* @param inputStream
* @param fileSize
* @param fileExtName
* @param metaDataSet
* @return
*/
private StorePath uploadFileAndMetaData(StorageNode client, InputStream inputStream, long fileSize,
String fileExtName, Set<MetaData> metaDataSet) {
// 上傳文件
StorageUploadFileCommand command = new StorageUploadFileCommand(client.getStoreIndex(), inputStream,
fileExtName, fileSize, false);
StorePath path = connectionManager.executeFdfsCmd(client.getInetSocketAddress(), command);
// 上傳metadata
if (hasMetaData(metaDataSet)) {
StorageSetMetadataCommand setMDCommand = new StorageSetMetadataCommand(path.getGroup(), path.getPath(),
metaDataSet, StorageMetadataSetType.STORAGE_SET_METADATA_FLAG_OVERWRITE);
connectionManager.executeFdfsCmd(client.getInetSocketAddress(), setMDCommand);
}
return path;
}
/**
* 上傳縮略圖
*
* @param client
* @param inputStream
* @param masterFilename
* @param fastImageFile
*/
private void uploadThumbImage(StorageNode client, InputStream inputStream,
String masterFilename, FastImageFile fastImageFile) {
ByteArrayInputStream thumbImageStream = null;
ThumbImage thumbImage = fastImageFile.getThumbImage();
try {
//生成縮略圖片
thumbImageStream = generateThumbImageStream(inputStream, thumbImage);
// 獲取文件大小
long fileSize = thumbImageStream.available();
// 獲取配置縮略圖前綴
String prefixName = thumbImage.getPrefixName();
LOGGER.error("獲取到縮略圖前綴{}", prefixName);
StorageUploadSlaveFileCommand command = new StorageUploadSlaveFileCommand(thumbImageStream, fileSize,
masterFilename, prefixName, fastImageFile.getFileExtName());
connectionManager.executeFdfsCmd(client.getInetSocketAddress(), command);
} catch (IOException e) {
LOGGER.error("upload ThumbImage error", e);
throw new FdfsUploadImageException("upload ThumbImage error", e.getCause());
} finally {
IOUtils.closeQuietly(thumbImageStream);
}
}
/**
* 上傳縮略圖
*
* @param client
* @param inputStream
* @param masterFilename
* @param fileExtName
*/
private void uploadThumbImage(StorageNode client, InputStream inputStream, String masterFilename,
String fileExtName) {
ByteArrayInputStream thumbImageStream = null;
try {
thumbImageStream = generateThumbImageByDefault(inputStream);// getFileInputStream
// 獲取文件大小
long fileSize = thumbImageStream.available();
// 獲取縮略圖前綴
String prefixName = thumbImageConfig.getPrefixName();
LOGGER.debug("上傳縮略圖主文件={},前綴={}", masterFilename, prefixName);
StorageUploadSlaveFileCommand command = new StorageUploadSlaveFileCommand(thumbImageStream, fileSize,
masterFilename, prefixName, fileExtName);
connectionManager.executeFdfsCmd(client.getInetSocketAddress(), command);
} catch (IOException e) {
LOGGER.error("upload ThumbImage error", e);
throw new FdfsUploadImageException("upload ThumbImage error", e.getCause());
} finally {
IOUtils.closeQuietly(thumbImageStream);
}
}
/**
* 生成縮略圖
*
* @param inputStream
* @param thumbImage
* @return
* @throws IOException
*/
private ByteArrayInputStream generateThumbImageStream(InputStream inputStream,
ThumbImage thumbImage) throws IOException {
//根據傳入配置生成縮略圖
if (thumbImage.isDefaultConfig()) {
//在中間修改配置,這里不是一個很好的實踐,如果有時間再進行優化
thumbImage.setDefaultSize(thumbImageConfig.getWidth(), thumbImageConfig.getHeight());
return generateThumbImageByDefault(inputStream);
} else if (thumbImage.getPercent() != 0) {
return generateThumbImageByPercent(inputStream, thumbImage);
} else {
return generateThumbImageBySize(inputStream, thumbImage);
}
}
/**
* 根據傳入比例生成縮略圖
*
* @param inputStream
* @param thumbImage
* @return
* @throws IOException
*/
private ByteArrayInputStream generateThumbImageByPercent(InputStream inputStream,
ThumbImage thumbImage) throws IOException {
LOGGER.debug("根據傳入比例生成縮略圖");
// 在內存當中生成縮略圖
ByteArrayOutputStream out = new ByteArrayOutputStream();
//@formatter:off
Thumbnails
.of(inputStream)
.scale(thumbImage.getPercent())
.toOutputStream(out);
//@formatter:on
return new ByteArrayInputStream(out.toByteArray());
}
/**
* 根據傳入尺寸生成縮略圖
*
* @param inputStream
* @param thumbImage
* @return
* @throws IOException
*/
private ByteArrayInputStream generateThumbImageBySize(InputStream inputStream,
ThumbImage thumbImage) throws IOException {
LOGGER.debug("根據傳入尺寸生成縮略圖");
// 在內存當中生成縮略圖
ByteArrayOutputStream out = new ByteArrayOutputStream();
//@formatter:off
Thumbnails
.of(inputStream)
.size(thumbImage.getWidth(), thumbImage.getHeight())
.toOutputStream(out);
//@formatter:on
return new ByteArrayInputStream(out.toByteArray());
}
/**
* 獲取縮略圖
*
* @param inputStream
* @return
* @throws IOException
*/
private ByteArrayInputStream generateThumbImageByDefault(InputStream inputStream) throws IOException {
LOGGER.debug("根據默認配置生成縮略圖");
// 在內存當中生成縮略圖
ByteArrayOutputStream out = new ByteArrayOutputStream();
//@formatter:off
Thumbnails
.of(inputStream)
.size(thumbImageConfig.getWidth(), thumbImageConfig.getHeight())
.toOutputStream(out);
//@formatter:on
return new ByteArrayInputStream(out.toByteArray());
}
/**
* 刪除文件
*/
@Override
public void deleteFile(String filePath) {
StorePath storePath = StorePath.parseFromUrl(filePath);
super.deleteFile(storePath.getGroup(), storePath.getPath());
}
public String getDefaultStorageIp() {
return defaultStorageIp;
}
public void setDefaultStorageIp(String defaultStorageIp) {
this.defaultStorageIp = defaultStorageIp;
}
public int getDefaultStoragePort() {
return defaultStoragePort;
}
public void setDefaultStoragePort(int defaultStoragePort) {
this.defaultStoragePort = defaultStoragePort;
}
}
搞定!重啟項目 上傳,下載為所欲為。
注:
- 因為Fdfs本身是沒有密碼的,所以大多都是局域網使用,還請謹慎暴露外網。
- 本項目為練手項目,Storage節點也就一個,所以沒有考慮多個存儲節點分配的問題。在此也就拋磚引玉了。寫的不好,大佬們勿噴。