mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-04 00:10:25 +08:00
feat: 新增数据库导出功能&其他小优化
This commit is contained in:
@@ -30,7 +30,7 @@ import { useStore } from '@/store/index.ts';
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'layoutBreadcrumbSearch',
|
name: 'layoutBreadcrumbSearch',
|
||||||
setup() {
|
setup() {
|
||||||
const layoutMenuAutocompleteRef = ref();
|
const layoutMenuAutocompleteRef: any = ref(null);
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const state: any = reactive({
|
const state: any = reactive({
|
||||||
@@ -68,7 +68,6 @@ export default defineComponent({
|
|||||||
// 初始化菜单数据
|
// 初始化菜单数据
|
||||||
const initTageView = () => {
|
const initTageView = () => {
|
||||||
if (state.tagsViewList.length > 0) return false;
|
if (state.tagsViewList.length > 0) return false;
|
||||||
console.log(getRoutes(store.state.routesList.routesList));
|
|
||||||
getRoutes(store.state.routesList.routesList).map((v: any) => {
|
getRoutes(store.state.routesList.routesList).map((v: any) => {
|
||||||
if (!v.meta.isHide) {
|
if (!v.meta.isHide) {
|
||||||
state.tagsViewList.push({ ...v });
|
state.tagsViewList.push({ ...v });
|
||||||
|
|||||||
@@ -72,6 +72,32 @@
|
|||||||
|
|
||||||
<el-dialog width="75%" :title="`${db} 表信息`" :before-close="closeTableInfo" v-model="tableInfoDialog.visible">
|
<el-dialog width="75%" :title="`${db} 表信息`" :before-close="closeTableInfo" v-model="tableInfoDialog.visible">
|
||||||
<el-row class="mb10">
|
<el-row class="mb10">
|
||||||
|
<el-popover v-model:visible="showDumpInfo" :width="470" placement="right">
|
||||||
|
<template #reference>
|
||||||
|
<el-button class="ml5" type="success" size="small" @click="showDumpInfo = !showDumpInfo">导出</el-button>
|
||||||
|
</template>
|
||||||
|
<el-form-item label="导出内容: ">
|
||||||
|
<el-radio-group v-model="dumpInfo.type">
|
||||||
|
<el-radio :label="1" size="small">结构</el-radio>
|
||||||
|
<el-radio :label="2" size="small">数据</el-radio>
|
||||||
|
<el-radio :label="3" size="small">结构+数据</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="导出表: ">
|
||||||
|
<el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small" :data="tableInfoDialog.infos">
|
||||||
|
<el-table-column type="selection" width="45" />
|
||||||
|
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip> </el-table-column>
|
||||||
|
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<div style="text-align: right">
|
||||||
|
<el-button @click="showDumpInfo = false" size="small">取消</el-button>
|
||||||
|
<el-button @click="dump(db)" type="success" size="small">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</el-popover>
|
||||||
|
|
||||||
<el-button type="primary" size="small" @click="tableCreateDialog.visible = true">创建表</el-button>
|
<el-button type="primary" size="small" @click="tableCreateDialog.visible = true">创建表</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-table v-loading="tableInfoDialog.loading" border stripe :data="tableInfoDialog.infos" size="small">
|
<el-table v-loading="tableInfoDialog.loading" border stripe :data="tableInfoDialog.infos" size="small">
|
||||||
@@ -226,6 +252,10 @@ import { dbApi } from './api';
|
|||||||
import enums from './enums';
|
import enums from './enums';
|
||||||
import { projectApi } from '../project/api.ts';
|
import { projectApi } from '../project/api.ts';
|
||||||
import SqlExecBox from './component/SqlExecBox.ts';
|
import SqlExecBox from './component/SqlExecBox.ts';
|
||||||
|
import config from '@/common/config';
|
||||||
|
import { getSession } from '@/common/utils/storage';
|
||||||
|
import { isTrue } from '@/common/assert';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'DbList',
|
name: 'DbList',
|
||||||
components: {
|
components: {
|
||||||
@@ -255,6 +285,13 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
datas: [],
|
datas: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
|
showDumpInfo: false,
|
||||||
|
dumpInfo: {
|
||||||
|
id: 0,
|
||||||
|
db: '',
|
||||||
|
type: 3,
|
||||||
|
tables: [],
|
||||||
|
},
|
||||||
// sql执行记录弹框
|
// sql执行记录弹框
|
||||||
sqlExecLogDialog: {
|
sqlExecLogDialog: {
|
||||||
title: '',
|
title: '',
|
||||||
@@ -392,6 +429,29 @@ export default defineComponent({
|
|||||||
searchSqlExecLog();
|
searchSqlExecLog();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择导出数据库表
|
||||||
|
*/
|
||||||
|
const handleDumpTableSelectionChange = (vals: any) => {
|
||||||
|
state.dumpInfo.tables = vals.map((x: any) => x.tableName);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库信息导出
|
||||||
|
*/
|
||||||
|
const dump = (db: string) => {
|
||||||
|
isTrue(state.dumpInfo.tables.length > 0, '请选择要导出的表');
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.setAttribute(
|
||||||
|
'href',
|
||||||
|
`${config.baseApiUrl}/dbs/${state.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${state.dumpInfo.tables.join(
|
||||||
|
','
|
||||||
|
)}&token=${getSession('token')}`
|
||||||
|
);
|
||||||
|
a.click();
|
||||||
|
state.showDumpInfo = false;
|
||||||
|
};
|
||||||
|
|
||||||
const onShowRollbackSql = async (sqlExecLog: any) => {
|
const onShowRollbackSql = async (sqlExecLog: any) => {
|
||||||
const columns = await dbApi.columnMetadata.request({ id: sqlExecLog.dbId, db: sqlExecLog.db, tableName: sqlExecLog.table });
|
const columns = await dbApi.columnMetadata.request({ id: sqlExecLog.dbId, db: sqlExecLog.db, tableName: sqlExecLog.table });
|
||||||
const primaryKey = columns[0].columnName;
|
const primaryKey = columns[0].columnName;
|
||||||
@@ -447,6 +507,7 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const closeTableInfo = () => {
|
const closeTableInfo = () => {
|
||||||
|
state.showDumpInfo = false;
|
||||||
state.tableInfoDialog.visible = false;
|
state.tableInfoDialog.visible = false;
|
||||||
state.tableInfoDialog.infos = [];
|
state.tableInfoDialog.infos = [];
|
||||||
};
|
};
|
||||||
@@ -516,6 +577,8 @@ export default defineComponent({
|
|||||||
valChange,
|
valChange,
|
||||||
deleteDb,
|
deleteDb,
|
||||||
onShowSqlExec,
|
onShowSqlExec,
|
||||||
|
handleDumpTableSelectionChange,
|
||||||
|
dump,
|
||||||
onBeforeCloseSqlExecDialog,
|
onBeforeCloseSqlExecDialog,
|
||||||
handleSqlExecPageChange,
|
handleSqlExecPageChange,
|
||||||
searchSqlExecLog,
|
searchSqlExecLog,
|
||||||
|
|||||||
@@ -927,7 +927,7 @@ export default defineComponent({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 转为字符串比较,可能存在数字等
|
// 转为字符串比较,可能存在数字等
|
||||||
let text = row[property] + '';
|
let text = (row[property] ? row[property] : '') + '';
|
||||||
let div = cell.children[0];
|
let div = cell.children[0];
|
||||||
if (div) {
|
if (div) {
|
||||||
let input = document.createElement('input');
|
let input = document.createElement('input');
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export const dbApi = {
|
|||||||
dbs: Api.create("/dbs", 'get'),
|
dbs: Api.create("/dbs", 'get'),
|
||||||
saveDb: Api.create("/dbs", 'post'),
|
saveDb: Api.create("/dbs", 'post'),
|
||||||
deleteDb: Api.create("/dbs/{id}", 'delete'),
|
deleteDb: Api.create("/dbs/{id}", 'delete'),
|
||||||
|
dumpDb: Api.create("/dbs/{id}/dump", 'post'),
|
||||||
tableInfos: Api.create("/dbs/{id}/t-infos", 'get'),
|
tableInfos: Api.create("/dbs/{id}/t-infos", 'get'),
|
||||||
tableIndex: Api.create("/dbs/{id}/t-index", 'get'),
|
tableIndex: Api.create("/dbs/{id}/t-index", 'get'),
|
||||||
tableDdl: Api.create("/dbs/{id}/t-create-ddl", 'get'),
|
tableDdl: Api.create("/dbs/{id}/t-create-ddl", 'get'),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px">
|
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px">
|
||||||
<codemirror height="350px" class="codesql" ref="cmEditor" language="sql" v-model="sqlValue" :options="cmOptions" />
|
<codemirror height="350px" class="codesql" ref="cmEditor" language="sql" v-model="sqlValue" :options="cmOptions" />
|
||||||
<el-input v-model="remark" placeholder="请输入执行备注" class="mt5" />
|
<el-input ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="cancel">取 消</el-button>
|
<el-button @click="cancel">取 消</el-button>
|
||||||
@@ -14,9 +14,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { toRefs, reactive, defineComponent } from 'vue';
|
import { toRefs, ref, nextTick, reactive, defineComponent } from 'vue';
|
||||||
import { dbApi } from '../api';
|
import { dbApi } from '../api';
|
||||||
import { ElDialog, ElButton, ElInput, ElMessage } from 'element-plus';
|
import { ElDialog, ElButton, ElInput, ElMessage, InputInstance } from 'element-plus';
|
||||||
// import base style
|
// import base style
|
||||||
import 'codemirror/lib/codemirror.css';
|
import 'codemirror/lib/codemirror.css';
|
||||||
// 引入主题后还需要在 options 中指定主题才会生效
|
// 引入主题后还需要在 options 中指定主题才会生效
|
||||||
@@ -50,6 +50,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props: any) {
|
setup(props: any) {
|
||||||
|
const remarkInputRef = ref<InputInstance>();
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
sqlValue: '',
|
sqlValue: '',
|
||||||
@@ -133,10 +134,14 @@ export default defineComponent({
|
|||||||
state.dbId = props.dbId;
|
state.dbId = props.dbId;
|
||||||
state.db = props.db;
|
state.db = props.db;
|
||||||
state.dialogVisible = true;
|
state.dialogVisible = true;
|
||||||
|
nextTick(() => {
|
||||||
|
remarkInputRef.value?.focus();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
|
remarkInputRef,
|
||||||
open,
|
open,
|
||||||
runSql,
|
runSql,
|
||||||
cancel,
|
cancel,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
module mayfly-go
|
module mayfly-go
|
||||||
|
|
||||||
go 1.17
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // jwt
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible // jwt
|
||||||
github.com/gin-gonic/gin v1.7.7
|
github.com/gin-gonic/gin v1.8.1
|
||||||
github.com/go-redis/redis v6.15.9+incompatible
|
github.com/go-redis/redis v6.15.9+incompatible
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/mojocn/base64Captcha v1.3.5 // 验证码
|
github.com/mojocn/base64Captcha v1.3.5 // 验证码
|
||||||
@@ -27,8 +27,8 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.10.1 // indirect
|
github.com/go-playground/validator/v10 v10.10.1 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||||
github.com/go-stack/stack v1.8.0 // indirect
|
github.com/go-stack/stack v1.8.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.9.7 // indirect
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
|
||||||
github.com/golang/snappy v0.0.1 // indirect
|
github.com/golang/snappy v0.0.1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.4 // indirect
|
github.com/jinzhu/now v1.1.4 // indirect
|
||||||
@@ -41,6 +41,7 @@ require (
|
|||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||||
github.com/onsi/gomega v1.18.1 // indirect
|
github.com/onsi/gomega v1.18.1 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
@@ -48,9 +49,10 @@ require (
|
|||||||
github.com/xdg-go/stringprep v1.0.2 // indirect
|
github.com/xdg-go/stringprep v1.0.2 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||||
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
|
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
google.golang.org/protobuf v1.27.1 // indirect
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"mayfly-go/internal/devops/api/form"
|
"mayfly-go/internal/devops/api/form"
|
||||||
"mayfly-go/internal/devops/api/vo"
|
"mayfly-go/internal/devops/api/vo"
|
||||||
"mayfly-go/internal/devops/application"
|
"mayfly-go/internal/devops/application"
|
||||||
@@ -16,8 +16,10 @@ import (
|
|||||||
"mayfly-go/pkg/ws"
|
"mayfly-go/pkg/ws"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/xwb1989/sqlparser"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Db struct {
|
type Db struct {
|
||||||
@@ -27,6 +29,8 @@ type Db struct {
|
|||||||
ProjectApp application.Project
|
ProjectApp application.Project
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_COLUMN_SIZE = 500
|
||||||
|
|
||||||
// @router /api/dbs [get]
|
// @router /api/dbs [get]
|
||||||
func (d *Db) Dbs(rc *ctx.ReqCtx) {
|
func (d *Db) Dbs(rc *ctx.ReqCtx) {
|
||||||
g := rc.GinCtx
|
g := rc.GinCtx
|
||||||
@@ -91,7 +95,7 @@ func (d *Db) ExecSql(rc *ctx.ReqCtx) {
|
|||||||
rc.ReqParam = fmt.Sprintf("db: %d:%s | sql: %s", id, db, sql)
|
rc.ReqParam = fmt.Sprintf("db: %d:%s | sql: %s", id, db, sql)
|
||||||
|
|
||||||
biz.NotEmpty(sql, "sql不能为空")
|
biz.NotEmpty(sql, "sql不能为空")
|
||||||
if strings.HasPrefix(sql, "SELECT") || strings.HasPrefix(sql, "select") || strings.HasPrefix(sql, "show") {
|
if strings.HasPrefix(sql, "SELECT") || strings.HasPrefix(sql, "select") || strings.HasPrefix(sql, "show") || strings.HasPrefix(sql, "explain") {
|
||||||
colNames, res, err := dbInstance.SelectData(sql)
|
colNames, res, err := dbInstance.SelectData(sql)
|
||||||
biz.ErrIsNilAppendErr(err, "查询失败: %s")
|
biz.ErrIsNilAppendErr(err, "查询失败: %s")
|
||||||
colAndRes := make(map[string]interface{})
|
colAndRes := make(map[string]interface{})
|
||||||
@@ -128,12 +132,8 @@ func (d *Db) ExecSqlFile(rc *ctx.ReqCtx) {
|
|||||||
fileheader, err := g.FormFile("file")
|
fileheader, err := g.FormFile("file")
|
||||||
biz.ErrIsNilAppendErr(err, "读取sql文件失败: %s")
|
biz.ErrIsNilAppendErr(err, "读取sql文件失败: %s")
|
||||||
|
|
||||||
// 读取sql文件并根据;切割sql语句
|
|
||||||
file, _ := fileheader.Open()
|
file, _ := fileheader.Open()
|
||||||
filename := fileheader.Filename
|
filename := fileheader.Filename
|
||||||
bytes, _ := ioutil.ReadAll(file)
|
|
||||||
sqlContent := string(bytes)
|
|
||||||
sqls := strings.Split(sqlContent, ";")
|
|
||||||
dbId, db := GetIdAndDb(g)
|
dbId, db := GetIdAndDb(g)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -153,12 +153,14 @@ func (d *Db) ExecSqlFile(rc *ctx.ReqCtx) {
|
|||||||
|
|
||||||
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, db.ProjectId), "%s")
|
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, db.ProjectId), "%s")
|
||||||
|
|
||||||
for _, sql := range sqls {
|
tokens := sqlparser.NewTokenizer(file)
|
||||||
sql = strings.Trim(sql, " ")
|
for {
|
||||||
if sql == "" || sql == "\n" {
|
stmt, err := sqlparser.ParseNext(tokens)
|
||||||
continue
|
if err == io.EOF {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
_, err := db.Exec(sql)
|
sql := sqlparser.String(stmt)
|
||||||
|
_, err = db.Exec(sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.MsgApp.CreateAndSend(rc.LoginAccount, ws.ErrMsg("sql脚本执行失败", fmt.Sprintf("[%s]%s执行失败: [%s]", filename, dbInfo, err.Error())))
|
d.MsgApp.CreateAndSend(rc.LoginAccount, ws.ErrMsg("sql脚本执行失败", fmt.Sprintf("[%s]%s执行失败: [%s]", filename, dbInfo, err.Error())))
|
||||||
return
|
return
|
||||||
@@ -168,6 +170,88 @@ func (d *Db) ExecSqlFile(rc *ctx.ReqCtx) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 数据库dump
|
||||||
|
func (d *Db) DumpSql(rc *ctx.ReqCtx) {
|
||||||
|
g := rc.GinCtx
|
||||||
|
dbId, db := GetIdAndDb(g)
|
||||||
|
dumpType := g.Query("type")
|
||||||
|
tablesStr := g.Query("tables")
|
||||||
|
biz.NotEmpty(tablesStr, "请选择要导出的表")
|
||||||
|
tables := strings.Split(tablesStr, ",")
|
||||||
|
|
||||||
|
// 是否需要导出表结构
|
||||||
|
needStruct := dumpType == "1" || dumpType == "3"
|
||||||
|
// 是否需要导出数据
|
||||||
|
needData := dumpType == "2" || dumpType == "3"
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
filename := fmt.Sprintf("%s.%s.sql", db, now.Format("200601021504"))
|
||||||
|
g.Header("Content-Type", "application/octet-stream")
|
||||||
|
g.Header("Content-Disposition", "attachment; filename="+filename)
|
||||||
|
|
||||||
|
rc.ReqParam = fmt.Sprintf("数据库id: %d -- %s", dbId, db)
|
||||||
|
dbInstance := d.DbApp.GetDbInstance(dbId, db)
|
||||||
|
writer := g.Writer
|
||||||
|
writer.WriteString("-- ----------------------------")
|
||||||
|
writer.WriteString("\n-- 导出平台: mayfly-go")
|
||||||
|
writer.WriteString(fmt.Sprintf("\n-- 导出时间: %s ", now.Format("2006-01-02 15:04:05")))
|
||||||
|
writer.WriteString(fmt.Sprintf("\n-- 导出数据库: %s ", db))
|
||||||
|
writer.WriteString("\n-- ----------------------------\n")
|
||||||
|
|
||||||
|
for _, table := range tables {
|
||||||
|
if needStruct {
|
||||||
|
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- 表结构: %s \n-- ----------------------------\n", table))
|
||||||
|
writer.WriteString(fmt.Sprintf("DROP TABLE IF EXISTS `%s`;\n", table))
|
||||||
|
writer.WriteString(dbInstance.GetCreateTableDdl(table)[0]["Create Table"].(string) + ";\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !needData {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- 表记录: %s \n-- ----------------------------\n", table))
|
||||||
|
writer.WriteString("BEGIN;\n")
|
||||||
|
|
||||||
|
countSql := fmt.Sprintf("SELECT COUNT(*) count FROM %s", table)
|
||||||
|
_, countRes, _ := dbInstance.SelectData(countSql)
|
||||||
|
// 查询出所有列信息总数,手动分页获取所有数据
|
||||||
|
maCount := int(countRes[0]["count"].(int64))
|
||||||
|
// 计算需要查询的页数
|
||||||
|
pageNum := maCount / DEFAULT_COLUMN_SIZE
|
||||||
|
if maCount%DEFAULT_COLUMN_SIZE > 0 {
|
||||||
|
pageNum++
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlTmp := "SELECT * FROM %s LIMIT %d, %d"
|
||||||
|
for index := 0; index < pageNum; index++ {
|
||||||
|
sql := fmt.Sprintf(sqlTmp, table, index*DEFAULT_COLUMN_SIZE, DEFAULT_COLUMN_SIZE)
|
||||||
|
columns, result, _ := dbInstance.SelectData(sql)
|
||||||
|
|
||||||
|
insertSql := "INSERT INTO `%s` VALUES (%s);\n"
|
||||||
|
for _, res := range result {
|
||||||
|
var values []string
|
||||||
|
for _, column := range columns {
|
||||||
|
value := res[column]
|
||||||
|
if value == nil {
|
||||||
|
values = append(values, "NULL")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
strValue, ok := value.(string)
|
||||||
|
if ok {
|
||||||
|
values = append(values, fmt.Sprintf("%#v", strValue))
|
||||||
|
} else {
|
||||||
|
values = append(values, utils.ToString(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.WriteString(fmt.Sprintf(insertSql, table, strings.Join(values, ", ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteString("COMMIT;\n")
|
||||||
|
}
|
||||||
|
rc.NoRes = true
|
||||||
|
}
|
||||||
|
|
||||||
// @router /api/db/:dbId/t-metadata [get]
|
// @router /api/db/:dbId/t-metadata [get]
|
||||||
func (d *Db) TableMA(rc *ctx.ReqCtx) {
|
func (d *Db) TableMA(rc *ctx.ReqCtx) {
|
||||||
dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
|
dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
|
||||||
|
|||||||
@@ -223,8 +223,9 @@ func (d *DbInstance) SelectData(execSql string) ([]string, []map[string]interfac
|
|||||||
execSql = strings.Trim(execSql, " ")
|
execSql = strings.Trim(execSql, " ")
|
||||||
isSelect := strings.HasPrefix(execSql, "SELECT") || strings.HasPrefix(execSql, "select")
|
isSelect := strings.HasPrefix(execSql, "SELECT") || strings.HasPrefix(execSql, "select")
|
||||||
isShow := strings.HasPrefix(execSql, "show")
|
isShow := strings.HasPrefix(execSql, "show")
|
||||||
|
isExplain := strings.HasPrefix(execSql, "explain")
|
||||||
|
|
||||||
if !isSelect && !isShow {
|
if !isSelect && !isShow && !isExplain {
|
||||||
return nil, nil, errors.New("该sql非查询语句")
|
return nil, nil, errors.New("该sql非查询语句")
|
||||||
}
|
}
|
||||||
// 没加limit,则默认限制50条
|
// 没加limit,则默认限制50条
|
||||||
@@ -272,14 +273,13 @@ func (d *DbInstance) SelectData(execSql string) ([]string, []map[string]interfac
|
|||||||
colName := colType.Name()
|
colName := colType.Name()
|
||||||
// 字段类型名
|
// 字段类型名
|
||||||
colScanType := colType.ScanType().Name()
|
colScanType := colType.ScanType().Name()
|
||||||
|
|
||||||
// 如果是密码字段,则脱敏显示
|
|
||||||
if colName == "password" {
|
|
||||||
v = []byte("******")
|
|
||||||
}
|
|
||||||
if isFirst {
|
if isFirst {
|
||||||
colNames = append(colNames, colName)
|
colNames = append(colNames, colName)
|
||||||
}
|
}
|
||||||
|
if v == nil {
|
||||||
|
rowData[colName] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
// 这里把[]byte数据转成string
|
// 这里把[]byte数据转成string
|
||||||
stringV := string(v)
|
stringV := string(v)
|
||||||
if stringV == "" {
|
if stringV == "" {
|
||||||
|
|||||||
@@ -61,6 +61,12 @@ func InitDbRouter(router *gin.RouterGroup) {
|
|||||||
rc.Handle(d.ExecSqlFile)
|
rc.Handle(d.ExecSqlFile)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
db.GET(":dbId/dump", func(g *gin.Context) {
|
||||||
|
ctx.NewReqCtxWithGin(g).
|
||||||
|
WithLog(ctx.NewLogInfo("Sql文件dump")).
|
||||||
|
Handle(d.DumpSql)
|
||||||
|
})
|
||||||
|
|
||||||
db.GET(":dbId/t-metadata", func(c *gin.Context) {
|
db.GET(":dbId/t-metadata", func(c *gin.Context) {
|
||||||
ctx.NewReqCtxWithGin(c).Handle(d.TableMA)
|
ctx.NewReqCtxWithGin(c).Handle(d.TableMA)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ type HandlerFunc func(*ReqCtx)
|
|||||||
type ReqCtx struct {
|
type ReqCtx struct {
|
||||||
GinCtx *gin.Context // gin context
|
GinCtx *gin.Context // gin context
|
||||||
|
|
||||||
// NeedToken bool // 是否需要token
|
|
||||||
RequiredPermission *Permission // 需要的权限信息,默认为nil,需要校验token
|
RequiredPermission *Permission // 需要的权限信息,默认为nil,需要校验token
|
||||||
LoginAccount *model.LoginAccount // 登录账号信息,只有校验token后才会有值
|
LoginAccount *model.LoginAccount // 登录账号信息,只有校验token后才会有值
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ type ReqCtx struct {
|
|||||||
Err interface{} // 请求错误
|
Err interface{} // 请求错误
|
||||||
|
|
||||||
timed int64 // 执行时间
|
timed int64 // 执行时间
|
||||||
noRes bool // 无需返回结果,即文件下载等
|
NoRes bool // 无需返回结果,即文件下载等
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *ReqCtx) Handle(handler HandlerFunc) {
|
func (rc *ReqCtx) Handle(handler HandlerFunc) {
|
||||||
@@ -55,13 +54,13 @@ func (rc *ReqCtx) Handle(handler HandlerFunc) {
|
|||||||
begin := time.Now()
|
begin := time.Now()
|
||||||
handler(rc)
|
handler(rc)
|
||||||
rc.timed = time.Now().Sub(begin).Milliseconds()
|
rc.timed = time.Now().Sub(begin).Milliseconds()
|
||||||
if !rc.noRes {
|
if !rc.NoRes {
|
||||||
ginx.SuccessRes(ginCtx, rc.ResData)
|
ginx.SuccessRes(ginCtx, rc.ResData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *ReqCtx) Download(reader io.Reader, filename string) {
|
func (rc *ReqCtx) Download(reader io.Reader, filename string) {
|
||||||
rc.noRes = true
|
rc.NoRes = true
|
||||||
ginx.Download(rc.GinCtx, reader, filename)
|
ginx.Download(rc.GinCtx, reader, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
@@ -107,3 +109,45 @@ func ReverStrTemplate(temp, str string, res map[string]interface{}) {
|
|||||||
ReverStrTemplate(next, StrTrim(SubString(str, UnicodeIndex(str, value)+StrLen(value), StrLen(str))), res)
|
ReverStrTemplate(next, StrTrim(SubString(str, UnicodeIndex(str, value)+StrLen(value), StrLen(str))), res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToString(value interface{}) string {
|
||||||
|
// interface 转 string
|
||||||
|
var key string
|
||||||
|
if value == nil {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
switch it := value.(type) {
|
||||||
|
case float64:
|
||||||
|
return strconv.FormatFloat(it, 'f', -1, 64)
|
||||||
|
case float32:
|
||||||
|
return strconv.FormatFloat(float64(it), 'f', -1, 64)
|
||||||
|
case int:
|
||||||
|
return strconv.Itoa(it)
|
||||||
|
case uint:
|
||||||
|
return strconv.Itoa(int(it))
|
||||||
|
case int8:
|
||||||
|
return strconv.Itoa(int(it))
|
||||||
|
case uint8:
|
||||||
|
return strconv.Itoa(int(it))
|
||||||
|
case int16:
|
||||||
|
return strconv.Itoa(int(it))
|
||||||
|
case uint16:
|
||||||
|
return strconv.Itoa(int(it))
|
||||||
|
case int32:
|
||||||
|
return strconv.Itoa(int(it))
|
||||||
|
case uint32:
|
||||||
|
return strconv.Itoa(int(it))
|
||||||
|
case int64:
|
||||||
|
return strconv.FormatInt(it, 10)
|
||||||
|
case uint64:
|
||||||
|
return strconv.FormatUint(it, 10)
|
||||||
|
case string:
|
||||||
|
return it
|
||||||
|
case []byte:
|
||||||
|
return string(value.([]byte))
|
||||||
|
default:
|
||||||
|
newValue, _ := json.Marshal(value)
|
||||||
|
return string(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user