外观
存储配置与访问
先选择文件实际保存的位置,再配置上传方式名称与对外访问地址。上传服务和文件记录使用同一套路由;切换配置不会搬迁已有对象或重写历史记录。
应用装配与首个 MinIO 示例见快速开始,上传签名及回调见上传与直传签名。
存储能力矩阵
下表描述当前资源适配器实现,不代表供应商产品本身的全部能力。供应商依赖的 groupId 均为 com.own.business,artifactId 使用 springboot-business-resource-module- 前缀。
| 存储 | type | 依赖后缀 | 经应用上传 | 直传签名 | 私有读签名 | 回调验签 |
|---|---|---|---|---|---|---|
| 本地 | LOCAL | Core 内置 | 有实现,存在下文列出的缺陷 | 无 | 无 | 无,继承默认放行 |
| MinIO | MINIO | minio | 有 | 无 | 无 | 无,继承默认放行 |
| 阿里云 OSS | ALIYUN_OSS | aliyun | 有,异常可能被吞掉 | 表单策略 | 有 | 有 |
| 华为 OBS | HUAWEI_OBS | huawei | 有 | PUT 预签名 URL | 无 | 无,继承默认放行 |
| 七牛 Kodo | QINIU_KODO | qiniu | 有 | 上传 token | 无 | 无,继承默认放行 |
| 火山 TOS | VOLC | volc | 有 | PUT 预签名 URL | 无 | 无,继承默认放行 |
归档恢复仅阿里云实现了 restore;其他适配器继承不支持异常。腾讯、百度没有当前可选的资源适配器,不应根据常量或注释中的名称推断支持。
供应商适配器当前都会传递依赖 App Controller,导入适配器时应同时审视应用实际暴露的上传、签名和回调路由。
公共配置
配置前缀是 own.resource,没有模块级 enabled 开关。
| 配置 | 默认值 | 作用 |
|---|---|---|
primary | default | 默认上传工具名称;必须对应已成功注册的源 |
source | 未配置 | 单源配置,固定注册为 default |
multiple-source | 空 Map | 多个命名源,key 用作 HTTP/Java 的 method |
unique | NEXT | 经应用上传的全局 MD5 复用策略,详见上传页 |
upload-callback-url | 未配置 | 源未指定回调地址时继承此值;只有具体实现使用它才产生效果 |
save-handler.enable | false | 是否在单条记录新增前运行路径处理链 |
save-handler.params | x-own | 传给路径处理器的字符串,详见记录扩展 |
每个 source 或 multiple-source.<名称> 使用以下配置:
| 配置 | 默认值 | 用途 |
|---|---|---|
type | 未配置 | 上表中的存储类型枚举 |
class-name | 未配置 | 自定义实现类;优先于 type,为空或类找不到时尝试枚举 |
unique | UNIQUE | 全局为 NEXT 时决定是否按源位置复用 |
key、secret | 未配置 | 对象存储访问凭证 |
region | 未配置 | 地域;火山客户端直接使用该值,七牛当前没有使用此配置切换地域 |
bucket | 未配置 | 目标桶 |
end-point | 未配置 | SDK 访问节点,各实现格式见下表 |
end-point-internal | 未配置 | 供同地域访问逻辑使用的内网节点 |
position | 未配置 | 保存到记录中的访问前缀;云源建议显式填写且不带结尾 / |
view-position | 未配置 | 地址转换映射的展示前缀;不等于自动 CDN 重写 |
private-read | false | 是否请求私有访问签名;不修改存储桶权限 |
private-read-expire | 3600 秒 | 临时读工具未指定正数有效期时使用;注释中的 [1,7200] 没有对应范围校验 |
upload-callback-url | 未配置 | 覆盖公共回调地址;需要完整可达地址,模块不会自动追加服务名 |
各供应商的配置要点
所有对象存储示例都应从环境变量读取凭证。以快速开始中的 source 结构为基础,替换适配器依赖和以下字段即可选择源;上传与回调能力仍以能力矩阵为准。
| 类型 | 必须准备的字段 | 格式及实现差异 |
|---|---|---|
MINIO | key、secret、bucket、end-point、显式 position | endpoint 包含协议和端口,position 通常为对外 endpoint 加 bucket;首次建客户端检查/创建 bucket,但检查标志是静态共享的,多源应预先准备各 bucket |
ALIYUN_OSS | key、secret、bucket、end-point、position | endpoint 使用不带协议的节点域名,签名 host 拼成 https://<bucket>.<end-point>;上传异常处理限制见上传页 |
HUAWEI_OBS | key、secret、bucket、end-point、position | endpoint 使用不带协议的域名,客户端自动添加 https:// |
QINIU_KODO | key、secret、bucket、position | position 为下载域名;服务端上传代码固定 Region.region0(),配置 region 不会改变它 |
VOLC | key、secret、bucket、position、region、end-point | 地域和 SDK endpoint 均需正确;当前 check() 没有检查后两者,不能以初始化无报错证明配置完整 |
配置校验失败、实现 JAR 缺失或 class-name 无效时,源可能无法注册。不要只检查配置绑定成功,应确认启动日志实际注册的源名及默认源。
多源与默认源
以下示例将图片与文档保存到两个 OSS bucket,应用需引入 springboot-business-resource-module-aliyun,并准备两个 bucket 的权限与访问域名:
yaml
own:
resource:
primary: documents
unique: REPEAT
multiple-source:
images:
type: ALIYUN_OSS
key: ${RESOURCE_OSS_ACCESS_KEY}
secret: ${RESOURCE_OSS_SECRET_KEY}
bucket: ${RESOURCE_IMAGE_BUCKET}
end-point: ${RESOURCE_OSS_ENDPOINT}
position: ${RESOURCE_IMAGE_POSITION}
documents:
type: ALIYUN_OSS
key: ${RESOURCE_OSS_ACCESS_KEY}
secret: ${RESOURCE_OSS_SECRET_KEY}
bucket: ${RESOURCE_DOCUMENT_BUCKET}
end-point: ${RESOURCE_OSS_ENDPOINT}
position: ${RESOURCE_DOCUMENT_POSITION}
private-read: true
private-read-expire: 3600调用 method=images 选择图片源,method=documents 选择文档源。单源 source 与 multiple-source 可以同时存在,后者同名注册可能覆盖先前工具,避免重复命名。
ResolveObjectStoreUtil.get(name) 对未知名称回退到默认工具,不会报“源不存在”。因此 HTTP 默认参数 method=default 的含义取决于是否真的注册了名为 default 的源:存在时使用该源,不存在时才回退到 primary。不要用不存在的 method 做业务隔离。
只配置多源时,必须将 primary 改成有效源名。若保留 primary=default 但没有 default 源,初始化可能因默认工具为空而失败。完全没有可用源时解析器会尝试本地兜底;这不是远端上传失败时的自动切换机制。
按模块、标识选择源可自定义 SignatureBusinessMethod,见签名业务路由。该 Bean 仅参与业务签名 POST,不接管普通上传或普通 GET 签名。
访问文件与私有读
公有文件
文件记录分别保存 position 和 path,不会自动生成 url 字段,也不会根据 private-read 自动改写 VO。Linux/macOS 上,经应用上传得到的记录通常可用 position + path 形成访问地址;对外前缀和对象路径都应按部署环境配置。
FileRecordUtil.build 使用平台文件分隔符构造 path,对象 SDK 的键则使用 /。Windows 环境需核对 URL 分隔符和记录路径,不能假定落库路径始终是浏览器可直接使用的 URL。
私有文件
当前仅阿里云适配器实现了私有读预签名。应用在完成自身授权后调用 ResourceTemporaryAccessUtil,传入包含实际存储域名的完整路径。示例放入应用扫描范围;storedUrl 应来自可信的文件记录及归属校验:
java
package com.example.resource;
import com.own.business.resource.business.util.ResourceTemporaryAccessUtil;
import org.springframework.stereotype.Service;
@Service
public class PrivateResourceLinks {
private final ResourceTemporaryAccessUtil temporaryAccess;
public PrivateResourceLinks(ResourceTemporaryAccessUtil temporaryAccess) {
this.temporaryAccess = temporaryAccess;
}
public String signedUrlAfterAuthorization(String storedUrl) {
return temporaryAccess.getTemporaryAccessPath("documents", storedUrl, 600L, null);
}
}该工具没有配套的公开 HTTP 签发接口,也不检查用户权限。它先按路径反查源属性:查不到源,或源没有开启 private-read 时直接返回原路径;仅传一个对象相对键不会触发正常的私有源识别。
识别出私有源后,工具去除域名和起始 /,再调用适配器生成临时地址。其他适配器继承的默认方法只返回传入路径,不能靠 private-read=true 获得 MinIO、OBS、Kodo 或 TOS 的私有读能力,甚至可能只得到去掉域名的对象键。
有效期参数为正数时使用该参数,否则取 private-read-expire。工具用 tap: 加路径和样式摘要缓存结果,缓存键不包含 method、有效期或用户;同一路径和样式的后续请求可能复用之前签发的 URL,不能把本次有效期参数当成强制重新签发。Map 重载返回原路径到临时地址的映射,传入路径集合应先去重。
展示地址与内网地址
view-position 会注册存储地址与展示地址的转换 Map,但当前 ResourceService、FileRecordVo 和管理 Controller 没有自动应用这些 Map。配置 CDN 地址后,应用仍需明确在读写哪个环节转换,并使用实际存储地址进行临时访问源识别。
路径反查按 URL origin 获取源;同一域名下不同 bucket 或路径前缀可能无法唯一识别。展示域名的反查注册也存在条件不一致,不能假定任意 CDN URL 都能自动签名。
内网 endpoint 选择依赖 isSameRegion。解析器通过反射直接创建适配器,当前没有对这些实例执行 Spring 依赖注入,因此基类 CustomProjectProperty 通常为空,同地域判断为 false;不能仅配置 region 和 end-point-internal 就承诺自动走内网。
本地实现的当前限制
本地工具位于 Core,无需供应商依赖;无有效源时会兜底创建。MVC 将 /files/** 映射到应用工作目录的 files/,目录基于 user.dir,不读取一个独立的资源上传根目录配置。
当前本地实现有两个影响文件可用性的源码问题:
getUploadFolder()返回files/upload,上传时直接拼接yyyy/MM/dd,缺少分隔符,实际可能写入files/upload2026/09/09,与返回位置/files/upload加记录路径不一致。- 底层 FileUtil.write 每次写出整个 1024 字节缓冲区,没有使用本次读取长度,最后一块不足 1024 字节时可能产生多余内容。
因此本地模式不能作为“上传后 URL 与文件内容均正确”的默认演示,需先修复并验证目录及文件内容。快速开始采用 MinIO,避免把这些实现问题描述为已解决。
自定义存储工具
通过源 class-name 指定直接继承 AbstractObjectStoreUtil 的类,并提供可访问的无参构造方法;解析器检查的是直接父类,不支持任意深度的继承链。实现 uploadFile、serviceName,并按需重写 check、signature、temporaryAccessPath、checkUploadCallBack 和 restore。
工具由反射构造,不是普通 Spring Bean,不应依赖字段 @Resource 自动注入。未重写回调方法会继承默认放行,未重写签名则抛出未定义异常;这些默认行为必须纳入自定义适配器的接入契约。