1. 程式人生 > 程式設計 >抽象思維實踐——ddl2plantuml開發記錄

抽象思維實踐——ddl2plantuml開發記錄

專案原始碼地址: github.com/wangyuheng/…

背景

利用plantuml繪製架構評審圖時,發現資料庫ER圖手寫欄位資訊成本太大,需要一個把DB表結構轉換為plantuml格式的工具。 搜尋了一番,沒有發現支援工具,所以準備手擼一個,並記錄下設計及編碼實現的過程。

需求

一句話需求: 讀取資料庫表結構,轉換為plantuml格式的ER圖。

設計

根據需求抽象出下列概念

基礎元素(一個輸入一個輸出)

  1. ER -> plantuml格式er
  2. db_schema -> 資料庫結構,ddl語句是其中一種實現形式

擴充套件概念

  1. table -> 表結構資訊
  2. template
    -> 模板,定義er圖輸出格式

操作行為

  1. reader -> 讀取資料庫結構,識別 table
  2. parser -> 根據 tabletemplate 轉換為ER
  3. 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
複製程式碼

design

選型

都是基本的檔案及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
複製程式碼

result