华为云obs;
This commit is contained in:
@@ -134,6 +134,11 @@
|
|||||||
<artifactId>minio</artifactId>
|
<artifactId>minio</artifactId>
|
||||||
<version>8.4.3</version>
|
<version>8.4.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.huaweicloud</groupId>
|
||||||
|
<artifactId>esdk-obs-java</artifactId>
|
||||||
|
<version>3.25.10</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.squareup.okhttp3</groupId>
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
<artifactId>okhttp</artifactId>
|
<artifactId>okhttp</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.linke.product.infrastructure.obs;
|
||||||
|
import com.mhd.common.core.web.domain.AjaxResult;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/file")
|
||||||
|
public class FileController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FileStorageService fileStorageService;
|
||||||
|
|
||||||
|
@PostMapping("/upload")
|
||||||
|
public AjaxResult<String> uploadFile(@RequestParam("file") MultipartFile file) {
|
||||||
|
String fileKey = fileStorageService.uploadFile(file);
|
||||||
|
String fileUrl = fileStorageService.getFileUrl(fileKey);
|
||||||
|
|
||||||
|
return AjaxResult.success(fileUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/url")
|
||||||
|
public AjaxResult<String> getFileUrl(@RequestParam String fileKey) {
|
||||||
|
String fileUrl = fileStorageService.getFileUrl(fileKey);
|
||||||
|
return AjaxResult.success(fileUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping
|
||||||
|
public AjaxResult<Boolean> deleteFile(@RequestParam String fileKey) {
|
||||||
|
boolean result = fileStorageService.deleteFile(fileKey);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package com.linke.product.infrastructure.obs;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public interface FileStorageService {
|
||||||
|
/**
|
||||||
|
* 文件上传
|
||||||
|
*/
|
||||||
|
String uploadFile(MultipartFile file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件上传(指定路径)
|
||||||
|
*/
|
||||||
|
String uploadFile(String filePath, MultipartFile file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件访问URL
|
||||||
|
*/
|
||||||
|
String getFileUrl(String fileKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载文件
|
||||||
|
*/
|
||||||
|
InputStream downloadFile(String fileKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文件
|
||||||
|
*/
|
||||||
|
boolean deleteFile(String fileKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断文件是否存在
|
||||||
|
*/
|
||||||
|
boolean fileExists(String fileKey);
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package com.linke.product.infrastructure.obs;
|
||||||
|
import com.mhd.common.core.utils.StringUtils;
|
||||||
|
import com.obs.services.ObsClient;
|
||||||
|
import com.obs.services.model.ObsObject;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class HuaweiObsService implements FileStorageService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObsClient obsClient;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObsProperties obsProperties;
|
||||||
|
|
||||||
|
private String bucketName;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
this.bucketName = obsProperties.getBucketName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String uploadFile(MultipartFile file) {
|
||||||
|
String originalFilename = file.getOriginalFilename();
|
||||||
|
String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
|
||||||
|
String fileKey = UUID.randomUUID().toString() + fileExtension;
|
||||||
|
|
||||||
|
return uploadFile(fileKey, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String uploadFile(String fileKey, MultipartFile file) {
|
||||||
|
try {
|
||||||
|
// 上传文件到OBS
|
||||||
|
obsClient.putObject(bucketName, fileKey, file.getInputStream());
|
||||||
|
|
||||||
|
log.info("文件上传成功: {}", fileKey);
|
||||||
|
return fileKey;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("文件上传失败: {}", fileKey, e);
|
||||||
|
throw new RuntimeException("文件上传失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFileUrl(String fileKey) {
|
||||||
|
if (StringUtils.isNotBlank(obsProperties.getCdnDomain())) {
|
||||||
|
// 使用CDN域名
|
||||||
|
return "https://" + obsProperties.getCdnDomain() + "/" + fileKey;
|
||||||
|
} else {
|
||||||
|
// 生成临时访问URL(默认1小时有效期)
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream downloadFile(String fileKey) {
|
||||||
|
try {
|
||||||
|
ObsObject obsObject = obsClient.getObject(bucketName, fileKey);
|
||||||
|
return obsObject.getObjectContent();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("文件下载失败: {}", fileKey, e);
|
||||||
|
throw new RuntimeException("文件下载失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean deleteFile(String fileKey) {
|
||||||
|
try {
|
||||||
|
obsClient.deleteObject(bucketName, fileKey);
|
||||||
|
log.info("文件删除成功: {}", fileKey);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("文件删除失败: {}", fileKey, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean fileExists(String fileKey) {
|
||||||
|
try {
|
||||||
|
return obsClient.doesObjectExist(bucketName, fileKey);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("检查文件存在失败: {}", fileKey, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分片上传(大文件)
|
||||||
|
*/
|
||||||
|
/*public String multipartUpload(String fileKey, MultipartFile file) {
|
||||||
|
try {
|
||||||
|
// 初始化分片上传
|
||||||
|
String uploadId = obsClient.initiateMultipartUpload(bucketName, fileKey).getUploadId();
|
||||||
|
|
||||||
|
// 计算分片数量(每片5MB)
|
||||||
|
long partSize = 5 * 1024 * 1024L;
|
||||||
|
long fileSize = file.getSize();
|
||||||
|
int partCount = (int) (fileSize / partSize);
|
||||||
|
if (fileSize % partSize != 0) {
|
||||||
|
partCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传分片
|
||||||
|
for (int i = 0; i < partCount; i++) {
|
||||||
|
long startPos = i * partSize;
|
||||||
|
long curPartSize = (i + 1 == partCount) ? (fileSize - startPos) : partSize;
|
||||||
|
|
||||||
|
InputStream inputStream = file.getInputStream();
|
||||||
|
inputStream.skip(startPos);
|
||||||
|
|
||||||
|
obsClient.uploadPart(bucketName, fileKey, uploadId, i + 1, inputStream, curPartSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成分片上传
|
||||||
|
obsClient.completeMultipartUpload(bucketName, fileKey, uploadId);
|
||||||
|
|
||||||
|
log.info("分片上传成功: {}", fileKey);
|
||||||
|
return fileKey;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("分片上传失败: {}", fileKey, e);
|
||||||
|
throw new RuntimeException("文件上传失败", e);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
+128
@@ -0,0 +1,128 @@
|
|||||||
|
package com.linke.product.infrastructure.obs;
|
||||||
|
import com.obs.services.ObsClient;
|
||||||
|
import com.obs.services.exception.ObsException;
|
||||||
|
import com.obs.services.model.BucketCors;
|
||||||
|
import com.obs.services.model.BucketCorsRule;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class ObsBucketInitializer {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObsClient obsClient;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObsProperties obsProperties;
|
||||||
|
|
||||||
|
private static final String BUCKET_PREFIX = "diglog";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用启动时自动创建并配置桶
|
||||||
|
*/
|
||||||
|
//@PostConstruct
|
||||||
|
public void initBucket() {
|
||||||
|
// 根据环境生成桶名称,如:diglog-dev, diglog-test, diglog-prod
|
||||||
|
//String env = getEnvironment();
|
||||||
|
String bucketName = BUCKET_PREFIX;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查桶是否存在
|
||||||
|
boolean exists = obsClient.headBucket(bucketName);
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
// 创建桶
|
||||||
|
obsClient.createBucket(bucketName);
|
||||||
|
log.info("创建OBS桶成功: {}", bucketName);
|
||||||
|
|
||||||
|
// 配置桶的CORS(跨域资源共享)
|
||||||
|
configureBucketCors(bucketName);
|
||||||
|
|
||||||
|
// 配置生命周期规则(可选)
|
||||||
|
//configureLifecycle(bucketName);
|
||||||
|
|
||||||
|
// 配置访问日志(可选)
|
||||||
|
//configureAccessLog(bucketName);
|
||||||
|
} else {
|
||||||
|
log.info("OBS桶已存在: {}", bucketName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新配置中的桶名称
|
||||||
|
obsProperties.setBucketName(bucketName);
|
||||||
|
|
||||||
|
} catch (ObsException e) {
|
||||||
|
log.error("OBS桶操作失败: {}", e.getErrorMessage(), e);
|
||||||
|
throw new RuntimeException("OBS桶初始化失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置CORS规则
|
||||||
|
*/
|
||||||
|
private void configureBucketCors(String bucketName) {
|
||||||
|
BucketCors cors = new BucketCors();
|
||||||
|
List<BucketCorsRule> rules = new ArrayList<>();
|
||||||
|
|
||||||
|
BucketCorsRule rule = new BucketCorsRule();
|
||||||
|
rule.getAllowedHeader().add("*");
|
||||||
|
rule.getAllowedMethod().add("GET");
|
||||||
|
rule.getAllowedMethod().add("PUT");
|
||||||
|
rule.getAllowedMethod().add("POST");
|
||||||
|
rule.getAllowedMethod().add("DELETE");
|
||||||
|
rule.getAllowedMethod().add("HEAD");
|
||||||
|
rule.getAllowedOrigin().add("*");
|
||||||
|
rule.getExposeHeader().add("*");
|
||||||
|
rule.setMaxAgeSecond(3600);
|
||||||
|
|
||||||
|
rules.add(rule);
|
||||||
|
cors.setRules(rules);
|
||||||
|
|
||||||
|
obsClient.setBucketCors(bucketName, cors);
|
||||||
|
log.info("配置桶CORS规则: {}", bucketName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置生命周期规则
|
||||||
|
*/
|
||||||
|
/*private void configureLifecycle(String bucketName) {
|
||||||
|
// 示例:30天后转为低频访问,365天后删除
|
||||||
|
String lifecycleConfig =
|
||||||
|
"<LifecycleConfiguration>" +
|
||||||
|
" <Rule>" +
|
||||||
|
" <ID>transition-and-expiration-rule</ID>" +
|
||||||
|
" <Prefix></Prefix>" +
|
||||||
|
" <Status>Enabled</Status>" +
|
||||||
|
" <Transition>" +
|
||||||
|
" <Days>30</Days>" +
|
||||||
|
" <StorageClass>WARM</StorageClass>" +
|
||||||
|
" </Transition>" +
|
||||||
|
" <Expiration>" +
|
||||||
|
" <Days>365</Days>" +
|
||||||
|
" </Expiration>" +
|
||||||
|
" </Rule>" +
|
||||||
|
"</LifecycleConfiguration>";
|
||||||
|
|
||||||
|
obsClient.setBucketLifecycle(bucketName, lifecycleConfig);
|
||||||
|
log.info("配置桶生命周期规则: {}", bucketName);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前环境
|
||||||
|
*/
|
||||||
|
/*private String getEnvironment() {
|
||||||
|
// 从配置文件或系统变量获取环境
|
||||||
|
String env = System.getProperty("spring.profiles.active",
|
||||||
|
System.getenv("SPRING_PROFILES_ACTIVE"));
|
||||||
|
|
||||||
|
if (env == null || env.isEmpty()) {
|
||||||
|
env = "dev"; // 默认开发环境
|
||||||
|
}
|
||||||
|
|
||||||
|
return env.toLowerCase();
|
||||||
|
}*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.linke.product.infrastructure.obs;
|
||||||
|
import com.mhd.common.core.utils.StringUtils;
|
||||||
|
import com.obs.services.ObsClient;
|
||||||
|
import com.obs.services.ObsConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ObsConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ObsClient obsClient(ObsProperties obsProperties) {
|
||||||
|
// 创建配置实例
|
||||||
|
ObsConfiguration config = new ObsConfiguration();
|
||||||
|
config.setSocketTimeout(obsProperties.getSocketTimeout());
|
||||||
|
config.setConnectionTimeout(obsProperties.getConnectionTimeout());
|
||||||
|
config.setEndPoint(obsProperties.getEndpoint());
|
||||||
|
|
||||||
|
// 创建ObsClient实例
|
||||||
|
if (StringUtils.isNotBlank(obsProperties.getSecurityToken())) {
|
||||||
|
// 使用临时安全令牌
|
||||||
|
return new ObsClient(
|
||||||
|
obsProperties.getAccessKey(),
|
||||||
|
obsProperties.getSecretKey(),
|
||||||
|
obsProperties.getSecurityToken(),
|
||||||
|
config
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 使用永久AK/SK
|
||||||
|
return new ObsClient(
|
||||||
|
obsProperties.getAccessKey(),
|
||||||
|
obsProperties.getSecretKey(),
|
||||||
|
config
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.linke.product.infrastructure.obs;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "huawei.obs")
|
||||||
|
@Data
|
||||||
|
public class ObsProperties {
|
||||||
|
private String accessKey;
|
||||||
|
private String secretKey;
|
||||||
|
private String endpoint;
|
||||||
|
private String bucketName;
|
||||||
|
private String securityToken;
|
||||||
|
private String region;
|
||||||
|
private String cdnDomain;
|
||||||
|
private int maxConnections = 100;
|
||||||
|
private int socketTimeout = 30000;
|
||||||
|
private int connectionTimeout = 10000;
|
||||||
|
private int idleConnectionTime = 30000;
|
||||||
|
|
||||||
|
// getters and setters
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user