mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 00:10:25 +08:00 
			
		
		
		
	feature: 导出数据库时采用gzip压缩
This commit is contained in:
		@@ -81,7 +81,7 @@
 | 
				
			|||||||
            <template #more="{ data }">
 | 
					            <template #more="{ data }">
 | 
				
			||||||
                <el-button @click="showInfo(data)" link>详情</el-button>
 | 
					                <el-button @click="showInfo(data)" link>详情</el-button>
 | 
				
			||||||
                <el-button class="ml5" type="primary" @click="onShowSqlExec(data)" link>SQL执行记录</el-button>
 | 
					                <el-button class="ml5" type="primary" @click="onShowSqlExec(data)" link>SQL执行记录</el-button>
 | 
				
			||||||
                <el-button class="ml5" type="primary" @click="onDumpDbs(data)" link>导出</el-button>
 | 
					                <el-button v-if="data.type=='mysql'" class="ml5" type="primary" @click="onDumpDbs(data)" link>导出</el-button>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #action="{ data }">
 | 
					            <template #action="{ data }">
 | 
				
			||||||
@@ -180,14 +180,25 @@
 | 
				
			|||||||
            </el-table>
 | 
					            </el-table>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-dialog width="601" :title="`${db} 数据库导出`" v-model="exportDialog.visible">
 | 
					        <el-dialog width="620" :title="`${db} 数据库导出`" v-model="exportDialog.visible">
 | 
				
			||||||
            <el-form-item label="导出内容: ">
 | 
					            <el-row justify="space-between">
 | 
				
			||||||
                <el-radio-group v-model="dumpInfo.type">
 | 
					                <el-col :span="9">
 | 
				
			||||||
                    <el-radio :label="1" size="small">结构</el-radio>
 | 
					                    <el-form-item label="导出内容: " size="small">
 | 
				
			||||||
                    <el-radio :label="2" size="small">数据</el-radio>
 | 
					                        <el-checkbox-group v-model="exportDialog.contents" :min=1>
 | 
				
			||||||
                    <el-radio :label="3" size="small">结构+数据</el-radio>
 | 
					                            <el-checkbox label="结构" />
 | 
				
			||||||
                </el-radio-group>
 | 
					                            <el-checkbox label="数据" />
 | 
				
			||||||
            </el-form-item>
 | 
					                        </el-checkbox-group>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					                </el-col>
 | 
				
			||||||
 | 
					                <el-col :span="9" >
 | 
				
			||||||
 | 
					                    <el-form-item label="扩展名: " size="small">
 | 
				
			||||||
 | 
					                        <el-radio-group v-model="exportDialog.extName">
 | 
				
			||||||
 | 
					                            <el-radio label="sql" />
 | 
				
			||||||
 | 
					                            <el-radio label="gz" />
 | 
				
			||||||
 | 
					                        </el-radio-group>
 | 
				
			||||||
 | 
					                    </el-form-item>
 | 
				
			||||||
 | 
					                </el-col>
 | 
				
			||||||
 | 
					            </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <el-form-item>
 | 
					            <el-form-item>
 | 
				
			||||||
                <el-transfer :titles="['全部数据库', '导出数据库']"  max-height="300" size="small" v-model="exportDialog.value" :data="exportDialog.data">
 | 
					                <el-transfer :titles="['全部数据库', '导出数据库']"  max-height="300" size="small" v-model="exportDialog.value" :data="exportDialog.data">
 | 
				
			||||||
