在企业项目中,文件上传和管理是非常常见的需求。本文基于 芋道源码 的实现,介绍如何封装一个通用的 文件服务 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://www.pswp.cn/web/96212.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/96212.shtml
英文地址,请注明出处:http://en.pswp.cn/web/96212.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Oracle RAC认证矩阵:规避风险的关键指南

RAC Certification Matrix&#xff08;RAC认证矩阵&#xff09; 是Oracle官方发布的硬件、软件与操作系统兼容性清单&#xff0c;明确规定了哪些平台、组件和版本可以正式支持Oracle RAC&#xff08;Real Application Clusters&#xff09;的部署。它是搭建或升级RAC环境时必须…

【自然语言处理与大模型】如何通过微调来agent性能?

虽然大模型本身具备一定的指令理解和工具调用潜力&#xff0c;但在实际应用中&#xff0c;尤其是在复杂或专业领域&#xff0c;往往需要通过微调来提升Agent的工具调用能力。问题一&#xff1a;基座模型无法准确识别或选择特定领域的工具当Agent需要在医疗、金融、法律、工业控…

在 Keil 中将 STM32 工程下载到 RAM 进行调试运行

在 Keil 中将 STM32 工程下载到 RAM 进行调试运行 在使用 STM32 进行调试时&#xff0c;默认情况下代码会被烧写到 Flash 中运行。然而&#xff0c;Flash 写入速度较慢&#xff0c;擦写次数有限&#xff0c;且调试过程中频繁烧写可能影响开发效率。在某些场景下&#xff08;如快…

【51单片机】【protues仿真】基于51单片机宠物投食系统

目录 一、主要功能 二、使用步骤 三、硬件资源 四、软件设计 五、实验现象 一、主要功能 1、LCD1602液晶显示时间、温度、食物重量 2、按键手动投喂食物​ 3、称重模块检测当前食物重量 4、食物重量小于阈值会声光警报并自动投喂 二、使用步骤 基于51单片机的宠物投食…

腾讯云负载均衡增加访问策略后访问失败

为了测试&#xff0c;在负载均衡的安全组添加2条安全策略&#xff0c;限制办公室内IP可访问&#xff0c;其他IP地址拒绝所有访问。结果&#xff0c;访问失败。经过反复测试&#xff0c;主要是js问价加载失败&#xff0c;动态接口访问代码返回正常。再进行测试&#xff0c;发现去…

CSS的文本样式

1.文本样式的分类注意&#xff1a;必须先建立标签&#xff0c;再在head中修改1.1字体样式1.1.1字体颜色代码演示<head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title&g…

R语言读取excel文件数据-解决na问题

文章目录安装R语言运行环境实现代码遇到的问题总结安装R语言运行环境 安装教程连接, 包含国内镜像快速下载 实现代码 实现思路&#xff1a;使用python将文件的空字符的位置变成0&#xff0c;生成csv文件后交给R语言处理python实现代码如下&#xff1a; import pandas as pd…

【Nginx 运维实战】版本替换:强制 vs 平滑升级全解析

【Nginx 运维实战】版本替换&#xff1a;强制 vs 平滑升级全解析一&#xff1a;版本替换的两种思路二&#xff1a;使用场景对比三&#xff1a;实战1&#xff09;强制替换1.备份旧版本2.替换为新版本3.**赋予执行权限**4.**重启 Nginx**2&#xff09;平滑替换1.确认进程文件2.备…

MQ-消息队列

定义 Mssage Queue&#xff1a;消息队列。它是一种“先进先出”&#xff08;FIFO&#xff09;的数据结构&#xff0c;用于在分布式系统或应用程序之间进行异步通信。组成1. 生产者&#xff08;Producer&#xff09;定义&#xff1a;消息的发送方&#xff0c;负责将业务系…

NVIDIA驱动程序核心的“即时编译器”(Just-in-Time, JIT Compiler)详细介绍

我们来详细、深入地剖析这个位于NVIDIA驱动程序核心的“即时编译器”&#xff08;Just-in-Time, JIT Compiler&#xff09;。它堪称CUDA生态系统成功的“幕后英雄”&#xff0c;是连接软件稳定性和硬件飞速发展的关键桥梁。 第一部分&#xff1a;JIT编译器的本质 首先&#xff…

【PS2025全网最新版】稳定版PS2025保姆级下载安装详细图文教程(附安装包)(Adobe Photoshop)

