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

[SpringBoot 对象存储实战]:预签名 URL 直传 OSS 全流程设计与实现

🔥你好我是fengxin_rou这是我的个人主页fengxin_rou的主页

❄️欢迎查看我的专栏我的专栏

《Java后端学习》、《JAVASE基础》、《JUC并发》、《redis》、《JVM虚拟机》、《MYSQL》、《黑马点评》、《rabbitmq》、《JavaWeb+AI的talis学习系统》、《苍穹外卖》

目录

前言

一、预签名直传:核心原理与优势

1.1 传统上传 vs 预签名直传

1.2 核心优势

1.3 整体流程

二、请求实体设计:标准化上传参数

字段说明

三、OSS 存储服务:核心工具类实现

3.1 头像上传(普通上传示例)

3.2 生成公开访问 URL

3.3 生成预签名 PUT URL(核心)

四、Controller 接口实现:权限校验与业务封装

4.1 权限校验关键点

4.2 返回体结构

五、前端直传执行流程

前端请求示例

结语


前言

在内容社区、自媒体平台等场景中,图片、文件上传是高频需求。 传统后端中转上传会占用大量带宽与内存,高并发下极易成为性能瓶颈。 本文基于 SpringBoot + 阿里云 OSS,实现预签名 URL 客户端直传方案,彻底解放后端服务,兼顾安全、高效与易用。


一、预签名直传:核心原理与优势

预签名 URL是对象存储提供的临时授权机制。 后端用密钥生成带时效、带权限的签名 URL,前端直接用此 URL 上传 / 下载文件,无需透传密钥。

1.1 传统上传 vs 预签名直传

  • 传统上传:客户端→后端→OSS,后端中转流量,压力大、速度慢。
  • 预签名直传:客户端请求签名→后端返回 URL→客户端直传 OSS→通知后端入库。

1.2 核心优势

  1. 服务无压力:后端不处理文件流,仅做授权与记录。
  2. 上传速度快:客户端直连 OSS,带宽不受后端限制。
  3. 安全可控:URL 有时效、有路径、有权限,防止越权与盗传。
  4. 成本更低:减少服务器出口带宽消耗。

1.3 整体流程

  1. 前端请求获取预签名上传 URL。
  2. 后端校验权限、生成路径、返回带签名 URL 与访问 URL。
  3. 前端直接 PUT 上传到 OSS。
  4. 上传完成后通知后端,保存文件 URL 到业务库。

二、请求实体设计:标准化上传参数

统一请求体,明确场景、文件标识、类型,便于权限校验与路径生成。

