diff --git a/das/pom.xml b/das/pom.xml index 61fde9d6..d4d40234 100644 --- a/das/pom.xml +++ b/das/pom.xml @@ -31,6 +31,7 @@ 3.2.10 3.4.4 5.4.3 + 8.4.3 @@ -201,6 +202,13 @@ + + + io.minio + minio + ${minio.version} + + diff --git a/das/src/main/java/com/das/common/constant/FileConstants.java b/das/src/main/java/com/das/common/constant/FileConstants.java new file mode 100644 index 00000000..656d425c --- /dev/null +++ b/das/src/main/java/com/das/common/constant/FileConstants.java @@ -0,0 +1,27 @@ +package com.das.common.constant; + +/** + * @Author liuyuxia + * @ClassName FilesvrConstants + * @Date 2018/12/10 0010 11:02 + * @Version 2.5 + * @Description 文件服务基础常量 + **/ +public class FileConstants { + + public static final String FILE_SEPARATOR = "/"; + + public static final String FILE_CHARSET = "UTF-8"; + + /** + * 递归 + */ + public static final Integer YES_RECURSIVE=1; + public static final Integer NO_RECURSIVE=0; + + public static final int META_R = 1001; + public static final int META_W = 1002; + + public static final String META_R_NAME = "读"; + public static final String META_W_NAME = "写"; +} diff --git a/das/src/main/java/com/das/modules/fdr/config/MinioConfig.java b/das/src/main/java/com/das/modules/fdr/config/MinioConfig.java new file mode 100644 index 00000000..92941ef5 --- /dev/null +++ b/das/src/main/java/com/das/modules/fdr/config/MinioConfig.java @@ -0,0 +1,71 @@ +package com.das.modules.fdr.config; + +import io.minio.*; +import io.minio.errors.*; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Slf4j +@Configuration +@ConditionalOnClass(MinioClient.class) +public class MinioConfig { + + @Resource + private MinioProperties minioAutoProperties; + @Bean + public MinioClient minioClient() { + log.info("开始初始化MinioClient, url为{}, accessKey为:{}", minioAutoProperties.getUrl(), minioAutoProperties.getAccessKey()); + MinioClient minioClient = MinioClient + .builder() + .endpoint(minioAutoProperties.getUrl()) + .credentials(minioAutoProperties.getAccessKey(), minioAutoProperties.getSecretKey()) + .build(); + + minioClient.setTimeout( + minioAutoProperties.getConnectTimeout(), + minioAutoProperties.getWriteTimeout(), + minioAutoProperties.getReadTimeout() + ); + // Start detection + if (minioAutoProperties.isCheckBucket()) { + log.info("checkBucket为{}, 开始检测桶是否存在", minioAutoProperties.isCheckBucket()); + String bucketName = minioAutoProperties.getBucket(); + if (!checkBucket(bucketName, minioClient)) { + log.info("文件桶[{}]不存在, 开始检查是否可以新建桶", bucketName); + if (minioAutoProperties.isCreateBucket()) { + log.info("createBucket为{},开始新建文件桶", minioAutoProperties.isCreateBucket()); + createBucket(bucketName, minioClient); + } + } + log.info("文件桶[{}]已存在, minio客户端连接成功!", bucketName); + } else { + throw new RuntimeException("桶不存在, 请检查桶名称是否正确或者将checkBucket属性改为false"); + } + return minioClient; + } + + private boolean checkBucket(String bucketName, MinioClient minioClient) { + boolean isExists = false; + try { + isExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); + } catch (Exception e) { + throw new RuntimeException("failed to check if the bucket exists", e); + } + return isExists; + } + + private void createBucket(String bucketName, MinioClient minioClient) { + try { + minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); + log.info("文件桶[{}]新建成功, minio客户端已连接", bucketName); + } catch (Exception e) { + throw new RuntimeException("failed to create default bucket", e); + } + } + +} diff --git a/das/src/main/java/com/das/modules/fdr/config/MinioProperties.java b/das/src/main/java/com/das/modules/fdr/config/MinioProperties.java new file mode 100644 index 00000000..3ce7cfed --- /dev/null +++ b/das/src/main/java/com/das/modules/fdr/config/MinioProperties.java @@ -0,0 +1,70 @@ +package com.das.modules.fdr.config; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import org.hibernate.validator.constraints.URL; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +@Data +@Validated +@Component +public class MinioProperties { + /** + * 服务地址 + */ + @NotEmpty(message = "minio服务地址不可为空") + @URL(message = "minio服务地址格式错误") + @Value("${minio.url}") + private String url; + + /** + * 认证账户 + */ + @NotEmpty(message = "minio认证账户不可为空") + @Value("${minio.accessKey}") + private String accessKey; + + /** + * 认证密码 + */ + @NotEmpty(message = "minio认证密码不可为空") + @Value("${minio.secretKey}") + private String secretKey; + + /** + * 桶名称, 优先级最低 + */ + @Value("${minio.bucket}") + private String bucket; + + /** + * 桶不在的时候是否新建桶 + */ + private boolean createBucket = true; + + /** + * 启动的时候检查桶是否存在 + */ + private boolean checkBucket = true; + + /** + * 设置HTTP连接、写入和读取超时。值为0意味着没有超时 + * HTTP连接超时,以毫秒为单位。 + */ + private long connectTimeout; + + /** + * 设置HTTP连接、写入和读取超时。值为0意味着没有超时 + * HTTP写超时,以毫秒为单位。 + */ + private long writeTimeout; + + /** + * 设置HTTP连接、写入和读取超时。值为0意味着没有超时 + * HTTP读取超时,以毫秒为单位。 + */ + private long readTimeout; +} diff --git a/das/src/main/java/com/das/modules/fdr/controller/FaultRecorderController.java b/das/src/main/java/com/das/modules/fdr/controller/FaultRecorderController.java new file mode 100644 index 00000000..0aff2c33 --- /dev/null +++ b/das/src/main/java/com/das/modules/fdr/controller/FaultRecorderController.java @@ -0,0 +1,31 @@ +package com.das.modules.fdr.controller; + +import com.das.common.result.R; +import com.das.modules.fdr.domain.FileNode; +import com.das.modules.fdr.service.FaultRecorderService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 故障录波controller + */ +@Slf4j +@RequestMapping("/api/fdr") +@RestController +public class FaultRecorderController { + + @Autowired + private FaultRecorderService faultRecorderService; + + @RequestMapping(value = "/files", method = RequestMethod.POST) + public R> findList(@RequestParam(required = false) String name, String startTime, String endTime) { + List result = faultRecorderService.getDirOrFileList(name,startTime,endTime); + return R.success(result); + } +} diff --git a/das/src/main/java/com/das/modules/fdr/domain/FileNode.java b/das/src/main/java/com/das/modules/fdr/domain/FileNode.java new file mode 100644 index 00000000..8a2894b5 --- /dev/null +++ b/das/src/main/java/com/das/modules/fdr/domain/FileNode.java @@ -0,0 +1,22 @@ +package com.das.modules.fdr.domain; + +import lombok.*; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FileNode { + //节点名称 + private String name; + + // 0代表文件夹,1代表文件 + private int type; + + private String size; + + private String lastModified; + + private String path; +} diff --git a/das/src/main/java/com/das/modules/fdr/service/FaultRecorderService.java b/das/src/main/java/com/das/modules/fdr/service/FaultRecorderService.java new file mode 100644 index 00000000..b282c184 --- /dev/null +++ b/das/src/main/java/com/das/modules/fdr/service/FaultRecorderService.java @@ -0,0 +1,19 @@ +package com.das.modules.fdr.service; + +import com.das.modules.fdr.domain.FileNode; +import org.springframework.web.multipart.MultipartFile; + +import java.io.OutputStream; +import java.nio.file.Path; +import java.util.List; + +public interface FaultRecorderService { + List getDirOrFileList(String name,String startTime, String endTime); + + String upload(String parent, String folderName, MultipartFile file); + + void readFileToSteam(String path, OutputStream stream); + + void download(String path, Path tempDir); + +} diff --git a/das/src/main/java/com/das/modules/fdr/service/MinioViewsServcie.java b/das/src/main/java/com/das/modules/fdr/service/MinioViewsServcie.java new file mode 100644 index 00000000..0c4f33f5 --- /dev/null +++ b/das/src/main/java/com/das/modules/fdr/service/MinioViewsServcie.java @@ -0,0 +1,218 @@ +package com.das.modules.fdr.service; + +import cn.hutool.core.io.FileUtil; +import com.das.common.constant.FileConstants; +import com.das.modules.fdr.config.MinioProperties; +import com.das.modules.fdr.domain.FileNode; +import io.micrometer.common.util.StringUtils; +import io.minio.*; +import io.minio.messages.Item; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.nio.file.Path; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +@Service +@Slf4j +public class MinioViewsServcie { + @Autowired + private MinioClient minioClient; + + @Autowired + private MinioProperties minioProperties; + + /** + * 删除文件 + * + * @param bucketName 存储桶 + * @param objectName 文件名称 + */ + public void removeFile(String bucketName, String objectName, Boolean recursive) throws Exception { + Iterable> results = minioClient.listObjects( + ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(true).build()); + List> list = StreamSupport.stream(results.spliterator(), false) + .collect(Collectors.toList()); + if (list.size() >= 2 && !recursive) { + throw new IOException("请清空文件后再删除目录"); + } + for (Result result : results) { + Item item = result.get(); + minioClient.removeObject( + RemoveObjectArgs.builder() + .bucket(bucketName) + .object(item.objectName()) + .build()); + } + } + + + public Boolean deleteFileViews(String path, Boolean recursive) throws IOException { + if (StringUtils.isBlank(path)) { + throw new IOException("请确认删除文件路径"); + } + Boolean success = null; + try { + path = path.substring(path.indexOf("/") + 1); + removeFile(minioProperties.getBucket(), path, recursive); + } catch (Exception e) { + throw new RuntimeException(e); + } + return success; + } + + public boolean deleteFile(File file) { + return file.delete(); + } + + + public String upload(String path, String folderName,MultipartFile file) { + String targetFile = null; + try { + // 上传一个空对象来模拟文件夹 + if (!StringUtils.isBlank(folderName)){ + targetFile = path + folderName + FileConstants.FILE_SEPARATOR; + ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); + minioClient.putObject( + PutObjectArgs.builder() + .bucket(minioProperties.getBucket()) + .object(targetFile) + .stream(bais, 0, -1) + .build()); + } + else { + targetFile= path +"/" + file.getOriginalFilename(); + uploadFile(minioProperties.getBucket(), file, targetFile, "application/octet-stream"); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return targetFile; + } + + + /** + * 使用MultipartFile进行文件上传 + * + * @param bucketName 存储桶 + * @param file 文件名 + * @param objectName 对象名 + * @param contentType 类型 + * @throws Exception + */ + public void uploadFile(String bucketName, MultipartFile file, String objectName, String contentType) throws Exception { + InputStream inputStream = file.getInputStream(); + try{ + minioClient.putObject( + PutObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .contentType(contentType) + .stream(inputStream, inputStream.available(), -1) + .build()); + }catch (Exception e){ + log.error("minio文件上传失败{}", e); + } + + } + + //获取路径下的文件夹文件列表 + public List getFileTree(String directoryName) { + List fileNodes = new ArrayList<>(); + ListObjectsArgs build; + + try { + if (StringUtils.isBlank(directoryName)) { + build = ListObjectsArgs.builder().bucket(minioProperties.getBucket()).recursive(false).build(); + } else { + build = ListObjectsArgs.builder().bucket(minioProperties.getBucket()).prefix(directoryName+"/").recursive(false).build(); + } + Iterable> results = minioClient.listObjects(build); + for (Result result : results) { + Item item = result.get(); + String itemName = item.objectName(); + boolean isDir = item.isDir(); + String size = FileUtil.readableFileSize(item.size()); + String relativePath = null; + String[] parts = null; + if (!StringUtils.isBlank(directoryName)){ + relativePath = itemName.substring(directoryName.length()); + parts = relativePath.split("/"); + } + else { + parts = itemName.split("/"); + } + String lastModifyTime = null; + DateTimeFormatter dateFormat =DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"); + if (!isDir){ + ZonedDateTime zonedDateTime = item.lastModified(); + lastModifyTime = zonedDateTime.format(dateFormat); + } + if (parts.length > 0) { + String nodeName = parts[0]; + int type = isDir ? 0 : 1; + itemName= isDir ? itemName.substring(0,itemName.lastIndexOf("/")) : itemName; + FileNode node = new FileNode(nodeName, type,size,lastModifyTime,"/"+itemName); + if (!fileNodes.contains(node)) { + fileNodes.add(node); + } + } + } + } catch (Exception e) { + log.error("minio获取树列表失败", e); + } + return fileNodes; + } + + public void readFileToStream(String path, OutputStream stream) { + + try ( GetObjectResponse res = minioClient.getObject( + GetObjectArgs.builder().bucket(minioProperties.getBucket()).object(path).build())){ + res.transferTo(stream); + } catch (Exception e) { + log.error("minio读取文件失败", e); + } + } + + public void download(String path, Path tempDir) { + + try (InputStream inputStream = minioClient.getObject(GetObjectArgs.builder() + .bucket(minioProperties.getBucket()) + .object(path) + .build())) { + + // 保存到临时文件夹 + File tempFile = tempDir.resolve(tempDir+path).toFile(); + FileUtil.writeFromStream(inputStream,tempFile); + } + catch (Exception ignored){ + + } + } + + + // 递归方式 计算文件的大小 + public long getTotalSizeOfFilesInDir(File file) { + if (file.isFile()) { + return file.length(); + } + + File[] children = file.listFiles(); + long total = 0; + if (children != null) { + + for (final File child : children) { + total += getTotalSizeOfFilesInDir(child); + } + } + return total; + } +} diff --git a/das/src/main/java/com/das/modules/fdr/service/impl/FaultRecorderServiceImpl.java b/das/src/main/java/com/das/modules/fdr/service/impl/FaultRecorderServiceImpl.java new file mode 100644 index 00000000..53d35767 --- /dev/null +++ b/das/src/main/java/com/das/modules/fdr/service/impl/FaultRecorderServiceImpl.java @@ -0,0 +1,74 @@ +package com.das.modules.fdr.service.impl; + +import com.das.common.constant.FileConstants; +import com.das.modules.fdr.domain.FileNode; +import com.das.modules.fdr.service.FaultRecorderService; +import com.das.modules.fdr.service.MinioViewsServcie; +import io.micrometer.common.util.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.OutputStream; +import java.nio.file.Path; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class FaultRecorderServiceImpl implements FaultRecorderService { + + @Autowired + private MinioViewsServcie minioViewsServcie; + + + @Override + public List getDirOrFileList(String name, String startTime, String endTime) { + List fileResult = new ArrayList<>(); + List monthsBetween = getMonthsBetween(startTime, endTime); + for (String item : monthsBetween){ + String directoryName = name + FileConstants.FILE_SEPARATOR + item.substring(0,item.indexOf("-")) + FileConstants.FILE_SEPARATOR + item.substring(item.indexOf("-")+1); + List fileTree = minioViewsServcie.getFileTree(directoryName); + fileResult.addAll(fileTree); + } + Comparator fileNodeComparator = Comparator.comparing(FileNode::getLastModified) + .thenComparing(FileNode::getName); + fileResult.sort(fileNodeComparator); + return fileResult; + } + + @Override + public String upload(String parent, String folderName, MultipartFile file) { + return minioViewsServcie.upload(parent, folderName, file); + } + + @Override + public void readFileToSteam(String path, OutputStream stream) { + minioViewsServcie.readFileToStream(path, stream); + } + + @Override + public void download(String path, Path tempDir) { + minioViewsServcie.download(path, tempDir); + } + + + private List getMonthsBetween(String startTime, String endTime) { + List months = new ArrayList<>(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + LocalDate start = LocalDate.parse(startTime + "-01", formatter); + LocalDate end = LocalDate.parse(endTime + "-01", formatter); + + DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM"); + + while (!start.isAfter(end)) { + months.add(start.format(monthFormatter)); + start = start.plusMonths(1); + } + return months; + } +} diff --git a/das/src/main/resources/application.yml b/das/src/main/resources/application.yml index 5dba9c52..3988874e 100644 --- a/das/src/main/resources/application.yml +++ b/das/src/main/resources/application.yml @@ -98,4 +98,10 @@ logging: tdengine: password: taosdata url: jdbc:TAOS-RS://192.168.109.160:6041/das - username: root \ No newline at end of file + username: root + +minio: + url: http://192.168.109.187:9000 + bucket: das + accessKey: das + secretKey: zaq12WSX \ No newline at end of file