@@ -333,7 +344,7 @@ const columns = ref([
 | 
				
			|||||||
    TableColumn.new('name', '名称'),
 | 
					    TableColumn.new('name', '名称'),
 | 
				
			||||||
    TableColumn.new('database', '数据库').isSlot().setMinWidth(70),
 | 
					    TableColumn.new('database', '数据库').isSlot().setMinWidth(70),
 | 
				
			||||||
    TableColumn.new('remark', '备注'),
 | 
					    TableColumn.new('remark', '备注'),
 | 
				
			||||||
    TableColumn.new('more', '更多').isSlot().setMinWidth(200).fixedRight(),
 | 
					    TableColumn.new('more', '更多').isSlot().setMinWidth(220).fixedRight(),
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 该用户拥有的的操作列按钮权限
 | 
					// 该用户拥有的的操作列按钮权限
 | 
				
			||||||
@@ -422,13 +433,17 @@ const state = reactive({
 | 
				
			|||||||
        tableNameSearch: '',
 | 
					        tableNameSearch: '',
 | 
				
			||||||
        tableCommentSearch: '',
 | 
					        tableCommentSearch: '',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    exportDialog: {
 | 
					    exportDialog: {
 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
        dbId: 0,
 | 
					        dbId: 0,
 | 
				
			||||||
        type: 3,
 | 
					        type: 3,
 | 
				
			||||||
        data: [],
 | 
					        data: [],
 | 
				
			||||||
        value: [],
 | 
					        value: [],
 | 
				
			||||||
 | 
					        contents: [],
 | 
				
			||||||
 | 
					        extName: '',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    columnDialog: {
 | 
					    columnDialog: {
 | 
				
			||||||
        visible: false,
 | 
					        visible: false,
 | 
				
			||||||
        columns: [],
 | 
					        columns: [],
 | 
				
			||||||
@@ -653,6 +668,8 @@ const onDumpDbs = async (row: any) => {
 | 
				
			|||||||
    state.exportDialog.value = []
 | 
					    state.exportDialog.value = []
 | 
				
			||||||
    state.exportDialog.data = data
 | 
					    state.exportDialog.data = data
 | 
				
			||||||
    state.exportDialog.dbId = row.id;
 | 
					    state.exportDialog.dbId = row.id;
 | 
				
			||||||
 | 
					    state.exportDialog.contents = ["结构", "数据"]
 | 
				
			||||||
 | 
					    state.exportDialog.extName = "sql"
 | 
				
			||||||
    state.exportDialog.visible = true;
 | 
					    state.exportDialog.visible = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -662,9 +679,17 @@ const onDumpDbs = async (row: any) => {
 | 
				
			|||||||
const dumpDbs = () => {
 | 
					const dumpDbs = () => {
 | 
				
			||||||
    isTrue(state.exportDialog.value.length > 0, '请添加要导出的数据库');
 | 
					    isTrue(state.exportDialog.value.length > 0, '请添加要导出的数据库');
 | 
				
			||||||
    const a = document.createElement('a');
 | 
					    const a = document.createElement('a');
 | 
				
			||||||
 | 
					    let type = 0
 | 
				
			||||||
 | 
					    for (let c of state.exportDialog.contents) {
 | 
				
			||||||
 | 
					        if (c == "结构") {
 | 
				
			||||||
 | 
					            type += 1
 | 
				
			||||||
 | 
					        } else if (c == "数据") {
 | 
				
			||||||
 | 
					            type += 2
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    a.setAttribute(
 | 
					    a.setAttribute(
 | 
				
			||||||
        'href',
 | 
					        'href',
 | 
				
			||||||
        `${config.baseApiUrl}/dbs/${state.exportDialog.dbId}/dump?db=${state.exportDialog.value.join(',')}&type=${state.exportDialog.type}&token=${getSession(
 | 
					        `${config.baseApiUrl}/dbs/${state.exportDialog.dbId}/dump?db=${state.exportDialog.value.join(',')}&type=${type}&extName=${state.exportDialog.extName}&token=${getSession(
 | 
				
			||||||
            'token'
 | 
					            'token'
 | 
				
			||||||
        )}`
 | 
					        )}`
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
package api
 | 
					package api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"compress/gzip"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"mayfly-go/internal/db/api/form"
 | 
						"mayfly-go/internal/db/api/form"
 | 
				
			||||||
@@ -32,6 +33,18 @@ type Db struct {
 | 
				
			|||||||
	TagApp       tagapp.TagTree
 | 
						TagApp       tagapp.TagTree
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type gzipResponseWriter struct {
 | 
				
			||||||
 | 
						writer *gzip.Writer
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (g gzipResponseWriter) WriteString(data string) {
 | 
				
			||||||
 | 
						g.writer.Write([]byte(data))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (g gzipResponseWriter) Close() {
 | 
				
			||||||
 | 
						g.writer.Close()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DEFAULT_ROW_SIZE = 5000
 | 
					const DEFAULT_ROW_SIZE = 5000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// @router /api/dbs [get]
 | 
					// @router /api/dbs [get]
 | 
				
			||||||
@@ -227,6 +240,13 @@ func (d *Db) DumpSql(rc *req.Ctx) {
 | 
				
			|||||||
	dbNamesStr := g.Query("db")
 | 
						dbNamesStr := g.Query("db")
 | 
				
			||||||
	dumpType := g.Query("type")
 | 
						dumpType := g.Query("type")
 | 
				
			||||||
	tablesStr := g.Query("tables")
 | 
						tablesStr := g.Query("tables")
 | 
				
			||||||
 | 
						extName := g.Query("extName")
 | 
				
			||||||
 | 
						switch extName {
 | 
				
			||||||
 | 
						case ".gz", ".gzip", "gz", "gzip":
 | 
				
			||||||
 | 
							extName = ".gz"
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							extName = ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 是否需要导出表结构
 | 
						// 是否需要导出表结构
 | 
				
			||||||
	needStruct := dumpType == "1" || dumpType == "3"
 | 
						needStruct := dumpType == "1" || dumpType == "3"
 | 
				
			||||||
@@ -237,9 +257,12 @@ func (d *Db) DumpSql(rc *req.Ctx) {
 | 
				
			|||||||
	biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, db.TagPath), "%s")
 | 
						biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, db.TagPath), "%s")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	now := time.Now()
 | 
						now := time.Now()
 | 
				
			||||||
	filename := fmt.Sprintf("%s.%s.sql", db.Name, now.Format("20060102150405"))
 | 
						filename := fmt.Sprintf("%s.%s.sql%s", db.Name, now.Format("20060102150405"), extName)
 | 
				
			||||||
	g.Header("Content-Type", "application/octet-stream")
 | 
						g.Header("Content-Type", "application/octet-stream")
 | 
				
			||||||
	g.Header("Content-Disposition", "attachment; filename="+filename)
 | 
						g.Header("Content-Disposition", "attachment; filename="+filename)
 | 
				
			||||||
 | 
						if extName != ".gz" {
 | 
				
			||||||
 | 
							g.Header("Content-Encoding", "gzip")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var dbNames, tables []string
 | 
						var dbNames, tables []string
 | 
				
			||||||
	if len(dbNamesStr) > 0 {
 | 
						if len(dbNamesStr) > 0 {
 | 
				
			||||||
@@ -248,7 +271,8 @@ func (d *Db) DumpSql(rc *req.Ctx) {
 | 
				
			|||||||
	if len(dbNames) == 1 && len(tablesStr) > 0 {
 | 
						if len(dbNames) == 1 && len(tablesStr) > 0 {
 | 
				
			||||||
		tables = strings.Split(tablesStr, ",")
 | 
							tables = strings.Split(tablesStr, ",")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	writer := g.Writer
 | 
						writer := gzipResponseWriter{writer: gzip.NewWriter(g.Writer)}
 | 
				
			||||||
 | 
						defer writer.Close()
 | 
				
			||||||
	for _, dbName := range dbNames {
 | 
						for _, dbName := range dbNames {
 | 
				
			||||||
		d.dumpDb(writer, db, dbName, tables, needStruct, needData)
 | 
							d.dumpDb(writer, db, dbName, tables, needStruct, needData)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -256,7 +280,7 @@ func (d *Db) DumpSql(rc *req.Ctx) {
 | 
				
			|||||||
	rc.ReqParam = fmt.Sprintf("DB[id=%d, tag=%s, name=%s, databases=%s, tables=%s, dumpType=%s]", db.Id, db.TagPath, db.Name, dbNamesStr, tablesStr, dumpType)
 | 
						rc.ReqParam = fmt.Sprintf("DB[id=%d, tag=%s, name=%s, databases=%s, tables=%s, dumpType=%s]", db.Id, db.TagPath, db.Name, dbNamesStr, tablesStr, dumpType)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (d *Db) dumpDb(writer gin.ResponseWriter, db *entity.Db, dbName string, tables []string, needStruct bool, needData bool) {
 | 
					func (d *Db) dumpDb(writer gzipResponseWriter, db *entity.Db, dbName string, tables []string, needStruct bool, needData bool) {
 | 
				
			||||||
	writer.WriteString("-- ----------------------------")
 | 
						writer.WriteString("-- ----------------------------")
 | 
				
			||||||
	writer.WriteString("\n-- 导出平台: mayfly-go")
 | 
						writer.WriteString("\n-- 导出平台: mayfly-go")
 | 
				
			||||||
	writer.WriteString(fmt.Sprintf("\n-- 导出时间: %s ", time.Now().Format("2006-01-02 15:04:05")))
 | 
						writer.WriteString(fmt.Sprintf("\n-- 导出时间: %s ", time.Now().Format("2006-01-02 15:04:05")))
 | 
				
			||||||
@@ -269,10 +293,8 @@ func (d *Db) dumpDb(writer gin.ResponseWriter, db *entity.Db, dbName string, tab
 | 
				
			|||||||
	switch dbInst.Info.Type {
 | 
						switch dbInst.Info.Type {
 | 
				
			||||||
	case entity.DbTypeMysql:
 | 
						case entity.DbTypeMysql:
 | 
				
			||||||
		writer.WriteString(fmt.Sprintf("use `%s`;\n", dbName))
 | 
							writer.WriteString(fmt.Sprintf("use `%s`;\n", dbName))
 | 
				
			||||||
	case entity.DbTypePostgres:
 | 
					 | 
				
			||||||
		writer.WriteString(fmt.Sprintf("\\connect `%s`;\n", dbName))
 | 
					 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		biz.IsTrue(false, "数据库类型必须为 MySQL 或 PostgreSQL")
 | 
							biz.IsTrue(false, "数据库类型必须为 %s", entity.DbTypeMysql)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	dbMeta := dbInst.GetMeta()
 | 
						dbMeta := dbInst.GetMeta()
 | 
				
			||||||
	if len(tables) == 0 {
 | 
						if len(tables) == 0 {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user