跳转至

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 返回 HTTP 403。
  • 执行用户:控制器内 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>

失败示例(鉴权失败):

<Error><Code>AccessDenied</Code><Message>签名验证失败</Message></Error>
(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):

<Error><Code>InvalidRequest</Code><Message>不支持的 list-type 参数</Message></Error>
(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(无响应体)。

失败示例(对象不存在):

<Error><Code>NoSuchKey</Code><Message>No such key</Message></Error>
(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):

<Error><Code>InvalidRequest</Code><Message>缺少 object key</Message></Error>
(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>

失败示例(参数缺失):

<Error><Code>InvalidRequest</Code><Message>不支持的 POST</Message></Error>
(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。