feat: add document export api
This commit is contained in:
parent
b8916ea8bf
commit
717370d9e9
|
@ -1,6 +1,7 @@
|
||||||
package com.databasir.api;
|
package com.databasir.api;
|
||||||
|
|
||||||
import com.databasir.common.JsonData;
|
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.DatabaseDocumentResponse;
|
||||||
import com.databasir.core.domain.document.data.DatabaseDocumentVersionResponse;
|
import com.databasir.core.domain.document.data.DatabaseDocumentVersionResponse;
|
||||||
import com.databasir.core.domain.document.service.DocumentService;
|
import com.databasir.core.domain.document.service.DocumentService;
|
||||||
|
@ -9,13 +10,24 @@ import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.data.web.PageableDefault;
|
import org.springframework.data.web.PageableDefault;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.http.ContentDisposition;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
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.UUID;
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Validated
|
@Validated
|
||||||
|
@RestController
|
||||||
public class DocumentController {
|
public class DocumentController {
|
||||||
|
|
||||||
private final DocumentService documentService;
|
private final DocumentService documentService;
|
||||||
|
@ -41,4 +53,25 @@ public class DocumentController {
|
||||||
return JsonData.ok(documentService.getVersionsBySchemaSourceId(projectId, page));
|
return JsonData.ok(documentService.getVersionsBySchemaSourceId(projectId, page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,8 @@ public interface Routes {
|
||||||
String SYNC_ONE = BASE + "/projects/{projectId}/documents";
|
String SYNC_ONE = BASE + "/projects/{projectId}/documents";
|
||||||
|
|
||||||
String LIST_VERSIONS = BASE + "/projects/{projectId}/document_versions";
|
String LIST_VERSIONS = BASE + "/projects/{projectId}/document_versions";
|
||||||
|
|
||||||
|
String EXPORT = BASE + "/projects/{projectId}/document_files";
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DocumentRemark {
|
interface DocumentRemark {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import com.databasir.core.domain.document.data.DatabaseDocumentVersionResponse;
|
||||||
import com.databasir.core.infrastructure.connection.DatabaseConnectionService;
|
import com.databasir.core.infrastructure.connection.DatabaseConnectionService;
|
||||||
import com.databasir.core.infrastructure.converter.JsonConverter;
|
import com.databasir.core.infrastructure.converter.JsonConverter;
|
||||||
import com.databasir.core.meta.data.DatabaseMeta;
|
import com.databasir.core.meta.data.DatabaseMeta;
|
||||||
|
import com.databasir.core.render.markdown.MarkdownBuilder;
|
||||||
import com.databasir.dao.impl.*;
|
import com.databasir.dao.impl.*;
|
||||||
import com.databasir.dao.tables.pojos.*;
|
import com.databasir.dao.tables.pojos.*;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
@ -21,10 +22,8 @@ import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.util.Collections;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.function.Function;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -183,4 +182,81 @@ public class DocumentService {
|
||||||
.build()))
|
.build()))
|
||||||
.orElseGet(Page::empty);
|
.orElseGet(Page::empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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++) {
|
||||||
|
DatabaseDocumentResponse.TableDocumentResponse table = doc.getTables().get(i);
|
||||||
|
overviewContent.add(List.of((i + 1) + "", table.getName(), table.getType(), table.getComment()));
|
||||||
|
}
|
||||||
|
builder.table(List.of("", "表名", "类型", "备注"), overviewContent);
|
||||||
|
|
||||||
|
Function<DatabaseDocumentResponse.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();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,45 @@
|
||||||
# ${meta.databaseName}
|
# ${meta.databaseName}
|
||||||
|
| | |
|
||||||
|
| --------------- | ---- |
|
||||||
|
| Database name | |
|
||||||
|
| Product name | |
|
||||||
|
| Product version | |
|
||||||
|
|
||||||
| seq | name | comment |
|
## Overview
|
||||||
| ---- | --------------- | ------ |
|
| | name | type | comment |
|
||||||
|
| ---- | --------------- | ------ | ------ |
|
||||||
<#list meta.tables as table>
|
<#list meta.tables as table>
|
||||||
| ${table_index+1} | [${table.name}](#${table.name}) | ${table.comment!'N/A'} |
|
| ${table_index+1} | [${table.name}](#${table.name}) | ${table.type} | ${table.comment!'N/A'} |
|
||||||
</#list>
|
</#list>
|
||||||
|
|
||||||
<#if config.renderTables>
|
<#if config.renderTables>
|
||||||
<#list meta.tables as table>
|
<#list meta.tables as table>
|
||||||
## ${table.name}
|
## ${table.name}
|
||||||
|
|
||||||
<#if config.renderColumns>
|
<#if config.renderColumns>
|
||||||
### Columns
|
### Columns
|
||||||
|
|
||||||
| seq | name | type | nullable | auto increment| default | comment |
|
| | name | type | primary Key | nullable | auto increment| default | comment |
|
||||||
| ---- | ------ | ------ | ------ | -------- | ------ | ------ |
|
| --- | ---- | ---- | ----------- | -------- | ------------- | ------- | ------- |
|
||||||
<#list table.columns as column>
|
<#list table.columns as column>
|
||||||
| ${column_index+1} | ${column.name} | ${column.type} | ${column.isNullable?then('YES','NO')} | ${column.isAutoIncrement?then('YES', 'NO')} | ${column.isNullable?then(column.defaultValue!'NULL', column.defaultValue!'')} | ${column.comment!''} |
|
| ${column_index+1} | ${column.name} | ${column.type} | ${column.isPrimaryKey?then('YES','NO')} | ${column.nullable } | ${column.autoIncrement} | ${column.defaultValue!'NULL'} | ${column.comment!''} |
|
||||||
</#list>
|
</#list>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
<#if config.renderIndexes>
|
<#if config.renderIndexes>
|
||||||
### Indexes
|
### Indexes
|
||||||
|
|
||||||
| seq | name | unique | primary key | columns |
|
| | name | unique | columns |
|
||||||
| ---- | ---- | -------- | -------- | ------ |
|
| --- | ---- | ------ | ------- |
|
||||||
<#list table.indexes as index>
|
<#list table.indexes as index>
|
||||||
| ${index_index+1} | ${index.name} | ${index.isUniqueKey?then('YES', 'NO')} | ${index.isPrimaryKey?then('YES','NO')} | ${index.columnNames?join(', ')} |
|
| ${index_index+1} | ${index.name} | ${index.isUniqueKey?then('YES', 'NO')} | ${index.columnNames?join(', ')} |
|
||||||
</#list>
|
</#list>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
<#if config.renderTriggers>
|
<#if config.renderTriggers>
|
||||||
### Triggers
|
### Triggers
|
||||||
|
|
||||||
| seq | name | timing | statement | created |
|
| | name | timing | statement | created |
|
||||||
| ---- | ---- | -------- | --------- | -------- |
|
| --- | ---- | ------ | --------- | ------- |
|
||||||
<#list table.triggers as trigger>
|
<#list table.triggers as trigger>
|
||||||
| ${trigger_index} | ${trigger.name} | ${trigger.timing + " " + trigger.manipulation } | ${trigger.statement?replace("\n", "<br>")?replace("\r", " ")} | ${trigger.createAt} |
|
| ${trigger_index} | ${trigger.name} | ${trigger.timing + " " + trigger.manipulation } | ${trigger.statement?replace("\n", "<br>")?replace("\r", " ")} | ${trigger.createAt} |
|
||||||
</#list>
|
</#list>
|
||||||
|
|
|
@ -13,10 +13,10 @@ public class App {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRenderAsMarkdown() throws SQLException, ClassNotFoundException {
|
public void testRenderAsMarkdown() throws SQLException, ClassNotFoundException {
|
||||||
try (FileOutputStream out = new FileOutputStream("user.md")) {
|
try (FileOutputStream out = new FileOutputStream("demo.md")) {
|
||||||
Connection connection = getJdbcConnection();
|
Connection connection = getJdbcConnection();
|
||||||
Databasir databasir = Databasir.of();
|
Databasir databasir = Databasir.of();
|
||||||
DatabaseMeta doc = databasir.get(connection, "user").orElseThrow();
|
DatabaseMeta doc = databasir.get(connection, "demo").orElseThrow();
|
||||||
databasir.renderAsMarkdown(doc, out);
|
databasir.renderAsMarkdown(doc, out);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
|
|
Loading…
Reference in New Issue