写在最前
如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
源码地址(后端):https://gitee.com/csps/mingyue
源码地址(前端):https://gitee.com/csps/mingyue-ui
文档地址:https://gitee.com/csps/mingyue/wikis
OSS 基础表设计
1. OSS对象存储表
DROP TABLE IF EXISTS sys_oss;
CREATE TABLE sys_oss (
oss_id BIGINT(20) NOT NULL COMMENT 'OSS对象ID',
file_name VARCHAR(255) NOT NULL DEFAULT '' COMMENT '文件名',
original_name VARCHAR(255) NOT NULL DEFAULT '' COMMENT '原名',
file_suffix VARCHAR(10) NOT NULL DEFAULT '' COMMENT '文件后缀名',
file_url VARCHAR(500) NOT NULL COMMENT '文件URL',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
create_by VARCHAR(64) DEFAULT '' COMMENT '上传人',
update_time DATETIME DEFAULT NULl COMMENT '更新时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新人',
service VARCHAR(20) NOT NULL DEFAULT 'minio' COMMENT '服务商',
primary key (oss_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='OSS对象存储表';
2. OSS对象存储动态配置表
DROP TABLE IF EXISTS sys_oss_config;
CREATE TABLE sys_oss_config (
oss_config_id BIGINT(20) NOT NULL COMMENT 'OSS动态配置ID',
config_key VARCHAR(20) NOT NULL DEFAULT '' COMMENT '配置key',
access_key VARCHAR(255) DEFAULT '' COMMENT 'accessKey',
secret_key VARCHAR(255) DEFAULT '' COMMENT '秘钥',
bucket_name VARCHAR(255) DEFAULT '' COMMENT '桶名称',
prefix VARCHAR(255) DEFAULT '' COMMENT '前缀',
endpoint VARCHAR(255) DEFAULT '' COMMENT '访问站点',
domain VARCHAR(255) DEFAULT '' COMMENT '自定义域名',
is_https CHAR(1) DEFAULT 'N' COMMENT '是否https(Y是 N否)',
region VARCHAR(255) DEFAULT '' COMMENT '域',
access_policy CHAR(1) NOT NULL DEFAULT '1' COMMENT '桶权限类型(0-private 1-public 2-custom)',
status CHAR(1) DEFAULT '1' COMMENT '是否默认(0是 1否)',
extend VARCHAR(255) DEFAULT '' COMMENT '扩展字段',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (oss_config_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='OSS对象存储动态配置表';
INSERT INTO `sys_oss_config` VALUES (1, 'minio', 'd6zVm5AP07uGCqSmsTxe', 'Vsm6qQDHgGchukEpyEoeX3dTe7fic60nTi8D9a0I', 'mingyue', '', 'mingyue-minio:5000', '', 'N', '', '1', '0', '', 'admin', '2023-09-11 17:50:40', 'admin', '2023-09-11 17:50:40');
COMMIT;
OSS 配置加载
初始化OSS配置
@Override
public void init() {
List<SysOssConfig> list = this.list();
// 加载 OSS 初始化配置
for (SysOssConfig config : list) {
String configKey = config.getConfigKey();
if ("0".equals(config.getStatus())) {
RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, configKey);
}
RedisUtils.setCacheMapValue(OssConstant.SYS_OSS_CONFIG, config.getConfigKey(), JSONUtil.toJsonStr(config));
}
}
OssApplicationRunner
@Slf4j
@Component
@RequiredArgsConstructor
public class OssApplicationRunner implements ApplicationRunner {
private final SysOssConfigService sysOssConfigService;
@Override
public void run(ApplicationArguments args) {
sysOssConfigService.init();
log.info("初始化 OSS 配置成功");
}
}
改进 OssFactory
@Slf4j
public class OssFactory {
private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
/**
* 获取默认实例
*/
public static OssClient instance() {
// 获取redis 默认类型
String configKey = RedisUtils.getCacheObject(OssConstant.DEFAULT_CONFIG_KEY);
if (StrUtil.isEmpty(configKey)) {
throw new OssException("文件存储服务类型无法找到!");
}
return instance(configKey);
}
/**
* 根据类型获取实例
*/
public static OssClient instance(String configKey) {
String json = RedisUtils.getCacheMapValue(OssConstant.SYS_OSS_CONFIG, configKey);
if (json == null) {
throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
}
OssProperties properties = JSONUtil.toBean(json, OssProperties.class);
OssClient client = CLIENT_CACHE.get(configKey);
if (client == null) {
CLIENT_CACHE.put(configKey, new OssClient(configKey, properties));
log.info("创建OSS实例 key => {}", configKey);
return CLIENT_CACHE.get(configKey);
}
// 配置不相同则重新构建
if (!client.checkPropertiesSame(properties)) {
CLIENT_CACHE.put(configKey, new OssClient(configKey, properties));
log.info("重载OSS实例 key => {}", configKey);
return CLIENT_CACHE.get(configKey);
}
return client;
}
}
移除 Nacos OSS 配置
因为从数据库加载配置,所以不在需要 Nacos 配置了
oss:
configKey: minio
endpoint: mingyue-minio:5000
domain:
prefix:
accessKey: d6zVm5AP07uGCqSmsTxe
secretKey: Vsm6qQDHgGchukEpyEoeX3dTe7fic60nTi8D9a0I
bucketName: mingyue
region:
isHttps: N
accessPolicy: 1
上传测试
{
"code": 200,
"msg": "操作成功",
"data": {
"ossId": "1701490497677180930",
"fileName": "2023-09-12/d1b5389a465f4bf7985844916d785c06.png",
"originalName": "head_1.png",
"fileSuffix": ".png",
"fileUrl": "http://mingyue-minio:5000/mingyue/2023-09-12/d1b5389a465f4bf7985844916d785c06.png",
"createTime": "2023-09-12 14:58:41",
"createBy": "mingyue",
"service": "minio"
}
}
OSS 上传信息保存
/**
* 构建上传文件返回信息
* @param originalFilename 原始文件名
* @param suffix 文件后缀
* @param configKey 配置key
* @param uploadResult OSS服务返回结果
* @return
*/
private SysOssVo buildResult(String originalFilename, String suffix, String configKey, UploadResult uploadResult) {
SysOss oss = new SysOss();
oss.setFileUrl(uploadResult.getFileUrl());
oss.setFileSuffix(suffix);
oss.setFileName(uploadResult.getFileName());
oss.setOriginalName(originalFilename);
oss.setService(configKey);
this.save(oss);
SysOssVo sysOssVo = BeanUtil.toBean(oss, SysOssVo.class);
return this.matchingUrl(sysOssVo);
}
删除文件
逻辑实现
删除数据库记录的同时需要删除OSS服务对应的文件
@Override
public Boolean deleteByOssIds(List<Long> ossIds) {
List<SysOss> list = this.listByIds(ossIds);
if (CollUtil.isEmpty(list)) {
return Boolean.FALSE;
}
for (SysOss sysOss : list) {
OssClient storage = OssFactory.instance(sysOss.getService());
storage.delete(sysOss.getFileUrl());
}
return this.removeBatchByIds(ossIds);
}
删除接口
@DeleteMapping("/{ossIds}")
@Operation(summary = "删除OSS对象存储",
parameters = { @Parameter(name = "ossIds", description = "oss对象Ids", required = true) })
public R<Boolean> remove(@NotEmpty(message = "主键不能为空") @PathVariable List<Long> ossIds) {
return R.ok(sysOssService.deleteByOssIds(ossIds));
}
删除测试
删除前打开文件查看:http://mingyue-minio:5000/mingyue/2023-09-12/d1b5389a465f4bf7985844916d785c06.png
curl -X 'DELETE' \
'http://mingyue-gateway:9100/oss/sysOss/1701490497677180930' \
-H 'accept: */*' \
-H 'Authorization: 6H1mlA91zFRa5yEpIl2b2mnCjbG5B44f'
删除后再打开
<Error>
<Code>NoSuchKey</Code>
<Message>The specified key does not exist.</Message>
<Key>2023-09-12/d1b5389a465f4bf7985844916d785c06.png</Key>
<BucketName>mingyue</BucketName>
<Resource>/mingyue/2023-09-12/d1b5389a465f4bf7985844916d785c06.png</Resource>
<RequestId>17841B7B6B41C214</RequestId>
<HostId>dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8</HostId>
</Error>
下载文件
逻辑实现
@Override
public void download(Long ossId, HttpServletResponse response) throws IOException {
SysOss sysOss = this.getById(ossId);
if (ObjectUtil.isNull(sysOss)) {
throw new ServiceException("文件数据不存在!");
}
FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
OssClient storage = OssFactory.instance(sysOss.getService());
try (InputStream inputStream = storage.getObjectContent(sysOss.getFileUrl())) {
int available = inputStream.available();
IoUtil.copy(inputStream, response.getOutputStream(), available);
response.setContentLength(available);
}
catch (Exception e) {
throw new ServiceException(e.getMessage());
}
}
下载接口
@GetMapping("/download/{ossId}")
@Operation(summary = "下载OSS对象存储",
parameters = { @Parameter(in = ParameterIn.PATH, name = "ossIds", description = "oss对象Ids", required = true) })
public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
sysOssService.download(ossId, response);
}
下载测试
curl -X 'GET' \
'http://mingyue-gateway:9100/oss/sysOss/download/1701492631160229889' \
-H 'accept: */*'
小结
文件服务基础已经完成啦,接下来可以自己尝试集成其他厂商的 OSS 服务。
文件服务更新暂告一段落,接下来弄一弄搜索服务,打算用 ES(Elasticsearch)作为搜索服务基础工具,期待一下吧~~