抽象思維實踐——ddl2plantuml開發記錄
阿新 • • 發佈:2020-06-24
專案原始碼地址: github.com/wangyuheng/…
背景
利用plantuml
繪製架構評審圖時,發現資料庫ER圖手寫欄位資訊成本太大,需要一個把DB表結構轉換為plantuml
格式的工具。
搜尋了一番,沒有發現支援工具,所以準備手擼一個,並記錄下設計及編碼實現的過程。
需求
一句話需求: 讀取資料庫表結構,轉換為plantuml格式的ER圖。
設計
根據需求抽象出下列概念
基礎元素(一個輸入一個輸出)
-
ER
->plantuml
格式er
圖 -
db_schema
-> 資料庫結構,ddl語句是其中一種實現形式
擴充套件概念
-
table
-> 表結構資訊 -
template
er
圖輸出格式
操作行為
-
reader
-> 讀取資料庫結構,識別table
-
parser
-> 根據table
和template
轉換為ER
圖 -
writer
-> 輸出ER
圖檔案
整體互動
storage "Context" {
node ddl
node db_schema
node ER
node template
node table
usecase parser
usecase reader
usecase writer
}
ddl -down-|> db_schema
db_schema -down-> reader
reader -> table
table -> parser
parser <-up- template
parser -down-> writer
writer -> ER
複製程式碼
選型
都是基本的檔案及String操作,通過 druid 進行sql解析
編碼實現
Reader
讀取ddl.sql檔案,解析成 table
interface Reader {
fun read(dbType: String? = DEFAULT_DB_TYPE): Iterable<Table>
fun extract(dbType: String,sql: String): Table {
...
}
}
class FileReader(private val path: String) : Reader {
override fun read(dbType: String?): Iterable<Table> {
return Files.readAllLines(Paths.get(path))
.filter { !it.startsWith("#") }
.joinToString("")
.split(";")
.filter { it.isNotBlank() }
.map { extract(dbType?: DEFAULT_DB_TYPE,it) }
.toList()
}
}
複製程式碼
Writer
template
通過resource檔案管理,接收table
輸出plantuml格式ER
圖
interface Writer {
fun write(tables: Iterable<Table>)
fun parse(tables: Iterable<Table>): String {
val template = Thread.currentThread().contextClassLoader.getResource("dot.template")!!.readText()
val content = tables.joinToString("") { table ->
val columns = table.columnList.joinToString("\n") { "${it.notNullNameWrapper()} ${it.type} ${it.defaultValue} ${it.comment}" }
"Table(${table.name},\"${table.name}\\n(${table.comment})\"){ \n $columns + \n } \n"
}
return template.replace("__content__",content)
}
private fun Column.notNullNameWrapper(): String {
return if (this.notNull) {
"not_null(${this.name})"
} else {
this.name
}
}
}
class FileWriter(private val path: String) : Writer {
override fun write(tables: Iterable<Table>) {
Files.write(Paths.get(path),parse(tables).toByteArray())
}
}
複製程式碼
Main
fun main(args: Array<String>) {
val inPath = args[0]
val outPath = args[1]
val dbType = args.getOrNull(2)
FileReader(inPath).read(dbType)
.apply { FileWriter(outPath).write(this) }
}
複製程式碼
效果
java -jar ddl2plantuml.jar ddl.sql er.puml
複製程式碼
程式會讀取當前目錄下的ddl.sql檔案,並轉換生成er.puml檔案。
@startuml
!define Table(name,desc) class name as "desc" << (T,#FFAAAA) >>
!define primary_key(x) <color:red><b>x</b></color>
!define unique(x) <color:green>x</color>
!define not_null(x) <u>x</u>
hide methods
hide stereotypes
Table(table_1,"table_1\n(This is table 1)"){
not_null(id) bigint column_1
not_null(prod_name) varchar column_2
not_null(prod_type) tinyint '0' column_3 0:活期 1:定期
not_null(start_time) time 停止交易開始時間
not_null(end_time) time 停止交易結束時間
not_null(online_type) tinyint '0' 0:上線 1:未上線
not_null(prod_info) varchar '' 產品介紹
not_null(over_limit) tinyint '0' 超額限制 0:限制 1:不限制
not_null(created_time) datetime CURRENT_TIMESTAMP
not_null(updated_time) datetime CURRENT_TIMESTAMP
}
Table(table_2,"table_2\n(This is table 2)"){
not_null(id) bigint
not_null(user_id) bigint 使用者id
not_null(user_name) varchar 使用者名稱稱
not_null(prod_id) bigint 產品id
interest_date dateNULL計息日期
not_null(created_time) datetime CURRENT_TIMESTAMP 建立時間
not_null(updated_time) datetime CURRENT_TIMESTAMP 更新時間
}
Table(table_3,"table_3\n(This is table 3)"){
not_null(id) bigint
not_null(user_id) bigint 使用者id
not_null(user_name) varchar 使用者名稱稱
not_null(prod_id) bigint 產品id
interest_date dateNULL計息日期
not_null(created_time) datetime CURRENT_TIMESTAMP 建立時間
not_null(updated_time) datetime CURRENT_TIMESTAMP 更新時間
}
@enduml
複製程式碼