feat: 新增sql脚本执行及其他优化

This commit is contained in:
meilin.huang
2021-11-25 14:34:15 +08:00
parent a8824d2f18
commit d762d346e0
10 changed files with 177 additions and 20 deletions

View File

@@ -71,8 +71,8 @@ export default defineComponent({
const state = reactive({
captchaImage: '',
loginForm: {
username: 'test',
password: '123456',
username: '',
password: '',
captcha: '',
cid: '',
},

View File

@@ -31,6 +31,24 @@
<el-button v-waves @click="saveSql" type="primary" icon="el-icon-document-add" size="mini" plain>保存</el-button>
</div>
<div style="float: right" class="fl">
<el-upload
:before-upload="beforeUpload"
:on-success="execSqlFileSuccess"
:headers="{ Authorization: token }"
:data="{
dbId: 1,
}"
:action="getUploadSqlFileUrl()"
:show-file-list="false"
name="file"
multiple
:limit="100"
>
<el-button class="fr" v-waves type="success" icon="el-icon-video-play" size="mini" plain>sql脚本执行</el-button>
</el-upload>
</div>
</div>
<codemirror @beforeChange="onBeforeChange" class="codesql" ref="cmEditor" language="sql" v-model="sql" :options="cmOptions" />
</el-aside>
@@ -95,6 +113,8 @@ import sqlFormatter from 'sql-formatter';
import { notNull, notEmpty } from '@/common/assert';
import { ElMessage } from 'element-plus';
import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
import config from '@/common/config';
import { getSession } from '@/common/utils/storage';
export default defineComponent({
name: 'SqlExec',
@@ -104,7 +124,10 @@ export default defineComponent({
},
setup() {
const cmEditor: any = ref(null);
const token = getSession('token');
const state = reactive({
token: token,
dbs: [],
tables: [],
dbId: null,
@@ -201,6 +224,26 @@ export default defineComponent({
state.execRes.data = res.res;
};
const beforeUpload = (file: File) => {
if (!state.dbId) {
ElMessage.error('请先选择数据库');
return false;
}
ElMessage.success(`'${file.name}' 正在上传执行, 请关注结果通知`);
};
// 执行sql成功
const execSqlFileSuccess = (res: any) => {
if (res.code !== 200) {
ElMessage.error(res.msg);
}
};
// 获取sql文件上传执行url
const getUploadSqlFileUrl = () => {
return `${config.baseApiUrl}/dbs/${state.dbId}/exec-sql-file`;
};
const flexColumnWidth = (str: any, tableData: any, flag = 'equal') => {
// str为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
// flag为可选值可不传该参数,传参时可选'max'或'equal',默认为'max'
@@ -244,10 +287,10 @@ export default defineComponent({
flexWidth += 8;
} else if (char >= '\u4e00' && char <= '\u9fa5') {
// 如果是中文字符为字符分配15个单位宽度
flexWidth += 15;
flexWidth += 16;
} else {
// 其他种类字符,为字符分配8个单位宽度
flexWidth += 8;
// 其他种类字符,为字符分配10个单位宽度
flexWidth += 10;
}
}
if (flexWidth < 80) {
@@ -367,6 +410,9 @@ export default defineComponent({
inputRead,
changeTable,
runSql,
beforeUpload,
getUploadSqlFileUrl,
execSqlFileSuccess,
flexColumnWidth,
saveSql,
changeDb,

View File

@@ -219,13 +219,31 @@ export default defineComponent({
}
if (script.type == enums.scriptTypeEnum['REAL_TIME'].value) {
state.terminalDialog.cmd = script.script;
script = script.script
if (state.scriptParamsDialog.params) {
script = templateResolve(script, state.scriptParamsDialog.params)
}
state.terminalDialog.cmd = script;
state.terminalDialog.visible = true;
state.terminalDialog.machineId = props.machineId;
return;
}
};
/**
* 解析 {{.param}} 形式模板字符串
*/
function templateResolve(template: string, param: any) {
return template.replace(/\{{.\w+\}}/g, (word) => {
const key = word.substring(3, word.length - 2);
const value = param[key];
if (value != null || value != undefined) {
return value;
}
return '';
});
}
const closeTermnial = () => {
state.terminalDialog.visible = false;
state.terminalDialog.machineId = 0;

View File

@@ -50,11 +50,11 @@
<el-card shadow="hover">
<template #header>
<span>消息通知</span>
<span class="personal-info-more">更多</span>
<span @click="showMsgs" class="personal-info-more">更多</span>
</template>
<div class="personal-info-box">
<ul class="personal-info-ul">
<li v-for="(v, k) in msgs" :key="k" class="personal-info-li">
<li v-for="(v, k) in msgDialog.msgs.list" :key="k" class="personal-info-li">
<a class="personal-info-li-title">{{ `[${getMsgTypeDesc(v.type)}] ${v.msg}` }}</a>
</li>
</ul>
@@ -62,6 +62,31 @@
</el-card>
</el-col>
<el-dialog width="900px" title="消息" v-model="msgDialog.visible">
<el-table border :data="msgDialog.msgs.list" size="small">
<el-table-column property="type" label="类型" width="60">
<template #default="scope">
{{ getMsgTypeDesc(scope.row.type) }}
</template>
</el-table-column>
<el-table-column property="msg" label="消息"></el-table-column>
<el-table-column property="createTime" label="时间" width="150">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
</el-table>
<el-pagination
@current-change="getMsgs"
style="text-align: center"
background
layout="prev, pager, next, total, jumper"
:total="msgDialog.msgs.total"
v-model:current-page="msgDialog.query.pageNum"
:page-size="msgDialog.query.pageSize"
/>
</el-dialog>
<!-- 营销推荐 -->
<!-- <el-col :span="24">
<el-card shadow="hover" class="mt15" header="营销推荐">
@@ -172,6 +197,17 @@ export default {
roles: [],
},
msgs: [],
msgDialog: {
visible: false,
query: {
pageSize: 10,
pageNum: 1,
},
msgs: {
list: [],
total: null,
},
},
recommendList,
accountForm: {
password: '',
@@ -187,6 +223,10 @@ export default {
return store.state.userInfos.userInfos;
});
const showMsgs = () => {
state.msgDialog.visible = true;
};
const roleInfo = computed(() => {
if (state.accountInfo.roles.length == 0) {
return '';
@@ -209,8 +249,8 @@ export default {
};
const getMsgs = async () => {
const res = await personApi.getMsgs.request();
state.msgs = res.list;
const res = await personApi.getMsgs.request(state.msgDialog.query);
state.msgDialog.msgs = res;
};
const getMsgTypeDesc = (type: number) => {
@@ -226,6 +266,7 @@ export default {
getUserInfos,
currentTime,
roleInfo,
showMsgs,
getAccountInfo,
getMsgs,
getMsgTypeDesc,

View File

@@ -2,15 +2,18 @@ package api
import (
"fmt"
"io/ioutil"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/ginx"
"mayfly-go/base/model"
"mayfly-go/base/utils"
"mayfly-go/base/ws"
"mayfly-go/server/devops/api/form"
"mayfly-go/server/devops/api/vo"
"mayfly-go/server/devops/application"
"mayfly-go/server/devops/domain/entity"
sysApplication "mayfly-go/server/sys/application"
"strconv"
"strings"
@@ -19,6 +22,7 @@ import (
type Db struct {
DbApp application.Db
MsgApp sysApplication.Msg
}
// @router /api/dbs [get]
@@ -77,18 +81,14 @@ func (d *Db) ExecSql(rc *ctx.ReqCtx) {
biz.NotEmpty(sql, "sql不能为空")
if strings.HasPrefix(sql, "SELECT") || strings.HasPrefix(sql, "select") {
colNames, res, err := d.DbApp.GetDbInstance(GetDbId(g)).SelectData(sql)
if err != nil {
panic(biz.NewBizErr(fmt.Sprintf("查询失败: %s", err.Error())))
}
biz.ErrIsNilAppendErr(err, "查询失败: %s")
colAndRes := make(map[string]interface{})
colAndRes["colNames"] = colNames
colAndRes["res"] = res
rc.ResData = colAndRes
} else {
rowsAffected, err := d.DbApp.GetDbInstance(GetDbId(g)).Exec(sql)
if err != nil {
panic(biz.NewBizErr(fmt.Sprintf("执行失败: %s", err.Error())))
}
biz.ErrIsNilAppendErr(err, "执行失败: %s")
res := make([]map[string]string, 0)
resData := make(map[string]string)
resData["影响条数"] = fmt.Sprintf("%d", rowsAffected)
@@ -102,6 +102,37 @@ func (d *Db) ExecSql(rc *ctx.ReqCtx) {
}
}
// 执行sql文件
func (d *Db) ExecSqlFile(rc *ctx.ReqCtx) {
g := rc.GinCtx
fileheader, err := g.FormFile("file")
biz.ErrIsNilAppendErr(err, "读取sql文件失败: %s")
// 读取sql文件并根据;切割sql语句
file, _ := fileheader.Open()
filename := fileheader.Filename
bytes, _ := ioutil.ReadAll(file)
sqlContent := string(bytes)
sqls := strings.Split(sqlContent, ";")
dbId := GetDbId(g)
go func() {
db := d.DbApp.GetDbInstance(dbId)
for _, sql := range sqls {
sql = strings.Trim(sql, " ")
if sql == "" || sql == "\n" {
continue
}
_, err := db.Exec(sql)
if err != nil {
d.MsgApp.CreateAndSend(rc.LoginAccount, ws.ErrMsg("sql脚本执行失败", fmt.Sprintf("[%s]执行失败: [%s]", filename, err.Error())))
return
}
}
d.MsgApp.CreateAndSend(rc.LoginAccount, ws.SuccessMsg("sql脚本执行成功", fmt.Sprintf("[%s]执行完成", filename)))
}()
}
// @router /api/db/:dbId/t-metadata [get]
func (d *Db) TableMA(rc *ctx.ReqCtx) {
rc.ResData = d.DbApp.GetDbInstance(GetDbId(rc.GinCtx)).GetTableMetedatas()

View File

@@ -1,6 +1,7 @@
package api
import (
"bytes"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/ginx"
@@ -30,6 +31,13 @@ func (m *Machine) Machines(rc *ctx.ReqCtx) {
rc.ResData = res
}
func (m *Machine) MachineStats(rc *ctx.ReqCtx) {
writer := bytes.NewBufferString("")
stats := m.MachineApp.GetCli(GetMachineId(rc.GinCtx)).GetAllStats()
machine.ShowStats(writer, stats)
rc.ResData = writer.String()
}
func (m *Machine) SaveMachine(rc *ctx.ReqCtx) {
g := rc.GinCtx
machineForm := new(form.MachineForm)

View File

@@ -151,7 +151,8 @@ func (m *machineFileAppImpl) UploadFile(la *model.LoginAccount, fileId uint64, p
createfile.Write(content)
// 保存消息并发送文件上传成功通知
m.msgApp.CreateAndSend(la, ws.SuccessMsg("文件上传成功", fmt.Sprintf("[%s]文件已成功上传至[%s]", filename, path)))
machine := m.machineRepo.GetById(machineId)
m.msgApp.CreateAndSend(la, ws.SuccessMsg("文件上传成功", fmt.Sprintf("[%s]文件已成功上传至 %s[%s:%s]", filename, machine.Name, machine.Ip, path)))
}

View File

@@ -4,6 +4,7 @@ import (
"mayfly-go/base/ctx"
"mayfly-go/server/devops/api"
"mayfly-go/server/devops/application"
sysApplication "mayfly-go/server/sys/application"
"github.com/gin-gonic/gin"
)
@@ -11,7 +12,9 @@ import (
func InitDbRouter(router *gin.RouterGroup) {
db := router.Group("dbs")
{
d := &api.Db{DbApp: application.DbApp}
d := &api.Db{DbApp: application.DbApp,
MsgApp: sysApplication.MsgApp,
}
// 获取所有数据库列表
db.GET("", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c)
@@ -50,6 +53,11 @@ func InitDbRouter(router *gin.RouterGroup) {
rc.Handle(d.ExecSql)
})
db.POST(":dbId/exec-sql-file", func(g *gin.Context) {
rc := ctx.NewReqCtxWithGin(g).WithLog(ctx.NewLogInfo("执行Sql文件"))
rc.Handle(d.ExecSqlFile)
})
db.GET(":dbId/t-metadata", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.TableMA)
})

View File

@@ -16,6 +16,10 @@ func InitMachineRouter(router *gin.RouterGroup) {
ctx.NewReqCtxWithGin(c).Handle(m.Machines)
})
machines.GET(":machineId/stats", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.MachineStats)
})
saveMachine := ctx.NewLogInfo("保存机器信息")
machines.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).

View File

@@ -126,7 +126,7 @@ func (a Account) UpdateAccount(rc *ctx.ReqCtx) {
a.AccountApp.Update(updateAccount)
}
// 获取账号接的消息列表
// 获取账号接的消息列表
func (a Account) GetMsgs(rc *ctx.ReqCtx) {
condition := &entity.Msg{
RecipientId: int64(rc.LoginAccount.Id),