Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
GeoFlyApi
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
GeoFly
GeoFlyApi
Commits
24356261
Commit
24356261
authored
Jul 28, 2026
by
真的三个金的鑫
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
接入versityGW
parent
40c6a67b
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
642 additions
and
8 deletions
+642
-8
cloud-sdk/src/main/java/com/dji/sdk/cloudapi/storage/OssTypeEnum.java
+13
-1
pom.xml
+21
-1
sample/src/main/java/com/dji/sample/component/oss/model/OssConfiguration.java
+19
-0
sample/src/main/java/com/dji/sample/component/oss/service/impl/VersityGwAdminClient.java
+153
-0
sample/src/main/java/com/dji/sample/component/oss/service/impl/VersityGwServiceImpl.java
+298
-0
sample/src/main/java/com/dji/sample/component/redis/RedisConst.java
+4
-0
sample/src/main/java/com/dji/sample/storage/service/impl/StorageServiceImpl.java
+2
-2
sample/src/main/resources/application-dev.yml
+7
-2
sample/src/main/resources/application.yml
+7
-2
sample/src/test/java/com/dji/sample/component/oss/VersityGwAdminClientTest.java
+118
-0
No files found.
cloud-sdk/src/main/java/com/dji/sdk/cloudapi/storage/OssTypeEnum.java
View file @
24356261
...
...
@@ -15,7 +15,12 @@ public enum OssTypeEnum {
AWS
(
"aws"
),
MINIO
(
"minio"
);
MINIO
(
"minio"
),
/**
* VersityGW(S3 兼容)。下发给 Pilot/机场时请映射为 {@link #MINIO} 或 {@link #AWS}。
*/
VERSITYGW
(
"versity"
);
private
String
type
;
...
...
@@ -27,4 +32,11 @@ public enum OssTypeEnum {
public
String
getType
()
{
return
type
;
}
/**
* Pilot / 机场仅识别 ali / aws / minio,VersityGW 对外按 minio 上报。
*/
public
OssTypeEnum
toClientProvider
()
{
return
this
==
VERSITYGW
?
MINIO
:
this
;
}
}
pom.xml
View file @
24356261
...
...
@@ -23,6 +23,9 @@
<project.reporting.outputEncoding>
UTF-8
</project.reporting.outputEncoding>
<javax-activation.version>
1.1.1
</javax-activation.version>
<mqtt.version>
5.5.5
</mqtt.version>
<!-- OSS 配置 -->
<aws.sdk.version>
2.28.22
</aws.sdk.version>
<aws.crt.version>
0.31.3
</aws.crt.version>
</properties>
<dependencyManagement>
...
...
@@ -67,7 +70,24 @@
<groupId>
com.fasterxml.jackson.datatype
</groupId>
<artifactId>
jackson-datatype-jsr310
</artifactId>
</dependency>
<!-- AWS SDK for Java 2.x -->
<dependency>
<groupId>
software.amazon.awssdk
</groupId>
<artifactId>
s3
</artifactId>
<version>
${aws.sdk.version}
</version>
</dependency>
<!-- 使用AWS基于 CRT 的 S3 客户端 -->
<dependency>
<groupId>
software.amazon.awssdk.crt
</groupId>
<artifactId>
aws-crt
</artifactId>
<version>
${aws.crt.version}
</version>
</dependency>
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
<dependency>
<groupId>
software.amazon.awssdk
</groupId>
<artifactId>
s3-transfer-manager
</artifactId>
<version>
${aws.sdk.version}
</version>
</dependency>
</dependencies>
<build>
...
...
sample/src/main/java/com/dji/sample/component/oss/model/OssConfiguration.java
View file @
24356261
...
...
@@ -44,6 +44,17 @@ public class OssConfiguration {
public
static
String
objectDirPrefix
;
/**
* VersityGW STS 模式:temp=Admin 创建临时用户;static=直接下发长期 AK/SK。
*/
public
static
String
stsMode
=
"temp"
;
/**
* 临时用户角色:user / userplus / admin。
* 共享 bucket 且未改 ownership 时建议 admin(可访问全部 bucket)。
*/
public
static
String
stsUserRole
=
"admin"
;
public
void
setProvider
(
OssTypeEnum
provider
)
{
OssConfiguration
.
provider
=
provider
;
}
...
...
@@ -87,6 +98,14 @@ public class OssConfiguration {
public
void
setObjectDirPrefix
(
String
objectDirPrefix
)
{
OssConfiguration
.
objectDirPrefix
=
objectDirPrefix
;
}
public
void
setStsMode
(
String
stsMode
)
{
OssConfiguration
.
stsMode
=
stsMode
;
}
public
void
setStsUserRole
(
String
stsUserRole
)
{
OssConfiguration
.
stsUserRole
=
stsUserRole
;
}
}
...
...
sample/src/main/java/com/dji/sample/component/oss/service/impl/VersityGwAdminClient.java
0 → 100644
View file @
24356261
package
com
.
dji
.
sample
.
component
.
oss
.
service
.
impl
;
import
com.dji.sample.component.oss.model.OssConfiguration
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.stereotype.Component
;
import
software.amazon.awssdk.auth.credentials.AwsBasicCredentials
;
import
software.amazon.awssdk.auth.signer.AwsS3V4Signer
;
import
software.amazon.awssdk.auth.signer.params.AwsS3V4SignerParams
;
import
software.amazon.awssdk.http.SdkHttpFullRequest
;
import
software.amazon.awssdk.http.SdkHttpMethod
;
import
software.amazon.awssdk.regions.Region
;
import
java.io.ByteArrayInputStream
;
import
java.net.URI
;
import
java.net.http.HttpClient
;
import
java.net.http.HttpRequest
;
import
java.net.http.HttpResponse
;
import
java.nio.charset.StandardCharsets
;
import
java.time.Duration
;
import
java.util.List
;
import
java.util.Map
;
/**
* VersityGW Admin API 客户端(S3 SigV4)。
* 使用 {@link AwsS3V4Signer},会自动带上并签名 x-amz-content-sha256。
*/
@Slf4j
@Component
public
class
VersityGwAdminClient
{
private
static
final
String
SERVICE
=
"s3"
;
private
final
HttpClient
httpClient
=
HttpClient
.
newBuilder
()
.
connectTimeout
(
Duration
.
ofSeconds
(
10
))
.
build
();
private
final
AwsS3V4Signer
signer
=
AwsS3V4Signer
.
create
();
public
void
createUser
(
String
accessKey
,
String
secretKey
,
String
role
)
{
String
body
=
""
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+
"<Account>"
+
"<Access>"
+
xmlEscape
(
accessKey
)
+
"</Access>"
+
"<Secret>"
+
xmlEscape
(
secretKey
)
+
"</Secret>"
+
"<Role>"
+
xmlEscape
(
role
)
+
"</Role>"
+
"</Account>"
;
HttpResponse
<
String
>
response
=
execute
(
SdkHttpMethod
.
PATCH
,
"/create-user"
,
null
,
body
);
int
code
=
response
.
statusCode
();
if
(
code
!=
201
&&
code
!=
200
)
{
throw
new
IllegalStateException
(
"VersityGW create-user failed, status="
+
code
+
", body="
+
response
.
body
());
}
log
.
info
(
"VersityGW temp user created: access={}"
,
accessKey
);
}
public
void
deleteUser
(
String
accessKey
)
{
HttpResponse
<
String
>
response
=
execute
(
SdkHttpMethod
.
PATCH
,
"/delete-user"
,
Map
.
of
(
"access"
,
accessKey
),
null
);
int
code
=
response
.
statusCode
();
if
(
code
!=
204
&&
code
!=
200
&&
code
!=
404
)
{
throw
new
IllegalStateException
(
"VersityGW delete-user failed, status="
+
code
+
", body="
+
response
.
body
());
}
log
.
info
(
"VersityGW temp user deleted: access={}"
,
accessKey
);
}
public
String
listUsers
()
{
HttpResponse
<
String
>
response
=
execute
(
SdkHttpMethod
.
PATCH
,
"/list-users"
,
null
,
null
);
int
code
=
response
.
statusCode
();
if
(
code
!=
200
&&
code
!=
204
)
{
throw
new
IllegalStateException
(
"VersityGW list-users failed, status="
+
code
+
", body="
+
response
.
body
());
}
return
response
.
body
()
==
null
?
""
:
response
.
body
();
}
private
HttpResponse
<
String
>
execute
(
SdkHttpMethod
method
,
String
path
,
Map
<
String
,
String
>
query
,
String
body
)
{
try
{
byte
[]
payload
=
body
==
null
?
new
byte
[
0
]
:
body
.
getBytes
(
StandardCharsets
.
UTF_8
);
URI
endpoint
=
URI
.
create
(
trimTrailingSlash
(
OssConfiguration
.
endpoint
));
SdkHttpFullRequest
.
Builder
requestBuilder
=
SdkHttpFullRequest
.
builder
()
.
method
(
method
)
.
protocol
(
endpoint
.
getScheme
())
.
host
(
endpoint
.
getHost
())
.
port
(
resolvePort
(
endpoint
))
.
encodedPath
(
path
)
.
contentStreamProvider
(()
->
new
ByteArrayInputStream
(
payload
));
if
(
query
!=
null
)
{
query
.
forEach
(
requestBuilder:
:
putRawQueryParameter
);
}
if
(
body
!=
null
)
{
requestBuilder
.
putHeader
(
"Content-Type"
,
"application/xml"
);
}
SdkHttpFullRequest
signed
=
signer
.
sign
(
requestBuilder
.
build
(),
AwsS3V4SignerParams
.
builder
()
.
awsCredentials
(
AwsBasicCredentials
.
create
(
OssConfiguration
.
accessKey
,
OssConfiguration
.
secretKey
))
.
signingName
(
SERVICE
)
.
signingRegion
(
Region
.
of
(
OssConfiguration
.
region
))
.
build
());
HttpRequest
.
Builder
httpBuilder
=
HttpRequest
.
newBuilder
()
.
uri
(
signed
.
getUri
())
.
timeout
(
Duration
.
ofSeconds
(
30
))
.
method
(
method
.
name
(),
HttpRequest
.
BodyPublishers
.
ofByteArray
(
payload
));
boolean
hasContentSha256
=
false
;
for
(
Map
.
Entry
<
String
,
List
<
String
>>
header
:
signed
.
headers
().
entrySet
())
{
String
name
=
header
.
getKey
();
if
(
"Content-Length"
.
equalsIgnoreCase
(
name
)
||
"Host"
.
equalsIgnoreCase
(
name
))
{
continue
;
}
if
(
"x-amz-content-sha256"
.
equalsIgnoreCase
(
name
))
{
hasContentSha256
=
true
;
}
for
(
String
value
:
header
.
getValue
())
{
httpBuilder
.
header
(
name
,
value
);
}
}
if
(!
hasContentSha256
)
{
throw
new
IllegalStateException
(
"Signed request missing x-amz-content-sha256 header"
);
}
return
httpClient
.
send
(
httpBuilder
.
build
(),
HttpResponse
.
BodyHandlers
.
ofString
());
}
catch
(
IllegalStateException
e
)
{
throw
e
;
}
catch
(
Exception
e
)
{
throw
new
IllegalStateException
(
"VersityGW admin API call failed: "
+
path
,
e
);
}
}
private
static
int
resolvePort
(
URI
endpoint
)
{
if
(
endpoint
.
getPort
()
!=
-
1
)
{
return
endpoint
.
getPort
();
}
return
"https"
.
equalsIgnoreCase
(
endpoint
.
getScheme
())
?
443
:
80
;
}
private
static
String
trimTrailingSlash
(
String
endpoint
)
{
if
(
endpoint
!=
null
&&
endpoint
.
endsWith
(
"/"
))
{
return
endpoint
.
substring
(
0
,
endpoint
.
length
()
-
1
);
}
return
endpoint
;
}
private
static
String
xmlEscape
(
String
value
)
{
return
value
.
replace
(
"&"
,
"&"
)
.
replace
(
"<"
,
"<"
)
.
replace
(
">"
,
">"
)
.
replace
(
"\""
,
"""
)
.
replace
(
"'"
,
"'"
);
}
}
sample/src/main/java/com/dji/sample/component/oss/service/impl/VersityGwServiceImpl.java
0 → 100644
View file @
24356261
package
com
.
dji
.
sample
.
component
.
oss
.
service
.
impl
;
import
com.dji.sample.component.oss.model.OssConfiguration
;
import
com.dji.sample.component.oss.service.IOssService
;
import
com.dji.sample.component.redis.RedisConst
;
import
com.dji.sample.component.redis.RedisOpsUtils
;
import
com.dji.sdk.cloudapi.storage.CredentialsToken
;
import
com.dji.sdk.cloudapi.storage.OssTypeEnum
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.scheduling.annotation.Scheduled
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.StringUtils
;
import
software.amazon.awssdk.auth.credentials.AwsBasicCredentials
;
import
software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
;
import
software.amazon.awssdk.core.sync.RequestBody
;
import
software.amazon.awssdk.regions.Region
;
import
software.amazon.awssdk.services.s3.S3Client
;
import
software.amazon.awssdk.services.s3.S3Configuration
;
import
software.amazon.awssdk.services.s3.model.*
;
import
software.amazon.awssdk.services.s3.presigner.S3Presigner
;
import
software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest
;
import
javax.annotation.PreDestroy
;
import
java.io.ByteArrayInputStream
;
import
java.io.InputStream
;
import
java.net.URI
;
import
java.net.URL
;
import
java.security.SecureRandom
;
import
java.time.Duration
;
import
java.util.*
;
import
java.util.concurrent.TimeUnit
;
/**
* VersityGW S3 兼容实现(AWS SDK v2 + path-style)。
* <p>
* STS:默认通过 Admin API 创建短期 IAM 用户({@code oss.sts-mode=temp}),到期后删除;
* 仍以明文 AK/SK 下发,但不再泄露长期 root,且可回收。
*/
@Service
@Slf4j
public
class
VersityGwServiceImpl
implements
IOssService
{
private
static
final
SecureRandom
RANDOM
=
new
SecureRandom
();
private
static
final
char
[]
SECRET_CHARS
=
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
.
toCharArray
();
private
S3Client
client
;
private
S3Presigner
presigner
;
@Autowired
private
VersityGwAdminClient
adminClient
;
@Override
public
OssTypeEnum
getOssType
()
{
return
OssTypeEnum
.
VERSITYGW
;
}
@Override
public
CredentialsToken
getCredentials
()
{
if
(
"static"
.
equalsIgnoreCase
(
OssConfiguration
.
stsMode
))
{
return
new
CredentialsToken
(
OssConfiguration
.
accessKey
,
OssConfiguration
.
secretKey
,
""
,
OssConfiguration
.
expire
);
}
return
createTempCredentials
();
}
private
CredentialsToken
createTempCredentials
()
{
String
accessKey
=
"tmp"
+
UUID
.
randomUUID
().
toString
().
replace
(
"-"
,
""
).
substring
(
0
,
17
);
String
secretKey
=
randomSecret
(
40
);
String
role
=
StringUtils
.
hasText
(
OssConfiguration
.
stsUserRole
)
?
OssConfiguration
.
stsUserRole
:
"admin"
;
adminClient
.
createUser
(
accessKey
,
secretKey
,
role
);
long
expireSec
=
OssConfiguration
.
expire
==
null
?
3600L
:
OssConfiguration
.
expire
;
long
expireAt
=
System
.
currentTimeMillis
()
/
1000
+
expireSec
;
RedisOpsUtils
.
hashSet
(
RedisConst
.
VGW_STS_USERS
,
accessKey
,
expireAt
);
return
new
CredentialsToken
(
accessKey
,
secretKey
,
""
,
expireSec
);
}
private
static
String
randomSecret
(
int
length
)
{
char
[]
buf
=
new
char
[
length
];
for
(
int
i
=
0
;
i
<
length
;
i
++)
{
buf
[
i
]
=
SECRET_CHARS
[
RANDOM
.
nextInt
(
SECRET_CHARS
.
length
)];
}
return
new
String
(
buf
);
}
/**
* 清理已过期的临时 IAM 用户。
*/
@Scheduled
(
initialDelay
=
60
,
fixedRate
=
60
,
timeUnit
=
TimeUnit
.
SECONDS
)
public
void
cleanupExpiredTempUsers
()
{
if
(!
OssConfiguration
.
enable
||
OssConfiguration
.
provider
!=
OssTypeEnum
.
VERSITYGW
)
{
return
;
}
if
(
"static"
.
equalsIgnoreCase
(
OssConfiguration
.
stsMode
))
{
return
;
}
Set
<
Object
>
fields
=
RedisOpsUtils
.
hashKeys
(
RedisConst
.
VGW_STS_USERS
);
if
(
fields
==
null
||
fields
.
isEmpty
())
{
return
;
}
long
now
=
System
.
currentTimeMillis
()
/
1000
;
for
(
Object
field
:
fields
)
{
String
accessKey
=
String
.
valueOf
(
field
);
Object
value
=
RedisOpsUtils
.
hashGet
(
RedisConst
.
VGW_STS_USERS
,
accessKey
);
long
expireAt
;
try
{
expireAt
=
Long
.
parseLong
(
String
.
valueOf
(
value
));
}
catch
(
Exception
e
)
{
RedisOpsUtils
.
hashDel
(
RedisConst
.
VGW_STS_USERS
,
new
Object
[]{
accessKey
});
continue
;
}
if
(
expireAt
>
now
)
{
continue
;
}
try
{
adminClient
.
deleteUser
(
accessKey
);
}
catch
(
Exception
e
)
{
log
.
warn
(
"Failed to delete expired VersityGW user {}: {}"
,
accessKey
,
e
.
getMessage
());
}
finally
{
RedisOpsUtils
.
hashDel
(
RedisConst
.
VGW_STS_USERS
,
new
Object
[]{
accessKey
});
}
}
}
@Override
public
URL
getObjectUrl
(
String
bucket
,
String
objectKey
)
{
try
{
GetObjectPresignRequest
presignRequest
=
GetObjectPresignRequest
.
builder
()
.
signatureDuration
(
Duration
.
ofSeconds
(
OssConfiguration
.
expire
))
.
getObjectRequest
(
b
->
b
.
bucket
(
bucket
).
key
(
objectKey
))
.
build
();
return
presigner
.
presignGetObject
(
presignRequest
).
url
();
}
catch
(
Exception
e
)
{
log
.
error
(
"Failed to generate presigned URL for {}/{}"
,
bucket
,
objectKey
,
e
);
throw
new
RuntimeException
(
"The file does not exist on the OssConfiguration."
);
}
}
@Override
public
Boolean
deleteObject
(
String
bucket
,
String
objectKey
)
{
try
{
client
.
deleteObject
(
DeleteObjectRequest
.
builder
().
bucket
(
bucket
).
key
(
objectKey
).
build
());
return
true
;
}
catch
(
Exception
e
)
{
log
.
error
(
"Failed to delete file {}/{}"
,
bucket
,
objectKey
,
e
);
return
false
;
}
}
@Override
public
InputStream
getObject
(
String
bucket
,
String
objectKey
)
{
try
{
byte
[]
bytes
=
client
.
getObjectAsBytes
(
GetObjectRequest
.
builder
().
bucket
(
bucket
).
key
(
objectKey
).
build
()).
asByteArray
();
return
new
ByteArrayInputStream
(
bytes
);
}
catch
(
Exception
e
)
{
log
.
error
(
"Failed to get object {}/{}"
,
bucket
,
objectKey
,
e
);
return
InputStream
.
nullInputStream
();
}
}
@Override
public
void
putObject
(
String
bucket
,
String
objectKey
,
InputStream
input
)
{
if
(
Boolean
.
TRUE
.
equals
(
objectExists
(
bucket
,
objectKey
)))
{
throw
new
RuntimeException
(
"The filename already exists."
);
}
try
{
byte
[]
bytes
=
input
.
readAllBytes
();
PutObjectResponse
response
=
client
.
putObject
(
PutObjectRequest
.
builder
().
bucket
(
bucket
).
key
(
objectKey
).
build
(),
RequestBody
.
fromBytes
(
bytes
));
log
.
info
(
"Upload FlighttaskCreateFile: {}"
,
response
.
eTag
());
}
catch
(
Exception
e
)
{
log
.
error
(
"Failed to upload FlighttaskCreateFile {}."
,
objectKey
,
e
);
}
}
@Override
public
void
createClient
()
{
if
(
Objects
.
nonNull
(
this
.
client
))
{
return
;
}
StaticCredentialsProvider
credentialsProvider
=
StaticCredentialsProvider
.
create
(
AwsBasicCredentials
.
create
(
OssConfiguration
.
accessKey
,
OssConfiguration
.
secretKey
));
S3Configuration
s3Configuration
=
S3Configuration
.
builder
()
.
pathStyleAccessEnabled
(
true
)
.
chunkedEncodingEnabled
(
false
)
.
build
();
URI
endpoint
=
URI
.
create
(
OssConfiguration
.
endpoint
);
Region
region
=
Region
.
of
(
OssConfiguration
.
region
);
this
.
client
=
S3Client
.
builder
()
.
endpointOverride
(
endpoint
)
.
region
(
region
)
.
credentialsProvider
(
credentialsProvider
)
.
serviceConfiguration
(
s3Configuration
)
.
build
();
this
.
presigner
=
S3Presigner
.
builder
()
.
endpointOverride
(
endpoint
)
.
region
(
region
)
.
credentialsProvider
(
credentialsProvider
)
.
serviceConfiguration
(
s3Configuration
)
.
build
();
}
@PreDestroy
public
void
destroy
()
{
if
(
presigner
!=
null
)
{
presigner
.
close
();
presigner
=
null
;
}
if
(
client
!=
null
)
{
client
.
close
();
client
=
null
;
}
}
@Override
public
List
<
Map
<
String
,
String
>>
listFolders
(
String
bucket
,
String
prefix
)
{
List
<
Map
<
String
,
String
>>
folders
=
new
ArrayList
<>();
try
{
String
normalizedPrefix
=
prefix
==
null
?
""
:
prefix
;
if
(!
normalizedPrefix
.
isEmpty
()
&&
!
normalizedPrefix
.
endsWith
(
"/"
))
{
normalizedPrefix
+=
"/"
;
}
final
String
listPrefix
=
normalizedPrefix
;
String
continuationToken
=
null
;
do
{
ListObjectsV2Request
.
Builder
requestBuilder
=
ListObjectsV2Request
.
builder
()
.
bucket
(
bucket
)
.
prefix
(
listPrefix
)
.
delimiter
(
"/"
);
if
(
continuationToken
!=
null
)
{
requestBuilder
.
continuationToken
(
continuationToken
);
}
ListObjectsV2Response
response
=
client
.
listObjectsV2
(
requestBuilder
.
build
());
if
(
response
.
commonPrefixes
()
!=
null
)
{
for
(
CommonPrefix
commonPrefix
:
response
.
commonPrefixes
())
{
String
folderPath
=
commonPrefix
.
prefix
();
String
folderName
=
folderPath
.
substring
(
listPrefix
.
length
());
if
(
folderName
.
endsWith
(
"/"
))
{
folderName
=
folderName
.
substring
(
0
,
folderName
.
length
()
-
1
);
}
Map
<
String
,
String
>
folderInfo
=
new
HashMap
<>();
folderInfo
.
put
(
"name"
,
folderName
);
folderInfo
.
put
(
"prefix"
,
folderPath
);
folders
.
add
(
folderInfo
);
}
}
continuationToken
=
Boolean
.
TRUE
.
equals
(
response
.
isTruncated
())
?
response
.
nextContinuationToken
()
:
null
;
}
while
(
continuationToken
!=
null
);
}
catch
(
Exception
e
)
{
log
.
error
(
"Failed to list folders: {}"
,
e
.
getMessage
());
}
return
folders
;
}
@Override
public
Boolean
objectExists
(
String
bucket
,
String
objectKey
)
{
try
{
client
.
headObject
(
HeadObjectRequest
.
builder
().
bucket
(
bucket
).
key
(
objectKey
).
build
());
return
true
;
}
catch
(
NoSuchKeyException
e
)
{
return
false
;
}
catch
(
S3Exception
e
)
{
if
(
e
.
statusCode
()
==
404
)
{
return
false
;
}
throw
e
;
}
}
@Override
public
Boolean
copyObject
(
String
bucket
,
String
sourceKey
,
String
destKey
)
{
try
{
client
.
copyObject
(
CopyObjectRequest
.
builder
()
.
sourceBucket
(
bucket
)
.
sourceKey
(
sourceKey
)
.
destinationBucket
(
bucket
)
.
destinationKey
(
destKey
)
.
build
());
return
true
;
}
catch
(
Exception
e
)
{
log
.
error
(
"Failed to copy object: {}"
,
e
.
getMessage
());
return
false
;
}
}
}
sample/src/main/java/com/dji/sample/component/redis/RedisConst.java
View file @
24356261
...
...
@@ -101,4 +101,7 @@ public final class RedisConst {
*/
public
static
final
String
DEVICE_TOTAL_FLIGHT_TIME_PREFIX
=
DEVICE_DETAIL_PREFIX
+
"total_flight_time"
+
DELIMITER
;
/** VersityGW 临时 STS 用户:hash field=accessKey, value=expireEpochSec */
public
static
final
String
VGW_STS_USERS
=
"oss"
+
DELIMITER
+
"vgw_sts_users"
;
}
\ No newline at end of file
sample/src/main/java/com/dji/sample/storage/service/impl/StorageServiceImpl.java
View file @
24356261
...
...
@@ -46,7 +46,7 @@ public class StorageServiceImpl extends AbstractMediaService implements IStorage
.
setEndpoint
(
OssConfiguration
.
endpoint
)
.
setBucket
(
OssConfiguration
.
bucket
)
.
setCredentials
(
ossService
.
getCredentials
())
.
setProvider
(
OssConfiguration
.
provider
)
.
setProvider
(
OssConfiguration
.
provider
.
toClientProvider
()
)
.
setObjectKeyPrefix
(
OssConfiguration
.
objectDirPrefix
)
.
setRegion
(
OssConfiguration
.
region
);
}
...
...
@@ -57,7 +57,7 @@ public class StorageServiceImpl extends AbstractMediaService implements IStorage
.
setEndpoint
(
OssConfiguration
.
endpoint
)
.
setBucket
(
OssConfiguration
.
bucket
)
.
setCredentials
(
ossService
.
getCredentials
())
.
setProvider
(
OssConfiguration
.
provider
)
.
setProvider
(
OssConfiguration
.
provider
.
toClientProvider
()
)
.
setObjectKeyPrefix
(
OssConfiguration
.
objectDirPrefix
+
"/"
+
workspaceId
)
.
setRegion
(
OssConfiguration
.
region
);
}
...
...
sample/src/main/resources/application-dev.yml
View file @
24356261
...
...
@@ -161,12 +161,15 @@ url:
# bucket: cloudapi-bucket
# object-dir-prefix: wayline
# VersityGW:provider=versitygw(OssTypeEnum.VERSITYGW)。下发给 Pilot/机场时会映射为 minio。
# sts-mode=temp:Admin API 创建短期用户(需开启 VersityGW 多租户 IAM);static:直接下发长期 AK/SK。
# 若仍用 MinIO,改为 provider: minio 即可。
oss
:
enable
:
true
provider
:
minio
provider
:
versitygw
# 香港
# endpoint: https://gt7-oss.geotwin.cc
# 深圳
# 深圳
/ VersityGW
endpoint
:
https://gt-oss-dev.geotwin.cn
access-key
:
minioadmin
secret-key
:
minioadmin
...
...
@@ -174,6 +177,8 @@ oss:
expire
:
86400
# 24 * 3600 24小时
region
:
us-east-1
# us-east-1
object-dir-prefix
:
wayline
sts-mode
:
temp
sts-user-role
:
admin
logging
:
level
:
...
...
sample/src/main/resources/application.yml
View file @
24356261
...
...
@@ -164,12 +164,15 @@ url:
# bucket: cloudapi-bucket
# object-dir-prefix: wayline
# VersityGW:provider=versitygw(OssTypeEnum.VERSITYGW)。下发给 Pilot/机场时会映射为 minio。
# sts-mode=temp:Admin API 创建短期用户(需开启 VersityGW 多租户 IAM);static:直接下发长期 AK/SK。
# 若仍用 MinIO,改为 provider: minio 即可。
oss
:
enable
:
true
provider
:
minio
provider
:
versitygw
# 香港
# endpoint: https://gt7-oss.geotwin.cc
# 深圳
# 深圳
/ VersityGW
endpoint
:
https://gt-oss-dev.geotwin.cn
access-key
:
minioadmin
secret-key
:
minioadmin
...
...
@@ -177,6 +180,8 @@ oss:
expire
:
86400
# 24 * 3600 24小时
region
:
us-east-1
# us-east-1
object-dir-prefix
:
wayline
sts-mode
:
temp
sts-user-role
:
admin
logging
:
level
:
...
...
sample/src/test/java/com/dji/sample/component/oss/VersityGwAdminClientTest.java
0 → 100644
View file @
24356261
package
com
.
dji
.
sample
.
component
.
oss
;
import
com.dji.sample.component.oss.model.OssConfiguration
;
import
com.dji.sample.component.oss.service.impl.VersityGwAdminClient
;
import
software.amazon.awssdk.auth.credentials.AwsBasicCredentials
;
import
software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
;
import
software.amazon.awssdk.core.sync.RequestBody
;
import
software.amazon.awssdk.regions.Region
;
import
software.amazon.awssdk.services.s3.S3Client
;
import
software.amazon.awssdk.services.s3.S3Configuration
;
import
software.amazon.awssdk.services.s3.model.S3Exception
;
import
java.net.URI
;
import
java.nio.charset.StandardCharsets
;
import
java.util.UUID
;
/**
* VersityGW Admin 用户创建/删除冒烟测试(IDE 直接跑 main)。
* <p>
* 前提:网关已开启多租户 IAM(如 --iam-dir),否则 create-user 会失败。
*/
public
class
VersityGwAdminClientTest
{
private
static
final
String
ENDPOINT
=
"https://gt-oss-dev.geotwin.cn"
;
private
static
final
String
ADMIN_ACCESS
=
"minioadmin"
;
private
static
final
String
ADMIN_SECRET
=
"minioadmin"
;
private
static
final
String
REGION
=
"us-east-1"
;
private
static
final
String
BUCKET
=
"gtfly"
;
public
static
void
main
(
String
[]
args
)
{
initOssConfig
();
VersityGwAdminClient
admin
=
new
VersityGwAdminClient
();
String
tempAccess
=
"tmp"
+
UUID
.
randomUUID
().
toString
().
replace
(
"-"
,
""
).
substring
(
0
,
17
);
String
tempSecret
=
"Secret"
+
UUID
.
randomUUID
().
toString
().
replace
(
"-"
,
""
);
try
{
System
.
out
.
println
(
"=== 1. list-users (before) ==="
);
System
.
out
.
println
(
admin
.
listUsers
());
System
.
out
.
println
(
"=== 2. create-user access="
+
tempAccess
+
" role=admin ==="
);
admin
.
createUser
(
tempAccess
,
tempSecret
,
"admin"
);
System
.
out
.
println
(
" created OK"
);
System
.
out
.
println
(
"=== 3. list-users (after create) ==="
);
String
listed
=
admin
.
listUsers
();
System
.
out
.
println
(
listed
);
if
(!
listed
.
contains
(
tempAccess
))
{
System
.
err
.
println
(
" WARN: list-users 响应中未找到 "
+
tempAccess
);
}
System
.
out
.
println
(
"=== 4. temp user S3 put/delete ==="
);
try
(
S3Client
s3
=
buildS3
(
tempAccess
,
tempSecret
))
{
String
key
=
"wayline/_vgw_admin_test_"
+
System
.
currentTimeMillis
()
+
".txt"
;
s3
.
putObject
(
b
->
b
.
bucket
(
BUCKET
).
key
(
key
).
contentType
(
"text/plain"
),
RequestBody
.
fromBytes
((
"admin-user-test "
+
tempAccess
).
getBytes
(
StandardCharsets
.
UTF_8
)));
System
.
out
.
println
(
" put OK: "
+
key
);
s3
.
deleteObject
(
b
->
b
.
bucket
(
BUCKET
).
key
(
key
));
System
.
out
.
println
(
" delete object OK"
);
}
System
.
out
.
println
(
"=== 5. delete-user ==="
);
admin
.
deleteUser
(
tempAccess
);
System
.
out
.
println
(
" deleted OK"
);
System
.
out
.
println
(
"=== 6. list-users (after delete) ==="
);
String
afterDelete
=
admin
.
listUsers
();
System
.
out
.
println
(
afterDelete
);
if
(
afterDelete
.
contains
(
tempAccess
))
{
System
.
err
.
println
(
" WARN: 删除后 list-users 仍包含 "
+
tempAccess
);
}
System
.
out
.
println
(
"=== 7. deleted user should NOT access S3 ==="
);
try
(
S3Client
s3
=
buildS3
(
tempAccess
,
tempSecret
))
{
s3
.
listBuckets
();
System
.
err
.
println
(
" FAIL: 已删除用户仍能 listBuckets"
);
}
catch
(
S3Exception
e
)
{
System
.
out
.
println
(
" expected fail: "
+
e
.
statusCode
()
+
" "
+
e
.
awsErrorDetails
().
errorMessage
());
}
System
.
out
.
println
(
"=== 8. delete-user idempotent (404 ok) ==="
);
admin
.
deleteUser
(
tempAccess
);
System
.
out
.
println
(
" delete again OK"
);
System
.
out
.
println
(
"VersityGW Admin user lifecycle OK"
);
}
catch
(
Exception
e
)
{
System
.
err
.
println
(
"VersityGW Admin test FAILED: "
+
e
.
getMessage
());
e
.
printStackTrace
();
try
{
admin
.
deleteUser
(
tempAccess
);
}
catch
(
Exception
ignore
)
{
// best-effort cleanup
}
}
}
private
static
void
initOssConfig
()
{
OssConfiguration
.
endpoint
=
ENDPOINT
;
OssConfiguration
.
accessKey
=
ADMIN_ACCESS
;
OssConfiguration
.
secretKey
=
ADMIN_SECRET
;
OssConfiguration
.
region
=
REGION
;
OssConfiguration
.
bucket
=
BUCKET
;
OssConfiguration
.
enable
=
true
;
}
private
static
S3Client
buildS3
(
String
access
,
String
secret
)
{
return
S3Client
.
builder
()
.
endpointOverride
(
URI
.
create
(
ENDPOINT
))
.
region
(
Region
.
of
(
REGION
))
.
credentialsProvider
(
StaticCredentialsProvider
.
create
(
AwsBasicCredentials
.
create
(
access
,
secret
)))
.
serviceConfiguration
(
S3Configuration
.
builder
()
.
pathStyleAccessEnabled
(
true
)
.
chunkedEncodingEnabled
(
false
)
.
build
())
.
build
();
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment