mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-04 00:10:25 +08:00
feat: 新增sql脚本执行及其他优化
This commit is contained in:
@@ -71,8 +71,8 @@ export default defineComponent({
|
||||
const state = reactive({
|
||||
captchaImage: '',
|
||||
loginForm: {
|
||||
username: 'test',
|
||||
password: '123456',
|
||||
username: '',
|
||||
password: '',
|
||||
captcha: '',
|
||||
cid: '',
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)))
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user