refactor: redesign file export api

This commit is contained in:
vran 2022-03-15 13:36:52 +08:00
parent 826131fed3
commit 94a10f7915
8 changed files with 267 additions and 103 deletions

View File

@ -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<StreamingResponseBody> 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)

View File

@ -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 {

View File

@ -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'

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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() {
}
}

View File

@ -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<List<String>> 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<TableDocumentResponse.ColumnDocumentResponse, String>
columnDefaultValueMapping = column -> {
if (Objects.equals(column.getNullable(), "YES")) {
return Objects.requireNonNullElse(column.getDefaultValue(), "null");
} else {
return Objects.requireNonNullElse(column.getDefaultValue(), "");
}
};
builder.thirdTitle("Columns");
List<List<String>> 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<List<String>> 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<List<String>> foreignKeys = new ArrayList<>();
builder.thirdTitle("Foreign Keys");
for (int i = 0; i < table.getForeignKeys().size(); i++) {
TableDocumentResponse.ForeignKeyDocumentResponse fk = table.getForeignKeys().get(i);
List<String> 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<List<String>> 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);
}
}
}

View File

@ -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<DocumentFileGenerator> documentFileGenerators;
@Transactional
public void syncByProjectId(Integer projectId) {
projectDao.selectOptionalById(projectId)
@ -303,82 +309,20 @@ public class DocumentService {
.collect(Collectors.toList());
}
public Optional<String> toMarkdown(Integer projectId, Long version) {
return getOneByProjectId(projectId, version)
.map(doc -> {
MarkdownBuilder builder = MarkdownBuilder.builder();
builder.primaryTitle(doc.getDatabaseName());
// overview
builder.secondTitle("overview");
List<List<String>> 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<TableDocumentResponse.ColumnDocumentResponse, String>
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<List<String>> 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<List<String>> 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<List<String>> 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));
});
}
}