-
Notifications
You must be signed in to change notification settings - Fork 0
[feature] add alibaba cloud oss support #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
ec01411
7955d97
78cb876
7fbb1bf
d494c75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
|
||
package org.apache.hertzbeat.manager.service.impl; | ||
|
||
import com.aliyun.oss.OSSClientBuilder; | ||
import com.fasterxml.jackson.core.type.TypeReference; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.obs.services.ObsClient; | ||
|
@@ -83,10 +84,28 @@ public void handler(ObjectStoreDTO<T> config) { | |
initObs(config); | ||
// case other object store service | ||
} | ||
if (config.getType() == ObjectStoreDTO.Type.OSS) { | ||
initOss(config); | ||
} | ||
ctx.publishEvent(new ObjectStoreConfigChangeEvent(config)); | ||
} | ||
} | ||
|
||
private void initOss(ObjectStoreDTO<T> config) { | ||
var ossConfig = objectMapper.convertValue(config.getConfig(), ObjectStoreDTO.OssConfig.class); | ||
Assert.hasText(ossConfig.getAccessKey(), "cannot find oss accessKey"); | ||
Assert.hasText(ossConfig.getSecretKey(), "cannot find oss secretKey"); | ||
Assert.hasText(ossConfig.getEndpoint(), "cannot find oss endpoint"); | ||
Assert.hasText(ossConfig.getBucketName(), "cannot find oss bucket name"); | ||
|
||
var ossClient = new OSSClientBuilder().build(ossConfig.getEndpoint(), ossConfig.getAccessKey(), ossConfig.getSecretKey()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing validation: Unlike the |
||
|
||
beanFactory.destroySingleton(BEAN_NAME); | ||
beanFactory.registerSingleton(BEAN_NAME, new OssObjectStoreServiceImpl(ossClient, ossConfig.getBucketName(), ossConfig.getSavePath())); | ||
|
||
log.info("oss store service init success."); | ||
} | ||
|
||
/** | ||
* init Huawei Cloud OBS | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,90 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Licensed to the Apache Software Foundation (ASF) under one or more | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* contributor license agreements. See the NOTICE file distributed with | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* this work for additional information regarding copyright ownership. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* The ASF licenses this file to You under the Apache License, Version 2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* (the "License"); you may not use this file except in compliance with | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* the License. You may obtain a copy of the License at | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Unless required by applicable law or agreed to in writing, software | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* distributed under the License is distributed on an "AS IS" BASIS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* See the License for the specific language governing permissions and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* limitations under the License. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
package org.apache.hertzbeat.manager.service.impl; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import com.aliyun.oss.OSS; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import com.aliyun.oss.model.ListObjectsRequest; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import com.aliyun.oss.model.OSSObjectSummary; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import java.io.InputStream; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import java.util.Objects; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import org.apache.hertzbeat.common.constants.SignConstants; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import org.apache.hertzbeat.manager.pojo.dto.FileDTO; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import org.apache.hertzbeat.manager.pojo.dto.ObjectStoreDTO; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import org.apache.hertzbeat.manager.service.ObjectStoreService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Alibaba cloud storage service | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Slf4j | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public class OssObjectStoreServiceImpl implements ObjectStoreService { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private final OSS ossClient; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private final String bucketName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private final String rootPath; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public OssObjectStoreServiceImpl(OSS ossClient, String bucketName, String rootPath) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.ossClient = ossClient; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.bucketName = bucketName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (rootPath.startsWith(SignConstants.RIGHT_DASH)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.rootPath = rootPath.substring(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.rootPath = rootPath; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Consider adding a |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public boolean upload(String filePath, InputStream is) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var objectKey = getObjectKey(filePath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var response = ossClient.putObject(bucketName, objectKey, is); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return Objects.equals(response.getResponse().getStatusCode(), 200); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+53
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling to prevent uncaught exceptions. The upload method doesn't have any exception handling, which could cause unexpected crashes if the OSS client throws an exception during upload. @Override
public boolean upload(String filePath, InputStream is) {
- var objectKey = getObjectKey(filePath);
- var response = ossClient.putObject(bucketName, objectKey, is);
- return Objects.equals(response.getResponse().getStatusCode(), 200);
+ try {
+ var objectKey = getObjectKey(filePath);
+ var response = ossClient.putObject(bucketName, objectKey, is);
+ return response.getResponse() != null &&
+ Objects.equals(response.getResponse().getStatusCode(), 200);
+ } catch (Exception ex) {
+ log.error("Failed to upload file to OSS: {}", filePath, ex);
+ return false;
+ }
} 📝 Committable suggestion
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue: The response check may not be reliable. The OSS client's |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public FileDTO download(String filePath) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var objectKey = getObjectKey(filePath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var ossObject = ossClient.getObject(bucketName, objectKey); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return new FileDTO(filePath, ossObject.getObjectContent()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (Exception ex) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
log.warn("download file from oss error {}", objectKey); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public List<FileDTO> list(String dir) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var request = new ListObjectsRequest(bucketName); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
request.setPrefix(getObjectKey(dir)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ossClient.listObjects(request).getObjectSummaries().stream() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.map(OSSObjectSummary::getKey).toList().stream() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Performance concern: This implementation loads all objects in memory at once. For directories with many files, this could lead to memory issues. Consider implementing pagination or streaming the results. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.map(k -> new FileDTO(k, ossClient.getObject(bucketName, k).getObjectContent())).toList(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential resource leak: The OSS objects retrieved in the list method aren't being closed. Each |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+72
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve error handling and performance in list method. The list method has two issues:
@Override
public List<FileDTO> list(String dir) {
+ try {
var request = new ListObjectsRequest(bucketName);
request.setPrefix(getObjectKey(dir));
- return ossClient.listObjects(request).getObjectSummaries().stream()
- .map(OSSObjectSummary::getKey).toList().stream()
- .map(k -> new FileDTO(k, ossClient.getObject(bucketName, k).getObjectContent())).toList();
+ var summaries = ossClient.listObjects(request).getObjectSummaries();
+ return summaries.stream()
+ .map(summary -> {
+ try {
+ String key = summary.getKey();
+ var object = ossClient.getObject(bucketName, key);
+ return new FileDTO(key, object.getObjectContent());
+ } catch (Exception e) {
+ log.warn("Failed to get object content for key: {}", summary.getKey(), e);
+ return null;
+ }
+ })
+ .filter(Objects::nonNull)
+ .toList();
+ } catch (Exception ex) {
+ log.error("Failed to list files from OSS directory: {}", dir, ex);
+ return List.of();
+ }
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public ObjectStoreDTO.Type type() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ObjectStoreDTO.Type.OSS; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private String getObjectKey(String filePath) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return rootPath + SignConstants.RIGHT_DASH + filePath; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,7 +32,11 @@ export enum ObjectStoreType { | |
/** | ||
* <a href="https://support.huaweicloud.com/obs/index.html">华为云OBS</a> | ||
*/ | ||
OBS = 'OBS' | ||
OBS = 'OBS', | ||
/** | ||
* <a href="https://oss.console.aliyun.com/services/tools">Alibaba Cloud OSS</a> | ||
*/ | ||
OSS = 'OSS' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Consider adding a trailing comma after the |
||
} | ||
|
||
export class ObsConfig { | ||
|
@@ -42,3 +46,11 @@ export class ObsConfig { | |
endpoint!: string; | ||
savePath: string = 'hertzbeat'; | ||
} | ||
|
||
export class OssConfig { | ||
accessKey!: string; | ||
secretKey!: string; | ||
bucketName!: string; | ||
endpoint!: string; | ||
savePath: string = 'hertzbeat'; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,16 +32,21 @@ | |
> | ||
<nz-option [nzValue]="ObjectStoreType.FILE" [nzLabel]="'settings.object-store.type.file' | i18n"></nz-option> | ||
<nz-option [nzValue]="ObjectStoreType.OBS" [nzLabel]="'settings.object-store.type.obs' | i18n"></nz-option> | ||
<nz-option [nzValue]="ObjectStoreType.OSS" [nzLabel]="'settings.object-store.type.oss' | i18n"></nz-option> | ||
</nz-select> | ||
</nz-form-item> | ||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS"> | ||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS || config.type == ObjectStoreType.OSS"> | ||
<nz-form-label [nzSpan]="6" nzFor="obs.accessKey" nzRequired="true">{{ | ||
'settings.object-store.obs.accessKey' | i18n | ||
}}</nz-form-label> | ||
<nz-form-control [nzErrorTip]="'validation.required' | i18n"> | ||
<input | ||
[(ngModel)]="config.config.accessKey" | ||
placeholder="{{ 'settings.object-store.obs.accessKey.placeholder' | i18n }}" | ||
placeholder="{{ | ||
config.type == ObjectStoreType.OBS | ||
? ('settings.object-store.obs.accessKey.placeholder' | i18n) | ||
: ('settings.object-store.oss.accessKey.placeholder' | i18n) | ||
}}" | ||
nz-input | ||
required | ||
name="accessKey" | ||
|
@@ -50,14 +55,18 @@ | |
/> | ||
</nz-form-control> | ||
</nz-form-item> | ||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS"> | ||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS || config.type == ObjectStoreType.OSS"> | ||
<nz-form-label [nzSpan]="6" nzFor="obs.secretKey" nzRequired="true">{{ | ||
'settings.object-store.obs.secretKey' | i18n | ||
}}</nz-form-label> | ||
<nz-form-control [nzErrorTip]="'validation.required' | i18n"> | ||
<input | ||
[(ngModel)]="config.config.secretKey" | ||
placeholder="{{ 'settings.object-store.obs.secretKey.placeholder' | i18n }}" | ||
placeholder="{{ | ||
config.type == ObjectStoreType.OBS | ||
? ('settings.object-store.obs.secretKey.placeholder' | i18n) | ||
: ('settings.object-store.oss.secretKey.placeholder' | i18n) | ||
}}" | ||
nz-input | ||
required | ||
name="secretKey" | ||
|
@@ -66,14 +75,18 @@ | |
/> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DRY principle: There's significant duplication in the HTML template for handling OBS and OSS configurations. Consider refactoring to use a common template or component for both cloud storage types to improve maintainability. |
||
</nz-form-control> | ||
</nz-form-item> | ||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS"> | ||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS || config.type == ObjectStoreType.OSS"> | ||
<nz-form-label [nzSpan]="6" nzFor="obs.bucketName" nzRequired="true">{{ | ||
'settings.object-store.obs.bucketName' | i18n | ||
}}</nz-form-label> | ||
<nz-form-control [nzErrorTip]="'validation.required' | i18n"> | ||
<input | ||
[(ngModel)]="config.config.bucketName" | ||
placeholder="{{ 'settings.object-store.obs.bucketName.placeholder' | i18n }}" | ||
placeholder="{{ | ||
config.type == ObjectStoreType.OBS | ||
? ('settings.object-store.obs.bucketName.placeholder' | i18n) | ||
: ('settings.object-store.oss.bucketName.placeholder' | i18n) | ||
}}" | ||
nz-input | ||
required | ||
name="bucketName" | ||
|
@@ -82,14 +95,18 @@ | |
/> | ||
</nz-form-control> | ||
</nz-form-item> | ||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS"> | ||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS || config.type == ObjectStoreType.OSS"> | ||
<nz-form-label [nzSpan]="6" nzFor="obs.endpoint" nzRequired="true">{{ | ||
'settings.object-store.obs.endpoint' | i18n | ||
}}</nz-form-label> | ||
<nz-form-control [nzErrorTip]="'validation.required' | i18n"> | ||
<input | ||
[(ngModel)]="config.config.endpoint" | ||
placeholder="{{ 'settings.object-store.obs.endpoint.placeholder' | i18n }}" | ||
placeholder="{{ | ||
config.type == ObjectStoreType.OBS | ||
? ('settings.object-store.obs.endpoint.placeholder' | i18n) | ||
: ('settings.object-store.oss.endpoint.placeholder' | i18n) | ||
}}" | ||
nz-input | ||
required | ||
name="endpoint" | ||
|
@@ -98,14 +115,18 @@ | |
/> | ||
</nz-form-control> | ||
</nz-form-item> | ||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS"> | ||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS || config.type == ObjectStoreType.OSS"> | ||
<nz-form-label [nzSpan]="6" nzFor="obs.savePath" nzRequired="true">{{ | ||
'settings.object-store.obs.savePath' | i18n | ||
}}</nz-form-label> | ||
<nz-form-control [nzErrorTip]="'validation.required' | i18n"> | ||
<input | ||
[(ngModel)]="config.config.savePath" | ||
placeholder="{{ 'settings.object-store.obs.savePath.placeholder' | i18n }}" | ||
placeholder="{{ | ||
config.type == ObjectStoreType.OBS | ||
? ('settings.object-store.obs.savePath.placeholder' | i18n) | ||
: ('settings.object-store.oss.savePath.placeholder' | i18n) | ||
}}" | ||
nz-input | ||
required | ||
name="savePath" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code structure: Consider using a
switch
statement instead of multipleif
conditions for handling different object store types, which would be more maintainable as more store types are added.