当前位置: 首页 > news >正文

SpringBoot + MinIO/S3 文件服务实现:FileService 接口与 FileServiceImpl 详解

在企业项目中,文件上传和管理是非常常见的需求。本文基于 芋道源码 的实现,介绍如何封装一个通用的 文件服务 FileService,支持:

  • 文件上传(保存数据库记录 + 存储文件到 S3/MinIO 等对象存储)

  • 文件下载与删除

  • 文件分页查询

  • 生成预签名上传地址(适合前端直传场景)


一、FileService 接口设计

首先定义 统一的文件服务接口,抽象业务逻辑,便于后续扩展。

public interface FileService {/*** 获得文件分页*/PageResult<FileDO> getFilePage(FilePageReqVO pageReqVO);/*** 保存文件,并返回访问路径*/String createFile(@NotEmpty byte[] content, String name, String directory, String type, String module);/*** 生成预签名地址信息(前端直传场景)*/FilePresignedUrlRespVO getFilePresignedUrl(@NotEmpty String name, String directory);/*** 数据库中保存文件*/Long createFile(FileCreateReqVO createReqVO);/*** 删除文件*/void deleteFile(Long id) throws Exception;/*** 获得文件内容(下载)*/byte[] getFileContent(Long configId, String path) throws Exception;
}

接口设计要点

  1. 上传文件:支持传入字节数组,自动处理文件名、路径、MIME 类型等。

  2. 预签名地址:便于前端直传文件,提升性能。

  3. 删除文件:既删除存储器里的物理文件,也删除数据库记录。

  4. 查询分页:方便后台管理。


二、FileServiceImpl 实现类

@Service
public class FileServiceImpl implements FileService {// 控制路径是否带日期/时间戳static boolean PATH_PREFIX_DATE_ENABLE = true;static boolean PATH_SUFFIX_TIMESTAMP_ENABLE = true;@Resourceprivate FileConfigService fileConfigService;@Resourceprivate FileMapper fileMapper;@Overridepublic PageResult<FileDO> getFilePage(FilePageReqVO pageReqVO) {if ("all".equals(pageReqVO.getModule())) {pageReqVO.setModule(null);}return fileMapper.selectPage(pageReqVO);}@Override@SneakyThrowspublic String createFile(byte[] content, String name, String directory, String type, String module) {// 1. 补全文件信息if (StrUtil.isEmpty(type)) {type = FileTypeUtils.getMineType(content, name);}if (StrUtil.isEmpty(name)) {name = DigestUtil.sha256Hex(content);}if (StrUtil.isEmpty(FileUtil.extName(name))) {String extension = FileTypeUtils.getExtension(type);if (StrUtil.isNotEmpty(extension)) {name = name + extension;}}// 2. 生成上传路径String path = generateUploadPath(name, directory);// 3. 上传文件FileClient client = fileConfigService.getMasterFileClient();Assert.notNull(client, "客户端(master) 不能为空");String url = client.upload(content, path, type);// 4. 保存数据库fileMapper.insert(new FileDO().setConfigId(client.getId()).setName(name).setPath(path).setUrl(url).setModule(module).setType(type).setSize(content.length));return url;}@VisibleForTestingString generateUploadPath(String name, String directory) {String prefix = PATH_PREFIX_DATE_ENABLE? LocalDateTimeUtil.format(LocalDateTimeUtil.now(), PURE_DATE_PATTERN): null;String suffix = PATH_SUFFIX_TIMESTAMP_ENABLE ? String.valueOf(System.currentTimeMillis()) : null;if (StrUtil.isNotEmpty(suffix)) {String ext = FileUtil.extName(name);if (StrUtil.isNotEmpty(ext)) {name = FileUtil.mainName(name) + "_" + suffix + "." + ext;} else {name = name + "_" + suffix;}}if (StrUtil.isNotEmpty(prefix)) {name = prefix + "/" + name;}if (StrUtil.isNotEmpty(directory)) {name = directory + "/" + name;}return name;}@Override@SneakyThrowspublic FilePresignedUrlRespVO getFilePresignedUrl(String name, String directory) {String path = generateUploadPath(name, directory);FileClient client = fileConfigService.getMasterFileClient();FilePresignedUrlRespDTO dto = client.getPresignedObjectUrl(path);return BeanUtils.toBean(dto, FilePresignedUrlRespVO.class,object -> object.setConfigId(client.getId()).setPath(path));}@Overridepublic Long createFile(FileCreateReqVO createReqVO) {FileDO file = BeanUtils.toBean(createReqVO, FileDO.class);fileMapper.insert(file);return file.getId();}@Overridepublic void deleteFile(Long id) throws Exception {FileDO file = validateFileExists(id);FileClient client = fileConfigService.getFileClient(file.getConfigId());Assert.notNull(client, "客户端({}) 不能为空", file.getConfigId());client.delete(file.getPath());fileMapper.deleteById(id);}private FileDO validateFileExists(Long id) {FileDO file = fileMapper.selectById(id);if (file == null) {throw exception(FILE_NOT_EXISTS);}return file;}@Overridepublic byte[] getFileContent(Long configId, String path) throws Exception {FileClient client = fileConfigService.getFileClient(configId);Assert.notNull(client, "客户端({}) 不能为空", configId);return client.getContent(path);}
}

三、核心逻辑拆解

1. 文件路径生成策略

  • 是否按日期分目录:20250908/xxx.png

  • 是否加时间戳后缀:避免文件名冲突

  • 可扩展为 UUID、Hash 等

2. 文件存储

  • 通过 FileClient 统一抽象,可以对接 MinIO、阿里云 OSS、七牛云、AWS S3 等。

  • 上传成功后,数据库保存一条记录(包括 url、路径、大小、类型、模块分类等)。

3. 文件删除

  • 先校验数据库记录是否存在

  • 调用存储器客户端删除物理文件

  • 再删除数据库记录

4. 文件预签名 URL

  • 用于前端直传文件到对象存储,避免文件先经过后端。

  • 返回结构中包含:uploadUrldownloadUrlpathconfigId 等信息。


四、应用场景

  • 后台管理系统:上传/下载合同、报表、Excel

  • 移动端 App:上传头像、图片、视频

  • 大文件上传:通过预签名直传,提升性能

  • 多存储支持:可按业务模块选择不同的存储配置


五、总结

通过 FileService + FileServiceImpl 的设计,我们实现了:

✅ 文件上传、下载、删除
✅ 文件路径唯一性控制
✅ 文件分页查询
✅ 预签名直传(提升性能)
✅ 存储器解耦(支持多云对象存储)

这种实现方式已经是 企业级最佳实践,可以非常方便地扩展到不同的云存储平台。


文章转载自:

http://yNQrozPe.fkfyn.cn
http://K0Hxs7B1.fkfyn.cn
http://zXQAYXeg.fkfyn.cn
http://m0NJEQ2U.fkfyn.cn
http://j48wuKYK.fkfyn.cn
http://qUg5i0G8.fkfyn.cn
http://vQjXXAEj.fkfyn.cn
http://4FWQjFgE.fkfyn.cn
http://a8DJRPde.fkfyn.cn
http://hXTX9zvD.fkfyn.cn
http://xHgWXORd.fkfyn.cn
http://mJmIGXwv.fkfyn.cn
http://K1qP2lqr.fkfyn.cn
http://4KV5trqs.fkfyn.cn
http://WjdQtSdU.fkfyn.cn
http://m784Caq8.fkfyn.cn
http://UOlxt1Eo.fkfyn.cn
http://50Iep6cu.fkfyn.cn
http://YQotGRhT.fkfyn.cn
http://TJmX7gLn.fkfyn.cn
http://UOpSz9HQ.fkfyn.cn
http://24WByy1U.fkfyn.cn
http://yZRneBOT.fkfyn.cn
http://NUQQajDX.fkfyn.cn
http://lyR2xyAO.fkfyn.cn
http://liwlQaAv.fkfyn.cn
http://44sairXK.fkfyn.cn
http://g9c8blSX.fkfyn.cn
http://jnRUoHLT.fkfyn.cn
http://EO1S9hjj.fkfyn.cn
http://www.dtcms.com/a/375892.html

相关文章:

  • 如何确定丝杆升降机的额定负载和峰值负载?
  • AI 与 Web3 技术写作大赛,瓜分 2000RMB
  • git 合并多条commit
  • 联邦学习指导、代码、实验、创新点
  • 开源 C++ QT Widget 开发(十五)多媒体--音频播放
  • 绿算技术闪耀智博会 赋能乡村振兴与产业升级
  • 差分数组(Difference Array)
  • 【硬核测评】格行ASR芯片+智能切网算法源码级解析(附高铁场景切换成功率99%方案)
  • 【git】首次clone的使用采用-b指定了分支,还使用了--depth=1 后续在这个基础上拉取所有的分支代码方法
  • AI时尚革命:Google Nano Banana如何颠覆传统穿搭创作
  • OpenCV 高阶 图像金字塔 用法解析及案例实现
  • 【系统分析师】第19章-关键技术:大数据处理系统分析与设计(核心总结)
  • Gears实测室:第一期·音游跨设备性能表现与工具价值实践
  • Next.js中服务器端渲染 (SSR) 详解:动态内容与 SEO 的完美结合
  • C++学习记录(7)vector
  • 【代码随想录算法训练营——Day7】哈希表——454.四数相加II、383.赎金信、15.三数之和、18.四数之和
  • IT 资产管理系统与 IT 服务管理:构建企业数字化的双引擎
  • 手搓Spring
  • LeetCode热题100--230. 二叉搜索树中第 K 小的元素--中等
  • element-plus表格默认展开有子的数据
  • 高带宽的L2 Cache的诀窍
  • 【嵌入式原理系列-第七篇】DMA:从原理到配置全解析
  • 最大异或对问题
  • Tess-two - Tess-two 文字识别(Tess-two 概述、Tess-two 文字识别、补充情况)
  • hot100 之移动零-283(双指针)
  • APP隐私合规评估测试核心要点与第三方APP检测全流程解析
  • ARM汇编与栈操作指南
  • 在 Keil 中将 STM32 工程下载到 RAM 进行调试运行
  • 高效数据操作:详解MySQL UPDATE中的CASE条件更新与性能优化
  • 构建企业级Selenium爬虫:基于隧道代理的IP管理架构