026-从零搭建微服务-文件服务(二)

news2024/12/24 20:58:41

写在最前

如果这个项目让你有所收获,记得 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: */*'

image-20230912190918686

小结

文件服务基础已经完成啦,接下来可以自己尝试集成其他厂商的 OSS 服务。

文件服务更新暂告一段落,接下来弄一弄搜索服务,打算用 ES(Elasticsearch)作为搜索服务基础工具,期待一下吧~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1004591.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

探索程序员需要掌握的算法?

文章目录 一&#xff1a;引言二&#xff1a;常见算法介绍三&#xff1a;重点算法总结 &#x1f389;欢迎来到数据结构学习专栏~探索程序员需要掌握的算法&#xff1f; ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&#xff1a;IT陈寒的博客&#x1f388;该系列文章…

SoC性能指标ARM内核运算能力

自动驾驶芯片常用的性能评价指标:TOPS,DMIPS,GFLOPS分别说的是啥&#xff1f; TOPS Tera Operation Per Second&#xff0c;表示每秒钟可以进行的操作数量&#xff0c;用于衡量自动驾驶的算力。 众所周知&#xff0c;汽车上最常用的传感器是摄像头&#xff0c;而与之对应的计…

【K8S系列】深入解析k8s网络插件—Canal

序言 做一件事并不难&#xff0c;难的是在于坚持。坚持一下也不难&#xff0c;难的是坚持到底。 文章标记颜色说明&#xff1a; 黄色&#xff1a;重要标题红色&#xff1a;用来标记结论绿色&#xff1a;用来标记论点蓝色&#xff1a;用来标记论点 在现代容器化应用程序的世界中…

Java密码学之数字签名

密码系统是加密技术及其附带基础工具的实现&#xff0c;以提供信息安全服务。基本密码系统的各种组件是明文&#xff0c;加密算法&#xff0c;密文&#xff0c;解密算法&#xff0c;加密密钥和解密密钥。其中加密密钥和解密密钥是&#xff1a; 加密密钥是发件人已知的值。发送…

Jmeter——结合Allure展示测试报告 _

在平时用jmeter做测试时&#xff0c;生成报告的模板&#xff0c;不是特别好。大家应该也知道allure报告&#xff0c;页面美观。 先来看效果图&#xff0c;报告首页&#xff0c;如下所示&#xff1a; ​编辑 报告详情信息&#xff0c;如下所示&#xff1a; ​编辑 运行run.…

van-list 下拉刷新 触底分页 触底分页事件只加载一次

我是 头部是筛选的条件&#xff0c;&#xff0c;更换不同的状态&#xff0c;显示不同的列表数据&#xff0c;比如 审批中数据是 对的&#xff0c;触底分页也是对的&#xff0c;如果我切换一个状态的话&#xff0c;总共是 15条数据&#xff0c;但是 切换了状态只显示第一页的数据…

Java“牵手”拼多多商品详情数据,拼多多商品详情接口,拼多多API接口申请指南

拼多多商品详情API接口的作用是获取拼多多平台上某个商品的详细信息&#xff0c;包括商品标题、价格、图片、规格、参数、店铺信息等。 开发者可以通过该接口获取到商品的原始数据&#xff0c;方便进行数据分析、价格比较、爬取等操作。通过该接口获取到的商品详情数据可以结合…

(2023|ICLR,)用于一般噪声反演问题的扩散后验采样

Diffusion posterior sampling for general noisy inverse problems 公众号&#xff1a;EDPJ&#xff08;添加 VX&#xff1a;CV_EDPJ 进交流群获取资料&#xff09; 目录 0. 摘要 1. 简介 2. 背景 2.1 基于分数的扩散模型 2.2 用扩散模型求解逆问题 3. 扩散后验采样…

接口优化1

接口优化 文章目录 接口优化1. 内容概述2. 集成RabbitMQ2.1 下载2.2 SpringBoot集成RabbitMQ 快速入门1.相关配置2.创建发送者者和接收者 2.3 rabbitmq四种交换模式2.4 秒杀接口优化 1. 内容概述 核心思路:减少对数据库的访问&#xff0c;利用Redis的高并发特性来实现。 系统初…

20.Xaml GroupBox控件 ---->带标题的内容控件

1.运行效果 2.运行源码 a.Xaml源码 <Window x:Class="testView.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.mic…

【excel加密】excel工作表保护和工作簿保护,有什么区别?

Excel不能编辑大家都知道时设置了工作表保护&#xff0c;在我们平时设置或取消工作表保护的时候也能够看到旁边的工作簿保护&#xff0c;那它的作用是什么呢&#xff1f;今天我们来看一下&#xff0c;工作表保护和工作簿保护的区别是什么吧&#xff01; 先统一讲一下如何设置保…

蓝色光标发布营销行业模型“Blue AI” 人机协同重构产业新格局

“Blue AI 行业模型的发布&#xff0c;标志着蓝色光标Al战略迈入新阶段”。蓝色光标集团CEO潘飞在发布会现场表示&#xff0c;把握好AI机会&#xff0c;会让蓝色光标更加接近于一家全球化的科技公司。今后&#xff0c;我们将继续沿着三个方面迭代与迈进&#xff1a;一是生产效率…

如何在多版本C#工程上添加程序集

给出工程配置文件添加程序集示例&#xff1a; <ItemGroup Condition"$(TargetFramework) net451"> <PackageReference Include"MySql.Data" Version"6.7.9" /> <Reference Include"System.Configuration" /&…

台式电脑组装爬坑之路

台式电脑十大部件 CPU 主板 内存 硬盘 机箱 电源 显卡 CPU散热器 显示器 鼠标 键盘 基本知识

Java面试笔试acm版输入

首先区分scanner.nextInt()//输入一个整数&#xff0c;只能读取一个数&#xff0c;空格就停止。 scanner.next()//输入字符串&#xff0c;只能读取一个字符串&#xff0c;空格就停止&#xff0c;但是逗号不停止。 scanner.nextLine() 读取一行&#xff0c;换行停止&#xff0c…

视频可以裁剪画面大小吗?教你几种简单裁切方法

视频可以裁剪画面大小吗&#xff1f;现在人们拍摄的视频越来越多&#xff0c;因此&#xff0c;视频编辑已成为一项必要的技能。视频裁剪是视频编辑中最基本的操作之一&#xff0c;它可以帮助你删除不需要的内容、调整画面大小和焦点&#xff0c;以及改善视频的视觉效果。那么视…

【uniapp】使用canvas组件编译到微信小程序兼容出错问题

使用uniapp编译跨平台项目会遇到不少兼容问题&#xff0c;这里主要讲canvas组件的&#xff0c;编译到微信小程序会有兼容出错问题&#xff0c;这里给讲一下解决方案&#xff0c;希望有帮助。 常见问题 draw无法绘制图形 如果使用CanvasContext绘制&#xff0c;以下代码&…

profinet是什么?

profinet是什么&#xff1f; 参考&#xff1a;一文读懂Profibus、Profinet、Ethernet的区别 PROFINETPROFIbusetherNET&#xff0c;把Profibus的主从结构移植到以太网上&#xff0c;所以profinet会有Controller和Device&#xff0c;他们的关系可以简单的对应于profibus的Maste…

动态 import

文章目录 动态 import1. 动态导入2. 语法3. 描述4. 模块命名空间对象5. 使用示例6. 动态导入的原理7. 兼容 动态 import import() 语法&#xff0c;通常称为动态导入&#xff0c;是一种类似函数的表达式&#xff0c;允许将 ECMAScript 模块异步和动态地加载到可能的非模块环境…

【python自动化应用】借助ChatGPT与Python轻松实现办公自动化 —— AIC松鼠活动第九期

背景&#xff1a;当今的工作环境中&#xff0c;高效和快速地完成日常任务对于个人和机构都至关重要。许多人正在利用Python自动化来提高他们的工作效率。Python自动化可以帮助您自动完成繁琐的、重复的、容易出错的任务&#xff0c;从而节省时间和精力。 Python自动化有很多应用…