refactor: redesign file export api
This commit is contained in:
parent
826131fed3
commit
94a10f7915
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue