跳转至

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 + KmsS3DataPlanePathKmsSecurityFilterdoFilter 开头调用 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>,常见 CodeAccessDenied(HTTP 403)、InvalidRequest(HTTP 400)、NoSuchKey(HTTP 404)、PreconditionFailed(HTTP 412)。
  • HTTP 状态码:成功 200 OK(列举、GetObject 整体下载、CopyObjectPutObject、分片相关);HeadObject 成功 200(带 Content-Length: 0);GetObject 区间命中 206 Partial Content;条件读取未改 304 Not ModifiedDeleteObject 成功 204 No Content;区间不满足 416 Requested Range Not Satisfiable
  • 路径变量bucket 实为 KMS 内部 网盘 Id(diskId),由 bridgeService.findDisk(bucket) 解析为 Disk/** 部分为对象 key(按 / 分隔的虚拟目录路径,服务端做百分号解码 + UTF-8 还原)。
  • ETag / 条件请求GetObject / HeadObject 支持 If-MatchIf-None-Match(基于 ETag);PutObject / DeleteObject 支持 If-MatchIf-None-MatchCopyObject 支持 x-amz-copy-source-if-matchx-amz-copy-source-if-none-match。条件不满足返回 PreconditionFailed(HTTP 412)。
  • Range 请求GetObject 支持 Range: bytes=<start>-<end> 单段区间(多段仅取第一段);返回 206 Partial ContentContent-Range: bytes <start>-<end>/<size>

1. 列举所有桶(ListBuckets)

返回当前用户可访问的全部网盘(个人网盘、公司网盘、团队网盘;公司/Wiki 网盘仅企业知识管理员可见),按 S3 协议序列化为桶列表 XML。

  • 接口类型:REST 资源
  • 请求方式GET
  • 请求路径/`(即基址根;同时匹配GET /s3GET /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-tokenlist-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)。包含 NamePrefixDelimiterKeyCountMaxKeysIsTruncated,以及 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-1023bytes=-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 按文件扩展名推断(.mp4video/mp4.webmvideo/webm.mp3audio/mpeg,其余为 application/octet-stream)。响应头包含 ETag(带引号)、Last-Modified(RFC 7231)、Accept-Ranges: bytesContent-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 OKETag(带引号)、Content-Length(对象字节大小)、Accept-Ranges: bytesLast-Modified;文件夹对象为 Content-Length: 0 + x-amz-meta-is-folder: trueIf-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(含 LastModifiedETag),响应头含 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