今天&#xff0c;给大家带来PS2025的保姆级下载安装图文教程。 前言&#xff1a; Adobe Photoshop 作为业界领先的图像处理与设计软件&#xff0c;持续推动着数字创意领域的发展。其应用涵盖平面设计、摄影后期、UI/UX 设计、影视特效等多个专业方向&#xff0c;为用户提供强…

分享TWS充电仓方案开发设计

TWS耳机市场“卷”到最后&#xff0c;拼的早已不只是音质&#xff0c;而是续航、交互、体积、成本四位一体。传统充电仓用多颗IC堆砌&#xff1a;升压、电量计、霍尔、LED驱动、保护IC……BOM高、贴片复杂、调试周期长。8位MCU把上述功能“一锅端”&#xff1a;单芯片即完成电源…

【Java实战㉖】深入Java单元测试:JUnit 5实战指南

目录一、单元测试概述1.1 单元测试概念1.2 单元测试优势1.3 JUnit 5 框架组成1.4 JUnit 5 环境搭建二、JUnit 5 核心功能实战2.1 测试类与测试方法2.2 测试生命周期2.3 断言方法2.4 异常测试三、单元测试进阶实战3.1 参数化测试3.2 测试套件3.3 Mockito 框架3.4 单元测试实战案…

分布式微服务--ZooKeeper作为分布式锁

看这篇博客之前可以先去了解博主的另一篇讲解ZooKeeper的博客&#xff1a;分布式微服务--ZooKeeper的客户端常用命令 & Java API 操作-CSDN博客 1. 为什么需要分布式锁&#xff1f; 在分布式系统中&#xff0c;多个服务节点可能同时访问或修改同一份共享资源&#xff08;例…

基于容器化云原生的 MySQL 及中间件高可用自动化集群项目

1 项目概述 本项目旨在构建一个高可用、高性能的 MySQL 集群,能够处理大规模并发业务。通过容器化部署、多级缓存、完善的监控和备份策略,确保数据库服务的连续性和数据安全性。 架构总览 预期目标 数据库服务可用性达到 99.99% 支持每秒 thousands 级别的并发访问 实现秒…

如何将 iPhone 备份到电脑/PC 的前 5 种方法

定期备份你的 iPhone&#xff08;最好每两周一次&#xff09;对于保护你的数据至关重要。它确保了如果设备损坏、丢失或被盗&#xff0c;或者你换了新手机&#xff0c;你不会丢失重要信息&#xff0c;并且可以轻松地从备份中恢复应用程序、照片、设置等。如果你不确定如何备份 …

国产AI芯片编程模型深度对比:寒武纪MLU vs 壁仞BR100异构计算设计

点击 “AladdinEdu&#xff0c;同学们用得起的【H卡】算力平台”&#xff0c;H卡级别算力&#xff0c;80G大显存&#xff0c;按量计费&#xff0c;灵活弹性&#xff0c;顶级配置&#xff0c;学生更享专属优惠。 引言&#xff1a;国产AI芯片的崛起与挑战 随着人工智能技术的飞速…

【项目】基于One Thread One Loop模型的高性能网络库实现 - 项目介绍与前置知识

目录 项目介绍 HTTP服务器基本认识 Reactor模式基本认识 单Reactor单线程模式认识 单Reactor多线程模式认识 多Reactor多线程模式认识 模块划分 Server模块 Buffer模块 Socket模块 Channel模块 Connection模块 Acceptor模块 TimerQueue模块 Poller模块 EventLo…

lua中table键类型及lua中table的初始化有几种方式

在 Lua 中&#xff0c;table 的键几乎可以是任何类型&#xff0c;但有几个重要的规则和最佳实践需要了解。1. 主要键类型(1) 字符串 (string)这是最常见、最推荐的键类型。local person {name "Alice", -- 等同于 ["name"] "Alice"["age…

matlab实现利用双MZI产生RZ33-QPSK信号

利用MATLAB实现双MZI产生RZ33-QPSK信号的代码&#xff1a; 参数设置 % 信号参数 fs 1e6; % 采样频率 fc 10e6; % 载波频率 T 1e-6; % 符号周期 N 1000; % 采样点数 t 0:1/fs:(N-1)/fs; % 时间向量生成QPSK信号 % 生成随机二进制序列 data randi([0,1],1,N);% 将二进制序列…