From 98af145acbc7be22ff9d684d73a3b710a5737db2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8E=8B=E5=A5=8E=E5=85=B4?= <2220574228@qq.com>
Date: Tue, 23 Dec 2025 16:18:19 +0800
Subject: [PATCH] =?UTF-8?q?=E5=8D=8E=E4=B8=BA=E4=BA=91obs;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
mhd_product/pom.xml | 5 +
.../infrastructure/obs/FileController.java | 33 +++++
.../obs/FileStorageService.java | 35 +++++
.../infrastructure/obs/HuaweiObsService.java | 134 ++++++++++++++++++
.../obs/ObsBucketInitializer.java | 128 +++++++++++++++++
.../product/infrastructure/obs/ObsConfig.java | 37 +++++
.../infrastructure/obs/ObsProperties.java | 23 +++
7 files changed, 395 insertions(+)
create mode 100644 mhd_product/src/main/java/com/linke/product/infrastructure/obs/FileController.java
create mode 100644 mhd_product/src/main/java/com/linke/product/infrastructure/obs/FileStorageService.java
create mode 100644 mhd_product/src/main/java/com/linke/product/infrastructure/obs/HuaweiObsService.java
create mode 100644 mhd_product/src/main/java/com/linke/product/infrastructure/obs/ObsBucketInitializer.java
create mode 100644 mhd_product/src/main/java/com/linke/product/infrastructure/obs/ObsConfig.java
create mode 100644 mhd_product/src/main/java/com/linke/product/infrastructure/obs/ObsProperties.java
diff --git a/mhd_product/pom.xml b/mhd_product/pom.xml
index 317ea4ab5..ca2e80a4b 100644
--- a/mhd_product/pom.xml
+++ b/mhd_product/pom.xml
@@ -134,6 +134,11 @@
minio
8.4.3
+
+ com.huaweicloud
+ esdk-obs-java
+ 3.25.10
+
com.squareup.okhttp3
okhttp
diff --git a/mhd_product/src/main/java/com/linke/product/infrastructure/obs/FileController.java b/mhd_product/src/main/java/com/linke/product/infrastructure/obs/FileController.java
new file mode 100644
index 000000000..6612d2a9d
--- /dev/null
+++ b/mhd_product/src/main/java/com/linke/product/infrastructure/obs/FileController.java
@@ -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 uploadFile(@RequestParam("file") MultipartFile file) {
+ String fileKey = fileStorageService.uploadFile(file);
+ String fileUrl = fileStorageService.getFileUrl(fileKey);
+
+ return AjaxResult.success(fileUrl);
+ }
+
+ @GetMapping("/url")
+ public AjaxResult getFileUrl(@RequestParam String fileKey) {
+ String fileUrl = fileStorageService.getFileUrl(fileKey);
+ return AjaxResult.success(fileUrl);
+ }
+
+ @DeleteMapping
+ public AjaxResult deleteFile(@RequestParam String fileKey) {
+ boolean result = fileStorageService.deleteFile(fileKey);
+ return AjaxResult.success(result);
+ }
+}
\ No newline at end of file
diff --git a/mhd_product/src/main/java/com/linke/product/infrastructure/obs/FileStorageService.java b/mhd_product/src/main/java/com/linke/product/infrastructure/obs/FileStorageService.java
new file mode 100644
index 000000000..0b4ab8fea
--- /dev/null
+++ b/mhd_product/src/main/java/com/linke/product/infrastructure/obs/FileStorageService.java
@@ -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);
+}
\ No newline at end of file
diff --git a/mhd_product/src/main/java/com/linke/product/infrastructure/obs/HuaweiObsService.java b/mhd_product/src/main/java/com/linke/product/infrastructure/obs/HuaweiObsService.java
new file mode 100644
index 000000000..1d962671d
--- /dev/null
+++ b/mhd_product/src/main/java/com/linke/product/infrastructure/obs/HuaweiObsService.java
@@ -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);
+ }
+ }*/
+}
\ No newline at end of file
diff --git a/mhd_product/src/main/java/com/linke/product/infrastructure/obs/ObsBucketInitializer.java b/mhd_product/src/main/java/com/linke/product/infrastructure/obs/ObsBucketInitializer.java
new file mode 100644
index 000000000..c575ac57e
--- /dev/null
+++ b/mhd_product/src/main/java/com/linke/product/infrastructure/obs/ObsBucketInitializer.java
@@ -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 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 =
+ "" +
+ " " +
+ " transition-and-expiration-rule" +
+ " " +
+ " Enabled" +
+ " " +
+ " 30" +
+ " WARM" +
+ " " +
+ " " +
+ " 365" +
+ " " +
+ " " +
+ "";
+
+ 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();
+ }*/
+}
\ No newline at end of file
diff --git a/mhd_product/src/main/java/com/linke/product/infrastructure/obs/ObsConfig.java b/mhd_product/src/main/java/com/linke/product/infrastructure/obs/ObsConfig.java
new file mode 100644
index 000000000..028fbd8dc
--- /dev/null
+++ b/mhd_product/src/main/java/com/linke/product/infrastructure/obs/ObsConfig.java
@@ -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
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/mhd_product/src/main/java/com/linke/product/infrastructure/obs/ObsProperties.java b/mhd_product/src/main/java/com/linke/product/infrastructure/obs/ObsProperties.java
new file mode 100644
index 000000000..f22636404
--- /dev/null
+++ b/mhd_product/src/main/java/com/linke/product/infrastructure/obs/ObsProperties.java
@@ -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
+}
\ No newline at end of file