From 3c1edfcd2e223b9d5721dd38f2a640a00c3cc4d9 Mon Sep 17 00:00:00 2001 From: gaoyutao Date: Wed, 11 Feb 2026 18:02:55 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E4=BC=98=E5=8C=96=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E7=AE=A1=E7=90=86=E5=88=97=E8=A1=A8ip=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E5=B1=95=E7=A4=BA=E3=80=82=202=E3=80=81=E9=9B=86?= =?UTF-8?q?=E6=88=90rustfs=20SDK=E3=80=82=203=E3=80=81=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E4=BA=A4=E6=8D=A2=E6=9C=BA=E6=95=B0=E6=8D=AEIoTDB=E5=85=A5?= =?UTF-8?q?=E5=BA=93=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storage/controller/BucketController.java | 48 +++ .../storage/controller/FileController.java | 68 ++-- .../storage/controller/FolderController.java | 54 +++ .../com/tongran/storage/dto/FileInfo.java | 2 + .../storage/service/BucketService.java | 46 +++ .../storage/service/FolderService.java | 54 +++ .../storage/service/StorageService.java | 17 +- .../service/impl/BucketServiceImpl.java | 215 ++++++++++++ .../service/impl/FolderServiceImpl.java | 316 +++++++++++++++++ .../service/impl/S3StorageServiceImpl.java | 329 ++++++++++++------ .../RmResourceRegistrationServiceImpl.java | 106 +++--- .../system/EpsInitialTrafficDataMapper.xml | 2 +- .../ProcessSwitchCollectDataService.java | 22 +- .../tongran/rocketmq/utils/IotDbUtils.java | 41 ++- 14 files changed, 1104 insertions(+), 216 deletions(-) create mode 100644 tongran-modules/tongran-storage/src/main/java/com/tongran/storage/controller/BucketController.java create mode 100644 tongran-modules/tongran-storage/src/main/java/com/tongran/storage/controller/FolderController.java create mode 100644 tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/BucketService.java create mode 100644 tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/FolderService.java create mode 100644 tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/impl/BucketServiceImpl.java create mode 100644 tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/impl/FolderServiceImpl.java diff --git a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/controller/BucketController.java b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/controller/BucketController.java new file mode 100644 index 0000000..ab048aa --- /dev/null +++ b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/controller/BucketController.java @@ -0,0 +1,48 @@ +package com.tongran.storage.controller; + +import com.tongran.common.core.web.controller.BaseController; +import com.tongran.common.core.web.domain.AjaxResult; +import com.tongran.storage.service.BucketService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/buckets") +@RequiredArgsConstructor +public class BucketController extends BaseController { + + private final BucketService bucketService; + + @GetMapping + public AjaxResult listBuckets() { + List buckets = bucketService.listBuckets(); + return success(buckets); + } + + @GetMapping("/details") + public AjaxResult listBucketsWithDetails() { + List> bucketsWithDetails = bucketService.listBucketsWithDetails(); + return success(bucketsWithDetails); + } + + @PostMapping("/{bucketName}") + public AjaxResult createBucket(@PathVariable String bucketName) { + boolean result = bucketService.createBucket(bucketName); + return result ? success() : error("创建存储桶失败"); + } + + @DeleteMapping("/{bucketName}") + public AjaxResult deleteBucket(@PathVariable String bucketName) { + boolean result = bucketService.deleteBucket(bucketName); + return result ? success() : error("删除存储桶失败"); + } + + @GetMapping("/{bucketName}/exists") + public AjaxResult bucketExists(@PathVariable String bucketName) { + boolean exists = bucketService.bucketExists(bucketName); + return success(exists); + } +} \ No newline at end of file diff --git a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/controller/FileController.java b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/controller/FileController.java index f0af4c4..a4237ef 100644 --- a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/controller/FileController.java +++ b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/controller/FileController.java @@ -1,5 +1,7 @@ package com.tongran.storage.controller; +import com.tongran.common.core.web.controller.BaseController; +import com.tongran.common.core.web.domain.AjaxResult; import com.tongran.storage.dto.FileInfo; import com.tongran.storage.dto.UploadResult; import com.tongran.storage.service.StorageService; @@ -15,23 +17,22 @@ import java.util.List; @RestController @RequestMapping("/storage") @RequiredArgsConstructor -public class FileController { +public class FileController extends BaseController { private final StorageService storageService; @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity uploadFile(@RequestParam("file") MultipartFile file) { - UploadResult result = storageService.uploadFile(file); - if (result.isSuccess()) { - return ResponseEntity.ok(result); - } else { - return ResponseEntity.badRequest().body(result); - } + public AjaxResult uploadFile(@RequestParam("file") MultipartFile file, + @RequestParam("bucketName") String bucketName, + @RequestParam("folderPath") String folderPath) { + UploadResult result = storageService.uploadFile(bucketName, folderPath, file); + return success(result); } - - @GetMapping("/download/{fileName}") - public ResponseEntity downloadFile(@PathVariable String fileName) { - byte[] fileBytes = storageService.downloadFile(fileName); + @PostMapping("/download") + public ResponseEntity downloadFile(@RequestParam("bucketName") String bucketName, + @RequestParam(value = "folderPath", required = false) String folderPath, + @RequestParam("fileName") String fileName) { + byte[] fileBytes = storageService.downloadFile(bucketName, folderPath, fileName); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, @@ -39,29 +40,30 @@ public class FileController { .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(fileBytes); } - - @DeleteMapping("/{fileName}") - public ResponseEntity deleteFile(@PathVariable String fileName) { - storageService.deleteFile(fileName); - return ResponseEntity.ok("文件删除成功: " + fileName); + @DeleteMapping + public AjaxResult deleteFile(@RequestParam("bucketName") String bucketName, + @RequestParam(value = "folderPath", required = false) String folderPath, + @RequestParam("fileName") String fileName) { + storageService.deleteFile(bucketName, folderPath, fileName); + return success("文件删除成功: " + fileName); } - - @GetMapping("/list") - public ResponseEntity> listFiles() { - List files = storageService.listFiles(); - return ResponseEntity.ok(files); + @PostMapping("/list") + public AjaxResult listFiles(@RequestParam("bucketName") String bucketName, + @RequestParam(value = "folderPath", required = false) String folderPath) { + List files = storageService.listFiles(bucketName, folderPath); + return success(files); } - @GetMapping("/createBucket") - public ResponseEntity createBucket(String bucketName) { - boolean success = storageService.createBucket(bucketName); - if(success){ - return ResponseEntity.ok("创建成功"); - } - return ResponseEntity.ok("存储桶已存在"); + @PostMapping("/generateTempUrl") + public AjaxResult generateTempUrl(@RequestParam("bucketName") String bucketName, + @RequestParam(value = "folderPath", required = false) String folderPath, + @RequestParam("fileName") String fileName) { + String url = storageService.generateTempUrl(bucketName, folderPath, fileName); + return success(url); } - @GetMapping("/generateTempUrl") - public ResponseEntity generateTempUrl(String fileName) { - String url = storageService.generateTempUrl(fileName); - return ResponseEntity.ok(url); + @PostMapping("/batchDelete") + public AjaxResult batchDelete(@RequestParam("bucketName") String bucketName, + @RequestBody List paths) { + boolean success = storageService.batchDelete(bucketName, paths); + return toAjax(success); } } \ No newline at end of file diff --git a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/controller/FolderController.java b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/controller/FolderController.java new file mode 100644 index 0000000..3c55019 --- /dev/null +++ b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/controller/FolderController.java @@ -0,0 +1,54 @@ +package com.tongran.storage.controller; + +import com.tongran.common.core.web.controller.BaseController; +import com.tongran.common.core.web.domain.AjaxResult; +import com.tongran.storage.dto.FileInfo; +import com.tongran.storage.service.FolderService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/folders") +@RequiredArgsConstructor +public class FolderController extends BaseController { + + private final FolderService folderService; + + @PostMapping("/{bucketName}") + public AjaxResult createFolder(@PathVariable String bucketName, + @RequestParam String folderPath) { + boolean result = folderService.createFolder(bucketName, folderPath); + return result ? success() : error("创建文件夹失败"); + } + + @DeleteMapping("/{bucketName}") + public AjaxResult deleteFolder(@PathVariable String bucketName, + @RequestParam String folderPath) { + boolean result = folderService.deleteFolder(bucketName, folderPath); + return result ? success() : error("删除文件夹失败"); + } + + @GetMapping("/{bucketName}") + public AjaxResult listFolder(@PathVariable String bucketName, + @RequestParam(required = false, defaultValue = "") String folderPath) { + List list = folderService.listFolder(bucketName, folderPath); + return success(list); + } + + @GetMapping("/{bucketName}/exists") + public AjaxResult folderExists(@PathVariable String bucketName, + @RequestParam String folderPath) { + boolean exists = folderService.folderExists(bucketName, folderPath); + return success(exists); + } + + @GetMapping("/{bucketName}/tree") + public AjaxResult getFolderTree(@PathVariable String bucketName, + @RequestParam(required = false, defaultValue = "") String folderPath) { + List> tree = folderService.getFolderTree(bucketName, folderPath); + return success(tree); + } +} \ No newline at end of file diff --git a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/dto/FileInfo.java b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/dto/FileInfo.java index 55f3394..7f017e8 100644 --- a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/dto/FileInfo.java +++ b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/dto/FileInfo.java @@ -5,8 +5,10 @@ import java.time.Instant; @Data public class FileInfo { + private String bucketName; private String fileName; private Long fileSize; private Instant lastModified; private String etag; + private Boolean directory = false; // 新增字段:是否为目录 } \ No newline at end of file diff --git a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/BucketService.java b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/BucketService.java new file mode 100644 index 0000000..7856a05 --- /dev/null +++ b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/BucketService.java @@ -0,0 +1,46 @@ +package com.tongran.storage.service; + +import java.util.List; +import java.util.Map; + +public interface BucketService { + /** + * 创建存储桶 + */ + boolean createBucket(String bucketName); + + /** + * 删除存储桶 + */ + boolean deleteBucket(String bucketName); + + /** + * 检查存储桶是否存在 + */ + boolean bucketExists(String bucketName); + + /** + * 获取所有存储桶列表 + */ + List listBuckets(); + + /** + * 获取存储桶详细信息 + */ + List> listBucketsWithDetails(); + + /** + * 设置存储桶策略 + */ + boolean setBucketPolicy(String bucketName, String policyJson); + + /** + * 获取存储桶策略 + */ + String getBucketPolicy(String bucketName); + + /** + * 获取存储桶大小 + */ + long getBucketSize(String bucketName); +} diff --git a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/FolderService.java b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/FolderService.java new file mode 100644 index 0000000..c609a43 --- /dev/null +++ b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/FolderService.java @@ -0,0 +1,54 @@ +package com.tongran.storage.service; + +import com.tongran.storage.dto.FileInfo; + +import java.util.List; +import java.util.Map; + +/** + * 文件夹管理服务接口 + */ +public interface FolderService { + + /** + * 创建文件夹 + */ + boolean createFolder(String bucketName, String folderPath); + + /** + * 删除文件夹 + */ + boolean deleteFolder(String bucketName, String folderPath); + + /** + * 检查文件夹是否存在 + */ + boolean folderExists(String bucketName, String folderPath); + + /** + * 列出文件夹内容 + */ + List listFolder(String bucketName, String folderPath); + + /** + * 获取文件夹大小 + */ + long getFolderSize(String bucketName, String folderPath); + + /** + * 复制文件夹 + */ + boolean copyFolder(String sourceBucket, String sourceFolder, + String targetBucket, String targetFolder); + + /** + * 移动文件夹 + */ + boolean moveFolder(String sourceBucket, String sourceFolder, + String targetBucket, String targetFolder); + + /** + * 获取文件夹树形结构 + */ + List> getFolderTree(String bucketName, String folderPath); +} \ No newline at end of file diff --git a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/StorageService.java b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/StorageService.java index 6c7b958..acdea88 100644 --- a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/StorageService.java +++ b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/StorageService.java @@ -7,10 +7,15 @@ import org.springframework.web.multipart.MultipartFile; import java.util.List; public interface StorageService { - UploadResult uploadFile(MultipartFile file); - byte[] downloadFile(String fileName); - void deleteFile(String fileName); - List listFiles(); - public String generateTempUrl(String fileName); - public boolean createBucket(String bucketName); + UploadResult uploadFile(String bucketName, String folderPath, MultipartFile file); + + byte[] downloadFile(String bucketName, String folderPath, String fileName); + + void deleteFile(String bucketName, String folderPath, String fileName); + + List listFiles(String bucketName, String folderPath); + + String generateTempUrl(String bucketName, String folderPath, String fileName); + + boolean batchDelete(String bucketName, List paths); } \ No newline at end of file diff --git a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/impl/BucketServiceImpl.java b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/impl/BucketServiceImpl.java new file mode 100644 index 0000000..4ae39aa --- /dev/null +++ b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/impl/BucketServiceImpl.java @@ -0,0 +1,215 @@ +package com.tongran.storage.service.impl; + +import com.tongran.storage.service.BucketService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.*; + +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class BucketServiceImpl implements BucketService { + + private final S3Client s3Client; + private static final DateTimeFormatter DATE_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Override + public boolean createBucket(String bucketName) { + try { + // 检查存储桶是否已存在 + if (bucketExists(bucketName)) { + log.warn("存储桶已存在: {}", bucketName); + return false; + } + + CreateBucketRequest createBucketRequest = CreateBucketRequest.builder() + .bucket(bucketName) + .build(); + + s3Client.createBucket(createBucketRequest); + log.info("存储桶创建成功: {}", bucketName); + return true; + + } catch (S3Exception e) { + log.error("创建存储桶失败: {}", bucketName, e); + throw new RuntimeException("创建存储桶失败: " + e.getMessage()); + } + } + + @Override + public boolean deleteBucket(String bucketName) { + try { + // 先检查桶是否存在 + if (!bucketExists(bucketName)) { + log.warn("存储桶不存在: {}", bucketName); + return false; + } + + // 删除存储桶 + DeleteBucketRequest deleteRequest = DeleteBucketRequest.builder() + .bucket(bucketName) + .build(); + s3Client.deleteBucket(deleteRequest); + + log.info("存储桶删除成功: {}", bucketName); + return true; + + } catch (S3Exception e) { + log.error("删除存储桶失败: {}", bucketName, e); + throw new RuntimeException("删除存储桶失败: " + e.getMessage()); + } + } + + @Override + public boolean bucketExists(String bucketName) { + try { + HeadBucketRequest headBucketRequest = HeadBucketRequest.builder() + .bucket(bucketName) + .build(); + + s3Client.headBucket(headBucketRequest); + return true; + } catch (NoSuchBucketException e) { + return false; + } catch (S3Exception e) { + log.error("检查存储桶状态失败: {}", bucketName, e); + throw new RuntimeException("检查存储桶状态失败: " + e.getMessage()); + } + } + + @Override + public List listBuckets() { + try { + ListBucketsResponse response = s3Client.listBuckets(); + + return response.buckets().stream() + .map(Bucket::name) + .sorted() + .collect(Collectors.toList()); + + } catch (S3Exception e) { + log.error("获取存储桶列表失败", e); + throw new RuntimeException("获取存储桶列表失败: " + e.getMessage()); + } + } + + @Override + public List> listBucketsWithDetails() { + try { + ListBucketsResponse response = s3Client.listBuckets(); + + return response.buckets().stream() + .map(bucket -> { + Map info = new HashMap<>(); + info.put("name", bucket.name()); + info.put("creationDate", bucket.creationDate()); + info.put("creationDateFormatted", + bucket.creationDate() + .atZone(ZoneId.of("Asia/Shanghai")) + .format(DATE_FORMATTER)); + + // 获取存储桶大小 + long size = getBucketSize(bucket.name()); + info.put("size", size); + info.put("sizeFormatted", formatFileSize(size)); + + // 获取对象数量 + long objectCount = getObjectCount(bucket.name()); + info.put("objectCount", objectCount); + + return info; + }) + .collect(Collectors.toList()); + + } catch (S3Exception e) { + log.error("获取存储桶详情失败", e); + throw new RuntimeException("获取存储桶详情失败: " + e.getMessage()); + } + } + + @Override + public boolean setBucketPolicy(String bucketName, String policyJson) { + try { + PutBucketPolicyRequest request = PutBucketPolicyRequest.builder() + .bucket(bucketName) + .policy(policyJson) + .build(); + + s3Client.putBucketPolicy(request); + log.info("存储桶策略设置成功: {}", bucketName); + return true; + + } catch (S3Exception e) { + log.error("设置存储桶策略失败: {}", bucketName, e); + throw new RuntimeException("设置存储桶策略失败: " + e.getMessage()); + } + } + + @Override + public String getBucketPolicy(String bucketName) { + try { + GetBucketPolicyRequest request = GetBucketPolicyRequest.builder() + .bucket(bucketName) + .build(); + + return s3Client.getBucketPolicy(request).policy(); + + } catch (S3Exception e) { + log.error("获取存储桶策略失败: {}", bucketName, e); + throw new RuntimeException("获取存储桶策略失败: " + e.getMessage()); + } + } + + @Override + public long getBucketSize(String bucketName) { + try { + ListObjectsV2Request request = ListObjectsV2Request.builder() + .bucket(bucketName) + .build(); + + ListObjectsV2Response response = s3Client.listObjectsV2(request); + return response.contents().stream() + .mapToLong(S3Object::size) + .sum(); + + } catch (S3Exception e) { + log.warn("无法获取存储桶 {} 的大小", bucketName, e); + return 0; + } + } + + // 辅助方法:获取存储桶中的对象数量 + private long getObjectCount(String bucketName) { + try { + ListObjectsV2Request request = ListObjectsV2Request.builder() + .bucket(bucketName) + .maxKeys(1000) // 限制查询数量 + .build(); + + ListObjectsV2Response response = s3Client.listObjectsV2(request); + return response.keyCount(); + + } catch (S3Exception e) { + log.warn("无法获取存储桶 {} 的对象数量", bucketName, e); + return 0; + } + } + + // 辅助方法:格式化文件大小 + private String formatFileSize(long size) { + if (size < 1024) return size + " B"; + else if (size < 1024 * 1024) return String.format("%.2f KB", size / 1024.0); + else if (size < 1024 * 1024 * 1024) return String.format("%.2f MB", size / (1024.0 * 1024)); + else return String.format("%.2f GB", size / (1024.0 * 1024 * 1024)); + } +} \ No newline at end of file diff --git a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/impl/FolderServiceImpl.java b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/impl/FolderServiceImpl.java new file mode 100644 index 0000000..bbfe0ae --- /dev/null +++ b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/impl/FolderServiceImpl.java @@ -0,0 +1,316 @@ +package com.tongran.storage.service.impl; + +import com.tongran.storage.dto.FileInfo; +import com.tongran.storage.service.FolderService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.*; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FolderServiceImpl implements FolderService { + + private final S3Client s3Client; + + @Override + public boolean createFolder(String bucketName, String folderPath) { + try { + // 确保文件夹路径以斜杠结尾 + if (!folderPath.endsWith("/")) { + folderPath += "/"; + } + + // 在S3中创建文件夹实际上是创建一个空对象 + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(bucketName) + .key(folderPath) + .build(); + + s3Client.putObject(putObjectRequest, software.amazon.awssdk.core.sync.RequestBody.empty()); + log.info("文件夹创建成功: {}/{}", bucketName, folderPath); + return true; + + } catch (S3Exception e) { + log.error("创建文件夹失败: {}/{}", bucketName, folderPath, e); + throw new RuntimeException("创建文件夹失败: " + e.getMessage()); + } + } + + @Override + public boolean deleteFolder(String bucketName, String folderPath) { + try { + // 确保文件夹路径以斜杠结尾 + if (!folderPath.endsWith("/")) { + folderPath += "/"; + } + + // 首先列出文件夹下的所有对象 + List objects = listAllObjectsInFolder(bucketName, folderPath); + + if (!objects.isEmpty()) { + // 批量删除所有对象 + List objectIdentifiers = objects.stream() + .map(obj -> ObjectIdentifier.builder().key(obj.key()).build()) + .collect(Collectors.toList()); + + DeleteObjectsRequest deleteRequest = DeleteObjectsRequest.builder() + .bucket(bucketName) + .delete(Delete.builder().objects(objectIdentifiers).build()) + .build(); + + s3Client.deleteObjects(deleteRequest); + log.info("删除文件夹内容: {}/{}, 共{}个对象", + bucketName, folderPath, objects.size()); + } + + // 删除文件夹标记对象 + DeleteObjectRequest deleteFolderRequest = DeleteObjectRequest.builder() + .bucket(bucketName) + .key(folderPath) + .build(); + + s3Client.deleteObject(deleteFolderRequest); + log.info("文件夹删除成功: {}/{}", bucketName, folderPath); + return true; + + } catch (S3Exception e) { + log.error("删除文件夹失败: {}/{}", bucketName, folderPath, e); + throw new RuntimeException("删除文件夹失败: " + e.getMessage()); + } + } + + @Override + public boolean folderExists(String bucketName, String folderPath) { + try { + // 确保文件夹路径以斜杠结尾 + if (!folderPath.endsWith("/")) { + folderPath += "/"; + } + + // 检查是否有以该路径为前缀的对象 + ListObjectsV2Request request = ListObjectsV2Request.builder() + .bucket(bucketName) + .prefix(folderPath) + .maxKeys(1) + .build(); + + ListObjectsV2Response response = s3Client.listObjectsV2(request); + return response.keyCount() > 0; + + } catch (S3Exception e) { + log.error("检查文件夹是否存在失败: {}/{}", bucketName, folderPath, e); + return false; + } + } + + @Override + public List listFolder(String bucketName, String folderPath) { + try { + // 确保文件夹路径以斜杠结尾 + if (!folderPath.endsWith("/") && !folderPath.isEmpty()) { + folderPath += "/"; + } + + ListObjectsV2Request request = ListObjectsV2Request.builder() + .bucket(bucketName) + .prefix(folderPath) + .delimiter("/") // 使用分隔符区分文件和文件夹 + .build(); + + ListObjectsV2Response response = s3Client.listObjectsV2(request); + List result = new ArrayList<>(); + + // 添加子文件夹 + response.commonPrefixes().forEach(prefix -> { + FileInfo folderInfo = new FileInfo(); + folderInfo.setFileName(prefix.prefix()); + folderInfo.setDirectory(true); + result.add(folderInfo); + }); + + // 添加文件(排除文件夹标记对象) + for (S3Object obj : response.contents()) { + if (!obj.key().equals(folderPath)) { // 排除文件夹本身 + FileInfo fileInfo = new FileInfo(); + fileInfo.setFileName(obj.key()); + fileInfo.setFileSize(obj.size()); + fileInfo.setLastModified(obj.lastModified()); + fileInfo.setEtag(obj.eTag()); + fileInfo.setDirectory(false); + result.add(fileInfo); + } + } + + return result; + + } catch (S3Exception e) { + log.error("列出文件夹内容失败: {}/{}", bucketName, folderPath, e); + throw new RuntimeException("列出文件夹内容失败: " + e.getMessage()); + } + } + + @Override + public long getFolderSize(String bucketName, String folderPath) { + try { + if (!folderPath.endsWith("/") && !folderPath.isEmpty()) { + folderPath += "/"; + } + + List objects = listAllObjectsInFolder(bucketName, folderPath); + return objects.stream() + .mapToLong(S3Object::size) + .sum(); + + } catch (S3Exception e) { + log.error("获取文件夹大小失败: {}/{}", bucketName, folderPath, e); + return 0; + } + } + + @Override + public boolean copyFolder(String sourceBucket, String sourceFolder, + String targetBucket, String targetFolder) { + try { + if (!sourceFolder.endsWith("/")) { + sourceFolder += "/"; + } + if (!targetFolder.endsWith("/")) { + targetFolder += "/"; + } + + // 获取源文件夹所有对象 + List sourceObjects = listAllObjectsInFolder(sourceBucket, sourceFolder); + + // 逐个复制对象 + for (S3Object sourceObj : sourceObjects) { + String sourceKey = sourceObj.key(); + String targetKey = sourceKey.replace(sourceFolder, targetFolder); + + CopyObjectRequest copyRequest = CopyObjectRequest.builder() + .sourceBucket(sourceBucket) + .sourceKey(sourceKey) + .destinationBucket(targetBucket) + .destinationKey(targetKey) + .build(); + + s3Client.copyObject(copyRequest); + log.debug("复制对象: {} -> {}", sourceKey, targetKey); + } + + log.info("文件夹复制完成: {}/{} -> {}/{} ({}个对象)", + sourceBucket, sourceFolder, targetBucket, targetFolder, sourceObjects.size()); + return true; + + } catch (S3Exception e) { + log.error("复制文件夹失败", e); + throw new RuntimeException("复制文件夹失败: " + e.getMessage()); + } + } + + @Override + public boolean moveFolder(String sourceBucket, String sourceFolder, + String targetBucket, String targetFolder) { + try { + // 先复制文件夹 + boolean copySuccess = copyFolder(sourceBucket, sourceFolder, targetBucket, targetFolder); + if (!copySuccess) { + return false; + } + + // 复制成功后删除源文件夹 + return deleteFolder(sourceBucket, sourceFolder); + + } catch (Exception e) { + log.error("移动文件夹失败", e); + throw new RuntimeException("移动文件夹失败: " + e.getMessage()); + } + } + + @Override + public List> getFolderTree(String bucketName, String folderPath) { + List> tree = new ArrayList<>(); + + try { + if (!folderPath.endsWith("/") && !folderPath.isEmpty()) { + folderPath += "/"; + } + + ListObjectsV2Request request = ListObjectsV2Request.builder() + .bucket(bucketName) + .prefix(folderPath) + .delimiter("/") + .build(); + + ListObjectsV2Response response = s3Client.listObjectsV2(request); + + // 处理子文件夹 + response.commonPrefixes().forEach(prefix -> { + Map folderNode = new HashMap<>(); + folderNode.put("name", prefix.prefix()); + folderNode.put("type", "folder"); + folderNode.put("path", prefix.prefix()); + + // 递归获取子文件夹 + folderNode.put("children", getFolderTree(bucketName, prefix.prefix())); + folderNode.put("size", getFolderSize(bucketName, prefix.prefix())); + + tree.add(folderNode); + }); + + // 处理文件 + for (S3Object obj : response.contents()) { + if (!obj.key().equals(folderPath) && !obj.key().endsWith("/")) { + Map fileNode = new HashMap<>(); + fileNode.put("name", getFileNameFromKey(obj.key())); + fileNode.put("type", "file"); + fileNode.put("path", obj.key()); + fileNode.put("size", obj.size()); + fileNode.put("lastModified", obj.lastModified()); + fileNode.put("etag", obj.eTag()); + + tree.add(fileNode); + } + } + + } catch (S3Exception e) { + log.error("获取文件夹树失败: {}/{}", bucketName, folderPath, e); + } + + return tree; + } + + // 辅助方法:获取文件夹下的所有对象 + private List listAllObjectsInFolder(String bucketName, String folderPath) { + List allObjects = new ArrayList<>(); + String continuationToken = null; + + do { + ListObjectsV2Request request = ListObjectsV2Request.builder() + .bucket(bucketName) + .prefix(folderPath) + .continuationToken(continuationToken) + .build(); + + ListObjectsV2Response response = s3Client.listObjectsV2(request); + allObjects.addAll(response.contents()); + continuationToken = response.nextContinuationToken(); + + } while (continuationToken != null); + + return allObjects; + } + + // 辅助方法:从key中提取文件名 + private String getFileNameFromKey(String key) { + if (key.contains("/")) { + return key.substring(key.lastIndexOf("/") + 1); + } + return key; + } +} \ No newline at end of file diff --git a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/impl/S3StorageServiceImpl.java b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/impl/S3StorageServiceImpl.java index a5f2a67..962f589 100644 --- a/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/impl/S3StorageServiceImpl.java +++ b/tongran-modules/tongran-storage/src/main/java/com/tongran/storage/service/impl/S3StorageServiceImpl.java @@ -1,6 +1,7 @@ // S3StorageServiceImpl.java package com.tongran.storage.service.impl; +import com.tongran.common.core.utils.StringUtils; import com.tongran.storage.config.StorageProperties; import com.tongran.storage.dto.FileInfo; import com.tongran.storage.dto.UploadResult; @@ -10,7 +11,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; 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.model.*; import software.amazon.awssdk.services.s3.presigner.S3Presigner; @@ -36,21 +36,27 @@ public class S3StorageServiceImpl implements StorageService { private final S3Presigner s3Presigner; @Override - public UploadResult uploadFile(MultipartFile file) { + public UploadResult uploadFile(String bucketName, String folderPath, MultipartFile file) { try { - // 1. 生成文件名 + // 1. 验证桶是否存在 + validateBucket(bucketName); + + // 2. 生成文件名 String originalName = file.getOriginalFilename(); String extension = getFileExtension(originalName); String fileName = UUID.randomUUID().toString() + (extension != null ? "." + extension : ""); - // 2. 自动判断:小文件直接传,大文件自动分片 + // 3. 构建完整的文件路径(包含文件夹) + String fullPath = buildFullPath(folderPath, fileName); + + // 4. 自动判断:小文件直接传,大文件自动分片 if (file.getSize() <= storageProperties.getChunkSize().toBytes()) { // 小文件:直接上传 - return uploadDirect(file, fileName); + return uploadDirect(bucketName, fullPath, file); } else { // 大文件:自动分片上传 - return uploadWithChunks(file, fileName); + return uploadWithChunks(bucketName, fullPath, file); } } catch (Exception e) { @@ -59,37 +65,47 @@ public class S3StorageServiceImpl implements StorageService { } } + // 构建完整路径 + private String buildFullPath(String folderPath, String fileName) { + if (StringUtils.hasText(folderPath)) { + // 确保文件夹路径以 / 结尾 + String normalizedFolder = folderPath.endsWith("/") ? folderPath : folderPath + "/"; + return normalizedFolder + fileName; + } + return fileName; + } + // 小文件直接上传 - private UploadResult uploadDirect(MultipartFile file, String fileName) throws IOException { + private UploadResult uploadDirect(String bucketName, String fullPath, MultipartFile file) throws IOException { s3Client.putObject(b -> b - .bucket(storageProperties.getBucketName()) - .key(fileName) + .bucket(bucketName) + .key(fullPath) .contentType(file.getContentType()), RequestBody.fromBytes(file.getBytes()) ); - String fileUrl = buildFileUrl(fileName); - log.info("小文件直接上传成功: {} ({} bytes)", fileName, file.getSize()); + String fileUrl = buildFileUrl(bucketName, fullPath); + log.info("小文件直接上传成功: bucket={}, path={} ({} bytes)", + bucketName, fullPath, file.getSize()); - return UploadResult.success(fileName, fileUrl, file.getSize()); + return UploadResult.success(fullPath, fileUrl, file.getSize()); } // 大文件自动分片上传 - private UploadResult uploadWithChunks(MultipartFile file, String fileName) throws IOException { + private UploadResult uploadWithChunks(String bucketName, String fullPath, MultipartFile file) throws IOException { long fileSize = file.getSize(); - long chunkSize = storageProperties.getChunkSize().toBytes(); // 比如10MB + long chunkSize = storageProperties.getChunkSize().toBytes(); String contentType = file.getContentType(); - String bucket = storageProperties.getBucketName(); // 1. 自动初始化分片上传 String uploadId = s3Client.createMultipartUpload(b -> b - .bucket(bucket) - .key(fileName) + .bucket(bucketName) + .key(fullPath) .contentType(contentType) ).uploadId(); - log.info("开始分片上传: {} ({} bytes, 分片大小: {} bytes)", - fileName, fileSize, chunkSize); + log.info("开始分片上传: bucket={}, path={} ({} bytes, 分片大小: {} bytes)", + bucketName, fullPath, fileSize, chunkSize); try (InputStream inputStream = file.getInputStream()) { List completedParts = new ArrayList<>(); @@ -104,7 +120,7 @@ public class S3StorageServiceImpl implements StorageService { byte[] chunkBytes = Arrays.copyOf(buffer, bytesRead); // 上传单个分片 - String etag = uploadPart(uploadId, fileName, partNumber, chunkBytes, bucket); + String etag = uploadPart(uploadId, bucketName, fullPath, partNumber, chunkBytes); completedParts.add(CompletedPart.builder() .partNumber(partNumber) @@ -122,25 +138,26 @@ public class S3StorageServiceImpl implements StorageService { // 3. 自动完成上传 s3Client.completeMultipartUpload(b -> b - .bucket(bucket) - .key(fileName) + .bucket(bucketName) + .key(fullPath) .uploadId(uploadId) .multipartUpload(CompletedMultipartUpload.builder() .parts(completedParts) .build()) ); - String fileUrl = buildFileUrl(fileName); - log.info("分片上传完成: {} (共{}个分片)", fileName, completedParts.size()); + String fileUrl = buildFileUrl(bucketName, fullPath); + log.info("分片上传完成: bucket={}, path={} (共{}个分片)", + bucketName, fullPath, completedParts.size()); - return UploadResult.success(fileName, fileUrl, fileSize); + return UploadResult.success(fullPath, fileUrl, fileSize); } catch (Exception e) { // 出错时自动取消上传 try { s3Client.abortMultipartUpload(b -> b - .bucket(bucket) - .key(fileName) + .bucket(bucketName) + .key(fullPath) .uploadId(uploadId) ); log.warn("上传失败,已取消分片上传: {}", uploadId); @@ -152,11 +169,11 @@ public class S3StorageServiceImpl implements StorageService { } // 上传单个分片(辅助方法) - private String uploadPart(String uploadId, String fileName, - int partNumber, byte[] chunkData, String bucket) { + private String uploadPart(String uploadId, String bucketName, String fullPath, + int partNumber, byte[] chunkData) { UploadPartRequest uploadRequest = UploadPartRequest.builder() - .bucket(bucket) - .key(fileName) + .bucket(bucketName) + .key(fullPath) .uploadId(uploadId) .partNumber(partNumber) .build(); @@ -170,46 +187,66 @@ public class S3StorageServiceImpl implements StorageService { } @Override - public byte[] downloadFile(String fileName) { + public byte[] downloadFile(String bucketName, String folderPath, String fileName) { try { + validateBucket(bucketName); + + // 构建完整路径 + String fullPath = buildFullPath(folderPath, fileName); + GetObjectRequest getObjectRequest = GetObjectRequest.builder() - .bucket(storageProperties.getBucketName()) - .key(fileName) + .bucket(bucketName) + .key(fullPath) .build(); return s3Client.getObjectAsBytes(getObjectRequest).asByteArray(); } catch (S3Exception e) { - log.error("下载文件失败: {}", fileName, e); + log.error("下载文件失败: bucket={}, path={}/{}", + bucketName, folderPath, fileName, e); throw new RuntimeException("下载文件失败: " + e.getMessage()); } } @Override - public void deleteFile(String fileName) { + public void deleteFile(String bucketName, String folderPath, String fileName) { try { + validateBucket(bucketName); + + // 构建完整路径 + String fullPath = buildFullPath(folderPath, fileName); + DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder() - .bucket(storageProperties.getBucketName()) - .key(fileName) + .bucket(bucketName) + .key(fullPath) .build(); s3Client.deleteObject(deleteObjectRequest); - log.info("文件删除成功: {}", fileName); + log.info("文件删除成功: bucket={}, path={}", bucketName, fullPath); } catch (S3Exception e) { - log.error("删除文件失败: {}", fileName, e); + log.error("删除文件失败: bucket={}, path={}/{}", + bucketName, folderPath, fileName, e); throw new RuntimeException("删除文件失败: " + e.getMessage()); } } @Override - public List listFiles() { + public List listFiles(String bucketName, String folderPath) { try { - ListObjectsV2Request request = ListObjectsV2Request.builder() - .bucket(storageProperties.getBucketName()) - .build(); + validateBucket(bucketName); - ListObjectsV2Response response = s3Client.listObjectsV2(request); + // 构建查询参数 + ListObjectsV2Request.Builder requestBuilder = ListObjectsV2Request.builder() + .bucket(bucketName); + + // 如果指定了文件夹,则只查询该文件夹下的文件 + if (StringUtils.hasText(folderPath)) { + String prefix = folderPath.endsWith("/") ? folderPath : folderPath + "/"; + requestBuilder.prefix(prefix); + } + + ListObjectsV2Response response = s3Client.listObjectsV2(requestBuilder.build()); return response.contents().stream() .map(s3Object -> { @@ -218,108 +255,174 @@ public class S3StorageServiceImpl implements StorageService { fileInfo.setFileSize(s3Object.size()); fileInfo.setLastModified(s3Object.lastModified()); fileInfo.setEtag(s3Object.eTag()); + fileInfo.setBucketName(bucketName); return fileInfo; }) .collect(Collectors.toList()); } catch (S3Exception e) { - log.error("获取文件列表失败", e); + log.error("获取文件列表失败: bucket={}", bucketName, e); throw new RuntimeException("获取文件列表失败: " + e.getMessage()); } } + @Override + public String generateTempUrl(String bucketName, String folderPath, String fileName) { + try { + validateBucket(bucketName); + + // 构建完整路径 + String fullPath = buildFullPath(folderPath, fileName); + + GetObjectRequest getObjectRequest = GetObjectRequest.builder() + .bucket(bucketName) + .key(fullPath) + .build(); + + GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() + .getObjectRequest(getObjectRequest) + .signatureDuration(Duration.ofHours(24)) + .build(); + + PresignedGetObjectRequest presignedRequest = s3Presigner.presignGetObject(presignRequest); + String url = presignedRequest.url().toString(); + + log.info("生成临时访问链接: bucket={}, path={} (24小时有效)", + bucketName, fullPath); + return url; + + } catch (S3Exception e) { + log.error("生成临时链接失败: bucket={}, path={}/{}", + bucketName, folderPath, fileName, e); + throw new RuntimeException("生成临时链接失败: " + e.getMessage()); + } + } + + + // 验证桶是否存在 + private void validateBucket(String bucketName) { + try { + s3Client.headBucket(HeadBucketRequest.builder() + .bucket(bucketName) + .build()); + } catch (S3Exception e) { + log.error("Bucket不存在或无权限: {}", bucketName, e); + throw new RuntimeException("Bucket不存在或无权限: " + bucketName); + } + } + + // 获取文件扩展名 private String getFileExtension(String fileName) { if (fileName == null || !fileName.contains(".")) { return null; } return fileName.substring(fileName.lastIndexOf(".") + 1); } - private String buildFileUrl(String fileName) { + + // 构建文件URL + private String buildFileUrl(String bucketName, String fileName) { return String.format("%s/%s/%s", storageProperties.getEndpoint(), - storageProperties.getBucketName(), + bucketName, fileName); } - /** - * 生成24小时有效的临时下载链接 - * @param fileName 文件名 - * @return 临时访问URL - */ + @Override - public String generateTempUrl(String fileName) { + public boolean batchDelete(String bucketName, List paths) { try { - GetObjectRequest getObjectRequest = GetObjectRequest.builder() - .bucket(storageProperties.getBucketName()) - .key(fileName) - .build(); - - GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() - .getObjectRequest(getObjectRequest) - .signatureDuration(Duration.ofHours(24)) // 24小时有效 - .build(); - - PresignedGetObjectRequest presignedRequest = s3Presigner.presignGetObject(presignRequest); - String url = presignedRequest.url().toString(); - - log.info("生成临时访问链接: {} (24小时有效)", fileName); - return url; - - } catch (S3Exception e) { - log.error("生成临时链接失败: {}", fileName, e); - throw new RuntimeException("生成临时链接失败: " + e.getMessage()); - } - } - - /** - * 创建新的存储桶 - * @param bucketName 存储桶名称 - * @return 是否创建成功 - */ - @Override - public boolean createBucket(String bucketName) { - try { - // 检查存储桶是否已存在 - if (isBucketExist(bucketName)) { - log.warn("存储桶已存在: {}", bucketName); - return false; + if (paths == null || paths.isEmpty()) { + log.warn("删除路径列表为空"); + return true; } - // 创建存储桶 - CreateBucketRequest createBucketRequest = CreateBucketRequest.builder() - .bucket(bucketName) - .createBucketConfiguration(b -> b - .locationConstraint(Region.of(storageProperties.getRegion()).id()) - ) - .build(); + // 收集所有要删除的对象键(包括文件夹内的所有文件) + List allKeysToDelete = new ArrayList<>(); - s3Client.createBucket(createBucketRequest); - log.info("存储桶创建成功: {}", bucketName); - return true; + for (String path : paths) { + if (path.endsWith("/")) { + // 如果是文件夹,获取文件夹内所有文件 + List folderObjects = listAllObjectsInFolder(bucketName, path); + for (S3Object folderObject : folderObjects) { + allKeysToDelete.add(folderObject.key()); + } + // 添加文件夹标记本身 + allKeysToDelete.add(path); + } else { + // 如果是文件,直接添加 + allKeysToDelete.add(path); + } + } - } catch (S3Exception e) { - log.error("创建存储桶失败: {}", bucketName, e); - throw new RuntimeException("创建存储桶失败: " + e.getMessage()); + // 去重 + allKeysToDelete = allKeysToDelete.stream().distinct().collect(Collectors.toList()); + + if (allKeysToDelete.isEmpty()) { + log.info("没有需要删除的对象"); + return true; + } + + // 批量删除 + return deleteObjectsBatch(bucketName, allKeysToDelete); + + } catch (Exception e) { + log.error("批量删除失败: 存储桶={}", bucketName, e); + throw new RuntimeException("批量删除失败: " + e.getMessage()); } } /** - * 检查存储桶是否存在 - * @param bucketName 存储桶名称 - * @return 是否存在 + * 批量删除对象 */ - private boolean isBucketExist(String bucketName) { + private boolean deleteObjectsBatch(String bucketName, List keys) { try { - HeadBucketRequest headBucketRequest = HeadBucketRequest.builder() - .bucket(bucketName) - .build(); + // 转换为删除对象格式 + List objectsToDelete = keys.stream() + .map(key -> ObjectIdentifier.builder().key(key).build()) + .collect(Collectors.toList()); - s3Client.headBucket(headBucketRequest); + // 分批删除(每批最多1000个) + int batchSize = 1000; + for (int i = 0; i < objectsToDelete.size(); i += batchSize) { + int end = Math.min(i + batchSize, objectsToDelete.size()); + List batch = objectsToDelete.subList(i, end); + + DeleteObjectsRequest deleteRequest = DeleteObjectsRequest.builder() + .bucket(bucketName) + .delete(Delete.builder().objects(batch).build()) + .build(); + + s3Client.deleteObjects(deleteRequest); + } + + log.info("批量删除成功: 存储桶={}, 删除对象数量={}", bucketName, keys.size()); return true; - } catch (NoSuchBucketException e) { + + } catch (Exception e) { + log.error("批量删除对象失败: 存储桶={}", bucketName, e); return false; - } catch (S3Exception e) { - log.error("检查存储桶状态失败: {}", bucketName, e); - throw new RuntimeException("检查存储桶状态失败: " + e.getMessage()); } } + + /** + * 获取文件夹下所有对象 + */ + private List listAllObjectsInFolder(String bucketName, String folderPath) { + List allObjects = new ArrayList<>(); + String continuationToken = null; + + do { + ListObjectsV2Request request = ListObjectsV2Request.builder() + .bucket(bucketName) + .prefix(folderPath) + .continuationToken(continuationToken) + .build(); + + ListObjectsV2Response response = s3Client.listObjectsV2(request); + allObjects.addAll(response.contents()); + continuationToken = response.nextContinuationToken(); + + } while (continuationToken != null); + + return allObjects; + } } \ No newline at end of file diff --git a/tongran-modules/tongran-system/src/main/java/com/tongran/system/service/impl/RmResourceRegistrationServiceImpl.java b/tongran-modules/tongran-system/src/main/java/com/tongran/system/service/impl/RmResourceRegistrationServiceImpl.java index 1f33862..6e6efc9 100644 --- a/tongran-modules/tongran-system/src/main/java/com/tongran/system/service/impl/RmResourceRegistrationServiceImpl.java +++ b/tongran-modules/tongran-system/src/main/java/com/tongran/system/service/impl/RmResourceRegistrationServiceImpl.java @@ -524,14 +524,19 @@ public class RmResourceRegistrationServiceImpl implements IRmResourceRegistratio if (businessIpCount > 3) { continue; // 最多只处理3个业务IP } - // 设置业务IP字段 switch (businessIpCount) { case 1: - registration.setIp1Isp(network.getIsp()); - registration.setIp1Province(network.getProvince()); + if (network.getIsp() != null) { + registration.setIp1Isp(network.getIsp()); + } + if (network.getProvince() != null) { + registration.setIp1Province(network.getProvince()); + } registration.setIp1City(network.getCity()); - registration.setIp1PublicIp(network.getPublicIp()); + if (network.getPublicIp() != null) { + registration.setIp1PublicIp(network.getPublicIp()); + } registration.setIp1InterfaceName(network.getInterfaceName()); registration.setIp1MacAddress(network.getMacAddress()); registration.setIp1InterfaceType(network.getInterfaceType()); @@ -579,53 +584,6 @@ public class RmResourceRegistrationServiceImpl implements IRmResourceRegistratio if(registration.getIp1Province() == null && network.getProvince() != null){ registration.setIp1Province(network.getProvince()); } - - // 如果还有字段为null,再尝试从子网卡中获取 - if(registration.getIp1PublicIp() == null || registration.getIp1Isp() == null || registration.getIp1Province() == null){ - // 查询子网卡信息 - RmNetworkInterfaceRemote query = new RmNetworkInterfaceRemote(); - query.setClientId(registration.getClientId()); - query.setIpv4Flag(true); - R> children = remoteRocketMqService.innerGetChildList(query, SecurityConstants.INNER); - - if(children != null && children.getData() != null && !children.getData().isEmpty()){ - // 按ip最后一位排序取最小的 - RmNetworkInterfaceRemote minIpChild = null; - int minLastOctet = Integer.MAX_VALUE; - - for (RmNetworkInterfaceRemote child : children.getData()) { - if (child.getIpv4Address() != null && !child.getIpv4Address().isEmpty()) { - String ip = child.getIpv4Address(); - String[] ipParts = ip.split("\\."); - - if (ipParts.length == 4) { - try { - int lastOctet = Integer.parseInt(ipParts[3]); - if (lastOctet < minLastOctet) { - minLastOctet = lastOctet; - minIpChild = child; - } - } catch (NumberFormatException e) { - // 忽略格式错误的IP - } - } - } - } - - if (minIpChild != null) { - // 只设置还为空的值 - if(registration.getIp1PublicIp() == null){ - registration.setIp1PublicIp(minIpChild.getPublicIp()); - } - if(registration.getIp1Isp() == null){ - registration.setIp1Isp(minIpChild.getIsp()); - } - if(registration.getIp1Province() == null){ - registration.setIp1Province(minIpChild.getProvince()); - } - } - } - } // 管理网IP处理 registration.setMgmtIsp(network.getIsp()); registration.setMgmtProvince(network.getProvince()); @@ -639,6 +597,52 @@ public class RmResourceRegistrationServiceImpl implements IRmResourceRegistratio registration.setMgmtGateway(network.getGateway()); } } + // 如果还有字段为null,再尝试从子网卡中获取 + if(registration.getIp1PublicIp() == null || registration.getIp1Isp() == null || registration.getIp1Province() == null){ + // 查询子网卡信息 + RmNetworkInterfaceRemote query = new RmNetworkInterfaceRemote(); + query.setClientId(registration.getClientId()); + query.setIpv4Flag(true); + R> children = remoteRocketMqService.innerGetChildList(query, SecurityConstants.INNER); + + if(children != null && children.getData() != null && !children.getData().isEmpty()){ + // 按ip最后一位排序取最小的 + RmNetworkInterfaceRemote minIpChild = null; + int minLastOctet = Integer.MAX_VALUE; + + for (RmNetworkInterfaceRemote child : children.getData()) { + if (child.getIpv4Address() != null && !child.getIpv4Address().isEmpty()) { + String ip = child.getIpv4Address(); + String[] ipParts = ip.split("\\."); + + if (ipParts.length == 4) { + try { + int lastOctet = Integer.parseInt(ipParts[3]); + if (lastOctet < minLastOctet) { + minLastOctet = lastOctet; + minIpChild = child; + } + } catch (NumberFormatException e) { + // 忽略格式错误的IP + } + } + } + } + + if (minIpChild != null) { + // 只设置还为空的值 + if(registration.getIp1PublicIp() == null){ + registration.setIp1PublicIp(minIpChild.getPublicIp()); + } + if(registration.getIp1Isp() == null){ + registration.setIp1Isp(minIpChild.getIsp()); + } + if(registration.getIp1Province() == null){ + registration.setIp1Province(minIpChild.getProvince()); + } + } + } + } } diff --git a/tongran-modules/tongran-system/src/main/resources/mapper/system/EpsInitialTrafficDataMapper.xml b/tongran-modules/tongran-system/src/main/resources/mapper/system/EpsInitialTrafficDataMapper.xml index 692afaf..8ff25c2 100644 --- a/tongran-modules/tongran-system/src/main/resources/mapper/system/EpsInitialTrafficDataMapper.xml +++ b/tongran-modules/tongran-system/src/main/resources/mapper/system/EpsInitialTrafficDataMapper.xml @@ -52,7 +52,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" CREATE TABLE IF NOT EXISTS ${tableName} ( id BIGINT(20) AUTO_INCREMENT COMMENT '唯一标识ID', `name` VARCHAR(255) COMMENT '接口名称', - `mac` VARCHAR(20) COMMENT 'MAC地址', + `mac` VARCHAR(255) COMMENT 'MAC地址', `status` VARCHAR(20) COMMENT '运行状态', `type` VARCHAR(30) COMMENT '接口类型', ipV4 VARCHAR(20) COMMENT 'IPv4地址', diff --git a/tongran-rocketmq/src/main/java/com/tongran/rocketmq/snmp/service/ProcessSwitchCollectDataService.java b/tongran-rocketmq/src/main/java/com/tongran/rocketmq/snmp/service/ProcessSwitchCollectDataService.java index 6e6dd34..f51d0d7 100644 --- a/tongran-rocketmq/src/main/java/com/tongran/rocketmq/snmp/service/ProcessSwitchCollectDataService.java +++ b/tongran-rocketmq/src/main/java/com/tongran/rocketmq/snmp/service/ProcessSwitchCollectDataService.java @@ -173,6 +173,16 @@ public class ProcessSwitchCollectDataService { private void handleSwitchNetMessage(CollectDataVo switchDataVo, String clientId) { List switchInfos = SwitchJsonDataParser.parseJsonData(switchDataVo.getValue(), InitialSwitchInfo.class); if(!switchInfos.isEmpty()){ + // 根据clientId查询交换机名称 +// String switchName = ""; +// RmSwitchManagementRemote rmSwitchManagementRemote = new RmSwitchManagementRemote(); +// rmSwitchManagementRemote.setClientId(clientId); +// R> rmSwitchManagementRemoteListR = remoteRevenueConfigService.getSwitchNameByClientId(rmSwitchManagementRemote, SecurityConstants.INNER); +// if(rmSwitchManagementRemoteListR != null && +// rmSwitchManagementRemoteListR.getData()!=null && +// !rmSwitchManagementRemoteListR.getData().isEmpty()){ +// switchName = rmSwitchManagementRemoteListR.getData().get(0).getSwitchName(); +// } // 时间戳转换 long timestamp = switchDataVo.getTimestamp(); long millis = timestamp * 1000; @@ -199,7 +209,7 @@ public class ProcessSwitchCollectDataService { BigDecimal divisor = new BigDecimal(300); // 3. 计算速度 - switchInfos.forEach(switchInfo -> { + for (InitialSwitchInfo switchInfo : switchInfos) { // Map filedMap = new HashMap(); switchInfo.setClientId(clientId); switchInfo.setCreateTime(createTime); @@ -224,10 +234,12 @@ public class ProcessSwitchCollectDataService { // filedMap.put("out", outDiffBit.divide(divisor, 0, RoundingMode.HALF_UP)); } } -// iotDbUtils.writeDataWithTag("switch", clientId, -// switchInfo.getName(), millis, filedMap -// ); - }); +// if(!"".equals(switchName)){ +// iotDbUtils.writeDataWithTag("switch", switchName, +// "traffic_" + switchInfo.getName(), millis, filedMap +// ); +// } + } // 清空临时表对应switch信息 initialSwitchInfoTempService.truncateSwitchInfoTemp(clientId); }else{ diff --git a/tongran-rocketmq/src/main/java/com/tongran/rocketmq/utils/IotDbUtils.java b/tongran-rocketmq/src/main/java/com/tongran/rocketmq/utils/IotDbUtils.java index 588eb7f..af54c13 100644 --- a/tongran-rocketmq/src/main/java/com/tongran/rocketmq/utils/IotDbUtils.java +++ b/tongran-rocketmq/src/main/java/com/tongran/rocketmq/utils/IotDbUtils.java @@ -106,22 +106,49 @@ // } // // /** -// * 路径安全处理 +// * 路径安全处理 - IoTDB路径命名规则 +// * IoTDB路径中不能包含特殊字符,统一替换为下划线 // */ // private String safePath(String input) { // if (input == null) return "unknown"; -// return input.replace(".", "_") -// .replace(":", "_") -// .replace("-", "_") -// .replaceAll("[^a-zA-Z0-9_]", "_"); +// +// // 替换所有非字母、数字、汉字、下划线的字符为下划线 +// String cleaned = input.trim(); +// StringBuilder result = new StringBuilder(); +// +// for (int i = 0; i < cleaned.length(); i++) { +// char c = cleaned.charAt(i); +// if (Character.isLetterOrDigit(c) || c == '_' || +// (c >= '\u4e00' && c <= '\u9fa5')) { // 汉字范围 +// result.append(c); +// } else { +// result.append('_'); +// } +// } +// +// return result.toString(); // } // // /** -// * 字段名安全处理 +// * 字段名安全处理 - 统一替换为下划线 // */ // private String safeField(String field) { // if (field == null) return "unknown"; -// return field.replaceAll("[^a-zA-Z0-9_]", "_"); +// +// // 替换所有非字母、数字、下划线的字符为下划线 +// String cleaned = field.trim(); +// StringBuilder result = new StringBuilder(); +// +// for (int i = 0; i < cleaned.length(); i++) { +// char c = cleaned.charAt(i); +// if (Character.isLetterOrDigit(c) || c == '_') { +// result.append(c); +// } else { +// result.append('_'); +// } +// } +// +// return result.toString(); // } // // /**