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