📚对象存储
对象存储可以动态地对存储空间进行扩展,提高数据持久性以及随时都可以进行数据迁移,最重要的是服务器与存储空间进行了分离。
📕流程图
为了实现不同平台的对象存储,通过使用各个平台的对象存储的SDK,发现大部分平台至少有四个参数:两个校验身份的参数、地域参数、存储桶,下面以腾讯云的对象存储 COS 为例。
腾讯云 对象存储 COS
// 1 初始化用户身份信息(secretId, secretKey)。
String secretId = "SECRETID";
String secretKey = "SECRETKEY";
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
// 2 设置 bucket 的地域
Region region = new Region("COS_REGION");
ClientConfig clientConfig = new ClientConfig(region);
// 这里建议设置使用 https 协议
// 从 5.6.54 版本开始,默认使用了 https
clientConfig.setHttpProtocol(HttpProtocol.https);
// 3 生成 cos 客户端。
COSClient cosClient = new COSClient(cred, clientConfig);
// 4 存储桶
String bucket = "examplebucket-1250000000"; //存储桶名称,格式:BucketName-APPID
CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucket);
// 5 设置 bucket 的权限为 Private(私有读写)、其他可选有 PublicRead(公有读私有写)、PublicReadWrite(公有读写)
createBucketRequest.setCannedAcl(CannedAccessControlList.Private);
try{
Bucket bucketResult = cosClient.createBucket(createBucketRequest);
} catch (CosServiceException serverException) {
serverException.printStackTrace();
} catch (CosClientException clientException) {
clientException.printStackTrace();
}
在上面 secretId 和 secretKey 就是用来确定身份的密钥,类似于 SSH 登录服务器需要用户名和密码一样。在腾讯云的对象存储 COS 里上传/下载/管理文件的域名是由 bucket 和 region 组成的,即<BucketName-APPID>.cos.<Region>.myqcloud.com。
在<BucketName-APPID>.cos.<Region>.myqcloud.com 命名规则里面 BucketName 和 Region 可以由我们改变以外,APPID 是不可以改变的,目前来看 APPID 是一个常数。
可能你看见了我创建了存储桶,实际上是没有必要利用代码来创建,这往往加大了我们的代码工作量,毕竟存储桶可以利用平台来手动创建。
有了云存储客户端,那么基本上就可以通过调用相应的数据接口来对对象存储进行操作,例如存储桶的操作,文件的操作都是可以通过客户端的接口来操作的。
📕代码思路
-
配置信息放在数据库
将对象存储的连接信息放在数据库里面,专门使用一张表来存放你所拥有的不同平台的不同存储桶的信息。
这样当我需要使用不同存储桶的时候,只需要切换配置信息就可以实现在不同的平台按照需求来存放数据。例如存储桶里面的文件进行迁移会非常方便,以及多个存储桶做备份处理。
-
统一接口
每个平台的对象存储其文件操作接口各有差异性,却可以通过接口来实现统一操作。
📕代码实现和表
sys_storage
字段 | 类型 | 长度 | 主键 | 非空 | 默认值 | 注释 |
---|---|---|---|---|---|---|
storage_id | char | 19 | √ | √ | —— | ID |
storage_type | char | 1 | —— | √ | —— | 存储平台 0 本地 1 七牛云 2 阿里云 3 腾讯云 |
access_id | varchar | 50 | —— | —— | —— | 用户身份信息accessId |
access_key | varchar | 50 | —— | —— | —— | 用户身份信息accessKey |
endpoint | varchar | 100 | —— | —— | —— | 所属地域 |
bucket | varchar | 100 | —— | —— | —— | 存储桶名称 |
host | varchar | 255 | —— | —— | —— | 访问地址 |
callback_url | varchar | 255 | —— | —— | —— | 回调地址 |
storage_dir | varchar | 255 | —— | —— | —— | 存储路径 |
create_by | char | 20 | —— | —— | —— | 创建人 |
create_time | datetime | —— | —— | —— | —— | 创建时间 |
update_by | char | 20 | —— | —— | —— | 修改人 |
update_time | datetime | —— | —— | —— | —— | 修改时间 |
CloudStorage.java
import java.io.File;
import java.io.FileInputStream;
/**
* @author hjhcos
* @date 2022/05/22
* 云存储
*/
public interface CloudStorage {
/**
* 云存储初始化
*/
void init();
/**
* 初始化客户端
*/
void initClient();
/**
* 创建云存储服务
*/
void createCloudStorageService();
/**
* 检验存储方式
* @param tag 检查云存储存储方式
* @return 存储方式平台是否符号
*/
boolean checkStorageMode(StorageTag tag);
/**
* 上传文件
* @param file 文件
* @return 文件路径
* @throws Exception 服务器或客户端异常
*/
String uploadFile(File file) throws Exception;
/**
* 下载文件
* @param filename 文件地址
* @return 文件
* @throws Exception 服务器或客户端异常
*/
FileInputStream downloadFile(String filename) throws Exception;
/**
* 复制文件
* @param srcKey 复制的源文件路径
* @param destKey 复制的目的地文件路径
* @return 文件路径
* @throws Exception 服务器或客户端异常
*/
String copyFile(String srcKey, String destKey) throws Exception;
/**
* 移动文件
* @param srcKey 移动的源文件路径
* @param destKey 移动的目的地文件路径
* @return 文件路径
* @throws Exception 服务器或客户端异常
*/
String moveFile(String srcKey, String destKey) throws Exception;
/**
* 文件是否存在
* @param bucketName 存储桶名称
* @param key 对象在存储桶中的唯一标识 文件路径
* @return 文件是否存在
* @throws Exception 服务器或客户端异常
*/
boolean doesFileExist(String bucketName, String key) throws Exception;
/**
* 删除文件
* @param srcKey 移动的源文件路径
* @return 文件路径
* @throws Exception 服务器或客户端异常
*/
String deleteFile(String srcKey) throws Exception;
}
CloudStorageImpl.java
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pearadmin.modules.storage.base.CloudStorage;
import com.pearadmin.modules.storage.base.StorageTag;
import com.pearadmin.modules.storage.domain.SysStorage;
import com.pearadmin.modules.storage.mapper.SysStorageMapper;
import java.util.List;
/**
* @author hjhcos
* @date 2022/05/24
* 云存储模板
*/
public abstract class CloudStorageImpl extends ServiceImpl<SysStorageMapper, SysStorage> implements CloudStorage {
protected StorageTag storageTag;
/**
* 云存储配置对象列表
*/
protected static List<SysStorage> sysStorageList;
/**
* 当前云存储配置对象
*/
protected static SysStorage currentSysStorage;
/**
* 云存储初始化
*/
@Override
public void init() {
SysStorage sysStorage = new SysStorage();
sysStorage.setStorageType(storageTag.getKey());
sysStorageList = baseMapper.selectSysStorageList(sysStorage);
currentSysStorage = sysStorageList.get(0);
createCloudStorageService();
}
}
CosCloudStorageImpl.java
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pearadmin.modules.storage.domain.SysStorage;
import com.pearadmin.modules.storage.base.CloudStorage;
import com.pearadmin.modules.storage.base.StorageTag;
import com.pearadmin.modules.storage.mapper.SysStorageMapper;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.model.PutObjectResult;
import com.qcloud.cos.region.Region;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.stereotype.Service;
import javax.activation.MimetypesFileTypeMap;
import java.io.File;
import java.io.FileInputStream;
import java.util.List;
/**
* @author hjhcos
* @date 2022/05/22
* 腾讯云对象存储 COS
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Service("CosCloudStorageImpl")
public class CosCloudStorageImpl extends CloudStorageImpl {
/**
* 云存储标识符
*/
private final StorageTag TAG = StorageTag.COS;
/**
* 云存储对象
*/
private static COSClient cosClient;
/**
* 初始化客户端
*/
@Override
public void initClient() {
super.storageTag = TAG;
init();
}
/**
* 云存储服务创建
*/
@Override
public void createCloudStorageService(){
// 初始化用户身份信息
COSCredentials cred = new BasicCOSCredentials(currentSysStorage.getAccessId(), currentSysStorage.getAccessKey());
// 设置 Bucket 的地域
Region region = new Region(currentSysStorage.getEndpoint());
ClientConfig clientConfig = new ClientConfig(region);
clientConfig.setHttpProtocol(HttpProtocol.https);
// 生成客户端
cosClient = new COSClient(cred, clientConfig);
// 关闭客户端
// cosClient.shutdown();
}
/**
* 检验存储方式
*
* @param tag 存储方式
* @return 存储方式是否一致
*/
@Override
public boolean checkStorageMode(StorageTag tag) {
if(tag == null){
return currentSysStorage.getStorageType().equals(TAG.getKey());
}
return currentSysStorage.getStorageType().equals(tag.getKey());
}
/**
* 上传文件
*
* @param file 文件
* @return 文件路径
* @throws Exception 服务器或客户端异常
*/
@Override
public String uploadFile(File file) throws Exception {
String bucketName = currentSysStorage.getBucket();
String key = currentSysStorage.getStorageDir() + '/' + file.getName();
String endpoint = currentSysStorage.getEndpoint();
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, file);
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(new MimetypesFileTypeMap().getContentType(file) + "; charset=utf-8");
putObjectRequest.setMetadata(objectMetadata);
PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
return String.format("%s.cos.%s.myqcloud.com%s", bucketName, endpoint, key);
}
/**
* 下载文件
*
* @param filename 文件地址
* @return 文件
* @throws Exception 服务器或客户端异常
*/
@Override
public FileInputStream downloadFile(String filename) throws Exception {
return null;
}
/**
* 复制文件
*
* @param srcKey 复制的源文件路径
* @param destKey 复制的目的地文件路径
* @return 文件路径
* @throws Exception 服务器或客户端异常
*/
@Override
public String copyFile(String srcKey, String destKey) throws Exception {
return null;
}
/**
* 移动文件
*
* @param srcKey 移动的源文件路径
* @param destKey 移动的目的地文件路径
* @return 文件路径
* @throws Exception 服务器或客户端异常
*/
@Override
public String moveFile(String srcKey, String destKey) throws Exception {
return null;
}
/**
* 文件是否存在
*
* @param bucketName 存储桶名称
* @param key 对象在存储桶中的唯一标识 文件路径
* @return 文件是否存在
* @throws Exception 服务器或客户端异常
*/
@Override
public boolean doesFileExist(String bucketName, String key) throws Exception {
return false;
}
/**
* 删除文件
*
* @param srcKey 移动的源文件路径
* @return 文件路径
* @throws Exception 服务器或客户端异常
*/
@Override
public String deleteFile(String srcKey) throws Exception {
return null;
}
}
🖊注意事项
- 根目录以 / 开头的路径不同的平台处理方式不同
- 腾讯云默认去掉
- 华为云默认创建一个空文件夹
- 阿里云默认会直接报错
- 七牛云默认创建一个未知文件夹
❌文件内容乱码现象
-
没有声明字符编码
该文档未声明字符编码,因此编码是凭内容猜测出来的。字符编码需使用字节顺序标记(BOM),或是在 Content-Type HTTP 头中进行声明。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, file); ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentType(new MimetypesFileTypeMap().getContentType(file) + "; charset=utf-8"); putObjectRequest.setMetadata(objectMetadata);
参考文献
如果你是无意刷到这篇文章并看到这里,希望你给我的文章来一个赞赞👍👍。如果你不同意其中的内容或有什么问题都可以在下方评论区留下你的想法或疑惑,谢谢你的支持!!😀😀