/** * 预签名直传请求实体 */ public record StoragePresignRequest( @NotBlank String scene, // 上传场景:文章内容/图片 @NotBlank String postId, // 帖子ID(字符串避免精度丢失) @NotBlank String contentType,// 文件类型:image/jpeg、video/mp4 String ext // 文件扩展名 ) {}

字段说明

  • scene:区分业务场景,便于权限控制与目录隔离。
  • postId:关联业务 ID,用于校验归属、防止越权。
  • contentType:必须与前端上传时一致,否则 OSS 验签失败。
  • ext:扩展名,用于生成规范文件路径。

三、OSS 存储服务:核心工具类实现

封装 OSS 基础操作,包括普通上传、生成公开 URL、生成预签名 URL

3.1 头像上传(普通上传示例)

适用于小文件、后端可承接的简单上传场景。

/** * 头像上传到 OSS */ public String uploadAvatar(long userId, MultipartFile file) { // 1.校验 OSS 配置 ensureConfigured(); // 2.提取文件扩展名 String original = file.getOriginalFilename(); String ext = ""; if (original != null && original.contains(".")) { ext = original.substring(original.lastIndexOf(".")); } // 3.生成唯一文件路径 String objectKey = props.getFolder() + "/" + userId + "-" + System.currentTimeMillis() + ext; // 4.创建 OSS 客户端 OSS client = new OSSClientBuilder().build( props.getEndpoint(), props.getAccessKeyId(), props.getAccessKeySecret() ); try { // 5.流式上传 PutObjectRequest request = new PutObjectRequest( props.getBucket(), objectKey, file.getInputStream() ); client.putObject(request); } catch (IOException e) { throw new BusinessException(ErrorCode.BAD_REQUEST, "文件读取失败"); } finally { client.shutdown(); } // 6.返回可访问 URL return publicUrl(objectKey); }

3.2 生成公开访问 URL

拼接 CDN 域名,规范路径,避免双斜杠问题。

/** * 生成公开访问 URL */ private String publicUrl(String objectKey) { if (props.getPublicDomain() != null && !props.getPublicDomain().isBlank()) { return props.getPublicDomain().replaceAll("/$", "") + "/" + objectKey; } return "https://" + props.getBucket() + "." + props.getEndpoint() + "/" + objectKey; }

3.3 生成预签名 PUT URL(核心)

生成前端可直接 PUT 上传的临时 URL,必须指定 ContentType

/** * 生成预签名 PUT 上传 URL */ public String generatePresignedPutUrl( String objectKey, String contentType, int expiresInSeconds ) { ensureConfigured(); OSS client = new OSSClientBuilder().build( props.getEndpoint(), props.getAccessKeyId(), props.getAccessKeySecret() ); try { // 设置过期时间 Date expiration = new Date(System.currentTimeMillis() + expiresInSeconds * 1000L); GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest( props.getBucket(), objectKey, HttpMethod.PUT ); request.setExpiration(expiration); // 必须设置 ContentType,否则前端上传会验签失败 if (contentType != null && !contentType.isBlank()) { request.setContentType(contentType); } URL url = client.generatePresignedUrl(request); return url.toString(); } finally { client.shutdown(); } }

四、Controller 接口实现:权限校验与业务封装

提供对外接口,完成用户认证、权限校验、路径生成、签名返回

/** * 预签名直传接口 */ @PostMapping("/presign") public StoragePresignResponse presign( @Valid @RequestBody StoragePresignRequest request, @AuthenticationPrincipal Jwt jwt ) { // 1.获取当前登录用户ID long userId = jwtService.extractUserId(jwt); // 2.postId 字符串转数字,防止前端精度丢失 long postId; try { postId = Long.parseLong(request.postId()); } catch (NumberFormatException e) { throw new BusinessException(ErrorCode.BAD_REQUEST, "postId 格式非法"); } // 3.校验帖子归属,防止越权上传 KnowPost post = knowPostMapper.findById(postId); if (post == null || !userId.equals(post.getCreatorId())) { throw new BusinessException(ErrorCode.BAD_REQUEST, "无权限操作该草稿"); } // 4.按场景生成文件存储路径 String scene = request.scene(); String ext = normalizeExt(request.ext(), request.contentType(), scene); String objectKey; if ("knowpost_content".equals(scene)) { objectKey = "posts/" + postId + "/content" + ext; } else if ("knowpost_image".equals(scene)) { String date = DateTimeFormatter.ofPattern("yyyyMMdd") .withZone(ZoneId.of("UTC")).format(Instant.now()); String rand = UUID.randomUUID().toString().replace("-", "").substring(0, 8); objectKey = "posts/" + postId + "/images/" + date + "/" + rand + ext; } else { throw new BusinessException(ErrorCode.BAD_REQUEST, "不支持的上传场景"); } // 5.生成10分钟有效期的预签名 URL int expiresIn = 600; String uploadUrl = ossStorageService.generatePresignedPutUrl( objectKey, request.contentType(), expiresIn ); Map<String, String> headers = Map.of("Content-Type", request.contentType()); // 6.返回:上传URL、文件URL、请求头、过期时间 return new StoragePresignResponse(objectKey, uploadUrl, headers, expiresIn); }

4.1 权限校验关键点

  • 必须校验postId 对应用户是否为当前登录用户
  • 无校验会导致越权覆盖、恶意上传、数据污染。
  • 这是系统安全的核心防线

4.2 返回体结构

{ "objectKey": "posts/123/images/20260524/xxxx.jpg", "uploadUrl": "https://xxx.oss-cn-beijing.aliyuncs.com/...?X-Oss-Signature=xxx", "headers": { "Content-Type": "image/jpeg" }, "expireSeconds": 600 }

五、前端直传执行流程

  1. 调用/api/v1/storage/presign获取上传 URL。
  2. 使用PUT方法,携带指定 Content-Type 上传文件。
  3. 上传成功后,将 fileUrl 提交给后端业务接口保存。

前端请求示例

// 获取预签名 const { uploadUrl, headers } = await api.post("/storage/presign", { scene: "knowpost_image", postId: "123", contentType: "image/jpeg", ext: "jpg" }); // 直传 OSS await axios.put(uploadUrl, file, { headers });

结语

本文完整实现 SpringBoot + OSS预签名 URL 直传方案,覆盖请求设计、服务封装、权限校验、接口开放全流程。 该方案大幅降低服务负载,提升上传速度与系统吞吐量,适合文章、图片、视频等大文件上传场景。

实际使用建议:

  1. 预签名有效期控制在5–10 分钟
  2. 按业务场景做目录隔离。
  3. 接入 CDN 加速文件访问。
  4. 增加文件大小、类型、数量限流防护。

http://www.jsqmd.com/news/880527/

相关文章:

  • Codex CLI高危漏洞CVE-2025-61260深度解析与工程化防御
  • DeepSeek接入codex app使用
  • 模块化触觉显示系统:气动软体机器人与信息论的创新结合
  • 2026宜宾装修公司推荐:宜宾装修公司哪家好/宜宾装修公司电话/宜宾装饰公司哪家好/宜宾装饰公司排行榜/宜宾装饰公司电话/选择指南 - 优质品牌商家
  • 深度专栏 | 撕碎“手工浪漫”:精品可可的硬核工业底色与绝对复现
  • 阴阳师智能自动化脚本:5个步骤实现游戏任务全托管
  • 手机HTTPS抓包全链路解析:从代理配置到SSL Pinning绕过
  • 开源项目推荐:ORIGIN AI Workspace —— 一键部署你的私有 AI 工作站
  • 2026年至今,河北扁钢走线架厂商实力与选择逻辑剖析 - 2026年企业推荐榜
  • Keil单用户许可证(LIC)更新与多设备管理指南
  • ImprovWifi 跨平台传输层设计:把协议层做薄,把宿主层做稳
  • FPG平台:信息透明度建设的深度解析
  • Android 全栈体系 150 讲 - 49 深度完整版 Android 常用设计模式 + 架构模式 源码剖析、业务落地、面试精讲
  • LeetCode 每日一题笔记 日期:2026.05.22 题目:33. 搜索旋转排序数组
  • 智能控制 第六章——集成智能控制系统
  • 基于SpringBoot+用户画像的商品个性化推荐毕业设计
  • Wireshark与FTK Imager电子证据采集实战指南
  • 从零开始单细胞分析:手把手教你用Scanpy复现PBMC3K教程(附避坑指南)
  • FPG平台:行业前景下的战略定位评估
  • 2026年当下常德卫生间防水公司实力盘点:优家房屋修缮中心为何备受青睐? - 2026年企业推荐榜
  • 2026年免费图片去水印保姆级教程:不用下载软件,微信小程序一步搞定
  • 渗透测试工具认知地图:从工作流理解工具本质
  • SpringBoot+Vue校车管理信息系统源码+论文
  • 首发!美团开源最强数字人 LongCat 1.5:性能狂飙15倍,8步闪电成片!
  • 基于Simulink的四开关buck-boost变换器闭环仿真模型
  • 四川钢板生产厂家名录|2026 年 5 月行情走势与价格预测 - 四川盛世钢联营销中心
  • 保姆级教程:在AirSim中用Python实现四旋翼的实时避障(附完整代码与避坑点)
  • SpringBoot+Vue实验室研究生信息管理系统源码+论文
  • 2026年Q2四川消防维修维保品牌名录及选型指南:成都消防维修口碑/消防技术服务/消防改造公司/消防改造多少钱/选择指南 - 优质品牌商家
  • 从原理到代码:用Python仿真TOA、TDOA和RSS定位算法(附GitHub源码)