小說精品屋-plus數據服務層的設計和實現

前言

小說精品屋中集成了比較多的數據服務。比如緩存相關的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)是程序要依賴於抽象接口,不要依賴於具體實現。簡單的說就是要求對抽象進行編程,不要對實現進行編程,這樣就降低了客戶與實現模塊間的耦合。數據服務層的設計正是遵循了依賴倒置原則,實現了模塊間的解耦,切換底層數據存儲只需要修改一個配置項即可。

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