47 Commits

Author SHA1 Message Date
meilin.huang
7b5f963ec4 fix: 构建脚本调整 2023-02-15 21:32:51 +08:00
meilin.huang
f22badb861 feat: 优化 2023-02-15 21:28:01 +08:00
meilin.huang
8c501c90cd feat: 标签路径展示优化,新增提示 2023-02-14 18:24:39 +08:00
Coder慌
11eebdfcf0 !39 feat: 表和schema超出20条后启用模糊高亮过滤
Merge pull request !39 from zongyangleo/dev_20230213_1
2023-02-14 06:36:41 +00:00
刘宗洋
46e331783f fix: sql提示bug修复 2023-02-14 14:03:18 +08:00
刘宗洋
7e33e64659 Merge remote-tracking branch 'upstream/dev' into dev_20230213_1 2023-02-14 12:24:48 +08:00
刘宗洋
7e4152ad6d fix: sql提示bug修复 2023-02-14 12:23:38 +08:00
meilin.huang
d189ee7c22 feat: 数据库表格重构 2023-02-14 11:44:48 +08:00
刘宗洋
641c2abb24 feat: 表名+schema模糊高亮过滤 2023-02-14 10:33:57 +08:00
meilin.huang
3ab4ac891b 表和schema超出20条后启用过滤 2023-02-13 21:36:18 +08:00
meilin.huang
70b586e45a refactor: sqlexec组件重构优化、新增数据库相关系统参数配置、相关问题修复 2023-02-13 21:11:16 +08:00
Coder慌
77aa724003 !37 fix: ref修复
Merge pull request !37 from zongyangleo/dev_20230206
2023-02-07 09:30:37 +00:00
刘宗洋
db7b50d96a fix: ref修复 2023-02-07 17:22:52 +08:00
meilin.huang
00fee24a85 feat: 代码小调整 2023-02-07 16:54:44 +08:00
Coder慌
fa0cb73ec9 !36 feat: redis新增实例树、各数据操作按钮跳转
Merge pull request !36 from zongyangleo/dev_20230206
2023-02-07 03:33:23 +00:00
刘宗洋
6ee815b71d debugger 2023-02-07 11:06:54 +08:00
刘宗洋
311a048af5 Merge remote-tracking branch 'upstream/dev' into dev_20230206
# Conflicts:
#	mayfly_go_web/src/views/ops/db/SqlExec.vue
#	mayfly_go_web/src/views/ops/db/component/InstanceTree.vue
#	mayfly_go_web/src/views/ops/mongo/MongoInstanceTree.vue
2023-02-07 10:59:46 +08:00
刘宗洋
3da9ecfaa3 feat: 数据操作按钮跳转 2023-02-07 10:32:42 +08:00
刘宗洋
4c2c6f613e feat: redis新增实例树 2023-02-07 08:39:14 +08:00
meilin.huang
ba63736871 feat: 小功能优化 2023-02-06 17:14:16 +08:00
Coder慌
ed3dbafc35 !34 feat:mongo新增实例树
Merge pull request !34 from zongyangleo/dev_20230206
2023-02-06 08:54:31 +00:00
刘宗洋
fdeffbd495 Merge branch 'dev' of https://gitee.com/objs/mayfly-go into dev_20230206
yes.
2023-02-06 16:46:52 +08:00
刘宗洋
9870812e6b feat:mongo新增实例树 2023-02-06 16:46:40 +08:00
Coder慌
46f35e14c4 !33 feat: 数据管理添加实例树,细节修改
Merge pull request !33 from zongyangleo/dev_20230206
2023-02-06 06:53:38 +00:00
刘宗洋
e89cf96ff4 feat:实例树优化:加宽菜单宽度、表加载中loading状态、新建查询等按钮自成一行、加宽实例详情宽度、实例树高度默认撑满 2023-02-06 14:50:57 +08:00
Coder慌
5cd19bf38d !32 feat: 数据管理添加实例树
Merge pull request !32 from zongyangleo/dev_20230206
2023-02-06 03:13:25 +00:00
刘宗洋
a6f69f2b62 修改团队bug 2023-02-06 11:08:18 +08:00
刘宗洋
e34b9adada feat: 数据管理添加实例树 2023-02-06 10:35:30 +08:00
meilin.huang
9f43f731b5 fix: 问题修复 2023-01-19 19:45:12 +08:00
meilin.huang
594ca43505 refactor: 包名变更ctx -> req 2023-01-14 16:29:52 +08:00
meilin.huang
2a91cdb67a fix: pgsql主键字段获取修复 2023-01-09 20:05:35 +08:00
Coder慌
11148e720b !30 feat: 执行sql,无数据时也提示一下
Merge pull request !30 from zongyangleo/dev_20221228
2023-01-01 06:43:54 +00:00
刘宗洋
c5835d6d8c feat: 执行sql,无数据时也提示一下 2022-12-28 10:23:38 +08:00
meilin.huang
4a26bb3ba5 feat: 支持redis缓存权限等信息 2022-12-26 20:53:07 +08:00
meilin.huang
4fec38724d fix: pgsql隧道连接问题修复 2022-12-22 18:41:34 +08:00
meilin.huang
85349df8a1 code review 2022-12-17 22:24:21 +08:00
meilin.huang
ffe250f8a9 feat: 构建文档调整&移除静态文件 2022-12-16 23:14:00 +08:00
Coder慌
eeb310a1d2 !28 redis监控信息面板重构
Merge pull request !28 from zongyangleo/dev_20221216
2022-12-16 03:40:02 +00:00
刘宗洋
a42c606d20 redis监控信息面板重构 2022-12-16 11:35:16 +08:00
Coder慌
4e64d46cd2 !27 redis监控信息面板重构
Merge pull request !27 from zongyangleo/dev_20221216
2022-12-16 03:17:50 +00:00
刘宗洋
9886ee6828 redis监控信息面板重构 2022-12-16 11:13:13 +08:00
Coder慌
0e1bde09c3 !26 fix: 修复双击编辑事件报错
Merge pull request !26 from zongyangleo/dev_20221213
2022-12-13 06:15:42 +00:00
刘宗洋
dda600709b fix: 修复双击编辑事件报错 2022-12-13 11:08:42 +08:00
meilin.huang
15f38491b2 feat: 新增docker部署 2022-12-09 22:02:37 +08:00
may-fly
4b140732f7 Merge pull request #23 from 1ch0/master
Feat: add Dockerfile
2022-12-08 19:14:41 +08:00
1ch0
f2119b2c52 Feat: add docker-compose 2022-12-05 11:27:05 +08:00
1ch0
f15c45793b Feat: add Dockerfile 2022-12-03 08:25:22 +08:00
262 changed files with 5535 additions and 4810 deletions

View File

@@ -34,7 +34,7 @@ web版 **linux(终端[终端回放] 文件 脚本 进程)、数据库mysql po
### 系统相关资料
- 项目文档: https://objs.gitee.io/mayfly-go-docs
- 系统操作视频: https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854
- 安装包下载https://gitee.com/objs/mayfly-go/releases
- 部署文档https://objs.gitee.io/mayfly-go-docs/download
### 系统核心功能截图

View File

@@ -44,7 +44,7 @@ function build() {
toFolder=$1
os=$2
arch=$3
copyStatic=$4
copyDocScript=$4
echo_yellow "-------------------${os}-${arch}打包构建开始-------------------"
@@ -68,17 +68,19 @@ function build() {
echo_green "移动二进制文件至'${toFolder}'"
mv ${server_folder}/${execFileName} ${toFolder}
if [ "${copy2Server}" == "1" ] ; then
echo_green "拷贝前端静态页面至'${toFolder}/static'"
mkdir -p ${toFolder}/static && cp -r ${web_folder}/dist/* ${toFolder}/static
fi
# if [ "${copy2Server}" == "1" ] ; then
# echo_green "拷贝前端静态页面至'${toFolder}/static'"
# mkdir -p ${toFolder}/static && cp -r ${web_folder}/dist/* ${toFolder}/static
# fi
echo_green "拷贝脚本等资源文件[config.yml、mayfly-go.sql、readme.txt、startup.sh、shutdown.sh]"
cp ${server_folder}/config.yml ${toFolder}
cp ${server_folder}/mayfly-go.sql ${toFolder}
cp ${server_folder}/readme.txt ${toFolder}
cp ${server_folder}/startup.sh ${toFolder}
cp ${server_folder}/shutdown.sh ${toFolder}
if [ "${copyDocScript}" == "1" ] ; then
echo_green "拷贝脚本等资源文件[config.yml、mayfly-go.sql、readme.txt、startup.sh、shutdown.sh]"
cp ${server_folder}/config.yml ${toFolder}
cp ${server_folder}/mayfly-go.sql ${toFolder}
cp ${server_folder}/readme.txt ${toFolder}
cp ${server_folder}/startup.sh ${toFolder}
cp ${server_folder}/shutdown.sh ${toFolder}
fi
echo_yellow ">>>>>>>>>>>>>>>>>>>${os}-${arch}打包构建完成<<<<<<<<<<<<<<<<<<<<\n"
}
@@ -99,45 +101,100 @@ function buildMac() {
build "$1/mayfly-go-mac" "darwin" "amd64" $2
}
function runBuild() {
# 构建结果的目的路径
read -p "请输入构建产物输出目录: " toPath
if [ ! -d ${toPath} ] ; then
echo_red "构建产物输出目录不存在!"
exit;
fi
# 进入目标路径,并赋值全路径
cd ${toPath}
toPath=`pwd`
function buildDocker() {
echo_yellow "-------------------构建docker镜像开始-------------------"
imageVersion=$1
cd ${server_folder}
imageName="mayflygo/mayfly-go:${imageVersion}"
docker build -t "${imageName}" .
echo_green "docker镜像构建完成->[${imageName}]"
echo_yellow "-------------------构建docker镜像结束-------------------"
}
read -p "是否构建前端[0|其他->否 1->是 2->构建并拷贝至server/static/static]: " runBuildWeb
read -p "请选择构建版本[0|其他->全部 1->linux-amd64 2->linux-arm64 3->windows 4->mac]: " buildType
if [ "${runBuildWeb}" == "1" ] || [ "${runBuildWeb}" == "2" ] ; then
buildWeb ${runBuildWeb}
function buildxDocker() {
echo_yellow "-------------------docker buildx构建镜像开始-------------------"
imageVersion=$1
cd ${server_folder}
imageName="mayflygo/mayfly-go:${imageVersion}"
docker buildx build --push --platform linux/amd64,linux/arm64 -t "${imageName}" .
echo_green "docker多版本镜像构建完成->[${imageName}]"
echo_yellow "-------------------docker buildx构建镜像结束-------------------"
}
function runBuild() {
read -p "请选择构建版本[0|其他->除docker镜像外其他 1->linux-amd64 2->linux-arm64 3->windows 4->mac 5->docker 6->docker buildx]: " buildType
toPath="."
imageVersion="latest"
copyDocScript="1"
if [[ "${buildType}" != "5" ]] && [[ "${buildType}" != "6" ]] ; then
# 构建结果的目的路径
read -p "请输入构建产物输出目录[默认当前路径]: " toPath
if [ ! -d ${toPath} ] ; then
echo_red "构建产物输出目录不存在!"
exit;
fi
if [ "${toPath}" == "" ] ; then
toPath="."
fi
read -p "是否拷贝文档&脚本[0->否 1->是][默认是]: " copyDocScript
if [ "${copyDocScript}" == "" ] ; then
copyDocScript="1"
fi
# 进入目标路径,并赋值全路径
cd ${toPath}
toPath=`pwd`
fi
if [[ "${buildType}" == "5" ]] || [[ "${buildType}" == "6" ]] ; then
read -p "请输入docker镜像版本号[默认latest]: " imageVersion
if [ "${imageVersion}" == "" ] ; then
imageVersion="latest"
fi
fi
# read -p "是否构建前端[0|其他->否 1->是 2->构建并拷贝至server/static/static]: " runBuildWeb
runBuildWeb="2"
# 编译web前端
buildWeb ${runBuildWeb}
case ${buildType} in
"1")
buildLinuxAmd64 ${toPath} ${runBuildWeb}
buildLinuxAmd64 ${toPath} ${copyDocScript}
;;
"2")
buildLinuxArm64 ${toPath} ${runBuildWeb}
buildLinuxArm64 ${toPath} ${copyDocScript}
;;
"3")
buildWindows ${toPath} ${runBuildWeb}
buildWindows ${toPath} ${copyDocScript}
;;
"4")
buildMac ${toPath} ${runBuildWeb}
buildMac ${toPath} ${copyDocScript}
;;
"5")
buildDocker ${imageVersion}
;;
"6")
buildxDocker ${imageVersion}
;;
*)
buildLinuxAmd64 ${toPath} ${runBuildWeb}
buildLinuxArm64 ${toPath} ${runBuildWeb}
buildWindows ${toPath} ${runBuildWeb}
buildMac ${toPath} ${runBuildWeb}
buildLinuxAmd64 ${toPath} ${copyDocScript}
buildLinuxArm64 ${toPath} ${copyDocScript}
buildWindows ${toPath} ${copyDocScript}
buildMac ${toPath} ${copyDocScript}
;;
esac
echo_green "删除['${server_folder}/static/static']下静态资源文件."
# 删除静态资源文件保留一个favicon.ico否则后端启动会报错
rm -rf ${server_folder}/static/static/assets
rm -rf ${server_folder}/static/static/config.js
rm -rf ${server_folder}/static/static/index.html
}
runBuild

View File

@@ -4,32 +4,34 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"build-preview": "npm run build && npm run preview",
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"asciinema-player": "^3.0.1",
"axios": "^1.2.0",
"axios": "^1.3.2",
"countup.js": "^2.0.7",
"cropperjs": "^1.5.11",
"echarts": "^5.4.0",
"element-plus": "^2.2.26",
"element-plus": "^2.2.30",
"jsencrypt": "^3.2.1",
"lodash": "^4.17.21",
"mitt": "^3.0.0",
"monaco-editor": "^0.34.1",
"monaco-sql-languages": "^0.9.5",
"monaco-editor": "^0.35.0",
"monaco-sql-languages": "^0.11.0",
"monaco-themes": "^0.4.2",
"nprogress": "^0.2.0",
"screenfull": "^6.0.2",
"sortablejs": "^1.13.0",
"sql-formatter": "^9.2.0",
"vue": "^3.2.45",
"vue": "^3.2.47",
"vue-clipboard3": "^1.0.1",
"vue-router": "^4.1.6",
"vuex": "^4.0.2",
"xterm": "^5.0.0",
"xterm-addon-fit": "^0.6.0"
"xterm": "^5.1.0",
"xterm-addon-fit": "^0.7.0"
},
"devDependencies": {
"@types/lodash": "^4.14.178",
@@ -44,10 +46,10 @@
"eslint": "^8.5.0",
"eslint-plugin-vue": "^8.2.0",
"prettier": "^2.3.0",
"sass": "^1.45.1",
"sass-loader": "^12.4.0",
"sass": "^1.58.0",
"sass-loader": "^13.2.0",
"typescript": "^4.7.4",
"vite": "^2.9.13",
"vite": "^4.1.1",
"vue-eslint-parser": "^8.0.1"
},
"browserslist": [

View File

@@ -1,9 +1,17 @@
function getBaseApiUrl() {
let path = window.location.pathname;
if (path == '/') {
return window.location.host;
}
return window.location.host + path;
}
const config = {
baseApiUrl: `${(window as any).globalConfig.BaseApiUrl}/api`,
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${location.host}`}/api`,
baseApiUrl: `${(window as any).globalConfig.BaseApiUrl || location.protocol + '//' + getBaseApiUrl()}/api`,
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
// 系统版本
version: 'v1.3.1'
version: 'v1.4.0'
}
export default config

View File

@@ -0,0 +1,39 @@
export function exportCsv(filename: string, columns: string[], datas: []) {
// 二维数组
const cvsData = [columns];
for (let data of datas) {
// 数据值组成的一维数组
let dataValueArr: any = [];
for (let column of columns) {
let val: any = data[column];
if (typeof val == 'string' && val) {
// csv格式如果有逗号整体用双引号括起来如果里面还有双引号就替换成两个双引号这样导出来的格式就不会有问题了
if (val.indexOf(',') != -1) {
// 如果还有双引号,先将双引号转义,避免两边加了双引号后转义错误
if (val.indexOf('"') != -1) {
val = val.replace(/\"/g, "\"\"");
}
// 再将逗号转义
val = `"${val}"`;
}
dataValueArr.push(val);
} else {
dataValueArr.push(val);
}
}
cvsData.push(dataValueArr);
}
const csvString = cvsData.map((e) => e.join(',')).join('\n');
// 导出
let link = document.createElement('a');
let exportContent = '\uFEFF';
let blob = new Blob([exportContent + csvString], {
type: 'text/plain;charset=utrf-8',
});
link.id = 'download-csv';
link.setAttribute('href', URL.createObjectURL(blob));
link.setAttribute('download', `${filename}.csv`);
document.body.appendChild(link);
link.click();
}

View File

@@ -1,5 +1,5 @@
import { nextTick } from 'vue';
import loadingCss from '@/theme/loading.scss';
import loadingCss from "@/theme/loading.scss?inline"
// 定义方法
export const NextLoading = {

View File

@@ -18,6 +18,8 @@ import SvgIcon from '@/components/svgIcon/index.vue';
import '@/assets/font/font.css'
const app = createApp(App);
// 屏蔽警告信息
app.config.warnHandler = () => null;
/**
* 导出全局注册 element plus svg 图标

View File

@@ -124,7 +124,7 @@ export const staticRoutes: Array<RouteRecordRaw> = [
name: 'login',
component: () => import('@/views/login/index.vue'),
meta: {
title: '登',
title: '登',
},
},
{

View File

@@ -107,13 +107,13 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
layout: 'classic',
// ssh终端字体颜色
terminalForeground: '#7e9192',
terminalForeground: '#50583E',
// ssh终端背景色
terminalBackground: '#002833',
terminalBackground: '#FFFFDD',
// ssh终端cursor色
terminalCursor: '#268F81',
terminalFontSize: 15,
terminalFontWeight: 'normal',
terminalCursor: '#979b7c',
terminalFontSize: 14,
terminalFontWeight: 'bold',
// 编辑器主题
editorTheme: 'vs',

View File

@@ -293,4 +293,8 @@ body,
.el-table-z-index-inherit .el-table .el-table__cell {
z-index: inherit !important;
}
.f12 {
font-size: 12px
}

View File

@@ -0,0 +1,65 @@
<template>
<div
style="display: inline-flex; justify-content: center; align-items: center; cursor: pointer;vertical-align: middle;">
<el-popover @show="showTagInfo" placement="right-end" title="标签信息" :width="300" trigger="hover">
<template #reference>
<el-icon>
<InfoFilled />
</el-icon>
</template>
<span v-for="(v, i) in tags" :key="i">
<el-tooltip effect="customized" :content="v.remark" placement="top">
<span class="color-success">{{ v.name }}</span>
</el-tooltip>
<span v-if="i != state.tags.length - 1" class="color-primary"> / </span>
</span>
</el-popover>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, onMounted } from 'vue';
import { tagApi } from '../tag/api';
const props = defineProps({
tagPath: {
type: [String],
required: true,
},
})
const state = reactive({
tagPath: '',
tags: [] as any,
})
const {
tags,
} = toRefs(state)
onMounted(async () => {
state.tagPath = props.tagPath;
})
const showTagInfo = async () => {
if (state.tags && state.tags.length > 0) {
return;
}
const tagStrs = state.tagPath.split('/');
const tagPaths = [];
let nowTag = '';
for (let tagStr of tagStrs) {
if (nowTag) {
nowTag = `${nowTag}/${tagStr}`
} else {
nowTag = tagStr
}
tagPaths.push(nowTag)
}
state.tags = await tagApi.listByQuery.request({ tagPaths: tagPaths.join(',') })
}
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,124 @@
<template>
<div class="instances-box layout-aside">
<el-row type="flex" justify="space-between">
<el-col :span="24"
:style="{ maxHeight: instanceMenuMaxHeight, height: instanceMenuMaxHeight, overflow: 'auto' }"
class="el-scrollbar flex-auto">
<el-menu background-color="transparent" :collapse-transition="false" ref="menuRef">
<!-- 第一级tag -->
<el-sub-menu v-for="tag of tags" :index="tag.tagPath" :key="tag.tagPath"
@click.stop="clickTag(tag.tagPath)">
<template #title>
<tag-info :tag-path="tag.tagPath" />
<el-icon>
<FolderOpened v-if="opend[tag.tagPath]" color="#e6a23c" />
<Folder v-else />
</el-icon>
<span>{{ tag.tagPath }}</span>
</template>
<slot :tag="tag" name="submenu"></slot>
</el-sub-menu>
</el-menu>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, Ref, toRefs } from 'vue';
import TagInfo from './TagInfo.vue';
const props = defineProps({
instanceMenuMaxHeight: {
type: [Number, String],
},
tags: {
type: Object, required: true
},
})
const menuRef = ref(null) as Ref
const state = reactive({
instanceMenuMaxHeight: props.instanceMenuMaxHeight,
tags: props.tags,
opend: {},
})
const {
opend,
} = toRefs(state)
const clickTag = (tagPath: string) => {
if (state.opend[tagPath] === undefined) {
state.opend[tagPath] = true;
return;
}
const opend = state.opend[tagPath]
state.opend[tagPath] = !opend
}
const open = (index: string, isTag: boolean = false) => {
if (!index) {
return;
}
menuRef.value.open(index)
if (isTag) {
clickTag(index)
}
}
defineExpose({
open
})
</script>
<style lang="scss">
.instances-box {
.el-menu {
width: 100%;
}
.el-sub-menu {
.checked {
.checked-schema {
color: var(--el-color-primary);
}
}
}
.el-sub-menu__title {
padding-left: 0 !important;
height: 30px !important;
line-height: 30px !important;
}
.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-sub-menu__title {
padding-right: 10px;
}
.el-menu-item {
padding-left: 0 !important;
height: 20px !important;
line-height: 20px !important;
}
.el-icon {
margin: 0;
}
.el-sub-menu__icon-arrow {
top: inherit;
right: 10px;
}
}
.instances-pop-form {
.el-form-item {
margin-bottom: unset;
}
}
</style>

View File

@@ -20,7 +20,14 @@
</el-radio>
</template>
</el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip>
<template #default="scope">
<tag-info :tag-path="scope.row.tagPath" />
<span class="ml5">
{{ scope.row.tagPath }}
</span>
</template>
</el-table-column>
<el-table-column prop="name" label="名称" min-width="160" show-overflow-tooltip></el-table-column>
<el-table-column min-width="170" label="host:port" show-overflow-tooltip>
<template #default="scope">
@@ -45,10 +52,9 @@
</el-input>
<div class="el-tag--plain el-tag--success" v-for="db in filterDb.list" :key="db"
style="border:1px var(--color-success-light-3) solid; margin-top: 3px;border-radius: 5px; padding: 2px;position: relative">
<el-link type="success" plain size="small" :underline="false"
@click="showTableInfo(scope.row, db)">{{ db }}</el-link>
<el-link type="success" plain size="small" :underline="false">{{ db }}</el-link>
<el-link type="primary" plain size="small" :underline="false"
@click="openSqlExec(scope.row, db)" style="position: absolute; right: 4px">数据操作
@click="showTableInfo(scope.row, db)" style="position: absolute; right: 4px">操作
</el-link>
</div>
</el-popover>
@@ -181,6 +187,8 @@
size="small">DELETE</el-tag>
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['INSERT'].value" color="#A8DEE0"
size="small">INSERT</el-tag>
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['QUERY'].value" color="#A8DEE0"
size="small">QUERY</el-tag>
</template>
</el-table-column>
<el-table-column prop="sql" label="SQL" min-width="230" show-overflow-tooltip> </el-table-column>
@@ -290,10 +298,9 @@ import config from '@/common/config';
import { getSession } from '@/common/utils/storage';
import { isTrue } from '@/common/assert';
import { Search as SearchIcon } from '@element-plus/icons-vue'
import router from '@/router';
import { store } from '@/store';
import { tagApi } from '../tag/api.ts';
import { dateFormat } from '@/common/utils/date';
import TagInfo from '../component/TagInfo.vue';
const permissions = {
saveDb: 'db:save',
@@ -695,20 +702,6 @@ const dropTable = async (row: any) => {
});
} catch (err) { }
};
const openSqlExec = (row: any, db: any) => {
// 判断db是否发生改变
let oldDb = store.state.sqlExecInfo.dbOptInfo.db;
if (db && oldDb !== db) {
const { tagPath, id } = row;
let params = {
tagPath,
dbId: id,
db
}
store.dispatch('sqlExecInfo/setSqlExecInfo', params);
}
router.push({ name: 'SqlExec' });
}
// 点击查看时初始化数据
const selectDb = (row: any) => {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,323 @@
<template>
<div>
<el-table @cell-dblclick="(row: any, column: any, cell: any, event: any) => cellClick(row, column, cell)"
@sort-change="(sort: any) => onTableSortChange(sort)" @selection-change="onDataSelectionChange"
:data="datas" size="small" :max-height="tableHeight" v-loading="loading" element-loading-text="查询中..."
:empty-text="emptyText" stripe border class="mt5">
<el-table-column v-if="datas.length > 0 && table" type="selection" width="35" />
<el-table-column min-width="100" :width="DbInst.flexColumnWidth(item, datas)" align="center"
v-for="item in columnNames" :key="item" :prop="item" :label="item" show-overflow-tooltip
:sortable="sortable">
<template #header v-if="showColumnTip">
<el-tooltip raw-content placement="top" effect="customized">
<template #content> {{ getColumnTip(item) }} </template>
{{ item }}
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script lang="ts" setup>
import { onMounted, watch, reactive, toRefs } from 'vue';
import { DbInst, UpdateFieldsMeta, FieldsMeta } from '../db';
const emits = defineEmits(['sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField'])
const props = defineProps({
dbId: {
type: Number,
required: true,
},
dbType: {
type: String,
default: ''
},
db: {
type: String,
required: true,
},
table: {
type: String,
default: '',
},
data: {
type: Array,
},
columnNames: {
type: Array,
},
sortable: {
type: [String, Boolean],
default: false,
},
loading: {
type: Boolean,
default: false,
},
emptyText: {
type: String,
default: '暂无数据',
},
showColumnTip: {
type: Boolean,
default: false,
},
height: {
type: String,
default: '600'
}
})
const state = reactive({
dbId: 0, // 当前选中操作的数据库实例
dbType: '',
db: '', // 数据库名
table: '', // 当前的表名
datas: [],
columnNames: [],
columns: [],
sortable: false,
loading: false,
selectionDatas: [] as any,
showColumnTip: false,
tableHeight: '600',
emptyText: '',
updatedFields: [] as UpdateFieldsMeta[],// 各个tab表被修改的字段信息
});
const {
tableHeight,
datas,
sortable,
loading,
columnNames,
showColumnTip,
} = toRefs(state);
watch(props, (newValue: any) => {
setState(newValue);
});
onMounted(async () => {
console.log('in DbTable mounted');
setState(props);
})
const setState = (props: any) => {
state.dbId = props.dbId;
state.dbType = props.dbType;
state.db = props.db;
state.table = props.table;
state.datas = props.data;
state.tableHeight = props.height;
state.sortable = props.sortable;
state.loading = props.loading;
state.columnNames = props.columnNames;
state.showColumnTip = props.showColumnTip;
state.emptyText = props.emptyText;
}
const getColumnTip = (columnName: string) => {
// 优先从 table map中获取
let columns = getNowDb().getColumns(state.table);
if (!columns) {
return '';
}
const column = columns.find((c: any) => c.columnName == columnName);
const comment = column.columnComment;
return `${column.columnType} ${comment ? ' | ' + comment : ''}`;
};
/**
* 表排序字段变更
*/
const onTableSortChange = async (sort: any) => {
if (!sort.prop) {
return;
}
cancelUpdateFields();
emits('sortChange', sort);
};
const onDataSelectionChange = (datas: []) => {
state.selectionDatas = datas;
emits('selectionChange', datas);
};
// 监听单元格点击事件
const cellClick = (row: any, column: any, cell: any) => {
const property = column.property;
// 如果当前操作的表名不存在 或者 当前列的property不存在(如多选框),则不允许修改当前单元格内容
if (!state.table || !property) {
return;
}
let div: HTMLElement = cell.children[0];
if (div && div.tagName === 'DIV') {
// 转为字符串比较,可能存在数字等
let text = (row[property] || row[property] == 0 ? row[property] : '') + '';
let input = document.createElement('input');
input.setAttribute('value', text);
// 将表格width也赋值于输入框避免输入框长度超过表格长度
input.setAttribute('style', 'height:23px;text-align:center;border:none;' + div.getAttribute('style'));
cell.replaceChildren(input);
input.focus();
input.addEventListener('blur', async () => {
row[property] = input.value;
cell.replaceChildren(div);
if (input.value !== text) {
let currentUpdatedFields = state.updatedFields
const dbInst = getNowDbInst();
// 主键
const primaryKey = await dbInst.loadTableColumn(state.db, state.table);
const primaryKeyValue = row[primaryKey.columnName];
// 更新字段列信息
const updateColumn = await dbInst.loadTableColumn(state.db, state.table, property);
const newField = {
div, row,
fieldName: column.rawColumnKey,
fieldType: updateColumn.columnType,
oldValue: text,
newValue: input.value
} as FieldsMeta;
// 被修改的字段
const primaryKeyFields = currentUpdatedFields.filter((meta) => meta.primaryKey === primaryKeyValue)
let hasKey = false;
if (primaryKeyFields.length <= 0) {
primaryKeyFields[0] = {
primaryKey: primaryKeyValue,
primaryKeyName: primaryKey.columnName,
primaryKeyType: primaryKey.columnType,
fields: [newField]
}
} else {
hasKey = true
let hasField = primaryKeyFields[0].fields.some(a => {
if (a.fieldName === newField.fieldName) {
a.newValue = newField.newValue
}
return a.fieldName === newField.fieldName
})
if (!hasField) {
primaryKeyFields[0].fields.push(newField)
}
}
let fields = primaryKeyFields[0].fields
const fieldsParam = fields.filter((a) => {
if (a.fieldName === column.rawColumnKey) {
a.newValue = input.value
}
return a.fieldName === column.rawColumnKey
})
const field = fieldsParam.length > 0 && fieldsParam[0] || {} as FieldsMeta
if (field.oldValue === input.value) { // 新值=旧值
// 删除数据
div.classList.remove('update_field_active')
let delIndex: number[] = [];
currentUpdatedFields.forEach((a, i) => {
if (a.primaryKey === primaryKeyValue) {
a.fields = a.fields && a.fields.length > 0 ? a.fields.filter(f => f.fieldName !== column.rawColumnKey) : [];
a.fields.length <= 0 && delIndex.push(i)
}
});
delIndex.forEach(i => delete currentUpdatedFields[i])
currentUpdatedFields = currentUpdatedFields.filter(a => a)
} else {
// 新增数据
div.classList.add('update_field_active')
if (hasKey) {
currentUpdatedFields.forEach((value, index, array) => {
if (value.primaryKey === primaryKeyValue) {
array[index].fields = fields
}
})
} else {
currentUpdatedFields.push({
primaryKey: primaryKeyValue,
primaryKeyName: primaryKey.columnName,
primaryKeyType: primaryKey.columnType,
fields
})
}
}
state.updatedFields = currentUpdatedFields;
changeUpdatedField();
}
});
}
};
const submitUpdateFields = () => {
let currentUpdatedFields = state.updatedFields;
if (currentUpdatedFields.length <= 0) {
return;
}
const db = state.db;
let res = '';
let divs: HTMLElement[] = [];
currentUpdatedFields.forEach(a => {
let sql = `UPDATE ${state.table} SET `;
let primaryKey = a.primaryKey;
let primaryKeyType = a.primaryKeyType;
let primaryKeyName = a.primaryKeyName;
a.fields.forEach(f => {
sql += ` ${f.fieldName} = ${DbInst.wrapColumnValue(f.fieldType, f.newValue)},`
divs.push(f.div)
})
sql = sql.substring(0, sql.length - 1)
sql += ` WHERE ${primaryKeyName} = ${DbInst.wrapColumnValue(primaryKeyType, primaryKey)} ;`
res += sql;
})
DbInst.getInst(state.dbId).promptExeSql(db, res, () => { }, () => {
currentUpdatedFields = [];
divs.forEach(a => {
a.classList.remove('update_field_active');
})
state.updatedFields = [];
changeUpdatedField();
});
}
const cancelUpdateFields = () => {
state.updatedFields.forEach((a: any) => {
a.fields.forEach((b: any) => {
b.div.classList.remove('update_field_active')
b.row[b.fieldName] = b.oldValue
})
})
state.updatedFields = [];
changeUpdatedField();
}
const changeUpdatedField = () => {
emits('changeUpdatedField', state.updatedFields);
}
const getNowDb = () => {
return DbInst.getInst(state.dbId).getDb(state.db);
}
const getNowDbInst = () => {
return DbInst.getInst(state.dbId);
}
defineExpose({
submitUpdateFields,
cancelUpdateFields
})
</script>
<style lang="scss">
.update_field_active {
background-color: var(--el-color-success)
}
</style>

View File

@@ -0,0 +1,346 @@
<template>
<tag-menu :instanceMenuMaxHeight="instanceMenuMaxHeight" :tags="tags" ref="menuRef">
<template #submenu="props">
<!-- 第二级数据库实例 -->
<el-sub-menu v-for="inst in tree[props.tag.tagId]" :index="'instance-' + inst.id"
:key="'instance-' + inst.id" @click.stop="changeInstance(inst, () => { })">
<template #title>
<el-popover placement="right-start" title="数据库实例信息" trigger="hover" :width="210">
<template #reference>
<span class="ml10">
<el-icon>
<MostlyCloudy color="#409eff" />
</el-icon>{{ inst.name }}
</span>
</template>
<template #default>
<el-form class="instances-pop-form" label-width="55px" :size="'small'">
<el-form-item label="类型:">{{ inst.type }}</el-form-item>
<el-form-item label="链接:">{{ inst.host }}:{{ inst.port }}</el-form-item>
<el-form-item label="用户:">{{ inst.username }}</el-form-item>
<el-form-item v-if="inst.remark" label="备注:">{{ inst.remark }}</el-form-item>
</el-form>
</template>
</el-popover>
</template>
<el-menu-item v-if="dbs[inst.id]?.length > 20" :index="'schema-filter-' + inst.id"
:key="'schema-filter-' + inst.id">
<template #title>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<el-input size="small" placeholder="过滤数据库" clearable @change="filterSchemaName(inst.id)"
@keyup="(e: any) => filterSchemaName(inst.id, e)"
v-model="state.schemaFilterParam[inst.id]" />
</template>
</el-menu-item>
<!-- 第三级数据库 -->
<el-sub-menu v-show="schema.show" v-for="schema in dbs[inst.id]" :index="inst.id + schema.name"
:key="inst.id + schema.name" :class="state.nowSchema === (inst.id + schema.name) && 'checked'"
@click.stop="changeSchema(inst, schema.name)">
<template #title>
<span class="checked-schema ml20">
<el-icon>
<Coin color="#67c23a" />
</el-icon>
<span v-html="schema.showName || schema.name"></span>
</span>
</template>
<!-- 第四级 01 -->
<el-sub-menu :index="inst.id + schema.name + '-table'">
<template #title>
<div class="ml30" style="width: 100%" @click="loadSchemaTables(inst, schema.name)">
<el-icon>
<Calendar color="#409eff" />
</el-icon>
<span></span>
<el-icon v-show="state.loading[inst.id + schema.name]" class="is-loading">
<Loading />
</el-icon>
</div>
</template>
<el-menu-item v-if="tables[inst.id + schema.name]?.length > 20"
:index="inst.id + schema.name + '-tableSearch'"
:key="inst.id + schema.name + '-tableSearch'">
<template #title>
<span class="ml35">
<el-input size="small" placeholder="表名、备注过滤表" clearable
@change="filterTableName(inst.id, schema.name)"
@keyup="(e: any) => filterTableName(inst.id, schema.name, e)"
v-model="state.filterParam[inst.id + schema.name]" />
</span>
</template>
</el-menu-item>
<template v-for="tb in tables[inst.id + schema.name]">
<el-menu-item :index="inst.id + schema.name + tb.tableName"
:key="inst.id + schema.name + tb.tableName" v-if="tb.show"
@click="clickSchemaTable(inst, schema.name, tb.tableName)">
<template #title>
<div class="ml35" style="width: 100%">
<el-icon>
<Calendar color="#409eff" />
</el-icon>
<el-tooltip v-if="tb.tableComment" effect="customized"
:content="tb.tableComment" placement="right">
<span v-html="tb.showName || tb.tableName"></span>
</el-tooltip>
<span v-else v-html="tb.showName || tb.tableName"></span>
</div>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<!-- 第四级 02sql -->
<el-sub-menu @click.stop="loadSqls(inst, schema.name)" :index="inst.id + schema.name + '-sql'">
<template #title>
<span class="ml30">
<el-icon>
<List color="#f56c6c" />
</el-icon>
<span>sql</span>
</span>
</template>
<template v-for="sql in sqls[inst.id + schema.name]">
<el-menu-item v-if="sql.show" :index="inst.id + schema.name + sql.name"
:key="inst.id + schema.name + sql.name"
@click="clickSqlName(inst, schema.name, sql.name)">
<template #title>
<div class="ml35" style="width: 100%">
<el-icon>
<Document />
</el-icon>
<span>{{ sql.name }}</span>
</div>
</template>
</el-menu-item>
</template>
</el-sub-menu>
</el-sub-menu>
</el-sub-menu>
</template>
</tag-menu>
</template>
<script lang="ts" setup>
import { onBeforeMount, reactive, toRefs } from 'vue';
import TagMenu from '../../component/TagMenu.vue';
import { dbApi } from '../api';
import { DbInst } from '../db';
const emits = defineEmits(['changeInstance', 'clickSqlName', 'clickSchemaTable', 'changeSchema', 'loadSqlNames'])
onBeforeMount(async () => {
await loadInstances();
state.instanceMenuMaxHeight = window.innerHeight - 140 + 'px';
})
const state = reactive({
tags: {},
tree: {},
dbs: {},
tables: {},
sqls: {},
nowSchema: '',
filterParam: {},
schemaFilterParam: {},
loading: {},
instanceMenuMaxHeight: '850px',
})
const {
instanceMenuMaxHeight,
tags,
tree,
dbs,
sqls,
tables,
} = toRefs(state)
// 加载实例数据
const loadInstances = async () => {
const res = await dbApi.dbs.request({ pageNum: 1, pageSize: 1000, })
if (!res.total) return
// state.instances = { tags: {}, tree: {}, dbs: {}, tables: {}, sqls: {} }; // 初始化变量
for (const db of res.list) {
let arr = state.tree[db.tagId] || []
const { tagId, tagPath } = db
// tags
state.tags[db.tagId] = { tagId, tagPath }
// tree
arr.push(db)
state.tree[db.tagId] = arr;
// dbs
let databases = db.database.split(' ')
let dbs = [] as any[];
databases.forEach((a: string) => dbs.push({ name: a, show: true }))
state.dbs[db.id] = dbs
}
}
/**
* 改变选中的数据库实例
* @param inst 选中的实例对象
* @param fn 选中的实例对象后的回调函数
*/
const changeInstance = (inst: any, fn: Function) => {
emits('changeInstance', inst, fn)
}
/**
* 改变选中的数据库schema
* @param inst 选中的实例对象
* @param schema 选中的数据库schema
*/
const changeSchema = (inst: any, schema: string) => {
state.nowSchema = inst.id + schema
emits('changeSchema', inst, schema)
}
/** 加载schema下所有表
*
* @param inst 数据库实例
* @param schema database名
*/
const loadSchemaTables = async (inst: any, schema: string) => {
const key = getSchemaKey(inst.id, schema);
state.loading[key] = true
try {
let { id } = inst
let tables = await DbInst.getInst(id).loadTables(schema);
tables && tables.forEach((a: any) => a.show = true)
state.tables[key] = tables;
changeSchema(inst, schema);
} finally {
state.loading[key] = false
}
}
/**
* 加载选中表数据
* @param inst 数据库实例
* @param schema database名
* @param tableName 表名
*/
const clickSchemaTable = (inst: any, schema: string, tableName: string) => {
emits('clickSchemaTable', inst, schema, tableName)
}
const filterTableName = (instId: number, schema: string, event?: any) => {
const key = getSchemaKey(instId, schema)
if (event) {
state.filterParam[key] = event.target.value
}
let param = state.filterParam[key] as string
state.tables[key].forEach((a: any) => {
let { match, showName } = matchAndHighLight(param, a.tableName + a.tableComment, a.tableName)
a.show = match;
a.showName = showName
})
}
const filterSchemaName = (instId: number, event?: any) => {
if (event) {
state.schemaFilterParam[instId] = event.target.value
}
let param = state.schemaFilterParam[instId] as string
param = param?.replace('/', '\/')
state.dbs[instId].forEach((a: any) => {
let { match, showName } = matchAndHighLight(param, a.name, a.name)
a.show = match
a.showName = showName
})
}
const matchAndHighLight = (searchParam: string, param: string, title: string): { match: boolean, showName: string } => {
if (!searchParam) {
return { match: true, showName: '' }
}
let str = '';
for (let c of searchParam?.replace('/', '\/')) {
str += `(${c}).*`
}
let regex = eval(`/${str}/i`)
let res = param.match(regex);
if (res?.length) {
if (res?.length) {
let tmp = '', showName = '';
for (let i = 1; i <= res.length - 1; i++) {
let head = (tmp || title).replace(res[i], `###${res[i]}!!!`);
let idx = head.lastIndexOf('!!!') + 3;
tmp = head.substring(idx);
showName += head.substring(0, idx)
if (!tmp) {
break
}
}
showName += tmp;
showName = showName.replaceAll('###', '<span style="color: red">')
showName = showName.replaceAll('!!!', '</span>')
return { match: true, showName }
}
}
return { match: false, showName: '' }
}
/**
* 加载用户保存的sql脚本
*
* @param inst
* @param schema
*/
const loadSqls = async (inst: any, schema: string) => {
const key = getSchemaKey(inst.id, schema)
let sqls = state.sqls[key];
if (!sqls) {
const sqls = await dbApi.getSqlNames.request({ id: inst.id, db: schema, })
sqls && sqls.forEach((a: any) => a.show = true)
state.sqls[key] = sqls;
} else {
sqls.forEach((a: any) => a.show = true);
}
}
const reloadSqls = async (inst: any, schema: string) => {
const sqls = await dbApi.getSqlNames.request({ id: inst.id, db: schema, })
sqls && sqls.forEach((a: any) => a.show = true)
state.sqls[getSchemaKey(inst.id, schema)] = sqls;
}
/**
* 点击sql模板名称时间加载用户保存的指定名称的sql内容并回调子组件指定事件
*/
const clickSqlName = async (inst: any, schema: string, sqlName: string) => {
emits('clickSqlName', inst, schema, sqlName)
changeSchema(inst, schema);
}
/**
* 根据实例以及库获取对应的唯一id
*
* @param inst 数据库实例
* @param schema 数据库
*/
const getSchemaKey = (instId: any, schema: string) => {
return instId + schema;
}
const getSchemas = (dbId: any) => {
return state.dbs[dbId] || []
}
defineExpose({
getSchemas,
reloadSqls,
})
</script>
<style lang="scss">
.instances-pop-form {
.el-form-item {
margin-bottom: unset;
}
}
</style>

View File

@@ -0,0 +1,548 @@
<template>
<div>
<div>
<div class="toolbar">
<div class="fl">
<el-link @click="onRunSql()" :underline="false" class="ml15" icon="VideoPlay">
</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-tooltip class="box-item" effect="dark" content="format sql" placement="top">
<el-link @click="formatSql()" type="primary" :underline="false" icon="MagicStick">
</el-link>
</el-tooltip>
<el-divider direction="vertical" border-style="dashed" />
<el-tooltip class="box-item" effect="dark" content="commit" placement="top">
<el-link @click="onCommit()" type="success" :underline="false" icon="CircleCheck">
</el-link>
</el-tooltip>
<el-divider direction="vertical" border-style="dashed" />
<el-upload class="sql-file-exec" :before-upload="beforeUpload" :on-success="execSqlFileSuccess"
:headers="{ Authorization: token }" :action="getUploadSqlFileUrl()" :show-file-list="false"
name="file" multiple :limit="100">
<el-tooltip class="box-item" effect="dark" content="SQL脚本执行" placement="top">
<el-link type="success" :underline="false" icon="Document"></el-link>
</el-tooltip>
</el-upload>
</div>
<div style="float: right" class="fl">
<el-button @click="saveSql()" type="primary" icon="document-add" plain size="small">保存SQL
</el-button>
<el-button v-if="sqlName" @click="deleteSql()" type="danger" icon="delete" plain size="small">删除SQL
</el-button>
</div>
</div>
</div>
<div class="mt5 sqlEditor">
<div :id="'MonacoTextarea-' + ti.key" :style="{ height: editorHeight }">
</div>
</div>
<div class="mt5">
<el-row>
<el-link v-if="table" @click="onDeleteData()" class="ml5" type="danger" icon="delete"
:underline="false"></el-link>
<span v-if="execRes.data.length > 0">
<el-divider direction="vertical" border-style="dashed" />
<el-link type="success" :underline="false" @click="exportData"><span
style="font-size: 12px">导出</span></el-link>
</span>
<span v-if="hasUpdatedFileds">
<el-divider direction="vertical" border-style="dashed" />
<el-link type="success" :underline="false" @click="submitUpdateFields()"><span
style="font-size: 12px">提交</span></el-link>
</span>
<span v-if="hasUpdatedFileds">
<el-divider direction="vertical" border-style="dashed" />
<el-link type="warning" :underline="false" @click="cancelUpdateFields"><span
style="font-size: 12px">取消</span></el-link>
</span>
</el-row>
<db-table ref="dbTableRef" :db-id="state.ti.dbId" :db="state.ti.db"
:data="execRes.data" :table="state.table" :column-names="execRes.tableColumn" :loading="loading"
height="250" empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
@selection-change="onDataSelectionChange" @change-updated-field="changeUpdatedField"></db-table>
</div>
</div>
</template>
<script lang="ts" setup>
import { nextTick, watch, onMounted, computed, reactive, toRefs, ref, Ref } from 'vue';
import { useStore } from '@/store/index.ts';
import { getSession } from '@/common/utils/storage';
import { isTrue, notBlank } from '@/common/assert';
import { format as sqlFormatter } from 'sql-formatter';
import config from '@/common/config';
import { ElMessage, ElMessageBox } from 'element-plus';
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
import * as monaco from 'monaco-editor';
import { editor } from 'monaco-editor';
// 主题仓库 https://github.com/brijeshb42/monaco-themes
// 主题例子 https://editor.bitwiser.in/
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json';
import DbTable from '../DbTable.vue'
import { TabInfo } from '../../db';
import { exportCsv } from '@/common/utils/export';
import { dateStrFormat } from '@/common/utils/date';
import { dbApi } from '../../api';
const emits = defineEmits(['saveSqlSuccess', 'deleteSqlSuccess'])
const props = defineProps({
data: {
type: TabInfo,
required: true
},
// sql脚本名若有则去加载该sql内容
sqlName: {
type: String,
default: '',
},
editorHeight: {
type: String,
default: '600'
}
})
const store = useStore();
const token = getSession('token');
let monacoEditor = {} as editor.IStandaloneCodeEditor;
const dbTableRef = ref(null) as Ref;
const state = reactive({
token,
ti: {} as TabInfo,
dbs: [],
dbId: null, // 当前选中操作的数据库实例
table: '', // 当前单表操作sql的表信息
sqlName: '',
sql: '', // 当前编辑器的sql内容
loading: false, // 是否在加载数据
execRes: {
data: [],
tableColumn: []
},
selectionDatas: [] as any,
editorHeight: '500',
hasUpdatedFileds: false,
});
const {
editorHeight,
ti,
execRes,
table,
sqlName,
loading,
hasUpdatedFileds,
} = toRefs(state);
watch(() => props.editorHeight, (newValue: any) => {
state.editorHeight = newValue;
});
onMounted(async () => {
console.log('in query mounted');
state.ti = props.data;
state.editorHeight = props.editorHeight;
const params = state.ti.params;
state.dbs = params && params.dbs;
if (params && params.sqlName) {
state.sqlName = params.sqlName;
const res = await dbApi.getSql.request({ id: state.ti.dbId, type: 1, name: state.sqlName, db: state.ti.db });
state.sql = res.sql;
}
nextTick(() => {
setTimeout(() => initMonacoEditor(), 50)
})
await state.ti.getNowDbInst().loadDbHints(state.ti.db);
})
// 获取布局配置信息
const getThemeConfig: any = computed(() => {
return store.state.themeConfig.themeConfig;
});
self.MonacoEnvironment = {
getWorker() {
return new EditorWorker();
}
};
const initMonacoEditor = () => {
let monacoTextarea = document.getElementById('MonacoTextarea-' + state.ti.key) as HTMLElement
// options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
// 初始化一些主题
monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
monacoEditor = monaco.editor.create(monacoTextarea, {
language: 'sql',
theme: getThemeConfig.value.editorTheme,
automaticLayout: true, //自适应宽高布局
folding: false,
roundedSelection: false, // 禁用选择文本背景的圆角
matchBrackets: 'near',
linkedEditing: true,
cursorBlinking: 'smooth',// 光标闪烁样式
mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时在编辑器中缩放字体
overviewRulerBorder: false, // 不要滚动条的边框
tabSize: 2, // tab 缩进长度
// fontFamily: 'JetBrainsMono', // 字体 暂时不要设置,否则光标容易错位
fontWeight: 'bold',
// letterSpacing: 1, 字符间距
// quickSuggestions:false, // 禁用代码提示
minimap: {
enabled: false, // 不要小地图
},
});
// 注册快捷键ctrl + R 运行选中的sql
monacoEditor.addAction({
// An unique identifier of the contributed action.
id: 'run-sql-action' + state.ti.key,
// A label of the action that will be presented to the user.
label: '执行SQL',
// A precondition for this action.
precondition: undefined,
// A rule to evaluate on top of the precondition in order to dispatch the keybindings.
keybindingContext: undefined,
keybindings: [
// chord
monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyR, 0)
],
contextMenuGroupId: 'navigation',
contextMenuOrder: 1.5,
// Method that will be executed when the action is triggered.
// @param editor The editor instance is passed in as a convenience
run: async function () {
try {
await onRunSql();
} catch (e: any) {
e.message && ElMessage.error(e.message)
}
}
});
// 注册快捷键ctrl + shift + f 格式化sql
monacoEditor.addAction({
// An unique identifier of the contributed action.
id: 'format-sql-action' + state.ti.key,
// A label of the action that will be presented to the user.
label: '格式化SQL',
// A precondition for this action.
precondition: undefined,
// A rule to evaluate on top of the precondition in order to dispatch the keybindings.
keybindingContext: undefined,
keybindings: [
// chord
monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, 0)
],
contextMenuGroupId: 'navigation',
contextMenuOrder: 2,
// Method that will be executed when the action is triggered.
// @param editor The editor instance is passed in as a convenience
run: async function () {
try {
await formatSql();
} catch (e: any) {
e.message && ElMessage.error(e.message)
}
}
});
// 动态设置主题
// monaco.editor.setTheme('hc-black');
// 如果sql有值则默认赋值
if (state.sql) {
monacoEditor.getModel()?.setValue(state.sql);
}
};
/**
* 执行sql
*/
const onRunSql = async () => {
// 没有选中的文本,则为全部文本
let sql = getSql() as string;
notBlank(sql && sql.trim(), '请选中需要执行的sql');
// 去除字符串前的空格、换行等
sql = sql.replace(/(^\s*)/g, '');
let execRemark = '';
let canRun = true;
if (
sql.startsWith('update') ||
sql.startsWith('UPDATE') ||
sql.startsWith('INSERT') ||
sql.startsWith('insert') ||
sql.startsWith('DELETE') ||
sql.startsWith('delete')
) {
const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^[\s\S]*.*[^\s][\s\S]*$/,
inputErrorMessage: '请输入执行该sql的备注信息',
});
execRemark = res.value;
if (!execRemark) {
canRun = false;
}
}
if (!canRun) {
return;
}
try {
state.loading = true;
const colAndData: any = await state.ti.getNowDbInst().runSql(state.ti.db, sql, execRemark);
if (!colAndData.res || colAndData.res.length === 0) {
ElMessage.warning('未查询到结果集')
}
state.execRes.data = colAndData.res;
state.execRes.tableColumn = colAndData.colNames;
cancelUpdateFields()
} catch (e: any) {
state.execRes.data = [];
state.execRes.tableColumn = [];
state.table = '';
return;
} finally {
state.loading = false;
}
// 即只有以该字符串开头的sql才可修改表数据内容
if (sql.startsWith('SELECT *') || sql.startsWith('select *') || sql.startsWith('SELECT\n *')) {
state.selectionDatas = [];
const tableName = sql.split(/from/i)[1];
if (tableName) {
const tn = tableName.trim().split(' ')[0];
state.table = tn;
state.table = tn;
} else {
state.table = '';
}
} else {
state.table = '';
}
};
/**
* 获取sql如果有鼠标选中则返回选中内容否则返回输入框内所有内容
*/
const getSql = () => {
let res = '' as string | undefined;
// 编辑器还没初始化
if (!monacoEditor?.getModel) {
return res;
}
// 选择选中的sql
let selection = monacoEditor.getSelection()
if (selection) {
res = monacoEditor.getModel()?.getValueInRange(selection)
}
// 整个编辑器的sql
if (!res) {
return monacoEditor.getModel()?.getValue()
}
return res
};
const saveSql = async () => {
const sql = monacoEditor.getModel()?.getValue();
notBlank(sql, 'sql内容不能为空');
let sqlName = state.sqlName;
const newSql = !sqlName;
if (newSql) {
try {
const input = await ElMessageBox.prompt('请输入SQL脚本名', 'SQL名', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern:
/\w+/,
inputErrorMessage: '请输入SQL脚本名',
});
sqlName = input.value;
state.sqlName = sqlName;
} catch (e) {
return;
}
}
await dbApi.saveSql.request({ id: state.ti.dbId, db: state.ti.db, sql: sql, type: 1, name: sqlName });
ElMessage.success('保存成功');
// 保存sql脚本成功事件
emits('saveSqlSuccess', state.ti.dbId, state.ti.db);
};
const deleteSql = async () => {
const sqlName = state.sqlName;
notBlank(sqlName, "该sql内容未保存");
const { dbId, db } = state.ti;
try {
await ElMessageBox.confirm(`确定删除【${sqlName}】该SQL内容?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
await dbApi.deleteDbSql.request({ id: dbId, db: db, name: sqlName });
ElMessage.success('删除成功');
emits('deleteSqlSuccess', dbId, db);
} catch (err) { }
};
/**
* 格式化sql
*/
const formatSql = () => {
let selection = monacoEditor.getSelection()
if (!selection) {
return;
}
let sql = monacoEditor.getModel()?.getValueInRange(selection)
// 有选中sql则格式化并替换选中sql, 否则格式化编辑器所有内容
if (sql) {
replaceSelection(sqlFormatter(sql), selection)
return;
}
monacoEditor.getModel()?.setValue(sqlFormatter(monacoEditor.getValue()));
};
/**
* 提交事务,用于没有开启自动提交事务
*/
const onCommit = () => {
state.ti.getNowDbInst().runSql(state.ti.db, 'COMMIT;');
ElMessage.success('COMMIT success');
};
/**
* 替换选中的内容
*/
const replaceSelection = (str: string, selection: any) => {
const model = monacoEditor.getModel();
if (!model) {
return;
}
if (!selection) {
model.setValue(str);
return;
}
const { startLineNumber, endLineNumber, startColumn, endColumn } = selection
const textBeforeSelection = model.getValueInRange({
startLineNumber: 1,
startColumn: 0,
endLineNumber: startLineNumber,
endColumn: startColumn,
})
const textAfterSelection = model.getValueInRange({
startLineNumber: endLineNumber,
startColumn: endColumn,
endLineNumber: model.getLineCount(),
endColumn: model.getLineMaxColumn(model.getLineCount()),
})
monacoEditor.setValue(textBeforeSelection + str + textAfterSelection)
monacoEditor.focus()
monacoEditor.setPosition({
lineNumber: startLineNumber,
column: 0,
})
}
/**
* 导出当前页数据
*/
const exportData = () => {
const dataList = state.execRes.data as any;
isTrue(dataList.length > 0, '没有数据可导出');
exportCsv(`数据查询导出-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, state.execRes.tableColumn, dataList)
};
const beforeUpload = (file: File) => {
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.ti.dbId}/exec-sql-file?db=${state.ti.db}`;
};
const onDataSelectionChange = (datas: []) => {
state.selectionDatas = datas;
};
const changeUpdatedField = (updatedFields: []) => {
// 如果存在要更新字段,则显示提交和取消按钮
state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
}
/**
* 执行删除数据事件
*/
const onDeleteData = async () => {
const deleteDatas = state.selectionDatas;
isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
const { db } = state.ti;
const dbInst = state.ti.getNowDbInst()
const primaryKey = await dbInst.loadTableColumn(db, state.table);
const primaryKeyColumnName = primaryKey.columnName;
dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
state.execRes.data = state.execRes.data.filter(
(d: any) => !(deleteDatas.findIndex((x: any) => x[primaryKeyColumnName] == d[primaryKeyColumnName]) != -1)
);
state.selectionDatas = [];
});
};
const submitUpdateFields = () => {
dbTableRef.value.submitUpdateFields();
}
const cancelUpdateFields = () => {
dbTableRef.value.cancelUpdateFields();
}
</script>
<style lang="scss">
.sql-file-exec {
display: inline-flex;
flex-direction: row;
align-items: center;
justify-content: center;
vertical-align: middle;
position: relative;
text-decoration: none;
}
.sqlEditor {
font-size: 8pt;
font-weight: 600;
border: 1px solid #ccc;
}
.update_field_active {
background-color: var(--el-color-success)
}
</style>

View File

@@ -0,0 +1,348 @@
<template>
<div>
<el-row>
<el-col :span="8">
<el-link @click="onRefresh()" icon="refresh" :underline="false" class="ml5">
</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click="addRow()" type="primary" icon="plus" :underline="false"></el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click="onDeleteData()" type="danger" icon="delete" :underline="false"></el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-tooltip class="box-item" effect="dark" content="commit" placement="top">
<el-link @click="onCommit()" type="success" icon="CircleCheck" :underline="false">
</el-link>
</el-tooltip>
<el-divider direction="vertical" border-style="dashed" />
<el-tooltip class="box-item" effect="dark" content="生成insert sql" placement="top">
<el-link @click="onGenerateInsertSql()" type="success" :underline="false">gi</el-link>
</el-tooltip>
<el-divider direction="vertical" border-style="dashed" />
<el-tooltip class="box-item" effect="dark" content="导出当前页的csv文件" placement="top">
<el-link type="success" :underline="false" @click="exportData"><span class="f12">导出</span></el-link>
</el-tooltip>
<el-divider direction="vertical" border-style="dashed" />
<el-tooltip v-if="hasUpdatedFileds" class="box-item" effect="dark" content="提交修改" placement="top">
<el-link @click="submitUpdateFields()" type="success" :underline="false" class="f12">提交</el-link>
</el-tooltip>
<el-divider v-if="hasUpdatedFileds" direction="vertical" border-style="dashed" />
<el-tooltip v-if="hasUpdatedFileds" class="box-item" effect="dark" content="取消修改" placement="top">
<el-link @click="cancelUpdateFields" type="warning" :underline="false" class="f12">取消</el-link>
</el-tooltip>
</el-col>
<el-col :span="16">
<el-input v-model="condition" placeholder="若需条件过滤,可选择列并点击对应的字段并输入需要过滤的内容点击查询按钮即可" clearable size="small"
style="width: 100%">
<template #prepend>
<el-popover trigger="click" :width="320" placement="right">
<template #reference>
<el-link type="success" :underline="false">选择列</el-link>
</template>
<el-table :data="columns" max-height="500" size="small" @row-click="
(...event: any) => {
onConditionRowClick(event);
}
" style="cursor: pointer">
<el-table-column property="columnName" label="列名" show-overflow-tooltip>
</el-table-column>
<el-table-column property="columnComment" label="备注" show-overflow-tooltip>
</el-table-column>
</el-table>
</el-popover>
</template>
<template #append>
<el-button @click="onSelectByCondition()" icon="search" size="small"></el-button>
</template>
</el-input>
</el-col>
</el-row>
<db-table ref="dbTableRef" :db-id="state.ti.dbId" :db="state.ti.db" :data="datas"
:table="state.table" :column-names="columnNames" :loading="loading" :height="tableHeight"
:show-column-tip="true" :sortable="'custom'" @sort-change="(sort: any) => onTableSortChange(sort)"
@selection-change="onDataSelectionChange" @change-updated-field="changeUpdatedField"></db-table>
<el-row type="flex" class="mt5" justify="center">
<el-pagination small :total="count" @current-change="pageChange()" layout="prev, pager, next, total, jumper"
v-model:current-page="pageNum" :page-size="DbInst.DefaultLimit"></el-pagination>
</el-row>
<div style=" font-size: 12px; padding: 0 10px; color: #606266"><span>{{ state.sql }}</span>
</div>
<el-dialog v-model="conditionDialog.visible" :title="conditionDialog.title" width="420px">
<el-row>
<el-col :span="5">
<el-select v-model="conditionDialog.condition">
<el-option label="=" value="="> </el-option>
<el-option label="LIKE" value="LIKE"> </el-option>
<el-option label=">" value=">"> </el-option>
<el-option label=">=" value=">="> </el-option>
<el-option label="<" value="<"> </el-option>
<el-option label="<=" value="<="> </el-option>
</el-select>
</el-col>
<el-col :span="19">
<el-input v-model="conditionDialog.value" :placeholder="conditionDialog.placeholder" />
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancelCondition">取消</el-button>
<el-button type="primary" @click="onConfirmCondition">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { onMounted, watch, reactive, toRefs, ref, Ref } from 'vue';
import { isTrue, notEmpty, notBlank } from '@/common/assert';
import { ElMessage } from 'element-plus';
import { DbInst, TabInfo } from '../../db';
import { exportCsv } from '@/common/utils/export';
import { dateStrFormat } from '@/common/utils/date';
import DbTable from '../DbTable.vue'
const emits = defineEmits(['genInsertSql'])
const props = defineProps({
data: {
type: TabInfo,
required: true
},
tableHeight: {
type: String,
default: '600'
}
})
const dbTableRef = ref(null) as Ref;
const state = reactive({
ti: {} as TabInfo,
table: '', // 当前的表名
datas: [],
sql: '', // 当前数据tab执行的sql
orderBy: '',
condition: '', // 当前条件框的条件
loading: false, // 是否在加载数据
columnNames: [],
columns: [],
pageNum: 1,
count: 0,
selectionDatas: [] as any,
conditionDialog: {
title: '',
placeholder: '',
columnRow: null,
dataTab: null,
visible: false,
condition: '=',
value: null
},
tableHeight: '600',
hasUpdatedFileds: false,
});
const {
datas,
condition,
loading,
columns,
columnNames,
pageNum,
count,
hasUpdatedFileds,
conditionDialog,
} = toRefs(state);
watch(() => props.tableHeight, (newValue: any) => {
state.tableHeight = newValue;
});
onMounted(async () => {
console.log('in table data mounted');
state.ti = props.data;
state.tableHeight = props.tableHeight;
state.table = state.ti.params.table;
notBlank(state.table, "TableData组件params.table信息不能为空")
const columns = await state.ti.getNowDbInst().loadColumns(state.ti.db, state.table);
state.columns = columns;
state.columnNames = columns.map((t: any) => t.columnName);
await onRefresh();
})
const onRefresh = async () => {
// 查询条件置空
state.condition = '';
state.pageNum = 1;
await selectData();
}
/**
* 数据tab修改页数
*/
const pageChange = async () => {
await selectData();
};
/**
* 单表数据信息查询数据
*/
const selectData = async () => {
state.loading = true;
const dbInst = state.ti.getNowDbInst();
const { db } = state.ti;
try {
const countRes = await dbInst.runSql(db, DbInst.getDefaultCountSql(state.table));
state.count = countRes.res[0].count;
let sql = dbInst.getDefaultSelectSql(state.table, state.condition, state.orderBy, state.pageNum);
state.sql = sql;
if (state.count > 0) {
const colAndData: any = await dbInst.runSql(db, sql);
state.datas = colAndData.res;
} else {
state.datas = [];
}
} finally {
state.loading = false;
}
}
/**
* 导出当前页数据
*/
const exportData = () => {
const dataList = state.datas as any;
isTrue(dataList.length > 0, '没有数据可导出');
exportCsv(`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, state.columnNames, dataList)
};
/**
* 条件查询,点击列信息后显示输入对应的值
*/
const onConditionRowClick = (event: any) => {
const row = event[0];
state.conditionDialog.title = `请输入 [${row.columnName}] 的值`;
state.conditionDialog.placeholder = `${row.columnType} ${row.columnComment}`;
state.conditionDialog.columnRow = row;
state.conditionDialog.visible = true;
};
// 确认条件
const onConfirmCondition = () => {
const conditionDialog = state.conditionDialog;
let condition = state.condition;
if (condition) {
condition += ` AND `;
}
const row = conditionDialog.columnRow as any;
condition += `${row.columnName} ${conditionDialog.condition} `;
state.condition = condition + DbInst.wrapColumnValue(row.columnType, conditionDialog.value);
onCancelCondition();
};
const onCancelCondition = () => {
state.conditionDialog.visible = false;
state.conditionDialog.title = ``;
state.conditionDialog.placeholder = ``;
state.conditionDialog.value = null;
state.conditionDialog.columnRow = null;
state.conditionDialog.dataTab = null;
};
/**
* 提交事务,用于没有开启自动提交事务
*/
const onCommit = () => {
state.ti.getNowDbInst().runSql(state.ti.db, 'COMMIT;');
ElMessage.success('COMMIT success');
};
const onSelectByCondition = async () => {
notEmpty(state.condition, '条件不能为空');
state.pageNum = 1;
await selectData();
}
/**
* 表排序字段变更
*/
const onTableSortChange = async (sort: any) => {
if (!sort.prop) {
return;
}
const sortType = sort.order == 'descending' ? 'DESC' : 'ASC';
state.orderBy = `ORDER BY ${sort.prop} ${sortType}`;
await onRefresh();
};
const onDataSelectionChange = (datas: []) => {
state.selectionDatas = datas;
};
const changeUpdatedField = (updatedFields: []) => {
// 如果存在要更新字段,则显示提交和取消按钮
state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
}
/**
* 执行删除数据事件
*/
const onDeleteData = async () => {
const deleteDatas = state.selectionDatas;
isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
const { db } = state.ti;
const dbInst = state.ti.getNowDbInst()
dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
onRefresh();
});
};
const onGenerateInsertSql = async () => {
isTrue(state.selectionDatas && state.selectionDatas.length > 0, '请先选择数据');
emits('genInsertSql', state.ti.getNowDbInst().genInsertSql(state.ti.db, state.table, state.selectionDatas));
};
const submitUpdateFields = () => {
dbTableRef.value.submitUpdateFields();
}
const cancelUpdateFields = () => {
dbTableRef.value.cancelUpdateFields();
}
// 添加新数据行
const addRow = async () => {
const columns = state.ti.getNowDb().getColumns(state.table);
// key: 字段名value: 字段名提示
let obj: any = {};
columns.forEach((item: any) => {
obj[`${item.columnName}`] = `'${item.columnComment || ''} ${item.columnName}[${item.columnType}]${item.nullable == 'YES' ? '' : '[not null]'}'`;
});
let columnNames = Object.keys(obj).join(',');
let values = Object.values(obj).join(',');
let sql = `INSERT INTO ${state.table} (${columnNames}) VALUES (${values});`;
state.ti.getNowDbInst().promptExeSql(state.ti.db, sql, null, () => {
onRefresh();
});
};
</script>
<style lang="scss">
.update_field_active {
background-color: var(--el-color-success)
}
</style>

View File

@@ -0,0 +1,468 @@
/* eslint-disable no-unused-vars */
import { dbApi } from './api';
import SqlExecBox from './component/SqlExecBox';
const dbInstCache: Map<number, DbInst> = new Map();
export class DbInst {
/**
* 标签路径
*/
tagPath: string
/**
* 实例id
*/
id: number
/**
* 实例名
*/
name: string
/**
* 数据库类型, mysql postgres
*/
type: string
/**
* schema -> db
*/
dbs: Map<string, Db> = new Map()
/**
* 默认查询分页数量
*/
static DefaultLimit = 20;
/**
* 获取指定数据库实例,若不存在则新建并缓存
* @param dbName 数据库名
* @returns db实例
*/
getDb(dbName: string) {
if (!dbName) {
throw new Error('dbName不能为空')
}
let db = this.dbs.get(dbName)
if (db) {
return db;
}
console.info(`new db -> dbId: ${this.id}, dbName: ${dbName}`);
db = new Db();
db.name = dbName;
this.dbs.set(dbName, db);
return db;
}
/**
* 加载数据库表信息
* @param dbName 数据库名
* @returns 表信息
*/
async loadTables(dbName: string) {
const db = this.getDb(dbName);
// 优先从 table map中获取
let tables = db.tables;
if (tables) {
return tables;
}
console.log(`load tables -> dbName: ${dbName}`);
tables = await dbApi.tableMetadata.request({ id: this.id, db: dbName });
db.tables = tables;
return tables;
}
/**
* 获取表的所有列信息
* @param table 表名
*/
async loadColumns(dbName: string, table: string) {
const db = this.getDb(dbName);
// 优先从 table map中获取
let columns = db.getColumns(table);
if (columns) {
return columns;
}
console.log(`load columns -> dbName: ${dbName}, table: ${table}`);
columns = await dbApi.columnMetadata.request({
id: this.id,
db: dbName,
tableName: table,
});
db.columnsMap.set(table, columns);
return columns;
}
/**
* 获取指定表的指定信息
* @param table 表名
*/
async loadTableColumn(dbName: string, table: string, columnName?: string) {
// 确保该表的列信息都已加载
await this.loadColumns(dbName, table);
return this.getDb(dbName).getColumn(table, columnName);
}
/**
* 获取库信息提示
*/
async loadDbHints(dbName: string) {
const db = this.getDb(dbName);
if (db.tableHints) {
return db.tableHints;
}
console.log(`load db-hits -> dbName: ${dbName}`);
const hits = await dbApi.hintTables.request({ id: this.id, db: db.name, })
db.tableHints = hits;
return hits;
}
/**
* 执行sql
*
* @param sql sql
* @param remark 执行备注
*/
async runSql(dbName: string, sql: string, remark: string = '') {
return await dbApi.sqlExec.request({
id: this.id,
db: dbName,
sql: sql.trim(),
remark,
});
}
// 获取指定表的默认查询sql
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number = DbInst.DefaultLimit) {
const baseSql = `SELECT * FROM ${table} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''}`;
if (this.type == 'mysql') {
return `${baseSql} LIMIT ${(pageNum - 1) * limit}, ${limit};`;
}
if (this.type == 'postgres') {
return `${baseSql} OFFSET ${(pageNum - 1) * limit} LIMIT ${limit};`;
}
return baseSql;
}
/**
* 生成指定数据的insert语句
* @param dbName 数据库名
* @param table 表名
* @param datas 要生成的数据
*/
genInsertSql(dbName: string, table: string, datas: any[]): string {
if (!datas) {
return '';
}
const columns = this.getDb(dbName).getColumns(table);
const sqls = [];
for (let data of datas) {
let colNames = [];
let values = [];
for (let column of columns) {
const colName = column.columnName;
colNames.push(colName);
values.push(DbInst.wrapValueByType(data[colName]));
}
sqls.push(`INSERT INTO ${table} (${colNames.join(', ')}) VALUES(${values.join(', ')})`);
}
return sqls.join(';\n') + ';'
}
/**
* 生成根据主键删除的sql语句
* @param table 表名
* @param datas 要删除的记录
*/
genDeleteByPrimaryKeysSql(db: string, table: string, datas: any[]) {
const primaryKey = this.getDb(db).getColumn(table);
const primaryKeyColumnName = primaryKey.columnName;
const ids = datas.map((d: any) => `${DbInst.wrapColumnValue(primaryKey.columnType, d[primaryKeyColumnName])}`).join(',');
return `DELETE FROM ${table} WHERE ${primaryKeyColumnName} IN (${ids})`;
}
/*
* 弹框提示是否执行sql
*/
promptExeSql = (db: string, sql: string, cancelFunc: any = null, successFunc: any = null) => {
SqlExecBox({
sql, dbId: this.id, db,
runSuccessCallback: successFunc,
cancelCallback: cancelFunc,
});
};
/**
* 获取或新建dbInst如果缓存中不存在则新建否则直接返回
* @param inst 数据库实例,后端返回的列表接口中的信息
* @returns DbInst
*/
static getOrNewInst(inst: any) {
if (!inst) {
throw new Error('inst不能为空')
}
let dbInst = dbInstCache.get(inst.id);
if (dbInst) {
return dbInst;
}
console.info(`new dbInst: ${inst.id}, tagPath: ${inst.tagPath}`);
dbInst = new DbInst();
dbInst.tagPath = inst.tagPath;
dbInst.id = inst.id;
dbInst.name = inst.name;
dbInst.type = inst.type;
dbInstCache.set(dbInst.id, dbInst);
return dbInst;
}
/**
* 获取数据库实例id若不存在则新建一个并缓存
* @param dbId 数据库实例id
* @param dbType 第一次获取时为必传项,即第一次创建时
* @returns 数据库实例
*/
static getInst(dbId?: number): DbInst {
if (!dbId) {
throw new Error('dbId不能为空');
}
let dbInst = dbInstCache.get(dbId);
if (dbInst) {
return dbInst;
}
throw new Error('dbInst不存在! 请在合适调用点使用DbInst.newInst()新建该实例');
}
/**
* 清空所有实例缓存信息
*/
static clearAll() {
dbInstCache.clear();
}
/**
* 获取count sql
* @param table 表名
* @param condition 条件
* @returns count sql
*/
static getDefaultCountSql = (table: string, condition?: string) => {
return `SELECT COUNT(*) count FROM ${table} ${condition ? 'WHERE ' + condition : ''}`;
};
/**
* 根据返回值包装值,若值为字符串类型则添加''
* @param val 值
* @returns 包装后的值
*/
static wrapValueByType = (val: any) => {
if (val == null) {
return 'NULL';
}
if (typeof val == 'number') {
return val;
}
return `'${val}'`;
};
/**
* 根据字段类型包装字段值,如为字符串等则添加‘’,数字类型则直接返回即可
*/
static wrapColumnValue(columnType: string, value: any) {
if (columnType.match(/int|double|float|nubmer|decimal|byte|bit/gi)) {
return value;
}
return `'${value}'`;
};
/**
*
* @param str 字符串
* @param tableData 表数据
* @param flag 标志
* @returns 列宽度
*/
static flexColumnWidth = (str: any, tableData: any, flag = 'equal') => {
// str为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
// flag为可选值可不传该参数,传参时可选'max'或'equal',默认为'max'
// flag为'max'则设置列宽适配该列中最长的内容,flag为'equal'则设置列宽适配该列中第一行内容的长度。
str = str + '';
let columnContent = '';
if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
return;
}
if (!str || !str.length || str.length === 0 || str === undefined) {
return;
}
if (flag === 'equal') {
// 获取该列中第一个不为空的数据(内容)
for (let i = 0; i < tableData.length; i++) {
// 转为字符串后比较
if ((tableData[i][str] + '').length > 0) {
columnContent = tableData[i][str] + '';
break;
}
}
} else {
// 获取该列中最长的数据(内容)
let index = 0;
for (let i = 0; i < tableData.length; i++) {
if (tableData[i][str] === null) {
return;
}
const now_temp = tableData[i][str] + '';
const max_temp = tableData[index][str] + '';
if (now_temp.length > max_temp.length) {
index = i;
}
}
columnContent = tableData[index][str] + '';
}
const contentWidth: number = DbInst.getContentWidth(columnContent);
// 获取列名称的长度 加上排序图标长度
const columnWidth: number = DbInst.getContentWidth(str) + 43;
const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
return flexWidth + 'px';
};
/**
* 获取内容所需要占用的宽度
*/
static getContentWidth = (content: any): number => {
// 以下分配的单位长度可根据实际需求进行调整
let flexWidth = 0;
for (const char of content) {
if (flexWidth > 500) {
break;
}
if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'z')) {
// 如果是小写字母、数字字符分配8个单位宽度
flexWidth += 8.5;
continue;
}
if (char >= 'A' && char <= 'Z') {
flexWidth += 9;
continue;
}
if (char >= '\u4e00' && char <= '\u9fa5') {
// 如果是中文字符为字符分配16个单位宽度
flexWidth += 16;
} else {
// 其他种类字符为字符分配9个单位宽度
flexWidth += 8;
}
}
if (flexWidth > 500) {
// 设置最大宽度
flexWidth = 500;
}
return flexWidth;
};
}
/**
* 数据库实例信息
*/
class Db {
name: string // 库名
tables: [] // 数据库实例表信息
columnsMap: Map<string, any> = new Map // table -> columns
tableHints: any = null // 提示词
/**
* 获取指定表列信息前提需要dbInst.loadColumns
* @param table 表名
*/
getColumns(table: string) {
return this.columnsMap.get(table);
}
/**
* 获取指定表中的指定列名信息,若列名为空则默认返回主键
* @param table 表名
* @param columnName 列名
*/
getColumn(table: string, columnName: string = '') {
const cols = this.getColumns(table);
if (!columnName) {
const col = cols.find((c: any) => c.columnKey == 'PRI');
return col || cols[0];
}
return cols.find((c: any) => c.columnName == columnName);
}
}
export enum TabType {
/**
* 表数据
*/
TableData,
/**
* 查询框
*/
Query,
}
export class TabInfo {
/**
* tab唯一key。与label、name都一致
*/
key: string
/**
* 数据库实例id
*/
dbId: number
/**
* 库名
*/
db: string = ''
/**
* tab 类型
*/
type: TabType
/**
* tab需要的其他信息
*/
params: any
getNowDbInst() {
return DbInst.getInst(this.dbId);
}
getNowDb() {
return this.getNowDbInst().getDb(this.db);
}
}
/** 修改表字段所需数据 */
export type UpdateFieldsMeta = {
// 主键值
primaryKey: string
// 主键名
primaryKeyName: string
// 主键类型
primaryKeyType: string
// 新值
fields: FieldsMeta[]
}
export type FieldsMeta = {
// 字段所在div
div: HTMLElement
// 字段名
fieldName: string
// 字段所在的表格行数据
row: any
// 字段类型
fieldType: string
// 原值
oldValue: string
// 新值
newValue: string
}

View File

@@ -7,5 +7,6 @@ export default {
// 数据库sql执行类型
DbSqlExecTypeEnum: new Enum().add('UPDATE', 'UPDATE', 1)
.add('DELETE', 'DELETE', 2)
.add('INSERT', 'INSERT', 3),
.add('INSERT', 'INSERT', 3)
.add('QUERY', 'QUERY', 4),
}

View File

@@ -29,14 +29,21 @@
</el-radio>
</template>
</el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip>
<template #default="scope">
<tag-info :tag-path="scope.row.tagPath" />
<span class="ml5">
{{ scope.row.tagPath }}
</span>
</template>
</el-table-column>
<el-table-column prop="name" label="名称" min-width="140" show-overflow-tooltip></el-table-column>
<el-table-column prop="ip" label="ip:port" min-width="150">
<template #default="scope">
<el-link :disabled="scope.row.status == -1" @click="showMachineStats(scope.row)" type="primary"
:underline="false">{{
`${scope.row.ip}:${scope.row.port}`
}}</el-link>
:underline="false">
{{ `${scope.row.ip}:${scope.row.port}`}}
</el-link>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" min-width="80">
@@ -121,7 +128,8 @@
<el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</el-descriptions-item>
<el-descriptions-item :span="2" label="用户名">{{ infoDialog.data.username }}</el-descriptions-item>
<el-descriptions-item :span="1" label="认证方式">{{ infoDialog.data.authMethod == 1 ? 'Password' :
<el-descriptions-item :span="1" label="认证方式">{{
infoDialog.data.authMethod == 1 ? 'Password' :
'PublicKey'
}}</el-descriptions-item>
@@ -176,6 +184,7 @@ import ProcessList from './ProcessList.vue';
import MachineStats from './MachineStats.vue';
import MachineRec from './MachineRec.vue';
import { dateFormat } from '@/common/utils/date';
import TagInfo from '../component/TagInfo.vue';
const router = useRouter();
const state = reactive({

View File

@@ -1,95 +1,68 @@
<template>
<div>
<div class="toolbar">
<el-row type="flex" justify="space-between">
<el-col :span="24">
<el-form class="search-form" label-position="right" :inline="true">
<el-form-item label="标签">
<el-select @change="changeTag" @focus="getTags" v-model="query.tagPath" placeholder="请选择标签"
filterable style="width: 250px">
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="实例" label-width="40px">
<el-select v-model="mongoId" placeholder="请选择mongo" @change="changeMongo">
<el-option v-for="item in mongoList" :key="item.id" :label="item.name" :value="item.id">
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{ `
[${item.uri}]`
}}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="库" label-width="20px">
<el-select v-model="database" placeholder="请选择库" @change="changeDatabase" filterable>
<el-option v-for="item in databases" :key="item.Name" :label="item.Name"
:value="item.Name">
<span style="float: left">{{ item.Name }}</span>
<span style="float: right; color: #8492a6; margin-left: 4px; font-size: 13px">{{
` [${formatByteSize(item.SizeOnDisk)}]`
}}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="集合" label-width="40px">
<el-select v-model="collection" placeholder="请选择集合" @change="changeCollection" filterable>
<el-option v-for="item in collections" :key="item" :label="item" :value="item">
</el-option>
</el-select>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
<el-container id="data-exec" style="border: 1px solid #eee; margin-top: 1px">
<el-tabs @tab-remove="removeDataTab" @tab-click="onDataTabClick" style="width: 100%; margin-left: 5px"
v-model="activeName">
<el-tab-pane closable v-for="dt in dataTabs" :key="dt.name" :label="dt.name" :name="dt.name">
<el-row v-if="mongoId">
<el-link @click="findCommand(activeName)" icon="refresh" :underline="false" class="ml5">
</el-link>
<el-link @click="showInsertDocDialog" class="ml5" type="primary" icon="plus" :underline="false">
</el-link>
</el-row>
<el-row class="mt5 mb5">
<el-input ref="findParamInputRef" v-model="dt.findParamStr" placeholder="点击输入相应查询条件"
@focus="showFindDialog(dt.name)">
<template #prepend>查询参数</template>
</el-input>
</el-row>
<el-row>
<el-col :span="6" v-for="item in dt.datas" :key="item">
<el-card :body-style="{ padding: '0px', position: 'relative' }">
<el-input type="textarea" v-model="item.value" :rows="12" />
<div style="padding: 3px; float: right" class="mr5 mongo-doc-btns">
<el-row>
<el-col :span="4">
<mongo-instance-tree @init-load-instances="loadInstances" @change-instance="changeInstance"
@load-table-names="loadTableNames" @load-table-data="changeCollection"
:instances="state.instances" />
</el-col>
<el-col :span="20">
<el-container id="data-exec" style="border: 1px solid #eee; margin-top: 1px">
<el-tabs @tab-remove="removeDataTab" style="width: 100%; margin-left: 5px"
v-model="state.activeName">
<el-tab-pane closable v-for="dt in state.dataTabs" :key="dt.key" :label="dt.label"
:name="dt.key">
<el-row>
<el-col :span="2">
<div>
<el-link @click="onJsonEditor(item)" :underline="false" type="success"
icon="MagicStick"></el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click="onSaveDoc(item.value)" :underline="false" type="warning"
icon="DocumentChecked"></el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-popconfirm @confirm="onDeleteDoc(item.value)" title="确定删除该文档?">
<template #reference>
<el-link :underline="false" type="danger" icon="DocumentDelete">
</el-link>
</template>
</el-popconfirm>
<el-link @click="findCommand(state.activeName)" icon="refresh"
:underline="false" class="">
</el-link>
<el-link @click="showInsertDocDialog" class="ml5" type="primary" icon="plus"
:underline="false">
</el-link>
</div>
</div>
</el-card>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</el-container>
</el-col>
<el-col :span="22">
<el-input ref="findParamInputRef" v-model="dt.findParamStr" placeholder="点击输入相应查询条件"
@focus="showFindDialog(dt.key)">
<template #prepend>查询参数</template>
</el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="6" v-for="item in dt.datas" :key="item">
<el-card :body-style="{ padding: '0px', position: 'relative' }">
<el-input type="textarea" v-model="item.value" :rows="10" />
<div style="padding: 3px; float: right" class="mr5 mongo-doc-btns">
<div>
<el-link @click="onJsonEditor(item)" :underline="false" type="success"
icon="MagicStick"></el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click="onSaveDoc(item.value)" :underline="false"
type="warning" icon="DocumentChecked"></el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-popconfirm @confirm="onDeleteDoc(item.value)" title="确定删除该文档?">
<template #reference>
<el-link :underline="false" type="danger" icon="DocumentDelete">
</el-link>
</template>
</el-popconfirm>
</div>
</div>
</el-card>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</el-container>
</el-col>
</el-row>
<el-dialog width="600px" title="find参数" v-model="findDialog.visible">
<el-form label-width="70px">
@@ -116,7 +89,7 @@
</template>
</el-dialog>
<el-dialog width="60%" :title="`新增'${activeName}'集合文档`" v-model="insertDocDialog.visible"
<el-dialog width="60%" :title="`新增'${state.activeName}'集合文档`" v-model="insertDocDialog.visible"
:close-on-click-modal="false">
<monaco-editor v-model="insertDocDialog.doc" language="json" />
<template #footer>
@@ -127,9 +100,9 @@
</template>
</el-dialog>
<el-dialog width="60%" title="json编辑器" v-model="jsoneditorDialog.visible" @close="onCloseJsonEditDialog"
<el-dialog width="60%" title="json编辑器" v-model="jsonEditorDialog.visible" @close="onCloseJsonEditDialog"
:close-on-click-modal="false">
<monaco-editor v-model="jsoneditorDialog.doc" language="json" />
<monaco-editor v-model="jsonEditorDialog.doc" language="json" />
</el-dialog>
<div style="text-align: center; margin-top: 10px"></div>
@@ -138,29 +111,18 @@
<script lang="ts" setup>
import { mongoApi } from './api';
import { toRefs, ref, reactive, watch } from 'vue';
import { reactive, ref, toRefs } from 'vue';
import { ElMessage } from 'element-plus';
import { isTrue, notBlank, notNull } from '@/common/assert';
import { formatByteSize } from '@/common/utils/format';
import { tagApi } from '../tag/api.ts';
import { useStore } from '@/store/index.ts';
import { isTrue, notBlank } from '@/common/assert';
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
import MongoInstanceTree from '@/views/ops/mongo/MongoInstanceTree.vue';
const store = useStore();
const findParamInputRef: any = ref(null);
const state = reactive({
tags: [],
mongoList: [] as any,
query: {
tagPath: null,
},
mongoId: null, // 当前选择操作的mongo
database: '', // 当前选择操作的库
collection: '', //当前选中的collection
activeName: '', // 当前操作的tab
databases: [] as any,
collections: [] as any,
dataTabs: {} as any, // 数据tabs
findDialog: {
visible: false,
@@ -175,77 +137,43 @@ const state = reactive({
visible: false,
doc: '',
},
jsoneditorDialog: {
jsonEditorDialog: {
visible: false,
doc: '',
item: {} as any,
},
instances: { tags: {}, tree: {}, dbs: {}, tables: {} }
});
const {
tags,
mongoList,
query,
mongoId,
database,
collection,
activeName,
databases,
collections,
dataTabs,
findDialog,
insertDocDialog,
jsoneditorDialog,
jsonEditorDialog,
} = toRefs(state)
const searchMongo = async () => {
notNull(state.query.tagPath, '请先选择标签');
const res = await mongoApi.mongoList.request(state.query);
state.mongoList = res.list;
};
const changeTag = (tagPath: string) => {
state.databases = [];
state.collections = [];
state.mongoId = null;
state.collection = '';
state.database = '';
state.dataTabs = {};
if (tagPath != null) {
searchMongo();
const changeInstance = async (inst: any, fn: Function) => {
if (inst) {
if (!state.instances.dbs[inst.id]) {
const res = await mongoApi.databases.request({ id: inst.id });
state.instances.dbs[inst.id] = res.Databases;
fn && fn(res.Databases)
}
}
};
}
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
const loadTableNames = async (inst: any, database: string, fn: Function) => {
let tbs = await mongoApi.collections.request({ id: inst.id, database });
let tables = [];
for (let tb of tbs) {
tables.push({ tableName: tb, show: true })
}
state.instances.tables[inst.id + database] = tables
fn(tables)
}
const changeMongo = () => {
state.databases = [];
state.collections = [];
state.dataTabs = {};
getDatabases();
};
const getDatabases = async () => {
const res = await mongoApi.databases.request({ id: state.mongoId });
state.databases = res.Databases;
};
const changeDatabase = () => {
state.collections = [];
state.collection = '';
state.dataTabs = {};
getCollections();
};
const getCollections = async () => {
state.collections = await mongoApi.collections.request({ id: state.mongoId, database: state.database });
};
const changeCollection = () => {
const collection = state.collection;
let dataTab = state.dataTabs[collection];
const changeCollection = (inst: any, schema: string, collection: string) => {
const label = `${inst.id}:\`${schema}\`.${collection}`;
let dataTab = state.dataTabs[label];
if (!dataTab) {
// 默认查询参数
const findParam = {
@@ -253,29 +181,33 @@ const changeCollection = () => {
sort: '{"_id": -1}',
skip: 0,
limit: 12,
},
dataTab = {
name: collection,
datas: [],
findParamStr: JSON.stringify(findParam),
findParam,
};
state.dataTabs[collection] = dataTab;
};
state.dataTabs[label] = {
key: label,
label: label,
name: label,
mongoId: inst.id,
database: schema,
collection,
datas: [],
findParamStr: JSON.stringify(findParam),
findParam,
};
}
state.activeName = collection;
findCommand(collection);
state.activeName = label;
findCommand(label);
};
const showFindDialog = (collection: string) => {
const showFindDialog = (key: string) => {
// 获取当前tab的索引位置将其输入框失去焦点防止输入以及重复获取焦点
const dataTabNames = Object.keys(state.dataTabs);
for (let i = 0; i < dataTabNames.length; i++) {
if (collection == dataTabNames[i]) {
if (key == dataTabNames[i]) {
findParamInputRef.value[i].blur();
}
}
state.findDialog.findParam = state.dataTabs[collection].findParam;
state.findDialog.findParam = state.dataTabs[key].findParam;
state.findDialog.visible = true;
};
@@ -286,8 +218,8 @@ const confirmFindDialog = () => {
findCommand(state.activeName);
};
const findCommand = async (collection: string) => {
const dataTab = state.dataTabs[collection];
const findCommand = async (key: string) => {
const dataTab = getNowDataTab();
const findParma = dataTab.findParam;
let filter, sort;
try {
@@ -298,15 +230,15 @@ const findCommand = async (collection: string) => {
return;
}
const datas = await mongoApi.findCommand.request({
id: state.mongoId,
database: state.database,
collection,
id: dataTab.mongoId,
database: dataTab.database,
collection: dataTab.collection,
filter,
sort,
limit: findParma.limit || 12,
skip: findParma.skip || 0,
});
state.dataTabs[collection].datas = wrapDatas(datas);
state.dataTabs[key].datas = wrapDatas(datas);
};
/**
@@ -344,10 +276,11 @@ const onInsertDoc = async () => {
} catch (e) {
ElMessage.error('文档内容错误,无法解析为json对象');
}
const dataTab = getNowDataTab();
const res = await mongoApi.insertCommand.request({
id: state.mongoId,
database: state.database,
collection: state.activeName,
id: dataTab.mongoId,
database: dataTab.database,
collection: dataTab.collection,
doc: docObj,
});
isTrue(res.InsertedID, '新增失败');
@@ -357,13 +290,13 @@ const onInsertDoc = async () => {
};
const onJsonEditor = (item: any) => {
state.jsoneditorDialog.item = item;
state.jsoneditorDialog.doc = item.value;
state.jsoneditorDialog.visible = true;
state.jsonEditorDialog.item = item;
state.jsonEditorDialog.doc = item.value;
state.jsonEditorDialog.visible = true;
};
const onCloseJsonEditDialog = () => {
state.jsoneditorDialog.item.value = JSON.stringify(JSON.parse(state.jsoneditorDialog.doc), null, 4);
state.jsonEditorDialog.item.value = JSON.stringify(JSON.parse(state.jsonEditorDialog.doc), null, 4);
};
const onSaveDoc = async (doc: string) => {
@@ -371,10 +304,11 @@ const onSaveDoc = async (doc: string) => {
const id = docObj._id;
notBlank(id, '文档的_id属性不存在');
delete docObj['_id'];
const dataTab = getNowDataTab();
const res = await mongoApi.updateByIdCommand.request({
id: state.mongoId,
database: state.database,
collection: state.collection,
id: dataTab.mongoId,
database: dataTab.database,
collection: dataTab.collection,
docId: id,
update: { $set: docObj },
});
@@ -386,10 +320,11 @@ const onDeleteDoc = async (doc: string) => {
const docObj = parseDocJsonString(doc);
const id = docObj._id;
notBlank(id, '文档的_id属性不存在');
const dataTab = getNowDataTab();
const res = await mongoApi.deleteByIdCommand.request({
id: state.mongoId,
database: state.database,
collection: state.collection,
id: dataTab.mongoId,
database: dataTab.database,
collection: dataTab.collection,
docId: id,
});
isTrue(res.DeletedCount == 1, '删除失败');
@@ -409,15 +344,6 @@ const parseDocJsonString = (doc: string) => {
}
};
/**
* 数据tab点击
*/
const onDataTabClick = (tab: any) => {
const name = tab.props.name;
// 修改选择框绑定的表信息
state.collection = name;
};
const removeDataTab = (targetName: string) => {
const tabNames = Object.keys(state.dataTabs);
let activeName = state.activeName;
@@ -430,41 +356,28 @@ const removeDataTab = (targetName: string) => {
}
});
state.activeName = activeName;
// 如果移除最后一个数据tab则将选择框绑定的collection置空
if (activeName == targetName) {
state.collection = '';
} else {
state.collection = activeName;
}
delete state.dataTabs[targetName];
};
// 加载选中的tagPath
const setSelects = async (mongoDbOptInfo: any) => {
const { tagPath, dbId, db } = mongoDbOptInfo.dbOptInfo;
state.query.tagPath = tagPath
await searchMongo();
state.mongoId = dbId
await getDatabases();
state.database = db
await getCollections();
if (state.collection) {
state.collection = ''
state.dataTabs = {}
const loadInstances = async () => {
const res = await mongoApi.mongoList.request({ pageNum: 1, pageSize: 1000, });
if (!res.total) return
state.instances = { tags: {}, tree: {}, dbs: {}, tables: {} }; // 初始化变量
for (const db of res.list) {
let arr = state.instances.tree[db.tagId] || []
const { tagId, tagPath } = db
// tags
state.instances.tags[db.tagId] = { tagId, tagPath }
// 实例
arr.push(db)
state.instances.tree[db.tagId] = arr;
}
}
// 判断如果有数据则加载下拉选项
let mongoDbOptInfo = store.state.mongoDbOptInfo
if (mongoDbOptInfo.dbOptInfo.tagPath) {
setSelects(mongoDbOptInfo)
const getNowDataTab = () => {
return state.dataTabs[state.activeName]
}
// 监听选中操作的db变化并加载下拉选项
watch(store.state.mongoDbOptInfo, async (newValue) => {
await setSelects(newValue)
})
</script>
<style>

View File

@@ -0,0 +1,178 @@
<template>
<tag-menu :instanceMenuMaxHeight="state.instanceMenuMaxHeight" :tags="instances.tags" ref="menuRef">
<template #submenu="props">
<el-sub-menu v-for="inst in instances.tree[props.tag.tagId]" :index="'mongo-instance-' + inst.id"
:key="'mongo-instance-' + inst.id" @click.stop="changeInstance(inst, () => { })">
<template #title>
<el-popover placement="right-start" title="mongo数据库实例信息" trigger="hover" :width="210">
<template #reference>
<span>&nbsp;&nbsp;<el-icon>
<MostlyCloudy color="#409eff" />
</el-icon>{{ inst.name }}</span>
</template>
<template #default>
<el-form class="instances-pop-form" label-width="55px" :size="'small'">
<el-form-item label="名称:">{{ inst.name }}</el-form-item>
<el-form-item label="链接:">{{ inst.uri }}</el-form-item>
</el-form>
</template>
</el-popover>
</template>
<!-- 第三级数据库 -->
<el-sub-menu v-for="db in instances.dbs[inst.id]" :index="inst.id + db.Name" :key="inst.id + db.Name"
:class="state.nowSchema === (inst.id + db.Name) && 'checked'"
@click.stop="changeSchema(inst, db.Name)">
<template #title>
&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Coin color="#67c23a" />
</el-icon>
<span class="checked-schema">
{{ db.Name }}
<span style="color: #8492a6;font-size: 13px">[{{
formatByteSize(db.SizeOnDisk)
}}]</span>
</span>
</template>
<!-- 第四级 01 -->
<el-sub-menu :index="inst.id + db.Name + '-table'">
<template #title>
<div style="width: 100%" @click="loadTableNames(inst, db.Name, () => { })">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Calendar color="#409eff" />
</el-icon>
<span>集合</span>
<el-icon v-show="state.loading[inst.id + db.Name]" class="is-loading">
<Loading />
</el-icon>
</div>
</template>
<el-menu-item :index="inst.id + db.Name + '-tableSearch'"
:key="inst.id + db.Name + '-tableSearch'">
<template #title>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<el-input size="small" placeholder="过滤" clearable
@change="filterTableName(inst.id, db.Name)"
@keyup="(e: any) => filterTableName(inst.id, db.Name, e)"
v-model="state.filterParam[inst.id + db.Name]" />
</template>
</el-menu-item>
<template v-for="tb in instances.tables[inst.id + db.Name]">
<el-menu-item :index="inst.id + db.Name + tb.tableName"
:key="inst.id + db.Name + tb.tableName" v-if="tb.show"
@click="loadTableData(inst, db.Name, tb.tableName)">
<template #title>
<div style="width: 100%">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Calendar color="#409eff" />
</el-icon>
<span :title="tb.tableComment || ''">{{ tb.tableName }}</span>
</div>
</template>
</el-menu-item>
</template>
</el-sub-menu>
</el-sub-menu>
</el-sub-menu>
</template>
</tag-menu>
</template>
<script lang="ts" setup>
import { onBeforeMount, reactive } from 'vue';
import { formatByteSize } from '@/common/utils/format';
import TagMenu from '../component/TagMenu.vue';
const props = defineProps({
instances: {
type: Object, required: true
},
})
const emits = defineEmits(['initLoadInstances', 'changeInstance', 'loadTableNames', 'loadTableData', 'changeSchema'])
onBeforeMount(async () => {
await initLoadInstances()
setHeight()
})
const setHeight = () => {
state.instanceMenuMaxHeight = window.innerHeight - 115 + 'px';
}
const state = reactive({
instanceMenuMaxHeight: '800px',
nowSchema: '',
filterParam: {},
loading: {},
})
/**
* 初始化加载实例数据
*/
const initLoadInstances = () => {
emits('initLoadInstances')
}
/**
* 改变选中的数据库实例
* @param inst 选中的实例对象
* @param fn 选中的实例对象后的回调事件
*/
const changeInstance = (inst: any, fn: Function) => {
emits('changeInstance', inst, fn)
}
/**
* 改变选中的数据库schema
* @param inst 选中的实例对象
* @param schema 选中的数据库schema
*/
const changeSchema = (inst: any, schema: string) => {
state.nowSchema = inst.id + schema
emits('changeSchema', inst, schema)
}
/**
* 加载schema下所有表
* @param inst 数据库实例
* @param fn 加载表名后的回调函数参数表名list
* @param schema database名
*/
const loadTableNames = async (inst: any, schema: string, fn: Function) => {
state.loading[inst.id + schema] = true
await emits('loadTableNames', inst, schema, (res: any) => {
state.loading[inst.id + schema] = false
fn && fn(res)
})
}
/**
* 加载选中表数据
* @param inst 数据库实例
* @param schema database名
* @param tableName 表名
*/
const loadTableData = (inst: any, schema: string, tableName: string) => {
emits('loadTableData', inst, schema, tableName)
}
const filterTableName = (instId: number, schema: string, event?: any) => {
if (event) {
state.filterParam[instId + schema] = event.target.value
}
let param = state.filterParam[instId + schema] as string
param = param?.replace('/', '\/')
const key = instId + schema;
props.instances.tables[key].forEach((a: any) => {
a.show = param ? eval('/' + param.split('').join('[_\w]*') + '[_\w]*/ig').test(a.tableName) : true
})
}
</script>
<style lang="scss">
.instances-pop-form {
.el-form-item {
margin-bottom: unset;
}
}
</style>

View File

@@ -20,7 +20,14 @@
</el-radio>
</template>
</el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip>
<template #default="scope">
<tag-info :tag-path="scope.row.tagPath" />
<span class="ml5">
{{ scope.row.tagPath }}
</span>
</template>
</el-table-column>
<el-table-column prop="name" label="名称" width></el-table-column>
<el-table-column prop="uri" label="连接uri" min-width="150" show-overflow-tooltip>
<template #default="scope">
@@ -65,9 +72,6 @@
<el-divider direction="vertical" border-style="dashed" />
<el-link type="primary" @click="showCollections(scope.row.Name)" plain size="small"
:underline="false">集合</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link type="primary" @click="openDataOps(scope.row)" plain size="small" :underline="false">
数据操作</el-link>
</template>
</el-table-column>
</el-table>
@@ -195,9 +199,8 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { tagApi } from '../tag/api.ts';
import MongoEdit from './MongoEdit.vue';
import { formatByteSize } from '@/common/utils/format';
import { store } from '@/store';
import router from '@/router';
import { dateFormat } from '@/common/utils/date';
import TagInfo from '../component/TagInfo.vue';
const state = reactive({
tags: [],
@@ -406,24 +409,6 @@ const valChange = () => {
search();
};
const openDataOps = (row: any) => {
state.dbOps.db = row.Name
debugger
let data = {
tagPath: state.currentData.tagPath,
dbId: state.dbOps.dbId,
db: state.dbOps.db,
}
state.databaseDialog.visible = false;
// 判断db是否发生改变
let oldDb = store.state.mongoDbOptInfo.dbOptInfo.db;
if (oldDb !== row.Name) {
store.dispatch('mongoDbOptInfo/setMongoDbOptInfo', data);
}
router.push({ name: 'MongoDataOp' });
}
</script>
<style>

View File

@@ -1,34 +1,13 @@
<template>
<div>
<el-card>
<div style="float: left">
<el-row type="flex" justify="space-between">
<el-col :span="24">
<el-form class="search-form" label-position="right" :inline="true">
<el-form-item label="标签">
<el-select @change="changeTag" @focus="getTags" v-model="query.tagPath"
placeholder="请选择标签" filterable style="width: 250px">
<el-option v-for="item in tags" :key="item" :label="item" :value="item">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="redis" label-width="40px">
<el-select v-model="scanParam.id" placeholder="请选择redis" @change="changeRedis"
@clear="clearRedis" clearable style="width: 250px">
<el-option v-for="item in redisList" :key="item.id"
:label="`${item.name ? item.name : ''} [${item.host}]`" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="库" label-width="20px">
<el-select v-model="scanParam.db" @change="changeDb" placeholder="库"
style="width: 85px">
<el-option v-for="db in dbList" :key="db" :label="db" :value="db"> </el-option>
</el-select>
</el-form-item>
</el-form>
</el-col>
<el-col class="mt10">
<el-row>
<el-col :span="4">
<redis-instance-tree @init-load-instances="initLoadInstances" @change-instance="changeInstance"
@change-schema="loadInitSchema" :instances="state.instances" />
</el-col>
<el-col :span="20" style="border-left: 1px solid var(--el-card-border-color);">
<div class="mt10 ml5">
<el-col>
<el-form class="search-form" label-position="right" :inline="true" label-width="60px">
<el-form-item label="key" label-width="40px">
<el-input placeholder="match 支持*模糊key" style="width: 250px" v-model="scanParam.match"
@@ -55,35 +34,36 @@
</el-popover>
</el-form-item>
<div style="float: right">
<span>keys: {{ dbsize }}</span>
<span>keys: {{ state.dbsize }}</span>
</div>
</el-form>
</el-col>
</el-row>
</div>
<el-table v-loading="loading" :data="keys" stripe :highlight-current-row="true" style="cursor: pointer">
<el-table-column show-overflow-tooltip prop="key" label="key"></el-table-column>
<el-table-column prop="type" label="type" width="80">
<template #default="scope">
<el-tag :color="getTypeColor(scope.row.type)" size="small">{{ scope.row.type }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="ttl" label="ttl(过期时间)" width="140">
<template #default="scope">
{{ ttlConveter(scope.row.ttl) }}
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button @click="getValue(scope.row)" type="success" icon="search" plain size="small">查看
</el-button>
<el-button @click="del(scope.row.key)" type="danger" icon="delete" plain size="small">删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-table v-loading="state.loading" :data="state.keys" stripe :highlight-current-row="true"
style="cursor: pointer">
<el-table-column show-overflow-tooltip prop="key" label="key"></el-table-column>
<el-table-column prop="type" label="type" width="80">
<template #default="scope">
<el-tag :color="getTypeColor(scope.row.type)" size="small">{{ scope.row.type }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="ttl" label="ttl(过期时间)" width="140">
<template #default="scope">
{{ ttlConveter(scope.row.ttl) }}
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button @click="getValue(scope.row)" type="success" icon="search" plain
size="small">查看
</el-button>
<el-button @click="del(scope.row.key)" type="danger" icon="delete" plain size="small">删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-col>
</el-row>
<div style="text-align: center; margin-top: 10px"></div>
@@ -107,7 +87,7 @@
<script lang="ts" setup>
import { redisApi } from './api';
import { toRefs, reactive, watch } from 'vue';
import { toRefs, reactive } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import HashValue from './HashValue.vue';
import StringValue from './StringValue.vue';
@@ -115,10 +95,8 @@ import SetValue from './SetValue.vue';
import ListValue from './ListValue.vue';
import { isTrue, notBlank, notNull } from '@/common/assert';
import { useStore } from '@/store/index.ts';
import { tagApi } from '../tag/api.ts';
import RedisInstanceTree from '@/views/ops/redis/RedisInstanceTree.vue';
let store = useStore();
const state = reactive({
loading: false,
tags: [],
@@ -159,64 +137,18 @@ const state = reactive({
},
keys: [],
dbsize: 0,
instances: { tags: {}, tree: {}, dbs: {}, tables: {} }
});
const {
loading,
tags,
redisList,
dbList,
query,
scanParam,
dataEdit,
hashValueDialog,
stringValueDialog,
setValueDialog,
listValueDialog,
keys,
dbsize,
} = toRefs(state)
const searchRedis = async () => {
notBlank(state.query.tagPath, '请先选择标签');
const res = await redisApi.redisList.request(state.query);
state.redisList = res.list;
};
const changeTag = (tagPath: string) => {
clearRedis();
if (tagPath != null) {
searchRedis();
}
};
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
const changeRedis = (id: number) => {
resetScanParam();
if (id != 0) {
const redis: any = state.redisList.find((x: any) => x.id == id);
if (redis) {
state.dbList = (state.redisList.find((x: any) => x.id == id) as any).db.split(',');
state.scanParam.mode = redis.mode;
}
}
// 默认选中配置的第一个库
state.scanParam.db = state.dbList[0];
state.keys = [];
state.dbsize = 0;
};
const changeDb = () => {
resetScanParam();
state.keys = [];
state.dbsize = 0;
searchKey();
};
const scan = async () => {
isTrue(state.scanParam.id != null, '请先选择redis');
notBlank(state.scanParam.count, 'count不能为空');
@@ -256,15 +188,6 @@ const searchKey = async () => {
await scan();
};
const clearRedis = () => {
state.redisList = [];
state.scanParam.id = null;
resetScanParam();
state.scanParam.db = '';
state.keys = [];
state.dbsize = 0;
};
const clear = () => {
resetScanParam();
if (state.scanParam.id) {
@@ -328,20 +251,18 @@ const del = (key: string) => {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
redisApi.delKey
.request({
key,
id: state.scanParam.id,
db: state.scanParam.db,
})
.then(() => {
ElMessage.success('删除成功!');
searchKey();
});
})
.catch(() => { });
}).then(() => {
redisApi.delKey
.request({
key,
id: state.scanParam.id,
db: state.scanParam.db,
})
.then(() => {
ElMessage.success('删除成功!');
searchKey();
});
}).catch(() => { });
};
const ttlConveter = (ttl: any) => {
@@ -392,27 +313,45 @@ const getTypeColor = (type: string) => {
}
};
// 加载选中的db
const setSelects = async (redisDbOptInfo: any) => {
// 设置标签路径等
const { tagPath, dbId } = redisDbOptInfo.dbOptInfo;
state.query.tagPath = tagPath;
await searchRedis();
state.scanParam.id = dbId;
changeRedis(dbId);
changeDb();
};
// 判断如果有数据则加载下拉选项
let redisDbOptInfo = store.state.redisDbOptInfo;
if (redisDbOptInfo.dbOptInfo.tagPath) {
setSelects(redisDbOptInfo);
const initLoadInstances = async () => {
const res = await redisApi.redisList.request({});
if (!res.total) return
state.instances = { tags: {}, tree: {}, dbs: {}, tables: {} }; // 初始化变量
for (const db of res.list) {
let arr = state.instances.tree[db.tagId] || []
const { tagId, tagPath } = db
// tags
state.instances.tags[db.tagId] = { tagId, tagPath }
// 实例
arr.push(db)
state.instances.tree[db.tagId] = arr;
}
}
// 监听选中操作的db变化并加载下拉选项
watch(store.state.redisDbOptInfo, async (newValue) => {
await setSelects(newValue);
});
const changeInstance = async (inst: any, fn: Function) => {
let dbs = inst.db.split(',').map((x: string) => {
return { name: `db${x}`, keys: 0 }
})
const res = await redisApi.redisInfo.request({ id: inst.id, host: inst.host, section: "Keyspace" });
for (let db in res.Keyspace) {
for (let d of dbs) {
if (db == d.name) {
d.keys = res.Keyspace[db]?.split(',')[0]?.split('=')[1] || 0
}
}
}
state.instances.dbs[inst.id] = dbs
fn && fn(dbs)
}
/** 初始化加载db数据 */
const loadInitSchema = (inst: any, schema: string) => {
state.scanParam.id = inst.id
state.scanParam.db = schema.replace('db', '')
scan()
}
</script>

View File

@@ -1,152 +1,91 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :show-close="true" width="35%" @close="close()">
<el-collapse>
<el-collapse-item title="Server(Redis服务器的一般信息)" name="server">
<div class="row">
<span class="title">redis_version(版本):</span>
<span class="value">{{ info.Server.redis_version }}</span>
</div>
<div class="row">
<span class="title">tcp_port(端口):</span>
<span class="value">{{ info.Server.tcp_port }}</span>
</div>
<div class="row">
<span class="title">redis_mode(模式):</span>
<span class="value">{{ info.Server.redis_mode }}</span>
</div>
<div class="row">
<span class="title">os(宿主操作系统):</span>
<span class="value">{{ info.Server.os }}</span>
</div>
<div class="row">
<span class="title">uptime_in_days(运行天数):</span>
<span class="value">{{ info.Server.uptime_in_days }}</span>
</div>
<div class="row">
<span class="title">executable(可执行文件路径):</span>
<span class="value">{{ info.Server.executable }}</span>
</div>
<div class="row">
<span class="title">config_file(配置文件路径):</span>
<span class="value">{{ info.Server.config_file }}</span>
</div>
</el-collapse-item>
<el-dialog :title="title" v-model="dialogVisible" :show-close="true" width="1000px" @close="close()">
<el-collapse-item title="Clients(客户端连接)" name="client">
<div class="row">
<span class="title">connected_clients(已连接客户端数):</span>
<span class="value">{{ info.Clients.connected_clients }}</span>
</div>
<div class="row">
<span class="title">blocked_clients(正在等待阻塞命令客户端数):</span>
<span class="value">{{ info.Clients.blocked_clients }}</span>
</div>
</el-collapse-item>
<el-collapse-item title="Keyspace(key信息)" name="keyspace">
<div class="row" v-for="(value, key) in info.Keyspace" :key="key">
<span class="title">{{ key }}: </span>
<span class="value">{{ value }}</span>
</div>
</el-collapse-item>
<el-row :gutter="20">
<el-col :lg="16" :md="16">
<el-descriptions class="redis-info info-server" title="Redis服务器信息" :column="3" size="small" border>
<el-descriptions-item label="版本">{{ info.Server.redis_version }}</el-descriptions-item>
<el-descriptions-item label="端口">{{ info.Server.tcp_port }}</el-descriptions-item>
<el-descriptions-item label="PID">{{ info.Server.process_id }}</el-descriptions-item>
<el-descriptions-item label="模式">{{ info.Server.redis_mode }}</el-descriptions-item>
<el-descriptions-item label="操作系统">{{ info.Server.os }}</el-descriptions-item>
<el-descriptions-item label="运行天数">{{ info.Server.uptime_in_days }}</el-descriptions-item>
<el-descriptions-item label="可执行文件路径">{{ info.Server.executable }}</el-descriptions-item>
<el-descriptions-item label="配置文件路径">{{ info.Server.config_file }}</el-descriptions-item>
</el-descriptions>
</el-col>
<el-col :lg="8" :md="8" class="redis-info">
<div class="info-memory-chart" ref="memRef"></div>
</el-col>
</el-row>
<el-collapse-item title="Stats(统计)" name="state">
<div class="row">
<span class="title">total_commands_processed(总处理命令数):</span>
<span class="value">{{ info.Stats.total_commands_processed }}</span>
</div>
<div class="row">
<span class="title">instantaneous_ops_per_sec(当前qps):</span>
<span class="value">{{ info.Stats.instantaneous_ops_per_sec }}</span>
</div>
<div class="row">
<span class="title">total_net_input_bytes(网络入口流量字节数):</span>
<span class="value">{{ info.Stats.total_net_input_bytes }}</span>
</div>
<div class="row">
<span class="title">total_net_output_bytes(网络出口流量字节数):</span>
<span class="value">{{ info.Stats.total_net_output_bytes }}</span>
</div>
<div class="row">
<span class="title">expired_keys(过期key的总数量):</span>
<span class="value">{{ info.Stats.expired_keys }}</span>
</div>
<div class="row">
<span class="title">instantaneous_ops_per_sec(当前qps):</span>
<span class="value">{{ info.Stats.instantaneous_ops_per_sec }}</span>
</div>
</el-collapse-item>
<el-row :gutter="20">
<el-col :lg="12" :md="12">
<el-descriptions class="redis-info info-cluster" title="节点信息" :column="3" size="small" border>
<el-descriptions-item label="是否启用集群模式">{{ info.Cluster.cluster_enabled }}</el-descriptions-item>
<el-descriptions-item label="DB总数">{{ info.Cluster.databases }}</el-descriptions-item>
<el-descriptions-item label="节点总数">{{ info.Cluster.nodecount }}</el-descriptions-item>
</el-descriptions>
</el-col>
<el-collapse-item title="Persistence(持久化)" name="persistence">
<div class="row">
<span class="title">aof_enabled(是否启用aof):</span>
<span class="value">{{ info.Persistence.aof_enabled }}</span>
</div>
<div class="row">
<span class="title">loading(是否正在载入持久化文件):</span>
<span class="value">{{ info.Persistence.loading }}</span>
</div>
</el-collapse-item>
<el-col :lg="12" :md="12">
<el-descriptions class="redis-info info-client" title="客户端连接" :column="3" size="small" border>
<el-descriptions-item label="已连接客户端数">{{ info.Clients.connected_clients
}}</el-descriptions-item>
<el-descriptions-item label="正在等待阻塞命令客户端数">{{ info.Clients.blocked_clients
}}</el-descriptions-item>
</el-descriptions>
</el-col>
</el-row>
<el-collapse-item title="Cluster(集群)" name="cluster">
<div class="row">
<span class="title">cluster_enabled(是否启用集群模式):</span>
<span class="value">{{ info.Cluster.cluster_enabled }}</span>
</div>
</el-collapse-item>
<el-descriptions class="redis-info info-memory" title="CPU" :column="2" size="small" border>
<el-descriptions-item label="系统CPU">{{ info.CPU.used_cpu_sys }}</el-descriptions-item>
<el-descriptions-item label="用户CPU">{{ info.CPU.used_cpu_user }}</el-descriptions-item>
<el-descriptions-item label="后台系统CPU">{{ info.CPU.used_cpu_sys_children }}</el-descriptions-item>
<el-descriptions-item label="后台用户CPU">{{ info.CPU.used_cpu_user_children }}</el-descriptions-item>
</el-descriptions>
<el-collapse-item title="Memory(内存消耗相关信息)" name="memory">
<div class="row">
<span class="title">used_memory(分配内存总量):</span>
<span class="value">{{ info.Memory.used_memory_human }}</span>
</div>
<div class="row">
<span class="title">maxmemory(最大内存配置):</span>
<span class="value">{{ info.Memory.maxmemory }}</span>
</div>
<div class="row">
<span class="title">used_memory_rss(已分配的内存总量操作系统角度):</span>
<span class="value">{{ info.Memory.used_memory_rss_human }}</span>
</div>
<div class="row">
<span class="title">mem_fragmentation_ratio(used_memory_rss和used_memory 之间的比率):</span>
<span class="value">{{ info.Memory.mem_fragmentation_ratio }}</span>
</div>
<div class="row">
<span class="title">used_memory_peak(内存消耗峰值):</span>
<span class="value">{{ info.Memory.used_memory_peak_human }}</span>
</div>
<div class="row">
<span class="title">total_system_memory(主机总内存):</span>
<span class="value">{{ info.Memory.total_system_memory_human }}</span>
</div>
</el-collapse-item>
<el-row :gutter="20" class="redis-info">
<el-col :lg="24" :md="24">
<span style="font-size: 14px; font-weight: 700">键值统计</span>
<el-table :data="Keyspace" stripe max-height="250" style="width: 100%" border>
<el-table-column prop="db" label="数据库" min-width="100" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="keys" label="keys" min-width="70" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="expires" label="expires" min-width="70" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="avg_ttl" label="avg_ttl" min-width="70" show-overflow-tooltip>
</el-table-column>
</el-table>
</el-col>
</el-row>
<el-descriptions class="redis-info info-state" title="统计信息" :column="3" size="small" border>
<el-descriptions-item label="总处理命令数">{{ info.Stats.total_commands_processed }}</el-descriptions-item>
<el-descriptions-item label="当前qps">{{ info.Stats.instantaneous_ops_per_sec }}</el-descriptions-item>
<el-descriptions-item label="过期key的总数量">{{ info.Stats.expired_keys }}</el-descriptions-item>
<el-descriptions-item label="网络入口流量字节数">{{ info.Stats.total_net_input_bytes }}</el-descriptions-item>
<el-descriptions-item label="网络出口流量字节数">{{ info.Stats.total_net_output_bytes }}</el-descriptions-item>
</el-descriptions>
<el-descriptions class="redis-info info-persistence" title="持久化" :column="3" size="small" border>
<el-descriptions-item label="是否启用aof">{{ info.Persistence?.aof_enabled || false
}}</el-descriptions-item>
<el-descriptions-item label="是否正在载入持久化文件">{{ info.Persistence?.loading || false
}}</el-descriptions-item>
</el-descriptions>
<el-collapse-item title="CPU" name="cpu">
<div class="row">
<span class="title">used_cpu_sys(由Redis服务器消耗的系统CPU):</span>
<span class="value">{{ info.CPU.used_cpu_sys }}</span>
</div>
<div class="row">
<span class="title">used_cpu_user(由Redis服务器消耗的用户CPU):</span>
<span class="value">{{ info.CPU.used_cpu_user }}</span>
</div>
<div class="row">
<span class="title">used_cpu_sys_children(由后台进程消耗的系统CPU):</span>
<span class="value">{{ info.CPU.used_cpu_sys_children }}</span>
</div>
<div class="row">
<span class="title">used_cpu_user_children(由后台进程消耗的用户CPU):</span>
<span class="value">{{ info.CPU.used_cpu_user_children }}</span>
</div>
</el-collapse-item>
</el-collapse>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, watch, toRefs } from 'vue';
import { reactive, watch, toRefs, ref, nextTick } from 'vue';
import { formatByteSize } from '@/common/utils/format';
import useEcharts from '@/common/echarts/useEcharts';
import tdTheme from '@/common/echarts/theme.json';
const props = defineProps({
visible: {
@@ -164,10 +103,17 @@ const emit = defineEmits(['update:visible', 'close'])
const state = reactive({
dialogVisible: false,
memInfo: {} as any,
Keyspace: [] as any[],
});
let memChart: any = null;
let memRef = ref(null);
const {
dialogVisible,
Keyspace,
} = toRefs(state)
watch(
@@ -176,6 +122,90 @@ watch(
state.dialogVisible = val;
}
);
watch(
() => props.info,
(info: any) => {
state.memInfo = info['Memory'];
if (state.memInfo) {
initCharts();
}
if (info['Keyspace']) {
let arr = [];
for (let k in info['Keyspace']) {
let data = { db: k }
let d = info['Keyspace'][k].split(',')
for (let f of d) {
let v = f.split('=')
data[v[0]] = v[1]
}
arr.push(data)
}
state.Keyspace = arr
}
}
);
const initCharts = () => {
nextTick(() => {
initMemStats();
});
}
const initMemStats = () => {
let maxMem = state.memInfo.maxmemory === '0' ? state.memInfo.total_system_memory : state.memInfo.maxmemory
const data = [
{ name: '可用内存:', value: maxMem - state.memInfo.used_memory },
{
name: '已用内存:',
value: state.memInfo.used_memory,
},
];
const option = {
title: {
text: '内存',
x: 'left',
textStyle: { fontSize: 14 },
},
tooltip: {
trigger: 'item',
valueFormatter: formatByteSize,
},
legend: {
top: '15%',
orient: 'vertical',
left: 'left',
textStyle: { fontSize: 12 },
},
series: [
{
name: '内存',
type: 'pie',
radius: ['30%', '60%'], // 饼图内圈和外圈大小
center: ['40%', '50%'], // 饼图位置0: 左右1: 上下
avoidLabelOverlap: false,
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
fontSize: '15',
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: data,
},
],
};
if (memChart) {
memChart.setOption(option, true);
return;
}
memChart = useEcharts(memRef.value, tdTheme, option);
}
const close = () => {
emit('update:visible', false);
@@ -183,7 +213,16 @@ const close = () => {
};
</script>
<style>
<style lang="scss">
.redis-info {
margin-top: 12px;
.info-memory-chart {
width: 360px;
height: 150px;
}
}
.row .title {
font-size: 12px;
color: #8492a6;
@@ -192,6 +231,6 @@ const close = () => {
.row .value {
font-size: 12px;
color: black;
color: var(--el-color-success);
}
</style>

View File

@@ -122,7 +122,6 @@ const cancel = () => {
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
state.key = newValue.key;
state.redisId = newValue.redisId;
state.db = newValue.db;
state.key = newValue.keyInfo;

View File

@@ -32,13 +32,6 @@
</el-popover>
</template></el-input>
</el-form-item>
<el-form-item prop="db" label="库号:" required>
<el-select @change="changeDb" v-model="dbList" multiple allow-create filterable
placeholder="请选择可操作库号" style="width: 100%">
<el-option v-for="db in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]" :key="db"
:label="db" :value="db" />
</el-select>
</el-form-item>
<el-form-item prop="remark" label="备注:">
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>

View File

@@ -0,0 +1,100 @@
<template>
<tag-menu :instanceMenuMaxHeight="state.instanceMenuMaxHeight" :tags="instances.tags" ref="menuRef">
<template #submenu="props">
<el-sub-menu v-for="inst in instances.tree[props.tag.tagId]" :index="'redis-' + inst.id"
:key="'redis-' + inst.id" @click.stop="changeInstance(inst)">
<template #title>
<el-popover placement="right-start" title="redis实例信息" trigger="hover" :width="210">
<template #reference>
<span>&nbsp;&nbsp;<el-icon>
<MostlyCloudy color="#409eff" />
</el-icon>{{ inst.name }}</span>
</template>
<template #default>
<el-form class="instances-pop-form" label-width="55px" :size="'small'">
<el-form-item label="名称:">{{ inst.name }}</el-form-item>
<el-form-item label="链接:">{{ inst.host }}</el-form-item>
<el-form-item label="备注:">{{ inst.remark }}</el-form-item>
</el-form>
</template>
</el-popover>
</template>
<!-- 第三级数据库 -->
<el-menu-item v-for="db in instances.dbs[inst.id]" :index="inst.id + db.name" :key="inst.id + db.name"
:class="state.nowSchema === (inst.id + db.name) && 'checked'" @click="changeSchema(inst, db.name)">
<template #title>
&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Coin color="#67c23a" />
</el-icon>
<span class="checked-schema">
{{ db.name }} [{{ db.keys }}]
</span>
</template>
</el-menu-item>
</el-sub-menu>
</template>
</tag-menu>
</template>
<script lang="ts" setup>
import { onBeforeMount, reactive, Ref, ref } from 'vue';
import TagMenu from '../component/TagMenu.vue';
defineProps({
instances: {
type: Object, required: true
},
})
const emits = defineEmits(['initLoadInstances', 'changeInstance', 'changeSchema'])
onBeforeMount(async () => {
await initLoadInstances();
setHeight();
})
const setHeight = () => {
state.instanceMenuMaxHeight = window.innerHeight - 115 + 'px';
}
const state = reactive({
instanceMenuMaxHeight: '800px',
nowSchema: '',
filterParam: {},
loading: {},
})
/**
* 初始化加载实例数据
*/
const initLoadInstances = () => {
emits('initLoadInstances')
}
/**
* 改变选中的数据库实例
* @param inst 选中的实例对象
* @param fn 选中的实例后的回调函数
*/
const changeInstance = (inst: any, fn?: Function) => {
emits('changeInstance', inst, fn);
}
/**
* 改变选中的数据库schema
* @param inst 选中的实例对象
* @param schema 选中的数据库schema
*/
const changeSchema = (inst: any, schema: string) => {
state.nowSchema = inst.id + schema;
emits('changeSchema', inst, schema);
}
</script>
<style lang="scss">
.instances-pop-form {
.el-form-item {
margin-bottom: unset;
}
}
</style>

View File

@@ -20,7 +20,14 @@
</el-radio>
</template>
</el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip>
<template #default="scope">
<tag-info :tag-path="scope.row.tagPath" />
<span class="ml5">
{{ scope.row.tagPath }}
</span>
</template>
</el-table-column>
<el-table-column prop="name" label="名称" min-width="100"></el-table-column>
<el-table-column prop="host" label="host:port" min-width="150" show-overflow-tooltip> </el-table-column>
<el-table-column prop="mode" label="mode" min-width="100"></el-table-column>
@@ -35,9 +42,6 @@
@click="showInfoDialog(scope.row)" :underline="false">单机信息</el-link>
<el-link @click="onShowClusterInfo(scope.row)" v-if="scope.row.mode === 'cluster'"
type="primary" :underline="false">集群信息</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click="openDataOpt(scope.row)" type="success" :underline="false">数据操作</el-link>
</template>
</el-table-column>
</el-table>
@@ -159,8 +163,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { tagApi } from '../tag/api.ts';
import RedisEdit from './RedisEdit.vue';
import { dateFormat } from '@/common/utils/date';
import { store } from '@/store';
import router from '@/router';
import TagInfo from '../component/TagInfo.vue';
const state = reactive({
tags: [],
@@ -258,7 +261,7 @@ const showInfoDialog = async (redis: any) => {
}
const res = await redisApi.redisInfo.request({ id: redis.id, host });
state.infoDialog.info = res;
state.infoDialog.title = `'${host}' info`;
state.infoDialog.title = `[${redis.name || host}] redis信息`;
state.infoDialog.visible = true;
};
@@ -296,21 +299,6 @@ const valChange = () => {
state.currentData = null;
search();
};
// 打开redis数据操作页
const openDataOpt = (row: any) => {
const { tagPath, id, db } = row;
// 判断db是否发生改变
let oldDbId = store.state.redisDbOptInfo.dbOptInfo.dbId;
if (oldDbId !== id) {
let params = {
tagPath,
dbId: id,
db
}
store.dispatch('redisDbOptInfo/setRedisDbOptInfo', params);
}
router.push({ name: 'DataOperation' });
}
</script>
<style>

View File

@@ -106,7 +106,6 @@ const cancel = () => {
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
state.key = newValue.key;
state.redisId = newValue.redisId;
state.db = newValue.db;
state.key = newValue.keyInfo;

View File

@@ -120,7 +120,6 @@ watch(
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
state.key = newValue.key;
state.redisId = newValue.redisId;
state.db = newValue.db;
state.key = newValue.keyInfo;

View File

@@ -2,8 +2,6 @@
<div class="role-list">
<el-card>
<el-button v-auth="'team:save'" type="primary" icon="plus" @click="showSaveTeamDialog(false)">添加</el-button>
<el-button v-auth="'team:save'" :disabled="!chooseId" @click="showSaveTeamDialog(chooseData)" type="primary"
icon="edit">编辑</el-button>
<el-button v-auth="'team:del'" :disabled="!chooseId" @click="deleteTeam(chooseData)" type="danger"
icon="delete">删除</el-button>
@@ -33,6 +31,8 @@
<el-link @click.prevent="showMembers(scope.row)" :underline="false" type="primary">成员</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click.prevent="showTags(scope.row)" :underline="false" type="success">标签</el-link>
<el-divider v-auth="'team:save'" direction="vertical" border-style="dashed" />
<el-link v-auth="'team:save'" @click.prevent="showSaveTeamDialog(scope.row)" :underline="false" type="warning">编辑</el-link>
</template>
</el-table-column>
</el-table>

View File

@@ -2,6 +2,7 @@ import Api from '@/common/Api';
export const tagApi = {
getAccountTags: Api.create("/tag-trees/account-has", 'get'),
listByQuery: Api.create("/tag-trees/query", 'get'),
getTagTrees: Api.create("/tag-trees", 'get'),
saveTagTree: Api.create("/tag-trees", 'post'),
delTagTree: Api.create("/tag-trees/{id}", 'delete'),
@@ -11,8 +12,8 @@ export const tagApi = {
delTeam: Api.create("/teams/{id}", 'delete'),
getTeamMem: Api.create("/teams/{teamId}/members", 'get'),
saveTeamMem: Api.create("/teams/{teamId}/members", 'post'),
delTeamMem: Api.create("/teams/{teamId}/members/{accountId}", 'delete'),
saveTeamMem: Api.create("/teams/{teamId}/members", 'post'),
delTeamMem: Api.create("/teams/{teamId}/members/{accountId}", 'delete'),
getTeamTagIds: Api.create("/teams/{teamId}/tags", 'get'),
saveTeamTags: Api.create("/teams/{teamId}/tags", 'post'),

View File

@@ -79,9 +79,11 @@
</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-row type="flex" class="mt5" justify="center">
<el-pagination small @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-row>
</el-dialog>
<!-- 营销推荐 -->

View File

@@ -13,16 +13,16 @@
</el-radio>
</template>
</el-table-column>
<el-table-column prop="name" label="配置项"></el-table-column>
<el-table-column prop="key" label="配置key"></el-table-column>
<el-table-column prop="value" label="配置值" min-width="100px" show-overflow-tooltip></el-table-column>
<el-table-column prop="name" label="配置项" min-width="100px" show-overflow-tooltip></el-table-column>
<el-table-column prop="key" label="配置key" min-width="100px"></el-table-column>
<el-table-column prop="value" label="配置值" show-overflow-tooltip></el-table-column>
<el-table-column prop="remark" label="备注" min-width="100px" show-overflow-tooltip></el-table-column>
<el-table-column prop="updateTime" label="更新时间" min-width="100px">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="modifier" label="修改者" show-overflow-tooltip></el-table-column>
<el-table-column prop="modifier" label="修改者" min-width="60px" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" min-width="50" fixed="right">
<template #default="scope">
<el-link :disabled="scope.row.status == -1" type="warning"
@@ -134,6 +134,8 @@ const showSetConfigDialog = (row: any) => {
if (row.value) {
state.paramsDialog.params = JSON.parse(row.value);
}
} else {
state.paramsDialog.params = row.value;
}
} else {
state.paramsDialog.params = row.value;

View File

@@ -29,6 +29,116 @@
resolved "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.0.9.tgz"
integrity sha512-okdrwiVeKBmW41Hkl0eMrXDjzJwhQMuKiBOu17rOszqM+LS/yBYpNQNV5Jvoh06Wc+89fMmb/uhzf8NZuDuUaQ==
"@esbuild/android-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23"
integrity sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==
"@esbuild/android-arm@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz#025b6246d3f68b7bbaa97069144fb5fb70f2fff2"
integrity sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==
"@esbuild/android-x64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz#c820e0fef982f99a85c4b8bfdd582835f04cd96e"
integrity sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==
"@esbuild/darwin-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz#edef4487af6b21afabba7be5132c26d22379b220"
integrity sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==
"@esbuild/darwin-x64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz#42829168730071c41ef0d028d8319eea0e2904b4"
integrity sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==
"@esbuild/freebsd-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz#1f4af488bfc7e9ced04207034d398e793b570a27"
integrity sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==
"@esbuild/freebsd-x64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz#636306f19e9bc981e06aa1d777302dad8fddaf72"
integrity sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==
"@esbuild/linux-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz#a003f7ff237c501e095d4f3a09e58fc7b25a4aca"
integrity sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==
"@esbuild/linux-arm@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz#b591e6a59d9c4fe0eeadd4874b157ab78cf5f196"
integrity sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==
"@esbuild/linux-ia32@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz#24333a11027ef46a18f57019450a5188918e2a54"
integrity sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==
"@esbuild/linux-loong64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz#d5ad459d41ed42bbd4d005256b31882ec52227d8"
integrity sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==
"@esbuild/linux-mips64el@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz#4e5967a665c38360b0a8205594377d4dcf9c3726"
integrity sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==
"@esbuild/linux-ppc64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz#206443a02eb568f9fdf0b438fbd47d26e735afc8"
integrity sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==
"@esbuild/linux-riscv64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz#c351e433d009bf256e798ad048152c8d76da2fc9"
integrity sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==
"@esbuild/linux-s390x@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz#661f271e5d59615b84b6801d1c2123ad13d9bd87"
integrity sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==
"@esbuild/linux-x64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz#e4ba18e8b149a89c982351443a377c723762b85f"
integrity sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==
"@esbuild/netbsd-x64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz#7d4f4041e30c5c07dd24ffa295c73f06038ec775"
integrity sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==
"@esbuild/openbsd-x64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz#970fa7f8470681f3e6b1db0cc421a4af8060ec35"
integrity sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==
"@esbuild/sunos-x64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz#abc60e7c4abf8b89fb7a4fe69a1484132238022c"
integrity sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==
"@esbuild/win32-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz#7b0ff9e8c3265537a7a7b1fd9a24e7bd39fcd87a"
integrity sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==
"@esbuild/win32-ia32@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz#e90fe5267d71a7b7567afdc403dfd198c292eb09"
integrity sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==
"@esbuild/win32-x64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091"
integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==
"@eslint/eslintrc@^1.0.5":
version "1.0.5"
resolved "https://registry.npmmirror.com/@eslint/eslintrc/download/@eslint/eslintrc-1.0.5.tgz"
@@ -228,13 +338,13 @@
estree-walker "^2.0.2"
source-map "^0.6.1"
"@vue/compiler-core@3.2.45":
version "3.2.45"
resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.45.tgz#d9311207d96f6ebd5f4660be129fb99f01ddb41b"
integrity sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==
"@vue/compiler-core@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.47.tgz#3e07c684d74897ac9aa5922c520741f3029267f8"
integrity sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/shared" "3.2.45"
"@vue/shared" "3.2.47"
estree-walker "^2.0.2"
source-map "^0.6.1"
@@ -246,25 +356,25 @@
"@vue/compiler-core" "3.2.39"
"@vue/shared" "3.2.39"
"@vue/compiler-dom@3.2.45":
version "3.2.45"
resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz#c43cc15e50da62ecc16a42f2622d25dc5fd97dce"
integrity sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==
"@vue/compiler-dom@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305"
integrity sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==
dependencies:
"@vue/compiler-core" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/compiler-core" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/compiler-sfc@3.2.45":
version "3.2.45"
resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz#7f7989cc04ec9e7c55acd406827a2c4e96872c70"
integrity sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==
"@vue/compiler-sfc@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz#1bdc36f6cdc1643f72e2c397eb1a398f5004ad3d"
integrity sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.45"
"@vue/compiler-dom" "3.2.45"
"@vue/compiler-ssr" "3.2.45"
"@vue/reactivity-transform" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/compiler-core" "3.2.47"
"@vue/compiler-dom" "3.2.47"
"@vue/compiler-ssr" "3.2.47"
"@vue/reactivity-transform" "3.2.47"
"@vue/shared" "3.2.47"
estree-walker "^2.0.2"
magic-string "^0.25.7"
postcss "^8.1.10"
@@ -294,13 +404,13 @@
"@vue/compiler-dom" "3.2.39"
"@vue/shared" "3.2.39"
"@vue/compiler-ssr@3.2.45":
version "3.2.45"
resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz#bd20604b6e64ea15344d5b6278c4141191c983b2"
integrity sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==
"@vue/compiler-ssr@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz#35872c01a273aac4d6070ab9d8da918ab13057ee"
integrity sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==
dependencies:
"@vue/compiler-dom" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/compiler-dom" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/devtools-api@^6.0.0-beta.11":
version "6.0.0-beta.20.1"
@@ -323,58 +433,58 @@
estree-walker "^2.0.2"
magic-string "^0.25.7"
"@vue/reactivity-transform@3.2.45":
version "3.2.45"
resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz#07ac83b8138550c83dfb50db43cde1e0e5e8124d"
integrity sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==
"@vue/reactivity-transform@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz#e45df4d06370f8abf29081a16afd25cffba6d84e"
integrity sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/compiler-core" "3.2.47"
"@vue/shared" "3.2.47"
estree-walker "^2.0.2"
magic-string "^0.25.7"
"@vue/reactivity@3.2.45":
version "3.2.45"
resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.45.tgz#412a45b574de601be5a4a5d9a8cbd4dee4662ff0"
integrity sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==
"@vue/reactivity@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.47.tgz#1d6399074eadfc3ed35c727e2fd707d6881140b6"
integrity sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==
dependencies:
"@vue/shared" "3.2.45"
"@vue/shared" "3.2.47"
"@vue/runtime-core@3.2.45":
version "3.2.45"
resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.45.tgz#7ad7ef9b2519d41062a30c6fa001ec43ac549c7f"
integrity sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==
"@vue/runtime-core@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.47.tgz#406ebade3d5551c00fc6409bbc1eeb10f32e121d"
integrity sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==
dependencies:
"@vue/reactivity" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/reactivity" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/runtime-dom@3.2.45":
version "3.2.45"
resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz#1a2ef6ee2ad876206fbbe2a884554bba2d0faf59"
integrity sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==
"@vue/runtime-dom@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz#93e760eeaeab84dedfb7c3eaf3ed58d776299382"
integrity sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==
dependencies:
"@vue/runtime-core" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/runtime-core" "3.2.47"
"@vue/shared" "3.2.47"
csstype "^2.6.8"
"@vue/server-renderer@3.2.45":
version "3.2.45"
resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.45.tgz#ca9306a0c12b0530a1a250e44f4a0abac6b81f3f"
integrity sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==
"@vue/server-renderer@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.47.tgz#8aa1d1871fc4eb5a7851aa7f741f8f700e6de3c0"
integrity sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==
dependencies:
"@vue/compiler-ssr" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/compiler-ssr" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/shared@3.2.39":
version "3.2.39"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.39.tgz"
integrity sha512-D3dl2ZB9qE6mTuWPk9RlhDeP1dgNRUKC3NJxji74A4yL8M2MwlhLKUC/49WHjrNzSPug58fWx/yFbaTzGAQSBw==
"@vue/shared@3.2.45":
version "3.2.45"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.45.tgz#a3fffa7489eafff38d984e23d0236e230c818bc2"
integrity sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==
"@vue/shared@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.47.tgz#e597ef75086c6e896ff5478a6bfc0a7aa4bbd14c"
integrity sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==
"@vueuse/core@^9.1.0":
version "9.2.0"
@@ -476,10 +586,10 @@ asynckit@^0.4.0:
resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
axios@^1.2.0:
version "1.2.0"
resolved "https://registry.npmmirror.com/axios/-/axios-1.2.0.tgz#1cb65bd75162c70e9f8d118a905126c4a201d383"
integrity sha512-zT7wZyNYu3N5Bu0wuZ6QccIf93Qk1eV8LOewxgjOZFd2DenOs98cJ7+Y6703d0wkaXGY6/nZd4EweJaHz9uzQw==
axios@^1.3.2:
version "1.3.2"
resolved "https://registry.npmmirror.com/axios/-/axios-1.3.2.tgz#7ac517f0fa3ec46e0e636223fd973713a09c72b3"
integrity sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
@@ -646,10 +756,10 @@ dotenv@^10.0.0:
resolved "https://registry.nlark.com/dotenv/download/dotenv-10.0.0.tgz"
integrity sha1-PUInuPuV+BCWzdK2ZlP7LHCFuoE=
dt-sql-parser@^4.0.0-beta.2.2:
version "4.0.0-beta.2.2"
resolved "https://registry.npmmirror.com/dt-sql-parser/-/dt-sql-parser-4.0.0-beta.2.2.tgz"
integrity sha512-LLAE659zgizdokkDniHFPk0PsLPV3cXFOQPW+QT+3W1/TQJ2h8yzKCBBufXmKAHMpAr+KjTRTa71VJRzWJx8Zg==
dt-sql-parser@^4.0.0-beta.3.2:
version "4.0.0-beta.3.2"
resolved "https://registry.npmmirror.com/dt-sql-parser/-/dt-sql-parser-4.0.0-beta.3.2.tgz#1df3341b5e12eaa31d6fd9174d440ed17c1e2f15"
integrity sha512-QrgsWzpqqUy6vAGkK4Ep4WdZfhy0s2SNR7QuwQ3RsdDNQ4rMzH1jQTdEBK6oU+16o1xpStwjnzk3vuxMOhiisw==
dependencies:
"@types/antlr4" "4.7.0"
antlr4 "4.7.2"
@@ -662,10 +772,10 @@ echarts@^5.4.0:
tslib "2.3.0"
zrender "5.4.0"
element-plus@^2.2.26:
version "2.2.26"
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.26.tgz#5e46aa5d8127786bb158713957f8a253b35bf019"
integrity sha512-O/rdY5m9DkclpVg8r3GynyqCunm7MxSR142xSsjrZA77bi7bcwA3SIy6SPEDqHi5R4KqgkGYgKSp4Q4e3irbYg==
element-plus@^2.2.30:
version "2.2.30"
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.30.tgz#b594efcbd6969f3f88130aa1edf50c98139d6e73"
integrity sha512-HYSnmf2VMGa0gmw03evxevodPy3WimbAd4sfenOAhNs7Wl8IdT+YJjQyGAQjgEjRvhmujN4O/CZqhuEffRyOZg==
dependencies:
"@ctrl/tinycolor" "^3.4.1"
"@element-plus/icons-vue" "^2.0.6"
@@ -690,131 +800,33 @@ enquirer@^2.3.5:
dependencies:
ansi-colors "^4.1.1"
esbuild-android-64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.14.38.tgz#5b94a1306df31d55055f64a62ff6b763a47b7f64"
integrity sha512-aRFxR3scRKkbmNuGAK+Gee3+yFxkTJO/cx83Dkyzo4CnQl/2zVSurtG6+G86EQIZ+w+VYngVyK7P3HyTBKu3nw==
esbuild-android-arm64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.38.tgz#78acc80773d16007de5219ccce544c036abd50b8"
integrity sha512-L2NgQRWuHFI89IIZIlpAcINy9FvBk6xFVZ7xGdOwIm8VyhX1vNCEqUJO3DPSSy945Gzdg98cxtNt8Grv1CsyhA==
esbuild-darwin-64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.38.tgz"
integrity sha512-5JJvgXkX87Pd1Og0u/NJuO7TSqAikAcQQ74gyJ87bqWRVeouky84ICoV4sN6VV53aTW+NE87qLdGY4QA2S7KNA==
esbuild-darwin-arm64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.38.tgz#01eb6650ec010b18c990e443a6abcca1d71290a9"
integrity sha512-eqF+OejMI3mC5Dlo9Kdq/Ilbki9sQBw3QlHW3wjLmsLh+quNfHmGMp3Ly1eWm981iGBMdbtSS9+LRvR2T8B3eQ==
esbuild-freebsd-64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.38.tgz#790b8786729d4aac7be17648f9ea8e0e16475b5e"
integrity sha512-epnPbhZUt93xV5cgeY36ZxPXDsQeO55DppzsIgWM8vgiG/Rz+qYDLmh5ts3e+Ln1wA9dQ+nZmVHw+RjaW3I5Ig==
esbuild-freebsd-arm64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.38.tgz#b66340ab28c09c1098e6d9d8ff656db47d7211e6"
integrity sha512-/9icXUYJWherhk+y5fjPI5yNUdFPtXHQlwP7/K/zg8t8lQdHVj20SqU9/udQmeUo5pDFHMYzcEFfJqgOVeKNNQ==
esbuild-linux-32@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.14.38.tgz#7927f950986fd39f0ff319e92839455912b67f70"
integrity sha512-QfgfeNHRFvr2XeHFzP8kOZVnal3QvST3A0cgq32ZrHjSMFTdgXhMhmWdKzRXP/PKcfv3e2OW9tT9PpcjNvaq6g==
esbuild-linux-64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.14.38.tgz#4893d07b229d9cfe34a2b3ce586399e73c3ac519"
integrity sha512-uuZHNmqcs+Bj1qiW9k/HZU3FtIHmYiuxZ/6Aa+/KHb/pFKr7R3aVqvxlAudYI9Fw3St0VCPfv7QBpUITSmBR1Q==
esbuild-linux-arm64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.38.tgz#8442402e37d0b8ae946ac616784d9c1a2041056a"
integrity sha512-HlMGZTEsBrXrivr64eZ/EO0NQM8H8DuSENRok9d+Jtvq8hOLzrxfsAT9U94K3KOGk2XgCmkaI2KD8hX7F97lvA==
esbuild-linux-arm@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.38.tgz#d5dbf32d38b7f79be0ec6b5fb2f9251fd9066986"
integrity sha512-FiFvQe8J3VKTDXG01JbvoVRXQ0x6UZwyrU4IaLBZeq39Bsbatd94Fuc3F1RGqPF5RbIWW7RvkVQjn79ejzysnA==
esbuild-linux-mips64le@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.38.tgz#95081e42f698bbe35d8ccee0e3a237594b337eb5"
integrity sha512-qd1dLf2v7QBiI5wwfil9j0HG/5YMFBAmMVmdeokbNAMbcg49p25t6IlJFXAeLzogv1AvgaXRXvgFNhScYEUXGQ==
esbuild-linux-ppc64le@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.38.tgz#dceb0a1b186f5df679618882a7990bd422089b47"
integrity sha512-mnbEm7o69gTl60jSuK+nn+pRsRHGtDPfzhrqEUXyCl7CTOCLtWN2bhK8bgsdp6J/2NyS/wHBjs1x8aBWwP2X9Q==
esbuild-linux-riscv64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.38.tgz#61fb8edb75f475f9208c4a93ab2bfab63821afd2"
integrity sha512-+p6YKYbuV72uikChRk14FSyNJZ4WfYkffj6Af0/Tw63/6TJX6TnIKE+6D3xtEc7DeDth1fjUOEqm+ApKFXbbVQ==
esbuild-linux-s390x@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.38.tgz#34c7126a4937406bf6a5e69100185fd702d12fe0"
integrity sha512-0zUsiDkGJiMHxBQ7JDU8jbaanUY975CdOW1YDrurjrM0vWHfjv9tLQsW9GSyEb/heSK1L5gaweRjzfUVBFoybQ==
esbuild-netbsd-64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.38.tgz#322ea9937d9e529183ee281c7996b93eb38a5d95"
integrity sha512-cljBAApVwkpnJZfnRVThpRBGzCi+a+V9Ofb1fVkKhtrPLDYlHLrSYGtmnoTVWDQdU516qYI8+wOgcGZ4XIZh0Q==
esbuild-openbsd-64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.38.tgz#1ca29bb7a2bf09592dcc26afdb45108f08a2cdbd"
integrity sha512-CDswYr2PWPGEPpLDUO50mL3WO/07EMjnZDNKpmaxUPsrW+kVM3LoAqr/CE8UbzugpEiflYqJsGPLirThRB18IQ==
esbuild-sunos-64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.38.tgz#c9446f7d8ebf45093e7bb0e7045506a88540019b"
integrity sha512-2mfIoYW58gKcC3bck0j7lD3RZkqYA7MmujFYmSn9l6TiIcAMpuEvqksO+ntBgbLep/eyjpgdplF7b+4T9VJGOA==
esbuild-windows-32@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.14.38.tgz#f8e9b4602fd0ccbd48e5c8d117ec0ba4040f2ad1"
integrity sha512-L2BmEeFZATAvU+FJzJiRLFUP+d9RHN+QXpgaOrs2klshoAm1AE6Us4X6fS9k33Uy5SzScn2TpcgecbqJza1Hjw==
esbuild-windows-64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.14.38.tgz#280f58e69f78535f470905ce3e43db1746518107"
integrity sha512-Khy4wVmebnzue8aeSXLC+6clo/hRYeNIm0DyikoEqX+3w3rcvrhzpoix0S+MF9vzh6JFskkIGD7Zx47ODJNyCw==
esbuild-windows-arm64@0.14.38:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.38.tgz#d97e9ac0f95a4c236d9173fa9f86c983d6a53f54"
integrity sha512-k3FGCNmHBkqdJXuJszdWciAH77PukEyDsdIryEHn9cKLQFxzhT39dSumeTuggaQcXY57UlmLGIkklWZo2qzHpw==
esbuild@^0.14.27:
version "0.14.38"
resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.14.38.tgz"
integrity sha512-12fzJ0fsm7gVZX1YQ1InkOE5f9Tl7cgf6JPYXRJtPIoE0zkWAbHdPHVPPaLi9tYAcEBqheGzqLn/3RdTOyBfcA==
esbuild@^0.16.14:
version "0.16.17"
resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259"
integrity sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==
optionalDependencies:
esbuild-android-64 "0.14.38"
esbuild-android-arm64 "0.14.38"
esbuild-darwin-64 "0.14.38"
esbuild-darwin-arm64 "0.14.38"
esbuild-freebsd-64 "0.14.38"
esbuild-freebsd-arm64 "0.14.38"
esbuild-linux-32 "0.14.38"
esbuild-linux-64 "0.14.38"
esbuild-linux-arm "0.14.38"
esbuild-linux-arm64 "0.14.38"
esbuild-linux-mips64le "0.14.38"
esbuild-linux-ppc64le "0.14.38"
esbuild-linux-riscv64 "0.14.38"
esbuild-linux-s390x "0.14.38"
esbuild-netbsd-64 "0.14.38"
esbuild-openbsd-64 "0.14.38"
esbuild-sunos-64 "0.14.38"
esbuild-windows-32 "0.14.38"
esbuild-windows-64 "0.14.38"
esbuild-windows-arm64 "0.14.38"
"@esbuild/android-arm" "0.16.17"
"@esbuild/android-arm64" "0.16.17"
"@esbuild/android-x64" "0.16.17"
"@esbuild/darwin-arm64" "0.16.17"
"@esbuild/darwin-x64" "0.16.17"
"@esbuild/freebsd-arm64" "0.16.17"
"@esbuild/freebsd-x64" "0.16.17"
"@esbuild/linux-arm" "0.16.17"
"@esbuild/linux-arm64" "0.16.17"
"@esbuild/linux-ia32" "0.16.17"
"@esbuild/linux-loong64" "0.16.17"
"@esbuild/linux-mips64el" "0.16.17"
"@esbuild/linux-ppc64" "0.16.17"
"@esbuild/linux-riscv64" "0.16.17"
"@esbuild/linux-s390x" "0.16.17"
"@esbuild/linux-x64" "0.16.17"
"@esbuild/netbsd-x64" "0.16.17"
"@esbuild/openbsd-x64" "0.16.17"
"@esbuild/sunos-x64" "0.16.17"
"@esbuild/win32-arm64" "0.16.17"
"@esbuild/win32-ia32" "0.16.17"
"@esbuild/win32-x64" "0.16.17"
escape-html@^1.0.3:
version "1.0.3"
@@ -1175,10 +1187,10 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.8.1:
version "2.9.0"
resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.9.0.tgz"
integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
is-core-module@^2.9.0:
version "2.11.0"
resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
dependencies:
has "^1.0.3"
@@ -1315,17 +1327,17 @@ mitt@^3.0.0:
resolved "https://registry.npmmirror.com/mitt/download/mitt-3.0.0.tgz"
integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
monaco-editor@^0.34.1:
version "0.34.1"
resolved "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.34.1.tgz"
integrity sha512-FKc80TyiMaruhJKKPz5SpJPIjL+dflGvz4CpuThaPMc94AyN7SeC9HQ8hrvaxX7EyHdJcUY5i4D0gNyJj1vSZQ==
monaco-editor@^0.35.0:
version "0.35.0"
resolved "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.35.0.tgz#49c4220c815262a900dacf0ae8a59bef66efab8b"
integrity sha512-BJfkAZ0EJ7JgrgWzqjfBNP9hPSS8NlfECEDMEIIiozV2UaPq22yeuOjgbd3TwMh3anH0krWZirXZfn8KUSxiOA==
monaco-sql-languages@^0.9.5:
version "0.9.5"
resolved "https://registry.npmmirror.com/monaco-sql-languages/-/monaco-sql-languages-0.9.5.tgz"
integrity sha512-IBIKQVIoW1Q90pJ/0Qi0sWMgbvho5ug17wx64hVid/XCr+L7ngJaTdaRnveOMPwg9qj+PQqOt1Ga0q0AwG85wA==
monaco-sql-languages@^0.11.0:
version "0.11.0"
resolved "https://registry.npmmirror.com/monaco-sql-languages/-/monaco-sql-languages-0.11.0.tgz#9a5f9061cb2095b8ef8854c6eb8185924fcf87a9"
integrity sha512-OoTQVLT6ldBXPEbQPw43hOy+u16dO+HkY/rCADXPM5NfRBQ1m9+04NdaQ94WcF2R6MPeansxW8AveIdjEhxqfw==
dependencies:
dt-sql-parser "^4.0.0-beta.2.2"
dt-sql-parser "^4.0.0-beta.3.2"
monaco-themes@^0.4.2:
version "0.4.2"
@@ -1439,10 +1451,10 @@ postcss@^8.1.10:
picocolors "^1.0.0"
source-map-js "^1.0.1"
postcss@^8.4.13:
version "8.4.14"
resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.14.tgz"
integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
postcss@^8.4.21:
version "8.4.21"
resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
dependencies:
nanoid "^3.3.4"
picocolors "^1.0.0"
@@ -1500,12 +1512,12 @@ resolve-from@^4.0.0:
resolved "https://registry.nlark.com/resolve-from/download/resolve-from-4.0.0.tgz"
integrity sha1-SrzYUq0y3Xuqv+m0DgCjbbXzkuY=
resolve@^1.22.0:
version "1.22.0"
resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.0.tgz"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
resolve@^1.22.1:
version "1.22.1"
resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
dependencies:
is-core-module "^2.8.1"
is-core-module "^2.9.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
@@ -1521,10 +1533,10 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
rollup@^2.59.0:
version "2.61.1"
resolved "https://registry.npmmirror.com/rollup/download/rollup-2.61.1.tgz"
integrity sha512-BbTXlEvB8d+XFbK/7E5doIcRtxWPRiqr0eb5vQ0+2paMM04Ye4PZY5nHOQef2ix24l/L0SpLd5hwcH15QHPdvA==
rollup@^3.10.0:
version "3.12.1"
resolved "https://registry.npmmirror.com/rollup/-/rollup-3.12.1.tgz#2975b97713e4af98c15e7024b88292d7fddb3853"
integrity sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig==
optionalDependencies:
fsevents "~2.3.2"
@@ -1535,18 +1547,18 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
sass-loader@^12.4.0:
version "12.4.0"
resolved "https://registry.npmmirror.com/sass-loader/download/sass-loader-12.4.0.tgz"
integrity sha512-7xN+8khDIzym1oL9XyS6zP6Ges+Bo2B2xbPrjdMHEYyV3AQYhd/wXeru++3ODHF0zMjYmVadblSKrPrjEkL8mg==
sass-loader@^13.2.0:
version "13.2.0"
resolved "https://registry.npmmirror.com/sass-loader/-/sass-loader-13.2.0.tgz#80195050f58c9aac63b792fa52acb6f5e0f6bdc3"
integrity sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==
dependencies:
klona "^2.0.4"
neo-async "^2.6.2"
sass@^1.45.1:
version "1.48.0"
resolved "https://registry.npmmirror.com/sass/download/sass-1.48.0.tgz"
integrity sha512-hQi5g4DcfjcipotoHZ80l7GNJHGqQS5LwMBjVYB/TaT0vcSSpbgM8Ad7cgfsB2M0MinbkEQQPO9+sjjSiwxqmw==
sass@^1.58.0:
version "1.58.0"
resolved "https://registry.npmmirror.com/sass/-/sass-1.58.0.tgz#ee8aea3ad5ea5c485c26b3096e2df6087d0bb1cc"
integrity sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
@@ -1712,15 +1724,15 @@ v8-compile-cache@^2.0.3:
resolved "https://registry.nlark.com/v8-compile-cache/download/v8-compile-cache-2.3.0.tgz"
integrity sha1-LeGWGMZtwkfc+2+ZM4A12CRaLO4=
vite@^2.9.13:
version "2.9.13"
resolved "https://registry.npmmirror.com/vite/-/vite-2.9.13.tgz"
integrity sha512-AsOBAaT0AD7Mhe8DuK+/kE4aWYFMx/i0ZNi98hJclxb4e0OhQcZYUrvLjIaQ8e59Ui7txcvKMiJC1yftqpQoDw==
vite@^4.1.1:
version "4.1.1"
resolved "https://registry.npmmirror.com/vite/-/vite-4.1.1.tgz#3b18b81a4e85ce3df5cbdbf4c687d93ebf402e6b"
integrity sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==
dependencies:
esbuild "^0.14.27"
postcss "^8.4.13"
resolve "^1.22.0"
rollup "^2.59.0"
esbuild "^0.16.14"
postcss "^8.4.21"
resolve "^1.22.1"
rollup "^3.10.0"
optionalDependencies:
fsevents "~2.3.2"
@@ -1756,16 +1768,16 @@ vue-router@^4.1.6:
dependencies:
"@vue/devtools-api" "^6.4.5"
vue@^3.2.45:
version "3.2.45"
resolved "https://registry.npmmirror.com/vue/-/vue-3.2.45.tgz#94a116784447eb7dbd892167784619fef379b3c8"
integrity sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==
vue@^3.2.47:
version "3.2.47"
resolved "https://registry.npmmirror.com/vue/-/vue-3.2.47.tgz#3eb736cbc606fc87038dbba6a154707c8a34cff0"
integrity sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==
dependencies:
"@vue/compiler-dom" "3.2.45"
"@vue/compiler-sfc" "3.2.45"
"@vue/runtime-dom" "3.2.45"
"@vue/server-renderer" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/compiler-dom" "3.2.47"
"@vue/compiler-sfc" "3.2.47"
"@vue/runtime-dom" "3.2.47"
"@vue/server-renderer" "3.2.47"
"@vue/shared" "3.2.47"
vuex@^4.0.2:
version "4.0.2"
@@ -1791,15 +1803,15 @@ wrappy@1:
resolved "https://registry.nlark.com/wrappy/download/wrappy-1.0.2.tgz?cache=0&sync_timestamp=1619133505879&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fwrappy%2Fdownload%2Fwrappy-1.0.2.tgz"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
xterm-addon-fit@^0.6.0:
version "0.6.0"
resolved "https://registry.npmmirror.com/xterm-addon-fit/-/xterm-addon-fit-0.6.0.tgz"
integrity sha512-9/7A+1KEjkFam0yxTaHfuk9LEvvTSBi0PZmEkzJqgafXPEXL9pCMAVV7rB09sX6ATRDXAdBpQhZkhKj7CGvYeg==
xterm-addon-fit@^0.7.0:
version "0.7.0"
resolved "https://registry.npmmirror.com/xterm-addon-fit/-/xterm-addon-fit-0.7.0.tgz#b8ade6d96e63b47443862088f6670b49fb752c6a"
integrity sha512-tQgHGoHqRTgeROPnvmtEJywLKoC/V9eNs4bLLz7iyJr1aW/QFzRwfd3MGiJ6odJd9xEfxcW36/xRU47JkD5NKQ==
xterm@^5.0.0:
version "5.0.0"
resolved "https://registry.npmmirror.com/xterm/-/xterm-5.0.0.tgz"
integrity sha512-tmVsKzZovAYNDIaUinfz+VDclraQpPUnAME+JawosgWRMphInDded/PuY0xmU5dOhyeYZsI0nz5yd8dPYsdLTA==
xterm@^5.1.0:
version "5.1.0"
resolved "https://registry.npmmirror.com/xterm/-/xterm-5.1.0.tgz#3e160d60e6801c864b55adf19171c49d2ff2b4fc"
integrity sha512-LovENH4WDzpwynj+OTkLyZgJPeDom9Gra4DMlGAgz6pZhIDCQ+YuO7yfwanY+gVbn/mmZIStNOnVRU/ikQuAEQ==
yallist@^4.0.0:
version "4.0.0"

26
server/Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
FROM golang:1.19-alpine3.16 as builder
ENV GOPROXY https://goproxy.cn
WORKDIR /mayfly
COPY go.mod go.mod
COPY go.sum go.sum
RUN go mod download
# Copy the go source for building server
COPY . .
# Build
RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux \
go build -a \
-o mayfly-go main.go
FROM alpine:3.16
RUN apk add --no-cache ca-certificates bash expat
WORKDIR /mayfly
COPY --from=builder /mayfly/config.yml /mayfly/config.yml
COPY --from=builder /mayfly/mayfly-go /usr/local/bin/mayfly-go
CMD ["mayfly-go"]

View File

@@ -21,12 +21,18 @@ mysql:
host: localhost:3306
username: root
password: 111049
db-name: mayfly-go
db-name: mayfly_go
config: charset=utf8&loc=Local&parseTime=true
max-idle-conns: 5
# 若同时部署多台机器则需要配置redis信息用于缓存权限码、验证码、公私钥等
# redis:
# host: localhost
# port: 6379
# passsord:
# db: 0
log:
# 日志等级, trace, debug, info, warn, error, fatal
level: info
# file:
# path: ./
# name: mayfly.log
# name: mayfly-go.log

View File

@@ -0,0 +1,33 @@
version: "3.9"
services:
mysql:
image: "mysql:5.7"
container_name: mayfly-go-mysql
environment:
MYSQL_ROOT_PASSWORD: 111049
MYSQL_DATABASE: mayfly-go
TZ: Asia/Shanghai
volumes:
- ./docs/docker-compose/mysql/data/mydir:/mydir
- ./docs/docker-compose/mysql/data/datadir:/var/lib/mysql
# 在宿主机编写 /apps/mysql/conf/my.cnf
- ./docs/docker-compose/mysql/my.cnf:/etc/my.cnf
# 数据库还原目录 可将需要还原的sql文件放在这里
- ./docs/docker-compose/mysql/init:/docker-entrypoint-initdb.d
restart: always
server:
image: mayfly-go:v1.3.1
build:
context: .
dockerfile: Dockerfile
container_name: mayfly-go-server
ports:
- "8888:8888"
environment:
TZ: Asia/Shanghai
WAIT_HOSTS: mysql:3306
depends_on:
- mysql
restart: always

View File

@@ -0,0 +1,808 @@
/*
Navicat Premium Data Transfer
Source Server Type : MySQL
Source Server Version : 50730
Source Schema : mayfly-go
Target Server Type : MySQL
Target Server Version : 50730
File Encoding : 65001
Date: 18/11/2021 14:33:55
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_db
-- ----------------------------
DROP TABLE IF EXISTS `t_db`;
CREATE TABLE `t_db` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库实例名称',
`host` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`port` int(8) NOT NULL,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '数据库实例类型(mysql...)',
`database` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库,空格分割多个数据库',
`params` varchar(125) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '其他连接参数',
`network` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`enable_ssh_tunnel` tinyint(2) DEFAULT NULL COMMENT '是否启用ssh隧道',
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
`remark` varchar(125) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注,描述等',
`tag_id` bigint(20) DEFAULT NULL COMMENT '标签id',
`tag_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '标签路径',
`create_time` datetime DEFAULT NULL,
`creator_id` bigint(20) DEFAULT NULL,
`creator` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`modifier_id` bigint(20) DEFAULT NULL,
`modifier` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_path` (`tag_path`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='数据库资源信息表';
-- ----------------------------
-- Records of t_db
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for t_db_sql
-- ----------------------------
DROP TABLE IF EXISTS `t_db_sql`;
CREATE TABLE `t_db_sql` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`db_id` bigint(20) NOT NULL COMMENT '数据库实例id',
`db` varchar(125) COLLATE utf8mb4_bin NOT NULL COMMENT '数据库',
`name` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'sql模板名',
`sql` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
`type` tinyint(8) NOT NULL,
`creator_id` bigint(20) NOT NULL,
`creator` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
`modifier_id` bigint(20) DEFAULT NULL,
`modifier` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='数据库sql信息';
-- ----------------------------
-- Records of t_db_sql
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for t_db_sql_exec
-- ----------------------------
DROP TABLE IF EXISTS `t_db_sql_exec`;
CREATE TABLE `t_db_sql_exec` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`db_id` bigint(20) NOT NULL COMMENT '数据库id',
`db` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '数据库',
`table` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '表名',
`type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'sql类型',
`sql` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '执行sql',
`old_value` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '操作前旧值',
`remark` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注',
`create_time` datetime NOT NULL,
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`creator_id` bigint(20) NOT NULL,
`update_time` datetime NOT NULL,
`modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`modifier_id` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='数据库sql执行记录表';
-- ----------------------------
-- Records of t_db_sql_exec
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for t_machine
-- ----------------------------
DROP TABLE IF EXISTS `t_machine`;
CREATE TABLE `t_machine` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`port` int(12) NOT NULL,
`username` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`auth_method` tinyint(2) DEFAULT NULL COMMENT '1.密码登录2.publickey登录',
`password` varchar(3200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`enable_ssh_tunnel` tinyint(2) DEFAULT NULL COMMENT '是否启用ssh隧道',
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
`enable_recorder` tinyint(2) DEFAULT NULL COMMENT '是否启用终端回放记录',
`status` tinyint(2) NOT NULL COMMENT '状态: 1:启用; -1:禁用',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`tag_id` bigint(20) DEFAULT NULL COMMENT '标签id',
`tag_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '标签路径',
`need_monitor` tinyint(2) DEFAULT NULL,
`create_time` datetime NOT NULL,
`creator` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`creator_id` bigint(32) DEFAULT NULL,
`update_time` datetime NOT NULL,
`modifier` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`modifier_id` bigint(32) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_path` (`tag_path`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='机器信息';
-- ----------------------------
-- Records of t_machine
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for t_machine_file
-- ----------------------------
DROP TABLE IF EXISTS `t_machine_file`;
CREATE TABLE `t_machine_file` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '机器文件配置linux一切皆文件故也可以表示目录',
`machine_id` bigint(20) NOT NULL,
`name` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`path` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`type` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '1目录2文件',
`creator_id` bigint(20) unsigned DEFAULT NULL,
`creator` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`modifier_id` bigint(20) unsigned DEFAULT NULL,
`modifier` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`create_time` datetime NOT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='机器文件';
-- ----------------------------
-- Records of t_machine_file
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for t_machine_monitor
-- ----------------------------
DROP TABLE IF EXISTS `t_machine_monitor`;
CREATE TABLE `t_machine_monitor` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`machine_id` bigint(20) unsigned NOT NULL COMMENT '机器id',
`cpu_rate` float(255,2) DEFAULT NULL,
`mem_rate` float(255,2) DEFAULT NULL,
`sys_load` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- ----------------------------
-- Records of t_machine_monitor
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for t_machine_script
-- ----------------------------
DROP TABLE IF EXISTS `t_machine_script`;
CREATE TABLE `t_machine_script` (
`id` bigint(64) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '脚本名',
`machine_id` bigint(64) NOT NULL COMMENT '机器id[0:公共]',
`script` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT '脚本内容',
`params` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '脚本入参',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '脚本描述',
`type` tinyint(8) DEFAULT NULL COMMENT '脚本类型[1: 有结果2无结果3实时交互]',
`creator_id` bigint(20) DEFAULT NULL,
`creator` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`modifier_id` bigint(20) DEFAULT NULL,
`modifier` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='机器脚本';
-- ----------------------------
-- Records of t_machine_script
-- ----------------------------
BEGIN;
INSERT INTO `t_machine_script` VALUES (1, 'sys_info', 9999999, '# 获取系统cpu信息\nfunction get_cpu_info() {\n Physical_CPUs=$(grep \"physical id\" /proc/cpuinfo | sort | uniq | wc -l)\n Virt_CPUs=$(grep \"processor\" /proc/cpuinfo | wc -l)\n CPU_Kernels=$(grep \"cores\" /proc/cpuinfo | uniq | awk -F \': \' \'{print $2}\')\n CPU_Type=$(grep \"model name\" /proc/cpuinfo | awk -F \': \' \'{print $2}\' | sort | uniq)\n CPU_Arch=$(uname -m)\n echo -e \'\\n-------------------------- CPU信息 --------------------------\'\n cat <<EOF | column -t\n物理CPU个数: $Physical_CPUs\n逻辑CPU个数: $Virt_CPUs\n每CPU核心数: $CPU_Kernels\nCPU型号: $CPU_Type\nCPU架构: $CPU_Arch\nEOF\n}\n\n# 获取系统信息\nfunction get_systatus_info() {\n sys_os=$(uname -o)\n sys_release=$(cat /etc/redhat-release)\n sys_kernel=$(uname -r)\n sys_hostname=$(hostname)\n sys_selinux=$(getenforce)\n sys_lang=$(echo $LANG)\n sys_lastreboot=$(who -b | awk \'{print $3,$4}\')\n echo -e \'-------------------------- 系统信息 --------------------------\'\n cat <<EOF | column -t\n系统: ${sys_os}\n发行版本: ${sys_release}\n系统内核: ${sys_kernel}\n主机名: ${sys_hostname}\nselinux状态: ${sys_selinux}\n系统语言: ${sys_lang}\n系统最后重启时间: ${sys_lastreboot}\nEOF\n}\n\nget_systatus_info\n#echo -e \"\\n\"\nget_cpu_info', NULL, '获取系统信息', 1, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `t_machine_script` VALUES (2, 'get_process_by_name', 9999999, '#! /bin/bash\n# Function: 根据输入的程序的名字过滤出所对应的PID并显示出详细信息如果有几个PID则全部显示\nNAME={{.processName}}\nN=`ps -aux | grep $NAME | grep -v grep | wc -l` ##统计进程总数\nif [ $N -le 0 ];then\n echo \"无该进程!\"\nfi\ni=1\nwhile [ $N -gt 0 ]\ndo\n echo \"进程PID: `ps -aux | grep $NAME | grep -v grep | awk \'NR==\'$i\'{print $0}\'| awk \'{print $2}\'`\"\n echo \"进程命令:`ps -aux | grep $NAME | grep -v grep | awk \'NR==\'$i\'{print $0}\'| awk \'{print $11}\'`\"\n echo \"进程所属用户: `ps -aux | grep $NAME | grep -v grep | awk \'NR==\'$i\'{print $0}\'| awk \'{print $1}\'`\"\n echo \"CPU占用率`ps -aux | grep $NAME | grep -v grep | awk \'NR==\'$i\'{print $0}\'| awk \'{print $3}\'`%\"\n echo \"内存占用率:`ps -aux | grep $NAME | grep -v grep | awk \'NR==\'$i\'{print $0}\'| awk \'{print $4}\'`%\"\n echo \"进程开始运行的时刻:`ps -aux | grep $NAME | grep -v grep | awk \'NR==\'$i\'{print $0}\'| awk \'{print $9}\'`\"\n echo \"进程运行的时间:` ps -aux | grep $NAME | grep -v grep | awk \'NR==\'$i\'{print $0}\'| awk \'{print $11}\'`\"\n echo \"进程状态:`ps -aux | grep $NAME | grep -v grep | awk \'NR==\'$i\'{print $0}\'| awk \'{print $8}\'`\"\n echo \"进程虚拟内存:`ps -aux | grep $NAME | grep -v grep | awk \'NR==\'$i\'{print $0}\'| awk \'{print $5}\'`\"\n echo \"进程共享内存:`ps -aux | grep $NAME | grep -v grep | awk \'NR==\'$i\'{print $0}\'| awk \'{print $6}\'`\"\n echo \"***************************************************************\"\n let N-- i++\ndone', '[{\"name\": \"进程名\",\"model\": \"processName\", \"placeholder\": \"请输入进程名\"}]', '获取进程运行状态', 1, NULL, NULL, 1, 'admin', NULL, '2021-07-12 15:33:41');
INSERT INTO `t_machine_script` VALUES (3, 'sys_run_info', 9999999, '#!/bin/bash\n# 获取要监控的本地服务器IP地址\nIP=`ifconfig | grep inet | grep -vE \'inet6|127.0.0.1\' | awk \'{print $2}\'`\necho \"IP地址\"$IP\n \n# 获取cpu总核数\ncpu_num=`grep -c \"model name\" /proc/cpuinfo`\necho \"cpu总核数\"$cpu_num\n \n# 1、获取CPU利用率\n################################################\n#us 用户空间占用CPU百分比\n#sy 内核空间占用CPU百分比\n#ni 用户进程空间内改变过优先级的进程占用CPU百分比\n#id 空闲CPU百分比\n#wa 等待输入输出的CPU时间百分比\n#hi 硬件中断\n#si 软件中断\n#################################################\n# 获取用户空间占用CPU百分比\ncpu_user=`top -b -n 1 | grep Cpu | awk \'{print $2}\' | cut -f 1 -d \"%\"`\necho \"用户空间占用CPU百分比\"$cpu_user\n \n# 获取内核空间占用CPU百分比\ncpu_system=`top -b -n 1 | grep Cpu | awk \'{print $4}\' | cut -f 1 -d \"%\"`\necho \"内核空间占用CPU百分比\"$cpu_system\n \n# 获取空闲CPU百分比\ncpu_idle=`top -b -n 1 | grep Cpu | awk \'{print $8}\' | cut -f 1 -d \"%\"`\necho \"空闲CPU百分比\"$cpu_idle\n \n# 获取等待输入输出占CPU百分比\ncpu_iowait=`top -b -n 1 | grep Cpu | awk \'{print $10}\' | cut -f 1 -d \"%\"`\necho \"等待输入输出占CPU百分比\"$cpu_iowait\n \n#2、获取CPU上下文切换和中断次数\n# 获取CPU中断次数\ncpu_interrupt=`vmstat -n 1 1 | sed -n 3p | awk \'{print $11}\'`\necho \"CPU中断次数\"$cpu_interrupt\n \n# 获取CPU上下文切换次数\ncpu_context_switch=`vmstat -n 1 1 | sed -n 3p | awk \'{print $12}\'`\necho \"CPU上下文切换次数\"$cpu_context_switch\n \n#3、获取CPU负载信息\n# 获取CPU15分钟前到现在的负载平均值\ncpu_load_15min=`uptime | awk \'{print $11}\' | cut -f 1 -d \',\'`\necho \"CPU 15分钟前到现在的负载平均值\"$cpu_load_15min\n \n# 获取CPU5分钟前到现在的负载平均值\ncpu_load_5min=`uptime | awk \'{print $10}\' | cut -f 1 -d \',\'`\necho \"CPU 5分钟前到现在的负载平均值\"$cpu_load_5min\n \n# 获取CPU1分钟前到现在的负载平均值\ncpu_load_1min=`uptime | awk \'{print $9}\' | cut -f 1 -d \',\'`\necho \"CPU 1分钟前到现在的负载平均值\"$cpu_load_1min\n \n# 获取任务队列(就绪状态等待的进程数)\ncpu_task_length=`vmstat -n 1 1 | sed -n 3p | awk \'{print $1}\'`\necho \"CPU任务队列长度\"$cpu_task_length\n \n#4、获取内存信息\n# 获取物理内存总量\nmem_total=`free -h | grep Mem | awk \'{print $2}\'`\necho \"物理内存总量:\"$mem_total\n \n# 获取操作系统已使用内存总量\nmem_sys_used=`free -h | grep Mem | awk \'{print $3}\'`\necho \"已使用内存总量(操作系统)\"$mem_sys_used\n \n# 获取操作系统未使用内存总量\nmem_sys_free=`free -h | grep Mem | awk \'{print $4}\'`\necho \"剩余内存总量(操作系统)\"$mem_sys_free\n \n# 获取应用程序已使用的内存总量\nmem_user_used=`free | sed -n 3p | awk \'{print $3}\'`\necho \"已使用内存总量(应用程序)\"$mem_user_used\n \n# 获取应用程序未使用内存总量\nmem_user_free=`free | sed -n 3p | awk \'{print $4}\'`\necho \"剩余内存总量(应用程序)\"$mem_user_free\n \n# 获取交换分区总大小\nmem_swap_total=`free | grep Swap | awk \'{print $2}\'`\necho \"交换分区总大小:\"$mem_swap_total\n \n# 获取已使用交换分区大小\nmem_swap_used=`free | grep Swap | awk \'{print $3}\'`\necho \"已使用交换分区大小:\"$mem_swap_used\n \n# 获取剩余交换分区大小\nmem_swap_free=`free | grep Swap | awk \'{print $4}\'`\necho \"剩余交换分区大小:\"$mem_swap_free', NULL, '获取cpu、内存等系统运行状态', 1, NULL, NULL, NULL, NULL, NULL, '2021-04-25 15:07:16');
INSERT INTO `t_machine_script` VALUES (4, 'top', 9999999, 'top', NULL, '实时获取系统运行状态', 3, NULL, NULL, 1, 'admin', NULL, '2021-05-24 15:58:20');
INSERT INTO `t_machine_script` VALUES (18, 'disk-mem', 9999999, 'df -h', '', '磁盘空间查看', 1, 1, 'admin', 1, 'admin', '2021-07-16 10:49:53', '2021-07-16 10:49:53');
COMMIT;
-- ----------------------------
-- Table structure for t_mongo
-- ----------------------------
DROP TABLE IF EXISTS `t_mongo`;
CREATE TABLE `t_mongo` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '名称',
`uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '连接uri',
`enable_ssh_tunnel` tinyint(2) DEFAULT NULL COMMENT '是否启用ssh隧道',
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
`tag_id` bigint(20) DEFAULT NULL COMMENT '标签id',
`tag_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '标签路径',
`create_time` datetime NOT NULL,
`creator_id` bigint(20) DEFAULT NULL,
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`modifier_id` bigint(20) DEFAULT NULL,
`modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- ----------------------------
-- Records of t_mongo
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for t_redis
-- ----------------------------
DROP TABLE IF EXISTS `t_redis`;
CREATE TABLE `t_redis` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '名称',
`host` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`db` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '库号: 多个库用,分割',
`mode` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
`enable_ssh_tunnel` tinyint(2) DEFAULT NULL COMMENT '是否启用ssh隧道',
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
`remark` varchar(125) COLLATE utf8mb4_bin DEFAULT NULL,
`tag_id` bigint(20) DEFAULT NULL COMMENT '标签id',
`tag_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '标签路径',
`creator` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`creator_id` bigint(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`modifier` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`modifier_id` bigint(20) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_tag_path` (`tag_path`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='redis信息';
-- ----------------------------
-- Records of t_redis
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for t_sys_account
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_account`;
CREATE TABLE `t_sys_account` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`status` tinyint(4) DEFAULT NULL,
`last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
`last_login_ip` varchar(24) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`create_time` datetime NOT NULL,
`creator_id` bigint(255) NOT NULL,
`creator` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`update_time` datetime NOT NULL,
`modifier_id` bigint(255) NOT NULL,
`modifier` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='账号信息表';
-- ----------------------------
-- Records of t_sys_account
-- ----------------------------
BEGIN;
INSERT INTO `t_sys_account` VALUES (1, "管理员", 'admin', '$2a$10$w3Wky2U.tinvR7c/s0aKPuwZsIu6pM1/DMJalwBDMbE6niHIxVrrm', 1, '2022-10-26 20:03:48', '::1', '2020-01-01 19:00:00', 1, 'admin', '2020-01-01 19:00:00', 1, 'admin');
COMMIT;
-- ----------------------------
-- Table structure for t_sys_account_role
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_account_role`;
CREATE TABLE `t_sys_account_role` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Id',
`account_id` bigint(20) NOT NULL COMMENT '账号id',
`role_id` bigint(20) NOT NULL COMMENT '角色id',
`creator` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`creator_id` bigint(20) unsigned DEFAULT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='账号角色关联表';
-- ----------------------------
-- Records of t_sys_account_role
-- ----------------------------
BEGIN;
INSERT INTO `t_sys_account_role` VALUES (25, 1, 1, 'admin', 1, '2021-05-28 16:21:45');
COMMIT;
-- ----------------------------
-- Table structure for t_sys_config
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_config`;
CREATE TABLE `t_sys_config` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '配置名',
`key` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '配置key',
`params` varchar(500) COLLATE utf8mb4_bin DEFAULT NULL,
`value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '配置value',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注',
`create_time` datetime NOT NULL,
`creator_id` bigint(20) NOT NULL,
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`update_time` datetime NOT NULL,
`modifier_id` bigint(20) NOT NULL,
`modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- ----------------------------
-- Records of t_sys_config
-- ----------------------------
BEGIN;
INSERT INTO `t_sys_config` VALUES (1, '是否启用登录验证码', 'UseLoginCaptcha', NULL, '1', '1: 启用、0: 不启用', '2022-08-25 22:27:17', 1, 'admin', '2022-08-26 10:26:56', 1, 'admin');
INSERT INTO `t_sys_config` VALUES (2, '是否启用水印', 'UseWartermark', NULL, '1', '1: 启用、0: 不启用', '2022-08-25 23:36:35', 1, 'admin', '2022-08-26 10:02:52', 1, 'admin');
COMMIT;
-- ----------------------------
-- Table structure for t_sys_log
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_log`;
CREATE TABLE `t_sys_log` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`type` tinyint(4) NOT NULL COMMENT '类型',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '描述',
`req_param` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '请求信息',
`resp` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '响应信息',
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '调用者',
`creator_id` bigint(20) NOT NULL COMMENT '调用者id',
`create_time` datetime NOT NULL COMMENT '操作时间',
PRIMARY KEY (`id`),
KEY `idx_creator_id` (`creator_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=58 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='系统操作日志';
-- ----------------------------
-- Records of t_sys_log
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for t_sys_msg
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_msg`;
CREATE TABLE `t_sys_msg` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`type` int(255) DEFAULT NULL,
`msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`recipient_id` bigint(20) DEFAULT NULL COMMENT '接收人id-1为所有接收',
`creator_id` bigint(20) DEFAULT NULL,
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=91 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='系统消息表';
-- ----------------------------
-- Records of t_sys_msg
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for t_sys_resource
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_resource`;
CREATE TABLE `t_sys_resource` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`pid` int(11) NOT NULL COMMENT '父节点id',
`type` tinyint(255) NOT NULL COMMENT '1菜单路由2资源按钮等',
`status` int(255) NOT NULL COMMENT '状态1:可用,-1:禁用',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '名称',
`code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '菜单路由为path其他为唯一标识',
`weight` int(11) DEFAULT NULL COMMENT '权重顺序',
`meta` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '元数据',
`creator_id` bigint(20) NOT NULL,
`creator` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`modifier_id` bigint(20) NOT NULL,
`modifier` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=103 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='资源表';
-- ----------------------------
-- Records of t_sys_resource
-- ----------------------------
BEGIN;
INSERT INTO `t_sys_resource` VALUES (1, 0, 1, 1, '首页', '/home', 1, '{\"component\":\"Home\",\"icon\":\"HomeFilled\",\"isAffix\":true,\"isKeepAlive\":true,\"routeName\":\"Home\"}', 1, 'admin', 1, 'admin', '2021-05-25 16:44:41', '2021-05-27 09:12:56');
INSERT INTO `t_sys_resource` VALUES (2, 0, 1, 1, '机器管理', '/machine', 4, '{\"icon\":\"Monitor\",\"isKeepAlive\":true,\"redirect\":\"machine/list\",\"routeName\":\"Machine\"}', 1, 'admin', 1, 'admin', '2021-05-25 16:48:16', '2022-10-06 14:58:49');
INSERT INTO `t_sys_resource` VALUES (3, 2, 1, 1, '机器列表', 'machines', 2, '{\"component\":\"MachineList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"MachineList\"}', 2, 'admin', 1, 'admin', '2021-05-25 16:50:04', '2021-06-30 16:20:08');
INSERT INTO `t_sys_resource` VALUES (4, 0, 1, 1, '系统管理', '/sys', 8, '{\"icon\":\"Setting\",\"isKeepAlive\":true,\"redirect\":\"/sys/resources\",\"routeName\":\"sys\"}', 1, 'admin', 1, 'admin', '2021-05-26 15:20:20', '2022-10-06 14:59:53');
INSERT INTO `t_sys_resource` VALUES (5, 4, 1, 1, '资源管理', 'resources', 3, '{\"component\":\"ResourceList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"ResourceList\"}', 1, 'admin', 1, 'admin', '2021-05-26 15:23:07', '2021-06-08 11:27:55');
INSERT INTO `t_sys_resource` VALUES (11, 4, 1, 1, '角色管理', 'roles', 2, '{\"component\":\"RoleList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"RoleList\"}', 1, 'admin', 1, 'admin', '2021-05-27 11:15:35', '2021-06-03 09:59:41');
INSERT INTO `t_sys_resource` VALUES (12, 3, 2, 1, '机器终端按钮', 'machine:terminal', 4, '', 1, 'admin', 1, 'admin', '2021-05-28 14:06:02', '2021-05-31 17:47:59');
INSERT INTO `t_sys_resource` VALUES (14, 4, 1, 1, '账号管理', 'accounts', 1, '{\"component\":\"AccountList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"AccountList\"}', 1, 'admin', 1, 'admin', '2021-05-28 14:56:25', '2021-06-03 09:39:22');
INSERT INTO `t_sys_resource` VALUES (15, 3, 2, 1, '文件管理按钮', 'machine:file', 5, NULL, 1, 'admin', 1, 'admin', '2021-05-31 17:44:37', '2021-05-31 17:48:07');
INSERT INTO `t_sys_resource` VALUES (16, 3, 2, 1, '机器添加按钮', 'machine:add', 1, NULL, 1, 'admin', 1, 'admin', '2021-05-31 17:46:11', '2021-05-31 19:34:15');
INSERT INTO `t_sys_resource` VALUES (17, 3, 2, 1, '机器编辑按钮', 'machine:update', 2, NULL, 1, 'admin', 1, 'admin', '2021-05-31 17:46:23', '2021-05-31 19:34:18');
INSERT INTO `t_sys_resource` VALUES (18, 3, 2, 1, '机器删除按钮', 'machine:del', 3, NULL, 1, 'admin', 1, 'admin', '2021-05-31 17:46:36', '2021-05-31 19:34:17');
INSERT INTO `t_sys_resource` VALUES (19, 14, 2, 1, '角色分配按钮', 'account:saveRoles', 1, NULL, 1, 'admin', 1, 'admin', '2021-05-31 17:50:51', '2021-05-31 19:19:30');
INSERT INTO `t_sys_resource` VALUES (20, 11, 2, 1, '分配菜单&权限按钮', 'role:saveResources', 1, NULL, 1, 'admin', 1, 'admin', '2021-05-31 17:51:41', '2021-05-31 19:33:37');
INSERT INTO `t_sys_resource` VALUES (21, 14, 2, 1, '账号删除按钮', 'account:del', 2, 'null', 1, 'admin', 1, 'admin', '2021-05-31 18:02:01', '2021-06-10 17:12:17');
INSERT INTO `t_sys_resource` VALUES (22, 11, 2, 1, '角色删除按钮', 'role:del', 2, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:02:29', '2021-05-31 19:33:38');
INSERT INTO `t_sys_resource` VALUES (23, 11, 2, 1, '角色新增按钮', 'role:add', 3, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:02:44', '2021-05-31 19:33:39');
INSERT INTO `t_sys_resource` VALUES (24, 11, 2, 1, '角色编辑按钮', 'role:update', 4, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:02:57', '2021-05-31 19:33:40');
INSERT INTO `t_sys_resource` VALUES (25, 5, 2, 1, '资源新增按钮', 'resource:add', 1, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:03:33', '2021-05-31 19:31:47');
INSERT INTO `t_sys_resource` VALUES (26, 5, 2, 1, '资源删除按钮', 'resource:delete', 2, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:03:47', '2021-05-31 19:29:40');
INSERT INTO `t_sys_resource` VALUES (27, 5, 2, 1, '资源编辑按钮', 'resource:update', 3, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:04:03', '2021-05-31 19:29:40');
INSERT INTO `t_sys_resource` VALUES (28, 5, 2, 1, '资源禁用启用按钮', 'resource:changeStatus', 4, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:04:33', '2021-05-31 18:04:33');
INSERT INTO `t_sys_resource` VALUES (29, 14, 2, 1, '账号添加按钮', 'account:add', 3, NULL, 1, 'admin', 1, 'admin', '2021-05-31 19:23:42', '2021-05-31 19:23:42');
INSERT INTO `t_sys_resource` VALUES (30, 14, 2, 1, '账号编辑修改按钮', 'account:update', 4, NULL, 1, 'admin', 1, 'admin', '2021-05-31 19:23:58', '2021-05-31 19:23:58');
INSERT INTO `t_sys_resource` VALUES (31, 14, 2, 1, '账号管理基本权限', 'account', 0, 'null', 1, 'admin', 1, 'admin', '2021-05-31 21:25:06', '2021-06-22 11:20:34');
INSERT INTO `t_sys_resource` VALUES (32, 5, 2, 1, '资源管理基本权限', 'resource', 0, NULL, 1, 'admin', 1, 'admin', '2021-05-31 21:25:25', '2021-05-31 21:25:25');
INSERT INTO `t_sys_resource` VALUES (33, 11, 2, 1, '角色管理基本权限', 'role', 0, NULL, 1, 'admin', 1, 'admin', '2021-05-31 21:25:40', '2021-05-31 21:25:40');
INSERT INTO `t_sys_resource` VALUES (34, 14, 2, 1, '账号启用禁用按钮', 'account:changeStatus', 5, NULL, 1, 'admin', 1, 'admin', '2021-05-31 21:29:48', '2021-05-31 21:29:48');
INSERT INTO `t_sys_resource` VALUES (36, 0, 1, 1, 'DBMS', '/dbms', 5, '{\"icon\":\"Grid\",\"isKeepAlive\":true,\"routeName\":\"DBMS\"}', 1, 'admin', 1, 'admin', '2021-06-01 14:01:33', '2022-10-06 15:00:40');
INSERT INTO `t_sys_resource` VALUES (37, 3, 2, 1, '添加文件配置', 'machine:addFile', 6, 'null', 1, 'admin', 1, 'admin', '2021-06-01 19:54:23', '2021-06-01 19:54:23');
INSERT INTO `t_sys_resource` VALUES (38, 36, 1, 1, '数据操作', 'sql-exec', 1, '{\"component\":\"SqlExec\",\"icon\":\"Search\",\"isKeepAlive\":true,\"routeName\":\"SqlExec\"}', 1, 'admin', 1, 'admin', '2021-06-03 09:09:29', '2021-11-08 09:59:26');
INSERT INTO `t_sys_resource` VALUES (39, 0, 1, 1, '个人中心', '/personal', 2, '{\"component\":\"Personal\",\"icon\":\"UserFilled\",\"isHide\":true,\"isKeepAlive\":true,\"routeName\":\"Personal\"}', 1, 'admin', 1, 'admin', '2021-06-03 14:25:35', '2021-09-10 09:18:46');
INSERT INTO `t_sys_resource` VALUES (40, 3, 2, 1, '文件管理-新增按钮', 'machine:file:add', 7, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:06:26', '2021-06-08 11:12:28');
INSERT INTO `t_sys_resource` VALUES (41, 3, 2, 1, '文件管理-删除按钮', 'machine:file:del', 8, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:06:49', '2021-06-08 11:06:49');
INSERT INTO `t_sys_resource` VALUES (42, 3, 2, 1, '文件管理-写入or下载文件权限', 'machine:file:write', 9, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:07:27', '2021-06-08 11:07:27');
INSERT INTO `t_sys_resource` VALUES (43, 3, 2, 1, '文件管理-文件上传按钮', 'machine:file:upload', 10, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:07:42', '2021-06-08 11:07:42');
INSERT INTO `t_sys_resource` VALUES (44, 3, 2, 1, '文件管理-删除文件按钮', 'machine:file:rm', 11, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:08:12', '2021-06-08 11:08:12');
INSERT INTO `t_sys_resource` VALUES (45, 3, 2, 1, '脚本管理-保存脚本按钮', 'machine:script:save', 12, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:09:01', '2021-06-08 11:09:01');
INSERT INTO `t_sys_resource` VALUES (46, 3, 2, 1, '脚本管理-删除按钮', 'machine:script:del', 13, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:09:27', '2021-06-08 11:09:27');
INSERT INTO `t_sys_resource` VALUES (47, 3, 2, 1, '脚本管理-执行按钮', 'machine:script:run', 14, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:09:50', '2021-06-08 11:09:50');
INSERT INTO `t_sys_resource` VALUES (49, 36, 1, 1, '数据库管理', 'dbs', 2, '{\"component\":\"DbList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"DbList\"}', 1, 'admin', 1, 'admin', '2021-07-07 15:13:55', '2021-07-07 15:13:55');
INSERT INTO `t_sys_resource` VALUES (54, 49, 2, 1, '数据库保存', 'db:save', 1, 'null', 1, 'admin', 1, 'admin', '2021-07-08 17:30:36', '2021-07-08 17:31:05');
INSERT INTO `t_sys_resource` VALUES (55, 49, 2, 1, '数据库删除', 'db:del', 2, 'null', 1, 'admin', 1, 'admin', '2021-07-08 17:30:48', '2021-07-08 17:30:48');
INSERT INTO `t_sys_resource` VALUES (57, 3, 2, 1, '基本权限', 'machine', 0, 'null', 1, 'admin', 1, 'admin', '2021-07-09 10:48:02', '2021-07-09 10:48:02');
INSERT INTO `t_sys_resource` VALUES (58, 49, 2, 1, '基本权限', 'db', 0, 'null', 1, 'admin', 1, 'admin', '2021-07-09 10:48:22', '2021-07-09 10:48:22');
INSERT INTO `t_sys_resource` VALUES (59, 38, 2, 1, '基本权限', 'db:exec', 1, 'null', 1, 'admin', 1, 'admin', '2021-07-09 10:50:13', '2021-07-09 10:50:13');
INSERT INTO `t_sys_resource` VALUES (60, 0, 1, 1, 'Redis', '/redis', 6, '{\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"RDS\"}', 1, 'admin', 1, 'admin', '2021-07-19 20:15:41', '2022-10-06 15:01:29');
INSERT INTO `t_sys_resource` VALUES (61, 60, 1, 1, '数据操作', 'data-operation', 1, '{\"component\":\"DataOperation\",\"icon\":\"Search\",\"isKeepAlive\":true,\"routeName\":\"DataOperation\"}', 1, 'admin', 1, 'admin', '2021-07-19 20:17:29', '2021-07-20 10:45:28');
INSERT INTO `t_sys_resource` VALUES (62, 61, 2, 1, '基本权限', 'redis:data', 1, 'null', 1, 'admin', 1, 'admin', '2021-07-19 20:18:54', '2021-07-19 20:18:54');
INSERT INTO `t_sys_resource` VALUES (63, 60, 1, 1, 'redis管理', 'manage', 2, '{\"component\":\"RedisList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"RedisList\"}', 1, 'admin', 1, 'admin', '2021-07-20 10:48:04', '2021-07-20 10:48:04');
INSERT INTO `t_sys_resource` VALUES (64, 63, 2, 1, '基本权限', 'redis:manage', 1, 'null', 1, 'admin', 1, 'admin', '2021-07-20 10:48:26', '2021-07-20 10:48:26');
INSERT INTO `t_sys_resource` VALUES (71, 61, 2, 1, '数据保存', 'redis:data:save', 6, 'null', 1, 'admin', 1, 'admin', '2021-08-17 11:20:37', '2021-08-17 11:20:37');
INSERT INTO `t_sys_resource` VALUES (72, 3, 2, 1, '终止进程', 'machine:killprocess', 6, 'null', 1, 'admin', 1, 'admin', '2021-08-17 11:20:37', '2021-08-17 11:20:37');
INSERT INTO `t_sys_resource` VALUES (79, 0, 1, 1, 'Mongo', '/mongo', 7, '{\"icon\":\"Document\",\"isKeepAlive\":true,\"routeName\":\"Mongo\"}', 1, 'admin', 1, 'admin', '2022-05-13 14:00:41', '2022-10-06 15:01:34');
INSERT INTO `t_sys_resource` VALUES (80, 79, 1, 1, '数据操作', 'mongo-data-operation', 1, '{\"component\":\"MongoDataOp\",\"icon\":\"Document\",\"isKeepAlive\":true,\"routeName\":\"MongoDataOp\"}', 1, 'admin', 1, 'admin', '2022-05-13 14:03:58', '2022-05-14 20:16:07');
INSERT INTO `t_sys_resource` VALUES (81, 80, 2, 1, '基本权限', 'mongo:base', 1, 'null', 1, 'admin', 1, 'admin', '2022-05-13 14:04:16', '2022-05-13 14:04:16');
INSERT INTO `t_sys_resource` VALUES (82, 79, 1, 1, 'Mongo管理', 'mongo-manage', 2, '{\"component\":\"MongoList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"MongoList\"}', 1, 'admin', 1, 'admin', '2022-05-16 18:13:06', '2022-05-16 18:13:06');
INSERT INTO `t_sys_resource` VALUES (83, 82, 2, 1, '基本权限', 'mongo:manage:base', 1, 'null', 1, 'admin', 1, 'admin', '2022-05-16 18:13:25', '2022-05-16 18:13:25');
INSERT INTO `t_sys_resource` VALUES (84, 4, 1, 1, '操作日志', 'syslogs', 4, '{\"component\":\"SyslogList\",\"icon\":\"Tickets\",\"routeName\":\"SyslogList\"}', 1, 'admin', 1, 'admin', '2022-07-13 19:57:07', '2022-07-13 22:58:19');
INSERT INTO `t_sys_resource` VALUES (85, 84, 2, 1, '操作日志基本权限', 'syslog', 1, 'null', 1, 'admin', 1, 'admin', '2022-07-13 19:57:55', '2022-07-13 19:57:55');
INSERT INTO `t_sys_resource` VALUES (87, 4, 1, 1, '系统配置', 'configs', 5, '{\"component\":\"ConfigList\",\"icon\":\"Setting\",\"isKeepAlive\":true,\"routeName\":\"ConfigList\"}', 1, 'admin', 1, 'admin', '2022-08-25 22:18:55', '2022-08-25 22:19:18');
INSERT INTO `t_sys_resource` VALUES (88, 87, 2, 1, '基本权限', 'config:base', 1, 'null', 1, 'admin', 1, 'admin', '2022-08-25 22:19:35', '2022-08-25 22:19:35');
INSERT INTO `t_sys_resource` VALUES (93, 0, 1, 1, '标签管理', '/tag', 3, '{\"icon\":\"CollectionTag\",\"isKeepAlive\":true,\"routeName\":\"Tag\"}', 1, 'admin', 1, 'admin', '2022-10-24 15:18:40', '2022-10-24 15:24:29');
INSERT INTO `t_sys_resource` VALUES (94, 93, 1, 1, '标签树', 'tag-trees', 1, '{\"component\":\"TagTreeList\",\"icon\":\"CollectionTag\",\"isKeepAlive\":true,\"routeName\":\"TagTreeList\"}', 1, 'admin', 1, 'admin', '2022-10-24 15:19:40', '2022-10-24 15:28:07');
INSERT INTO `t_sys_resource` VALUES (95, 93, 1, 1, '团队管理', 'teams', 2, '{\"component\":\"TeamList\",\"icon\":\"UserFilled\",\"isKeepAlive\":true,\"routeName\":\"TeamList\"}', 1, 'admin', 1, 'admin', '2022-10-24 15:20:09', '2022-10-24 15:24:01');
INSERT INTO `t_sys_resource` VALUES (96, 94, 2, 1, '保存标签', 'tag:save', 1, 'null', 1, 'admin', 1, 'admin', '2022-10-24 15:20:40', '2022-10-26 13:58:36');
INSERT INTO `t_sys_resource` VALUES (97, 95, 2, 1, '保存团队', 'team:save', 1, 'null', 1, 'admin', 1, 'admin', '2022-10-24 15:20:57', '2022-10-26 13:58:56');
INSERT INTO `t_sys_resource` VALUES (98, 94, 2, 1, '删除标签', 'tag:del', 2, 'null', 1, 'admin', 1, 'admin', '2022-10-26 13:58:47', '2022-10-26 13:58:47');
INSERT INTO `t_sys_resource` VALUES (99, 95, 2, 1, '删除团队', 'team:del', 2, 'null', 1, 'admin', 1, 'admin', '2022-10-26 13:59:06', '2022-10-26 13:59:06');
INSERT INTO `t_sys_resource` VALUES (100, 95, 2, 1, '新增团队成员', 'team:member:save', 3, 'null', 1, 'admin', 1, 'admin', '2022-10-26 13:59:27', '2022-10-26 13:59:27');
INSERT INTO `t_sys_resource` VALUES (101, 95, 2, 1, '移除团队成员', 'team:member:del', 4, 'null', 1, 'admin', 1, 'admin', '2022-10-26 13:59:43', '2022-10-26 13:59:43');
INSERT INTO `t_sys_resource` VALUES (102, 95, 2, 1, '保存团队标签', 'team:tag:save', 5, 'null', 1, 'admin', 1, 'admin', '2022-10-26 13:59:57', '2022-10-26 13:59:57');
COMMIT;
-- ----------------------------
-- Table structure for t_sys_role
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_role`;
CREATE TABLE `t_sys_role` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '角色code',
`status` tinyint(255) DEFAULT NULL,
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`type` tinyint(2) NOT NULL COMMENT '类型1:公共角色2:特殊角色',
`create_time` datetime DEFAULT NULL,
`creator_id` bigint(20) DEFAULT NULL,
`creator` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`modifier_id` bigint(20) DEFAULT NULL,
`modifier` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='角色表';
-- ----------------------------
-- Records of t_sys_role
-- ----------------------------
BEGIN;
INSERT INTO `t_sys_role` VALUES (1, '超级管理员', 'SUPBER_ADMIN', 1, '权限超级大,拥有所有权限', 2, '2021-05-27 14:09:50', 1, 'admin', '2021-05-28 10:26:28', 1, 'admin');
INSERT INTO `t_sys_role` VALUES (6, '普通管理员', 'ADMIN', 1, '只拥有部分管理权限', 2, '2021-05-28 15:55:36', 1, 'admin', '2021-05-28 15:55:36', 1, 'admin');
INSERT INTO `t_sys_role` VALUES (7, '公共角色', 'COMMON', 1, '所有账号基础角色', 1, '2021-07-06 15:05:47', 1, 'admin', '2021-07-06 15:05:47', 1, 'admin');
INSERT INTO `t_sys_role` VALUES (8, '开发', 'DEV', 1, '研发人员', 0, '2021-07-09 10:46:10', 1, 'admin', '2021-07-09 10:46:10', 1, 'admin');
COMMIT;
-- ----------------------------
-- Table structure for t_sys_role_resource
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_role_resource`;
CREATE TABLE `t_sys_role_resource` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_id` bigint(20) NOT NULL,
`resource_id` bigint(20) NOT NULL,
`creator_id` bigint(20) unsigned DEFAULT NULL,
`creator` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=526 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='角色资源关联表';
-- ----------------------------
-- Records of t_sys_role_resource
-- ----------------------------
BEGIN;
INSERT INTO `t_sys_role_resource` VALUES (1, 1, 1, 1, 'admin', '2021-05-27 15:07:39');
INSERT INTO `t_sys_role_resource` VALUES (323, 1, 2, 1, 'admin', '2021-05-28 09:04:50');
INSERT INTO `t_sys_role_resource` VALUES (326, 1, 4, 1, 'admin', '2021-05-28 09:04:50');
INSERT INTO `t_sys_role_resource` VALUES (327, 1, 5, 1, 'admin', '2021-05-28 09:04:50');
INSERT INTO `t_sys_role_resource` VALUES (328, 1, 11, 1, 'admin', '2021-05-28 09:04:50');
INSERT INTO `t_sys_role_resource` VALUES (335, 1, 14, 1, 'admin', '2021-05-28 17:42:21');
INSERT INTO `t_sys_role_resource` VALUES (336, 1, 3, 1, 'admin', '2021-05-28 17:42:43');
INSERT INTO `t_sys_role_resource` VALUES (337, 1, 12, 1, 'admin', '2021-05-28 17:42:43');
INSERT INTO `t_sys_role_resource` VALUES (338, 6, 2, 1, 'admin', '2021-05-28 19:19:38');
INSERT INTO `t_sys_role_resource` VALUES (339, 6, 3, 1, 'admin', '2021-05-28 19:19:38');
INSERT INTO `t_sys_role_resource` VALUES (342, 6, 1, 1, 'admin', '2021-05-29 01:31:22');
INSERT INTO `t_sys_role_resource` VALUES (343, 5, 1, 1, 'admin', '2021-05-31 14:05:23');
INSERT INTO `t_sys_role_resource` VALUES (344, 5, 4, 1, 'admin', '2021-05-31 14:05:23');
INSERT INTO `t_sys_role_resource` VALUES (345, 5, 14, 1, 'admin', '2021-05-31 14:05:23');
INSERT INTO `t_sys_role_resource` VALUES (346, 5, 5, 1, 'admin', '2021-05-31 14:05:23');
INSERT INTO `t_sys_role_resource` VALUES (347, 5, 11, 1, 'admin', '2021-05-31 14:05:23');
INSERT INTO `t_sys_role_resource` VALUES (348, 5, 3, 1, 'admin', '2021-05-31 16:33:14');
INSERT INTO `t_sys_role_resource` VALUES (349, 5, 12, 1, 'admin', '2021-05-31 16:33:14');
INSERT INTO `t_sys_role_resource` VALUES (350, 5, 2, 1, 'admin', '2021-05-31 16:33:14');
INSERT INTO `t_sys_role_resource` VALUES (353, 1, 15, 1, 'admin', '2021-05-31 17:48:33');
INSERT INTO `t_sys_role_resource` VALUES (354, 1, 16, 1, 'admin', '2021-05-31 17:48:33');
INSERT INTO `t_sys_role_resource` VALUES (355, 1, 17, 1, 'admin', '2021-05-31 17:48:33');
INSERT INTO `t_sys_role_resource` VALUES (356, 1, 18, 1, 'admin', '2021-05-31 17:48:33');
INSERT INTO `t_sys_role_resource` VALUES (358, 1, 20, 1, 'admin', '2021-05-31 17:52:08');
INSERT INTO `t_sys_role_resource` VALUES (360, 1, 22, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (361, 1, 23, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (362, 1, 24, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (363, 1, 25, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (364, 1, 26, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (365, 1, 27, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (366, 1, 28, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (369, 1, 31, 1, 'admin', '2021-05-31 21:25:56');
INSERT INTO `t_sys_role_resource` VALUES (370, 1, 32, 1, 'admin', '2021-05-31 21:25:56');
INSERT INTO `t_sys_role_resource` VALUES (371, 1, 33, 1, 'admin', '2021-05-31 21:25:56');
INSERT INTO `t_sys_role_resource` VALUES (374, 1, 36, 1, 'admin', '2021-06-01 14:01:57');
INSERT INTO `t_sys_role_resource` VALUES (375, 1, 19, 1, 'admin', '2021-06-01 17:34:03');
INSERT INTO `t_sys_role_resource` VALUES (376, 1, 21, 1, 'admin', '2021-06-01 17:34:03');
INSERT INTO `t_sys_role_resource` VALUES (377, 1, 29, 1, 'admin', '2021-06-01 17:34:03');
INSERT INTO `t_sys_role_resource` VALUES (378, 1, 30, 1, 'admin', '2021-06-01 17:34:03');
INSERT INTO `t_sys_role_resource` VALUES (379, 1, 34, 1, 'admin', '2021-06-01 17:34:03');
INSERT INTO `t_sys_role_resource` VALUES (380, 1, 37, 1, 'admin', '2021-06-03 09:09:42');
INSERT INTO `t_sys_role_resource` VALUES (381, 1, 38, 1, 'admin', '2021-06-03 09:09:42');
INSERT INTO `t_sys_role_resource` VALUES (383, 1, 40, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (384, 1, 41, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (385, 1, 42, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (386, 1, 43, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (387, 1, 44, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (388, 1, 45, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (389, 1, 46, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (390, 1, 47, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (391, 6, 39, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (392, 6, 15, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (395, 6, 31, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (396, 6, 33, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (397, 6, 32, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (398, 6, 4, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (399, 6, 14, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (400, 6, 11, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (401, 6, 5, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (403, 7, 1, 1, 'admin', '2021-07-06 15:07:09');
INSERT INTO `t_sys_role_resource` VALUES (405, 1, 49, 1, 'admin', '2021-07-07 15:14:17');
INSERT INTO `t_sys_role_resource` VALUES (410, 1, 54, 1, 'admin', '2021-07-08 17:32:19');
INSERT INTO `t_sys_role_resource` VALUES (411, 1, 55, 1, 'admin', '2021-07-08 17:32:19');
INSERT INTO `t_sys_role_resource` VALUES (413, 1, 57, 1, 'admin', '2021-07-09 10:48:50');
INSERT INTO `t_sys_role_resource` VALUES (414, 1, 58, 1, 'admin', '2021-07-09 10:48:50');
INSERT INTO `t_sys_role_resource` VALUES (415, 8, 1, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (416, 8, 39, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (418, 8, 57, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (419, 8, 12, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (420, 8, 15, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (421, 8, 38, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (422, 8, 58, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (423, 8, 2, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (425, 8, 3, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (426, 8, 36, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (427, 8, 49, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (428, 1, 59, 1, 'admin', '2021-07-09 10:50:20');
INSERT INTO `t_sys_role_resource` VALUES (429, 8, 59, 1, 'admin', '2021-07-09 10:50:32');
INSERT INTO `t_sys_role_resource` VALUES (431, 6, 57, 1, 'admin', '2021-07-12 16:44:12');
INSERT INTO `t_sys_role_resource` VALUES (433, 1, 60, 1, 'admin', '2021-07-19 20:19:29');
INSERT INTO `t_sys_role_resource` VALUES (434, 1, 61, 1, 'admin', '2021-07-19 20:19:29');
INSERT INTO `t_sys_role_resource` VALUES (435, 1, 62, 1, 'admin', '2021-07-19 20:19:29');
INSERT INTO `t_sys_role_resource` VALUES (436, 1, 63, 1, 'admin', '2021-07-20 10:48:39');
INSERT INTO `t_sys_role_resource` VALUES (437, 1, 64, 1, 'admin', '2021-07-20 10:48:39');
INSERT INTO `t_sys_role_resource` VALUES (444, 7, 39, 1, 'admin', '2021-09-09 10:10:30');
INSERT INTO `t_sys_role_resource` VALUES (450, 6, 16, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (451, 6, 17, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (452, 6, 18, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (453, 6, 37, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (454, 6, 40, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (455, 6, 41, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (456, 6, 42, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (457, 6, 43, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (458, 6, 44, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (459, 6, 45, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (460, 6, 46, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (461, 6, 47, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (462, 6, 36, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (463, 6, 38, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (464, 6, 59, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (465, 6, 49, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (466, 6, 58, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (467, 6, 54, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (468, 6, 55, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (469, 6, 60, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (470, 6, 61, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (471, 6, 62, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (472, 6, 63, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (473, 6, 64, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (479, 6, 19, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (480, 6, 21, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (481, 6, 29, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (482, 6, 30, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (483, 6, 34, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (484, 6, 20, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (485, 6, 22, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (486, 6, 23, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (487, 6, 24, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (488, 6, 25, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (489, 6, 26, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (490, 6, 27, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (491, 6, 28, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (492, 8, 42, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (493, 8, 43, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (494, 8, 47, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (495, 8, 60, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (496, 8, 61, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (497, 8, 62, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (498, 8, 63, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (499, 8, 64, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (500, 1, 72, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (501, 1, 71, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (502, 1, 79, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (503, 1, 80, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (504, 1, 81, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (505, 1, 82, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (506, 1, 83, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (507, 1, 84, 1, 'admin', '2022-07-14 11:10:11');
INSERT INTO `t_sys_role_resource` VALUES (508, 1, 85, 1, 'admin', '2022-07-14 11:10:11');
INSERT INTO `t_sys_role_resource` VALUES (510, 1, 87, 1, 'admin', '2022-07-14 11:10:11');
INSERT INTO `t_sys_role_resource` VALUES (511, 1, 88, 1, 'admin', '2022-10-08 10:54:06');
INSERT INTO `t_sys_role_resource` VALUES (512, 8, 80, 1, 'admin', '2022-10-08 10:54:34');
INSERT INTO `t_sys_role_resource` VALUES (513, 8, 81, 1, 'admin', '2022-10-08 10:54:34');
INSERT INTO `t_sys_role_resource` VALUES (515, 8, 79, 1, 'admin', '2022-10-08 10:54:34');
INSERT INTO `t_sys_role_resource` VALUES (516, 1, 93, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (517, 1, 94, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (518, 1, 96, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (519, 1, 98, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (520, 1, 95, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (521, 1, 97, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (522, 1, 99, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (523, 1, 100, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (524, 1, 101, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (525, 1, 102, 1, 'admin', '2022-10-26 20:03:14');
COMMIT;
-- ----------------------------
-- Table structure for t_tag_tree
-- ----------------------------
DROP TABLE IF EXISTS `t_tag_tree`;
CREATE TABLE `t_tag_tree` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`pid` bigint(20) NOT NULL DEFAULT '0',
`code` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '标识符',
`code_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '标识符路径',
`name` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '名称',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`create_time` datetime NOT NULL,
`creator_id` bigint(20) NOT NULL,
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`update_time` datetime NOT NULL,
`modifier_id` bigint(20) NOT NULL,
`modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_code_path` (`code_path`(100)) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='标签树';
-- ----------------------------
-- Records of t_tag_tree
-- ----------------------------
BEGIN;
INSERT INTO `t_tag_tree` VALUES (33, 0, 'default', 'default', '默认', '默认标签', '2022-10-26 20:04:19', 1, 'admin', '2022-10-26 20:04:19', 1, 'admin');
COMMIT;
-- ----------------------------
-- Table structure for t_tag_tree_team
-- ----------------------------
DROP TABLE IF EXISTS `t_tag_tree_team`;
CREATE TABLE `t_tag_tree_team` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`tag_id` bigint(20) NOT NULL COMMENT '项目树id',
`tag_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`team_id` bigint(20) NOT NULL COMMENT '团队id',
`create_time` datetime NOT NULL,
`creator_id` bigint(20) NOT NULL,
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`update_time` datetime NOT NULL,
`modifier_id` bigint(20) NOT NULL,
`modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_tag_id` (`tag_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='标签树团队关联信息';
-- ----------------------------
-- Records of t_tag_tree_team
-- ----------------------------
BEGIN;
INSERT INTO `t_tag_tree_team` VALUES (31, 33, 'default', 3, '2022-10-26 20:04:45', 1, 'admin', '2022-10-26 20:04:45', 1, 'admin');
COMMIT;
-- ----------------------------
-- Table structure for t_team
-- ----------------------------
DROP TABLE IF EXISTS `t_team`;
CREATE TABLE `t_team` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '名称',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注',
`create_time` datetime NOT NULL,
`creator_id` bigint(20) NOT NULL,
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`modifier_id` bigint(20) DEFAULT NULL,
`modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='团队信息';
-- ----------------------------
-- Records of t_team
-- ----------------------------
BEGIN;
INSERT INTO `t_team` VALUES (3, '默认团队', '默认团队', '2022-10-26 20:04:36', 1, 'admin', '2022-10-26 20:04:36', 1, 'admin');
COMMIT;
-- ----------------------------
-- Table structure for t_team_member
-- ----------------------------
DROP TABLE IF EXISTS `t_team_member`;
CREATE TABLE `t_team_member` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`team_id` bigint(20) NOT NULL COMMENT '项目团队id',
`account_id` bigint(20) NOT NULL COMMENT '成员id',
`username` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`create_time` datetime NOT NULL,
`creator_id` bigint(20) NOT NULL,
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`update_time` datetime NOT NULL,
`modifier_id` bigint(20) NOT NULL,
`modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='团队成员表';
-- ----------------------------
-- Records of t_team_member
-- ----------------------------
BEGIN;
INSERT INTO `t_team_member` VALUES (7, 3, 1, 'admin', '2022-10-26 20:04:36', 1, 'admin', '2022-10-26 20:04:36', 1, 'admin');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -0,0 +1,11 @@
[mysqld]
user=mysql
default-storage-engine=INNODB
character-set-server=utf8
character-set-client-handshake=FALSE
collation-server=utf8_unicode_ci
init_connect='SET NAMES utf8'
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8

View File

@@ -1,25 +1,25 @@
module mayfly-go
go 1.19
go 1.20
require (
github.com/gin-gonic/gin v1.8.1
github.com/gin-gonic/gin v1.8.2
github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.6.0
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/go-sql-driver/mysql v1.7.0
github.com/golang-jwt/jwt/v4 v4.4.3
github.com/gorilla/websocket v1.5.0
github.com/lib/pq v1.10.6
github.com/lib/pq v1.10.7
github.com/mojocn/base64Captcha v1.3.5 //
github.com/pkg/sftp v1.13.5
github.com/robfig/cron/v3 v3.0.1 //
github.com/sirupsen/logrus v1.9.0
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
go.mongodb.org/mongo-driver v1.11.0 // mongo
golang.org/x/crypto v0.3.0 // ssh
go.mongodb.org/mongo-driver v1.11.1 // mongo
golang.org/x/crypto v0.6.0 // ssh
gopkg.in/yaml.v3 v3.0.1
// gorm
gorm.io/driver/mysql v1.4.1
gorm.io/gorm v1.24.0
gorm.io/driver/mysql v1.4.5
gorm.io/gorm v1.24.3
)
require (
@@ -28,8 +28,8 @@ require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.1 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/goccy/go-json v0.9.11 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
@@ -38,11 +38,11 @@ require (
github.com/klauspost/compress v1.13.6 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
@@ -50,11 +50,11 @@ require (
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/net v0.6.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.28.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

View File

@@ -74,7 +74,6 @@ func InitRouter() *gin.Engine {
sys_router.Init(api)
// project_router.Init(api)
tag_router.Init(api)
machine_router.Init(api)
db_router.Init(api)

View File

@@ -2,9 +2,9 @@ package initialize
import (
sysapp "mayfly-go/internal/sys/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
)
func InitSaveLogFunc() ctx.SaveLogFunc {
func InitSaveLogFunc() req.SaveLogFunc {
return sysapp.GetSyslogApp().SaveFromReq
}

View File

@@ -2,14 +2,14 @@ package api
import (
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
)
type Common struct {
}
func (i *Common) RasPublicKey(rc *ctx.ReqCtx) {
func (i *Common) RasPublicKey(rc *req.Ctx) {
publicKeyStr, err := utils.GetRsaPublicKey()
biz.ErrIsNilAppendErr(err, "rsa生成公私钥失败")
rc.ResData = publicKeyStr

View File

@@ -10,7 +10,7 @@ import (
redisapp "mayfly-go/internal/redis/application"
redisentity "mayfly-go/internal/redis/domain/entity"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
)
type Index struct {
@@ -21,7 +21,7 @@ type Index struct {
MongoApp mongoapp.Mongo
}
func (i *Index) Count(rc *ctx.ReqCtx) {
func (i *Index) Count(rc *req.Ctx) {
accountId := rc.LoginAccount.Id
tagIds := i.TagApp.ListTagIdByAccountId(accountId)

View File

@@ -2,7 +2,7 @@ package router
import (
"mayfly-go/internal/common/api"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -13,7 +13,7 @@ func InitCommonRouter(router *gin.RouterGroup) {
{
// 获取公钥
common.GET("public-key", func(g *gin.Context) {
ctx.NewReqCtxWithGin(g).
req.NewCtxWithGin(g).
WithNeedToken(false).
Handle(c.RasPublicKey)
})

View File

@@ -7,7 +7,7 @@ import (
mongoapp "mayfly-go/internal/mongo/application"
redisapp "mayfly-go/internal/redis/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -24,7 +24,7 @@ func InitIndexRouter(router *gin.RouterGroup) {
{
// 首页基本信息统计
index.GET("count", func(g *gin.Context) {
ctx.NewReqCtxWithGin(g).
req.NewCtxWithGin(g).
Handle(i.Count)
})
}

View File

@@ -10,9 +10,9 @@ import (
sysapp "mayfly-go/internal/sys/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
"mayfly-go/pkg/ws"
"strconv"
@@ -33,7 +33,7 @@ type Db struct {
const DEFAULT_ROW_SIZE = 1800
// @router /api/dbs [get]
func (d *Db) Dbs(rc *ctx.ReqCtx) {
func (d *Db) Dbs(rc *req.Ctx) {
condition := new(entity.DbQuery)
condition.TagPathLike = rc.GinCtx.Query("tagPath")
@@ -47,7 +47,7 @@ func (d *Db) Dbs(rc *ctx.ReqCtx) {
rc.ResData = d.DbApp.GetPageList(condition, ginx.GetPageParam(rc.GinCtx), new([]vo.SelectDataDbVO))
}
func (d *Db) Save(rc *ctx.ReqCtx) {
func (d *Db) Save(rc *req.Ctx) {
form := &form.DbForm{}
ginx.BindJsonAndValid(rc.GinCtx, form)
@@ -68,7 +68,7 @@ func (d *Db) Save(rc *ctx.ReqCtx) {
}
// 获取数据库实例密码,由于数据库是加密存储,故提供该接口展示原文密码
func (d *Db) GetDbPwd(rc *ctx.ReqCtx) {
func (d *Db) GetDbPwd(rc *req.Ctx) {
dbId := GetDbId(rc.GinCtx)
dbEntity := d.DbApp.GetById(dbId, "Password")
dbEntity.PwdDecrypt()
@@ -76,7 +76,7 @@ func (d *Db) GetDbPwd(rc *ctx.ReqCtx) {
}
// 获取数据库实例的所有数据库名
func (d *Db) GetDatabaseNames(rc *ctx.ReqCtx) {
func (d *Db) GetDatabaseNames(rc *req.Ctx) {
form := &form.DbForm{}
ginx.BindJsonAndValid(rc.GinCtx, form)
@@ -95,30 +95,30 @@ func (d *Db) GetDatabaseNames(rc *ctx.ReqCtx) {
rc.ResData = d.DbApp.GetDatabases(db)
}
func (d *Db) DeleteDb(rc *ctx.ReqCtx) {
func (d *Db) DeleteDb(rc *req.Ctx) {
dbId := GetDbId(rc.GinCtx)
d.DbApp.Delete(dbId)
// 删除该库的sql执行记录
d.DbSqlExecApp.DeleteBy(&entity.DbSqlExec{DbId: dbId})
}
func (d *Db) TableInfos(rc *ctx.ReqCtx) {
func (d *Db) TableInfos(rc *req.Ctx) {
rc.ResData = d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx)).GetMeta().GetTableInfos()
}
func (d *Db) TableIndex(rc *ctx.ReqCtx) {
func (d *Db) TableIndex(rc *req.Ctx) {
tn := rc.GinCtx.Query("tableName")
biz.NotEmpty(tn, "tableName不能为空")
rc.ResData = d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx)).GetMeta().GetTableIndex(tn)
}
func (d *Db) GetCreateTableDdl(rc *ctx.ReqCtx) {
func (d *Db) GetCreateTableDdl(rc *req.Ctx) {
tn := rc.GinCtx.Query("tableName")
biz.NotEmpty(tn, "tableName不能为空")
rc.ResData = d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx)).GetMeta().GetCreateTableDdl(tn)
}
func (d *Db) ExecSql(rc *ctx.ReqCtx) {
func (d *Db) ExecSql(rc *req.Ctx) {
g := rc.GinCtx
form := &form.DbSqlExecForm{}
ginx.BindJsonAndValid(g, form)
@@ -128,7 +128,7 @@ func (d *Db) ExecSql(rc *ctx.ReqCtx) {
dbInstance := d.DbApp.GetDbInstance(id, db)
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbInstance.Info.TagPath), "%s")
rc.ReqParam = fmt.Sprintf("%s -> %s", dbInstance.Info.GetLogDesc(), form.Sql)
rc.ReqParam = fmt.Sprintf("%s\n-> %s", dbInstance.Info.GetLogDesc(), form.Sql)
biz.NotEmpty(form.Sql, "sql不能为空")
// 去除前后空格及换行符
@@ -153,7 +153,9 @@ func (d *Db) ExecSql(rc *ctx.ReqCtx) {
}
execReq.Sql = s
execRes, err := d.DbSqlExecApp.Exec(execReq)
biz.ErrIsNilAppendErr(err, "执行失败: %s")
if err != nil {
biz.ErrIsNilAppendErr(err, fmt.Sprintf("[%s] -> 执行失败: ", s)+"%s")
}
if execResAll == nil {
execResAll = execRes
@@ -169,7 +171,7 @@ func (d *Db) ExecSql(rc *ctx.ReqCtx) {
}
// 执行sql文件
func (d *Db) ExecSqlFile(rc *ctx.ReqCtx) {
func (d *Db) ExecSqlFile(rc *req.Ctx) {
g := rc.GinCtx
fileheader, err := g.FormFile("file")
biz.ErrIsNilAppendErr(err, "读取sql文件失败: %s")
@@ -178,24 +180,33 @@ func (d *Db) ExecSqlFile(rc *ctx.ReqCtx) {
filename := fileheader.Filename
dbId, db := GetIdAndDb(g)
rc.ReqParam = fmt.Sprintf("dbId: %d, db: %s, filename: %s", dbId, db, filename)
dbInstance := d.DbApp.GetDbInstance(dbId, db)
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbInstance.Info.TagPath), "%s")
rc.ReqParam = fmt.Sprintf("%s -> filename: %s", dbInstance.Info.GetLogDesc(), filename)
logExecRecord := true
// 如果执行sql文件大于该值则不记录sql执行记录
if fileheader.Size > 500*1024 {
logExecRecord = false
}
go func() {
db := d.DbApp.GetDbInstance(dbId, db)
dbEntity := d.DbApp.GetById(dbId)
dbInfo := fmt.Sprintf("于%s的%s环境", dbEntity.Name, dbEntity.TagPath)
defer func() {
if err := recover(); err != nil {
switch t := err.(type) {
case *biz.BizError:
d.MsgApp.CreateAndSend(rc.LoginAccount, ws.ErrMsg("sql脚本执行失败", fmt.Sprintf("[%s]%s执行失败: [%s]", filename, dbInfo, t.Error())))
d.MsgApp.CreateAndSend(rc.LoginAccount, ws.ErrMsg("sql脚本执行失败", fmt.Sprintf("[%s]%s执行失败: [%s]", filename, dbInstance.Info.GetLogDesc(), t.Error())))
}
}
}()
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, db.Info.TagPath), "%s")
execReq := &application.DbSqlExecReq{
DbId: dbId,
Db: db,
Remark: fileheader.Filename,
DbInstance: dbInstance,
LoginAccount: rc.LoginAccount,
}
tokens := sqlparser.NewTokenizer(file)
for {
@@ -204,18 +215,25 @@ func (d *Db) ExecSqlFile(rc *ctx.ReqCtx) {
break
}
sql := sqlparser.String(stmt)
_, err = db.Exec(sql)
execReq.Sql = sql
// 需要记录执行记录
if logExecRecord {
_, err = d.DbSqlExecApp.Exec(execReq)
} else {
_, err = dbInstance.Exec(sql)
}
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] -> sql=[%s] 执行失败: [%s]", filename, dbInstance.Info.GetLogDesc(), sql, err.Error())))
return
}
}
d.MsgApp.CreateAndSend(rc.LoginAccount, ws.SuccessMsg("sql脚本执行成功", fmt.Sprintf("[%s]%s执行完成", filename, dbInfo)))
d.MsgApp.CreateAndSend(rc.LoginAccount, ws.SuccessMsg("sql脚本执行成功", fmt.Sprintf("[%s]执行完成 -> %s", filename, dbInstance.Info.GetLogDesc())))
}()
}
// 数据库dump
func (d *Db) DumpSql(rc *ctx.ReqCtx) {
func (d *Db) DumpSql(rc *req.Ctx) {
g := rc.GinCtx
dbId, db := GetIdAndDb(g)
dumpType := g.Query("type")
@@ -258,33 +276,13 @@ func (d *Db) DumpSql(rc *ctx.ReqCtx) {
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 := 0
// 查询出所有列信息总数,手动分页获取所有数据
if count64, is64 := countRes[0]["count"].(int64); is64 {
maCount = int(count64)
} else {
maCount = countRes[0]["count"].(int)
}
// 计算需要查询的页数
pageNum := maCount / DEFAULT_ROW_SIZE
if maCount%DEFAULT_ROW_SIZE > 0 {
pageNum++
}
var sqlTmp string
switch dbInstance.Info.Type {
case entity.DbTypeMysql:
sqlTmp = "SELECT * FROM %s LIMIT %d, %d"
case entity.DbTypePostgres:
sqlTmp = "SELECT * FROM %s OFFSET %d LIMIT %d"
}
for index := 0; index < pageNum; index++ {
sql := fmt.Sprintf(sqlTmp, table, index*DEFAULT_ROW_SIZE, DEFAULT_ROW_SIZE)
columns, result, _ := dbInstance.SelectData(sql)
pageNum := 1
for {
columns, result, _ := dbmeta.GetTableRecord(table, pageNum, DEFAULT_ROW_SIZE)
resultLen := len(result)
if resultLen == 0 {
break
}
insertSql := "INSERT INTO `%s` VALUES (%s);\n"
for _, res := range result {
var values []string
@@ -303,37 +301,38 @@ func (d *Db) DumpSql(rc *ctx.ReqCtx) {
}
writer.WriteString(fmt.Sprintf(insertSql, table, strings.Join(values, ", ")))
}
if resultLen < DEFAULT_ROW_SIZE {
break
}
pageNum++
}
writer.WriteString("COMMIT;\n")
}
rc.NoRes = true
rc.ReqParam = fmt.Sprintf("dbId: %d, db: %s, tables: %s, dumpType: %s", dbId, db, tablesStr, dumpType)
rc.ReqParam = fmt.Sprintf("%s, tables: %s, dumpType: %s", dbInstance.Info.GetLogDesc(), tablesStr, dumpType)
}
// @router /api/db/:dbId/t-metadata [get]
func (d *Db) TableMA(rc *ctx.ReqCtx) {
func (d *Db) TableMA(rc *req.Ctx) {
dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbi.Info.TagPath), "%s")
rc.ResData = dbi.GetMeta().GetTables()
}
// @router /api/db/:dbId/c-metadata [get]
func (d *Db) ColumnMA(rc *ctx.ReqCtx) {
func (d *Db) ColumnMA(rc *req.Ctx) {
g := rc.GinCtx
tn := g.Query("tableName")
biz.NotEmpty(tn, "tableName不能为空")
dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
// biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbInstance.TagPath), "%s")
rc.ResData = dbi.GetMeta().GetColumns(tn)
}
// @router /api/db/:dbId/hint-tables [get]
func (d *Db) HintTables(rc *ctx.ReqCtx) {
func (d *Db) HintTables(rc *req.Ctx) {
dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
// biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbInstance.TagPath), "%s")
dm := dbi.GetMeta()
// 获取所有表
@@ -372,7 +371,7 @@ func (d *Db) HintTables(rc *ctx.ReqCtx) {
}
// @router /api/db/:dbId/sql [post]
func (d *Db) SaveSql(rc *ctx.ReqCtx) {
func (d *Db) SaveSql(rc *req.Ctx) {
g := rc.GinCtx
account := rc.LoginAccount
dbSqlForm := &form.DbSqlSaveForm{}
@@ -400,7 +399,7 @@ func (d *Db) SaveSql(rc *ctx.ReqCtx) {
}
// 获取所有保存的sql names
func (d *Db) GetSqlNames(rc *ctx.ReqCtx) {
func (d *Db) GetSqlNames(rc *req.Ctx) {
id, db := GetIdAndDb(rc.GinCtx)
// 获取用于是否有该dbsql的保存记录有则更改否则新增
dbSql := &entity.DbSql{Type: 1, DbId: id, Db: db}
@@ -412,17 +411,18 @@ func (d *Db) GetSqlNames(rc *ctx.ReqCtx) {
}
// 删除保存的sql
func (d *Db) DeleteSql(rc *ctx.ReqCtx) {
func (d *Db) DeleteSql(rc *req.Ctx) {
dbSql := &entity.DbSql{Type: 1, DbId: GetDbId(rc.GinCtx)}
dbSql.CreatorId = rc.LoginAccount.Id
dbSql.Name = rc.GinCtx.Query("name")
dbSql.Db = rc.GinCtx.Query("db")
model.DeleteByCondition(dbSql)
}
// @router /api/db/:dbId/sql [get]
func (d *Db) GetSql(rc *ctx.ReqCtx) {
func (d *Db) GetSql(rc *req.Ctx) {
id, db := GetIdAndDb(rc.GinCtx)
// 根据创建者id 数据库id以及sql模板名称查询保存的sql信息
dbSql := &entity.DbSql{Type: 1, DbId: id, Db: db}

View File

@@ -3,15 +3,15 @@ package api
import (
"mayfly-go/internal/db/application"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
)
type DbSqlExec struct {
DbSqlExecApp application.DbSqlExec
}
func (d *DbSqlExec) DbSqlExecs(rc *ctx.ReqCtx) {
func (d *DbSqlExec) DbSqlExecs(rc *req.Ctx) {
g := rc.GinCtx
m := &entity.DbSqlExec{DbId: uint64(ginx.QueryInt(g, "dbId", 0)),
Db: g.Query("db"),

View File

@@ -19,7 +19,7 @@ type DbForm struct {
}
type DbSqlSaveForm struct {
Name string
Name string `binding:"required"`
Sql string `binding:"required"`
Type int `binding:"required"`
Db string `binding:"required"`

View File

@@ -1,6 +1,8 @@
package application
import "mayfly-go/internal/db/infrastructure/persistence"
import (
"mayfly-go/internal/db/infrastructure/persistence"
)
var (
dbApp Db = newDbApp(persistence.GetDbRepo(), persistence.GetDbSqlRepo())

View File

@@ -1,28 +1,22 @@
package application
import (
"context"
"database/sql"
"fmt"
"mayfly-go/internal/constant"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
machineapp "mayfly-go/internal/machine/application"
"mayfly-go/internal/machine/infrastructure/machine"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/global"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils"
"net"
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/go-sql-driver/mysql"
"github.com/lib/pq"
)
type Db interface {
@@ -165,7 +159,7 @@ func (d *dbAppImpl) GetDatabases(ed *entity.Db) []string {
biz.ErrIsNilAppendErr(err, "数据库连接失败: %s")
defer dbConn.Close()
_, res, err := SelectDataByDb(dbConn, getDatabasesSql, true)
_, res, err := SelectDataByDb(dbConn, getDatabasesSql)
biz.ErrIsNilAppendErr(err, "获取数据库列表失败")
for _, re := range res {
databases = append(databases, re["dbname"].(string))
@@ -190,10 +184,10 @@ func (da *dbAppImpl) GetDbInstance(id uint64, db string) *DbInstance {
defer mutex.Unlock()
d := da.GetById(id)
// 密码解密
d.PwdDecrypt()
biz.NotNil(d, "数据库信息不存在")
biz.IsTrue(strings.Contains(d.Database, db), "未配置该库的操作权限")
// 密码解密
d.PwdDecrypt()
dbInfo := new(DbInfo)
utils.Copy(dbInfo, d)
@@ -254,7 +248,7 @@ type DbInstance struct {
// 执行查询语句
// 依次返回 列名数组结果map错误
func (d *DbInstance) SelectData(execSql string) ([]string, []map[string]interface{}, error) {
return SelectDataByDb(d.db, execSql, false)
return SelectDataByDb(d.db, execSql)
}
// 将查询结果映射至struct可具体参考sqlx库
@@ -265,7 +259,7 @@ func (d *DbInstance) SelectData2Struct(execSql string, dest interface{}) error {
// 执行内部查询语句,不返回列名以及不限制行数
// 依次返回 结果map错误
func (d *DbInstance) innerSelect(execSql string) ([]map[string]interface{}, error) {
_, res, err := SelectDataByDb(d.db, execSql, true)
_, res, err := SelectDataByDb(d.db, execSql)
return res, err
}
@@ -347,22 +341,14 @@ func TestConnection(d *entity.Db) {
// 获取数据库连接
func GetDbConn(d *entity.Db, db string) (*sql.DB, error) {
// SSH Conect
if d.EnableSshTunnel == 1 && d.SshTunnelMachineId != 0 {
sshTunnelMachine := machineapp.GetMachineApp().GetSshTunnelMachine(d.SshTunnelMachineId)
if d.Type == entity.DbTypeMysql {
mysql.RegisterDialContext(d.Network, func(ctx context.Context, addr string) (net.Conn, error) {
return sshTunnelMachine.GetDialConn("tcp", addr)
})
} else if d.Type == entity.DbTypePostgres {
_, err := pq.DialOpen(&PqSqlDialer{sshTunnelMachine: sshTunnelMachine}, getDsn(d, db))
if err != nil {
panic(biz.NewBizErr(fmt.Sprintf("postgres隧道连接失败: %s", err.Error())))
}
}
var DB *sql.DB
var err error
if d.Type == entity.DbTypeMysql {
DB, err = getMysqlDB(d, db)
} else if d.Type == entity.DbTypePostgres {
DB, err = getPgsqlDB(d, db)
}
DB, err := sql.Open(d.Type, getDsn(d, db))
if err != nil {
return nil, err
}
@@ -375,29 +361,7 @@ func GetDbConn(d *entity.Db, db string) (*sql.DB, error) {
return DB, nil
}
// 获取dataSourceName
func getDsn(d *entity.Db, db string) string {
var dsn string
if d.Type == entity.DbTypeMysql {
// 更多参数参考https://github.com/go-sql-driver/mysql#dsn-data-source-name
dsn = fmt.Sprintf("%s:%s@%s(%s:%d)/%s?timeout=8s", d.Username, d.Password, d.Network, d.Host, d.Port, db)
if d.Params != "" {
dsn = fmt.Sprintf("%s&%s", dsn, d.Params)
}
return dsn
}
if d.Type == entity.DbTypePostgres {
dsn = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", d.Host, d.Port, d.Username, d.Password, db)
if d.Params != "" {
dsn = fmt.Sprintf("%s %s", dsn, strings.Join(strings.Split(d.Params, "&"), " "))
}
return dsn
}
return ""
}
func SelectDataByDb(db *sql.DB, selectSql string, isInner bool) ([]string, []map[string]interface{}, error) {
func SelectDataByDb(db *sql.DB, selectSql string) ([]string, []map[string]interface{}, error) {
rows, err := db.Query(selectSql)
if err != nil {
return nil, nil, err
@@ -424,14 +388,7 @@ func SelectDataByDb(db *sql.DB, selectSql string, isInner bool) ([]string, []map
colNames := make([]string, 0)
// 是否第一次遍历,列名数组只需第一次遍历时加入
isFirst := true
rowNum := 0
for rows.Next() {
rowNum++
// 非内部sql则校验返回结果数量
if !isInner {
biz.IsTrue(rowNum <= Max_Rows, "结果集 > 2000, 请完善条件或分页信息")
}
// 不Scan也会导致等待该链接实际处于未工作的状态然后也会导致连接数迅速达到最大
err := rows.Scan(scans...)
if err != nil {
@@ -497,7 +454,7 @@ func valueConvert(data []byte, colType *sql.ColumnType) interface{} {
return intV
}
}
if strings.Contains(colScanType, "float") {
if strings.Contains(colScanType, "float") || strings.Contains(colDatabaseTypeName, "decimal") {
floatV, _ := strconv.ParseFloat(stringV, 64)
return floatV
}
@@ -525,20 +482,3 @@ func Select2StructByDb(db *sql.DB, selectSql string, dest interface{}) error {
func CloseDb(dbId uint64, db string) {
dbCache.Delete(GetDbCacheKey(dbId, db))
}
type PqSqlDialer struct {
sshTunnelMachine *machine.SshTunnelMachine
}
func (pd *PqSqlDialer) Dial(network, address string) (net.Conn, error) {
if sshConn, err := pd.sshTunnelMachine.GetDialConn("tcp", address); err == nil {
// 将ssh conn包装否则redis内部设置超时会报错,ssh conn不支持设置超时会返回错误: ssh: tcpChan: deadline not supported
return &utils.WrapSshConn{Conn: sshConn}, nil
} else {
return nil, err
}
}
func (pd *PqSqlDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
return pd.Dial(network, address)
}

View File

@@ -5,7 +5,11 @@ import (
"fmt"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
sysapp "mayfly-go/internal/sys/application"
sysentity "mayfly-go/internal/sys/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
"strconv"
"strings"
"github.com/xwb1989/sqlparser"
@@ -73,32 +77,42 @@ func createSqlExecRecord(execSqlReq *DbSqlExecReq) *entity.DbSqlExec {
func (d *dbSqlExecAppImpl) Exec(execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error) {
sql := execSqlReq.Sql
stmt, err := sqlparser.Parse(sql)
if err != nil {
// 就算解析失败也执行sql让数据库来判断错误
//global.Log.Error("sql解析失败: ", err)
if strings.HasPrefix(strings.ToLower(execSqlReq.Sql), "select") ||
strings.HasPrefix(strings.ToLower(execSqlReq.Sql), "show") {
return doSelect(execSqlReq)
}
// 保存执行记录
d.dbSqlExecRepo.Insert(createSqlExecRecord(execSqlReq))
return doExec(execSqlReq.Sql, execSqlReq.DbInstance)
}
dbSqlExecRecord := createSqlExecRecord(execSqlReq)
var execRes *DbSqlExecRes
isSelect := false
stmt, err := sqlparser.Parse(sql)
if err != nil {
// 就算解析失败也执行sql让数据库来判断错误。如果是查询sql则简单判断是否有limit分页参数信息兼容pgsql
// global.Log.Warnf("sqlparse解析sql[%s]失败: %s", sql, err.Error())
lowerSql := strings.ToLower(execSqlReq.Sql)
isSelect := strings.HasPrefix(lowerSql, "select")
if isSelect {
biz.IsTrue(strings.Contains(lowerSql, "limit"), "请完善分页信息")
}
var execErr error
if isSelect || strings.HasPrefix(lowerSql, "show") {
execRes, execErr = doRead(execSqlReq)
} else {
execRes, execErr = doExec(execSqlReq.Sql, execSqlReq.DbInstance)
}
if execErr != nil {
return nil, execErr
}
d.saveSqlExecLog(isSelect, dbSqlExecRecord)
return execRes, nil
}
switch stmt := stmt.(type) {
case *sqlparser.Select:
isSelect = true
execRes, err = doSelect(execSqlReq)
execRes, err = doSelect(stmt, execSqlReq)
case *sqlparser.Show:
isSelect = true
execRes, err = doSelect(execSqlReq)
execRes, err = doRead(execSqlReq)
case *sqlparser.OtherRead:
isSelect = true
execRes, err = doSelect(execSqlReq)
execRes, err = doRead(execSqlReq)
case *sqlparser.Update:
execRes, err = doUpdate(stmt, execSqlReq, dbSqlExecRecord)
case *sqlparser.Delete:
@@ -111,12 +125,22 @@ func (d *dbSqlExecAppImpl) Exec(execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error)
if err != nil {
return nil, err
}
d.saveSqlExecLog(isSelect, dbSqlExecRecord)
return execRes, nil
}
if !isSelect {
// 保存执行记录
// 保存sql执行记录如果是查询类则根据系统配置判断是否保存
func (d *dbSqlExecAppImpl) saveSqlExecLog(isQuery bool, dbSqlExecRecord *entity.DbSqlExec) {
if !isQuery {
d.dbSqlExecRepo.Insert(dbSqlExecRecord)
return
}
if sysapp.GetConfigApp().GetConfig(sysentity.ConfigKeyDbSaveQuerySQL).BoolValue(false) {
dbSqlExecRecord.Table = "-"
dbSqlExecRecord.OldValue = "-"
dbSqlExecRecord.Type = entity.DbSqlExecTypeQuery
d.dbSqlExecRepo.Insert(dbSqlExecRecord)
}
return execRes, nil
}
func (d *dbSqlExecAppImpl) DeleteBy(condition *entity.DbSqlExec) {
@@ -127,7 +151,22 @@ func (d *dbSqlExecAppImpl) GetPageList(condition *entity.DbSqlExec, pageParam *m
return d.dbSqlExecRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
func doSelect(execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error) {
func doSelect(selectStmt *sqlparser.Select, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error) {
selectExprsStr := sqlparser.String(selectStmt.SelectExprs)
if selectExprsStr == "*" || strings.Contains(selectExprsStr, ".*") ||
len(strings.Split(selectExprsStr, ",")) > 1 {
limit := selectStmt.Limit
biz.NotNil(limit, "请完善分页信息后执行")
count, err := strconv.Atoi(sqlparser.String(limit.Rowcount))
biz.ErrIsNil(err, "分页参数有误")
maxCount := sysapp.GetConfigApp().GetConfig(sysentity.ConfigKeyDbQueryMaxCount).IntValue(200)
biz.IsTrue(count <= maxCount, fmt.Sprintf("查询结果集数需小于系统配置的%d条", maxCount))
}
return doRead(execSqlReq)
}
func doRead(execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error) {
dbInstance := execSqlReq.DbInstance
sql := execSqlReq.Sql
colNames, res, err := dbInstance.SelectData(sql)
@@ -220,5 +259,5 @@ func doExec(sql string, dbInstance *DbInstance) (*DbSqlExecRes, error) {
return &DbSqlExecRes{
ColNames: []string{"sql", "rowsAffected", "result"},
Res: res,
}, nil
}, err
}

View File

@@ -1,7 +1,7 @@
package application
// -----------------------------------元数据接口定义------------------------------------------
// 数据库元信息接口(表、列等元信息)
// 数据库元信息接口(表、列、获取表数据等元信息)
// 所有数据查出来直接用map接收注意不同数据库实现该接口返回的map中的key需要统一.
// 即: 使用别名统一即可。如table_name AS tableName
type DbMetadata interface {
@@ -25,4 +25,8 @@ type DbMetadata interface {
// 获取建表ddl
GetCreateTableDdl(tableName string) []map[string]interface{}
// 获取指定表的数据-分页查询
// @return columns: 列字段名result: 结果集error: 错误
GetTableRecord(tableName string, pageNum, pageSize int) ([]string, []map[string]interface{}, error)
}

View File

@@ -1,10 +1,33 @@
package application
import (
"context"
"database/sql"
"fmt"
"mayfly-go/internal/db/domain/entity"
machineapp "mayfly-go/internal/machine/application"
"mayfly-go/pkg/biz"
"net"
"github.com/go-sql-driver/mysql"
)
func getMysqlDB(d *entity.Db, db string) (*sql.DB, error) {
// SSH Conect
if d.EnableSshTunnel == 1 && d.SshTunnelMachineId != 0 {
sshTunnelMachine := machineapp.GetMachineApp().GetSshTunnelMachine(d.SshTunnelMachineId)
mysql.RegisterDialContext(d.Network, func(ctx context.Context, addr string) (net.Conn, error) {
return sshTunnelMachine.GetDialConn("tcp", addr)
})
}
// 设置dataSourceName -> 更多参数参考https://github.com/go-sql-driver/mysql#dsn-data-source-name
dsn := fmt.Sprintf("%s:%s@%s(%s:%d)/%s?timeout=8s", d.Username, d.Password, d.Network, d.Host, d.Port, db)
if d.Params != "" {
dsn = fmt.Sprintf("%s&%s", dsn, d.Params)
}
return sql.Open(d.Type, dsn)
}
// ---------------------------------- mysql元数据 -----------------------------------
const (
// mysql 表信息元数据
@@ -103,3 +126,7 @@ func (mm *MysqlMetadata) GetCreateTableDdl(tableName string) []map[string]interf
res, _ := mm.di.innerSelect(fmt.Sprintf("show create table %s ", tableName))
return res
}
func (mm *MysqlMetadata) GetTableRecord(tableName string, pageNum, pageSize int) ([]string, []map[string]interface{}, error) {
return mm.di.SelectData(fmt.Sprintf("SELECT * FROM %s LIMIT %d, %d", tableName, (pageNum-1)*pageSize, pageSize))
}

View File

@@ -1,10 +1,60 @@
package application
import (
"database/sql"
"database/sql/driver"
"fmt"
"mayfly-go/internal/db/domain/entity"
machineapp "mayfly-go/internal/machine/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/utils"
"net"
"strings"
"time"
"github.com/lib/pq"
)
func getPgsqlDB(d *entity.Db, db string) (*sql.DB, error) {
driverName := d.Type
// SSH Conect
if d.EnableSshTunnel == 1 && d.SshTunnelMachineId != 0 {
// 如果使用了隧道,则使用`postgres:ssh:隧道机器id`注册名
driverName = fmt.Sprintf("postgres:ssh:%d", d.SshTunnelMachineId)
if !utils.ArrContains(sql.Drivers(), driverName) {
sql.Register(driverName, &PqSqlDialer{sshTunnelMachineId: d.SshTunnelMachineId})
}
sql.Drivers()
}
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", d.Host, d.Port, d.Username, d.Password, db)
if d.Params != "" {
dsn = fmt.Sprintf("%s %s", dsn, strings.Join(strings.Split(d.Params, "&"), " "))
}
return sql.Open(driverName, dsn)
}
// pgsql dialer
type PqSqlDialer struct {
sshTunnelMachineId uint64
}
func (d *PqSqlDialer) Open(name string) (driver.Conn, error) {
return pq.DialOpen(d, name)
}
func (pd *PqSqlDialer) Dial(network, address string) (net.Conn, error) {
if sshConn, err := machineapp.GetMachineApp().GetSshTunnelMachine(pd.sshTunnelMachineId).GetDialConn("tcp", address); err == nil {
return sshConn, nil
} else {
return nil, err
}
}
func (pd *PqSqlDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
return pd.Dial(network, address)
}
// ---------------------------------- pgsql元数据 -----------------------------------
const (
// postgres 表信息元数据
@@ -22,7 +72,7 @@ const (
A.attname AS "columnName",
tc.is_nullable AS "nullable",
concat_ws ( '', t.typname, SUBSTRING ( format_type ( a.atttypid, a.atttypmod ) FROM '\(.*\)' ) ) AS "columnType",
(CASE WHEN ( SELECT COUNT(*) FROM pg_constraint WHERE conrelid = a.attrelid AND conkey[1]= attnum AND contype = 'p' ) > 0 THEN 'PRI' ELSE '' END ) AS columnKey,
(CASE WHEN ( SELECT COUNT(*) FROM pg_constraint WHERE conrelid = a.attrelid AND conkey[1]= attnum AND contype = 'p' ) > 0 THEN 'PRI' ELSE '' END ) AS "columnKey",
d.description AS "columnComment"
FROM
pg_attribute a LEFT JOIN pg_description d ON d.objoid = a.attrelid
@@ -95,7 +145,11 @@ func (pm *PgsqlMetadata) GetTableIndex(tableName string) []map[string]interface{
}
// 获取建表ddl
func (mm *PgsqlMetadata) GetCreateTableDdl(tableName string) []map[string]interface{} {
func (pm *PgsqlMetadata) GetCreateTableDdl(tableName string) []map[string]interface{} {
biz.IsTrue(tableName == "", "暂不支持获取pgsql建表DDL")
return nil
}
func (pm *PgsqlMetadata) GetTableRecord(tableName string, pageNum, pageSize int) ([]string, []map[string]interface{}, error) {
return pm.di.SelectData(fmt.Sprintf("SELECT * FROM %s OFFSET %d LIMIT %d", tableName, (pageNum-1)*pageSize, pageSize))
}

View File

@@ -19,4 +19,5 @@ const (
DbSqlExecTypeUpdate int8 = 1 // 更新类型
DbSqlExecTypeDelete int8 = 2 // 删除类型
DbSqlExecTypeInsert int8 = 3 // 插入类型
DbSqlExecTypeQuery int8 = 4 // 查询类型如select、show等
)

View File

@@ -5,7 +5,7 @@ import (
"mayfly-go/internal/db/application"
sysapp "mayfly-go/internal/sys/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -21,94 +21,94 @@ func InitDbRouter(router *gin.RouterGroup) {
}
// 获取所有数据库列表
db.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.Dbs)
req.NewCtxWithGin(c).Handle(d.Dbs)
})
saveDb := ctx.NewLogInfo("db-保存数据库信息").WithSave(true)
saveDb := req.NewLogInfo("db-保存数据库信息").WithSave(true)
db.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(saveDb).
Handle(d.Save)
})
// 获取数据库实例的所有数据库名
db.POST("databases", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
Handle(d.GetDatabaseNames)
})
db.GET(":dbId/pwd", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.GetDbPwd)
req.NewCtxWithGin(c).Handle(d.GetDbPwd)
})
deleteDb := ctx.NewLogInfo("db-删除数据库信息").WithSave(true)
deleteDb := req.NewLogInfo("db-删除数据库信息").WithSave(true)
db.DELETE(":dbId", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(deleteDb).
Handle(d.DeleteDb)
})
db.GET(":dbId/t-infos", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.TableInfos)
req.NewCtxWithGin(c).Handle(d.TableInfos)
})
db.GET(":dbId/t-index", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.TableIndex)
req.NewCtxWithGin(c).Handle(d.TableIndex)
})
db.GET(":dbId/t-create-ddl", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.GetCreateTableDdl)
req.NewCtxWithGin(c).Handle(d.GetCreateTableDdl)
})
execSqlLog := ctx.NewLogInfo("db-执行Sql")
execSqlLog := req.NewLogInfo("db-执行Sql")
db.POST(":dbId/exec-sql", func(g *gin.Context) {
rc := ctx.NewReqCtxWithGin(g).WithLog(execSqlLog)
rc := req.NewCtxWithGin(g).WithLog(execSqlLog)
rc.Handle(d.ExecSql)
})
execSqlFileLog := ctx.NewLogInfo("db-执行Sql文件").WithSave(true)
execSqlFileLog := req.NewLogInfo("db-执行Sql文件").WithSave(true)
db.POST(":dbId/exec-sql-file", func(g *gin.Context) {
ctx.NewReqCtxWithGin(g).
req.NewCtxWithGin(g).
WithLog(execSqlFileLog).
Handle(d.ExecSqlFile)
})
dumpLog := ctx.NewLogInfo("db-导出sql文件").WithSave(true)
dumpLog := req.NewLogInfo("db-导出sql文件").WithSave(true)
db.GET(":dbId/dump", func(g *gin.Context) {
ctx.NewReqCtxWithGin(g).
req.NewCtxWithGin(g).
WithLog(dumpLog).
Handle(d.DumpSql)
})
db.GET(":dbId/t-metadata", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.TableMA)
req.NewCtxWithGin(c).Handle(d.TableMA)
})
db.GET(":dbId/c-metadata", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.ColumnMA)
req.NewCtxWithGin(c).Handle(d.ColumnMA)
})
db.GET(":dbId/hint-tables", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.HintTables)
req.NewCtxWithGin(c).Handle(d.HintTables)
})
/** db sql相关接口 */
db.POST(":dbId/sql", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c)
rc := req.NewCtxWithGin(c)
rc.Handle(d.SaveSql)
})
db.GET(":dbId/sql", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.GetSql)
req.NewCtxWithGin(c).Handle(d.GetSql)
})
db.DELETE(":dbId/sql", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.DeleteSql)
req.NewCtxWithGin(c).Handle(d.DeleteSql)
})
db.GET(":dbId/sql-names", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.GetSqlNames)
req.NewCtxWithGin(c).Handle(d.GetSqlNames)
})
}
}

View File

@@ -3,7 +3,7 @@ package router
import (
"mayfly-go/internal/db/api"
"mayfly-go/internal/db/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -16,7 +16,7 @@ func InitDbSqlExecRouter(router *gin.RouterGroup) {
}
// 获取所有数据库sql执行记录列表
db.GET("", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c)
rc := req.NewCtxWithGin(c)
rc.Handle(d.DbSqlExecs)
})
}

View File

@@ -11,9 +11,9 @@ import (
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/config"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
"mayfly-go/pkg/ws"
"os"
@@ -31,7 +31,7 @@ type Machine struct {
TagApp tagapp.TagTree
}
func (m *Machine) Machines(rc *ctx.ReqCtx) {
func (m *Machine) Machines(rc *req.Ctx) {
condition := new(entity.MachineQuery)
condition.Ip = rc.GinCtx.Query("ip")
condition.Name = rc.GinCtx.Query("name")
@@ -58,12 +58,12 @@ func (m *Machine) Machines(rc *ctx.ReqCtx) {
rc.ResData = res
}
func (m *Machine) MachineStats(rc *ctx.ReqCtx) {
func (m *Machine) MachineStats(rc *req.Ctx) {
stats := m.MachineApp.GetCli(GetMachineId(rc.GinCtx)).GetAllStats()
rc.ResData = stats
}
func (m *Machine) SaveMachine(rc *ctx.ReqCtx) {
func (m *Machine) SaveMachine(rc *req.Ctx) {
g := rc.GinCtx
machineForm := new(form.MachineForm)
ginx.BindJsonAndValid(g, machineForm)
@@ -87,14 +87,14 @@ func (m *Machine) SaveMachine(rc *ctx.ReqCtx) {
}
// 获取机器实例密码,由于数据库是加密存储,故提供该接口展示原文密码
func (m *Machine) GetMachinePwd(rc *ctx.ReqCtx) {
func (m *Machine) GetMachinePwd(rc *req.Ctx) {
mid := GetMachineId(rc.GinCtx)
me := m.MachineApp.GetById(mid, "Password")
me.PwdDecrypt()
rc.ResData = me.Password
}
func (m *Machine) ChangeStatus(rc *ctx.ReqCtx) {
func (m *Machine) ChangeStatus(rc *req.Ctx) {
g := rc.GinCtx
id := uint64(ginx.PathParamInt(g, "machineId"))
status := int8(ginx.PathParamInt(g, "status"))
@@ -102,19 +102,19 @@ func (m *Machine) ChangeStatus(rc *ctx.ReqCtx) {
m.MachineApp.ChangeStatus(id, status)
}
func (m *Machine) DeleteMachine(rc *ctx.ReqCtx) {
func (m *Machine) DeleteMachine(rc *req.Ctx) {
id := uint64(ginx.PathParamInt(rc.GinCtx, "machineId"))
rc.ReqParam = id
m.MachineApp.Delete(id)
}
// 关闭机器客户端
func (m *Machine) CloseCli(rc *ctx.ReqCtx) {
func (m *Machine) CloseCli(rc *req.Ctx) {
machine.DeleteCli(GetMachineId(rc.GinCtx))
}
// 获取进程列表信息
func (m *Machine) GetProcess(rc *ctx.ReqCtx) {
func (m *Machine) GetProcess(rc *req.Ctx) {
g := rc.GinCtx
cmd := "ps -aux "
sortType := g.Query("sortType")
@@ -145,7 +145,7 @@ func (m *Machine) GetProcess(rc *ctx.ReqCtx) {
}
// 终止进程
func (m *Machine) KillProcess(rc *ctx.ReqCtx) {
func (m *Machine) KillProcess(rc *req.Ctx) {
pid := rc.GinCtx.Query("pid")
biz.NotEmpty(pid, "进程id不能为空")
@@ -169,8 +169,8 @@ func (m *Machine) WsSSH(g *gin.Context) {
biz.ErrIsNilAppendErr(err, "升级websocket失败: %s")
// 权限校验
rc := ctx.NewReqCtxWithGin(g).WithRequiredPermission(ctx.NewPermission("machine:terminal"))
if err = ctx.PermissionHandler(rc); err != nil {
rc := req.NewCtxWithGin(g).WithRequiredPermission(req.NewPermission("machine:terminal"))
if err = req.PermissionHandler(rc); err != nil {
panic(biz.NewBizErr("\033[1;31m您没有权限操作该机器终端,请重新登录后再试~\033[0m"))
}
@@ -197,16 +197,16 @@ func (m *Machine) WsSSH(g *gin.Context) {
biz.ErrIsNilAppendErr(err, "\033[1;31m连接失败: %s\033[0m")
// 记录系统操作日志
rc.WithLog(ctx.NewLogInfo("机器-终端操作").WithSave(true))
rc.WithLog(req.NewLogInfo("机器-终端操作").WithSave(true))
rc.ReqParam = cli.GetMachine().GetLogDesc()
ctx.LogHandler(rc)
req.LogHandler(rc)
mts.Start()
defer mts.Stop()
}
// 获取机器终端回放记录的相应文件夹名或文件内容
func (m *Machine) MachineRecDirNames(rc *ctx.ReqCtx) {
func (m *Machine) MachineRecDirNames(rc *req.Ctx) {
readPath := rc.GinCtx.Query("path")
biz.NotEmpty(readPath, "path不能为空")
path_ := path.Join(config.Conf.Server.GetMachineRecPath(), readPath)

View File

@@ -10,8 +10,8 @@ import (
"mayfly-go/internal/machine/domain/entity"
sysApplication "mayfly-go/internal/sys/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
"mayfly-go/pkg/ws"
"sort"
@@ -33,13 +33,13 @@ const (
max_read_size = 1 * 1024 * 1024
)
func (m *MachineFile) MachineFiles(rc *ctx.ReqCtx) {
func (m *MachineFile) MachineFiles(rc *req.Ctx) {
g := rc.GinCtx
condition := &entity.MachineFile{MachineId: GetMachineId(g)}
rc.ResData = m.MachineFileApp.GetPageList(condition, ginx.GetPageParam(g), new([]vo.MachineFileVO))
}
func (m *MachineFile) SaveMachineFiles(rc *ctx.ReqCtx) {
func (m *MachineFile) SaveMachineFiles(rc *req.Ctx) {
g := rc.GinCtx
fileForm := new(form.MachineFileForm)
ginx.BindJsonAndValid(g, fileForm)
@@ -52,7 +52,7 @@ func (m *MachineFile) SaveMachineFiles(rc *ctx.ReqCtx) {
m.MachineFileApp.Save(entity)
}
func (m *MachineFile) DeleteFile(rc *ctx.ReqCtx) {
func (m *MachineFile) DeleteFile(rc *req.Ctx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
m.MachineFileApp.Delete(fid)
@@ -60,7 +60,7 @@ func (m *MachineFile) DeleteFile(rc *ctx.ReqCtx) {
/*** sftp相关操作 */
func (m *MachineFile) CreateFile(rc *ctx.ReqCtx) {
func (m *MachineFile) CreateFile(rc *req.Ctx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
@@ -79,7 +79,7 @@ func (m *MachineFile) CreateFile(rc *ctx.ReqCtx) {
}
func (m *MachineFile) ReadFileContent(rc *ctx.ReqCtx) {
func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
readPath := g.Query("path")
@@ -109,7 +109,7 @@ func (m *MachineFile) ReadFileContent(rc *ctx.ReqCtx) {
}
}
func (m *MachineFile) GetDirEntry(rc *ctx.ReqCtx) {
func (m *MachineFile) GetDirEntry(rc *req.Ctx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
readPath := g.Query("path")
@@ -134,7 +134,7 @@ func (m *MachineFile) GetDirEntry(rc *ctx.ReqCtx) {
rc.ReqParam = fmt.Sprintf("path: %s", readPath)
}
func (m *MachineFile) WriteFileContent(rc *ctx.ReqCtx) {
func (m *MachineFile) WriteFileContent(rc *req.Ctx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
@@ -148,7 +148,7 @@ func (m *MachineFile) WriteFileContent(rc *ctx.ReqCtx) {
rc.ReqParam = fmt.Sprintf("%s -> 修改文件内容: %s", mi.GetLogDesc(), path)
}
func (m *MachineFile) UploadFile(rc *ctx.ReqCtx) {
func (m *MachineFile) UploadFile(rc *req.Ctx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
path := g.PostForm("path")
@@ -179,7 +179,7 @@ func (m *MachineFile) UploadFile(rc *ctx.ReqCtx) {
}()
}
func (m *MachineFile) RemoveFile(rc *ctx.ReqCtx) {
func (m *MachineFile) RemoveFile(rc *req.Ctx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
path := g.Query("path")

View File

@@ -8,8 +8,8 @@ import (
"mayfly-go/internal/machine/domain/entity"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
"strconv"
@@ -22,13 +22,13 @@ type MachineScript struct {
TagApp tagapp.TagTree
}
func (m *MachineScript) MachineScripts(rc *ctx.ReqCtx) {
func (m *MachineScript) MachineScripts(rc *req.Ctx) {
g := rc.GinCtx
condition := &entity.MachineScript{MachineId: GetMachineId(g)}
rc.ResData = m.MachineScriptApp.GetPageList(condition, ginx.GetPageParam(g), new([]vo.MachineScriptVO))
}
func (m *MachineScript) SaveMachineScript(rc *ctx.ReqCtx) {
func (m *MachineScript) SaveMachineScript(rc *req.Ctx) {
form := new(form.MachineScriptForm)
ginx.BindJsonAndValid(rc.GinCtx, form)
rc.ReqParam = form
@@ -41,7 +41,7 @@ func (m *MachineScript) SaveMachineScript(rc *ctx.ReqCtx) {
m.MachineScriptApp.Save(machineScript)
}
func (m *MachineScript) DeleteMachineScript(rc *ctx.ReqCtx) {
func (m *MachineScript) DeleteMachineScript(rc *req.Ctx) {
msa := m.MachineScriptApp
sid := GetMachineScriptId(rc.GinCtx)
ms := msa.GetById(sid)
@@ -50,7 +50,7 @@ func (m *MachineScript) DeleteMachineScript(rc *ctx.ReqCtx) {
msa.Delete(sid)
}
func (m *MachineScript) RunMachineScript(rc *ctx.ReqCtx) {
func (m *MachineScript) RunMachineScript(rc *req.Ctx) {
g := rc.GinCtx
scriptId := GetMachineScriptId(g)

View File

@@ -92,19 +92,18 @@ func (c *Cli) GetSession() (*ssh.Session, error) {
// 执行shell
// @param shell shell脚本命令
func (c *Cli) Run(shell string) (*string, error) {
func (c *Cli) Run(shell string) (string, error) {
session, err := c.GetSession()
if err != nil {
c.Close()
return nil, err
return "", err
}
defer session.Close()
buf, rerr := session.CombinedOutput(shell)
if rerr != nil {
return nil, rerr
buf, err := session.CombinedOutput(shell)
if err != nil {
return "", err
}
res := string(buf)
return &res, nil
return string(buf), nil
}
func (c *Cli) GetMachine() *entity.Machine {

View File

@@ -73,7 +73,7 @@ top -b -n 1 | grep Cpu
func (c *Cli) GetAllStats() *Stats {
res, _ := c.Run(StatsShell)
infos := strings.Split(*res, "-----")
infos := strings.Split(res, "-----")
stats := new(Stats)
getUptime(infos[0], stats)
getHostname(infos[1], stats)

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/internal/machine/api"
"mayfly-go/internal/machine/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -18,64 +18,64 @@ func InitMachineRouter(router *gin.RouterGroup) {
machines := router.Group("machines")
{
machines.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.Machines)
req.NewCtxWithGin(c).Handle(m.Machines)
})
machines.GET(":machineId/pwd", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetMachinePwd)
req.NewCtxWithGin(c).Handle(m.GetMachinePwd)
})
machines.GET(":machineId/stats", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.MachineStats)
req.NewCtxWithGin(c).Handle(m.MachineStats)
})
machines.GET(":machineId/process", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetProcess)
req.NewCtxWithGin(c).Handle(m.GetProcess)
})
// 终止进程
killProcessL := ctx.NewLogInfo("终止进程").WithSave(true)
killProcessP := ctx.NewPermission("machine:killprocess")
killProcessL := req.NewLogInfo("终止进程").WithSave(true)
killProcessP := req.NewPermission("machine:killprocess")
machines.DELETE(":machineId/process", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(killProcessL).
WithRequiredPermission(killProcessP).
Handle(m.KillProcess)
})
saveMachine := ctx.NewLogInfo("保存机器信息").WithSave(true)
saveMachineP := ctx.NewPermission("machine:update")
saveMachine := req.NewLogInfo("保存机器信息").WithSave(true)
saveMachineP := req.NewPermission("machine:update")
machines.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(saveMachine).
WithRequiredPermission(saveMachineP).
Handle(m.SaveMachine)
})
changeStatus := ctx.NewLogInfo("调整机器状态").WithSave(true)
changeStatus := req.NewLogInfo("调整机器状态").WithSave(true)
machines.PUT(":machineId/:status", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(changeStatus).
Handle(m.ChangeStatus)
})
delMachine := ctx.NewLogInfo("删除机器").WithSave(true)
delMachine := req.NewLogInfo("删除机器").WithSave(true)
machines.DELETE(":machineId", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(delMachine).
Handle(m.DeleteMachine)
})
closeCli := ctx.NewLogInfo("关闭机器客户端").WithSave(true)
closeCli := req.NewLogInfo("关闭机器客户端").WithSave(true)
machines.DELETE(":machineId/close-cli", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(closeCli).Handle(m.CloseCli)
req.NewCtxWithGin(c).WithLog(closeCli).Handle(m.CloseCli)
})
machines.GET(":machineId/terminal", m.WsSSH)
// 获取机器终端回放记录的相应文件夹名或文件名,目前具有保存机器信息的权限标识才有权限查看终端回放
machines.GET("rec/names", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithRequiredPermission(saveMachineP).
Handle(m.MachineRecDirNames)
})

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/internal/machine/api"
"mayfly-go/internal/machine/application"
sysApplication "mayfly-go/internal/sys/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -19,66 +19,66 @@ func InitMachineFileRouter(router *gin.RouterGroup) {
// 获取指定机器文件列表
machineFile.GET(":machineId/files", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(mf.MachineFiles)
req.NewCtxWithGin(c).Handle(mf.MachineFiles)
})
// 新增修改机器文件
addFileConf := ctx.NewLogInfo("机器-新增文件配置").WithSave(true)
afcP := ctx.NewPermission("machine:file:add")
addFileConf := req.NewLogInfo("机器-新增文件配置").WithSave(true)
afcP := req.NewPermission("machine:file:add")
machineFile.POST(":machineId/files", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(addFileConf).
req.NewCtxWithGin(c).WithLog(addFileConf).
WithRequiredPermission(afcP).
Handle(mf.SaveMachineFiles)
})
// 删除机器文件
delFileConf := ctx.NewLogInfo("机器-删除文件配置").WithSave(true)
dfcP := ctx.NewPermission("machine:file:del")
delFileConf := req.NewLogInfo("机器-删除文件配置").WithSave(true)
dfcP := req.NewPermission("machine:file:del")
machineFile.DELETE(":machineId/files/:fileId", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delFileConf).
req.NewCtxWithGin(c).WithLog(delFileConf).
WithRequiredPermission(dfcP).
Handle(mf.DeleteFile)
})
getContent := ctx.NewLogInfo("机器-获取文件内容").WithSave(true)
getContent := req.NewLogInfo("机器-获取文件内容").WithSave(true)
machineFile.GET(":machineId/files/:fileId/read", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(getContent).
req.NewCtxWithGin(c).WithLog(getContent).
Handle(mf.ReadFileContent)
})
getDir := ctx.NewLogInfo("机器-获取目录")
getDir := req.NewLogInfo("机器-获取目录")
machineFile.GET(":machineId/files/:fileId/read-dir", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(getDir).
req.NewCtxWithGin(c).WithLog(getDir).
Handle(mf.GetDirEntry)
})
writeFile := ctx.NewLogInfo("机器-修改文件内容").WithSave(true)
wfP := ctx.NewPermission("machine:file:write")
writeFile := req.NewLogInfo("机器-修改文件内容").WithSave(true)
wfP := req.NewPermission("machine:file:write")
machineFile.POST(":machineId/files/:fileId/write", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(writeFile).
req.NewCtxWithGin(c).WithLog(writeFile).
WithRequiredPermission(wfP).
Handle(mf.WriteFileContent)
})
createFile := ctx.NewLogInfo("机器-创建文件or目录").WithSave(true)
createFile := req.NewLogInfo("机器-创建文件or目录").WithSave(true)
machineFile.POST(":machineId/files/:fileId/create-file", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(createFile).
req.NewCtxWithGin(c).WithLog(createFile).
WithRequiredPermission(wfP).
Handle(mf.CreateFile)
})
uploadFile := ctx.NewLogInfo("机器-文件上传").WithSave(true)
ufP := ctx.NewPermission("machine:file:upload")
uploadFile := req.NewLogInfo("机器-文件上传").WithSave(true)
ufP := req.NewPermission("machine:file:upload")
machineFile.POST(":machineId/files/:fileId/upload", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(uploadFile).
req.NewCtxWithGin(c).WithLog(uploadFile).
WithRequiredPermission(ufP).
Handle(mf.UploadFile)
})
removeFile := ctx.NewLogInfo("机器-删除文件or文件夹").WithSave(true)
rfP := ctx.NewPermission("machine:file:rm")
removeFile := req.NewLogInfo("机器-删除文件or文件夹").WithSave(true)
rfP := req.NewPermission("machine:file:rm")
machineFile.DELETE(":machineId/files/:fileId/remove", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(removeFile).
req.NewCtxWithGin(c).WithLog(removeFile).
WithRequiredPermission(rfP).
Handle(mf.RemoveFile)
})

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/internal/machine/api"
"mayfly-go/internal/machine/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -20,32 +20,32 @@ func InitMachineScriptRouter(router *gin.RouterGroup) {
// 获取指定机器脚本列表
machines.GET(":machineId/scripts", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(ms.MachineScripts)
req.NewCtxWithGin(c).Handle(ms.MachineScripts)
})
saveMachienScriptLog := ctx.NewLogInfo("机器-保存脚本").WithSave(true)
smsP := ctx.NewPermission("machine:script:save")
saveMachienScriptLog := req.NewLogInfo("机器-保存脚本").WithSave(true)
smsP := req.NewPermission("machine:script:save")
// 保存脚本
machines.POST(":machineId/scripts", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveMachienScriptLog).
req.NewCtxWithGin(c).WithLog(saveMachienScriptLog).
WithRequiredPermission(smsP).
Handle(ms.SaveMachineScript)
})
deleteLog := ctx.NewLogInfo("机器-删除脚本").WithSave(true)
dP := ctx.NewPermission("machine:script:del")
deleteLog := req.NewLogInfo("机器-删除脚本").WithSave(true)
dP := req.NewPermission("machine:script:del")
// 保存脚本
machines.DELETE(":machineId/scripts/:scriptId", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(deleteLog).
req.NewCtxWithGin(c).WithLog(deleteLog).
WithRequiredPermission(dP).
Handle(ms.DeleteMachineScript)
})
runLog := ctx.NewLogInfo("机器-执行脚本").WithSave(true)
rP := ctx.NewPermission("machine:script:run")
runLog := req.NewLogInfo("机器-执行脚本").WithSave(true)
rP := req.NewPermission("machine:script:run")
// 运行脚本
machines.GET(":machineId/scripts/:scriptId/run", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(runLog).
req.NewCtxWithGin(c).WithLog(runLog).
WithRequiredPermission(rP).
Handle(ms.RunMachineScript)
})

View File

@@ -7,9 +7,9 @@ import (
"mayfly-go/internal/mongo/domain/entity"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
"regexp"
"strconv"
@@ -25,7 +25,7 @@ type Mongo struct {
TagApp tagapp.TagTree
}
func (m *Mongo) Mongos(rc *ctx.ReqCtx) {
func (m *Mongo) Mongos(rc *req.Ctx) {
condition := new(entity.MongoQuery)
condition.TagPathLike = rc.GinCtx.Query("tagPath")
@@ -39,7 +39,7 @@ func (m *Mongo) Mongos(rc *ctx.ReqCtx) {
rc.ResData = m.MongoApp.GetPageList(condition, ginx.GetPageParam(rc.GinCtx), new([]entity.Mongo))
}
func (m *Mongo) Save(rc *ctx.ReqCtx) {
func (m *Mongo) Save(rc *req.Ctx) {
form := &form.Mongo{}
ginx.BindJsonAndValid(rc.GinCtx, form)
@@ -57,18 +57,18 @@ func (m *Mongo) Save(rc *ctx.ReqCtx) {
m.MongoApp.Save(mongo)
}
func (m *Mongo) DeleteMongo(rc *ctx.ReqCtx) {
func (m *Mongo) DeleteMongo(rc *req.Ctx) {
m.MongoApp.Delete(m.GetMongoId(rc.GinCtx))
}
func (m *Mongo) Databases(rc *ctx.ReqCtx) {
func (m *Mongo) Databases(rc *req.Ctx) {
cli := m.MongoApp.GetMongoCli(m.GetMongoId(rc.GinCtx))
res, err := cli.ListDatabases(context.TODO(), bson.D{})
biz.ErrIsNilAppendErr(err, "获取mongo所有库信息失败: %s")
rc.ResData = res
}
func (m *Mongo) Collections(rc *ctx.ReqCtx) {
func (m *Mongo) Collections(rc *req.Ctx) {
cli := m.MongoApp.GetMongoCli(m.GetMongoId(rc.GinCtx))
db := rc.GinCtx.Query("database")
biz.NotEmpty(db, "database不能为空")
@@ -78,7 +78,7 @@ func (m *Mongo) Collections(rc *ctx.ReqCtx) {
rc.ResData = res
}
func (m *Mongo) RunCommand(rc *ctx.ReqCtx) {
func (m *Mongo) RunCommand(rc *req.Ctx) {
commandForm := new(form.MongoRunCommand)
ginx.BindJsonAndValid(rc.GinCtx, commandForm)
cli := m.MongoApp.GetMongoCli(m.GetMongoId(rc.GinCtx))
@@ -94,7 +94,7 @@ func (m *Mongo) RunCommand(rc *ctx.ReqCtx) {
rc.ResData = bm
}
func (m *Mongo) FindCommand(rc *ctx.ReqCtx) {
func (m *Mongo) FindCommand(rc *req.Ctx) {
g := rc.GinCtx
cli := m.MongoApp.GetMongoCli(m.GetMongoId(g))
commandForm := new(form.MongoFindCommand)
@@ -127,7 +127,7 @@ func (m *Mongo) FindCommand(rc *ctx.ReqCtx) {
rc.ResData = res
}
func (m *Mongo) UpdateByIdCommand(rc *ctx.ReqCtx) {
func (m *Mongo) UpdateByIdCommand(rc *req.Ctx) {
g := rc.GinCtx
cli := m.MongoApp.GetMongoCli(m.GetMongoId(g))
commandForm := new(form.MongoUpdateByIdCommand)
@@ -150,7 +150,7 @@ func (m *Mongo) UpdateByIdCommand(rc *ctx.ReqCtx) {
rc.ResData = res
}
func (m *Mongo) DeleteByIdCommand(rc *ctx.ReqCtx) {
func (m *Mongo) DeleteByIdCommand(rc *req.Ctx) {
g := rc.GinCtx
cli := m.MongoApp.GetMongoCli(m.GetMongoId(g))
commandForm := new(form.MongoUpdateByIdCommand)
@@ -173,7 +173,7 @@ func (m *Mongo) DeleteByIdCommand(rc *ctx.ReqCtx) {
rc.ResData = res
}
func (m *Mongo) InsertOneCommand(rc *ctx.ReqCtx) {
func (m *Mongo) InsertOneCommand(rc *req.Ctx) {
g := rc.GinCtx
cli := m.MongoApp.GetMongoCli(m.GetMongoId(g))
commandForm := new(form.MongoInsertCommand)

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/internal/mongo/api"
"mayfly-go/internal/mongo/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -19,68 +19,68 @@ func InitMongoRouter(router *gin.RouterGroup) {
// 获取所有mongo列表
m.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
Handle(ma.Mongos)
})
saveMongo := ctx.NewLogInfo("mongo-保存信息")
saveMongo := req.NewLogInfo("mongo-保存信息")
m.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(saveMongo).
Handle(ma.Save)
})
deleteMongo := ctx.NewLogInfo("mongo-删除信息")
deleteMongo := req.NewLogInfo("mongo-删除信息")
m.DELETE(":id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(deleteMongo).
Handle(ma.DeleteMongo)
})
// 获取mongo下的所有数据库
m.GET(":id/databases", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
Handle(ma.Databases)
})
// 获取mongo指定库的所有集合
m.GET(":id/collections", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
Handle(ma.Collections)
})
// 获取mongo runCommand
m.POST(":id/run-command", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
Handle(ma.RunCommand)
})
// 执行mongo find命令
m.POST(":id/command/find", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
Handle(ma.FindCommand)
})
// 执行mongo update by id命令
updateDocById := ctx.NewLogInfo("mongo-更新文档").WithSave(true)
updateDocById := req.NewLogInfo("mongo-更新文档").WithSave(true)
m.POST(":id/command/update-by-id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(updateDocById).
Handle(ma.UpdateByIdCommand)
})
// 执行mongo delete by id命令
deleteDoc := ctx.NewLogInfo("mongo-删除文档").WithSave(true)
deleteDoc := req.NewLogInfo("mongo-删除文档").WithSave(true)
m.POST(":id/command/delete-by-id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(deleteDoc).
Handle(ma.DeleteByIdCommand)
})
// 执行mongo insert 命令
insertDoc := ctx.NewLogInfo("mongo-新增文档").WithSave(true)
insertDoc := req.NewLogInfo("mongo-新增文档").WithSave(true)
m.POST(":id/command/insert", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(insertDoc).
Handle(ma.InsertOneCommand)
})

View File

@@ -9,9 +9,9 @@ import (
"mayfly-go/internal/redis/domain/entity"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
"strconv"
"strings"
@@ -26,7 +26,7 @@ type Redis struct {
TagApp tagapp.TagTree
}
func (r *Redis) RedisList(rc *ctx.ReqCtx) {
func (r *Redis) RedisList(rc *req.Ctx) {
condition := new(entity.RedisQuery)
condition.TagPathLike = rc.GinCtx.Query("tagPath")
@@ -40,7 +40,7 @@ func (r *Redis) RedisList(rc *ctx.ReqCtx) {
rc.ResData = r.RedisApp.GetPageList(condition, ginx.GetPageParam(rc.GinCtx), new([]vo.Redis))
}
func (r *Redis) Save(rc *ctx.ReqCtx) {
func (r *Redis) Save(rc *req.Ctx) {
form := &form.Redis{}
ginx.BindJsonAndValid(rc.GinCtx, form)
@@ -61,51 +61,57 @@ func (r *Redis) Save(rc *ctx.ReqCtx) {
}
// 获取redis实例密码由于数据库是加密存储故提供该接口展示原文密码
func (r *Redis) GetRedisPwd(rc *ctx.ReqCtx) {
func (r *Redis) GetRedisPwd(rc *req.Ctx) {
rid := uint64(ginx.PathParamInt(rc.GinCtx, "id"))
re := r.RedisApp.GetById(rid, "Password")
re.PwdDecrypt()
rc.ResData = re.Password
}
func (r *Redis) DeleteRedis(rc *ctx.ReqCtx) {
func (r *Redis) DeleteRedis(rc *req.Ctx) {
r.RedisApp.Delete(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}
func (r *Redis) RedisInfo(rc *ctx.ReqCtx) {
func (r *Redis) RedisInfo(rc *req.Ctx) {
g := rc.GinCtx
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), 0)
var res string
var err error
section := rc.GinCtx.Query("section")
mode := ri.Info.Mode
ctx := context.Background()
var redisCli *redis.Client
if mode == "" || mode == entity.RedisModeStandalone || mode == entity.RedisModeSentinel {
res, err = ri.Cli.Info(ctx).Result()
redisCli = ri.Cli
} else if mode == entity.RedisModeCluster {
host := rc.GinCtx.Query("host")
biz.NotEmpty(host, "集群模式host信息不能为空")
clusterClient := ri.ClusterCli
var redisClient *redis.Client
// 遍历集群的master节点找到该redis client
clusterClient.ForEachMaster(ctx, func(ctx context.Context, client *redis.Client) error {
if host == client.Options().Addr {
redisClient = client
redisCli = client
}
return nil
})
if redisClient == nil {
if redisCli == nil {
// 遍历集群的slave节点找到该redis client
clusterClient.ForEachSlave(ctx, func(ctx context.Context, client *redis.Client) error {
if host == client.Options().Addr {
redisClient = client
redisCli = client
}
return nil
})
}
biz.NotNil(redisClient, "该实例不在该集群中")
res, err = redisClient.Info(ctx).Result()
biz.NotNil(redisCli, "该实例不在该集群中")
}
var res string
var err error
if section == "" {
res, err = ri.Cli.Info(ctx).Result()
} else {
res, err = ri.Cli.Info(ctx, section).Result()
}
biz.ErrIsNilAppendErr(err, "获取redis info失败: %s")
@@ -144,7 +150,7 @@ func (r *Redis) RedisInfo(rc *ctx.ReqCtx) {
rc.ResData = parseMap
}
func (r *Redis) ClusterInfo(rc *ctx.ReqCtx) {
func (r *Redis) ClusterInfo(rc *req.Ctx) {
g := rc.GinCtx
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), 0)
biz.IsEquals(ri.Info.Mode, entity.RedisModeCluster, "非集群模式")
@@ -189,7 +195,7 @@ func (r *Redis) ClusterInfo(rc *ctx.ReqCtx) {
}
// scan获取redis的key列表信息
func (r *Redis) Scan(rc *ctx.ReqCtx) {
func (r *Redis) Scan(rc *req.Ctx) {
g := rc.GinCtx
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
@@ -318,7 +324,7 @@ func (r *Redis) Scan(rc *ctx.ReqCtx) {
rc.ResData = &vo.Keys{Cursor: cursorRes, Keys: kis, DbSize: size}
}
func (r *Redis) DeleteKey(rc *ctx.ReqCtx) {
func (r *Redis) DeleteKey(rc *req.Ctx) {
g := rc.GinCtx
key := g.Query("key")
biz.NotEmpty(key, "key不能为空")
@@ -330,7 +336,7 @@ func (r *Redis) DeleteKey(rc *ctx.ReqCtx) {
ri.GetCmdable().Del(context.Background(), key)
}
func (r *Redis) checkKey(rc *ctx.ReqCtx) (*application.RedisInstance, string) {
func (r *Redis) checkKey(rc *req.Ctx) (*application.RedisInstance, string) {
g := rc.GinCtx
key := g.Query("key")
biz.NotEmpty(key, "key不能为空")
@@ -341,14 +347,14 @@ func (r *Redis) checkKey(rc *ctx.ReqCtx) (*application.RedisInstance, string) {
return ri, key
}
func (r *Redis) GetStringValue(rc *ctx.ReqCtx) {
func (r *Redis) GetStringValue(rc *req.Ctx) {
ri, key := r.checkKey(rc)
str, err := ri.GetCmdable().Get(context.TODO(), key).Result()
biz.ErrIsNilAppendErr(err, "获取字符串值失败: %s")
rc.ResData = str
}
func (r *Redis) SetStringValue(rc *ctx.ReqCtx) {
func (r *Redis) SetStringValue(rc *req.Ctx) {
g := rc.GinCtx
keyValue := new(form.StringValue)
ginx.BindJsonAndValid(g, keyValue)
@@ -363,7 +369,7 @@ func (r *Redis) SetStringValue(rc *ctx.ReqCtx) {
rc.ResData = str
}
func (r *Redis) Hscan(rc *ctx.ReqCtx) {
func (r *Redis) Hscan(rc *req.Ctx) {
ri, key := r.checkKey(rc)
g := rc.GinCtx
count := ginx.QueryInt(g, "count", 10)
@@ -384,7 +390,7 @@ func (r *Redis) Hscan(rc *ctx.ReqCtx) {
}
}
func (r *Redis) Hdel(rc *ctx.ReqCtx) {
func (r *Redis) Hdel(rc *req.Ctx) {
ri, key := r.checkKey(rc)
field := rc.GinCtx.Query("field")
@@ -393,7 +399,7 @@ func (r *Redis) Hdel(rc *ctx.ReqCtx) {
rc.ResData = delRes
}
func (r *Redis) Hget(rc *ctx.ReqCtx) {
func (r *Redis) Hget(rc *req.Ctx) {
ri, key := r.checkKey(rc)
field := rc.GinCtx.Query("field")
@@ -402,7 +408,7 @@ func (r *Redis) Hget(rc *ctx.ReqCtx) {
rc.ResData = res
}
func (r *Redis) SetHashValue(rc *ctx.ReqCtx) {
func (r *Redis) SetHashValue(rc *req.Ctx) {
g := rc.GinCtx
hashValue := new(form.HashValue)
ginx.BindJsonAndValid(g, hashValue)
@@ -422,14 +428,14 @@ func (r *Redis) SetHashValue(rc *ctx.ReqCtx) {
}
}
func (r *Redis) GetSetValue(rc *ctx.ReqCtx) {
func (r *Redis) GetSetValue(rc *req.Ctx) {
ri, key := r.checkKey(rc)
res, err := ri.GetCmdable().SMembers(context.TODO(), key).Result()
biz.ErrIsNilAppendErr(err, "获取set值失败: %s")
rc.ResData = res
}
func (r *Redis) SetSetValue(rc *ctx.ReqCtx) {
func (r *Redis) SetSetValue(rc *req.Ctx) {
g := rc.GinCtx
keyvalue := new(form.SetValue)
ginx.BindJsonAndValid(g, keyvalue)
@@ -448,7 +454,7 @@ func (r *Redis) SetSetValue(rc *ctx.ReqCtx) {
}
}
func (r *Redis) GetListValue(rc *ctx.ReqCtx) {
func (r *Redis) GetListValue(rc *req.Ctx) {
ri, key := r.checkKey(rc)
ctx := context.TODO()
cmdable := ri.GetCmdable()
@@ -468,7 +474,7 @@ func (r *Redis) GetListValue(rc *ctx.ReqCtx) {
}
}
func (r *Redis) SaveListValue(rc *ctx.ReqCtx) {
func (r *Redis) SaveListValue(rc *req.Ctx) {
g := rc.GinCtx
listValue := new(form.ListValue)
ginx.BindJsonAndValid(g, listValue)
@@ -488,7 +494,7 @@ func (r *Redis) SaveListValue(rc *ctx.ReqCtx) {
}
}
func (r *Redis) SetListValue(rc *ctx.ReqCtx) {
func (r *Redis) SetListValue(rc *req.Ctx) {
g := rc.GinCtx
listSetValue := new(form.ListSetValue)
ginx.BindJsonAndValid(g, listSetValue)

View File

@@ -7,8 +7,6 @@ type Redis struct {
Name *string `json:"name"`
Host *string `json:"host"`
Db string `json:"db"`
ProjectId *int64 `json:"projectId"`
Project *string `json:"project"`
Mode *string `json:"mode"`
EnableSshTunnel *int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId *uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/internal/redis/api"
"mayfly-go/internal/redis/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -19,94 +19,94 @@ func InitRedisRouter(router *gin.RouterGroup) {
// 获取redis list
redis.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.RedisList)
req.NewCtxWithGin(c).Handle(rs.RedisList)
})
save := ctx.NewLogInfo("redis-保存信息").WithSave(true)
save := req.NewLogInfo("redis-保存信息").WithSave(true)
redis.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(save).Handle(rs.Save)
req.NewCtxWithGin(c).WithLog(save).Handle(rs.Save)
})
redis.GET(":id/pwd", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.GetRedisPwd)
req.NewCtxWithGin(c).Handle(rs.GetRedisPwd)
})
delRedis := ctx.NewLogInfo("redis-删除信息").WithSave(true)
delRedis := req.NewLogInfo("redis-删除信息").WithSave(true)
redis.DELETE(":id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delRedis).Handle(rs.DeleteRedis)
req.NewCtxWithGin(c).WithLog(delRedis).Handle(rs.DeleteRedis)
})
redis.GET(":id/info", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.RedisInfo)
req.NewCtxWithGin(c).Handle(rs.RedisInfo)
})
redis.GET(":id/cluster-info", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.ClusterInfo)
req.NewCtxWithGin(c).Handle(rs.ClusterInfo)
})
// 获取指定redis keys
redis.POST(":id/:db/scan", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.Scan)
req.NewCtxWithGin(c).Handle(rs.Scan)
})
// 删除key
deleteKeyL := ctx.NewLogInfo("redis-删除key").WithSave(true)
deleteKeyL := req.NewLogInfo("redis-删除key").WithSave(true)
redis.DELETE(":id/:db/key", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(deleteKeyL).Handle(rs.DeleteKey)
req.NewCtxWithGin(c).WithLog(deleteKeyL).Handle(rs.DeleteKey)
})
// 获取string类型值
redis.GET(":id/:db/string-value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.GetStringValue)
req.NewCtxWithGin(c).Handle(rs.GetStringValue)
})
// 设置string类型值
setStringL := ctx.NewLogInfo("redis-setString").WithSave(true)
setStringL := req.NewLogInfo("redis-setString").WithSave(true)
redis.POST(":id/:db/string-value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(setStringL).Handle(rs.SetStringValue)
req.NewCtxWithGin(c).WithLog(setStringL).Handle(rs.SetStringValue)
})
// hscan
redis.GET(":id/:db/hscan", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.Hscan)
req.NewCtxWithGin(c).Handle(rs.Hscan)
})
redis.GET(":id/:db/hget", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.Hget)
req.NewCtxWithGin(c).Handle(rs.Hget)
})
hdelL := ctx.NewLogInfo("redis-hdel").WithSave(true)
hdelL := req.NewLogInfo("redis-hdel").WithSave(true)
redis.DELETE(":id/:db/hdel", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(hdelL).Handle(rs.Hdel)
req.NewCtxWithGin(c).WithLog(hdelL).Handle(rs.Hdel)
})
// 设置hash类型值
setHashValueL := ctx.NewLogInfo("redis-setHashValue").WithSave(true)
setHashValueL := req.NewLogInfo("redis-setHashValue").WithSave(true)
redis.POST(":id/:db/hash-value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(setHashValueL).Handle(rs.SetHashValue)
req.NewCtxWithGin(c).WithLog(setHashValueL).Handle(rs.SetHashValue)
})
// 获取set类型值
redis.GET(":id/:db/set-value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.GetSetValue)
req.NewCtxWithGin(c).Handle(rs.GetSetValue)
})
// 设置set类型值
redis.POST(":id/:db/set-value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.SetSetValue)
req.NewCtxWithGin(c).Handle(rs.SetSetValue)
})
// 获取list类型值
redis.GET(":id/:db/list-value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.GetListValue)
req.NewCtxWithGin(c).Handle(rs.GetListValue)
})
redis.POST(":id/:db/list-value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.SaveListValue)
req.NewCtxWithGin(c).Handle(rs.SaveListValue)
})
redis.POST(":id/:db/list-value/lset", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.SetListValue)
req.NewCtxWithGin(c).Handle(rs.SetListValue)
})
}
}

View File

@@ -8,9 +8,9 @@ import (
"mayfly-go/internal/sys/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/captcha"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
"regexp"
"strconv"
@@ -29,7 +29,7 @@ type Account struct {
/** 登录者个人相关操作 **/
// @router /accounts/login [post]
func (a *Account) Login(rc *ctx.ReqCtx) {
func (a *Account) Login(rc *req.Ctx) {
loginForm := &form.LoginForm{}
ginx.BindJsonAndValid(rc.GinCtx, loginForm)
@@ -65,7 +65,7 @@ func (a *Account) Login(rc *ctx.ReqCtx) {
}
}
// 保存该账号的权限codes
ctx.SavePermissionCodes(account.Id, permissions)
req.SavePermissionCodes(account.Id, permissions)
clientIp := rc.GinCtx.ClientIP()
// 保存登录消息
@@ -76,7 +76,7 @@ func (a *Account) Login(rc *ctx.ReqCtx) {
rc.LoginAccount = &model.LoginAccount{Id: account.Id, Username: account.Username}
rc.ResData = map[string]interface{}{
"token": ctx.CreateToken(account.Id, account.Username),
"token": req.CreateToken(account.Id, account.Username),
"name": account.Name,
"username": account.Username,
"lastLoginTime": account.LastLoginTime,
@@ -86,7 +86,7 @@ func (a *Account) Login(rc *ctx.ReqCtx) {
}
}
func (a *Account) ChangePassword(rc *ctx.ReqCtx) {
func (a *Account) ChangePassword(rc *req.Ctx) {
form := new(form.AccountChangePasswordForm)
ginx.BindJsonAndValid(rc.GinCtx, form)
@@ -153,7 +153,7 @@ func (a *Account) saveLogin(account *entity.Account, ip string) {
}
// 获取个人账号信息
func (a *Account) AccountInfo(rc *ctx.ReqCtx) {
func (a *Account) AccountInfo(rc *req.Ctx) {
ap := new(vo.AccountPersonVO)
// 角色信息
roles := new([]vo.AccountRoleVO)
@@ -164,7 +164,7 @@ func (a *Account) AccountInfo(rc *ctx.ReqCtx) {
}
// 更新个人账号信息
func (a *Account) UpdateAccount(rc *ctx.ReqCtx) {
func (a *Account) UpdateAccount(rc *req.Ctx) {
updateForm := &form.AccountUpdateForm{}
ginx.BindJsonAndValid(rc.GinCtx, updateForm)
@@ -181,7 +181,7 @@ func (a *Account) UpdateAccount(rc *ctx.ReqCtx) {
}
// 获取账号接收的消息列表
func (a *Account) GetMsgs(rc *ctx.ReqCtx) {
func (a *Account) GetMsgs(rc *req.Ctx) {
condition := &entity.Msg{
RecipientId: int64(rc.LoginAccount.Id),
}
@@ -191,14 +191,14 @@ func (a *Account) GetMsgs(rc *ctx.ReqCtx) {
/** 后台账号操作 **/
// @router /accounts [get]
func (a *Account) Accounts(rc *ctx.ReqCtx) {
func (a *Account) Accounts(rc *req.Ctx) {
condition := &entity.Account{}
condition.Username = rc.GinCtx.Query("username")
rc.ResData = a.AccountApp.GetPageList(condition, ginx.GetPageParam(rc.GinCtx), new([]vo.AccountManageVO))
}
// @router /accounts
func (a *Account) SaveAccount(rc *ctx.ReqCtx) {
func (a *Account) SaveAccount(rc *req.Ctx) {
form := &form.AccountCreateForm{}
ginx.BindJsonAndValid(rc.GinCtx, form)
rc.ReqParam = form
@@ -218,7 +218,7 @@ func (a *Account) SaveAccount(rc *ctx.ReqCtx) {
}
}
func (a *Account) ChangeStatus(rc *ctx.ReqCtx) {
func (a *Account) ChangeStatus(rc *req.Ctx) {
g := rc.GinCtx
account := &entity.Account{}
@@ -228,25 +228,25 @@ func (a *Account) ChangeStatus(rc *ctx.ReqCtx) {
a.AccountApp.Update(account)
}
func (a *Account) DeleteAccount(rc *ctx.ReqCtx) {
func (a *Account) DeleteAccount(rc *req.Ctx) {
id := uint64(ginx.PathParamInt(rc.GinCtx, "id"))
rc.ReqParam = id
a.AccountApp.Delete(id)
}
// 获取账号角色id列表用户回显角色分配
func (a *Account) AccountRoleIds(rc *ctx.ReqCtx) {
func (a *Account) AccountRoleIds(rc *req.Ctx) {
rc.ResData = a.RoleApp.GetAccountRoleIds(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}
// 获取账号角色id列表用户回显角色分配
func (a *Account) AccountRoles(rc *ctx.ReqCtx) {
func (a *Account) AccountRoles(rc *req.Ctx) {
vos := new([]vo.AccountRoleVO)
a.RoleApp.GetAccountRoles(uint64(ginx.PathParamInt(rc.GinCtx, "id")), vos)
rc.ResData = vos
}
func (a *Account) AccountResources(rc *ctx.ReqCtx) {
func (a *Account) AccountResources(rc *req.Ctx) {
var resources vo.ResourceManageVOList
// 获取账号菜单资源
a.ResourceApp.GetAccountResources(uint64(ginx.PathParamInt(rc.GinCtx, "id")), &resources)
@@ -254,7 +254,7 @@ func (a *Account) AccountResources(rc *ctx.ReqCtx) {
}
// 保存账号角色信息
func (a *Account) SaveRoles(rc *ctx.ReqCtx) {
func (a *Account) SaveRoles(rc *req.Ctx) {
g := rc.GinCtx
var form form.AccountRoleForm

View File

@@ -2,10 +2,10 @@ package api
import (
"mayfly-go/pkg/captcha"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
)
func GenerateCaptcha(rc *ctx.ReqCtx) {
func GenerateCaptcha(rc *req.Ctx) {
id, image := captcha.Generate()
rc.ResData = map[string]interface{}{"base64Captcha": image, "cid": id}
}

View File

@@ -5,8 +5,8 @@ import (
"mayfly-go/internal/sys/application"
"mayfly-go/internal/sys/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
)
@@ -14,19 +14,19 @@ type Config struct {
ConfigApp application.Config
}
func (c *Config) Configs(rc *ctx.ReqCtx) {
func (c *Config) Configs(rc *req.Ctx) {
g := rc.GinCtx
condition := &entity.Config{Key: g.Query("key")}
rc.ResData = c.ConfigApp.GetPageList(condition, ginx.GetPageParam(g), new([]entity.Config))
}
func (c *Config) GetConfigValueByKey(rc *ctx.ReqCtx) {
func (c *Config) GetConfigValueByKey(rc *req.Ctx) {
key := rc.GinCtx.Query("key")
biz.NotEmpty(key, "key不能为空")
rc.ResData = c.ConfigApp.GetConfig(key).Value
}
func (c *Config) SaveConfig(rc *ctx.ReqCtx) {
func (c *Config) SaveConfig(rc *req.Ctx) {
g := rc.GinCtx
form := &form.ConfigForm{}
ginx.BindJsonAndValid(g, form)

View File

@@ -6,8 +6,8 @@ import (
"mayfly-go/internal/sys/api/vo"
"mayfly-go/internal/sys/application"
"mayfly-go/internal/sys/domain/entity"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
)
@@ -15,17 +15,17 @@ type Resource struct {
ResourceApp application.Resource
}
func (r *Resource) GetAllResourceTree(rc *ctx.ReqCtx) {
func (r *Resource) GetAllResourceTree(rc *req.Ctx) {
var resources vo.ResourceManageVOList
r.ResourceApp.GetResourceList(new(entity.Resource), &resources, "weight asc")
rc.ResData = resources.ToTrees(0)
}
func (r *Resource) GetById(rc *ctx.ReqCtx) {
func (r *Resource) GetById(rc *req.Ctx) {
rc.ResData = r.ResourceApp.GetById(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}
func (r *Resource) SaveResource(rc *ctx.ReqCtx) {
func (r *Resource) SaveResource(rc *req.Ctx) {
g := rc.GinCtx
form := new(form.ResourceForm)
ginx.BindJsonAndValid(g, form)
@@ -41,11 +41,11 @@ func (r *Resource) SaveResource(rc *ctx.ReqCtx) {
r.ResourceApp.Save(entity)
}
func (r *Resource) DelResource(rc *ctx.ReqCtx) {
func (r *Resource) DelResource(rc *req.Ctx) {
r.ResourceApp.Delete(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}
func (r *Resource) ChangeStatus(rc *ctx.ReqCtx) {
func (r *Resource) ChangeStatus(rc *req.Ctx) {
re := &entity.Resource{}
re.Id = uint64(ginx.PathParamInt(rc.GinCtx, "id"))
re.Status = int8(ginx.PathParamInt(rc.GinCtx, "status"))

View File

@@ -5,8 +5,8 @@ import (
"mayfly-go/internal/sys/api/vo"
"mayfly-go/internal/sys/application"
"mayfly-go/internal/sys/domain/entity"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
"strconv"
"strings"
@@ -18,14 +18,14 @@ type Role struct {
ResourceApp application.Resource
}
func (r *Role) Roles(rc *ctx.ReqCtx) {
func (r *Role) Roles(rc *req.Ctx) {
g := rc.GinCtx
condition := &entity.Role{}
rc.ResData = r.RoleApp.GetPageList(condition, ginx.GetPageParam(g), new([]entity.Role))
}
// 保存角色信息
func (r *Role) SaveRole(rc *ctx.ReqCtx) {
func (r *Role) SaveRole(rc *req.Ctx) {
g := rc.GinCtx
form := &form.RoleForm{}
ginx.BindJsonAndValid(g, form)
@@ -39,17 +39,17 @@ func (r *Role) SaveRole(rc *ctx.ReqCtx) {
}
// 删除角色及其资源关联关系
func (r *Role) DelRole(rc *ctx.ReqCtx) {
func (r *Role) DelRole(rc *req.Ctx) {
r.RoleApp.DeleteRole(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}
// 获取角色关联的资源id数组用于分配资源时回显已拥有的资源
func (r *Role) RoleResourceIds(rc *ctx.ReqCtx) {
func (r *Role) RoleResourceIds(rc *req.Ctx) {
rc.ResData = r.RoleApp.GetRoleResourceIds(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}
// 查看角色关联的资源树信息
func (r *Role) RoleResource(rc *ctx.ReqCtx) {
func (r *Role) RoleResource(rc *req.Ctx) {
g := rc.GinCtx
var resources vo.ResourceManageVOList
@@ -59,7 +59,7 @@ func (r *Role) RoleResource(rc *ctx.ReqCtx) {
}
// 保存角色资源
func (r *Role) SaveResource(rc *ctx.ReqCtx) {
func (r *Role) SaveResource(rc *req.Ctx) {
g := rc.GinCtx
var form form.RoleResourceForm

View File

@@ -3,15 +3,15 @@ package api
import (
"mayfly-go/internal/sys/application"
"mayfly-go/internal/sys/domain/entity"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
)
type Syslog struct {
SyslogApp application.Syslog
}
func (r *Syslog) Syslogs(rc *ctx.ReqCtx) {
func (r *Syslog) Syslogs(rc *req.Ctx) {
g := rc.GinCtx
condition := &entity.Syslog{
Type: int8(ginx.QueryInt(g, "type", 0)),

View File

@@ -2,8 +2,8 @@ package api
import (
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/global"
"mayfly-go/pkg/req"
"mayfly-go/pkg/ws"
"github.com/gin-gonic/gin"
@@ -30,8 +30,8 @@ func (s *System) ConnectWs(g *gin.Context) {
panic(biz.NewBizErr("升级websocket失败"))
}
// 权限校验
rc := ctx.NewReqCtxWithGin(g)
if err = ctx.PermissionHandler(rc); err != nil {
rc := req.NewCtxWithGin(g)
if err = req.PermissionHandler(rc); err != nil {
panic(biz.NewBizErr("没有权限"))
}

View File

@@ -1,12 +1,17 @@
package application
import (
"encoding/json"
"mayfly-go/internal/sys/domain/entity"
"mayfly-go/internal/sys/domain/repository"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/global"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils"
)
const SysConfigKeyPrefix = "sys:config:"
type Config interface {
GetPageList(condition *entity.Config, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
@@ -36,12 +41,22 @@ func (a *configAppImpl) Save(config *entity.Config) {
} else {
a.configRepo.Update(config)
}
cache.Del(SysConfigKeyPrefix + config.Key)
}
func (a *configAppImpl) GetConfig(key string) *entity.Config {
config := &entity.Config{Key: key}
// 优先从缓存中获取
cacheStr := cache.GetStr(SysConfigKeyPrefix + key)
if cacheStr != "" {
json.Unmarshal([]byte(cacheStr), &config)
return config
}
if err := a.configRepo.GetConfig(config, "Id", "Key", "Value"); err != nil {
global.Log.Warnf("不存在key = [%s] 的系统配置", key)
} else {
cache.SetStr(SysConfigKeyPrefix+key, utils.ToJsonStr(config))
}
return config
}

View File

@@ -6,10 +6,9 @@ import (
"mayfly-go/internal/sys/domain/entity"
"mayfly-go/internal/sys/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
"reflect"
"time"
)
@@ -17,7 +16,7 @@ type Syslog interface {
GetPageList(condition *entity.Syslog, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
// 从请求上下文的参数保存系统日志
SaveFromReq(req *ctx.ReqCtx)
SaveFromReq(req *req.Ctx)
}
func newSyslogApp(syslogRepo repository.Syslog) Syslog {
@@ -34,7 +33,7 @@ func (m *syslogAppImpl) GetPageList(condition *entity.Syslog, pageParam *model.P
return m.syslogRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
func (m *syslogAppImpl) SaveFromReq(req *ctx.ReqCtx) {
func (m *syslogAppImpl) SaveFromReq(req *req.Ctx) {
lg := req.LoginAccount
if lg == nil {
return
@@ -51,7 +50,7 @@ func (m *syslogAppImpl) SaveFromReq(req *ctx.ReqCtx) {
}
reqParam := req.ReqParam
if !utils.IsBlank(reflect.ValueOf(reqParam)) {
if !utils.IsBlank(reqParam) {
// 如果是字符串类型则不使用json序列化
if reqStr, ok := reqParam.(string); ok {
syslog.ReqParam = reqStr

View File

@@ -3,10 +3,13 @@ package entity
import (
"encoding/json"
"mayfly-go/pkg/model"
"strconv"
)
const (
ConfigKeyUseLoginCaptcha string = "UseLoginCaptcha" // 是否使用登录验证码
ConfigKeyDbQueryMaxCount string = "DbQueryMaxCount" // 数据库查询的最大数量
ConfigKeyDbSaveQuerySQL string = "DbSaveQuerySQL" // 数据库是否记录查询相关sql
)
type Config struct {
@@ -41,3 +44,16 @@ func (c *Config) GetJsonMap() map[string]string {
_ = json.Unmarshal([]byte(c.Value), &res)
return res
}
// 获取配置的int值如果配置值非int或不存在则返回默认值
func (c *Config) IntValue(defaultValue int) int {
// 如果值不存在,则返回默认值
if c.Id == 0 {
return defaultValue
}
if intV, err := strconv.Atoi(c.Value); err != nil {
return defaultValue
} else {
return intV
}
}

View File

@@ -3,7 +3,7 @@ package router
import (
"mayfly-go/internal/sys/api"
"mayfly-go/internal/sys/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -19,17 +19,17 @@ func InitAccountRouter(router *gin.RouterGroup) {
}
{
// 用户登录
loginLog := ctx.NewLogInfo("用户登录").WithSave(true)
loginLog := req.NewLogInfo("用户登录").WithSave(true)
account.POST("login", func(g *gin.Context) {
ctx.NewReqCtxWithGin(g).
req.NewCtxWithGin(g).
WithNeedToken(false).
WithLog(loginLog).
Handle(a.Login)
})
changePwdLog := ctx.NewLogInfo("用户修改密码").WithSave(true)
changePwdLog := req.NewLogInfo("用户修改密码").WithSave(true)
account.POST("change-pwd", func(g *gin.Context) {
ctx.NewReqCtxWithGin(g).
req.NewCtxWithGin(g).
WithNeedToken(false).
WithLog(changePwdLog).
Handle(a.ChangePassword)
@@ -37,45 +37,45 @@ func InitAccountRouter(router *gin.RouterGroup) {
// 获取个人账号信息
account.GET("/self", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(a.AccountInfo)
req.NewCtxWithGin(c).Handle(a.AccountInfo)
})
// 更新个人账号信息
account.PUT("/self", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(a.UpdateAccount)
req.NewCtxWithGin(c).Handle(a.UpdateAccount)
})
account.GET("/msgs", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(a.GetMsgs)
req.NewCtxWithGin(c).Handle(a.GetMsgs)
})
/** 后台管理接口 **/
// 获取所有用户列表
account.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(a.Accounts)
req.NewCtxWithGin(c).Handle(a.Accounts)
})
createAccount := ctx.NewLogInfo("保存账号信息").WithSave(true)
addAccountPermission := ctx.NewPermission("account:add")
createAccount := req.NewLogInfo("保存账号信息").WithSave(true)
addAccountPermission := req.NewPermission("account:add")
account.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithRequiredPermission(addAccountPermission).
WithLog(createAccount).
Handle(a.SaveAccount)
})
changeStatus := ctx.NewLogInfo("修改账号状态").WithSave(true)
changeStatus := req.NewLogInfo("修改账号状态").WithSave(true)
account.PUT("change-status/:id/:status", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(changeStatus).
Handle(a.ChangeStatus)
})
delAccount := ctx.NewLogInfo("删除账号").WithSave(true)
delAccountPermission := ctx.NewPermission("account:del")
delAccount := req.NewLogInfo("删除账号").WithSave(true)
delAccountPermission := req.NewPermission("account:del")
account.DELETE(":id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithRequiredPermission(delAccountPermission).
WithLog(delAccount).
Handle(a.DeleteAccount)
@@ -83,26 +83,26 @@ func InitAccountRouter(router *gin.RouterGroup) {
// 获取所有用户角色id列表
account.GET(":id/roleIds", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(a.AccountRoleIds)
req.NewCtxWithGin(c).Handle(a.AccountRoleIds)
})
// 保存用户角色
saveAccountRole := ctx.NewLogInfo("保存用户角色").WithSave(true)
sarPermission := ctx.NewPermission("account:saveRoles")
saveAccountRole := req.NewLogInfo("保存用户角色").WithSave(true)
sarPermission := req.NewPermission("account:saveRoles")
account.POST("/roles", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveAccountRole).
req.NewCtxWithGin(c).WithLog(saveAccountRole).
WithRequiredPermission(sarPermission).
Handle(a.SaveRoles)
})
// 获取用户角色
account.GET(":id/roles", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(a.AccountRoles)
req.NewCtxWithGin(c).Handle(a.AccountRoles)
})
// 获取用户资源列表
account.GET(":id/resources", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(a.AccountResources)
req.NewCtxWithGin(c).Handle(a.AccountResources)
})
}
}

View File

@@ -2,7 +2,7 @@ package router
import (
"mayfly-go/internal/sys/api"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -11,7 +11,7 @@ func InitCaptchaRouter(router *gin.RouterGroup) {
captcha := router.Group("sys/captcha")
{
captcha.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithNeedToken(false).Handle(api.GenerateCaptcha)
req.NewCtxWithGin(c).WithNeedToken(false).Handle(api.GenerateCaptcha)
})
}
}

View File

@@ -3,7 +3,7 @@ package router
import (
"mayfly-go/internal/sys/api"
"mayfly-go/internal/sys/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -13,16 +13,16 @@ func InitSysConfigRouter(router *gin.RouterGroup) {
db := router.Group("sys/configs")
{
db.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(r.Configs)
req.NewCtxWithGin(c).Handle(r.Configs)
})
db.GET("/value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithNeedToken(false).Handle(r.GetConfigValueByKey)
req.NewCtxWithGin(c).WithNeedToken(false).Handle(r.GetConfigValueByKey)
})
saveConfig := ctx.NewLogInfo("保存系统配置信息").WithSave(true)
saveConfig := req.NewLogInfo("保存系统配置信息").WithSave(true)
db.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(saveConfig).
Handle(r.SaveConfig)
})

View File

@@ -3,7 +3,7 @@ package router
import (
"mayfly-go/internal/sys/api"
"mayfly-go/internal/sys/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -13,35 +13,35 @@ func InitResourceRouter(router *gin.RouterGroup) {
db := router.Group("sys/resources")
{
db.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(r.GetAllResourceTree)
req.NewCtxWithGin(c).Handle(r.GetAllResourceTree)
})
db.GET(":id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(r.GetById)
req.NewCtxWithGin(c).Handle(r.GetById)
})
saveResource := ctx.NewLogInfo("保存资源").WithSave(true)
srPermission := ctx.NewPermission("resource:add")
saveResource := req.NewLogInfo("保存资源").WithSave(true)
srPermission := req.NewPermission("resource:add")
db.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(saveResource).
WithRequiredPermission(srPermission).
Handle(r.SaveResource)
})
changeStatus := ctx.NewLogInfo("修改资源状态").WithSave(true)
csPermission := ctx.NewPermission("resource:changeStatus")
changeStatus := req.NewLogInfo("修改资源状态").WithSave(true)
csPermission := req.NewPermission("resource:changeStatus")
db.PUT(":id/:status", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(changeStatus).
WithRequiredPermission(csPermission).
Handle(r.ChangeStatus)
})
delResource := ctx.NewLogInfo("删除资源").WithSave(true)
dePermission := ctx.NewPermission("resource:delete")
delResource := req.NewLogInfo("删除资源").WithSave(true)
dePermission := req.NewPermission("resource:delete")
db.DELETE(":id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
req.NewCtxWithGin(c).
WithLog(delResource).
WithRequiredPermission(dePermission).
Handle(r.DelResource)

View File

@@ -3,7 +3,7 @@ package router
import (
"mayfly-go/internal/sys/api"
"mayfly-go/internal/sys/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -17,37 +17,37 @@ func InitRoleRouter(router *gin.RouterGroup) {
{
db.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(r.Roles)
req.NewCtxWithGin(c).Handle(r.Roles)
})
saveRole := ctx.NewLogInfo("保存角色").WithSave(true)
sPermission := ctx.NewPermission("role:add")
saveRole := req.NewLogInfo("保存角色").WithSave(true)
sPermission := req.NewPermission("role:add")
db.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveRole).
req.NewCtxWithGin(c).WithLog(saveRole).
WithRequiredPermission(sPermission).
Handle(r.SaveRole)
})
delRole := ctx.NewLogInfo("删除角色").WithSave(true)
drPermission := ctx.NewPermission("role:del")
delRole := req.NewLogInfo("删除角色").WithSave(true)
drPermission := req.NewPermission("role:del")
db.DELETE(":id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delRole).
req.NewCtxWithGin(c).WithLog(delRole).
WithRequiredPermission(drPermission).
Handle(r.DelRole)
})
db.GET(":id/resourceIds", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(r.RoleResourceIds)
req.NewCtxWithGin(c).Handle(r.RoleResourceIds)
})
db.GET(":id/resources", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(r.RoleResource)
req.NewCtxWithGin(c).Handle(r.RoleResource)
})
saveResource := ctx.NewLogInfo("保存角色资源").WithSave(true)
srPermission := ctx.NewPermission("role:saveResources")
saveResource := req.NewLogInfo("保存角色资源").WithSave(true)
srPermission := req.NewPermission("role:saveResources")
db.POST(":id/resources", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveResource).
req.NewCtxWithGin(c).WithLog(saveResource).
WithRequiredPermission(srPermission).
Handle(r.SaveResource)
})

View File

@@ -3,7 +3,7 @@ package router
import (
"mayfly-go/internal/sys/api"
"mayfly-go/internal/sys/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -15,7 +15,7 @@ func InitSyslogRouter(router *gin.RouterGroup) {
sys := router.Group("syslogs")
{
sys.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(s.Syslogs)
req.NewCtxWithGin(c).Handle(s.Syslogs)
})
}
}

View File

@@ -5,15 +5,16 @@ import (
"mayfly-go/internal/tag/api/vo"
"mayfly-go/internal/tag/application"
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
"strings"
)
type TagTree struct {
TagTreeApp application.TagTree
}
func (p *TagTree) GetAccountTags(rc *ctx.ReqCtx) {
func (p *TagTree) GetAccountTags(rc *req.Ctx) {
tagPaths := p.TagTreeApp.ListTagByAccountId(rc.LoginAccount.Id)
allTagPath := make([]string, 0)
if len(tagPaths) > 0 {
@@ -25,13 +26,22 @@ func (p *TagTree) GetAccountTags(rc *ctx.ReqCtx) {
rc.ResData = allTagPath
}
func (p *TagTree) GetTagTree(rc *ctx.ReqCtx) {
func (p *TagTree) GetTagTree(rc *req.Ctx) {
var tagTrees vo.TagTreeVOS
p.TagTreeApp.ListByQuery(new(entity.TagTreeQuery), &tagTrees)
rc.ResData = tagTrees.ToTrees(0)
}
func (p *TagTree) SaveTagTree(rc *ctx.ReqCtx) {
func (p *TagTree) ListByQuery(rc *req.Ctx) {
cond := new(entity.TagTreeQuery)
tagPaths := rc.GinCtx.Query("tagPaths")
cond.CodePaths = strings.Split(tagPaths, ",")
var tagTrees vo.TagTreeVOS
p.TagTreeApp.ListByQuery(cond, &tagTrees)
rc.ResData = tagTrees
}
func (p *TagTree) SaveTagTree(rc *req.Ctx) {
projectTree := &entity.TagTree{}
ginx.BindJsonAndValid(rc.GinCtx, projectTree)
@@ -42,6 +52,6 @@ func (p *TagTree) SaveTagTree(rc *ctx.ReqCtx) {
rc.ReqParam = fmt.Sprintf("tagTreeId: %d, tagName: %s, codePath: %s", projectTree.Id, projectTree.Name, projectTree.CodePath)
}
func (p *TagTree) DelTagTree(rc *ctx.ReqCtx) {
func (p *TagTree) DelTagTree(rc *req.Ctx) {
p.TagTreeApp.Delete(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}

View File

@@ -9,8 +9,8 @@ import (
"mayfly-go/internal/tag/application"
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
)
@@ -20,15 +20,15 @@ type Team struct {
AccountApp sys_applicaiton.Account
}
func (p *Team) GetTeams(rc *ctx.ReqCtx) {
func (p *Team) GetTeams(rc *req.Ctx) {
teams := &[]entity.Team{}
rc.ResData = p.TeamApp.GetPageList(&entity.Team{}, ginx.GetPageParam(rc.GinCtx), teams)
}
func (p *Team) SaveTeam(rc *ctx.ReqCtx) {
func (p *Team) SaveTeam(rc *req.Ctx) {
team := &entity.Team{}
ginx.BindJsonAndValid(rc.GinCtx, team)
rc.ReqParam = team
isAdd := team.Id == 0
loginAccount := rc.LoginAccount
@@ -47,12 +47,12 @@ func (p *Team) SaveTeam(rc *ctx.ReqCtx) {
}
}
func (p *Team) DelTeam(rc *ctx.ReqCtx) {
func (p *Team) DelTeam(rc *req.Ctx) {
p.TeamApp.Delete(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}
// 获取团队的成员信息
func (p *Team) GetTeamMembers(rc *ctx.ReqCtx) {
func (p *Team) GetTeamMembers(rc *req.Ctx) {
condition := &entity.TeamMember{TeamId: uint64(ginx.PathParamInt(rc.GinCtx, "id"))}
condition.Username = rc.GinCtx.Query("username")
@@ -60,7 +60,7 @@ func (p *Team) GetTeamMembers(rc *ctx.ReqCtx) {
}
// 保存团队信息
func (p *Team) SaveTeamMember(rc *ctx.ReqCtx) {
func (p *Team) SaveTeamMember(rc *req.Ctx) {
teamMems := &form.TeamMember{}
ginx.BindJsonAndValid(rc.GinCtx, teamMems)
@@ -88,7 +88,7 @@ func (p *Team) SaveTeamMember(rc *ctx.ReqCtx) {
}
// 删除团队成员
func (p *Team) DelTeamMember(rc *ctx.ReqCtx) {
func (p *Team) DelTeamMember(rc *req.Ctx) {
g := rc.GinCtx
tid := ginx.PathParamInt(g, "id")
aid := ginx.PathParamInt(g, "accountId")
@@ -98,12 +98,12 @@ func (p *Team) DelTeamMember(rc *ctx.ReqCtx) {
}
// 获取团队关联的标签id
func (p *Team) GetTagIds(rc *ctx.ReqCtx) {
func (p *Team) GetTagIds(rc *req.Ctx) {
rc.ResData = p.TeamApp.ListTagIds(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}
// 保存团队关联标签信息
func (p *Team) SaveTags(rc *ctx.ReqCtx) {
func (p *Team) SaveTags(rc *req.Ctx) {
g := rc.GinCtx
var form form.TagTreeTeam

View File

@@ -5,11 +5,11 @@ import "mayfly-go/pkg/model"
type TagTreeQuery struct {
model.Model
Pid uint64
Code string `json:"code"` // 标识
CodePath string `json:"codePath"` // 标识路径
Name string `json:"name"` // 名称
Pid uint64
Code string `json:"code"` // 标识
CodePath string `json:"codePath"` // 标识路径
CodePaths []string
Name string `json:"name"` // 名称
CodePathLike string // 标识符路径模糊查询
CodePathLikes []string
}

View File

@@ -6,6 +6,7 @@ import (
"mayfly-go/internal/tag/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
"strings"
)
type tagTreeRepoImpl struct{}
@@ -22,6 +23,14 @@ func (p *tagTreeRepoImpl) SelectByCondition(condition *entity.TagTreeQuery, toEn
if condition.CodePath != "" {
sql = fmt.Sprintf("%s AND p.code_path = '%s'", sql, condition.CodePath)
}
if len(condition.CodePaths) > 0 {
strCodePaths := make([]string, 0)
// 将字符串用''包裹
for _, v := range condition.CodePaths {
strCodePaths = append(strCodePaths, fmt.Sprintf("'%s'", v))
}
sql = fmt.Sprintf("%s AND p.code_path IN (%s)", sql, strings.Join(strCodePaths, ","))
}
if condition.CodePathLike != "" {
sql = fmt.Sprintf("%s AND p.code_path LIKE '%s'", sql, condition.CodePathLike+"%")
}

View File

@@ -3,7 +3,7 @@ package router
import (
"mayfly-go/internal/tag/api"
"mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -17,28 +17,33 @@ func InitTagTreeRouter(router *gin.RouterGroup) {
{
// 获取标签树列表
project.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetTagTree)
req.NewCtxWithGin(c).Handle(m.GetTagTree)
})
// 根据条件获取标签
project.GET("query", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(m.ListByQuery)
})
// 获取登录账号拥有的标签信息
project.GET("account-has", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetAccountTags)
req.NewCtxWithGin(c).Handle(m.GetAccountTags)
})
saveProjectTreeLog := ctx.NewLogInfo("标签树-保存信息").WithSave(true)
savePP := ctx.NewPermission("tag:save")
saveProjectTreeLog := req.NewLogInfo("标签树-保存信息").WithSave(true)
savePP := req.NewPermission("tag:save")
// 保存项目树下的环境信息
project.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveProjectTreeLog).
req.NewCtxWithGin(c).WithLog(saveProjectTreeLog).
WithRequiredPermission(savePP).
Handle(m.SaveTagTree)
})
delProjectLog := ctx.NewLogInfo("标签树-删除信息").WithSave(true)
delPP := ctx.NewPermission("tag:del")
delProjectLog := req.NewLogInfo("标签树-删除信息").WithSave(true)
delPP := req.NewPermission("tag:del")
// 删除标签
project.DELETE(":id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delProjectLog).
req.NewCtxWithGin(c).WithLog(delProjectLog).
WithRequiredPermission(delPP).
Handle(m.DelTagTree)
})

View File

@@ -4,7 +4,7 @@ import (
sysapp "mayfly-go/internal/sys/application"
"mayfly-go/internal/tag/api"
"mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
@@ -16,63 +16,63 @@ func InitTeamRouter(router *gin.RouterGroup) {
AccountApp: sysapp.GetAccountApp(),
}
project := router.Group("/teams")
team := router.Group("/teams")
{
// 获取团队列表
project.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetTeams)
team.GET("", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(m.GetTeams)
})
saveProjectTeamLog := ctx.NewLogInfo("团队-保存信息").WithSave(true)
savePP := ctx.NewPermission("team:save")
saveTeamLog := req.NewLogInfo("团队-保存信息").WithSave(true)
savePP := req.NewPermission("team:save")
// 保存项目团队信息
project.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveProjectTeamLog).
team.POST("", func(c *gin.Context) {
req.NewCtxWithGin(c).WithLog(saveTeamLog).
WithRequiredPermission(savePP).
Handle(m.SaveTeam)
})
delProjectTeamLog := ctx.NewLogInfo("团队-删除信息").WithSave(true)
delPP := ctx.NewPermission("team:del")
project.DELETE(":id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delProjectTeamLog).
delTeamLog := req.NewLogInfo("团队-删除信息").WithSave(true)
delPP := req.NewPermission("team:del")
team.DELETE(":id", func(c *gin.Context) {
req.NewCtxWithGin(c).WithLog(delTeamLog).
WithRequiredPermission(delPP).
Handle(m.DelTeam)
})
// 获取团队的成员信息列表
project.GET("/:id/members", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetTeamMembers)
team.GET("/:id/members", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(m.GetTeamMembers)
})
// 保存团队成员
saveProjectTeamMemLog := ctx.NewLogInfo("团队-新增成员").WithSave(true)
savePmP := ctx.NewPermission("team:member:save")
project.POST("/:id/members", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveProjectTeamMemLog).
saveTeamMemLog := req.NewLogInfo("团队-新增成员").WithSave(true)
savePmP := req.NewPermission("team:member:save")
team.POST("/:id/members", func(c *gin.Context) {
req.NewCtxWithGin(c).WithLog(saveTeamMemLog).
WithRequiredPermission(savePmP).
Handle(m.SaveTeamMember)
})
// 删除团队成员
delProjectTeamMemLog := ctx.NewLogInfo("团队-删除成员").WithSave(true)
savePmdP := ctx.NewPermission("team:member:del")
project.DELETE("/:id/members/:accountId", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delProjectTeamMemLog).
delTeamMemLog := req.NewLogInfo("团队-删除成员").WithSave(true)
savePmdP := req.NewPermission("team:member:del")
team.DELETE("/:id/members/:accountId", func(c *gin.Context) {
req.NewCtxWithGin(c).WithLog(delTeamMemLog).
WithRequiredPermission(savePmdP).
Handle(m.DelTeamMember)
})
// 获取团队关联的标签id列表
project.GET("/:id/tags", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetTagIds)
team.GET("/:id/tags", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(m.GetTagIds)
})
// 保存团队标签关联信息
saveTeamTagLog := ctx.NewLogInfo("团队-保存标签关联信息").WithSave(true)
saveTeamTagP := ctx.NewPermission("team:tag:save")
project.POST("/:id/tags", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
saveTeamTagLog := req.NewLogInfo("团队-保存标签关联信息").WithSave(true)
saveTeamTagP := req.NewPermission("team:tag:save")
team.POST("/:id/tags", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithLog(saveTeamTagLog).
WithRequiredPermission(saveTeamTagP).
Handle(m.SaveTags)

View File

@@ -352,6 +352,8 @@ CREATE TABLE `t_sys_config` (
BEGIN;
INSERT INTO `t_sys_config` VALUES (1, '是否启用登录验证码', 'UseLoginCaptcha', NULL, '1', '1: 启用、0: 不启用', '2022-08-25 22:27:17', 1, 'admin', '2022-08-26 10:26:56', 1, 'admin');
INSERT INTO `t_sys_config` VALUES (2, '是否启用水印', 'UseWartermark', NULL, '1', '1: 启用、0: 不启用', '2022-08-25 23:36:35', 1, 'admin', '2022-08-26 10:02:52', 1, 'admin');
INSERT INTO `t_sys_config` VALUES (3, '数据库查询最大结果集', 'DbQueryMaxCount', '[]', '200', '允许sql查询的最大结果集数', '2023-02-11 14:29:03', 1, 'admin', '2023-02-11 14:40:56', 1, 'admin');
INSERT INTO `t_sys_config` VALUES (4, '数据库是否记录查询SQL', 'DbSaveQuerySQL', '[]', '0', '1: 记录、0:不记录', '2023-02-11 16:07:14', 1, 'admin', '2023-02-11 16:44:17', 1, 'admin');
COMMIT;
-- ----------------------------

View File

@@ -56,7 +56,7 @@ func NotNil(data interface{}, msg string) {
}
func NotBlank(data interface{}, msg string) {
if utils.IsBlank(reflect.ValueOf(data)) {
if utils.IsBlank(data) {
panic(NewBizErr(msg))
}
}

44
server/pkg/cache/str_cache.go vendored Normal file
View File

@@ -0,0 +1,44 @@
package cache
import "mayfly-go/pkg/rediscli"
var strCache map[string]string
// 如果系统有设置redis信息则从redis获取否则本机内存获取
func GetStr(key string) string {
if rediscli.GetCli() == nil {
checkStrCache()
return strCache[key]
}
res, err := rediscli.Get(key)
if err != nil {
return ""
}
return res
}
// 如果系统有设置redis信息则使用redis存否则存于本机内存
func SetStr(key, value string) {
if rediscli.GetCli() == nil {
checkStrCache()
strCache[key] = value
return
}
rediscli.Set(key, value, 0)
}
// 删除指定key
func Del(key string) {
if rediscli.GetCli() == nil {
checkStrCache()
delete(strCache, key)
return
}
rediscli.Del(key)
}
func checkStrCache() {
if strCache == nil {
strCache = make(map[string]string)
}
}

View File

@@ -2,15 +2,25 @@ package captcha
import (
"mayfly-go/pkg/biz"
"mayfly-go/pkg/rediscli"
"time"
"github.com/mojocn/base64Captcha"
)
var store = base64Captcha.DefaultMemStore
var store base64Captcha.Store
var driver base64Captcha.Driver = base64Captcha.DefaultDriverDigit
// 生成验证码
func Generate() (string, string) {
if store == nil {
if rediscli.GetCli() != nil {
store = new(RedisStore)
} else {
store = base64Captcha.DefaultMemStore
}
}
c := base64Captcha.NewCaptcha(driver, store)
// 获取
id, b64s, err := c.Generate()
@@ -26,3 +36,34 @@ func Verify(id string, val string) bool {
// 同时清理掉这个图片
return store.Verify(id, val, true)
}
type RedisStore struct {
}
const CAPTCHA = "mayfly:captcha:"
// 实现设置captcha的方法
func (r RedisStore) Set(id string, value string) error {
//time.Minute*2有效时间2分钟
rediscli.Set(CAPTCHA+id, value, time.Minute*2)
return nil
}
// 实现获取captcha的方法
func (r RedisStore) Get(id string, clear bool) string {
key := CAPTCHA + id
val, err := rediscli.Get(key)
if err != nil {
return ""
}
if clear {
//clear为true验证通过删除这个验证码
rediscli.Del(key)
}
return val
}
// 实现验证captcha的方法
func (r RedisStore) Verify(id, answer string, clear bool) bool {
return r.Get(id, clear) == answer
}

Some files were not shown because too many files have changed in this diff Show More