From 94a10f7915ba7e5c896590a997bd91fe92e5bbc5 Mon Sep 17 00:00:00 2001 From: vran Date: Tue, 15 Mar 2022 13:36:52 +0800 Subject: [PATCH] refactor: redesign file export api --- .../com/databasir/api/DocumentController.java | 37 ++-- build.gradle | 3 + core/build.gradle | 1 + .../generator/DocumentFileGenerator.java | 27 +++ .../document/generator/DocumentFileType.java | 13 ++ .../generator/ExcelDocumentFileGenerator.java | 22 +++ .../MarkdownDocumentFileGenerator.java | 165 ++++++++++++++++++ .../document/service/DocumentService.java | 102 +++-------- 8 files changed, 267 insertions(+), 103 deletions(-) create mode 100644 core/src/main/java/com/databasir/core/domain/document/generator/DocumentFileGenerator.java create mode 100644 core/src/main/java/com/databasir/core/domain/document/generator/DocumentFileType.java create mode 100644 core/src/main/java/com/databasir/core/domain/document/generator/ExcelDocumentFileGenerator.java create mode 100644 core/src/main/java/com/databasir/core/domain/document/generator/MarkdownDocumentFileGenerator.java diff --git a/api/src/main/java/com/databasir/api/DocumentController.java b/api/src/main/java/com/databasir/api/DocumentController.java index 7a1b409..52f78e2 100644 --- a/api/src/main/java/com/databasir/api/DocumentController.java +++ b/api/src/main/java/com/databasir/api/DocumentController.java @@ -1,11 +1,11 @@ package com.databasir.api; import com.databasir.common.JsonData; -import com.databasir.common.SystemException; import com.databasir.core.domain.document.data.DatabaseDocumentResponse; import com.databasir.core.domain.document.data.DatabaseDocumentSimpleResponse; import com.databasir.core.domain.document.data.DatabaseDocumentVersionResponse; import com.databasir.core.domain.document.data.TableDocumentResponse; +import com.databasir.core.domain.document.generator.DocumentFileType; import com.databasir.core.domain.document.service.DocumentService; import com.databasir.core.domain.log.annotation.Operation; import lombok.RequiredArgsConstructor; @@ -20,13 +20,8 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; -import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; -import java.util.UUID; import static org.springframework.data.domain.Sort.Direction.DESC; @@ -62,24 +57,18 @@ public class DocumentController { @GetMapping(Routes.Document.EXPORT) public ResponseEntity getDocumentFiles(@PathVariable Integer projectId, - @RequestParam(required = false) Long version) { - String data = documentService.toMarkdown(projectId, version).get(); - try { - Path path = Files.writeString(Paths.get(UUID.randomUUID().toString() + ".md"), data, - StandardCharsets.UTF_8); - HttpHeaders headers = new HttpHeaders(); - headers.setContentDisposition(ContentDisposition.attachment() - .filename("demo.md", StandardCharsets.UTF_8) - .build()); - byte[] bytes = Files.readAllBytes(path); - Files.deleteIfExists(path); - headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); - return ResponseEntity.ok() - .headers(headers) - .body(out -> out.write(bytes)); - } catch (IOException e) { - throw new SystemException("System error"); - } + @RequestParam(required = false) + Long version, + @RequestParam DocumentFileType fileType) { + HttpHeaders headers = new HttpHeaders(); + String fileName = "project[" + projectId + "]." + fileType.getFileExtension(); + headers.setContentDisposition(ContentDisposition.attachment() + .filename("demo.md", StandardCharsets.UTF_8) + .build()); + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); + return ResponseEntity.ok() + .headers(headers) + .body(out -> documentService.export(projectId, version, fileType, out)); } @GetMapping(Routes.Document.GET_SIMPLE_ONE) diff --git a/build.gradle b/build.gradle index 78dfd38..ff76286 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,7 @@ subprojects { postgresqlConnectorVersion = '42.3.1' hikariVersion = '5.0.0' jacksonVersion = '2.13.1' + easyExcelVersion = '3.0.5' } dependencies { @@ -47,6 +48,8 @@ subprojects { annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" implementation "org.slf4j:slf4j-api:${slf4jVersion}" + implementation "com.alibaba:easyexcel:${easyExcelVersion}" + } test { diff --git a/core/build.gradle b/core/build.gradle index 58cd16d..bb87958 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -30,6 +30,7 @@ dependencies { implementation 'com.auth0:java-jwt:3.18.3' implementation 'org.commonmark:commonmark:0.18.1' implementation 'org.freemarker:freemarker:2.3.31' + implementation 'com.alibaba:easyexcel' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-jackson:2.9.0' diff --git a/core/src/main/java/com/databasir/core/domain/document/generator/DocumentFileGenerator.java b/core/src/main/java/com/databasir/core/domain/document/generator/DocumentFileGenerator.java new file mode 100644 index 0000000..a4e83cb --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/generator/DocumentFileGenerator.java @@ -0,0 +1,27 @@ +package com.databasir.core.domain.document.generator; + +import com.databasir.core.domain.document.data.DatabaseDocumentResponse; +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; + +import java.io.OutputStream; + +public interface DocumentFileGenerator { + + boolean support(DocumentFileType type); + + void generate(DocumentFileGenerateContext context, OutputStream outputStream); + + @Getter + @Builder + class DocumentFileGenerateContext { + + @NonNull + private DocumentFileType documentFileType; + + @NonNull + private DatabaseDocumentResponse databaseDocument; + + } +} diff --git a/core/src/main/java/com/databasir/core/domain/document/generator/DocumentFileType.java b/core/src/main/java/com/databasir/core/domain/document/generator/DocumentFileType.java new file mode 100644 index 0000000..94e345f --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/generator/DocumentFileType.java @@ -0,0 +1,13 @@ +package com.databasir.core.domain.document.generator; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum DocumentFileType { + + MARKDOWN("md"), EXCEL("xlsx"); + + private String fileExtension; +} diff --git a/core/src/main/java/com/databasir/core/domain/document/generator/ExcelDocumentFileGenerator.java b/core/src/main/java/com/databasir/core/domain/document/generator/ExcelDocumentFileGenerator.java new file mode 100644 index 0000000..168420a --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/generator/ExcelDocumentFileGenerator.java @@ -0,0 +1,22 @@ +package com.databasir.core.domain.document.generator; + +import org.springframework.stereotype.Component; + +import java.io.OutputStream; + +@Component +public class ExcelDocumentFileGenerator implements DocumentFileGenerator { + + @Override + public boolean support(DocumentFileType type) { + return type == DocumentFileType.EXCEL; + } + + @Override + public void generate(DocumentFileGenerateContext context, OutputStream outputStream) { + + } + + private void buildTableWithSheet() { + } +} diff --git a/core/src/main/java/com/databasir/core/domain/document/generator/MarkdownDocumentFileGenerator.java b/core/src/main/java/com/databasir/core/domain/document/generator/MarkdownDocumentFileGenerator.java new file mode 100644 index 0000000..d88e617 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/generator/MarkdownDocumentFileGenerator.java @@ -0,0 +1,165 @@ +package com.databasir.core.domain.document.generator; + +import com.databasir.common.SystemException; +import com.databasir.core.domain.document.data.DatabaseDocumentResponse; +import com.databasir.core.domain.document.data.TableDocumentResponse; +import com.databasir.core.render.markdown.MarkdownBuilder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Function; + +@Component +@Slf4j +public class MarkdownDocumentFileGenerator implements DocumentFileGenerator { + + @Override + public boolean support(DocumentFileType type) { + return type == DocumentFileType.MARKDOWN; + } + + @Override + public void generate(DocumentFileGenerateContext context, OutputStream outputStream) { + String fileName = context.getDatabaseDocument().getDatabaseName() + "-" + UUID.randomUUID().toString(); + String data = markdownData(context); + Path tempFile = null; + try { + tempFile = Files.createTempFile(fileName, ".md"); + Path path = Files.writeString(tempFile, data, StandardCharsets.UTF_8); + byte[] bytes = Files.readAllBytes(path); + outputStream.write(bytes); + } catch (IOException e) { + if (tempFile != null) { + try { + Files.deleteIfExists(tempFile); + } catch (IOException ex) { + log.warn("delete temp file error", ex); + } + } + throw new SystemException("System error"); + } + } + + private String markdownData(DocumentFileGenerateContext context) { + DatabaseDocumentResponse doc = context.getDatabaseDocument(); + MarkdownBuilder builder = MarkdownBuilder.builder(); + builder.primaryTitle(doc.getDatabaseName()); + // overview + overviewBuild(builder, doc); + // tables + doc.getTables().forEach(table -> tableBuild(builder, table)); + return builder.build(); + } + + private void overviewBuild(MarkdownBuilder builder, DatabaseDocumentResponse doc) { + builder.secondTitle("overview"); + List> overviewContent = new ArrayList<>(); + for (int i = 0; i < doc.getTables().size(); i++) { + TableDocumentResponse table = doc.getTables().get(i); + overviewContent.add(List.of((i + 1) + "", table.getName(), table.getType(), + table.getComment())); + } + builder.table(List.of("", "表名", "类型", "备注"), overviewContent); + } + + private void tableBuild(MarkdownBuilder builder, TableDocumentResponse table) { + builder.secondTitle(table.getName()); + columnBuild(builder, table); + indexBuild(builder, table); + foreignKeyBuild(builder, table); + triggerBuild(builder, table); + } + + private void columnBuild(MarkdownBuilder builder, TableDocumentResponse table) { + Function + columnDefaultValueMapping = column -> { + if (Objects.equals(column.getNullable(), "YES")) { + return Objects.requireNonNullElse(column.getDefaultValue(), "null"); + } else { + return Objects.requireNonNullElse(column.getDefaultValue(), ""); + } + }; + builder.thirdTitle("Columns"); + List> columnContent = new ArrayList<>(); + for (int i = 0; i < table.getColumns().size(); i++) { + var column = table.getColumns().get(i); + String type; + if (column.getDecimalDigits() == null || column.getDecimalDigits() == 0) { + type = column.getType() + "(" + column.getSize() + ")"; + } else { + type = column.getType() + "(" + column.getSize() + "," + column.getDecimalDigits() + ")"; + } + columnContent.add(List.of((i + 1) + "", + column.getName(), + type, + column.getIsPrimaryKey() ? "YES" : "NO", + column.getNullable(), + column.getAutoIncrement(), + columnDefaultValueMapping.apply(column), + column.getComment())); + } + builder.table(List.of("", "名称", "类型", "是否为主键", "可为空", "自增", "默认值", "备注"), + columnContent); + } + + + private void indexBuild(MarkdownBuilder builder, TableDocumentResponse table) { + builder.thirdTitle("Indexes"); + List> indexContent = new ArrayList<>(); + for (int i = 0; i < table.getIndexes().size(); i++) { + var index = table.getIndexes().get(i); + String columnNames = String.join(", ", index.getColumnNames()); + String isUnique = index.getIsUnique() ? "YES" : "NO"; + indexContent.add(List.of((i + 1) + "", index.getName(), isUnique, columnNames)); + } + builder.table(List.of("", "名称", "是否唯一", "关联列"), indexContent); + + } + + private void foreignKeyBuild(MarkdownBuilder builder, TableDocumentResponse table) { + if (!table.getForeignKeys().isEmpty()) { + List> foreignKeys = new ArrayList<>(); + builder.thirdTitle("Foreign Keys"); + for (int i = 0; i < table.getForeignKeys().size(); i++) { + TableDocumentResponse.ForeignKeyDocumentResponse fk = table.getForeignKeys().get(i); + List item = List.of( + (i + 1) + "", + fk.getFkName(), fk.getFkColumnName(), + fk.getPkName(), fk.getPkTableName(), fk.getPkColumnName(), + fk.getUpdateRule(), fk.getDeleteRule() + ); + foreignKeys.add(item); + } + builder.table( + List.of("", "FK Name", "FK Column", "PK Name", "PK Table", "PK Column", + "Update Rule", "Delete Rule"), + foreignKeys + ); + } + } + + private void triggerBuild(MarkdownBuilder builder, TableDocumentResponse table) { + if (!table.getTriggers().isEmpty()) { + List> triggerContent = new ArrayList<>(); + for (int i = 0; i < table.getTriggers().size(); i++) { + var trigger = table.getTriggers().get(i); + triggerContent.add(List.of((i + 1) + "", + trigger.getName(), + trigger.getTiming(), + trigger.getManipulation(), + trigger.getStatement())); + } + builder.thirdTitle("Triggers"); + builder.table(List.of("", "名称", "timing", "manipulation", "statement"), triggerContent); + } + } +} diff --git a/core/src/main/java/com/databasir/core/domain/document/service/DocumentService.java b/core/src/main/java/com/databasir/core/domain/document/service/DocumentService.java index af5b4c3..2af3c1b 100644 --- a/core/src/main/java/com/databasir/core/domain/document/service/DocumentService.java +++ b/core/src/main/java/com/databasir/core/domain/document/service/DocumentService.java @@ -10,10 +10,11 @@ import com.databasir.core.domain.document.data.DatabaseDocumentResponse; import com.databasir.core.domain.document.data.DatabaseDocumentSimpleResponse; import com.databasir.core.domain.document.data.DatabaseDocumentVersionResponse; import com.databasir.core.domain.document.data.TableDocumentResponse; +import com.databasir.core.domain.document.generator.DocumentFileGenerator; +import com.databasir.core.domain.document.generator.DocumentFileType; import com.databasir.core.infrastructure.connection.DatabaseConnectionService; import com.databasir.core.infrastructure.converter.JsonConverter; import com.databasir.core.meta.data.DatabaseMeta; -import com.databasir.core.render.markdown.MarkdownBuilder; import com.databasir.dao.impl.*; import com.databasir.dao.tables.pojos.*; import com.databasir.dao.value.DocumentDiscussionCountPojo; @@ -26,9 +27,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; +import java.io.OutputStream; import java.sql.Connection; -import java.util.*; -import java.util.function.Function; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; @Service @@ -72,6 +76,8 @@ public class DocumentService { private final JsonConverter jsonConverter; + private final List documentFileGenerators; + @Transactional public void syncByProjectId(Integer projectId) { projectDao.selectOptionalById(projectId) @@ -303,82 +309,20 @@ public class DocumentService { .collect(Collectors.toList()); } - public Optional toMarkdown(Integer projectId, Long version) { - return getOneByProjectId(projectId, version) - .map(doc -> { - MarkdownBuilder builder = MarkdownBuilder.builder(); - builder.primaryTitle(doc.getDatabaseName()); - // overview - builder.secondTitle("overview"); - List> overviewContent = new ArrayList<>(); - for (int i = 0; i < doc.getTables().size(); i++) { - TableDocumentResponse table = doc.getTables().get(i); - overviewContent.add(List.of((i + 1) + "", table.getName(), table.getType(), - table.getComment())); - } - builder.table(List.of("", "表名", "类型", "备注"), overviewContent); - - Function - columnDefaultValueMapping = column -> { - if (Objects.equals(column.getNullable(), "YES")) { - return Objects.requireNonNullElse(column.getDefaultValue(), "null"); - } else { - return Objects.requireNonNullElse(column.getDefaultValue(), ""); - } - }; - // tables - doc.getTables().forEach(table -> { - builder.secondTitle(table.getName()); - - // columns - List> columnContent = new ArrayList<>(); - for (int i = 0; i < table.getColumns().size(); i++) { - var column = table.getColumns().get(i); - String type; - if (column.getDecimalDigits() == null || column.getDecimalDigits() == 0) { - type = table.getType() + "(" + column.getSize() + ")"; - } else { - type = table.getType() + "(" + column.getSize() + "," + column.getDecimalDigits() + ")"; - } - columnContent.add(List.of((i + 1) + "", - column.getName(), - type, - column.getIsPrimaryKey() ? "YES" : "NO", - column.getNullable(), - column.getAutoIncrement(), - columnDefaultValueMapping.apply(column), - column.getComment())); - } - builder.thirdTitle("columns"); - builder.table(List.of("", "名称", "类型", "是否为主键", "可为空", "自增", "默认值", "备注"), - columnContent); - - // indexes - List> indexContent = new ArrayList<>(); - for (int i = 0; i < table.getIndexes().size(); i++) { - var index = table.getIndexes().get(i); - String columnNames = String.join(", ", index.getColumnNames()); - String isUnique = index.getIsUnique() ? "YES" : "NO"; - indexContent.add(List.of((i + 1) + "", index.getName(), isUnique, columnNames)); - } - builder.thirdTitle("indexes"); - builder.table(List.of("", "名称", "是否唯一", "关联列"), indexContent); - - if (!table.getTriggers().isEmpty()) { - List> triggerContent = new ArrayList<>(); - for (int i = 0; i < table.getTriggers().size(); i++) { - var trigger = table.getTriggers().get(i); - triggerContent.add(List.of((i + 1) + "", - trigger.getName(), - trigger.getTiming(), - trigger.getManipulation(), - trigger.getStatement())); - } - builder.thirdTitle("triggers"); - builder.table(List.of("", "名称", "timing", "manipulation", "statement"), triggerContent); - } - }); - return builder.build(); + public void export(Integer projectId, + Long version, + DocumentFileType type, + OutputStream out) { + getOneByProjectId(projectId, version) + .ifPresent(doc -> { + var context = DocumentFileGenerator.DocumentFileGenerateContext.builder() + .documentFileType(type) + .databaseDocument(doc) + .build(); + documentFileGenerators.stream() + .filter(g -> g.support(type)) + .findFirst() + .ifPresent(generator -> generator.generate(context, out)); }); } }