S3 兼容数据面(S3ApiController)¶
提供 KMS 知识管理模块的 S3 兼容数据面(path-style),供 rclone、AWS CLI、MinIO Client 等 S3 客户端直接挂载/上传/下载/列举 KMS 网盘内的对象。本控制器以 AWS S3 协议(XML 请求/响应、Authorization: AWS4-HMAC-SHA256 ... SigV4 签名或 Authorization: Bearer <JWT>)工作,不走 KMS 统一 Resource JSON 封装。
- 接口类型:REST 资源(
@RestController,类级@RequestMapping同时注册两个前缀:${myapps.kms.s3.path-prefix:/s3}与/kms${myapps.kms.s3.path-prefix:/s3}) - 基址:
${myapps.kms.s3.path-prefix:/s3}(默认/s3);当server.servlet.context-path=/且对外 URL 仍为/kms/s3(网关或习惯路径)时由/kms/s3命中。完整基址形如{kms-context}/s3(context-path 为/kms时为/kms/s3,已隐含 context-path) - Tag:kms S3 数据面模块
公共说明¶
- 鉴权(据源码
S3ApiController+S3KmsAuthService+KmsSecurityFilter+KmsS3DataPlanePath):KmsSecurityFilter在doFilter开头调用KmsS3DataPlanePath.matches(request),命中(请求 URI 以{contextPath}/s3或/kms/s3开头)即直接放行,不经 Cookie/JWT 校验;鉴权交由本控制器调用S3KmsAuthService.authenticate(request)完成。鉴权方式两选一(据S3KmsAuthService.authenticate): - Bearer JWT:请求头
Authorization: Bearer <accessToken>,服务端用Security.getUserIdFromToken(token)解析用户 Id(与 KMS 常规 accessToken 同一令牌体系)。 - AWS SigV4:请求头
Authorization: AWS4-HMAC-SHA256 Credential=<accessKey>/...,服务端按 AccessKey 查找KmsS3Credential,用其 Secret 解密后由AwsSigV4Verifier.verify验签。AccessKey/SecretAccessKey 通过 s3-credential.md 自助签发。 - 两者均失败抛
UnauthorizedException,控制器在catch内以 S3 XML 错误AccessDenied返回 HTTP403。 - 执行用户:控制器内
authService.authenticate(request)解析得到KmsUser,不调用AbstractBaseController.getUser()的 Feign 装载路径,亦**不使用userCode参数**。 - 响应结构:本控制器**不返回 KMS 统一
Resource**,而是直接写HttpServletResponse输出流: - 成功:S3 协议 XML(
ListAllMyBucketsResult/ListBucketResult/CopyObjectResult/InitiateMultipartUploadResult/CompleteMultipartUploadResult),Content-Type: application/xml;二进制下载为application/octet-stream(视频等按扩展名推断)。 - 失败:S3 协议 XML
<Error><Code>...</Code><Message>...</Message></Error>,常见Code:AccessDenied(HTTP 403)、InvalidRequest(HTTP 400)、NoSuchKey(HTTP 404)、PreconditionFailed(HTTP 412)。 - HTTP 状态码:成功
200 OK(列举、GetObject整体下载、CopyObject、PutObject、分片相关);HeadObject成功200(带Content-Length: 0);GetObject区间命中206 Partial Content;条件读取未改304 Not Modified;DeleteObject成功204 No Content;区间不满足416 Requested Range Not Satisfiable。 - 路径变量:
bucket实为 KMS 内部 网盘 Id(diskId),由bridgeService.findDisk(bucket)解析为Disk;/**部分为对象 key(按/分隔的虚拟目录路径,服务端做百分号解码 + UTF-8 还原)。 - ETag / 条件请求:
GetObject/HeadObject支持If-Match、If-None-Match(基于 ETag);PutObject/DeleteObject支持If-Match、If-None-Match;CopyObject支持x-amz-copy-source-if-match、x-amz-copy-source-if-none-match。条件不满足返回PreconditionFailed(HTTP 412)。 - Range 请求:
GetObject支持Range: bytes=<start>-<end>单段区间(多段仅取第一段);返回206 Partial Content与Content-Range: bytes <start>-<end>/<size>。
1. 列举所有桶(ListBuckets)¶
返回当前用户可访问的全部网盘(个人网盘、公司网盘、团队网盘;公司/Wiki 网盘仅企业知识管理员可见),按 S3 协议序列化为桶列表 XML。
- 接口类型:REST 资源
- 请求方式:
GET - 请求路径:
/或`(即基址根;同时匹配GET /s3与GET /s3/)。完整:/s3(或/kms/s3`) - 鉴权:是(S3 数据面 SigV4 / Bearer JWT,据源码)
- Tag:kms S3 数据面模块
请求参数¶
无。
请求示例¶
GET /s3 HTTP/1.1
Host: kms.example.com
Authorization: AWS4-HMAC-SHA256 Credential=AKxxx/20260805/cn-kms/s3/aws4_request, ...
响应¶
结构:S3 XML(非 KMS Resource)。
成功示例:
<?xml version="1.0" encoding="UTF-8"?>
<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Owner><ID>obpm</ID><DisplayName>obpm</DisplayName></Owner>
<Buckets>
<Bucket><Name>__DISKID_PERSON__</Name><CreationDate>1970-01-01T00:00:00.000Z</CreationDate></Bucket>
<Bucket><Name>__DISKID_DOMAIN__</Name><CreationDate>1970-01-01T00:00:00.000Z</CreationDate></Bucket>
</Buckets>
</ListAllMyBucketsResult>
失败示例(鉴权失败):
(HTTP 403)2. 列举桶内对象(ListObjects v1 / v2)¶
按 S3 协议列举指定桶(网盘)下的对象与公共前缀(虚拟目录)。同时兼容 ListObjects v1(marker)与 v2(continuation-token、list-type=2)。
- 接口类型:REST 资源
- 请求方式:
GET - 请求路径:
/{bucket}(完整:{kms-context}/s3/{bucket}) - 鉴权:是(S3 数据面 SigV4 / Bearer JWT,据源码)
- Tag:kms S3 数据面模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| bucket | path | string | 是 | 桶名(网盘 diskId) |
| list-type | query | string | 否 | 列举协议版本;仅接受 2(v2)或不传(v1);其他值返回 InvalidRequest |
| continuation-token | query | string | 否 | v2 分页续接 token |
| marker | query | string | 否 | v1 分页起始 key |
| prefix | query | string | 否 | 仅返回该前缀下的对象 |
| delimiter | query | string | 否 | 分隔符(默认 /),用于折叠虚拟目录并产生 CommonPrefixes |
| max-keys | query | int | 否 | 单页最大对象数(默认 1000) |
请求示例¶
GET /s3/__DISKID__?list-type=2&prefix=docs/&delimiter=/&max-keys=100 HTTP/1.1
Host: kms.example.com
Authorization: AWS4-HMAC-SHA256 Credential=AKxxx/20260805/cn-kms/s3/aws4_request, ...
响应¶
结构:S3 XML(非 KMS Resource)。包含 Name、Prefix、Delimiter、KeyCount、MaxKeys、IsTruncated,以及 Contents(对象数组,每项含 Key/LastModified/ETag/Size/StorageClass)与 CommonPrefixes(折叠的虚拟目录)。响应中文件夹(key 以 / 结尾)排在文件之前。
成功示例:
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>__DISKID__</Name>
<Prefix>docs/</Prefix>
<Delimiter>/</Delimiter>
<KeyCount>2</KeyCount>
<MaxKeys>100</MaxKeys>
<IsTruncated>false</IsTruncated>
<Contents><Key>docs/report.docx</Key><LastModified>2026-08-01T03:00:00Z</LastModified><ETag>"etag"</ETag><Size>1024</Size><StorageClass>STANDARD</StorageClass></Contents>
<CommonPrefixes><Prefix>docs/sub/</Prefix></CommonPrefixes>
</ListBucketResult>
失败示例(不支持的 list-type):
(HTTP 400)3. 下载对象(GetObject)¶
按对象 key 下载文件二进制流。支持 Range 区间、If-Match / If-None-Match 条件读取,返回 ETag 与 Last-Modified 头。
- 接口类型:REST 资源(二进制流响应)
- 请求方式:
GET - 请求路径:
/{bucket}/**(完整:{kms-context}/s3/{bucket}/{objectKey}) - 鉴权:是(S3 数据面 SigV4 / Bearer JWT,据源码)
- Tag:kms S3 数据面模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| bucket | path | string | 是 | 桶名(网盘 diskId) |
| objectKey | path | string | 是 | 对象 key(剩余路径,服务端做百分号解码 + UTF-8 还原) |
| Range | header | string | 否 | 如 bytes=0-1023、bytes=-512(后缀)、bytes=1024- |
| If-Match | header | string | 否 | 条件读取:仅当 ETag 匹配时返回内容,否则 PreconditionFailed(412) |
| If-None-Match | header | string | 否 | 条件读取:当 ETag 匹配时返回 304 Not Modified |
请求示例¶
GET /s3/__DISKID__/docs/report.docx HTTP/1.1
Host: kms.example.com
Authorization: AWS4-HMAC-SHA256 Credential=AKxxx/20260805/cn-kms/s3/aws4_request, ...
Range: bytes=0-1023
响应¶
结构:二进制流。Content-Type 按文件扩展名推断(.mp4 → video/mp4,.webm → video/webm,.mp3 → audio/mpeg,其余为 application/octet-stream)。响应头包含 ETag(带引号)、Last-Modified(RFC 7231)、Accept-Ranges: bytes、Content-Length;区间请求时为 206 Partial Content + Content-Range: bytes <start>-<end>/<size>;条件命中 If-None-Match 时为 304 Not Modified(无响应体)。
失败示例(对象不存在):
(HTTP 404)4. 获取对象元数据(HeadObject)¶
按对象 key 用 HEAD 方法获取元数据,不返回响应体。对以 / 结尾的 key(或库中存在 <key>/ 形式的文件夹标记)返回文件夹元数据(x-amz-meta-is-folder: true)。
- 接口类型:REST 资源(HEAD 响应,无 body)
- 请求方式:
HEAD - 请求路径:
/{bucket}/**(完整:{kms-context}/s3/{bucket}/{objectKey}) - 鉴权:是(S3 数据面 SigV4 / Bearer JWT,据源码)
- Tag:kms S3 数据面模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| bucket | path | string | 是 | 桶名(网盘 diskId) |
| objectKey | path | string | 是 | 对象 key |
| If-Match | header | string | 否 | 条件读取,行为同 #3 |
| If-None-Match | header | string | 否 | 条件读取,命中时返回 304 |
请求示例¶
HEAD /s3/__DISKID__/docs/report.docx HTTP/1.1
Host: kms.example.com
Authorization: AWS4-HMAC-SHA256 Credential=AKxxx/20260805/cn-kms/s3/aws4_request, ...
响应¶
结构:无响应体。响应头:HTTP 200 OK、ETag(带引号)、Content-Length(对象字节大小)、Accept-Ranges: bytes、Last-Modified;文件夹对象为 Content-Length: 0 + x-amz-meta-is-folder: true。If-None-Match 命中返回 304 Not Modified。
失败:对象不存在返回 HTTP 404(无响应体);鉴权失败返回 HTTP 403(XML 错误体)。
5. 上传对象(PutObject / UploadPart / CopyObject)¶
按对象 key 整体上传文件、上传分片,或在网盘内/跨网盘复制对象。同一端点按 query 参数 uploadId + partNumber、请求头 x-amz-copy-source 与请求体是否为空分流为三种语义。
- 接口类型:REST 资源
- 请求方式:
PUT - 请求路径:
/{bucket}/**(完整:{kms-context}/s3/{bucket}/{objectKey}) - 鉴权:是(S3 数据面 SigV4 / Bearer JWT,据源码)
- Tag:kms S3 数据面模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| bucket | path | string | 是 | 桶名(网盘 diskId) |
| objectKey | path | string | 是 | 对象 key;以 / 结尾时作为文件夹标记创建 |
| uploadId | query | string | 否 | 分片上传 Id;与 partNumber 同时传入表示 UploadPart |
| partNumber | query | int | 否 | 分片序号(1 起始),配合 uploadId |
| x-amz-copy-source | header | string | 否 | CopyObject 源(/<srcBucket>/<srcKey>,需 URL 编码);当请求体为空或 x-id=CopyObject 时触发 CopyObject |
| If-Match | header | string | 否 | 写入条件:目标已存在对象的 ETag 必须匹配,否则 PreconditionFailed |
| If-None-Match | header | string | 否 | 写入条件:传 * 表示仅当目标不存在时写入 |
| body | body | binary | 否 | 整体上传/分片二进制流;CopyObject 时为空 |
请求示例¶
整体上传:
PUT /s3/__DISKID__/docs/report.docx HTTP/1.1
Host: kms.example.com
Authorization: AWS4-HMAC-SHA256 Credential=AKxxx/20260805/cn-kms/s3/aws4_request, ...
Content-Type: application/octet-stream
Content-Length: 1024
<二进制内容>
UploadPart:
PUT /s3/__DISKID__/docs/big.zip?uploadId=__UPLOADID__&partNumber=1 HTTP/1.1
Authorization: AWS4-HMAC-SHA256 Credential=AKxxx/...
CopyObject:
PUT /s3/__DISKID__/docs/copy.docx HTTP/1.1
Authorization: AWS4-HMAC-SHA256 Credential=AKxxx/...
x-amz-copy-source: /__DISKID_SRC__/docs/origin.docx
Content-Length: 0
响应¶
结构:S3 XML 或纯响应头。
- 整体上传:HTTP 200,响应头 ETag(带引号),无响应体。
- UploadPart:HTTP 200,响应头 ETag(分片 ETag),无响应体。
- CopyObject:HTTP 200,响应体为 CopyObjectResult XML(含 LastModified、ETag),响应头含 ETag。
成功示例(CopyObject):
<?xml version="1.0" encoding="UTF-8"?>
<CopyObjectResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<LastModified>2026-08-05T03:00:00.000Z</LastModified>
<ETag>"etag"</ETag>
</CopyObjectResult>
失败示例(缺少 key):
(HTTP 400)6. POST 操作(InitiateMultipartUpload / CompleteMultipartUpload)¶
S3 分片上传的初始化与完成通过 POST 触发:query 参数 uploads(无 uploadId)触发初始化;query 参数 uploadId 配合请求体(分片 ETag XML)触发完成。
- 接口类型:REST 资源
- 请求方式:
POST - 请求路径:
/{bucket}/**(完整:{kms-context}/s3/{bucket}/{objectKey}) - 鉴权:是(S3 数据面 SigV4 / Bearer JWT,据源码)
- Tag:kms S3 数据面模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| bucket | path | string | 是 | 桶名(网盘 diskId) |
| objectKey | path | string | 是 | 对象 key |
| uploads | query | string | 否 | 传入(任意值)且不传 uploadId 时触发 InitiateMultipartUpload |
| uploadId | query | string | 是(完成时) | 触发 CompleteMultipartUpload;服务端按 uploadId 校验对象 key 一致性 |
| body | body | string | 否 | CompleteMultipartUpload 的分片列表 XML(由 S3MultipartService.completeMultipartUpload 解析) |
请求示例¶
初始化分片上传:
POST /s3/__DISKID__/docs/big.zip?uploads= HTTP/1.1
Authorization: AWS4-HMAC-SHA256 Credential=AKxxx/...
完成分片上传:
POST /s3/__DISKID__/docs/big.zip?uploadId=__UPLOADID__ HTTP/1.1
Authorization: AWS4-HMAC-SHA256 Credential=AKxxx/...
Content-Type: application/xml
<CompleteMultipartUpload><Part><PartNumber>1</PartNumber><ETag>"etag1"</ETag></Part></CompleteMultipartUpload>
响应¶
结构:S3 XML。
成功示例(InitiateMultipartUpload):
<?xml version="1.0" encoding="UTF-8"?>
<InitiateMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Bucket>__DISKID__</Bucket>
<Key>docs/big.zip</Key>
<UploadId>__UPLOADID__</UploadId>
</InitiateMultipartUploadResult>
成功示例(CompleteMultipartUpload):
<?xml version="1.0" encoding="UTF-8"?>
<CompleteMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Location>local</Location>
<Bucket>__DISKID__</Bucket>
<Key>docs/big.zip</Key>
<ETag>"合并后etag"</ETag>
</CompleteMultipartUploadResult>
失败示例(参数缺失):
(HTTP 400)7. 删除对象(DeleteObject / AbortMultipartUpload)¶
按对象 key 删除文件或文件夹;当带 query 参数 uploadId 时取消未完成的分片上传。
- 接口类型:REST 资源
- 请求方式:
DELETE - 请求路径:
/{bucket}/**(完整:{kms-context}/s3/{bucket}/{objectKey}) - 鉴权:是(S3 数据面 SigV4 / Bearer JWT,据源码)
- Tag:kms S3 数据面模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| bucket | path | string | 是 | 桶名(网盘 diskId) |
| objectKey | path | string | 是 | 对象 key |
| uploadId | query | string | 否 | 传入时表示 AbortMultipartUpload(取消分片上传) |
| If-Match | header | string | 否 | 写入条件(仅对非文件夹对象生效);不满足返回 PreconditionFailed |
| If-None-Match | header | string | 否 | 写入条件;不满足返回 PreconditionFailed |
请求示例¶
DELETE /s3/__DISKID__/docs/report.docx HTTP/1.1
Host: kms.example.com
Authorization: AWS4-HMAC-SHA256 Credential=AKxxx/20260805/cn-kms/s3/aws4_request, ...
响应¶
结构:无响应体。成功 HTTP 204 No Content;鉴权失败 HTTP 403(XML 错误体);参数有误 HTTP 400。