适配器模式 + Nacos动态配置,实现多源 OSS 无感切换
一、 前言在一个微服务项目里我们的 OSS 云存储服务常常需要配置诸如阿里云、腾讯云、minio 等多个云存储厂商的业务代码而且后续无法确保是否会增添新的云存储厂商。此时倘若我们要修改具体使用的云存储厂商就会致使 controller 层和 service 层发生变动这并不符合低耦合的理念。在这种情况下我们完全可以采用适配器模式来开展项目开发二、适配器模式改造MinioUtils和AliyunUtils被适配者类作为源接口执行原子性操作的具体逻辑各不相同想要把多个OSS共用一个相同的接口返回就需要使用到适配器模式。被适配器类Component publicclassMinioUtil{ Resource private MinioClient minioClient; /** * 创建Bucket桶(文件夹目录) */ publicvoidcreateBucket(String bucket)throws Exception { boolean exists minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build()); if(!exists) { //不存在创建 minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build()); } } /** * 上传文件 * inputStream处理文件的输入流 * bucket桶名称 * objectName桶中的对象名称也就是上传后的文件在存储桶中的存储路径和文件名。 * stream(inputStream处理文件的输入流-1指定缓冲区大小的参数[-1为默认大小] 5242889L指定文件内容长度的上限) */ publicvoiduploadFile(InputStream inputStream, String bucket, String objectName)throws Exception { minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(objectName) .stream(inputStream, -1, 5242889L).build()); } }这是目标接口 (目标抽象类即客户需要的方法)我们想要的不同OSS都可通过该接口进行操作/** * 为了方便切换任何一个oss我们将公共方法抽取为接口由某个oss的实现类去编写具体逻辑 */ publicinterfaceStorageAdapter{ /** * 创建bucket * param bucket */ voidcreateBucket(String bucket); /** * 上传文件 * param multipartFile * param bucket * param objectName */ voiduploadFile(MultipartFile multipartFile, String bucket, String objectName); /** * 获取文件在oss中的url * param bucket * param objectName * return */ String getUrl(String bucket, String objectName); }Minio适配器类通过继承或者组合方式将被适配者类(minioUtils)的接口与目标抽象类的接口转换起来使得客户端可以按照目标抽象类的接口进行操作。/** * Minio相关操作的具体逻辑 */ Log4j2 publicclassMinioStorageAdapterimplementsStorageAdapter{ Resource private MinioUtil minioUtil; Value(${minio.url}) private String url; Override SneakyThrows//Lombok中的注解 会在编译期补上异常处理 publicvoidcreateBucket(String bucket){ minioUtil.createBucket(bucket); } /** * 上传文件 * param multipartFile * param bucket * param objectName 为空文件路径为根目录不为空文件路径为objectName目录下 */ Override SneakyThrows publicvoiduploadFile(MultipartFile multipartFile, String bucket, String objectName){ minioUtil.createBucket(bucket); if(objectName ! null) { minioUtil.uploadFile(multipartFile.getInputStream(), bucket, objectName / multipartFile.getOriginalFilename()); } else { minioUtil.uploadFile(multipartFile.getInputStream(), bucket, multipartFile.getOriginalFilename()); } } /** * 获取文件在oss中的url * param bucket * param objectName * return */ Override public String getUrl(String bucket, String objectName){ return url / bucket / objectName; } }Aliyun适配器类/** * 阿里云oss 具体实现逻辑 */ publicclassAliStorageAdapterimplementsStorageAdapter{ Override publicvoidcreateBucket(String bucket){ System.out.println(aliyun); } Override publicvoiduploadFile(MultipartFile multipartFile, String bucket, String objectName){ } Override public String getUrl(String bucket, String objectName){ returnaliyun; } }三、定义StorageConfig类来获取指定的文件适配器通过Nacos的动态配置读取来得到当前的storageType此时如果想再加入一个新的OSS对象(得到xxUtils jar包等我们无法进行修改)只需新增一个xxadapter适配器类且在Bean注解的方法中加一个else即可。注意这里直接使用new的方式创建实现类(实现类也不需要使用Service注解)而不是先把所有的实现类通过注解定义出来再直接返回对象这样如果新增一个OSS的话不光要加else还需再把实现类通过直接定义出来。Configuration publicclassStorageConfig{ Value(${storage.service.type}) private String storageType; Bean public StorageAdapter storageAdapter(){ if(minio.equals(storageType)) { returnnew MinioStorageAdapter(); } elseif(aliyun.equals(storageType)) { returnnew AliStorageAdapter(); } else { thrownew IllegalArgumentException(为找到对应的文件存储处理器); } } }四、新增FileService防腐提高可维护性/** * FileService防腐层 使用fileService(相当于domain防腐层)与adapter(相当于service层只做原子性操作)进行交互、Utils相当于dao层 */ Component publicclassFileService{ /** * 通过构造函数注入 */ privatefinal StorageAdapter storageAdapter; publicFileService(StorageAdapter storageAdapter){ this.storageAdapter storageAdapter; } /** * 创建bucket * param bucket */ publicvoidcreateBucket(String bucket){ storageAdapter.createBucket(bucket); } /** * 上传图片、返回图片在minio的地址 * param multipartFile * param bucket * param objectName */ public String uploadFile(MultipartFile multipartFile, String bucket, String objectName){ storageAdapter.uploadFile(multipartFile, bucket, objectName); objectName (StringUtils.isEmpty(objectName) ? : objectName /) multipartFile.getOriginalFilename(); return storageAdapter.getUrl(bucket, objectName); } }五、Controller层Controller层通过注入FileService来进行操作RestController Log4j2 publicclassFileController{ Resource//根据名称注入 private FileService fileService; /** * 上传文件, 返回文件在oss中的地址 * param uploadFile:文件, getOriginalFilename获取原始文件名 * param bucket桶名称 * param objectName上传后的文件在存储桶中的存储路径(存储目录) * return String: 返回文件在minio的链接地址 */ PostMapping(/upload) public ResultString upload(MultipartFile uploadFile, String bucket, String objectName)throws Exception { try { Preconditions.checkArgument(!ObjectUtils.isEmpty(uploadFile), 文件不能为空); Preconditions.checkArgument(!StringUtils.isEmpty(bucket), bucket桶名称不能为空); if(log.isInfoEnabled()) { log.info(FileController.upload.uploadFile:{}, bucket:{}, objectName:{}, uploadFile.getOriginalFilename(), bucket, objectName); } String url fileService.uploadFile(uploadFile, bucket, objectName); return Result.ok(url); } catch (Exception e) { log.info(FileController.upload.error{}, e.getMessage(), e); return Result.fail(上传文件失败); } } }六、Nacos搭建6.1 Nacos部署服务器需开启8848、9848端口docker search nacos docker pull nacos/nacos-server # 镜像拉完之后启动脚本 docker run -d \ --name nacos \ --privileged \ --cgroupns host \ --env JVM_XMX256m \ --env MODEstandalone \ --env JVM_XMS256m \ -p 8848:8848/tcp \ -p 9848:9848/tcp \ --restartalways \ -w /home/nacos \ nacos/nacos-server--privileged赋予容器扩展的特权--cgroupns host让容器使用宿主机的 cgroup 命名空间在资源限制方面容器会遵循宿主机规则--env设置Nacos服务使用的jvm参数JVM_XMX最大堆内存为 256mJVM_XMS初始堆内存为 256 m--env MODEstandalonenacos运行模式为单机模式-w /home/nacos指定容器内的工作目录为 “/home/nacos”容器内执行的命令如果涉及到相对路径的操作就会以这个目录作为当前工作目录的基准。8848Nacos服务端端口9848客户端gRPC请求服务端端口6.2 引入nacos客户依赖除了引入nacos依赖还要引入log4j2依赖来输出nacos日志信息SpringCloudAlibaba 版本为2.2.6.RELEASE时springboot版本要为2.3.8.RELEASE!--nacos依赖(配合日志打印nacos信息)-- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-config/artifactId version2.2.6.RELEASE/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-log4j2/artifactId version2.4.2/version /dependency6.3 编写配置文件把nacos相关配置写入bootstrap.yml文件中项目启动后会优先读取。spring: application: name:jc-club-oss#微服务名称 profiles: active:dev#指定环境为开发环境 cloud: nacos: server-addr:117.72.118.73:8848 config: file-extension:yaml#文件后缀名6.4 新增配置管理dataId:jc-club-oss-dev.yaml服务名称开发环境.yaml配置内容:这时spring会根据bootstrap.yml文件中的${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件id来读取配置6.5 添加RefreshScope注解开启热更新在Value注入的变量所在类上添加注解RefreshScop当配置文件内容发生变化后会重新读取当文件更新后Bean已加入到了IOC容器即使storageType属性值变了Bean也无法重新加载。所以在Bean方法上也要加入RefreshScop注解当文件更新后带有此注解的Bean能够自动重新初始化Configuration RefreshScope publicclassStorageConfig{ Value(${storage.service.type}) private String storageType; Bean RefreshScope public StorageAdapter storageAdapter(){ if(minio.equals(storageType)) { returnnew MinioStorageAdapter(); } elseif(aliyun.equals(storageType)) { returnnew AliStorageAdapter(); } else { thrownew IllegalArgumentException(为找到对应的文件存储处理器); } } }6.6 测试1.type为阿里云结果为成功返回aliyun2.修改属性为minio结果为图片成功上传。在配置文件更新时nacos也会打印出对应的日志提示2024-12-03 17:05:50.719 INFO 35932 --- [.72.118.73_8848] o.s.c.e.e.RefreshEventListener : Refresh keys changed: [storage.service.type]看完等于学会点个赞吧

相关新闻

雷电预警装置部署于:机场、景区、学校等场所,有效规避雷击事故

雷电预警装置部署于:机场、景区、学校等场所,有效规避雷击事故

雷电灾害是威胁公共安全的重要自然因素,尤其在人员密集的公共场所,雷击事故可能造成严重后果。现代雷电预警装置通过先进传感技术与智能分析系统,为机场、景区、学校等场所提供实时风险预警,成为防范雷击事故的重要技术手段。一、…

2026/5/17 8:02:59 阅读更多 →
玩转全协议快充移动电源 SOC:高压 SCP + 双向 PD3.0 实战指南

玩转全协议快充移动电源 SOC:高压 SCP + 双向 PD3.0 实战指南

目录 一、先明确核心:全协议快充 SOC 的关键特性 二、选型推荐:3 款量产级全协议快充 SOC(按场景适配) 三、硬件设计:全协议快充的关键落地要点(IP5383 为例) 1. 核心功率器件选型&#xff…

2026/7/4 6:58:24 阅读更多 →
资深鸿蒙开发工程师:技术深度、生态融合与实战精要

资深鸿蒙开发工程师:技术深度、生态融合与实战精要

引言 随着万物互联时代的加速演进,HarmonyOS(鸿蒙系统)作为面向未来的分布式操作系统,正日益展现出其强大的生命力和广阔的应用前景。对于有志于投身鸿蒙生态建设的技术人才而言,成为一名“资深鸿蒙开发工程师”不仅意味着掌握了领先的操作系统开发技术,更代表着能够驾驭…

2026/7/4 12:13:17 阅读更多 →

最新新闻

Terraform工程化实战:模块化、状态治理与计划审计三大策略体系

Terraform工程化实战:模块化、状态治理与计划审计三大策略体系

1. 项目概述:这不是一次简单的“语法升级”,而是一场基础设施即代码的思维跃迁“Terraforming Parts Unknown”这个标题乍看像科幻小说——在未知疆域上重塑地貌。但对每天和云资源、Kubernetes集群、网络拓扑打交道的工程师来说,它精准戳中了…

2026/7/6 10:30:09 阅读更多 →
YOLO目标检测全系列教程:从v1到v13的系统学习指南

YOLO目标检测全系列教程:从v1到v13的系统学习指南

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 这次我们来看一套完整的 YOLO 目标检测算法教程。这套教程号称覆盖了从 YOLOv1 到 YOLOv13 的所有核心算法,内容长达 100 …

2026/7/6 10:30:08 阅读更多 →
Excel COUNT系列函数:数据质量诊断与业务逻辑翻译指南

Excel COUNT系列函数:数据质量诊断与业务逻辑翻译指南

1. 项目概述:Excel中COUNT系列函数不是“数数”那么简单,而是数据质量的守门员 在Excel里写个COUNT(A1:A100),看起来只是统计非空单元格个数——但如果你真这么想,说明你还没摸到Excel数据处理的门槛。我带过二十多个企业数据分析…

2026/7/6 10:28:04 阅读更多 →
Plone 4教职员目录系统:组织级身份服务中间件实践

Plone 4教职员目录系统:组织级身份服务中间件实践

1. 项目概述:一个被低估的组织级信息基础设施重建 Plone 4 时代的 Faculty/Staff Directory(教职员工名录)绝不是简单做个“通讯录页面”——它是一套嵌入在高校数字生态毛细血管里的身份服务中间件。我2012年接手某州立大学文理学院的这个项…

2026/7/6 10:26:00 阅读更多 →
Excel乘法的三层逻辑:从单元格相乘到矩阵运算

Excel乘法的三层逻辑:从单元格相乘到矩阵运算

1. 为什么“乘法”是Excel里最被低估却最该优先掌握的核心能力 很多人刚学Excel时,第一反应是学求和、排序、筛选——这没错,但真正拉开效率差距的,往往不是你会不会用数据透视表,而是你能不能在3秒内把一整列价格打85折、把12个月…

2026/7/6 10:26:00 阅读更多 →
Hermes+Kimi K2.6构建高可用智能体生产流水线

Hermes+Kimi K2.6构建高可用智能体生产流水线

1. 项目概述:这不是一个“搭个API就能跑”的玩具,而是一套可量产、能扛压的智能体生产流水线“万字保姆级教程:HermesKimi K2.6 打造7x24h Agent军团”——光看标题,你可能以为这是又一篇调用大模型API的入门笔记。但实际操作过的…

2026/7/6 10:23:56 阅读更多 →

日新闻

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2与MySQL单元测试兼容性:5个关键SQL语句差异与规避方案1. 单元测试中的数据库兼容性挑战在Java开发领域,单元测试是保证代码质量的重要环节。当应用涉及数据库操作时,测试环境的搭建往往成为开发者的痛点。H2数据库因其轻量级、内存模式和快…

2026/7/6 0:01:17 阅读更多 →
Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘 【免费下载链接】rbtray A fork of RBTray from http://sourceforge.net/p/rbtray/code/. 项目地址: https://gitcode.com/gh_mirrors/rb/rbtray 你是否厌倦了Windows任务栏上密密麻麻的图标&…

2026/7/6 0:01:17 阅读更多 →
Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C 运行时库一键安装终极指南:告别DLL缺失烦恼 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过这样的情况:下载了…

2026/7/6 0:05:19 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/6 8:11:50 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/6 8:11:52 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/6 6:52:56 阅读更多 →

月新闻