前言
小說精品屋中集成了比較多的數據服務。比如緩存相關的Redis和Ehcache,文件相關的本地、Aliyun OSS和FastDfs,搜索相關的ElasticSearch和Mysql,這些數據服務均可在配置文件中通過一行代碼進行切換底層實現,下面以文件服務爲例來說說數據服務層的具體設計與實現。
文件服務模塊的設計與實現
1. 新建文件服務接口,定義存儲圖片的抽象方法。
package com.java2nb.novel.service;
/**
* @author 11797
*/
public interface FileService {
/**
* 將爬取的網絡圖片轉存爲自己的存儲介質(本地、OSS、fastDfs)
* @param picSrc 爬取的網絡圖片路徑
* @param picSavePath 保存路徑
* @return 新圖片地址
* */
String transFile(String picSrc, String picSavePath);
}
2. 新建本地文件服務實現類,實現文件服務接口,保存文件到本地,並通過@ConditionalOnProperty註解來控制當配置屬性pic.save.storage=local時,該實現類會實例化被Spring容器管理。
package com.java2nb.novel.service.impl;
import com.java2nb.novel.core.utils.Constants;
import com.java2nb.novel.core.utils.FileUtil;
import com.java2nb.novel.service.FileService;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
/**
* @author 11797
*/
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "pic.save", name = "storage", havingValue = "local")
public class LocalFileServiceImpl implements FileService {
@Override
public String transFile(String picSrc, String picSavePath){
return FileUtil.network2Local(picSrc, picSavePath, Constants.LOCAL_PIC_PREFIX);
}
}
3. 新建Aliyun OSS文件服務實現類,實現文件服務接口,保存文件到Aliyun OSS,並通過@ConditionalOnProperty註解來控制當配置屬性pic.save.storage=OSS時,該實現類會實例化被Spring容器管理。
package com.java2nb.novel.service.impl;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.CreateBucketRequest;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.java2nb.novel.core.config.OssProperties;
import com.java2nb.novel.core.utils.Constants;
import com.java2nb.novel.core.utils.FileUtil;
import com.java2nb.novel.service.FileService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import java.io.File;
/**
* @author 11797
*/
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "pic.save", name = "storage", havingValue = "OSS")
@Slf4j
public class OssFileServiceImpl implements FileService {
private final OssProperties ossProperties;
@Override
public String transFile(String picSrc, String picSavePath) {
File file;
String filePath = FileUtil.network2Local(picSrc, picSavePath, Constants.LOCAL_PIC_PREFIX);
if (filePath.contains(Constants.LOCAL_PIC_PREFIX)) {
file = new File(picSavePath+filePath);
} else {
//默認圖片不存儲
return filePath;
}
filePath = filePath.replaceFirst(picSavePath,"");
filePath = filePath.startsWith("/") ? filePath.replaceFirst("/","") : filePath;
OSSClient ossClient = new OSSClient(ossProperties.getEndpoint(), ossProperties.getKeyId(), ossProperties.getKeySecret());
try {
//容器不存在,就創建
if (!ossClient.doesBucketExist(ossProperties.getBucketName())) {
ossClient.createBucket(ossProperties.getBucketName());
CreateBucketRequest createBucketRequest = new CreateBucketRequest(ossProperties.getBucketName());
createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
ossClient.createBucket(createBucketRequest);
}
//上傳文件
PutObjectResult result = ossClient.putObject(new PutObjectRequest(ossProperties.getBucketName(), filePath, file));
//設置權限 這裏是公開讀
ossClient.setBucketAcl(ossProperties.getBucketName(), CannedAccessControlList.PublicRead);
if(result != null) {
return ossProperties.getWebUrl() + "/" + filePath;
}
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
//關閉
ossClient.shutdown();
file.delete();
}
return "/images/default.gif";
}
}
4. 新建FastDfs文件服務實現類,實現文件服務接口,保存文件到FastDfs,並通過@ConditionalOnProperty註解來控制當配置屬性pic.save.storage=fastDfs時,該實現類會實例化被Spring容器管理。
package com.java2nb.novel.service.impl;
import com.github.tobato.fastdfs.domain.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.java2nb.novel.core.utils.Constants;
import com.java2nb.novel.core.utils.FileUtil;
import com.java2nb.novel.service.FileService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
/**
* @author 11797
*/
@Service
@RequiredArgsConstructor
@Slf4j
@ConditionalOnProperty(prefix = "pic.save", name = "storage", havingValue = "fastDfs")
public class FastDfsFileServiceImpl implements FileService {
private final FastFileStorageClient storageClient;
@Value("${fdfs.webUrl}")
private String webUrl;
@Override
public String transFile(String picSrc, String picSavePath) {
File file;
String filePath = FileUtil.network2Local(picSrc, picSavePath, Constants.LOCAL_PIC_PREFIX);
if (filePath.contains(Constants.LOCAL_PIC_PREFIX)) {
file = new File(picSavePath + filePath);
} else {
//默認圖片不存儲
return filePath;
}
try {
FileInputStream inputStream = new FileInputStream(file);
StorePath storePath = storageClient.uploadFile(inputStream, file.length(),
FilenameUtils.getExtension(file.getName()), null);
//這裏額外加上LOCAL_PIC_PREFIX路徑,表明該圖片是個人資源,而不是爬蟲爬取的網絡資源,不需要再次進行轉換,
// 實際訪問時,再通過nginx的rewite指令來重寫路徑,去掉LOCAL_PIC_PREFIX
return webUrl+Constants.LOCAL_PIC_PREFIX+storePath.getFullPath();
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
//刪除
file.delete();
}
return "/images/default.gif";
}
}
5. 新建配置pic.save.storage,用來控制真正被Spring容器管理的實現類。
pic:
save:
type: 2 #圖片保存方式, 1不保存,使用爬取的網絡圖片 ,2保存在自己的存儲介質
storage: local #存儲介質,local:本地,OSS:阿里雲對象存儲,fastDfs:分佈式文件系統
path: /var/pic #圖片保存路徑
6. 在需要使用文件服務的類中注入文件服務接口FileService,具體實現類只有在運行期才知道,由配置屬性pic.save.storage來指定。
@Autowire
private FileService fileService;
@Override
public void updateBookPicToLocal(String picUrl, Long bookId) {
picUrl = fileService.transFile(picUrl, picSavePath);
bookMapper.update(update(book)
.set(BookDynamicSqlSupport.picUrl)
.equalTo(picUrl)
.set(updateTime)
.equalTo(new Date())
.where(id, isEqualTo(bookId))
.build()
.render(RenderingStrategies.MYBATIS3));
}
依賴倒置原則
依賴倒置原則(Dependence Inversion Principle)是程序要依賴於抽象接口,不要依賴於具體實現。簡單的說就是要求對抽象進行編程,不要對實現進行編程,這樣就降低了客戶與實現模塊間的耦合。數據服務層的設計正是遵循了依賴倒置原則,實現了模塊間的解耦,切換底層數據存儲只需要修改一個配置項即可。