From 62cf0c284bda4ec476524c86e08408c968169f7f Mon Sep 17 00:00:00 2001 From: landaiqing Date: Thu, 17 Apr 2025 11:49:54 +0800 Subject: [PATCH] :tada: Initial commit --- .gitignore | 106 -------------------------- README.md | 24 ++++++ config/dbconfig.json | 9 +++ entity/entity.go | 30 ++++++++ go.mod | 10 +++ go.sum | 6 ++ main.go | 71 +++++++++++++++++ util/database.go | 177 +++++++++++++++++++++++++++++++++++++++++++ util/export_word.go | 162 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 489 insertions(+), 106 deletions(-) delete mode 100644 .gitignore create mode 100644 config/dbconfig.json create mode 100644 entity/entity.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 util/database.go create mode 100644 util/export_word.go diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b34b5b2..0000000 --- a/.gitignore +++ /dev/null @@ -1,106 +0,0 @@ -# ---> Go -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -# Go workspace file -go.work -go.work.sum - -# env file -.env - -# ---> JetBrains -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - diff --git a/README.md b/README.md index a65ffc9..a096cfb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,26 @@ # Go-TriTabula +这是一个使用Go语言实现的数据库三线表生成工具,用于从MySQL数据库中提取表结构信息并生成Word格式的三线表文档。 + +## 功能特点 + +- 连接MySQL数据库并获取表结构信息 +- 将表结构信息转换为三线表格式 +- 生成Word文档格式的三线表 +- 支持自定义数据库连接参数 + +## 使用方法 + +1. 配置`config/dbconfig.json`文件中的数据库连接信息 +2. 运行主程序:`go run main.go` +3. 生成的Word文档将保存在程序运行目录下 + +## 项目结构 + +``` +Go-TriTabula/ +├── config/ # 配置文件目录 +├── entity/ # 实体定义 +├── util/ # 工具类 +└── main.go # 主程序入口 +``` \ No newline at end of file diff --git a/config/dbconfig.json b/config/dbconfig.json new file mode 100644 index 0000000..dfd3db5 --- /dev/null +++ b/config/dbconfig.json @@ -0,0 +1,9 @@ +{ + "driver": "mysql", + "url": "127.0.0.1:3306", + "database": "schisandra-cloud-album", + "username": "root", + "password": "1611", + "maxOpenConns": 100, + "maxIdleConns": 10 +} \ No newline at end of file diff --git a/entity/entity.go b/entity/entity.go new file mode 100644 index 0000000..ad0ce62 --- /dev/null +++ b/entity/entity.go @@ -0,0 +1,30 @@ +package entity + +// Result 表示数据库表的结构信息 +type Result struct { + TableSchema string `json:"tableSchema"` + TableName string `json:"tableName"` + TableDetails []TableDetail `json:"tableDetails"` +} + +// TableDetail 表示表中列的详细信息 +type TableDetail struct { + ColumnName string `json:"columnName" field:"字段名"` + ColumnType string `json:"columnType" field:"类型"` + IsNullable string `json:"isNullable" field:"是否为空"` + ColumnKey string `json:"columnKey" field:"索引"` + ColumnDefault string `json:"columnDefault" field:"默认值"` + ColumnComment string `json:"columnComment" field:"说明"` +} + +// GetFieldMapping 返回字段名与显示名称的映射 +func GetFieldMapping() map[string]string { + return map[string]string{ + "ColumnName": "字段名", + "ColumnType": "类型", + "IsNullable": "是否为空", + "ColumnKey": "索引", + "ColumnDefault": "默认值", + "ColumnComment": "说明", + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..eb1efdd --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/landaiqing/Go-TriTabula + +go 1.24.2 + +require ( + github.com/go-sql-driver/mysql v1.9.2 + github.com/landaiqing/go-dockit v0.1.0 +) + +require filippo.io/edwards25519 v1.1.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b5e8872 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/landaiqing/go-dockit v0.1.0 h1:GIUTdV2xQI/7M1xERup870dsdCcp5LPoeTMCP9An1BU= +github.com/landaiqing/go-dockit v0.1.0/go.mod h1:OCdoK6iw0MGcr+5WaqHgWkFALhZPib8H/nkywfeJCww= diff --git a/main.go b/main.go new file mode 100644 index 0000000..5cc1d0a --- /dev/null +++ b/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "path/filepath" + + "github.com/landaiqing/Go-TriTabula/util" +) + +func main() { + // 解析命令行参数 + var configPath string + var outputFile string + + // 设置命令行参数 + flag.StringVar(&configPath, "config", "config/dbconfig.json", "数据库配置文件路径") + flag.StringVar(&outputFile, "output", "output.docx", "输出文件名") + flag.Parse() + + // 获取当前工作目录 + wd, err := os.Getwd() + if err != nil { + log.Fatalf("获取工作目录失败: %v", err) + } + + // 加载数据库配置 + configFullPath := filepath.Join(wd, configPath) + config, err := util.LoadDBConfig(configFullPath) + if err != nil { + log.Fatalf("加载数据库配置失败: %v", err) + } + + fmt.Printf("正在连接数据库: %s\n", config.Database) + + // 获取数据库连接 + db, err := util.GetDatabaseConnection(config) + if err != nil { + log.Fatalf("连接数据库失败: %v", err) + } + defer db.Close() + + fmt.Println("数据库连接成功") + + // 获取表结构信息 + fmt.Printf("正在获取数据库 %s 的表结构信息...\n", config.Database) + results, err := util.GetTableDetails(db, config.Database) + if err != nil { + log.Fatalf("获取表结构信息失败: %v", err) + } + + fmt.Printf("成功获取 %d 个表的结构信息\n", len(results)) + + // 创建导出工具 + exportWord := &util.ExportWord{} + + // 创建文档 + fmt.Println("正在生成Word文档...") + doc := exportWord.CreateDocument(results) + + // 导出文档 + outputFullPath := filepath.Join(wd, outputFile) + err = exportWord.ExportToFile(doc, outputFullPath) + if err != nil { + log.Fatalf("导出文档失败: %v", err) + } + + fmt.Printf("文档生成成功: %s\n", outputFullPath) +} diff --git a/util/database.go b/util/database.go new file mode 100644 index 0000000..e5d6e72 --- /dev/null +++ b/util/database.go @@ -0,0 +1,177 @@ +package util + +import ( + "database/sql" + "encoding/json" + "fmt" + "io/ioutil" + "log" // 添加 log 包 + "os" + "path/filepath" + + "github.com/landaiqing/Go-TriTabula/entity" + + "github.com/go-sql-driver/mysql" +) + +// DBConfig 数据库配置结构 +type DBConfig struct { + Driver string `json:"driver"` + URL string `json:"url"` + Database string `json:"database"` + Username string `json:"username"` + Password string `json:"password"` + MaxOpenConns int `json:"maxOpenConns"` + MaxIdleConns int `json:"maxIdleConns"` +} + +// LoadDBConfig 从配置文件加载数据库配置 +func LoadDBConfig(configPath string) (*DBConfig, error) { + absPath, err := filepath.Abs(configPath) + if err != nil { + return nil, fmt.Errorf("获取配置文件绝对路径失败: %v", err) + } + + file, err := os.Open(absPath) + if err != nil { + return nil, fmt.Errorf("打开配置文件失败: %v", err) + } + defer file.Close() + + byteValue, err := ioutil.ReadAll(file) + if err != nil { + return nil, fmt.Errorf("读取配置文件失败: %v", err) + } + + var config DBConfig + if err := json.Unmarshal(byteValue, &config); err != nil { + return nil, fmt.Errorf("解析配置文件失败: %v", err) + } + + return &config, nil +} + +// GetDatabaseConnection 获取数据库连接 +func GetDatabaseConnection(config *DBConfig) (*sql.DB, error) { + // 配置MySQL连接 + mysqlConfig := mysql.Config{ + User: config.Username, + Passwd: config.Password, + Net: "tcp", + Addr: config.URL, + DBName: config.Database, + AllowNativePasswords: true, + ParseTime: true, + } + + // 打开数据库连接 + db, err := sql.Open("mysql", mysqlConfig.FormatDSN()) + if err != nil { + return nil, fmt.Errorf("连接数据库失败: %v", err) + } + + // 设置连接池参数 + db.SetMaxOpenConns(config.MaxOpenConns) + db.SetMaxIdleConns(config.MaxIdleConns) + + // 测试连接 + if err := db.Ping(); err != nil { + return nil, fmt.Errorf("测试数据库连接失败: %v", err) + } + + return db, nil +} + +// GetTableDetails 获取数据库表结构详情 +func GetTableDetails(db *sql.DB, databaseName string) ([]entity.Result, error) { + query := ` + SELECT table_schema, table_name, column_name, column_type, column_key, + is_nullable, column_default, column_comment, character_set_name, EXTRA + FROM information_schema.columns + WHERE table_schema = ? + ORDER BY table_name, ORDINAL_POSITION + ` + + // 执行查询 + rows, err := db.Query(query, databaseName) + if err != nil { + return nil, fmt.Errorf("查询表结构失败: %v", err) + } + defer rows.Close() + + // 用于存储结果的映射和列表 + tableMap := make(map[string]int) // Map tableName to index in results slice + var results []entity.Result + + // 遍历查询结果 + for rows.Next() { + var tableSchema, tableName, columnName, columnType, columnKey string + var isNullable, columnDefault, columnComment, characterSetName, extra sql.NullString + + // 扫描行数据 + err := rows.Scan( + &tableSchema, &tableName, &columnName, &columnType, &columnKey, + &isNullable, &columnDefault, &columnComment, &characterSetName, &extra, + ) + if err != nil { + return nil, fmt.Errorf("扫描行数据失败: %v", err) + } + log.Printf("读取到行: Table=%s, Column=%s\n", tableName, columnName) // 添加行读取日志 + + // 处理表信息 + idx, exists := tableMap[tableName] + if !exists { + // 创建新的表结果并添加到 results slice + newResult := entity.Result{ + TableSchema: tableSchema, + TableName: tableName, + TableDetails: []entity.TableDetail{}, + } + results = append(results, newResult) + idx = len(results) - 1 // Get the index of the newly added result + tableMap[tableName] = idx // Store the index in the map + } + + // 处理列信息 + columnDefaultValue := "无" + if columnDefault.Valid { + columnDefaultValue = columnDefault.String + } + + columnKeyValue := "无" + if columnKey != "" { + columnKeyValue = columnKey + } + + columnCommentValue := "" + if columnComment.Valid { + columnCommentValue = columnComment.String + } + + isNullableValue := "NO" + if isNullable.Valid { + isNullableValue = isNullable.String + } + + // 创建列详情 + tableDetail := entity.TableDetail{ + ColumnName: columnName, + ColumnType: columnType, + ColumnKey: columnKeyValue, + IsNullable: isNullableValue, + ColumnDefault: columnDefaultValue, + ColumnComment: columnCommentValue, + } + + // 添加到表的列列表 (直接修改 results 切片中的元素) + results[idx].TableDetails = append(results[idx].TableDetails, tableDetail) + } + + // 检查是否有错误发生 + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("遍历结果集时发生错误: %v", err) + } + + log.Printf("GetTableDetails 完成, 共处理 %d 个表的结果。\n", len(results)) // 添加最终结果日志 + return results, nil +} diff --git a/util/export_word.go b/util/export_word.go new file mode 100644 index 0000000..e07f021 --- /dev/null +++ b/util/export_word.go @@ -0,0 +1,162 @@ +package util + +import ( + "fmt" + "log" + + "github.com/landaiqing/Go-TriTabula/entity" + + "github.com/landaiqing/go-dockit/document" +) + +// ExportWord 用于导出Word文档的工具结构体 +type ExportWord struct{} + +// CreateDocument 创建一个新的Word文档 +func (ew *ExportWord) CreateDocument(results []entity.Result) *document.Document { + // 创建新文档 + doc := document.NewDocument() + + // 设置文档属性 + doc.SetTitle("数据库三线表") + doc.SetCreator("Go-TriTabula") + doc.SetDescription("使用go-dockit库创建数据库三线表") + + // 为每个表结果创建表格和标题 + for _, result := range results { + // 创建表名标题段落 + tableTitlePara := doc.AddParagraph() + tableTitlePara.SetAlignment("center") + tableTitlePara.SetSpacingAfter(0) + tableTitlePara.SetSpacingBefore(0) + tableTitlePara.SetLineSpacing(1.5, "auto") // 设置1.5倍行距 + tableTitleRun := tableTitlePara.AddRun() + tableTitleRun.AddText(result.TableName) + tableTitleRun.SetBold(true) + tableTitleRun.SetFontSize(21) // 五号字体约为10.5磅(21) + tableTitleRun.SetFontFamily("宋体") + + // 获取字段映射 + fieldMapping := entity.GetFieldMapping() + fieldNames := []string{"ColumnName", "ColumnType", "IsNullable", "ColumnKey", "ColumnDefault", "ColumnComment"} + + // 创建表格 + table := doc.AddTable(len(result.TableDetails)+1, len(fieldNames)) + table.SetWidth("100%", "pct") // 与文字齐宽 + table.SetAlignment("center") + + // 填充表头 + for i, fieldName := range fieldNames { + cellPara := table.Rows[0].Cells[i].AddParagraph() + cellPara.SetAlignment("center") + cellPara.SetLineSpacing(1.5, "auto") // 1.5倍行距 + cellRun := cellPara.AddRun() + cellRun.AddText(fieldMapping[fieldName]) + cellRun.SetBold(false) + cellRun.SetFontSize(21) // 五号字体 + cellRun.SetFontFamily("宋体") + } + + // 填充数据行 + for i, detail := range result.TableDetails { + // 字段名 + para := table.Rows[i+1].Cells[0].AddParagraph() + para.SetAlignment("center") + para.SetLineSpacing(1.5, "auto") // 1.5倍行距 + cellRun := para.AddRun() + cellRun.AddText(detail.ColumnName) + cellRun.SetFontSize(21) // 五号字体 + cellRun.SetFontFamily("Times New Roman") + + // 类型 + para = table.Rows[i+1].Cells[1].AddParagraph() + para.SetAlignment("center") + para.SetLineSpacing(1.5, "auto") + cellRun = para.AddRun() + cellRun.AddText(detail.ColumnType) + cellRun.SetFontSize(21) + cellRun.SetFontFamily("Times New Roman") // 英文使用Times New Roman + + // 是否为空 + para = table.Rows[i+1].Cells[2].AddParagraph() + para.SetAlignment("center") + para.SetLineSpacing(1.5, "auto") + cellRun = para.AddRun() + // 将NO和YES转换为更易读的否和是 + isNullableText := "否" + if detail.IsNullable == "YES" { + isNullableText = "是" + } + cellRun.AddText(isNullableText) + cellRun.SetFontSize(21) + cellRun.SetFontFamily("宋体") + + // 索引 + para = table.Rows[i+1].Cells[3].AddParagraph() + para.SetAlignment("center") + para.SetLineSpacing(1.5, "auto") + cellRun = para.AddRun() + cellRun.AddText(detail.ColumnKey) + cellRun.SetFontSize(21) + cellRun.SetFontFamily("宋体") + + // 默认值 + para = table.Rows[i+1].Cells[4].AddParagraph() + para.SetAlignment("center") + para.SetLineSpacing(1.5, "auto") + cellRun = para.AddRun() + // 将"无"替换为"NULL",使其更符合数据库术语 + defaultValue := "NULL" + if detail.ColumnDefault != "无" { + defaultValue = detail.ColumnDefault + } + cellRun.AddText(defaultValue) + cellRun.SetFontSize(21) + cellRun.SetFontFamily("宋体") + + // 说明 + para = table.Rows[i+1].Cells[5].AddParagraph() + para.SetAlignment("center") + para.SetLineSpacing(1.5, "auto") + cellRun = para.AddRun() + cellRun.AddText(detail.ColumnComment) + cellRun.SetFontSize(21) + cellRun.SetFontFamily("宋体") + } + + // 设置三线表样式 + // 1. 首先清除所有默认边框 + table.SetBorders("all", "", 0, "") + // 2. 顶线(表格顶部边框),1.5磅 + table.SetBorders("top", "single", 10, "000000") + // 3. 表头分隔线(第一行底部边框),1磅 + for i := 0; i < len(fieldNames); i++ { + table.Rows[0].Cells[i].SetBorders("bottom", "single", 4, "000000") + } + // 4. 底线(表格底部边框),1.5磅 + table.SetBorders("bottom", "single", 10, "000000") + // 5. 显式设置内部边框为"none" + table.SetBorders("insideH", "none", 0, "000000") + table.SetBorders("insideV", "none", 0, "000000") + + // 添加空行 + doc.AddParagraph() + } + + // 添加页脚(页码) + footer := doc.AddFooterWithReference("default") + footer.AddPageNumber() + + return doc +} + +// ExportToFile 将文档导出到文件 +func (ew *ExportWord) ExportToFile(doc *document.Document, filePath string) error { + err := doc.Save(filePath) + if err != nil { + return fmt.Errorf("保存文档失败: %v", err) + } + + log.Printf("文档已成功保存到: %s", filePath) + return nil +}