feat: use plantuml to export er diagram
This commit is contained in:
parent
06f5044e39
commit
6c5965f466
|
@ -22,9 +22,10 @@ import org.springframework.web.bind.annotation.*;
|
|||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
|
@ -75,9 +76,10 @@ public class DocumentController {
|
|||
Long version,
|
||||
@RequestParam DocumentFileType fileType) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
String fileName = "project[" + projectId + "]." + fileType.getFileExtension();
|
||||
String projectName = projectService.getOne(projectId).getName();
|
||||
String fileName = projectName + "." + fileType.getFileExtension();
|
||||
headers.setContentDisposition(ContentDisposition.attachment()
|
||||
.filename("demo.md", StandardCharsets.UTF_8)
|
||||
.filename(fileName)
|
||||
.build());
|
||||
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||
return ResponseEntity.ok()
|
||||
|
@ -85,6 +87,14 @@ public class DocumentController {
|
|||
.body(out -> documentService.export(projectId, version, fileType, out));
|
||||
}
|
||||
|
||||
@GetMapping(Routes.Document.EXPORT_TYPES)
|
||||
public JsonData<List<DocumentFileTypeResponse>> getDocumentFileTypes() {
|
||||
List<DocumentFileTypeResponse> types = Arrays.stream(DocumentFileType.values())
|
||||
.map(type -> new DocumentFileTypeResponse(type.getName(), type.getFileExtension(), type))
|
||||
.collect(Collectors.toList());
|
||||
return JsonData.ok(types);
|
||||
}
|
||||
|
||||
@GetMapping(Routes.Document.GET_SIMPLE_ONE)
|
||||
@Operation(summary = "获取文档(无详情信息)")
|
||||
public JsonData<DatabaseDocumentSimpleResponse> getSimpleByProjectId(@PathVariable Integer projectId,
|
||||
|
|
|
@ -92,6 +92,8 @@ public interface Routes {
|
|||
|
||||
String EXPORT = BASE + "/projects/{projectId}/document_files";
|
||||
|
||||
String EXPORT_TYPES = BASE + "/document_file_types";
|
||||
|
||||
String LIST_TABLES = BASE + "/projects/{projectId}/tables";
|
||||
}
|
||||
|
||||
|
|
|
@ -11,15 +11,30 @@ import org.springframework.context.annotation.Bean;
|
|||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.web.config.EnableSpringDataWebSupport;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Configuration
|
||||
@EnableSpringDataWebSupport
|
||||
public class WebConfig extends WebMvcConfigurerAdapter {
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
|
||||
ThreadPoolTaskExecutor mvcExecutor = new ThreadPoolTaskExecutor();
|
||||
int maxCorePoolSize = 64;
|
||||
int availableProcessorCount = Runtime.getRuntime().availableProcessors();
|
||||
int corePoolSize = Math.min(maxCorePoolSize, availableProcessorCount);
|
||||
mvcExecutor.setCorePoolSize(corePoolSize);
|
||||
mvcExecutor.setMaxPoolSize(maxCorePoolSize);
|
||||
mvcExecutor.setThreadNamePrefix("mvc-executor-");
|
||||
mvcExecutor.initialize();
|
||||
configurer.setTaskExecutor(mvcExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
server.port=8080
|
||||
logging.level.org.jooq=INFO
|
||||
logging.level.com.databasir.core.domain.document.generator=debug
|
||||
spring.jooq.sql-dialect=mysql
|
||||
springdoc.swagger-ui.path=/open-api.html
|
||||
# flyway
|
||||
|
|
|
@ -16,4 +16,5 @@ spring.flyway.locations=classpath:db/migration
|
|||
databasir.db.driver-directory=drivers
|
||||
databasir.jwt.secret=${DATABASIR_JWT_SECRET:${random.uuid}}
|
||||
# api doc
|
||||
springdoc.api-docs.enabled=false
|
||||
springdoc.api-docs.enabled=false
|
||||
spring.mvc.async.request-timeout=3600000
|
|
@ -9,6 +9,8 @@ dependencies {
|
|||
implementation project(":dao")
|
||||
implementation project(":meta")
|
||||
|
||||
implementation 'net.sourceforge.plantuml:plantuml:1.2022.5'
|
||||
|
||||
// spring boot
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package com.databasir.core.domain.document.data;
|
||||
|
||||
import com.databasir.core.domain.document.generator.DocumentFileType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class DocumentFileTypeResponse {
|
||||
|
||||
private String name;
|
||||
|
||||
private String fileExtension;
|
||||
|
||||
private DocumentFileType type;
|
||||
|
||||
}
|
|
@ -7,7 +7,15 @@ import lombok.Getter;
|
|||
@Getter
|
||||
public enum DocumentFileType {
|
||||
|
||||
MARKDOWN("md"), EXCEL("xlsx");
|
||||
MARKDOWN("md", "Markdown"),
|
||||
|
||||
PLANT_UML_ER_SVG("svg", "UML SVG"),
|
||||
|
||||
PLANT_UML_ER_PNG("png", "UML PNG"),
|
||||
;
|
||||
|
||||
private String fileExtension;
|
||||
|
||||
private String name;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
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,134 @@
|
|||
package com.databasir.core.domain.document.generator.plantuml;
|
||||
|
||||
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.domain.document.generator.DocumentFileGenerator;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sourceforge.plantuml.FileFormatOption;
|
||||
import net.sourceforge.plantuml.SourceStringReader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public abstract class BasePlantUmlFileGenerator implements DocumentFileGenerator {
|
||||
|
||||
public static final String LINE = "\r\n";
|
||||
|
||||
@Override
|
||||
public void generate(DocumentFileGenerateContext context, OutputStream outputStream) {
|
||||
String dsl = new ErDsl(context).toDsl();
|
||||
try {
|
||||
new SourceStringReader(dsl).outputImage(outputStream, fileFormatOption());
|
||||
} catch (IOException e) {
|
||||
log.error("export plantuml error", e);
|
||||
throw new SystemException("System error");
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract FileFormatOption fileFormatOption();
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ErDsl {
|
||||
|
||||
private final DocumentFileGenerateContext context;
|
||||
|
||||
private Set<String> foreignKeyRelations = new HashSet<>(16);
|
||||
|
||||
public String toDsl() {
|
||||
DatabaseDocumentResponse databaseDocument = context.getDatabaseDocument();
|
||||
StringBuilder dslBuilder = new StringBuilder(1024);
|
||||
dslBuilder.append("@startuml").append(LINE);
|
||||
|
||||
// configuration
|
||||
dslBuilder.append("' hide the spot").append(LINE);
|
||||
dslBuilder.append("hide circle").append(LINE);
|
||||
|
||||
// entities
|
||||
String entities = databaseDocument.getTables()
|
||||
.stream()
|
||||
.map(table -> toErDsl(table))
|
||||
.collect(Collectors.joining(LINE));
|
||||
dslBuilder.append(entities);
|
||||
|
||||
// relation
|
||||
dslBuilder.append(LINE);
|
||||
String relations = foreignKeyRelations.stream()
|
||||
.collect(Collectors.joining(LINE));
|
||||
dslBuilder.append(relations);
|
||||
dslBuilder.append(LINE);
|
||||
|
||||
dslBuilder.append("@enduml");
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("------------------------------");
|
||||
log.debug(dslBuilder.toString());
|
||||
log.debug("------------------------------");
|
||||
}
|
||||
return dslBuilder.toString();
|
||||
}
|
||||
|
||||
private String toErDsl(TableDocumentResponse table) {
|
||||
StringBuilder dslBuilder = new StringBuilder(1024);
|
||||
dslBuilder.append("entity ").append(table.getName())
|
||||
.append(" {");
|
||||
table.getColumns()
|
||||
.stream()
|
||||
.filter(TableDocumentResponse.ColumnDocumentResponse::getIsPrimaryKey)
|
||||
.forEach(primaryCol -> {
|
||||
dslBuilder.append(LINE);
|
||||
dslBuilder.append("*")
|
||||
.append(primaryCol.getName())
|
||||
.append(" : ")
|
||||
.append(primaryCol.getType())
|
||||
.append("(")
|
||||
.append(primaryCol.getSize())
|
||||
.append(")")
|
||||
.append(" <PK> ");
|
||||
dslBuilder.append(LINE);
|
||||
dslBuilder.append("--");
|
||||
});
|
||||
table.getColumns()
|
||||
.stream()
|
||||
.filter(col -> !col.getIsPrimaryKey())
|
||||
.forEach(col -> {
|
||||
dslBuilder.append(LINE);
|
||||
if ("NO".equalsIgnoreCase(col.getNullable())) {
|
||||
dslBuilder.append("*");
|
||||
}
|
||||
dslBuilder.append(col.getName())
|
||||
.append(" : ")
|
||||
.append(col.getType())
|
||||
.append("(")
|
||||
.append(col.getSize())
|
||||
.append(")");
|
||||
if (col.getComment() != null && !"".equals(col.getComment().trim())) {
|
||||
dslBuilder.append(" /* ").append(col.getComment()).append(" */");
|
||||
}
|
||||
dslBuilder.append(LINE);
|
||||
});
|
||||
dslBuilder.append("}");
|
||||
dslBuilder.append(LINE);
|
||||
|
||||
table.getForeignKeys().forEach(fk -> {
|
||||
String fkTableName = fk.getFkTableName();
|
||||
String fkColumnName = fk.getFkColumnName();
|
||||
String pkTableName = fk.getPkTableName();
|
||||
String pkColumnName = fk.getPkColumnName();
|
||||
StringBuilder relationBuilder = new StringBuilder();
|
||||
relationBuilder.append(fkTableName).append("::").append(fkColumnName)
|
||||
.append(" --> ")
|
||||
.append(pkTableName).append("::").append(pkColumnName)
|
||||
.append(" : ")
|
||||
.append(Objects.requireNonNullElse(fk.getFkName(), ""));
|
||||
foreignKeyRelations.add(relationBuilder.toString());
|
||||
});
|
||||
return dslBuilder.toString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.databasir.core.domain.document.generator.plantuml;
|
||||
|
||||
import com.databasir.core.domain.document.generator.DocumentFileType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sourceforge.plantuml.FileFormat;
|
||||
import net.sourceforge.plantuml.FileFormatOption;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class PlantUmlErSvgFileGenerator extends BasePlantUmlFileGenerator {
|
||||
|
||||
@Override
|
||||
public boolean support(DocumentFileType type) {
|
||||
return type == DocumentFileType.PLANT_UML_ER_SVG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FileFormatOption fileFormatOption() {
|
||||
return new FileFormatOption(FileFormat.SVG);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.databasir.core.domain.document.generator.plantuml;
|
||||
|
||||
import com.databasir.core.domain.document.generator.DocumentFileType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sourceforge.plantuml.FileFormat;
|
||||
import net.sourceforge.plantuml.FileFormatOption;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class PlantUmlPngFileGenerator extends BasePlantUmlFileGenerator {
|
||||
|
||||
@Override
|
||||
public boolean support(DocumentFileType type) {
|
||||
return type == DocumentFileType.PLANT_UML_ER_PNG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FileFormatOption fileFormatOption() {
|
||||
return new FileFormatOption(FileFormat.PNG);
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
Subproject commit 0f8da6c5011505d3e45d996ca59a022e3abcc5a7
|
||||
Subproject commit 7bac0c7f123c89a1e70c82d43f2c7c7d061dd943
|
Loading…
Reference in New Issue