前言
前几天自己搭建了一个小的练手项目,其中文件存储使用了 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节点也就一个,所以没有考虑多个存储节点分配的问题。在此也就抛砖引玉了。写的不好,大佬们勿喷。